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#

img

// 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:

  1. Selection of seeds: the quality of corpus

  2. Mutation: the efficiency of random variation

  3. Coverage: the overhead of the implementation method


AFL (American Fuzz Loop)#

Two fuzz approaches of AFL

  1. For open-source software, instrument it directly during compilation
  2. 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#

image-20251004171318202

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。

image-20251004171859706

太黑客了。

Check the seed that caused the program to crash under fuzz_out/default/crashes

  1. crash case 2

image-20251004173249791

  1. crash case 3

image-20251004173302271

  1. crash case 3

image-20251004172127854

  1. crash case 1

image-20251004173331488

cool