Exploring Native Functions with Frida on Android — part 1

Native library static inspection and the JNI

The Mobile Security Guys
5 min readMay 18, 2020

Introduction

Mobile security testing of Android applications involves code review in order to understand how the app logic and flow works, as well as identifying any potential security vulnerabilities. If the app was developed in Java, decompiling the app means reversing the compilation process in order to extract the Java source-code from the binary compiled code. To accomplish this, testers can use commercial tools (such as jadx and enjarify) which take the APK file and attempt to retrieve the Java source code.

Sometimes decompilation of the code back to Java class files is not enough.

However, this might not be enough. Sometimes developers might use the so-called Android NDK (Native Development Kit), which allows developers to use native C and C++ language. In this case we are talking about Native Functions, rather than Java methods and Native Libraries, referred to as .so files.

There are several reasons why a developer would use the Android NDK; efficiency, optimisation, as well as better memory management, as in comparison to Java, C/C++ lets developers manually manage the memory usage. C/C++ code might also be used for security reasons.

It is not possible to decompile and retrieve the source-code of a compiled C/C++ native library.

Fortunately for security testers, tools like Frida exist. In a nutshell, Frida is a dynamic binary instrumentation tool that let testers inject their own code (JavaScript) inside a program. This is accomplished by using the Frida JavaScript API.

The first part of this post will explain the concept of native libraries, native implementation, the JNI and locating these using static analysis. The second part demonstrates how to explore and interact with these libraries using the Frida API.

Static Inspection

Finding native libraries

First of all, we must understand if the app actually uses any native functions, therefore we need to locate these native libraries. A native library is a .so file, where “so” stands for Shared Object, which is essentially a compiled C file. On Android, we can have two types of native library: system (for example libc.so) and custom application ones (for example myApp.so).

A few examples are shown below. Where the libraries are located on the Android device (/system/lib64); or custom libs for the social apps Spotify and Facebook Messenger in their respective app directories (/data/app/package_name/lib/arm64).

Android system libraries in /system/lib64
Android system libraries located in /system/lib64
A list of Spotify native libraries
Spotify libraries
A list of TikTok native libraries
TikTok libraries

Native libraries within the codebase

Having identified native libraries are being used, the next step consists of inspecting the Java class code to understand where and when these native libraries are actually loaded by the app. This task can be done by searching for the Java method: System.loadLibrary().

TikTok code showing the flow of loading a native library named Encryptor

Once we know and understand the workflow of the native libraries being loaded, we need to identify the actual C functions implemented in these native libraries. These are invoked directly within the app and are the link between the C and Java. This link is defined by the JNI (Java Native Interface), which requires these functions to be declared within a Java class file by using the key word “native” and then wrapped inside a normal Java method. Therefore, to identify all the functions in the app we can use a search expression inside our decompiled app folder:

egrep -r “(private|public) static native” *
Native function declarations in Java

C implementation of a JNI function

Let us now consider a simple example to better understand how JNI works. In our scenario we have a hypothetical app named “myBank”, with package name co.uk.mybank, where we have a native function which we wish to be invoked within a Java class. This function is implemented in a C file named “verifyCertJNI.c” which is used for SSL certificate pinning; inside is a Java class named “SSLpinning” that checks whether a certificate has been verified and return either true or false.

We can observe the structure that a function must follow which is required by the JNI. The function name declaration must start with “Java_”, followed by the package name, then the actual Java class file.

Structure of a JNI call based in Java
JNI structure call for invoking C function into Java

Note that JNIEXPORT and JNICALL are proper keywords from JNI, so it is necessary to include the header file jni.h, as well as the type casting from C to Java: jboolean, jstring and jobject.

Moreover, bear in mind that there might be other C/C++ functions in use when running the app but not directly invoked by the app using Java, therefore these functions do not need to follow the JNI structure or declared as “Java_”.

So far we have taken more of a static approach to identifying native libraries and how they are loaded. Using Frida will allow us to delve more in-depth to examine these functions more closely.

--

--

The Mobile Security Guys

Random posts about mobile security and testing techniques from a bunch of mobile professionals.