expert
Mayhem and Test Drivers¶
In this lesson we'll walk you through how a test driver works to test specific functions of an overall program!
Beware the Nomenclature
Users familiar with fuzzing may use the term "fuzzing harness" instead. To be clear, when we say "test driver" this is the same as a "fuzzing harness".
Estimated Time: 10 minutes
By the end of this lesson, you will be able to:
- Understand why a test driver is important.
- Define what a test driver is.
- Walk through an example test driver.
- Dissect the Heartbleed vulnerability.
Why Create a Test Driver?¶
Oftentimes it's necessary to create a more focused fuzzing operation by writing a test driver, or a small program that calls functions of interest. For example, if you had a large web server application, but only wanted to test the associated HTTP request parser, you can create a test driver to fuzz just the HTTP request parser rather than the entire web server application.
In addition, if your application does not meet the Mayhem Program Compatibility criteria, you can also write a test driver to make it compatible with Mayhem. For example, you can write a test driver that mocks out specialized hardware, turning an application that is otherwise unfuzzable (and difficult to test in general!) into one that can you analyze.
Note
This section assumes you have already gone through previous tutorials and have basic familiarity with Mayhem and the Mayhem CLI.
Mayhem and Test Drivers¶
A test driver creates a new entrypoint to the binary and can be written around any section of code to be more generic, or targeted, depending on the user's needs. Therefore, if you can identify an interesting function, you can vector Mayhem to focus its analysis on that specific region of code.
By creating a test driver, Mayhem will only focus on that part of the code, reducing time to find bugs.
Some examples of interesting functions may include:
- User exposed code paths.
- A critical procedure.
- Historically buggy code from a developers perspective.
Test drivers can also help skip over initialization or blocking functions. For example, a target may require a username and password login to first authenticate before executing any interesting functions. A test driver can help skip over any initialization or blocking function that may exist.
Understanding an Example Test Driver¶
Let's now walk through how to an example test driver application works.
1 2 3 4 5 6 7 8 |
|
In this example test driver, a SwordHarness
function reads input (such as from a file) and calls the function of interest: process_nuke_cmd
. Here we assume that the function parameter *Data
is the input file provided by the fuzzer, and Size
is the length of the input.
Note
The test driver must initialize any buffers of invariants needed to call the function.
The SwordHarness
function uses the first byte of *Data
to fill in the cmd
variable and use the remaining bytes for nuke_data
. In the case of the cmd
set to LAUNCH_NUKE
or CONFIGURE_NUKE
, the process_nuke_cmd
will use the cmd
and nuke_data
variables. Because this is a test driver, we can expect that the input will be mutated accordingly to maximize code coverage.
A Real World Use Case¶
In 2014, two teams of security researchers began independently fuzzing the OpenSSL library and found what was called the Heartbleed Vulnerability, a serious flaw in the OpenSSL encryption software that powered a lot of secure communications on the web.
Tip
Check out our blog for more information on the Heartbleed Vulnerability
In the next example, we'll go over a test driver that reproduces the Heartbleed vulnerability. Similar to our previous example test driver, the *Data
and Size
parameters represent the fuzzing inputs to the HeartBleed
function.
Here we can see that the test driver sets up the openssl context required for most of the library's functionality and then performs a handshake with the data provided into the test driver. This is indicated on lines 4 and 13, respectively.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Writing Quality Test Drivers¶
There are several best practices you should consider when writing a test driver in order to improve your test driver performance and maximize the efficiency of the analysis.
- Ensure determinism: Avoid modifying global state on each iteration.
- Avoid things that cause delays: Mock databases, network connections, or other external components.
- Isolate code under test: Only run the code you want to test or necessary for interesting paths.
- Avoid reading/writing from disk: Slows down analysis and reduces determinism.
- Check that your test driver makes progress quickly and continually: Monitor the progress of your test driver to ensure that there are no pain points causing performance slowdowns.
Below is a visual comparison for code coverage of an example target application. Here we see that when comparing a test driver written without best practices compared to a test driver written with best practices, the latter test driver is more performant and can therefore achieve more code coverage in regards to the underlying target application.
✏️ Summary and Recap¶
In this lesson, you learned why you should create a test driver, how Mayhem uses test drivers to fuzz specific functionality of an overall taret application, and the best practices to keep in mind when writing your own test driver.
I learned how to...
1. Understand why a test driver is important.
- Oftentimes it's necessary to create a more focused fuzzing operation by writing a test driver, or a small program that calls functions of interest. For example, if you had a large web server application, but only wanted to test the associated HTTP request parser, you can create a test driver to fuzz just the HTTP request parser rather than the entire web server application.
- If your application does not meet the Mayhem Program Compatibility criteria, you can also write a test driver to make it compatible with Mayhem. For example, you can write a test driver that mocks out specialized hardware, turning an application that is otherwise unfuzzable (and difficult to test in general!) into one that can you analyze.
2. Define what a test driver is.
- A test driver creates a new entrypoint to the binary and can be written around any section of code to be more generic, or targeted, depending on the user's needs. Therefore, if you can identify an interesting function, you can vector Mayhem to focus its analysis on that specific region of code.
3. Walk through an example test driver.
- In this example test driver, a
SwordHarness
function reads input (such as from a file) and calls the function of interest:process_nuke_cmd
. Here we assume that the function parameter*Data
is the input file provided by the fuzzer, andSize
is the length of the input. -
The
SwordHarness
function uses the first byte of*Data
to fill in thecmd
variable and use the remaining bytes fornuke_data
. In the case of thecmd
set toLAUNCH_NUKE
orCONFIGURE_NUKE
, theprocess_nuke_cmd
will use thecmd
andnuke_data
variables. Because this is a test driver, we can expect that the input will be mutated accordingly to maximize code coverage.1 2 3 4 5 6 7 8
int SwordHarness(uint8_t *Data, size_t Size) { char cmd = Data[0]; uint8_t *nuke_data = Data + 1; if(cmd == LAUNCH_NUKE || cmd == CONFIGURE_NUKE) process_nuke_cmd(cmd, nuke_data); return 0; }
4. Dissect the Heartbleed vulnerability.
- In 2014, two teams of security researchers began independently fuzzing the OpenSSL library and found what was called the Heartbleed Vulnerability, a serious flaw in the OpenSSL encryption software that powered a lot of secure communications on the web.
-
Here we can see that the test driver sets up the openssl context required for most of the library's functionality and then performs a handshake with the data provided into the test driver. This is indicated on lines 4 and 13, respectively.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
void HeartBleed(const uint8_t *Data, size_t Size) { //initialization SSL_CTX *sctx = Init(); SSL *server = SSL_new(sctx); BIO *sinbio = BIO_new(BIO_s_mem()); BIO *soutbio = BIO_new(BIO_s_mem()); //send one message SSL_set_bio(server, sinbio, soutbio); SSL_set_accept_state(server); BIO_write(sinbio, Data, Size); SSL_do_handshake(server); SSL_free(server); SSL_CTX_free(sctx); }