Fuzzing zlog v1.2.17 with AFL++

We write a very simple harness that takes the configuration file as an argument and then uses zlog init() to parse the configuration file and populate values in structures. A harness should be simple and mainly contain code that uses untrusted data from the outside world.

team
Marketing Team
  • May 23, 2025

Harness

We write a very simple harness that takes the configuration file as an argument and then uses zlog init() to parse the configuration file and populate values in structures. A harness should be simple and mainly contain code that uses untrusted data from the outside world.

#include <stdio.h> 
#include "<path-to-zlog-root-directory>/src/zlog.h" 
int main(int argc, char** argv) { 
while (__AFL_LOOP(1000)) { // using AFL persistence mode 
int rc; 
zlog_category_t *c; 
rc = zlog_init(argv[1]); 
if (rc) { 
printf("init failed\n"); 
return -1; 
} 
c = zlog_get_category("my_cat"); 
if (!c) { 
printf("get cat fail\n"); 
zlog_fini(); 
return -2; 
} 
zlog_info(c, "hello, zlog"); 
zlog_fini(); 
} 
return 0; 
} 

Patching the target

The code written above will take the configuration file and create logs according to that configuration. This could potentially create hundreds of thousands of files, significantly slowing down the fuzzing campaign. So, we need to patch the lines in the source code that write the logs to a file. Patch all the open() and fopen() calls in the rule.c file. Set all the file descriptors to -1 and the file pointers to NULL.

Target Compilation

The way we try to fuzz targets is by making copies of the target and compiling each copy of the target with a specific sanitizer or AFL++ specific option.

for i in cmplog compcov asan ubsan simple;do cp -r $(target_name) 
$(target_name)-$i;done ; CC=afl-clang-lto make -C $(target_name)-simple ; 
CC=afl-clang-lto make AFL_USE_ASAN=1 -C $(target_name)-asan ; CC=aflclang-lto make AFL_USE_UBSAN=1 -C $(target_name)-ubsan ; CC=afl-clang-lto 
make AFL_LLVM_CMPLOG=1 -C $(target_name)-cmplog ; CC=afl-clang-lto make 
AFL_LLVM_LAF_ALL=1 -C $(target_name)-compcov ; fi

Harness Compilation

After compiling the target, it's time to compile the harness. Similar to the target, we will create multiple executables for the harness, each compiled with different sanitizers or AFL++ specific options.

afl-clang-lto harness.c -o zlog-simple/zlog-simple -Lzlog-simple/src/ - 
lzlog -lpthread 
AFL_USE_ASAN=1 afl-clang-lto harness.c -o zlog-asan/zlog-asan -Lzlogasan/src/ -lzlog -lpthread 
AFL_USE_UBSAN=1 afl-clang-lto harness.c -o zlog-ubsan/zlog-ubsan -Lzlogubsan/src/ -lzlog -lpthread 
AFL_LLVM_CMPLOG=1 afl-clang-lto harness.c -o zlog-cmplog/zlog-cmplog - 
Lzlog-cmplog/src/ -lzlog -lpthread 
AFL_LLVM_LAF_ALL=1 afl-clang-lto harness.c -o zlog-compcov/zlog-compcov - 
Lzlog-compcov/src/ -lzlog -lpthread

Collecting Corpus

We have collected the input corpus from the test directory in the zlog source directory. There is a configuration for each functionality provided by the zlog library, so this is a good corpus.

Minimizing inputs

If we run the fuzzer with the inputs as they are, the fuzzer gives the following super useful warning:

[!] WARNING: Some test cases look useless. Consider using a smaller set. 
[!] WARNING: You have lots of input files; try starting small. 
 

So, now we will try to minimize the corpus and AFL provides a tool afl-cmin for that.

ChatGPTThis will minimize the corpus and place it in the cmin directory.

Note that the above command reduces the overall corpus size but not the size of individual inputs. To achieve that, AFL++ provides another useful tool called afl-tmin.

mkdir tmin 
cd cmin/ 
for i in *; do 
afl-tmin -i "$i" -o "../tmin/$i" -m none -- ../zlog-simple/zlog-simple @@ 
done

