summary refs log tree commit diff
path: root/README.md
blob: 2119d58f29d68563e54259b6d1884325fee3f0c6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# AFL Dyninst

AFL Dyninst uses Dyninst to insert AFL instrumentations into binaries
for binary fuzzing.

The tool has two parts. The instrumentation tool and the instrumentation 
library. Instrumentation library has an initialization callback and basic 
block callback functions which are designed to emulate what AFL is doing
with afl-gcc/afl-g++/afl-as. 

Instrumentation tool (afl-dyninst) instruments the supplied binary by
inserting callbacks for each basic block and an initialization
callback either at `_init` or at specified entry point.

## Installation

As its name suggest, AFL Dyninst requires [AFL++] 4.22 or above
and [Dyninst] 10 or above at running time.  To build afl-dyninst,
you also need make(1p), m4(1p) and a C++11 compiler.

Since `afl-dyninst` does not relink the output binary,
path to Dyninst library needs to be passed to the build tools
to be injected into wrapper scripts:

    make DYNINST_LIB=$dyninst_prefix/lib PREFIX=$afl_dyninst_prefix install

## Commandline options
```
Usage: afl-dyninst -vxD -i binary -o binary -e address -E address -s number -S funcname -I funcname -m size
   -i: input binary program
   -o: output binary program
   -r: runtime library to instrument (path to, repeat for more than one)
   -e: entry point address to patch (required for stripped binaries)
   -E: exit point - force exit(0) at this address (repeat for more than one)
   -s: number of initial basic blocks to skip in binary
   -m: minimum size of a basic bock to instrument (default: 10)
   -I: only instrument this function and nothing else (repeat for more than one)
   -S: do not instrument this function (repeat for more than one)
   -D: instrument only a simple fork server and also forced exit functions
   -x: experimental performance mode (~25-50% speed improvement)
   -v: verbose output
```

Switch -e is used to manualy specify the entry point where initialization
callback is to be inserted. For unstipped binaries, afl-dyninst defaults 
to using `_init` of the binary as an entry point. In case of stripped binaries
this option is required and is best set to the address of main which 
can easily be determined by disassembling the binary and looking for an 
argument to `__libc_start_main`.

Switch -E is used to specify addresses that should force a clean exit
when reached. This can speed up the fuzzing tremendously.

Switch -s instructs afl-dyninst to skip the first NUMBER of basic blocks. 
Currently, it is used to work around a bug in Dyninst but doubles as an
optimization option, as skipping the basic blocks of the initialization
routines makes things run faster.  If the instrumented binary is crashing by
itself, try skiping a number of blocks.

Switch -r allows you to specify a path to a library that is loaded
via dlopen() at runtime. Instrumented runtime libraries will be 
written to the same location with a ".ins" suffix as not to overwrite
the original ones. Make sure to backup the originals and then rename the
instrumented ones to original name. 

Switch -m allows you to only instrument basic blocks of a minimum size - the
default minimum size is 1

Switch -S allows you to not instrument specific functions.
This options is mainly to hunt down bugs in dyninst.
Can be specified multiple times.

Switch -I specified to only instrument specific functions.
This option is amazing with large and threaded targets.
Can be specified multiple times.

Switch -D installs the afl fork server and forced exit functions but no
basic block instrumentation. That would serve no purpose - unless there are
other tools that need that: 
* [afl-dynamorio]
* [afl-pin]

Switch -x enables an experimental performance mode (+25-50% speed). Just try it
and if the target crashes too often, instrument again without this. Should not
crash though.

## Example of instrumenting a target binary

```
$ ./afl-dyninst -i ./unrar -o ./rar_ins -e 0x4034c0 -x
Skipping library: libafldyninst.so
Instrumenting module: DEFAULT_MODULE
Inserting init callback.
Saving the instrumented binary to ./unrar_ins...
All done! Happy fuzzing!
```

Here we are instrumenting the rar binary with entrypoint at 0x4034c0
(manually found address of main), skipping the first 10 basic blocks 
and outputing to `unrar_ins`.

## Running AFL on the instrumented binary

NOTE: The instrumentation library "libDyninst.so" must be available in the current working
directory or LD_LIBRARY_PATH as that is where the instrumented binary will be looking for it.

Since AFL checks if the binary has been instrumented by afl-gcc, the
AFL_SKIP_BIN_CHECK environment variable needs to be set.
No modifications to AFL itself is needed. 
```
$ export AFL_SKIP_BIN_CHECK=1
```
Then, AFL can be run as usual:
```
$ afl-fuzz -i testcases/archives/common/gzip/ -o test_gzip -- ./gzip_ins -d -c 
```

You can also use the afl-dyninst-env helper script
which sets the required environment variables for you.

    afl-dyninst-env afl-fuzz -i testcases/archives/common/gzip/ -o test_gzip -- ./gzip_ins -d -c

## Problems

After instrumenting the target binary always check if it works.
Dyninst is making big changes to the code, and hence more often than not
things are not working anymore.

Problem 1: The binary does not work (crashes or hangs)

Solution: increase the -m parameter. -m 8 is the minimum recommended, on some
          targets -m 16 is required etc.
          You can also try to remove the -x performance enhancer


Problem 2: Basically every fuzzing test case is reported as crash although it
           does not when running it from the command line

Solution: This happens if the target is using throw/catch, and dyninst's
          modification result in that the caught exception is not resetted and
          hence abort() is triggered.
          No solution to this issue is known yet.
          Binary editing the target binary
          to perform `_exit(0)` would help though.

## Copying

AFL Dyninst is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

AFL Dyninst was originally developed as part of a project
with the Cisco Talos VULNDEV Team and released under the Apache License,
version 2.0.

[AFL++]: https://github.com/AFLplusplus/AFLplusplus
[Dyninst]: https://github.com/dyninst/dyninst
[afl-dynamorio]: https://github.com/vanhauser-thc/afl-dynamorio
[afl-pin]: https://github.com/vanhauser-thc/afl-pin