Testing applications for Android: analysis and changing the way applications work by using the Frida framework

What is Frida?

Frida, as the website of this project says, is a world-class dynamic instrumentation framework. To simplify: a framework that will allow us to inject our own code into a working process (it can be a process on Android, but it also supports iOS, Windows, Linux or macOS), and then to control this process from the javascript code. If this is not clear, the example of a few paragraphs below should dispel all doubts

While the code that will be injected into another process will be written in JavaScript, the Frida support can be done in other programming languages, for example in Python. Therefore, the easiest way to install Frida is to use the command:

This will install the frida, frida-kill, frida-ps, frida-discover, frida-ls-devices and frida-trace tools. For Frida to be able to inject into processes on a mobile device, we also need to run an appropriate binary with a server on it. Binaries can be found on the github of the project. We should look for files whose name starts with: frida-server. In case of my device, the correct file will be: frida-server-10.1.1-android-arm. The file must be unpacked and thrown on the android phone, and then run on the phone with root privileges. The file is best copied with the adb tool:

The Frida server does not print out any messages when it starts; if everything is OK, it just works.

Before we begin

For the presentation of Frida, I will use the same android application as in my previous text. As a reminder: the application tries to download a file from the path https://raw.githubusercontent.com/securityMB/random-stuff/master/apk-file.txt and display it. By default, however, this will certainly not work because of the way certificates are implemented. For greater clarity of text, below I recall the decompiled code of both classes appearing in the application pl.sekurak.ssltest:

Listing 1. Class pl.sekurak.ssltest.MainActivity:

Listing 2. Class pl.sekurak.ssltest.a

ThThe code that will be of particular interest to us is contained in lines 13-21 of listing 2. This is where the correctness of the certificate is checked in the checkServerTrusted method. The specificity of this method is that if the certificate is correct, the method returns nothing; if the certificate is incorrect, the exception is returned.

The code of the checkServerTrusted method specified above is only an example. It is not a valid way to validate SSL/TLS certificates, and you should never place a similar code in your application. You can read about the correct approach to the subject of checking certificates in the Android documentation.

Ultimately, we will want to change the operation of the method so that it never throws an exception.

Testing Frida

So we return to Frida. We assume that we already have Frida running on a mobile device. The first thing we are interested in is process listing. We will use the frida-ps command for this. We will pass to it the parameter -U, which means that Frida will try to communicate with the device connected via USB.

We learn that the PID of the process in which we are going to inject is 9717. So let’s try to inject into the process with a recommendation:

Thanks to the -enable-jit parameter, our JavaScript code will work much faster (although it may be more memory consuming). After correct execution of the command, we should see an image similar to Picture nr 1.

picture nr 1 Executing “Frida” command

In the console we can now enter the JS code, which will be executed in the context of the android application.

Importantly, when running on Android, we should always place our code inside the Java.perform call – thanks to this Frida assures us that the code is executed in the context of a Java virtual machine.

Listing of existing classes

So let’s first try to list all the classes that exist in the running application. In Frida we have Java.enumerateLoadedClasses method, where the argument is a JS object with two fields:

  • onMatch – a call back performed when a class is found
  • onComplete – call back executed at the moment of completing the operation

Let’s write down all classes existing in the program with the code below:

After executing the code, we get gigantic list of every class (Picture nr 2)

picture nr 2 Listing of classes in application

Calling methods on existing instances of classes

From the Frida level we can also call methods on existing classes in the application. The Java.choose() function looks for living instances of the class that we will pass on as an argument. Similarly to enumerateLoadedClasses, the second argument is an object with onMatch in onComplete call back definition. So let’s try to look for a live instance of class pl.sekurak.ssltest.a (this is a class from listing 2). If there is one, just write an appropriate information in the console. We will use the following code:

On picture nr 3 we can see the result of executing a code.

picture nr 3 We search for live class instance

The entry pl.sekurak.ssltest.a@236c1ee2, which appears on the screen, is probably well known to all Java programmers. This is the result of a standard calling of the toString() method on the class object. Conclusion: class pl.sekurak.ssltest.a in memory exists!

So now let’s call some method on this class. For example, simply checkServerTrusted by passing two null arguments:

We can see result in Picture nr 4

picture nr 4 Calling the checkServerTrusted method on the class

As a result of execution of the code, there was an exception – NullPointerException. It is expected as much as possible. Let’s remember the code of the checkServerTrusted method:

In the third line there is a reference to the zero index of the array paramArrayOfX509Certificate. Before that, however, there is no way to check if this object is not a null. Hence, naturally: NullPointerException, as we passed null in the function call.

Performance variation of the method

We have shown that we can call up the checkServerTrusted method, but now we would like to change its operation: it should not give up exceptions, but always end its operation immediately.

To do this, we must first refer to the class in which this method is defined and then change its implementation property to the action that is expected by us. We use Java.use to refer to the class itself:

The code will work, of course, but in our application we have a problem because the code tries to download the contents of the file through HTTPS just after starting. So, changing the method during the process is no longer sufficient. Therefore, we have to start a new process on the phone ourselves and change the method as soon as possible. For this purpose we will use the code written in Python according to the template below:

In the CODE variable, we define the same JS code as we pasted in the Frida tool. In the further part of the programme, however, we have a proper preparation of the environment to connect Frida to the process immediately after its launch.

Picture nr 5 shows the view from the application execution without Frida, and Picture nr 6 – with Frida.

picture nr 5 Turning on test application without Frida
picture nr 6 Turning on test application with Frida

The attempt to change the method was successful. As a result, the application does not check the SSL certificate in any way.

Using Frida to log in to called methods

The last interesting case of using Frida, which I am going to show in this article, is the logging of methods called; i.e., with which arguments a given method was called. It is very useful when testing mobile applications, when on the basis of code analysis it is sometimes difficult to say what exactly is the purpose of a given function, and on the basis of the analysis of its mode of operation (arguments and returned value), it can be stated in no time at all.

In MainActivity class, the setText method is used to set the text visible in the mobile application (such as “Wow, you made it!”):

So let’s try to capture all calls of setText method in TextView class. Rapid shaving will allow us to say that the full class name is: android.widget.TextView, and the setText method is overloaded and can take different combinations of arguments. Let’s not think about what we want to intercept exactly. Let’s capture all of them 😉

If the Java method is overloaded, we can get all the ways to call it by accessing the class.method.overloads object. Therefore, in the code below I will refer to all setText methods, write on the console what arguments were passed on and call the original method:

The effect can be seen in Picture nr 7.

picture nr 7 Intercepting setText calls

Summary

Frida is a very complex framework, which can significantly help in the analysis of the behaviour of mobile applications, thanks to the possibility of carrying out the following operations:

  • Analysis of classes living in memory
  • Call up any methods on classes living in memory
  • Capture and replacement of methods
  • Logging arguments passed to called methods

Frida offers a much wider range of possibilities than described in this text; for those interested, I recommend that you have a look at the documentation.