googleprojectzero / winafl

A fork of AFL for fuzzing Windows binaries
Apache License 2.0
2.35k stars 533 forks source link

How to fuzz an executable with WinAFL? #213

Open hongxuchen opened 5 years ago

hongxuchen commented 5 years ago

Sorry this is a question rather than an issue, I reposted it here since there were no replies on afl-users list (https://groups.google.com/forum/#!topic/afl-users/x-GbNuqnvEM).

I was trying WinAFL and followed the tutorials from README.md . I had a simple program from_file.exe (zipped source code from_file.c attached below) which was built with "cl.exe from_file.c" and a mingw command file from_file.exe tells that ".\from_file.exe: PE32 executable (console) Intel 80386, for MS Windows". from_file.c.zip

And I confirmed that when executing C:\Users\hchen017\tools\DynamoRIO-7.1.0-1\bin32\drrun.exe -- from_file.exe from_file.exe, it can correctly output the expected results "CASE_13 [nread>16] common".

However, when I tried to fuzz it with any of the following commands (where "inputs" are the input directory with a simple seed file and "outputs" is an empty directory.), it always reports the "usage" message, which indicates that these command options are incorrect.

C:\Users\hchen017\tools\winafl\bin64\afl-fuzz.exe -i inputs -o outputs -t 4000 -- -D C:\Users\hchen017\tools\DynamoRIO-7.1.0-1\bin32 -nargs 1  -- C:\Users\hchen017\tools\targets\simple\from_file.exe @@
C:\Users\hchen017\tools\winafl\bin64\afl-fuzz.exe -i inputs -o outputs -t 4000 -D C:\Users\hchen017\tools\DynamoRIO-7.1.0-1\bin32 -nargs 1  -- C:\Users\hchen017\tools\targets\simple\from_file.exe @@
C:\Users\hchen017\tools\winafl\bin64\afl-fuzz.exe -i inputs -o outputs -t 4000 -- -D C:\Users\hchen017\tools\DynamoRIO-7.1.0-1\bin32  -- C:\Users\hchen017\tools\targets\simple\from_file.exe @@
C:\Users\hchen017\tools\winafl\bin64\afl-fuzz.exe -i inputs -o outputs -t 4000 -D C:\Users\hchen017\tools\DynamoRIO-7.1.0-1\bin32  -- C:\Users\hchen017\tools\targets\simple\from_file.exe @@

And by adding some "printf"s in source code I noticed that https://github.com/googleprojectzero/winafl/blob/master/afl-fuzz.c#L7595 the value of "i == argc" is true.

So where I've got wrong?

ifratric commented 5 years ago

Yep, that doesn't look like the correct usage.

As stated in the README, the usage pattern for WinAFL is afl-fuzz [afl options] -- [instrumentation options] -- target_cmd_line

In your case, note that -D falls under afl options, not instrumentation options. You are also missing multiple required instrumentation options such as the target module, method offset etc. Additionally, when using WinAFL for the first time on a new target, always run the debug mode first and only proceed with running afl-fuzz once the debug mode doesn't detect any errors.

Please see more about the usage and the examples in https://github.com/googleprojectzero/winafl/blob/master/README.md and https://github.com/googleprojectzero/winafl/blob/master/readme_dr.md

hongxuchen commented 5 years ago

Thanks, @ifratric ! I followed your suggestions and modified the from_file.c (add a function myfunc as the fuzzing entry, see below) and compiled it with x64 cl.exe for a 64bit from_file.exe. Then I built with the following options:

C:\Users\hchen017\tools\winafl\build64\bin\Release\afl-fuzz.exe  # 64bit afl-fuzz
-i inputs # input directory
-o outputs # output directory
-t 4000+  # 4000ms, do not report as timeout during dryrun
-D C:\Users\hchen017\tools\DynamoRIO-7.1.0-1\bin64    # path to drrun.exe
--  # switch for instrumentation options
-coverage_module C:\Users\hchen017\tools\targets\simple\from_file.exe # record all coverage
-target_module C:\Users\hchen017\tools\targets\simple\from_file.exe # target on from_file.exe
-target_method myfunc  # fuzzing function is `myfunc`
-fuzz_iterations 40000
-nargs 1  # fuzzing function `myfunc` contains 1 argument (`name`)
--  # switch for target command line
C:\Users\hchen017\tools\targets\simple\from_file.exe # fuzzed executable
@@  # placeholder for input file

I also put the generated winafl.dll inside C:\Users\hchen017\tools\targets\simple (same directory as from_file.exe). Now winafl passes the previous "usage" check, however it reports these messages:

[+] You have 12 CPU cores and 0 runnable tasks (utilization: 0%).
[+] Try parallel jobs - see docs\parallel_fuzzing.txt.
[*] Checking CPU core loadout...
[+] Found a free CPU core, binding to #0.
[+] Process affinity is set to 1.

[*] Setting up output directories...
[+] Output directory exists but deemed OK to reuse.
[*] Deleting old session data...
[+] Output dir cleanup successful.
[*] Scanning 'inputs'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Attempting dry run with 'id_000000'...
[!] WARNING: Test case results in a timeout (skipping)

[-] PROGRAM ABORT : All test cases time out, giving up!
         Location : perform_dry_run(), C:\Users\hchen017\tools\winafl\afl-fuzz.c:3070

0 processes nudged
nudge operation failed, verify permissions and parameters.

Literally it means that there is a timeout during dry run. Since the input file inside input should not be timeout at all, something must have been wrong with the above CLI options. Can you help tell what's wrong?

// from_file.c
#include<stdio.h>
#include<stdint.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>

#define CHUNK 1024

int j;

int main(int argc, char** argv) {
    if (argc != 2) {
        fprintf(stderr, "usage: %s file", argv[0]);
        exit(1);
    }
    myfunc(argv[1]);
}

int myfunc(char *fname) { // my entry function for fuzzing
    char buf[CHUNK];

    FILE *file = fopen(fname, "r");
    if (!file) {
        perror("fopen");
        exit(2);
    }

    size_t nread = fread(buf, 1, sizeof buf, file);
    if (nread > 0) {
        if (nread > 16) {
            switch(buf[8]) {
                case 0x41: // 'A'
                    printf("CASE_01 [nread>16] crashing: pointer to illegal\n");
                    int *b = 0x123;
                    *b = 99;
                    break;
                case 0x61: // 'a'
                    printf("CASE_02 [nread>16] timeout\n");
                    break;
                case 0x2A: // '*'
                    printf("CASE_03 [nread>16] assert_failure: \n");
                    assert(0);
                    break;
                case 0x3F: // '?'
                    if (buf[7] == 0x3C) { // '<'
                        uint8_t j = 0, k = 0;
                        uint8_t limit = buf[7];
                        for (j=0; j < limit; j++) {
                            k++;
                        }
                        printf("CASE_04 [nread>16] loop: k=%d\n", k);
                        exit(0);
                    } else if (buf[7] > 0x64) {
                        printf("CASE_04 [nread>16] exit 6\n");
                        exit(6);
                    } else {
                        if ((buf[6] + buf[7]) == 0x64) {
                            printf("CASE_05 [nread>16] equality condition\n");
                            exit(0);
                        } else {
                            printf("CASE_06 [nread>16] inequality condition\n");
                            exit(0);
                        }
                    }
                    break;
                case 0x77:
                    if (strcmp(buf+16, "american fuzzy lop") == 0) {
                        printf("CASE_07 Oh no, I've been caught, excellent!\n");
                        abort();
                    } else if (strcmp(buf+16, "afl") == 0) {
                        printf("CASE_08 oops, I surrender\n");
                        abort();
                    } else  {
                        printf("CASE_09 nothing found\n");
                    }
                    break;
                case 0x7F:
                    if (buf[2048]) {
                        printf("CASE_10 can you catch me?\n");
                    } else {
                        printf("CASE_11 and me?\n");
                    }
                    ;
                    printf("CASE_12 so you've survived others, Here is a bonus value: %d\n", 12 / (buf[8] - 0x7F));
                    break;
                default:
                    printf("CASE_13 [nread>16] common\n");
                    break;
            }
        } else {
            switch(buf[8]) {
                case 0x41: // 'A'
                    printf("CASE_14 [nread<=16] crashing: pointer to illegal\n");
                    int *b = 0x123;
                    *b = 99;
                    break;
                case 0x61: // 'a'
                    printf("CASE_15 [nread<=16] timeout\n");
                    break;
                case 0x2A: // '*'
                    printf("CASE_16 [nread<=16] assert_failure: \n");
                    assert(0);
                    break;
                case 0x3F: // '?'
                    if (buf[7] == 0x3C) { // '<'
                        uint8_t j = 0, k = 0;
                        uint8_t limit = buf[7];
                        for (j=0; j < limit; j++) {
                            k++;
                        }
                        printf("CASE_17 [nread<=16] loop: k=%d\n", k);
                        exit(0);
                    } else if (buf[7] > 0x64) {
                        printf("CASE_18 [nread<=16] exit 6\n");
                        exit(6);
                    } else {
                        if ((buf[6] + buf[7]) == 0x64) {
                            printf("CASE_19 [nread<=16] equality condition\n");
                            exit(0);
                        } else {
                            printf("CASE_20 [nread<=16] inequality condition\n");
                            exit(0);
                        }
                    }
                    break;
                default:
                    printf("CASE_21 [nread<16] common\n");
                    break;
            }
        }
    }
    return 0;
}
ifratric commented 5 years ago

Once again, always run the debug mode first (see the README for details) and only proceed with running afl-fuzz once the debug mode doesn't detect any errors. The errors you are getting are exactly what the debug mode is supposed to help you with.

Additionally, note that coverage_module and target_module flags take the module name (usually the same as filename) and not the full path.

hongxuchen commented 5 years ago

@ifratric Got it, thanks! Will follow your advice and try :smile:

hxm-cpp commented 11 months ago

that debug thing wont help you in a shet. winafl have serious troubles when it tries to start fuzzing from main