This short article will cover our journey on fuzzing blackbox compiled libraries in Android. Although fuzzing open-source code is already available in Android with AFL++ we really couldn’t find any publicly available sources on how to fuzz closed-source libraries in Android apps.
Our approach is to take advantage of already available programs and use them to fuzz our target. We decided to use AFL++ to fuzz binaries and to utilize its QEMU support to instrument closed-source binaries.
There are two main approaches on fuzzing libraries from Android with AFL++, the first one is to pull the binary from the device, then fuzz the binary on a Linux host with QEMU emulation of the Android target (This approach was used here and here).
The second approach which we will demonstrate is to compile AFL++ with QEMU support to Android and fuzz our target on an Android device. This can be advantageous to us by having our target application run on a native environment and by that avoid having dependency issues.
We posted a tweet several months ago and we have received a lot of interest in this topic, this article will be a walkthrough with examples on how to compile QEMU and fuzz Android libraries on a device.
Disclaimer: This project ended around May this year thus we will compile for Android 11 and use AFL++ version 3.13c.
To compile AFL++ for Android we first need to compile AOSP, to do so we can follow the official instructions and compile Android 11 to our target (aosp_arm64-eng).
After AOSP compilation we should clone AFL++ to the working directory, and check out version 3.13c.
git checkout tags/3.13c
Now that we are at the right version, we need to extract frida-gum (download here) to the utils/afl_frida/android/arm directory.
All that’s left for us to do is build AFL++ from the working directory with the following command:
mmm AFLplusplus
AFL++ supports Android open-source fuzzing since v2.54c which can be used to compile AFL to Android and run it there. But trying to run QEMU mode with the binary results with the following message:
Program 'afl-qemu-trace' not found or not executable
We can’t fuzz with QEMU until we build afl-qemu-trace to Android at the right architecture, in this article we will focus on aarch64 architecture.
Before compiling QEMU itself, we need to compile several libraries which QEMU depends on:
glib (do not confuse with glibc which is a completely different library)
All of the libraries we build will be placed in the build
directory that we create first.
These are the environment variables we are going to use (Fill the correct paths to your tools).
While compiling you might encounter errors with pthreads, if this happens you might wanna follow these instructions.
mkdir build
# path to Android clang compiler (..../Sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang)
export CC=
# path to Android clang++ compiler (..../Sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang++)
export CXX=
export BUILD_DIR=`realpath ./build/`
# path to toolchain directory (..../Sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/)
export TOOLCHAIN=
# path to afl++ qemu_mode directory (..../AFLplusplus/qemu_mode)
export QEMU_DIR=
wget https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.16.tar.gz
tar zxvf libiconv-1.16.tar.gz
cd libiconv-1.16
./configure --prefix=$BUILD_DIR --host=aarch64-linux-android30 --disable-rpath
make -j 16
make install
wget https://github.com/libffi/libffi/releases/download/v3.4.2/libffi-3.4.2.tar.gz
tar zxvf libffi-3.4.2.tar.gz
cd libffi-3.4.2/
./configure --prefix=$BUILD_DIR --host=aarch64-linux-android30
make -j 16
make install
wget https://ftp.gnu.org/gnu/gettext/gettext-0.19.tar.gz
tar zxvf gettext-0.19.tar.gz
cd gettext-0.19/
./configure --prefix=$BUILD_DIR --host=aarch64-linux-android30 --disable-threads
make -j 16
make install
To compile glib, a special cache file should be used with the following content:
glib_cv_long_long_format=ll
glib_cv_stack_grows=no
glib_cv_sane_realloc=yes
glib_cv_have_strlcpy=no
glib_cv_va_val_copy=yes
glib_cv_rtldglobal_broken=no
glib_cv_uscore=no
glib_cv_monotonic_clock=no
ac_cv_func_nonposix_getpwuid_r=no
ac_cv_func_posix_getpwuid_r=no
ac_cv_func_posix_getgrgid_r=no
glib_cv_use_pid_surrogate=yes
ac_cv_func_printf_unix98=no
ac_cv_func_vsnprintf_c99=yes
ac_cv_func_realloc_0_nonnull=yes
ac_cv_func_realloc_works=yes
wget https://download-fallback.gnome.org/sources/glib/2.57/glib-2.57.1.tar.xz
tar xvf glib-2.57.1.tar.xz
cd glib-2.57.1/
chmod -w android.cache
CFLAGS="-L$BUILD_DIR/lib -I$BUILD_DIR/include" ./configure --prefix=$BUILD_DIR --host=aarch64-linux-android30 --cache-file=android.cache --with-pcre=no --enable-libmount=no --with-libiconv=gnu --disable-libelf
make -j 16
make install
To compile qemuafl (which is a modified version of QEMU for fuzzing) we need to apply a patch(here) to the source code and compile.
After compilation, we should transfer afl-qemu-trace
to the same directory as afl-fuzz and upload the compiled libraries to the device.
(cd $QEMU_DIR ; sudo --preserve-env=CC,CXX PATH=$PATH:$TOOLCHAIN/bin NO_CHECKOUT=1 HOST="aarch64-linux-android" PKG_CONFIG_PATH=$BUILD_DIR/lib/pkgconfig CPU_TARGET=aarch64 CROSS=$CC ./build_qemu_support.sh)
Now that we have compiled QEMU, we can proceed and write a harness for a program. We will include a vulnerable app we wrote as an example, this app will use the external library we wrote to demonstrate a crash in the JNI library.
The example application can be found here. The harness code can be found in our repo. The harness should be compiled with the following flags:
-ldl -Wl,--export-dynamic
When writing a harness for a library, we need to simulate the original behavior of the app through the harness code. We must call the library code the same way the app does, so in case of a crash we can reach the same buggy code through the app.
JNI (or Java Native Interface) is a Java framework that enables running native code in Java applications. The example application we built uses JNI to call a native function in our library. Our application will transfer a Java VM instance to our native function as part of the JNI call. The VM object exports functions that can be used to access Java objects from native code, to use them the same way as Java does.
Because in our harness (written in C) we are calling a function in our target library, we need to create a Java VM instance and transfer it to our library function. We modified the code written by Caleb Fenton and created a JVM instance to pass to the native library.
After creating the JVM instance and calling our target function, the harness is ready and can be fuzzed.
To fuzz the harness all we need to do is to call afl-fuzz with the right parameters and with the compiled libraries included in LD_LIBRARY_PATH. One of the flags we need to provide to the Android environment is AFL_QEMU_FORCE_DFL=1. Without it, AFL won’t catch fault signals. When fuzzing, we want to utilize AFL’s fork server feature and run the JVM initialization code only once.
After running afl-fuzz we should see AFL++ up and running.
This post presented a quick walkthrough on how to compile QEMU support to AFL++ on Android. It is possible now to fuzz blackbox binaries on a device, furthermore, it is also possible to create many Android virtual machines and fuzz programs parallelly on a “native” environment.
Compile qemu user mode that can be run on Android