Instrumenting Native Android Functions using Frida
In our previous post: Pentesting Android Application Using Frida, Rohit looked at how we can use Frida for basic run time instrumentation. In short Frida can be used to dynamically alter the behavior of an Android application such as bypassing functions which can detect if the Android device is rooted or not. It was easy to hook into a function which is running in the ART (Android Runtime) environment using the Java.perform.
However, there are instances wherein developers perform various action such as root detection using Android NDK which allows developing code in C++ or C language and accessing the functionality in your APK.
In this blog post we’ll look into how to dynamically instrument the code written using Android’s NDK i.e. hooking the functions developed in C++ or C using Frida.
Motivation to hook into Native Code:
Frameworks like Xposed by default do not provide the functionality to hook into native functions and other options such as android eagle eye are not easy to use involving a steep learning curve. However, using Frida it’s possible to hook into functions built using the Android NDK framework. So let’s see how this works:
The Target – Rootinspector:
In this post we’ll be making use of an Android application called Rootinspector which implements checks for root detection using native code written purely in C++.Our target would be to hook into those functions and bypass the root detection logic.
The root detection logic implemented in rootinspector has two parts to it. A wrapper function written in the APK which makes a call to the underlying C++ function i.e. checkifstream() and the java function implemented as checkRootMethodNative12(). This is shown in the figure below.
checkRootMethodNative12() is the function written in Java in the Android APK which calls the underlying C++ function checkifstream().
All the native functions declared in the Android APK are declared as shown below.
Upon inspection of the native source code, this function is implemented as Java_com_devadvance_rootinspector_Root_checkifstream which is nothing but the package name followed by the function name, separated by _ .
We first attempted to hook the Java function checkRootMethodNative12() using the code as shown below.
However, the above code failed to hook into the function with the error shown below. Frida was unable to obtain the reference of the “localRoot” object of the Root Class.
In such a scenario, attempting to hook the functions developed in C++ was not possible as they are running beyond the Java VM context. Hence, something different had to be done in order to hook into the native C++ code.
Hooking into the Native Code:
Using the Interceptor function of Frida, we can dive into a lower level of memory in the device and hook into a specified library or a memory location.
When the APK is packaged, the C++ code is compiled and placed into the lib directory of the unzipped APK file as shown below, named as “libnative.so”.
Using the Interceptor, we need to hook libnative2.so shared object and the functions defined as Java_com_devadvance_rootinspector_Root_checkfopen and so on.The exact function name needs to be extracted through reverse engineering by reading the .so file in an hex editor or your favourite debugger. We cheated here as we already were aware of the source code.
Lets fire the below code and check if we are successful in intercepting the checkfopen native function.
Upon executing the above code we get the error that a pointer was expected implying that either the libnative.so file was not loaded or it was not found.
However, after running the code again, keeping the App started, our code runs just fine. The below screenshot is taken, by first killing the above script, keeping the app open, running the script again and clicking on the “inspect using native code”.
It’s worthwhile to understand what exactly is happening in here. With Android version 1.5, Android NDK has provided the dynamic linker library (DLL in Windows context) to support dynamic loading in NDK. When we start the app for the first time the dll file (libnative2.so) is not loaded and hence we get an error message “expected a pointer”. Now, when we kill the script keeping the app open, and then re-run the script, it finds that the dll file has been loaded and we were able to hook into our target function.
Now instead of waiting for the dll file to load, we can put a trap on the “dlopen” function which is a native system call to load all the dynamic linker libraries associated with that app. Once the dlopen function is hooked, we check if our target dll has been loaded or not. If it is loaded for the first time then we can go ahead and hook into the native functions. There is a boolean check being performed using the didHookApi to avoid hooking into dlopen multiple times.
To be able to hook into the native function directly the below code was used. The code is divided into two parts.
In lines 17-30, we are first attempting to hook into the dlopen function using Frida’s Module.findExportByName API wherein we are performing an expansive search for the dlopen function (fingers crossed that this function has not been overridden) in the memory.
In the onLeave event, we are first attempting to check if our target DLL has been loaded or not; if it has, only then we would go about hooking into the native functions.
By executing the code we were able to bypass the Jailbreak detection mechanisms implemented by Root inspector through native functions the bypass is shown below.
Before Running the script:
Now closing the app and executing the script without starting the app.The script opens the app by itself. Just click on Inspect using Native.