By Duc Duy Phan
What is fuzzing?
Fuzzing can simply be understood as the process of automatic discovery of anomalies in applications. This is not limited to binary application. The same concept is applied to web application in which vulnerabilities like XSS, SQL injection can be fuzzed. This article, however, will be focused on binary applications.
The fuzzing process basically means feeding the application with different inputs to reach as many code paths as possible to make the program crash. Such crashes can mean memory corruption, which can lead to further exploitation such as code execution.
So why do we need fuzzing in vulnerability discovery? First of all, it automates the process of feeding input to the program and recording the outcome (which include crashes). More importantly, for programs or projects with a large code base, there is a large number of code path that may not be reached by normal usage. Some types of fuzzer allows discovery of these rare code paths or in other words, it is coverage-guidance. This basically means the fuzzer tries to modify the input so that it can reach as much lines of the program code as possible. Therefore, fuzzing allows the discovery of vulnerabilities lying in code paths that are hard to reach by normal usage.
Types of fuzzers
- Dumb fuzzers: This type of fuzzer only operates by generating random inputs, feeding it to the application and hoping for a crash. This also means that dumb fuzzers are not efficient against application that validate input formats or checksums because chances are the randomly generated input will not pass the validation. However, this also means that these fuzzers are fast. This article will not discuss this type of fuzzers.
- Semi-smart fuzzer: When using this type of fuzzer, we usually target a specific functionality of the application. The input is generated in a way such that it will pass the validation of the program. For example, the CRC checksum is recalculated after mutating a PNG, making it a valid input.
- Feedback-driven fuzzer: This type of fuzzer is different from other types of fuzzer because it is able to use the output from a run to modify the input for the next run. American Fuzzy Lop is an example for this type of fuzzer. This article will focused on this specific fuzzer.
American Fuzzy Lop
During the setup of AFL, we use the AFL compiler to introduce instrumentation to the application. This means extra codes are inserted into the application in a way that it does not affect the memory mapping or the execution flow of the application and at the same time allow the fuzzer to monitor the execution as well as the memory mapping of the application.
Setting up AFL fuzzer
Setting up AFL fuzzer is simple. In this article, lib yaml-cpp will be used as out target. The environment on which we are setting up the fuzzer will be a 64-bit Ubuntu 16.04 machine.
To start out, let’s install AFL on our machine.
$ sudo apt-get update $ sudo apt-get -y install autoconf automake bison build-essential \ ca-certificates clang llvm-dev g++ gcc git libtool libtool-bin \ libglib2.0-dev make nasm wget cmake $ wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz $ tar xvzf afl-latest.tgz $ cd afl-* $ make $ cd llvm_mode $ make $ cd ../qemu_mode $ ./build_qemu_support.sh $ cd ../libdislocator $ make $ cd ../libtokencap $ make $ cd .. $ sudo make install
Next, let’s download the source code of yaml-cpp from Github and compile it using the AFL compiler.
$ git clone https://github.com/jbeder/yaml-cpp $ cd yaml-cpp $ mkdir build $ cd build $ cmake -DCMAKE_CXX_COMPILER=afl-clang-fast++ .. $ make
Our target will be the parser program in build/util/parse. For simplicity, we will use the initial testcases at https://github.com/brandonprry/yaml-fuzz/tree/master/min_testcases. These testcases are generated by the afl-tmin program that comes with AFL. afl-tmin reads valid input and try to zero-out and delete as many bytes in the input as possible to minimize the input without affecting the feedback from the application. The purpose of this is to reduce CPU cycles wasted by mutating input that does not lead to a different code path.
Before running the fuzzer, you may need to run this command:
echo core > /proc/sys/kernel/core_pattern
Let’s start the fuzzer.
# afl-fuzz -i yaml-fuzz/min_testcases/ -o out-dir -- yaml-cpp/build/util/parse
This is how the stat screen looks like
The fuzzer can be stopped anytime and the crashes will be in the out-dir/crashes directory.
In this part, we will crashwalk, a program to help with crash triage
# cd # apt-get install gdb golang # mkdir src # cd src # git clone https://github.com/jfoote/exploitabless # cd && mkdir go # export GOPATH=~/go # go get -u github.com/bnagy/crashwalk/cmd/… # ~/go/bin/cwtriage -root afl/out-dir/crashes -match id -- afl/yaml-cpp/build/util/parse @@
This will start feeding the application with all input that caused a crash. The information of the crash will be displayed with a gdb-friendly format. This is one of the output entries by crashwalk
The efficiency of AFL fuzzer depends on the initial input the we feed it with. More input minimizing will also reduce the CPU cycles being used and speed up the process. There are also more efficient way of fuzzing application, one of which is using libFuzzer to do in-process fuzzing. Please stay tuned as I will definitely post more about this on http://simpleguy.tech
The article has used the following resources as references: