Exploring Native Functions with Frida on Android — part 3

Using Frida to explore libraries during runtime

The Mobile Security Guys
5 min readMay 27, 2020

In part 2 we moved into a dynamic approach at investigating native libraries using frida-trace and frida CLI. We leveraged the power of the API to construct our own scripts to get useful information from these native functions; however we were left with two considerations.

The script can only enumerate the modules loaded at its execution — not during the app lifecycle.

Using the memory base address and the size of library, monitor the memory to extract useful values.

We will be further exploring the Frida API; and using code examples, try to solve these issues.

Exploring Runtime Lifecycle Libraries

The first point can be resolved using the Interceptor API, which, as the name suggests lets us intercept a target function. We are interested in any library that is opened at any time during the app lifecycle in order to enumerate its exports, as opposed to just at app execution. This is due to certain libraries not being called until a later stage when certain functionality is triggered by the user.

By using Interceptor.attach() it is possible to intercept calls of a desired target function while the app is running.

It is also possible to associate callbacks (functions to do something when the target function is intercepted) specified with the keyword onEnter and onLeave, respectively for when the target function is called but not executed, and when the target function is executed.

The only required input of this API is a pointer specifying the current memory address of the desired target function. Therefore, either we provide the actual address if it is known, or we use Module.findExportbyName() inline, which will return a pointer to the function given as input, in this case ‘open’ from the system libc library or null.

A script to list out libraries at runtime, compared to app execution.
Using Interceptor to list out runtime libraries

It is worth noting again that, when using Module.findExportbyName() it should be given an explicit library name as input, using ‘null’ only when the library is unknown or the desire is to scan all modules.

Let us now discuss the function explorerOpenedLibs().

  • It first intercepts any function found with the name “open” (a proper C function not a JNI one).
  • As a string we read the first input given to open() represented by args[0], which represents the file-path to be opened.
  • Checking if this file-path string contains “.so”, we can determine that it is a native library and we can extract its name by using the JavaScript function .split().
  • At this point we use another API, Process.findModulebyName(), which will return the module specified by its name if it is found, otherwise it will return null.
  • If a match is found, we then print out its exports using the same code from previous scripts.

Explicit Library Loading

Let us take another scenario, for instance, when we know the specific module we wish to target and the function to be called, yet neither during app execution or runtime the function is ever called.
In such circumstances it is possible to manually load a specific library and target a certain call explicitly.

Let us revisit our app, myBank, with the fork of the OpenSSL native library, openssl_mybank.
It is important to note that, in order to avoid any errors, all the related libraries need to be loaded as well. In our scenario the myBank OpenSSL library also requires another library, crypto_mybank.

Script used to explicitly load a library manually rather than via the app
Explicitly loading a library manually rather than via the app

The JavaScript function loadMyBankLibSSL() loads the specific library by using Module.load(), using the absolute path to the library and prints out its respective information. This is done for both libraries that are required.
Then we use the Module.findExportByName() to find the pointer to the memory address of the particular function we are interested in, in our example, X509_verify_cert.

Module and export info for the loaded libraries.

An output generated by a variant of this script shows the output of the libraries being loaded, the base addresses of these, as well as the export address of the function we are interested in. This goes to show this function actually exists, otherwise it would have been ‘null’.

Hooking Native OpenSSL Functions

Using all the techniques we have looked at we now have enough knowledge to build a script to combine all these functions together and start using callbacks to provide useful values.

Using callbacks, we can inspect function arguments - onEnter, or any returned values - onLeave.

The complete function script hookOpenSSLVerifyCert() is shown below;

  • First check if the library we are interested in is ever opened by specifying the full absolute file path (var str).
  • Retrieve the module (var module)
  • Print out some information from it and attempt to retrieve the address of the function X509_verify_cert() that we are interested in (var myExportAddr).
  • If the function is found, therefore the returned value is not null, pass this address as input to Interceptor.attach()
  • Implement the callbacks for onEnter and onLeave that will be executed when and if the function is ever used by the running app.
  • Inside these callbacks, print out the value for the first argument of the function args[0], using JSON or Memory, as well as the returned value.

Finally, the output generated by our script allows us to observe different values for our defined callbacks, onEnter and onLeave.

Complete script to view module exports and associated arguments and return values

The detail in this post allowed us to find a way around a shortcoming of frida-trace, or standard Frida CLI. The use of Interceptor means we can get ahead of the game and trace modules even if they’ve not yet been called by the running app; while the use of callbacks lets us determine how a specific native function is behaving on its return and how this could affect further behaviour of the app.

In the final post of the series, we will look at how we can modify these callback values and defeat certain protections that have been put in place via native code as a deterrent to attackers.

--

--

The Mobile Security Guys

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