Fuzz
To be done… @source https://lidaxian121.github.io/posts/fuzz/
Example#
// gcc test.c -o test
#include <unistd.h>
int main() {
char input[8] = {0};
read(STDIN_FILENO, input, 8);
if (input[0] == 'A' && input[1] == 'B') {
*((unsigned int *)0) = 0xdeadbeef; // crash
}
write(STDOUT_FILENO, input, 8);
return 0;
}
# fuzzer.py
import subprocess
target = './test'
inps = ['AA', 'BB', 'BA', 'AB']
for inp in inps:
try:
subprocess.run([target], input=inp.encode(), capture_output=True, check=True)
except subprocess.CalledProcessError:
print(f"bug found with input: {inp}")
>>> bug found with input: 'AB'
fuzzing process#

// gcc test.c -o test
#include <unistd.h>
#include <stdio.h>
int main() {
char input[8] = {0};
read(STDIN_FILENO, input, 8);
if (input[0] == 'A') {
puts("AAA");
if (input[1] == 'B') {
puts("BBB");
if (input[2] == 'C') {
*((unsigned int*)0) = 0xdeadbeef; // crash
}
}
}
return 0;
}
# fuzzer.py
# python3 fuzzer.py >> output.txt
import subprocess
import random
target = './test'
inps = ['A', 'B'] # corpus
count = 1
while True:
inp = inps[0] # select a seed from corpus
inp += random.choice(['A', 'B', 'C']) # mutate
del inps[0]
count += 1
try:
comp = subprocess.run([target], input=inp.encode(), capture_output=True, check=True)
if comp.stdout != b'': # has some output
inps.append(inp) # add to corpus
print(f"inp: {inp}, inps: {inps}", end='\n', flush=True)
except subprocess.CalledProcessError:
print(f"inp: {inp}, inps: {inps}", end='\n', flush=True)
print(f"bug found with input: '{inp}'")
break
if count % 100 == 0 or len(inps) == 0: # periodically reset corpus
inps = ['A', 'B']
inp: AB, inps: ['B', 'AB']
inp: BC, inps: ['AB']
inp: ABC, inps: []
bug found with input: 'ABC'
The output can be very long (a thousand lines) or very short (3 lines). WOW
The quality of a fuzzer depends on:
-
Selection of seeds: the quality of
corpus -
Mutation: the efficiency of random variation
-
Coverage: the overhead of the implementation method
AFL (American Fuzz Loop)#
Two fuzz approaches of AFL
- For open-source software, instrument it directly during compilation
- For close-source software, use QEMU to fuzz the byte code
Instrumentation (插桩)#
While ensuring the logical integrity of the program, make the program output some logs to collect the status during runtime
int test_var = 0;
// original
void b() { ...; }
void a() { ...; }
// instrumented
void b() { printf("test_var: %d\n", test_var); ...; }
void a() { printf("test_var: %d\n", test_var); ...; }
example#
int had_exec[100] = {0};
void a() {
had_exec[0] = 1;
// ...
}
void b() {
had_exec[1] = 1;
// ...
}
void c() {
had_exec[2] = 1;
// ...
}
int main() {
// ...
if (had_exec[0]) {
puts("function `a` had been called");
}
}
demo#

In addition to the target program, you also need to prepare the copus folder fuzz_in and the folder fuzz_out for storing the fuzz results. Put some random text in the former, and leave the latter empty.
// afl-gcc -g -o ./afl-test/afl-test ./afl-test/testcase.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
int vuln(char *str) {
int len = strlen(str);
// crash case 1
if(str[0] == 'A' && len == 66) {
raise(SIGSEGV);
}
// crash case 2
else if(str[0] == 'F' && len == 6) {
raise(SIGSEGV);
} else {
printf("it is good!\n");
}
return 0;
}
int main(int argc, char *argv[]) {
char buf[100]={0};
// crash case 3
gets(buf); // stack overflow vulnerability
// crash case 4
printf(buf); // format string vulnerability
vuln(buf);
return 0;
}
Use command afl-fuzz -i fuzz_in -o fuzz_out ./afl-test/afl-test to fuzz。

太黑客了。
Check the seed that caused the program to crash under fuzz_out/default/crashes
- crash case 2

- crash case 3

- crash case 3

- crash case 1

cool