Fuzzing is something that everyone has heard about, but isn’t used nearly as often as it should be. I think one of the reasons is that it has a reputation for being harder than it really is. I want to use an example reversing challenge from the machine “Smasher” on Hack The Box to demonstrate just how easy it is. You can find the code I use in this example by attempting the box, or in one of the numerous writeups across the internet.
One of the things I like to do on binary exploitation challenges like Smasher’s is to start an American Fuzzy Lop (AFL) process while I manually review the source and/or assembly. This not only serves to give me helpful guidance on which parts of the application to focus on in large-scale applications, but it also catches edge cases that I would have never though of. This is exactly what I did on Smasher.
To start, AFL works by using the signals emitted from a process as it executes to determine how it should
react. For example, if it detects a SIGSTOP
, it assumes that fuzzing iteration completed without error, whereas
a SIGSEV
would be reported in the findings as a crash. The simplest way to do this is to compile the target application with an AFL-based compiler, where possible. Luckily, in this case we have the full source, so we can modify the original Makefile:
To look like this:
All we had to do was swap out the compiler in the Makefile. Super easy. Unfortunately, we’re not
quite done yet. Because the binary on Smasher is a web server, it utilizes both fork
, as well as
bind
, accept
, connect
, et al. This presents us with a few challenges.
First, AFL expects the process to terminate (or to emit the appropriate signal) after every fuzzing iteration; however, a web server like the one we’re dealing with runs in an infinite loop and forks into child processes, both of which can prevent AFL from doing it’s job. Take a look:
The fix for this is extremely simple: remove the problems! Simply comment out the call(s) to fork
and
the surrounding pid
checks, and remove the infinite loop (or, return the appropriate signal at the end
of each loop):
The reason I opted for a raise(SIGSTOP)
instead of removing the infinite loop is that it
allows AFL to continue to running the same process until a potential issue which greatly speeds up
the number of attempts per second. Otherwise, it would have to spin up a new process on every
single iteration.
That’s all the changes we have to make to the code. Time to build the AFL-ified server (after you install afl, of course):
There is only one last challenge to deal with: the networking calls. AFL works by passing input into a program and then
seeing what happens, and that’s it. It isn’t built to support connecting to a socket and delivering
input that way. To help us get around this, there is a great tool named Preeny. Preeny comes with a bunch of modules which perform various useful purposes, such as removing fork
, removing alarm
, changing time
, etc. For our purposes, one of the modules included is called desock
, which moves all socket
-based operations to the console, which is exactly what AFL needs! Once downloaded and compiled, all that is required is to use
LD_PRELOAD
to make sure Preeny’s libraries overwrite the standard library.
Almost there! As I mentioned earlier, AFL works by passing input into our target binary, and then
mutating that as it detects (or doesn’t detect) irregular behavior. In order to do this, it needs a starting
point of known “good” input that it can mutate. AFL calls these “testcases”, and you can pass in as many as
you’d like. In our case, we could put various GET
, POST
, HEAD
, etc. requests, but to keep it simple we’ll
just do one GET
for now. It also needs somewhere to put its findings:
And with that, we’re ready. Simply launch AFL, use LD_PRELOAD
to overwrite the networking calls with preeny,
and specify the appropriate directories:
In a very short amount of time, AFL finds the binary and its very first crash, a buffer overflow on the URL:
Pretty awesome, right? This may seem like a lot of work to find a simple buffer overflow, but once you have these basics down, the required changes often only take ten minutes, and the output can be awesome.
Happy fuzzing!