radamsa: The Black-Box Fuzzer That Doesn't Need Your Source, Your Symbols, or a Weekend of Setup

2026-09-13

Everybody's heard of AFL. Fewer people know radamsa, and that's a shame. AFL is a coverage-guided fuzzer — instrument the binary, mutate inputs to reach new code paths. Powerful, but it wants a build you control. Sometimes you just want to answer: does this parser survive 10,000 slightly-broken variations of a valid input, tonight?

That's radamsa. Reads samples, emits mutants. Language-independent, format-independent, no instrumentation, no build step. If you have a valid input and a program that reads bytes, you can fuzz it. Written by OUSPG at the University of Oulu — the same folks who've been fuzzing since before it was fashionable — it found bugs in libraries most people had forgotten existed.

The basics

# Mutate stdin
echo "hello world" | radamsa
# hello worldhello wor��ld

# 100 mutants to disk, named by counter
radamsa -n 100 -o "fuzz-%n.pdf" original.pdf

# Combine multiple seed samples
radamsa a.xml b.xml c.xml > frankenstein.xml

The canonical fuzz loop

Pipe mutants at the target until something segfaults:

ulimit -c unlimited
mkdir -p crashes
n=0
while :; do
  n=$((n+1))
  radamsa sample.json | ./parser > /dev/null 2>&1
  rc=$?
  if [ $rc -gt 127 ]; then
    radamsa -s $n sample.json > "crashes/crash-$n.json"
    echo "signal $((rc-128)) at iter $n"
  fi
done

Exit status > 127 means killed by signal. Save the seed (-s $n) instead of the mutant and you can regenerate the exact bytes six weeks later — reproducibility that "just save the crashing input" doesn't give you when the sample set changes.

Fuzzing over the network

Radamsa's -o speaks TCP. Serve infinite mutants of a captured packet on a port:

radamsa -o :12345 -n inf sample.pkt
# ... then hammer localhost:12345 from the target

Or reverse it — -o host:port connects out and blasts a listening daemon. No wrapper script, no netcat gymnastics.

Structural mutation is the secret sauce

Radamsa isn't a byte-flipper. It parses samples as trees where it can (parens, brackets, XML tags, JSON) and performs swaps, repetitions, and truncations at structural boundaries. That's why it hits recursion limits, unbalanced-delimiter bugs, and encoding edge cases that pure random mutation misses. List the mutators it used:

radamsa -v -s 42 -n 3 sample.json
# ... "-p od -m bf,bp,tr2 ..."  ← printed to stderr, reproducible

Where it beats coverage-guided fuzzers

AFL/libFuzzer want an instrumented binary. Radamsa doesn't care what's on the other end of the pipe. It fuzzes:

Anywhere bytes go in, radamsa fuzzes.

The honest trade-off

No coverage feedback. It won't discover that flipping byte 42 unlocks a new branch — it's blind. For deep exploration of open-source code, reach for AFL++ or libFuzzer. For "here's a valid input, find me a crash before lunch on a binary I can't rebuild," radamsa gets you fuzzing in a single command instead of a weekend of build config and harness code.

Install: apt install radamsa, brew install radamsa, or clone gitlab.com/akihe/radamsa and make. It's ~3000 lines of Owl Lisp and compiles to a single static binary.

Key Takeaway: radamsa fuzzes anything that reads bytes — no source, no symbols, no instrumentation — turning "I wonder if this parser is robust" into a one-liner shell loop.

All newsletters