In this article I want to break down how to fuzz embedded systems using pre-installed interpreters and minimal resources, introducing a new portable fuzzing tool.
We are going to have a look at a general purpose fuzzer I've built entirely for educational purposes to include a rich debug output so we see what is happening to our input stream in memory. The fuzzer is able to fuzz input streams and seed files (bytebreaker.zip).
We are using different mutation techniques to modify the target input, including:
To get an overview of the affected bytes, we create a matrix to navigate through the affected mutation area so we can map the sourrounding bytes and the attack radius.
We use the byte matrix to see the effect of our applied fuzzing methods. Also we can track down the offset for easier debugging of a segmentation fault.
To calculate the available attack radius we determine the amount of usable byte segments.
It's possible to visualize our fuzzing patterns by enabling the verbose mode with the -v or --verbose parameter.
Generating input data
The tool contains a data generator for fuzzing input streams and creating raw data.
With the -g or --generate parameter we can use the data generator to create crafted input data on the fly without the need of a seed file.
We can pass this raw data to our compiled buffer overflow application to trigger a memory corruption.
Let's use and compile the following C program:
Compiling the program can be done using a standard compiler like gcc without disabling any security restrictions including ASLR and NX.
After this we use valgrind for debugging (feel free to use another debugging tool).
At the first execution we probably get a memory corruption.
If we keep fuzzing the test application using the built-in data generator of our fuzzing tool, the secret function will be called without needing to bypass any more security restrictions at all.
Using a proper CPU this will take less than 20 minutes on average and will produce a reusable payload.
We are using probabilities to increase our virtual randomness.
This makes sure that some combinations are used more often while others are used less often.
There are two groups of fuzzing methods we are using to mutate data.
The first group contains fuzzing methods with a high verbose output so we can actually see in realtime what is happening to our data.
We also have high performance methods which don't need logging and don't need to access the byte matrix. We skip using the byte matrix and manipulate the existing input by directly writing our result to the output buffer by enabling the performance mode with the -p or --performance parameter.
This is especially useful when creating a larger amount of seed files. We can even speed up the process by reducing the mutation count using the -m or --mutations parameter.
It's possible to use seed files as input by using the -f or --file parameter.
Therefore it's necessary to specify an output directory for the generated sample files using the -d or --directory parameter and a sample count using the -c or --count parameter. We also set our mutation count to a low amount to speed up the generation of samples and use the verbose mode for debugging.
If it's necessary to use the fuzzer inside of a script we can initialize and use the class including all of itt's provided methods.