Now, we have both a minimized corpus and minimized individual files. With this setup, we can start fuzzing, and the fuzzer will no longer give warnings about trying smaller input sizes.

Checking Coverage

Before running the fuzzer, it's a good idea to check the coverage that the harness and our set of input corpus are providing. First, we need to compile the target for coverage.

mkdir zlog-coverage && cd zlog-coverage 
CFLAGS="$$CFLAGS -fprofile-arcs -ftest-coverage" make -C zlog-coverage 
 

We also need to compile the harness with the same flags

gcc harness.c -fprofile-arcs -ftest-coverage -o zlog-coverage/zlogcoverage -Lzlog-coverage/src/ -lzlog -lpthread 
 

Next, we will use gcov to create coverage data and view coverage statistics in a very presentable way using gcovr.

./zlog-coverage input_file 
# to get coverage details on a webpage use: 
gcovr –html –html-details –o coverage.html 

This is the sample coverage showed by coverage.html . Although this image is not of zlog source files, I just wanted to show how convenient gcovr is and how easy and presentable code coverage becomes.

Fuzzing

And finally, after all the setup, we can run the fuzzer. Again, I have tried running the fuzzer with many different options provided by AFL++ to maximize the chance of finding crashes.

gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -M main -- ./$(target_name)-simple/$(target_name)-simple 
@@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S asan -- ./$(target_name)-asan/$(target_name)-asan @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S ubsan -- ./$(target_name)-ubsan/$(target_name)-ubsan 
@@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S cmplog2 -l2at -c $(target_name)-cmplog/$(target_name)- 
cmplog -- ./$(target_name)-simple/$(target_name)-simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S cmplog3 -l3a -c $(target_name)-cmplog/$(target_name)- 
cmplog -- ./$(target_name)-simple/$(target_name)-simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S compcov -- ./$(target_name)-compcov/$(target_name)- 
compcov @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S binexploit -a binary -P exploit -- ./$(target_name)- 
simple/$(target_name)-simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S binexplore -a binary -P explore -- ./$(target_name)- 
simple/$(target_name)-simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S asciiexploit -a ascii -P exploit -- ./$(target_name)- 
simple/$(target_name)-simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S asciiexplore -a ascii -P explore -- ./$(target_name)- 
simple/$(target_name)-simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S genericexploit -P exploit -- ./$(target_name)- 
simple/$(target_name)-simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S genericexplore -P explore -- ./$(target_name)- 
simple/$(target_name)-simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S rare -P rare -- ./$(target_name)-simple/$(target_name)- 
simple @@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S Z -Z -- ./$(target_name)-simple/$(target_name)-simple 
@@" 
gnome-terminal --window -- bash -c "afl-fuzz -i $(input) -o $(out_afl) -m 
none -x $(dict) -S mopt -L 0 -- ./$(target_name)-simple/$(target_name)- 
simple @@" 
 

Crash Triage

Before triaging crashes, let's take a look at the directory structure of the AFL output directory:

faran@faran:~/AFL/zlog_fuzz$ ls out/ 
asan asciiexploit asciiexplore binexploit binexplore cmplog2 cmplog3 
compcov genericexploit genericexplore main mopt rare ubsan Z 
 

Each of these directories has the following structure:

faran@faran:~/AFL/zlog_fuzz/out$ ls asan 
cmdline crashes fuzz_bitmap fuzzer_setup fuzzer_stats hangs 
plot_data queue 
 

We can use the casr or crashwalk tool to generate reports for each type of crash and also find the file that caused the crash. After running the tool, we find the following input that causes a heap-based buffer overflow in zlog.

"[formats]" 
"simple = "\%m\%n"" 
"[rules]" 
"mycat.* 
$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAA" 

Command to trigger the crash:

./zlog-asan/zlog-asan out/asan/crashes/crash-file 
 

The above command will produce a crash report specifying exactly where the crash occurred.

