Introduction to Frida

Frida is a dynamic instrumentation toolkit for developers,
reverse-engineers, and security researchers.

Project requirements

Required tools to follow along:

  • Java decompiler (JD-GUI)
  • Android emulator (Genymotion)
  • Dynamic instrumentation toolkit (Frida)

You’ll need to download 3 files from here: https://github.com/frida/frida/releases

  • Python-frida
  • Python-frida-tools
  • Frida-server-android

Depending on your distribution, you can easily install the first two and their dependencies. As for the frida-server-android, I’m going to walk you through the installation and emulator setup.

The Android application

I created an Android application just for demonstration and testing purposes. I’m going to use it during the examples, you can download it from Github: https://github.com/t0thkr1s/frida-demo/releases

Creating a virtual device

I added a new Genymotion virtual device with Android version 5.0 (API 21).
The setup is pretty straightforward just the usual next, next and finish. It’s time to download the Frida Server for the Android client. Don’t forget to check the correct architecture! Next, we need to upload the server to the emulator. I installed Genymotion in the /opt directory

t0thkr1s@btksoftware:/opt/genymobile/genymotion/tools$ ls
aapt  adb  glewinfo  lib64

Uploading the file:

./adb push ~/Downloads/frida_server /data/local/tmp/

Changing file permissions:

./adb shell "chmod 755 /data/local/tmp/frida_server"

Running the server in detached mode:

./adb shell "/data/local/tmp/frida_server &"

Now, the emulator is ready and the server is running!

Reverse engineering

In order to understand the inner workings of an application, we need to reverse engineer it. Fortunately, we can restore the java source files easily.

I’m not going to write about reverse engineering Android apps here, because I already did it in my previous post. Check it out!

I have to admit that the reverse engineering of the demo application reveals all the secrets hidden in it. So, in order to make it more realistic let’s suppose the encryption key is generated from the user-provided PIN code which is used to encrypt private data in the app.

In this case, brute-forcing the PIN code might be a good solution for compromising the security of the whole app. That’s why you need to choose long and strong PINs.

PIN Bypass

Okay, you looked through the reversed source code and you found a method, which checks if the provided PIN is correct or not.

Spoiler: The PIN is in the strings.xml file.

Most of the time, it’s not that easy… Let’s suppose, we don’t know the PIN. You found the PinUtil class and the boolean checkPin(String pin) method. This checks the pin and returns true if the pin is correct, otherwise, it returns false.

The idea here is that we don’t need to know the pin just return true and we’re in. The following python script does just like that. I wrote a little Javascript code using the Javascript API and hardcoded it in the python script. Basically, it uses the PinUtil’s checkPin() method and overrides the return value. It’s that easy. Next, you need to specify the package name of the application to attach Frida, then load the script and wait for the log messages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import frida, sys
jscode = """
Java.perform(function() {
console.log("[ * ] Starting implementation override...")
var MainActivity = Java.use("infosecadventures.fridademo.utils.PinUtil");
MainActivity.checkPin.implementation = function(pin){
console.log("[ + ] PIN check successfully bypassed!")
return true;
}
});
"""

process = frida.get_usb_device().attach('infosecadventures.fridademo')
script = process.create_script(jscode)
print('[ * ] Running Frida Demo application')
script.load()
sys.stdin.read()

PIN Brute-force

Previously, I mentioned that knowing the PIN could be really beneficial. In this example, I going to show you how to brute-force with Frida.

First, let’s suppose that the PinUtil’s checkPin(String pin) method is not static. By using Java.choose, we can search the memory for a PinUtil instance and the onMatch is called when the instance is found. Then, we can use that instance’s method in a loop to test all numbers with a length of 4. This is actually not a time-consuming process. You can even try brute-forcing numbers with a length of 5 and finish in a day depending on the number.

The PinUtil’s class checkPin(String pin) function is static. This means that we don’t need to search for the PinUtil object in the memory just call the method using the class name. However, I implemented both (static and non-static solution) in the script below. I hope it’s not confusing. The jscode variable will be overridden by the second assignment and that will be used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import frida, sys

# For non-static classes
jscode = """
Java.perform(function() {
console.log("[ * ] Starting PIN Brute-force, please wait...");
Java.choose("infosecadventures.fridademo.utils.PinUtil", {
onMatch: function(instance) {
console.log("[ * ] Instance found in memory: " + instance);
for(var i = 1000; i < 9999; i++){
if(instance.checkPin(i + "") == true){
console.log("[ + ] Found correct PIN: " + i);
}
}
},
onComplete: function() { }
});
});
"""

# For static classes
jscode = """
Java.perform(function () {
console.log("[ * ] Starting PIN Brute-force, please wait...")
var PinUtil = Java.use("infosecadventures.fridademo.utils.PinUtil");

for(var i=1000; i < 9999; i++)
{
if(PinUtil.checkPin(i+"") == true){
console.log("[ + ] Found correct PIN: " + i);
}
}
});
"""

process = frida.get_usb_device().attach('infosecadventures.fridademo')
script = process.create_script(jscode)
print('[ * ] Running Frida Demo application')
script.load()
sys.stdin.read()

Root Check Bypass

I included this example because it’s quite common in banking and other applications to restrict rooted device access. It’s a simple check and very, very similar to the PIN bypass example.

I encourage you to write the script yourself and check back, when you finished!

Finding the Encryption Key

Now, everything in this script should also be familiar to you. You can log a method’s incoming parameters and return normally. This way, we have the ability to log the encryption key used and also the plain text. Again, the key is hardcoded in the code, but you won’t always be this lucky in real life. Here is how I implemented this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import frida, sys

jscode = """
Java.perform(function() {
console.log("[ * ] Starting implementation override...")
var EncryptionUtil = Java.use("infosecadventures.fridademo.utils.EncryptionUtil");
EncryptionUtil.encrypt.implementation = function(key, value){
console.log("Key: ");
console.log(key);
console.log("Value: ");
console.log(value);
return this.encrypt(key, value);
}
});
"""

process = frida.get_usb_device().attach('infosecadventures.fridademo')
script = process.create_script(jscode)
print('[ * ] Running Frida Demo application')
script.load()
sys.stdin.read()

Modify these script and experiment with them.
It’s the best way to learn new things!

Before you go

If you found this article helpful, please share to help others with similar interest find it! + Feedback and donations are always welcome!