Skip to content

ifratric/winafl

 
 

Repository files navigation

======
WinAFL
======

   Original AFL code written by Michal Zalewski <[email protected]>

   Windows fork written and maintained by Ivan Fratric <[email protected]>

   Copyright 2016 Google Inc. All Rights Reserved.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


1) Background
-------------

AFL is a popular fuzzing tool for coverage-guided fuzzing. The tool combines
fast target execution with clever heuristics to find new execution paths in
the target binary. It has been successfully used to find a large number of
vulnerabilities in real products. For more info about the original project,
please refer to the original documentation at

http://lcamtuf.coredump.cx/afl/

Unfortunately, the original AFL does not work on Windows due to very
*nix-specific design (e.g. instrumentation, forkserver etc). This project is
a fork of AFL that uses different instrumentation approach which works on
Windows even for black box binary fuzzing.

2) The WinAFL approach
----------------------

Instead of instrumenting the code at compilation time, WinAFL relies on dynamic
instrumentation using DynamoRIO (http://dynamorio.org/) to measure and extract
target coverage. This approach has been found to introduce an overhead about 2x
compared to the native execution speed, which is comparable to the original AFL
in binary instrumentation mode.

To improve the process startup time, WinAFL relies heavily on persistant
fuzzing mode, that is, executing multiple input samples without restarting the
target process. This is accomplished by selecting a target function (that the
user wants to fuzz) and instrumenting it so that it runs in a loop.

3) Building WinAFL
------------------

1. Download and build DynamoRio sources or download DynamoRio Windows binary
package from https://github.com/DynamoRIO/dynamorio/wiki/Downloads

2. Open Visual Studio Command Prompt (or Visual Studio x64 Win64 Command Prompt
if you want a 64-bit build). Note that you need a 64-bit winafl.dll build if
you are fuzzing 64-bit targets and vice versa.

3. Go to the directory containing the source

4. Type the following commands. Modify the -DDynamoRIO_DIR flag to point to the
location of your DynamoRIO cmake files (relative to the source directory).

For a 32-bit build:

mkdir build32
cd build32
cmake .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake
cmake --build . --config Release

For a 64-bit build:

mkdir build64
cd build64
cmake -G"Visual Studio 10 Win64" .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake
cmake --build . --config Release

4) Using WinAFL
---------------

Note: If you are using pre-built binaries you'll need to download DynamoRIO
release 6.2.0-2 from https://github.com/DynamoRIO/dynamorio/wiki/Downloads.
If you built WinAFL from source, you can use whatever version of DynamoRIO
you used to build WinAFL.

The command line for afl-fuzz on Windows is different than on Linux. Instead of

%s [ afl options ] -- [instrumentation options] --

it now looks like this

afl-fuzz [afl options] -- [instrumentation options] -- target_cmd_line

The followin afl-fuzz options are supported:

  -i dir        - input directory with test cases
  -o dir        - output directory for fuzzer findings
  -D dir        - directory containing DynamoRIO binaries (drrun, drconfig)
  -t msec       - timeout for each run
  -f file       - location read by the fuzzed program
  -M \\ -S id   - distributed mode
  -x dir        - optional fuzzer dictionary

Please refer to the original AFL documentation for more info on these flags.

The following instrumentation options are used

  -covtype         - the type of coverage being recorded. Supported options are
                     bb (basic block, default) or edge.

  -coverage_module - module for which to record coverage. Multiple module flags
                     are supported.

  -target_module   - module which contains the target function to be fuzzed.
                     Either -target_method or -target_offset need to be
                     specified together with this option.

  -target_method   - name of the method to fuzz in persistent mode. For this to
                     work either the method needs to be exported or the symbols
					 for target_module need to be available.
                     Otherwise use -target_offset instead.

  -target_offset   - offset of the method to fuzz from the start of the module.

  -fuzz_iterations - Maximum nuber of iterations for the target function to run
                     before restarting the target process.

  -nargs           - Number of arguments the fuzzed method takes. This is used
                     to save/restore the arguments between runs.

  -debug           - Debug mode. Does not try to connect to the server. Outputs
                     a log file containing loaded modules, opened files and
                     coverage infrormation.

  -logdir          - specifies in which directory the log file will be written
                     (only to be used with -debug).

  -call_convention - The default calling convention is cdecl on 32-bit x86
                     platforms and Microsoft x64 for Visual Studio 64-bit
                     applications. Possible values:
                         * fastcall: fastcall
                         * ms64: Microsoft x64 (Visual Studio)
                         * stdcall: cdecl or stdcall
                         * thiscall: thiscall

  -thread_coverage - If set, WinAFL will only collect coverage from a thread
                     that executed the target function

In general, you should perform the following steps when fuzzing a new target:

0. Make sure your target is running correctly without instrumentations.

1. Open the target binary in WinDbg and locate the function you want to fuzz.
Note the offset of the function from the start of the module. For example, if
you want to fuzz the main function and happen to have symbols around, you can
use the following windbg command:

x test!main

2. Make sure that the target is running correctly under DynamoRIO. For this
purpose you can use the standalone debug mode of WinAFL client which does not
require connecting to afl-fuzz. Make sure you use the drrun.exe and winafl.dll
version which corresponds to your target (32 vs. 64 bit).