faran@faran:~/AFL/zlog_fuzz/out/asan/crashes$ ../../../zlog-asan/zlog-asan 
id\:000026\,sig\:06\,src\:000567\,time\:15033214\,execs\:2330122\,op\:havo 
c\,rep\:10
================================================================= 
==2086524==ERROR: AddressSanitizer: heap-buffer-overflow on address 
0x52500002a1c0 at pc 0x5555555f2340 bp 0x7fffffffd6e0 sp 0x7fffffffce70 
WRITE of size 4086 at 0x52500002a1c0 thread T0 
 #0 0x5555555f233f in scanf_common(void*, int, bool, char const*, 
__va_list_tag*) crtstuff.c 
 #1 0x5555555f33cf in __isoc99_sscanf (/home/faran/AFL/zlog_fuzz/zlogasan/zlog-asan+0x9f3cf) (BuildId: 6c2aa937314eff4f) 
 #2 0x5555556cbbb8 in zlog_rule_new /home/faran/AFL/zlog_fuzz/zlog- 
asan/src/rule.c:877:3 
 #3 0x5555556b4a77 in zlog_conf_parse_line 
/home/faran/AFL/zlog_fuzz/zlog-asan/src/conf.c:744:12 
 #4 0x5555556b21dd in zlog_conf_build_with_file 
/home/faran/AFL/zlog_fuzz/zlog-asan/src/conf.c:549:8 
 #5 0x5555556b21dd in zlog_conf_new /home/faran/AFL/zlog_fuzz/zlogasan/src/conf.c:190:7 
 #6 0x5555556a9020 in zlog_init_inner /home/faran/AFL/zlog_fuzz/zlogasan/src/zlog.c:138:18 
 #7 0x5555556a8e34 in zlog_init /home/faran/AFL/zlog_fuzz/zlogasan/src/zlog.c:181:6 
 #8 0x5555556d912c in main /home/faran/AFL/zlog_fuzz/harness.c:9:8 
 #9 0x7ffff7c23a8f in __libc_start_call_main 
csu/../sysdeps/nptl/libc_start_call_main.h:58:16 
 #10 0x7ffff7c23b48 in __libc_start_main csu/../csu/libc-start.c:360:3 
 #11 0x5555555cff14 in _start (/home/faran/AFL/zlog_fuzz/zlogasan/zlog-asan+0x7bf14) (BuildId: 6c2aa937314eff4f) 
0x52500002a1c0 is located 0 bytes after 8384-byte region 
[0x525000028100,0x52500002a1c0)
allocated by thread T0 here: 
 #0 0x55555566c4a8 in calloc (/home/faran/AFL/zlog_fuzz/zlog-asan/zlogasan+0x1184a8) (BuildId: 6c2aa937314eff4f) 
 #1 0x5555556ca9fb in zlog_rule_new /home/faran/AFL/zlog_fuzz/zlogasan/src/rule.c:600:11 
 #2 0x5555556b4a77 in zlog_conf_parse_line 
/home/faran/AFL/zlog_fuzz/zlog-asan/src/conf.c:744:12 
 #3 0x5555556b21dd in zlog_conf_build_with_file 
/home/faran/AFL/zlog_fuzz/zlog-asan/src/conf.c:549:8 
 #4 0x5555556b21dd in zlog_conf_new /home/faran/AFL/zlog_fuzz/zlogasan/src/conf.c:190:7 
 #5 0x5555556a9020 in zlog_init_inner 
 /home/faran/AFL/zlog_fuzz/zlogasan/src/zlog.c:138:18 
SUMMARY: AddressSanitizer: heap-buffer-overflow crtstuff.c in 
scanf_common(void*, int, bool, char const*, __va_list_tag*) 
Shadow bytes around the buggy address: 
 0x525000029f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
 0x525000029f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
 0x52500002a000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
 0x52500002a080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
 0x52500002a100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
=>0x52500002a180: 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa fa 
 0x52500002a200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
 0x52500002a280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
 0x52500002a300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
 0x52500002a380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
 0x52500002a400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Now for the root cause analysis and exploitation, they have been discussed in
detail in the
following blog .
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==2086524==ABORTING

Now for the root cause analysis and exploitation, they have been discussed in detail in thefollowing blog.

Credits

Author: Faran Abdullah