Summary

I have recently been playing with AFL by Lcamtuf, a high performance fuzzer that is exceedingly efficient at finding problems in code when you either have or dont have the source code.

The most efficient way to use AFL is to recompile your target application using the modified version of GCC, this allows AFL to pick up on hangs and crashes. I wanted to write a little piece on using the fuzzer from installing, choosing test cases, finding a crash in a application to following it through to see if we can do anything "Evil" with it. I chose to have a look at LibreSSL as the codebase is huge and it has multiple places where complex user input is used. AFL has been used to fuz many many projects already, so dont be surprised if you find a crash it may have already been reported! To fuzz a project you need test cases, in this post the test cases will be a Public Certificate, a Certificate Signing Request and a Private Key.

Installing and linking your target

AFL is available pre-compiled on Arch, but anything else all you have to do is grab the source, this is easy enough so we grab it and compile.

wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
tar -xvf afl-latest.tgz
cd afl*
make
# If you want to move the executables to your path you can also do make install
# make install; 

After this you need to chose something to fuzz. In this mini guide I chose LibreSSL as why not, I was curious on if AFL would be able to create a valid cert that validates in some "weird" way, it also gives lots of mangled SSL certs to throw at Internet Explorer. So lets grab the source and compile it. When installing we change the install DIR as we don't want the rebuilt package effecting the local systems OpenSSL.

wget http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.1.6.tar.gz
tar -xvf libressl-2.1.6.tar.gz
cd libressl-2.1.6/
CC=~/afl/afl-1.57b/afl-gcc ./configure
make check
mkdir /root/testing
export DESTDIR=/root/testing/
make install

Making Test Cases and Fuzzing

Now you have it compiled you need to create some test cases to run against LibreSSL. Here we create a Public certificate, Private Key, and Certificate request, and as by suggestion of the Readme we keep the size of them to a minimum. After generating these we feed them into afl and see what it finds. We move each of the user generated files into a in folder for afl to process, and also create a out folder for afl to place its findings. The -- specifies bash to stop processing command flags and the @@ is afl's placeholder where it inserts the test cases. If all goes well AFL will spin up and start churning out other test cases to run through. Before we are able to start AFL we need to export the LD_LIBRARY_PATH environment variable so that LibreSSL uses the compiled library rather than the system ones

export LD_LIBRARY_PATH="/root/testing/usr/local/lib"
/root/testing/usr/local/bin/openssl req -x509 -nodes -days 365 -newkey rsa:512 -keyout privateKey.key -out certificate.crt -out CSR.csr
screen
mkdir incrt inkey incsr outcrt outkey outcsr
~/afl/afl-1.57b/afl-fuzz -i inkey -o outkey -- /root/testing/usr/local/bin/openssl x509 -in @@ -text -noout
~/afl/afl-1.57b/afl-fuzz -i incsr -o outcsr -- /root/testing/usr/local/bin/openssl x509 -in @@ -text -noout
~/afl/afl-1.57b/afl-fuzz -i incrt -o outcrt -- /root/testing/usr/local/bin/openssl x509 -in @@ -text -noout

Then after a while your hoping for some crashes, the files will be stored in the out directory we specified before.

Exploring Crashes

~/afl/afl-1.57b/afl-fuzz -i incrash -o outcrash -- /root/testing/usr/local/bin/openssl x509 -in @@ -text -noout
                       american fuzzy lop 1.57b (openssl)

┌─ process timing ─────────────────────────────────────┬─ overall results ─────┐
│        run time : 3 days, 1 hrs, 27 min, 38 sec      │  cycles done : 0      │
│   last new path : 0 days, 0 hrs, 15 min, 11 sec      │  total paths : 1830   │
│ last uniq crash : 0 days, 0 hrs, 18 min, 41 sec      │ uniq crashes : 108    │
│  last uniq hang : 0 days, 3 hrs, 54 min, 39 sec      │   uniq hangs : 18     │
├─ cycle progress ────────────────────┬─ map coverage ─┴───────────────────────┤
│  now processing : 1708 (93.33%)     │    map density : 5453 (8.32%)          │
│ paths timed out : 0 (0.00%)         │ count coverage : 3.07 bits/tuple       │
├─ stage progress ────────────────────┼─ findings in depth ────────────────────┤
│  now trying : bitflip 1/1           │ favored paths : 318 (17.38%)           │
│ stage execs : 854/4176 (20.45%)     │  new edges on : 511 (27.92%)           │
│ total execs : 40.1M                 │ total crashes : 71.0k (108 unique)     │
│  exec speed : 138.9/sec             │   total hangs : 249 (18 unique)        │
├─ fuzzing strategy yields ───────────┴───────────────┬─ path geometry ────────┤
│   bit flips : 714/1.41M, 100/1.41M, 100/1.41M       │    levels : 6          │
│  byte flips : 2/175k, 8/170k, 4/172k                │   pending : 1424       │
│ arithmetics : 312/9.30M, 2/247k, 0/14.1k            │  pend fav : 41         │
│  known ints : 14/1.08M, 2/6.31M, 10/8.60M           │ own finds : 1829       │
│  dictionary : 0/0, 0/0, 174/4.97M                   │  imported : n/a        │
│       havoc : 439/4.70M, 0/0                        │  variable : 1023       │
│        trim : 12.33%/86.6k, 3.65%                   ├────────────────────────┘
└─────────────────────────────────────────────────────┘             [cpu: 76%]