Example command line

path\to\DynamoRIO\bin64\drrun.exe -c winafl.dll -debug
-target_module test_gdiplus.exe -target_offset 0x1270 -fuzz_iterations 10
-nargs 2 -- test_gdiplus.exe input.bmp

You should see the output corresponding to your target function being run 10
times after which the target executable will exit. A .log file should be
created in the current directory. The log file contains useful information
such as the files and modules loaded by the target as well as the dump of AFL
coverage map. In the log you should see pre_fuzz_handler and post_fuzz_handler
being run exactly 10 times as well as your input file being open in each
iteration. Note the list of loaded modules for setting the -coverage_module
flag. Note that you must use the same values for module names as seen in the
log file (not case sensitive).

3. Now you should be ready to fuzz the target. First, make sure that both
afl-fuzz.exe and winafl.dll are in the current directory. As stated earlier,
the command line for afl-fuzz on Windows is

afl-fuzz [afl options] -- [instrumentation options] -- target_cmd_line

Please refer above for the list of supported AFL and instrumentation options.

In AFL options, you must specify the DynamoRIO binaries directory via the new
-D option. You need to match the DynamoRIO and winafl.dll build (32 vs. 64 bit)
to the target binary. -t (timeout) option is mandatory for winafl as execution
time can vary significantly under instrumentation so it�s not a good idea to
rely on the auto-determined values.

You can use the same winafl options as in step 2 but remember to exclude the
-debug flag and you'll probably want to increase the iteration count.

As in afl-fuzz on Linux you can replace the input file param of the target
binary with @@

An example command line would look like

afl-fuzz.exe -i in -o out -D C:\work\winafl\DynamoRIO\bin64 -t 20000 --
-coverage_module gdiplus.dll -coverage_module WindowsCodecs.dll
-fuzz_iterations 5000 -target_module test_gdiplus.exe -target_offset 0x1270
-nargs 2 -- test_gdiplus.exe @@

Alternately, if symbols for test_gdiplus.exe are available, you can use
-target_method instead of -target_offset like so

afl-fuzz.exe -i in -o out -D C:\work\winafl\DynamoRIO\bin64 -t 20000 --
-coverage_module gdiplus.dll -coverage_module WindowsCodecs.dll
-fuzz_iterations 5000 -target_module test_gdiplus.exe -target_method main
-nargs 2 -- test_gdiplus.exe @@

That's it. Happy fuzzing! Let me know if you find any bugs.

5) How does my target run onder WinAFL
--------------------------------------

When you select a target function and fuzz the app this happens:

1. Your target runs normally until your target function is reached.
2. WinAFL starts recording coverage
3. Your target function runs until return
4. WinAFL reports coverage, rewrites the input file and patches EIP
   so that the execution jumps back to step 2
5. After your target function runs for the specified number of iterations,
   the target process is killed and restarted. Note that anything that runs
   after the target function returns is never reached.

6) How to select a target function
----------------------------------

The target function should do these things during its lifetime:

1. Open the input file. This needs to happen withing the target function so
   that you can read a new input file for each iteration as the input file is
   rewritten between target function runs).
2. Parse it (so that you can measure coverage of file parsing)
3. Close the input file. This is important because if the input file is
   not closed WinAFL won't be able to rewrite it.
4. Return normally (So that WinAFL can "catch" this return and redirect
   execution. "returning" via ExitProcess() and such won't work)

7) Corpus minimization
----------------------
WinAFL includes the windows port of afl-cmin in winafl-cmin.py. Please run
python winafl-cmin.py -h
to see the options and usage examples.

8) FAQ
------

Q: WinAFL reports timeouts while processing initial testcases.
A: You should run your target in debug mode first (-debug flag) and only
   run WinAFL once you get a message in the debug log that everything
   appears to be running normally.

Q: WinAFL runs slower than expected
A: This can commonly happen for several reasons
 - Your target function loads a dll for every iteration. This causes
   DynamoRIO to translate the same code for every iteration which causes
   slowdowns. You will be able to see this in the debug log. To
   resolve, select (or write) your target function differently.
 - Your target function does not close the input file properly, which
   causes WinAFL to kill the process in order to rewrite it. Please refer
   to 6) for what a target function should look like.

Q: Can I fuzz DLLs with WinAFL
A: Yes, if you can write a harness that loads a library and runs some
   function within. Write your target function according to 6) and for
   best performance, load the dll outside of your target function (see
   the previous question).

Q: Can I fuzz GUI apps with WinAFL
A: Yes, provided that
 - There is a target function that behaves as explained in section 6)
 - The target function is reachable without user interaction
 - The target function runs and returns without user interaction
If these conditions are not satisfied, you might need to make custom changes
to WinAFL and/or your target.

9) Special Thanks
-----------------

Special thanks to Axel "0vercl0k" Souchet of MSRC Vulnerabilities and
Mitigations Team for his contributions!

About

A fork of AFL for fuzzing Windows binaries

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C 90.7%
  • Python 7.0%
  • C++ 1.9%
  • Other 0.4%