Fuzz testing is a great way to find exploitable bugs or bugs in system utilities. It allows auditors to gauge the effectiveness of file handlers and any other application against malformed and possibly malicious input, and helps determine whether there are any easily exploitable entry points on a system. It's also a great way to automate security testing.
Android is no different from any other system and has a myriad of interesting fuzz targets. The attack surface of an Android device doesn't stop at the Java application layer; in fact, root exploits are sometimes based on a native executable or system utility that doesn't properly handle any given input or respond in a secure way to certain situations. Fuzzing is a great way to find these situations and possible root exploits on an Android device.
What I'm going to cover here is how to port a fuzz test generator called Radamsa to the Android platform, and also install some utilities that will help you to script some robust fuzzing scripts that use Radamsa.
Before we can start porting, you will need to grab a copy of the Radamsa fuzzer; here's how you do that:
sudo apt-get install gcc curl
Running this command should produce an output similar to the following screenshot:
curl http://ouspg.googlecode.com/files/radamsa-0.3.tar.gz > radamsa-0.3.tar.gz
Running this command should produce an output similar to the following screenshot:
tar –zxvf radamsa-0.3.tar.gz
If you've executed this command correctly, your output should be similar to the following screenshot:
Your directory should look something like the following when you're done:
Everything is set up now; we can begin setting up the jni
directory structure and compiling Radamsa for Android.
To cross-compile Radamsa for Android, you should do the following:
radamsa-0.3
after unpacking the Radamsa source inside this directory; you should create a directory called jni
, just as we've done in the Cross-compiling native executables recipe.Android.mk
file you used for the buffer overflow recipe and stick it inside the jni
directory; your directory should look similar to the following screenshot:radamsa.c
file, which contains the Radamsa source, into the jni
directory as in the following screenshot:Android.mk
file and stick it inside the jni
folder.Copying your Android.mk
file should be similar to the demonstration shown in the following screenshot:
Android.mk
file you copied in the previous step so that it looks like the following:Android.mk
file, you can execute the ndk-build
command; you should get the following output:This means the build has failed. GCC also shows you which lines of code cause the error. It is, in actual fact, one issue cascading through the rest of the code, namely typedef
, which aliases an unsigned long into something called in_addr_t
; in the next step, we will fix this issue to get Radamsa compiled successfully.
radamsa.c
file in your favorite code editor—preferably one that displays line numbers. Scroll down to line number 3222
; you should see the following code if you're using the vim text editor:3222
of the radamsa.c
code, replace the in_addr_t
type name to an unsigned long. The code should look something like this when you've changed it correctly:typedef
command in line 2686
; before editing the line, it should look like the following:After commenting it out, it should look something like the following:
radamsa.c
source so it pleases the NDK GCC compiler, you can run the ndk-build
script. If you've done everything correctly, your output should look something like this:radamsa –-help
This should generate the following output:
echo "99 bottles of beer on the wall" | radamsa
Running this command should produce an output similar to the following screenshot:
And that's it! Radamsa is up and running on Android. The next section talks about setting up a simple fuzzing script and pointing it at dexdump to try and generate some crashes and hopefully find some exploitable vulnerabilities.
If you're going to be doing some fuzzing, you will eventually need to do some bash scripting to hone Radamsa at the right targets and autonomously report input data that causes interesting behavior. Unfortunately, Android platforms don't come packaged with all the utilities that make bash scripting powerful; they don't even come with a bash shell application, mostly because it's not needed.
We could use the sh
shell do to our scripting, but bash is a little more powerful and robust and generally most people are more accustomed to bash scripting. Because of this, the following section of this recipe explains how to get Busybox running on an Android platform.
To get Busybox utilities (a package of useful terminal applications) on Android, you need to do the following:
wget
to do this:busybox
directory on your Android emulator—assuming you have one already set up and ready to go.For this example, the busybox
directory was made in the /data/
folder; since it's writable and executable, any folder on a partition mounted with write, read, and execute permissions should work well.
adb push [path to busybox] /data/busybox/.
You should be doing something similar to the following screenshot:
busybox
binary to your emulator, you can install the binaries by executing the following command on your emulator:/data/busybox –-install
Here's an example from a Samsung Galaxy S3 smartphone:
After executing this command, your busybox
folder should look something like the following:
Now that you've got your test case generator up and running and the Busybox utilities installed, you can start generating some crashes!
In this example, we will see how to set up a simple script to do some "dumb" fuzz testing against dexdump, a utility that dissects an Android DEX file and prints its contents:
Example.dex
file created in the previous chapter's recipes. If you'd like to generate this file, see the Compiling from Java to DEX recipe in Chapter 6, Reverse Engineering Applications./data/
directory once again is a great place to do this, though it would be good to consider emulating an SD card and saving your data there.#!/bin/bash ROOT=$1 TARGET=dexdump ITER=$2 for ((c=0;1;c++)) do cat $ROOT | radamsa -m bf,br,sr -p bu > fuzz.dex $TARGET -d fuzz.dex 2>&1 > /dev/null RET_CODE=$? echo "[$c] {$RET_CODE} ($WINS)" test $RET_CODE -gt 127 && cp fuzz.dex win-dexdump_$ITER"_"$c.dex && WINS=`expr $WINS + 1` done
/data/busybox/bash; /data/busybox/source [fuzz script name] [example.dex]
And now you're fuzzing!
In the first part of the How to do it… section of this recipe, we covered cross-compiling a popular fuzz test generator called Radamsa. Most of what we did is already explained in the Cross-compiling native executables recipe. Things get interesting when the NDK build script fails to compile Radamsa because of a type definition; here's what it looked like:
typedef unsigned long in_addr_t;
This causes the build script to fail because the GCC compiler used by the NDK build script—namely one that was built to support the ARM Application Binary Interface—failed to recognize the effect of the type definition.
When the type defined by the mentioned statement is referenced, it causes GCC to halt and report that it basically doesn't know what in_addr_t
is. This issue was resolved by removing the need for typedef
by replacing the mentioning of the in_addr_t
alias with the full variable type of unsigned long and commenting out the typedef
statement.
Once this issue was resolved, Radamsa could compile successfully and be deployed to an Android device.
Then we wrote a makeshift fuzzing script to the target dexdump. To make sure you guys understand exactly what you're doing in this recipe, it's important we detail what the bash script does.
The first few instructions make sure we have some useful mnemonics to help us refer to the arguments passed to the script. These instructions—appearing after the #!/bin/bash
instruction—simply assign values to some variable names.
After assigning these values, the script steps into a for
loop with a sentinel value—the value that limits the number of times the for
loop iterates—which will cause the script to iterate forever unless explicitly stopped by the user or the operating system.
Inside the for loop, we see the following line:
cat $ROOT | radamsa -m bf,br,sr -p bu > fuzz.dex
All this does is grab the file pointed to by the ROOT
variable and feeds it to Radamsa. Radamsa then applies some randomized transformations to the file.
After making the requested random transformations to the DEX file, Radamsa redirects the output to a file called fuzz.dex
, which is the "fuzzed" version of the sample DEX file.
Then dexdump is invoked with the fuzzed DEX file as an argument; here's what it looks like:
$TARGET -d fuzz.dex 2>&1 > /dev/null
And all output is redirected to /dev/null
, since we probably won't be interested in it. This line of code also redirects all the output from STDIN
(the standard output file) to the STDERR
file (the standard error output file). This allows all the output generated by the program—any that would likely clutter the screen—to be redirected to /dev/null
.
The next instruction looks like this:
RET_CODE=$?
This records the exit code of whatever the last command was; in this case, it was dexdump
.
The script does this because it will reveal information about how dexdump
exited. If dexdump
exited execution normally, the return code will be 0
; should anything have happened that caused dexdump to exit or halt abnormally—say, like a fault due to corrupted input—the exit code will be nonzero.
And even more interestingly, if the fault required the operating system to halt dexdump via the use of inter-process signaling, the return code will be greater than 127. These return codes are the ones we are interested in generating since they give us a strong indication that a relatively serious flaw was exposed due to the given dexdump input. Errors like segmentation faults, which usually happen when an invalid portion of memory is used in an incorrect manner, always generate return codes higher than 127. For more detail on how exit codes or rather exit statuses work, see the Work the Shell - Understanding Exit Codes link in the See also section.
Moving on, the rest of the code looks like this:
echo "[$c] {$RET_CODE} ($WINS)" test $RET_CODE -gt 127 && cp fuzz.dex win-dexdump_$ITER"_"$c.dex && WINS=`expr $WINS + 1
The first instruction of this portion of the code simply helps us keep track of which iteration the script is currently executing—by printing the $c
value. It also prints out the return code of the previous run of dexdump and how many notable halts have occurred.
After printing out the mentioned "status indicators", the script compares the value saved in the RET_CODE
variable's value to 127
; if this value is greater, it makes a copy of the sample input that caused this error and increments the WINS
variable by 1
to reflect that another notable error was generated.
18.223.33.157