Fuzzing and Robustness Testing
Rerius processes untrusted binary files from potentially hostile sources. This guide explains how to fuzz the parser and analysis engine.
Repository: https://github.com/ECLS-Studio/rerius
Why Fuzz Rerius?#
Rerius is often used to analyze malware and obfuscated binaries. A malicious analyst might feed crafted binaries to crash the tool or trigger unexpected behavior. The parser handles arbitrary byte sequences as ELF/PE structures: integer overflows in size calculations or OOB reads are the primary risk class.
As of v1.0.0, Rerius applies a three-layer fault isolation model (include/core/dax_guard.h). Each of 32 analysis passes runs independently; a structural error in one pass causes a recovery notice on stderr rather than a crash. Fuzzing is still essential to:
- Find inputs that bypass the isolation layer and cause actual crashes
- Find inputs that trigger the fault path incorrectly on valid binaries
- Measure which passes are most fragile against adversarial data
Quick Robustness Check#
Before fuzzing, verify Rerius handles obviously malformed input. With v1.0.0+, passes that encounter corrupt data print [!] pass '...' recovered from fault on stderr and continue: no crash, no hang.
# Empty file
touch /tmp/empty && ./rerius /tmp/empty
# Random bytes (not a valid ELF/PE)
dd if=/dev/urandom bs=4096 count=1 of=/tmp/random.bin 2>/dev/null
./rerius -X /tmp/random.bin < /dev/null # -X also enables interactive mode; close stdin so it exits instead of waiting at the shell prompt
# Truncated ELF header
echo -n $'\x7fELF' > /tmp/bad_elf
./rerius /tmp/bad_elf
# All zeros
dd if=/dev/zero bs=4096 count=1 of=/tmp/zeros.bin 2>/dev/null
./rerius /tmp/zeros.bin
# ELF magic + garbage section headers
python3 -c "
import struct
# ELF magic + class=64 + LE + version + OS=Linux
hdr = b'\x7fELF\x02\x01\x01\x00' + b'\x00'*8
# e_type=DYN, e_machine=x86_64, e_version=1
hdr += struct.pack('<HHI', 3, 62, 1)
# Garbage for everything else
hdr += b'\xff'*(64-len(hdr))
open('/tmp/elf_garbage.bin','wb').write(hdr)
"
./rerius -X /tmp/elf_garbage.bin < /dev/null
All of these should print error messages and exit cleanly: no segfault, no hang. Any pass that encounters a structural problem should print [!] pass '...' recovered from fault and continue. A segfault on any of these inputs is a bug: please report it.
AFL++ Fuzzing#
Important: setup.sh autodetects its own compiler and ignores an exported CC/CFLAGS_EXTRA: CC=afl-clang-fast make and make CFLAGS_EXTRA=... will silently build a normal, non-instrumented binary rather than failing loudly. Compile directly against the source list instead of going through setup.sh for fuzzing builds.
# Install AFL++
git clone https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus && make -j$(nproc) && sudo make install
# Build Rerius directly with AFL++ instrumentation (bypasses setup.sh)
cd /path/to/Rerius
afl-clang-fast -std=c99 -D_GNU_SOURCE -O1 -g \
-fsanitize=address,undefined \
-I./include -I./include/core -I./include/formats -I./include/arch -I./include/ui \
src/cli/main.c src/cli/interactive.c \
src/core/loader.c src/core/config.c src/core/plugin.c src/core/daxc.c src/core/hardening.c \
src/formats/macho.c \
src/arch/disasm.c src/arch/x86_decode.c src/arch/arm64_decode.c src/arch/riscv_decode.c \
src/analysis/analysis.c src/analysis/cfg.c src/analysis/callgraph.c src/analysis/loops.c \
src/analysis/symexec.c src/analysis/decomp.c src/analysis/correct.c src/analysis/dsa.c src/analysis/entropy.c \
src/emu/emulate.c \
src/util/sha256.c src/util/unicode.c src/util/demangle.c src/util/symbols.c \
-lm -o rerius-afl
# Create seed corpus
mkdir -p fuzz/corpus fuzz/findings
cp /bin/ls fuzz/corpus/ls_elf
cp /usr/bin/file fuzz/corpus/file_elf
# Add a small PE if available:
# cp /path/to/small.exe fuzz/corpus/small_pe
# Run AFL++
afl-fuzz -i fuzz/corpus -o fuzz/findings \
-t 5000 -m 512 \
-- ./rerius-afl -x -n @@
libFuzzer Integration#
Build a dedicated fuzz target:
cat > fuzz/fuzz_rerius.c << 'EOF'
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "dax.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < 4) return 0;
// Write to temp file (dax_load_binary takes a path)
char tmpname[] = "/tmp/rerius_fuzz_XXXXXX";
int fd = mkstemp(tmpname);
if (fd < 0) return 0;
write(fd, data, size);
close(fd);
dax_binary_t bin;
memset(&bin, 0, sizeof(bin));
if (dax_load_binary(tmpname, &bin) == 0) {
dax_opts_t opts;
memset(&opts, 0, sizeof(opts));
opts.color = 0;
// Exercise core analysis
dax_sym_load(&bin);
dax_xref_build(&bin);
// Exercise CFG builder
for (int i = 0; i < bin.nsections; i++) {
dax_section_t *sec = &bin.sections[i];
if (sec->type == SEC_TYPE_CODE &&
sec->size > 0 &&
sec->offset + sec->size <= bin.size) {
dax_cfg_build(&bin,
bin.data + sec->offset,
(size_t)sec->size,
sec->vaddr, -1);
}
}
// Exercise unicode scanner
dax_scan_unicode(&bin);
// Exercise entropy
dax_entropy_scan(&bin, &opts, fopen("/dev/null","w"));
// Exercise IVF
dax_ivf_scan(&bin, &opts, fopen("/dev/null","w"));
dax_free_binary(&bin);
}
unlink(tmpname);
return 0;
}
EOF
# Compile with libFuzzer
clang -fsanitize=fuzzer,address,undefined \
-std=c99 -D_GNU_SOURCE \
-I./include -I./include/core -I./include/formats -I./include/arch -I./include/ui \
fuzz/fuzz_rerius.c \
src/core/loader.c src/core/config.c src/core/plugin.c src/core/daxc.c src/core/hardening.c \
src/formats/macho.c \
src/arch/disasm.c src/arch/x86_decode.c src/arch/arm64_decode.c src/arch/riscv_decode.c \
src/analysis/analysis.c src/analysis/cfg.c src/analysis/callgraph.c src/analysis/loops.c \
src/analysis/symexec.c src/analysis/decomp.c src/analysis/correct.c src/analysis/dsa.c src/analysis/entropy.c \
src/emu/emulate.c \
src/util/sha256.c src/util/unicode.c src/util/demangle.c src/util/symbols.c \
-lm \
-o fuzz/fuzz_rerius
# Run libFuzzer
./fuzz/fuzz_rerius fuzz/corpus/ \
-max_total_time=3600 \
-max_len=65536 \
-jobs=4 -workers=4
AddressSanitizer Build#
For catching memory errors during normal testing, compile directly rather than through setup.sh (which ignores CFLAGS_EXTRA):
clang -std=c99 -D_GNU_SOURCE -O1 -g -fsanitize=address,undefined \
-I./include -I./include/core -I./include/formats -I./include/arch -I./include/ui \
src/cli/main.c src/cli/interactive.c \
src/core/loader.c src/core/config.c src/core/plugin.c src/core/daxc.c src/core/hardening.c \
src/formats/macho.c \
src/arch/disasm.c src/arch/x86_decode.c src/arch/arm64_decode.c src/arch/riscv_decode.c \
src/analysis/analysis.c src/analysis/cfg.c src/analysis/callgraph.c src/analysis/loops.c \
src/analysis/symexec.c src/analysis/decomp.c src/analysis/correct.c src/analysis/dsa.c src/analysis/entropy.c \
src/emu/emulate.c \
src/util/sha256.c src/util/unicode.c src/util/demangle.c src/util/symbols.c \
-lm -o rerius-asan
./rerius-asan -X ./binary < /dev/null # run normally - ASan/UBSan report any issues on exit; stdin closed since -X also enables the interactive shell
What to Fuzz#
Priority targets based on attack surface:
dax_load_binary()- parses ELF/PE headers; integer overflows in e_phoff, sh_offset, sh_sizedax_cfg_build()- decodes arbitrary byte sequences as instructionsdax_scan_unicode()- scans non-code sections byte by bytedax_ivf_scan()- linear scan of code sectionsdax_rda_section()- BFS with queue; queue overflow potential
Reporting Crashes#
If fuzzing finds a crash, follow SECURITY.md for private reporting. Include:
- The crashing input (minimized with afl-tmin or llvm-reduce)
- The ASAN/UBSAN output
- The command that triggered it
docs/FUZZING.md · Rerius v1.0.0