banner
raye~

Raye's Journey

且趁闲身未老,尽放我、些子疏狂。
medium
tg_channel
twitter
github
email
nintendo switch
playstation
steam_profiles

Frida hook tool usage

375791

This article serves as a review of the use of Frida and provides some example questions, cover image via AI.

Common Frida Command Quick Reference#

frida-ls-devices # List all devices

frida-ps -U # List processes on USB devices

frida-ps -Ua # List running applications

frida-ps -Uai # List all installed applications

frida-ps -D xxxxx # Connect to a specified device, device ID can be obtained from the first command

# Start the application and inject the script; the difference between these two will be mentioned later
frida -U -l exploit.js -f com.xxx.yyy

# The application is already running, inject the script
frida -U -l exploit.js "XXXX"

Frida Basics#

frida is a hook framework based on Python and JavaScript, which can run on various platforms such as Android, iOS, Linux, and Windows, primarily using dynamic binary instrumentation technology.

Install frida and frida-tools using pip:

pip install frida
pip install frida-tools

Check the version of Frida installed with Python:

frida --version

Then install Frida and Frida-server on both PC and mobile, ensuring the versions match.

Next, run Frida-server on the mobile device and forward the mobile port to the PC so that the Python script on the PC can communicate with the Frida-server on the mobile. You can use the adb command to achieve port forwarding; this step does not seem to be mandatory, as it appears to work without forwarding as well. For example:

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

You can use the frida-ps -U command to view process information on the mobile device to select the target process to hook.

In summary:

  1. Download the corresponding Frida server from the official GitHub release page. Note that if using an emulator (like Nox), the corresponding architecture is x86 instead of ARM.
  2. Connect to the emulator via adb, push the downloaded server to the directory /data/local/tmp (usually this directory), and start it after running chmod a+x.
  3. Run pip install frida frida-tools.
  4. Enter frida-ps -U to see the list of processes on the mobile device, indicating successful configuration.

Writing and Running Hook Scripts#

Start and inject a hook script:

frida -U -l exploit.js -f com.xxx.yyy

Some simple writing usage, which will be easy to understand if you are familiar with Java reflection.

If you want to use a string, since java.lang.String is a class, you need to instantiate it (the same applies to other classes if you want to use class methods; if it's a static method, you don't need to instantiate):

var x = Java.use("java.lang.String").$new("xxxxxxxx")

If you want to use a static method of a certain class (class methods require an object to be instantiated first):

Java.use("com.xxx.yyy").method(param1, param2)

If you want to hook a certain method:

Java.perform(function() {
	var class = Java.use("com.xxx.yyy")
	class.methodName.implementation = function(param1, param2){
      send(); // Print log
			return this.xxxx	// Hook return value		
	}
})

That's about it for the basic usage.

Pitfalls & Tips#

Jadx supports directly copying Frida code snippets, which can save a lot of effort.

Untitled

Hook methods need to specify parameters:

// JS code
Java.perform(function () {
    var MainActivity = Java.use("com.example.MainActivity"); // Get class reference
    var a = MainActivity.a.overload("int", "int", "long"); // Specify parameter types of the overloaded method to hook
    a.implementation = function (x, y, z) { // Replace the implementation of the overloaded method
        console.log("x: " + x + ", y: " + y + ", z: " + z); // Print parameters
        var result = this.a(x, y, z); // Call the original overloaded method
        console.log("result: " + result); // Print return value
        return result; // Return the original value
    };
});

If some functions have no parameters, then overload does not need to be specified:

// JS code
Java.perform(function () {
    var MainActivity = Java.use("com.example.MainActivity"); // Get class reference
    var onCreate = MainActivity.onCreate.overload(); // Specify the overloaded type of the function to hook, without passing any parameters
    onCreate.implementation = function () { // Replace the implementation of the function
        console.log("onCreate() is called"); // Print log
        this.onCreate(); // Call the original function
    };
});

Example#

Hook MainActivity Method Example#

Refer to the official documentation example; the APK can also be downloaded: Android | Frida • A world-class dynamic instrumentation toolkit

Analyze the logic with jadx, and you can see that it requires this.n and this.m to differ by 1, and this.cnt+1 must equal 1000.

