SANS Black Box Fuzzing Android Native Libraries
SANS Black Box Fuzzing Android Native Libraries
SANS Black Box Fuzzing Android Native Libraries
gh
Ri
Black-Box Fuzzing for Android Native Libraries
ll
Fu
GIAC (GMOB) Gold Certification
ns
Author: Nawaf Alkeraithe, alkeraithe@gmail.com
Advisor: Christopher Walker
ai
Accepted: November 22, 2021
et
rR
ho
Abstract
ut
,A
te
Many Android application developers are adopting C\C++ native language development
itu
in their Android mobile applications to exceed Java limits for performance issues. The
use of native development in Android occurs by either using a known library in the
st
application or using an in-house developed native library. Without the source code of
In
native libraries, this could be a blind spot for penetration testers. Demonstrating the
process of finding native functions, capturing a sample input data, and writing an
NS
Android application wrapper to implement and fuzz the native functions with AFL fuzzer
SA
may prove useful for mobile penetration testers to shed light on detecting memory
management issues in Android.
e
Th
21
20
©
gh
Ri
ll
1. Introduction
Fu
Android offers developers the possibility of developing part of their mobile
ns
applications in C and C++ languages through Android NDK. NDK enables the
ai
developers to manage memory more effectively and perform different tasks that could
et
consume more resources and time through Java. The use of C\C++ in Android
rR
applications could be seen in Video and Audio calling applications such as WhatsApp or
ho
even through image processing applications such as Instagram.
ut
With the freedom and flexibility offered by C\C++, the risk of memory
,A
management vulnerabilities arises. Security researchers were able to find RCE in GIF
te
decoding library affecting WhatsApp CVE-2019-11932 (NIST, 2020a), and another RCE
itu
Checkpoint, 2020).
NS
In the case of open-source libraries, mobile penetration testers could search for
SA
known vulnerabilities affecting the used library or research for 0-days in the library
through static code analysis or fuzzing. Many open-source libraries offer the possibility
e
Th
limits the options for mobile security analysts to examine the library for security issues.
©
This paper will demonstrate the process that could be followed to fuzz closed-
source Android native libraries. The next sections will explain how to find native libraries
and functions used by the application, how to capture a sample input that will be used as
a seed for afl-fuzz, and then demonstrates how to write an Android wrapper application
for the native library to be in the middle between the library and AFL.
gh
Ri
2. Android Native Development
ll
Android NDK (Native Development Kit) offers a range of tools for the developers
Fu
to develop parts of their code in native C\C++ language (Google, n.d.-c). As developers
ns
build their code, the native code is built to native shared libraries (.so), and since Android
ai
devices are shipped on different CPUs, the native code will also be compiled for each
et
architecture (Google, n.d.-b). Android currently supports 32-bit ARM, AArch64, x86,
rR
and x86-64 through ABI (Google, n.d.-a).
ho
For the native code to interact with Java, developers use and implement JNI (Java
ut
Native Interface). JNI introduces different sets of data types that could be mapped to Java
,A
data types, as shown in Table 1.
Table 1: Primitive Types (Oracle, n.d.-b)
te
Java Type Native Type
itu
boolean jboolean
byte jbyte
st
char jchar
In
short jshort
NS
int jint
long jlong
SA
float jfloat
double jdouble
void void
e
Th
There are two ways for the developer to declare the native methods used by the
21
Android application through either statically following a naming convention in the native
20
functions are declared and identifying them is the first step for choosing our fuzzing
targets. The following two subsections will avoid looking at native code from developers’
perspective and how to develop a native code in Android applications. We will look at it
from a decompiled application which is the point-of-view for most penetration testers.
gh
Ri
The following figure shows lib folder contents for a decompiled APK implementing
native code, where the “lib” folder exists.
ll
Fu
ns
Figure 1: Decompiled Application Folder
ai
et
The contents of the “lib” folder show different versions of the same native code
rR
but compiled differently to support different CPU architectures.
ho
ut
,A
te
itu
st
In
From the tool jadx-gui, the application is loading a 7z files extraction and
SA
compression library. Notice and compare the name between the following and the
e
previous figure. The undecorated name is used as p7zip, which is mapped to libp7zip.so
Th
library file.
21
20
©
gh
Ri
package AppPkgName
class ApplicationClassExample {
ll
…..
Fu
native String calculateToken(String userInput);
…
ns
ai
Then the native method declaration in C\C++ code would be as follow:
et
jstring Java_AppPkgName_ApplicationClassExample_calculateToken(JNIEnv *env,
jobject obj, jstring userInput)
rR
ho
The naming convention should be concatenated as below (Oracle, n.d.-a):
1- Prefix Java_
ut
2- Package name + class name
,A
3- Underscore “_”
4- Method name
te
5- If the methods are overloaded, then the method arguments should follow ("__").
itu
Figure 4 shows how the developer is declaring native methods used by the application.
st
In
NS
SA
e
Th
The methods declaration differs from the library side as the functions’ names are
©
mangled according to the JNI convention. Checking the native library in Figure 5 shows
the method “executeCommand” with the method signature following the JNI naming
convention.
Even though this technique of declaring methods allows the developers to write
less code, the exported native methods show more stack-trace error messages when an
error or exception is thrown from the native code. Using the “nm” command and
gh
grepping for the “Java_” prefix from the JNI naming signature, we can find the native
Ri
methods declared by JNI naming convention. From the decompiled APK, we can see
ll
Fu
what data type the native method is expecting in its arguments and what data type is
returned.
ns
ai
For the second declaration method using the RegisterNatives method, developers
et
do not need to follow the long convention name. However, they need to explicitly register
rR
each method the RegisterNatives. Now let us assume we have the following piece of
ho
code,
ut
package AppPkgName
,A
class ApplicationClassExample {
…..
te
native String getToken(String userInput);
itu
st
Then by using the RegisterNatives, the declaration of the native method could be as
In
follows:
NS
{"getToken",
"(Ljava/lang/String;)Ljava/lang/String;",(void*)getToken},
e
};
Th
When trying to capture sample data of the input and output of the targeted native
methods, Frida could be used to trace and monitor native methods. Android functions
could be hooked with Frida to capture the input used later as a fuzzer sample.
The following Frida script hooks the native method as shown to grab a sample
data that is to be provided to the native method.
gh
Ri
After spawning the application with Frida, the input provided to the native
ll
Fu
function could be used as guidance to formulate an input to fuzz the targeted native
method with AFL.
ns
ai
et
rR
ho
ut
Figure 7: Captured Input Sample from Frida
,A
te
4. Wrapper Android Application
itu
After identifying the native library and choosing its native function as a target for
st
fuzzing, we need to prepare our fuzzer to fuzz the closed-source library. Two different
In
main approaches could be taken; the first approach is to perform fuzzing on the target
NS
function through a Frida script that hooks the native target function. Python could be used
to direct input from the terminal to an RPC exported Frida function. The second approach
SA
would be to write a harness that uses the exported target function in a standalone
e
executable and then fuzz the harness with AFL. The figure below shows the relation of
Th
The decompiled target function from Ghidra in Figure 9 shows the last parameter
in the function declaration, which is the input received from the Android Java/Kotiln
side. The input is then passed to another function called “executeCommand”.
gh
Ri
ll
Fu
ns
ai
et
rR
Figure 9: Decompiled Function from Ghidra
ho
Calling the above function requires the wrapper code to provide values for the
ut
first two parameters, which are handled through Android runtime. The function
,A
“executeCommand” is an exported function that the figure below indicates using the
te
“nm” tool.
itu
st
In
The tool “readelf” could be used to find the shared library's dependencies which
SA
gh
Ri
From the decompiled application in Figure 9, the function takes a string argument
as an input and returns an integer indicating the function execution result. The wrapper
ll
Fu
code below is an example of how to use the dlopen() function to import the Android
native library and get a pointer to the target function “executeCommand” through
ns
dlsym().
ai
et
rR
Figure 13: Using dlsym()
ho
The type definition for the function pointer “FunctionPointerOne” in Figure 14
ut
,A
matches its method declaration, as shown in Figure 9.
te
itu
Figure 15 below shows the complete wrapper code and how to pass the terminal
In
argument to the native functions to prepare the wrapper to be in the middle between the
NS
gh
Ri
ll
Fu
ns
ai
et
rR
ho
ut
,A
te
itu
st
In
NS
SA
e
Th
The figure below shows the compilation and testing of the application.
20
©
gh
Ri
5. Fuzzing with AFL
ll
After preparing the wrapper application, then it could be fuzzed as a binary with
Fu
afl-fuzz. The tool afl-fuzz requires an input directory for initial test samples and an output
ns
directory to store its findings. For this target function, it needs 7z files to extract, and
ai
therefore to prepare the environment, we will create the following folders:
et
- samples_in/, which contains a test.7z, and test2.7z as input samples
rR
- afl_out/, which is needed by afl-fuzz to store findings and track testing progress
ho
The captured input to the target function “executeCommand” is shown below.
ut
,A
"7z x '/storage/emulated/0/test.7z' '-o/storage/emulated/0/test.7z-ext' -aoa"
te
itu
The tool afl-fuzz uses the annotation “@@” to replace its input files and test cases
st
with the target binary. The figure below shows the command used to start afl-fuzz with
In
The figure below shows afl-fuzz running, and it takes a while until it finds and
captures crashes to the wrapper and the target native function.
gh
Ri
ll
Fu
ns
ai
et
rR
ho
ut
,A
te
itu
Once crashes are captured, afl-fuzz stores its findings in the "crashes” folder, as
In
We will rename each test case as “crash#.7z” and test each crash on the Android
application to verify captured crashes.
©
gh
Ri
ll
Fu
ns
ai
et
rR
ho
ut
,A
Figure 20: Files Listing from Target Application
te
After selecting any crash files, the application will start the extraction process
itu
through the native method “executeCommand”, which is noticed through “adb logcat”, as
st
gh
Ri
ll
6. Conclusion
Fu
The C\C++ native code developed in Android applications might be necessary for
ns
some applications to perform data processing such as image processing and video calls
ai
more efficiently. Developers can use different open-source native libraries in their
et
applications. However, some choose to write closed-source native code. As demonstrated
rR
in this paper, security researchers can write wrapper applications to use the target
ho
function and fuzz it with AFL to ensure thorough coverage in their Android applications
ut
security assessments. Memory management issues might lead to critical security issues
,A
compromising the security of applications users’ and bringing back vulnerabilities that do
te
not exist in Java or Kotlin applications.
itu
st
In
NS
SA
e
Th
21
20
©
gh
Ri
References
ll
Gal Elbaz. Checkpoint. (2020, September 24). #Instagram_RCE: Code execution
Fu
vulnerability in Instagram app for Android and iOS. Retrieved from
ns
ai
https://research.checkpoint.com/2020/instagram_rce-code-execution-
et
vulnerability-in-instagram-app-for-android-and-ios/
rR
Google. (n.d.-a). Android ABIs. Retrieved October 11, 2021, from
ho
ut
https://developer.android.com/ndk/guides/abis
,A
Google. (n.d.-b). Concepts. Retrieved October 11, 2021, from
te
https://developer.android.com/ndk/guides/concepts?hl=en
itu
st
Google. (n.d.-c). Get started with the NDK. Retrieved October 12, 2021, from
In
https://developer.android.com/ndk/guides
NS
https://developer.android.com/training/articles/perf-jni#native-libraries
e
Th
11932
©
NIST. (2020b, April 10). Cve-2020-1895. Retrieved from NVD - National Vulnerability
https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html
Oracle. (n.d.-b). JNI types and data structures. Retrieved October 24, 2021, from
https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html