├── bin ├── dx.jar └── dx ├── adapters ├── sample1 │ ├── bin │ │ ├── sample1.dex │ │ └── libnative.so │ ├── testcase │ │ └── first.bin │ ├── NativeJni.java │ ├── native │ │ ├── Android.mk │ │ └── jni.cpp │ ├── NativeLogger.java │ └── Adapter.java ├── push_sample1.sh ├── fuzz_sample1.sh └── build_sample1.sh ├── .github └── workflows │ └── sourceguard.yml └── README.md /bin/dx.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/android_appfuzz/HEAD/bin/dx.jar -------------------------------------------------------------------------------- /adapters/sample1/bin/sample1.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/android_appfuzz/HEAD/adapters/sample1/bin/sample1.dex -------------------------------------------------------------------------------- /adapters/sample1/bin/libnative.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/android_appfuzz/HEAD/adapters/sample1/bin/libnative.so -------------------------------------------------------------------------------- /adapters/sample1/testcase/first.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/android_appfuzz/HEAD/adapters/sample1/testcase/first.bin -------------------------------------------------------------------------------- /adapters/sample1/NativeJni.java: -------------------------------------------------------------------------------- 1 | package com.sample1; 2 | 3 | public class NativeJni { 4 | public native void dummy(int file_size); 5 | } 6 | -------------------------------------------------------------------------------- /adapters/sample1/native/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH:= $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | LOCAL_MODULE := libnative 5 | LOCAL_SRC_FILES := jni.cpp 6 | include $(BUILD_SHARED_LIBRARY) 7 | -------------------------------------------------------------------------------- /adapters/sample1/NativeLogger.java: -------------------------------------------------------------------------------- 1 | package com.sample1; 2 | 3 | public class NativeLogger { 4 | public static void log(int priority, String msg) { 5 | System.out.println(msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/sourceguard.yml: -------------------------------------------------------------------------------- 1 | name: SourceGuard Code Analysis 2 | on: [push] 3 | jobs: 4 | code-analysis: 5 | runs-on: ubuntu-latest 6 | container: 7 | image: sourceguard/sourceguard-cli 8 | steps: 9 | - name: Scan 10 | uses: CheckPointSW/sourceguard-action@main 11 | with: 12 | SG_CLIENT_ID: ${{ secrets.SG_CLIENT_ID }} 13 | SG_SECRET_KEY: ${{ secrets.SG_SECRET_KEY }} 14 | -------------------------------------------------------------------------------- /adapters/sample1/Adapter.java: -------------------------------------------------------------------------------- 1 | package com.sample1; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.lang.RuntimeException; 6 | 7 | public class Adapter { 8 | public static void load() { 9 | NativeLogger.log(0, "Java load()"); 10 | System.load("/data/local/tmp/fuzz/sample1/libnative.so"); 11 | } 12 | 13 | public static void main(String[] args) { 14 | NativeLogger.log(0, "Java main()"); 15 | 16 | File file = new File(args[0]); 17 | int file_size = (int)file.length(); 18 | 19 | NativeJni jni = new NativeJni(); 20 | jni.dummy(file_size); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /adapters/sample1/native/jni.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define JAVA_PROXY(func) Java_com_sample1_NativeJni_##func 5 | #define JAVA_LOGGER "com/sample1/NativeLogger" 6 | 7 | void Log(JNIEnv *env, const char* msg) { 8 | jclass cls = env->FindClass(JAVA_LOGGER); 9 | jmethodID mid = env->GetStaticMethodID(cls, "log", "(ILjava/lang/String;)V"); 10 | env->CallStaticVoidMethod(cls, mid, 0, env->NewStringUTF(msg)); 11 | } 12 | 13 | extern "C" void JAVA_PROXY(dummy)(JNIEnv *env, jobject, jint file_size) { 14 | Log(env, "Native dummy()"); 15 | 16 | if (file_size == 2283) { 17 | Log(env, "Native crash!!!"); 18 | 19 | int* ptr = NULL; 20 | *ptr = 1; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /adapters/push_sample1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | ADPT_DIR=$(cd "$(dirname "$0")"; pwd -P) 5 | FUZZ_DIR=/data/local/tmp/fuzz 6 | 7 | adb shell mkdir ${FUZZ_DIR} 8 | adb shell mkdir ${FUZZ_DIR}/sample1 9 | 10 | adb push ${ADPT_DIR}/sample1/bin/libnative.so ${FUZZ_DIR}/sample1 11 | adb shell chmod 755 ${FUZZ_DIR}/sample1/libnative.so 12 | 13 | adb push ${ADPT_DIR}/sample1/bin/sample1.dex ${FUZZ_DIR}/sample1 14 | adb shell chmod 755 ${FUZZ_DIR}/sample1/sample1.dex 15 | 16 | adb shell mkdir ${FUZZ_DIR}/sample1/out 17 | adb shell mkdir ${FUZZ_DIR}/sample1/in 18 | 19 | adb push ${ADPT_DIR}/sample1/testcase/first.bin ${FUZZ_DIR}/sample1/in 20 | 21 | # Push fuzzing script 22 | adb push ${ADPT_DIR}/fuzz_sample1.sh ${FUZZ_DIR} 23 | adb shell chmod 755 ${FUZZ_DIR}/fuzz_sample1.sh 24 | -------------------------------------------------------------------------------- /adapters/fuzz_sample1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | FUZZ_DIR=/data/local/tmp/fuzz 5 | 6 | # Set terminal settings 7 | ${FUZZ_DIR}/busybox stty rows 100 cols 100 8 | 9 | # Skip CPU frequency scaling 10 | # If you have root permissions, run this cmd instead 11 | # su 12 | # cd /sys/devices/system/cpu && echo performance | tee cpu*/cpufreq/scaling_governor 13 | # exit 14 | export AFL_SKIP_CPUFREQ=1 15 | 16 | export LD_LIBRARY_PATH=${FUZZ_DIR}:/system/lib 17 | export AFL_DALVIKVM_FUZZ_LIB=libnative.so 18 | 19 | rm -rf ${FUZZ_DIR}/sample1/out/* 20 | 21 | # Skiped deterministic steps for the sample 22 | # If you need full analysis, remove -d option 23 | ${FUZZ_DIR}/afl-fuzz \ 24 | -Q -m 1024Mb \ 25 | -d \ 26 | -i ${FUZZ_DIR}/sample1/in \ 27 | -o ${FUZZ_DIR}/sample1/out \ 28 | ${FUZZ_DIR}/dalvikvm -classpath ${FUZZ_DIR}/sample1/sample1.dex com.sample1.Adapter \ 29 | @@ 30 | -------------------------------------------------------------------------------- /adapters/build_sample1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | ADPT_DIR=$(cd "$(dirname "$0")"; pwd -P) 5 | NDK=${ADPT_DIR}/../android-ndk-r10e 6 | 7 | SRC_DIR=${ADPT_DIR}/sample1 8 | BLD_DIR=${ADPT_DIR}/sample1/build 9 | OUT_DIR=${ADPT_DIR}/sample1/bin 10 | 11 | if [ ! -d ${BLD_DIR} ]; then 12 | mkdir ${BLD_DIR} 13 | fi 14 | 15 | if [ ! -d ${OUT_DIR} ]; then 16 | mkdir ${OUT_DIR} 17 | fi 18 | 19 | echo "[*] libnative.so building..." 20 | 21 | # Download NDK r10e 22 | if [ ! -d ${NDK} ]; then 23 | cd ${ADPT_DIR}/.. 24 | wget https://dl.google.com/android/repository/android-ndk-r10e-linux-x86_64.zip 25 | unzip android-ndk-r10e-linux-x86_64.zip 26 | rm android-ndk-r10e-linux-x86_64.zip 27 | fi 28 | 29 | # Build libnative.so 30 | ${NDK}/ndk-build \ 31 | NDK_TOOLCHAIN=arm-linux-androideabi-4.9 \ 32 | NDK_PROJECT_PATH=. \ 33 | NDK_OUT=${BLD_DIR} \ 34 | NDK_LIBS_OUT=${BLD_DIR} \ 35 | APP_BUILD_SCRIPT=${SRC_DIR}/native/Android.mk \ 36 | APP_ABI=armeabi \ 37 | APP_PLATFORM=android-21 38 | 39 | cp ${BLD_DIR}/armeabi/libnative.so ${OUT_DIR} 40 | 41 | echo "[*] sample1.dex building..." 42 | 43 | # Let's use javac 1.7 (as Android 6.x Runtime) 44 | if [ -z "$(echo $(javac -version 2>&1) | grep '[ "]1\.7[\. "$$]')" ]; then 45 | echo "[-] Error: incorrect javac version. 1.7 is required" 46 | exit 1 47 | fi 48 | 49 | # Build sample1.dex 50 | # If your adapter uses Android Framework specific namespaces, indicate relevant Android SDK JAR file as [-cp] javac option 51 | # For example, javac -cp ~/Android/Sdk/platforms/android-21/layoutlib.jar 52 | javac -d ${BLD_DIR} -g \ 53 | ${SRC_DIR}/NativeJni.java \ 54 | ${SRC_DIR}/NativeLogger.java \ 55 | ${SRC_DIR}/Adapter.java 56 | 57 | ${ADPT_DIR}/../bin/dx --dex --output=${OUT_DIR}/sample1.dex ${BLD_DIR} 58 | 59 | echo "[+] Done!" 60 | -------------------------------------------------------------------------------- /bin/dx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2007 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Set up prog to be the path of this script, including following symlinks, 18 | # and set up progdir to be the fully-qualified pathname of its directory. 19 | prog="$0" 20 | while [ -h "${prog}" ]; do 21 | newProg=`/bin/ls -ld "${prog}"` 22 | newProg=`expr "${newProg}" : ".* -> \(.*\)$"` 23 | if expr "x${newProg}" : 'x/' >/dev/null; then 24 | prog="${newProg}" 25 | else 26 | progdir=`dirname "${prog}"` 27 | prog="${progdir}/${newProg}" 28 | fi 29 | done 30 | oldwd=`pwd` 31 | progdir=`dirname "${prog}"` 32 | cd "${progdir}" 33 | progdir=`pwd` 34 | prog="${progdir}"/`basename "${prog}"` 35 | cd "${oldwd}" 36 | 37 | jarfile=dx.jar 38 | libdir="$progdir" 39 | 40 | if [ ! -r "$libdir/$jarfile" ]; then 41 | # set dx.jar location for the SDK case 42 | libdir="$libdir/lib" 43 | fi 44 | 45 | 46 | if [ ! -r "$libdir/$jarfile" ]; then 47 | # set dx.jar location for the Android tree case 48 | libdir=`dirname "$progdir"`/framework 49 | fi 50 | 51 | if [ ! -r "$libdir/$jarfile" ]; then 52 | echo `basename "$prog"`": can't find $jarfile" 53 | exit 1 54 | fi 55 | 56 | # By default, give dx a max heap size of 1 gig. This can be overridden 57 | # by using a "-J" option (see below). 58 | defaultMx="-Xmx1024M" 59 | 60 | # The following will extract any initial parameters of the form 61 | # "-J" from the command line and pass them to the Java 62 | # invocation (instead of to dx). This makes it possible for you to add 63 | # a command-line parameter such as "-JXmx256M" in your scripts, for 64 | # example. "java" (with no args) and "java -X" give a summary of 65 | # available options. 66 | 67 | javaOpts="" 68 | 69 | while expr "x$1" : 'x-J' >/dev/null; do 70 | opt=`expr "x$1" : 'x-J\(.*\)'` 71 | javaOpts="${javaOpts} -${opt}" 72 | if expr "x${opt}" : "xXmx[0-9]" >/dev/null; then 73 | defaultMx="no" 74 | fi 75 | shift 76 | done 77 | 78 | if [ "${defaultMx}" != "no" ]; then 79 | javaOpts="${javaOpts} ${defaultMx}" 80 | fi 81 | 82 | if [ "$OSTYPE" = "cygwin" ]; then 83 | # For Cygwin, convert the jarfile path into native Windows style. 84 | jarpath=`cygpath -w "$libdir/$jarfile"` 85 | else 86 | jarpath="$libdir/$jarfile" 87 | fi 88 | 89 | exec java $javaOpts -jar "$jarpath" "$@" 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Application's Native Fuzzer 2 | 3 | A Fuzzer for the native part of Android apps (closed source .so files). This tool is based on [AFL Fuzzer](https://github.com/mcarpenter/afl) and [QEMU emulator](https://github.com/qemu/qemu). 4 | 5 | **Since this tool may be used for malicious intentions other than security research, we do not publicly share the code or the binary. Nevertheless, we will be happy to cooperate and share the tool with fellow security researchers. For more details you're welcome to contact** slavam@checkpoint.com 6 | 7 | ##### How does it work? 8 | 9 | This tool consists of several parts including patched version of Android Runtime (libart.so), dalvikvm tool, GLib, QEMU and AFL. The assembly of these tools eventually allows to fuzz a user custom lib wrapped in a DEX file on an actual Android device or an ARM device emulator. The execution flow is as follows: 10 | `AFL Fuzz Engine → QEMU ARM CPU emulator → dalvikvm tool → Android Runtime + adapter DEX` 11 | 12 | ## Compatibility 13 | * Android 6.x (Marshmallow) 14 | * All build scripts target ARMv7 platform 15 | 16 | ## Test Run 17 | 18 | First make sure an Android 6.x device is connected and accessible via ADB. 19 | Execute the following commands: 20 | 21 | ```bash 22 | ./push.sh # push base fuzzer binaries to the device 23 | adapters/push_sample1.sh # push sample1 adapter to the device 24 | adb shell source /data/local/tmp/fuzz/fuzz_sample1.sh # start fuzzing sample1 25 | ``` 26 | 27 | ## Running your first custom adapter 28 | A full example of a custom Java adapter as well as build, push and execution scripts can be found in [adapters](adapters) directory. 29 | 30 | A custom Java adapter is a class which implements at least two main methods: 31 | * `load()` - will be executed only once when the fuzzing starts 32 | * `main()` - will be called for each test case. The first argument is the path to the file containing the permuted data 33 | 34 | The Java adapter should be compiled as a DEX to be later fuzzed in the following way: 35 | 36 | ```bash 37 | FUZZ_DIR=/data/local/tmp/fuzz # Directory containing all fuzzer binaries 38 | export LD_LIBRARY_PATH=${FUZZ_DIR}:/system/lib # Use FUZZ_DIR as a path to the patched libs 39 | export AFL_DALVIKVM_FUZZ_LIB=libnative.so # Target lib to be fuzzed 40 | 41 | ${FUZZ_DIR}/afl-fuzz -Q -m 1024Mb -i ${FUZZ_DIR}/sample1/in -o ${FUZZ_DIR}/sample1/out \ 42 | ${FUZZ_DIR}/dalvikvm -classpath \ 43 | ${FUZZ_DIR}/sample1/sample1.dex \ 44 | com.sample1.Adapter @@ # Compiled DEX and Adapter’s class name 45 | ``` 46 | 47 | For further information please refer to [AFL documentation](https://github.com/mcarpenter/afl/tree/master/docs). 48 | 49 | ## How to build the Fuzzer? 50 | 51 | All binaries are pre-built and ready for use, they can be found in [lib](lib) and [bin](bin) directories. 52 | 53 | Should you have any reason to build the Fuzzer on your own, here’s what you need to do: 54 | 55 | ##### Prerequisites (For Ubuntu 16.04) 56 | 57 | * Install Git: 58 | 59 | ```bash 60 | sudo apt-get install git-core 61 | ``` 62 | 63 | * Install repo: 64 | 65 | ```bash 66 | mkdir ~/bin && PATH=~/bin:$PATH 67 | wget -O ~/bin/repo https://storage.googleapis.com/git-repo-downloads/repo 68 | chmod a+x ~/bin/repo 69 | ``` 70 | 71 | * Install Java 7: 72 | 73 | ```bash 74 | sudo add-apt-repository ppa:openjdk-r/ppa 75 | sudo apt-get update 76 | sudo apt-get install openjdk-7-jdk 77 | ``` 78 | 79 | ##### Run the build script 80 | 81 | ```bash 82 | ./build.sh 83 | ``` 84 | 85 | What does the script do? 86 | * Download and unpack NDK r10e 87 | * Patch & Build 88 | * libiconv 1.14 89 | * libffi 3.2.1 90 | * gettext 0.19.7 91 | * glib 2.48.1 92 | * AFL 2.52b 93 | * QEMU 2.10.0 94 | * AOSP 6.0.1_r65 (libart, dalvikvm, dx) 95 | 96 | It should be noted that downloading AOSP will take a very long time! 97 | 98 | ## License 99 | 100 | Released under “Apache 2.0” license. 101 | 102 | 103 | Presented in DEF CON 26 (2018) by Slava Makkaveev --------------------------------------------------------------------------------