Untitled 1

So when will these variables be modified? Looking further down, we see the onClick method.

Untitled 2

Then we can write a JS script to hook the onClick method:

Java.perform (() => {
  // Select class
  const MainActivity = Java.use ('com.example.seccon2015.rock_paper_scissors.MainActivity');
  // Select onClick method
  const onClick = MainActivity.onClick;
  // Hook onClick
  onClick.implementation = function (v) {
		// Print a message when the function is called
    send ('onClick');
    // Call the original onClick method
    onClick.call (this, v);
    // After calling, modify m, n, cnt values
    this.m.value = 0;
    this.n.value = 1;
    this.cnt.value = 999;
		// Print result in Frida console
		// Hook ends
    console.log ('Done:'  JSON.stringify (this.cnt));
  };
});

Run the command:

frida -U -l .\seccon2015.js rock_paper_scissors

After execution, it will enter the Frida shell, and log messages will be printed.

Untitled 3

Frida also provides a way to call it using Python, but there is a pitfall in the code comments; I personally prefer using the JS method.

import frida, sys
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """ 
...copy the above jscode
"""

# Note that there is a pitfall here; when attaching, do not enter the full package name
process = frida.get_usb_device().attach('rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()

Found the reason:

import frida
process = frida.get_usb_device().enumerate_processes() # Enumerate all processes

At this point, the actual process name is found, so the code directly writes rock_paper_scissors instead of the full package name.

Process(pid=2982, name="rock_paper_scissors", parameters={})

Running effect:

Untitled 4

At this point, the application has printed the flag.

Untitled 5

Love Breaking Red Packet Third Question#

Analyze the logic with jadx; as long as the result returned by the check function equals 999, the flag can be obtained.

Untitled 6

Therefore, directly hook the check method under MainActivity to return 999.

function main(){
    Java.perform(function(){
        var activity = Java.use("com.zj.wuaipojie2023_3.MainActivity");
        var check_method = activity.check.overload(); // No overload here, so it is not needed

        check_method.implementation = function(arg){
            // send('check');
            check_method.call(this);
            return 999
        }
    })
 
}
setImmediate(main)

Click a few times to obtain the flag.

Untitled 7

Hook RSA Encryption#

In a competition, there was a level to crack an APK (source files lost). Using jadx to obtain the source code revealed that it was an RSA encryption, and there was a pitfall that took a long time to solve.

Android's RSA implementation is different from that in the Java package; you need to manually download a BouncyCastleProvider.jar package, import it into the project, and modify the code to run.

Actually, the simplest solution is to use Frida to hook.

The implemented code is as follows:

function main(){
    Java.perform(function(){
        var enc = Java.use("java.lang.String").$new("F3RSCi59ewpMm+dIGtiAn752tBQ0TPx7tCayrCXrrwyG2gtlUJ3C82rcrfgg2A4X7XfhXWGidsJgGDzbD9JwvzGbKMHTK5kxJUf1EJ605R/Rv2N+6Jtr54wQAKbqGyU8g/rUhLUf/EgNqPPYTQ6fa4LqFEMTrPm/UZMvL/4yTNhFoM5vQbceQeD9QNATh6kdYEzLfRdWbv/ESllURlgZbQsc7+pHC8Sg9wVh/2MGZHKzZI8a67Usqhk8ojmfgN5ABodQ2z1DOCICg7AH+l+3fx6eOn1dup/Q1hpCHVCgtojMOPfoLcxICY5LkSonrFjAKzv4JHO/f5ihFXX8Dr4MFw=="); // Instantiate a string
        var PrivateKey = Java.use("java.lang.String").$new("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcscawF6Fy4Smk\nqwOy2W/IcewNTYzHe9ZFV6w1dqrZ+sRBRD6Sapv7k4deLF9ziYsWXfcDNuvG2HN6\nAgk6CeN5QHUj7OiJmTnitHAQFIw7Ed6O3uyE64i3dhZIKh7oAies6EMpfKusXIZ6\nt1lFwVVpPjlrW6435j/nW5X0OeaRRk89zOxIbzAhS0yxArkMSecy9lr+UwzWQhFO\nW4+O/ywesds9VdCuycx2S/1zbG7BwJP507nTxstLkUnet0BHqXH3tH8cRXXDyFo1\n13gBgrBZ7Y79Z7T39B+21qq7RxfwvPDqu6Rq3p9cOUQCwfJSWlHYBVtYOME04vZP\nkZj/PqLhAgMBAAECggEBAL9QLRkBoydfISZqSW6FJ23vs91kWKGlLH44HkTKdhk4\nVWDus/9hA472uOZeOPCDT1s4YaUbuxBJGvGtSipR4CuAQQu/l2WQuqlYGb0W0ulx\n+CiJ/ybKt12ytGYifWBKXfJ59Z7FQFzOaJWA05awnkX5dvUmKMS0iLkan8dzWfic\nCn379Jn/bLmRXSTKgWNMi9toT4kNhnr2/cf1ah1i2MHHZb4ZMdC9rqnSvyzFRAFM\nuess0Qg8ckyoNzx2daP9vXapn8rg6B/MFBZqjorwyv0KJms/S6W/YKw88OixM/J3\nxlBsk8QHJqaL5IZW6/QeIHJk4K6o9+3fTxtHEjcWoAECgYEA8ASApzgATrHUFVdT\nZ6ZohLFMyQQZzaKLtZhBRfenASrWQSpHHcGdF15C45fik/vohZvF/byrHLkwvosp\nvC6BHyF7toJDrTEWBo/xymGNYmovr0keMUc3T4aRomaERz0gY8CLhKpHMKJrVtgj\nkHlAGqkRZB6AAlcEQUSOyvYziyECgYEA62Pg4xMw1BfJKSfLhCoaWHeWBMAbuZ8K\ncEOLIO8CtGKwXRLBe6MUKTDnHt7aXawVd/j6jk27uhD8p1noYqbsAexzMIKAAzic\nI6fsCVgUfvRZzhaQ98xyBO34AmAtzhJQEg65TnybyEf8tC5xPSegCzzYA5w8QfaM\necRq8V8R38ECgYEAkRNXpDuATBDGzaN8AXGfSV1VuUcmRZpTcg82nREFO/pliPwC\nAmVuC5rpOWIsDSC9ukyezzEECJeTdAjv0BQX7fYyLe3s9DlwQ8Ur9BUk/XCLpBrB\nCi4uq33+dzgaCmlTM5hFfInj/+ycjuCUFpaDfdnlbivVWhS4uK601M8d9qECgYEA\nuxel1ZaZRtqo/tcsgZ3dVtemG5x97OmmKFjnKoQOiEWwnCmeM6EJEzxVV0oWEkIG\nxlbU/2p1fYym5HUnqdG24EiJvdLb4LOMFyMPDtY9ZDLfdlilXO/Y6GYkq+66OYKA\ntfmR+/o679OX3bbUNqFaKdRwdV4m3t8SBG7D+Zlw4YECgYAp2B4F1WOu5H/P0yC6\n6Hn/FGcQec8ty6MCPlH1km9dQBS3jMvBAFpmnMv+hWiR4W93tOHRqRndR6VFC/dX\n+io76NklgjQGteUJ/Nawhg0sDpWCUGECRypwbNTgmhcrmu/RF1B22rO8sckPXXeB\nDzVmjKFXgpDDIJ0eDUTQbqEEMw=="); // Instantiate a string
        var encB64Decode = Java.use("com.chaos.view.example.Base64Utils").decode(enc); // Call Base64Utils's decode method
        var loadPrivateKey = Java.use("com.chaos.view.example.RSAUtils").loadPrivateKey(PrivateKey); // Call RSAUtils's loadPrivateKey method

        var res = Java.use("java.lang.String").$new(Java.use("com.chaos.view.example.RSAUtils").decryptDataPrivate(encB64Decode, loadPrivateKey)); // Call RSAUtils's decryptDataPrivate decryption method
        
        var qaq = Java.use("com.chaos.view.example.Qaq").decode(res, 127);
        console.log(qaq);
    })
 
}
setImmediate(main)

Call frida -U -l xxx.js.

OWASP MSTG Uncrackable 1 Bypass Root Detection#

The APK has root detection; using jadx to view the logic reveals that the judgment is written in the a.a function.

Untitled 8

Untitled 9

Following the a.a method, simply hook the return result to be true.

Untitled 10

First, bypass the root detection and find a script shared by an expert:

Frida CodeShare

Then try it, and successfully bypass the root detection.

frida -U --codeshare dzonerzy/fridantiroot -f "owasp.mstg.uncrackable1"

Next, hook it.

Java.perform(() => {
    // Find class a
    const a = Java.use('sg.vantagepoint.uncrackable1.a');
  
    // Find method a; although there is no overload, still specify the parameter type
    const a_method = a.a.overload("java.lang.String");
    a_method.implementation = function (v) {
      // Print a message indicating it has been hooked
      send('a method call');
  
      // Call the original method
      a_method.call(this, v);
  
      return true; // Hook the return value of the original function
    };
});

Then run your script with -l, and --codeshare is used to run the script that bypasses root detection.

frida -U --codeshare dzonerzy/fridantiroot -l .\uncrackable1.js -f "owasp.mstg.uncrackable1"

Untitled 11

OWASP MSTG Uncrackable 2#

The logic here is similar to the first one, but the method is located in the native lib, so you need to hook the function in libfoo.so.

The template for hooking native methods is as follows:

Java.perform(() => {        
        setTimeout(function(){
            Interceptor.attach(Module.findExportByName('libfoo.so', 'xxxx'),{
    
                onEnter: function(args){
								    // Modify the parameters when entering the function
                },
    
                onLeave: function(retval){
								    // Modify the return value of the function
                }
    
            });
        },2000);
});

Referencing the template code, you can write the following hook script:

Java.perform(() => {
        setTimeout(function(){
            Interceptor.attach(Module.findExportByName('libfoo.so', 'Java_sg_vantagepoint_uncrackable2_CodeCheck_bar'),{
    
                onEnter: function(args){
    
                },
    
                onLeave: function(retval){
                    console.log(retval);
                    retval.replace(ptr(1));
                    console.log(retval);
                }
    
            });
        },2000);
});

Note the pitfall here:

There are two ways to hook with Frida:

  • Inject
frida -U  -l .\uncrackable2.js "Uncrackable Level 2" # Inject into the corresponding process
  • Start
frida -U  -l .\uncrackable2.js -f owasp.mstg.uncrackable2 # Start the corresponding process

I initially used -f, which caused the program not to load libfoo.so at the beginning, leading to confusion for a long time, even thinking that the Nox emulator was unsupported.

The final code that works for both methods just needs to add setTimeout.

Note that the first run may fail, but the second will succeed.

frida -U --codeshare dzonerzy/fridantiroot  -l .\uncrackable2.js -f owasp.mstg.uncrackable2

Untitled 12

Refer to https://1337.dcodx.com/mobile-security/owasp-mstg-crackme-2-writeup-android for code to hook the strncmp method in native.

Java.perform(() => {
        console.log("[*] Hijacking the onClick button")
        var clazz_main = Java.use('sg.vantagepoint.uncrackable2.MainActivity$1')

        clazz_main.onClick.implementation = function () {
            console.log('onCLick() is replaced ');
            
        };

        console.log()
        console.log('[*] ACTION NEEDED: Insert the string "I want your secret asap" as input')
        console.log()

        
        setTimeout(function(){
            Interceptor.attach(Module.findExportByName('libfoo.so', 'strncmp'),{
    
                onEnter: function(args){
    
                    if( Memory.readUtf8String(args[1]).length == 23 && Memory.readUtf8String(args[0]).includes("I want your secret asap")){
                        console.log()
                        console.log()
                        console.log("*******SECRET********")
                        console.log(Memory.readUtf8String(args[1]))
                        console.log("*******SECRET********")
                        console.log()
                        console.log()
                    }
    
                },
    
                onLeave: function(retval){
    
                }
    

            });
        },2000);
});

Run it:

frida -U  -l .\uncrackable2.js "Uncrackable Level 2"

OWASP MSTG Uncrackable 3#

To be continued...

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.