[root@fw ~]# ls /root/testing/usr/local/outkey/crashes/
id:000000,sig:11,src:000000,op:flip1,pos:32           id:000028,sig:11,src:000065,op:flip1,pos:59            id:000056,sig:11,src:000748,op:flip2,pos:82            id:000084,sig:11,src:001255,op:flip1,pos:57
id:000001,sig:11,src:000000,op:flip1,pos:58           id:000029,sig:11,src:000065,op:flip1,pos:59            id:000057,sig:11,src:000748,op:flip2,pos:470           id:000085,sig:11,src:001255,op:flip1,pos:61

After we have a crash we need to generate other test cases around this crash, this allows us to see if we have any control over registers. (Really at this point we should chuck it through GDB to see what the crash is but I'm skipping this till later). It just happens that most of these are all the same crash after analysis so copy any across on to the folder then to start the fuzzing we pass the -C flag on afl. What this does is keep the program in a crashing state while creating other test cases with cause a similar crash. We create new folders and start the fuzzing again.

                      peruvian were-rabbit 1.57b (openssl)

┌─ process timing ─────────────────────────────────────┬─ overall results ─────┐
│        run time : 2 days, 2 hrs, 20 min, 10 sec      │  cycles done : 0      │
│   last new path : 0 days, 0 hrs, 4 min, 13 sec       │  total paths : 564    │
│ last uniq crash : 0 days, 1 hrs, 35 min, 0 sec       │ uniq crashes : 235    │
│  last uniq hang : 0 days, 20 hrs, 50 min, 36 sec     │   uniq hangs : 5      │
├─ cycle progress ────────────────────┬─ map coverage ─┴───────────────────────┤
│  now processing : 559 (99.11%)      │    map density : 2190 (3.34%)          │
│ paths timed out : 0 (0.00%)         │ count coverage : 1.63 bits/tuple       │
├─ stage progress ────────────────────┼─ findings in depth ────────────────────┤
│  now trying : havoc                 │ favored paths : 145 (25.71%)           │
│ stage execs : 16.0k/30.0k (53.21%)  │  new edges on : 228 (40.43%)           │
│ total execs : 28.1M                 │   new crashes : 7.59M (235 unique)     │
│  exec speed : 155.4/sec             │   total hangs : 373 (5 unique)         │
├─ fuzzing strategy yields ───────────┴───────────────┬─ path geometry ────────┤
│   bit flips : 169/938k, 41/938k, 41/938k            │    levels : 15         │
│  byte flips : 0/117k, 11/114k, 19/115k              │   pending : 300        │
│ arithmetics : 94/6.19M, 0/178k, 0/3083              │  pend fav : 5          │
│  known ints : 20/715k, 7/4.22M, 18/5.79M            │ own finds : 563        │
│  dictionary : 0/0, 0/0, 53/897k                     │  imported : n/a        │
│       havoc : 318/6.87M, 0/0                        │  variable : 0          │
│        trim : 2.49%/56.1k, 3.84%                    ├────────────────────────┘
└─────────────────────────────────────────────────────┘             [cpu: 78%]

Once we have left it generating test cases for a while we can chuck them through GDB to get more information about the crash.

export LD_LIBRARY_PATH="./lib"
gdb ./bin/openssl
(gdb) set args rsa -in "./crashout/crashes/id:000049,sig:11,src:000000,op:havoc,rep:2" -check
(gdb) r
Starting program: /root/testing/usr/local/bin/openssl rsa -in "./crashout/crashes/id:000049,sig:11,src:000000,op:havoc,rep:2" -check
Missing separate debuginfos, use: debuginfo-install glibc-2.20-8.fc21.x86_64

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff74b4117 in pkey_cb (operation=2, pval=0x7fffffffd938, it=<optimized out>, exarg=0x0) at asn1/p8_pkey.c:71
71                      if (key->pkey->value.octet_string)
(gdb) i r
rax            0x74a8c0 7645376
rbx            0x7ffff7ab4e40   140737348587072
rcx            0x0      0
rdx            0x0      0
rsi            0x7fffffffd938   140737488345400
rdi            0x2      2
rbp            0x7fffffffd938   0x7fffffffd938
rsp            0x7fffffffd838   0x7fffffffd838
r8             0x74a610 7644688
r9             0xfffffffffffffffc       -4
r10            0x748bb0 7637936
r11            0xd000000        218103808
r12            0x7ffff74b4050   140737342292048
r13            0x2      2
r14            0x2      2
r15            0x7ffff7ac6410   140737348658192
rip            0x7ffff74b4117   0x7ffff74b4117 <pkey_cb+199>
eflags         0x10246  [ PF ZF IF RF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0

After throwing a few test cases through GDB we can see that we don't have control over any registers (Boo) so there isn't much evil to be done here. Still crashes are worth reporting. In this case the bug had already been reported Here, Oh well! Also, if we look at the function that is being called, all that is happening is explicit BZero, nothing interesting even if we go through the if statement.

As a little amusing side fact I found another blog with a similar piece and very similar title while writing this one, I guess great minds think alike.

Further Reading

If youd like to know more about afl its worth checking the AFL Homepage. There is also a pretty active Google Group where you can ask any questions or just follow it to keep upto date with whats going on with the project. AFL has the ability to fuz programs where the source is unavialable using either Qemu mode or the new Afl-dyninst. I may do a bit arounf these next! I wonder if it could fuzz Internet Explorer?