Precut corners and other tidbits all about Frida.

Android

Hook an overloaded Java function

Java.perform(function() {
    var str = Java.use('java.lang.String'), objectClass = 'java.lang.Object';
    str.equals.overload(objectClass).implementation = function(obj) {
        var response = str.equals.overload(objectClass).call(this, obj);
        if (obj) {
            if (obj.toString().length > 5) {
                console.log('what was I doing here')
            }
        }
        return response;
    }
});

Hook two functions that have the same parameters and name, but different return types

Say we have the following decompiled Java class:

public class c{
    public c(){

    }

    public static int a(int a){
        return 1;
    }

    // This is the method we want to hook
    public static String a(String a){
        return "Hello";
    }
}

Because the a methods both have the same parameter types, it can be hard to hook the correct one. The strategy you want to apply here is to inspect the return type of the overload objects, and then only create a hook on the one that your interested in.

Java.perform(function() {
    const helper = Java.use('com.example.testapp3.c')
    const overloads = helper.a.overloads
    overloads.forEach(overload => {
        // Assuming we want to hook the method that returns a string
        if (overload.returnType.name == 'Ljava/lang/String;'){ 
            overload.implementation = function(args){
                // Your hook here

                return overload.call(this, args)
            }
        }
    })
});

Stacktrace

console.log(
    Java.use('android.util.Log')
        .getStackTraceString(Java.use('java.lang.Exception').$new())
    )

Enumerators

To modify an existing enumerator, see below:

Java.perform(() => {
    // Hooking logger so we can get some detail back
    let Logger = Java.use('android.util.Log')
    Logger.i.overload('java.lang.String', 'java.lang.String').implementation = function(tag, message){
        console.log(tag + ':' + message)
        return 0
    }

    // Hooking the function that uses Colour
    let MainActivity = Java.use('com.example.testapp.MainActivity')
    MainActivity.test.implementation = function(colour){
        colour = colour.RED.value // Overwrite the colour with the new value
        return colour
    }

    // This should print 'red', instead of 'orange'
})

It’s also possible to create a new enumerator as follows:

let hCarEnum = Java.use("com.dealership.CarsEnum")
hCarEnum.getCarType.implementation = function(car){
    return hCarEnum.MAZDA.value
}

iOS

Hooking Objective-C functions

Interceptor.attach(ObjC.classes.NSFileManager['- fileExistsAtPath:'].implementation, {
    onEnter: function (args) {
        console.log('open' , ObjC.Object(args[2]).toString());
    }
});

Native

static fingerprint(addr, offset = 0, size = 64) {
        console.log(hexdump(ptr(addr), {
            offset: offset,
            length: size,
            header: true,
            ansi: true
        }));
    }

Software Breakpoints

Basically a copy paste from Giovanni Rocca, who has some excellent blog posts about the topic.

const SIGTRAP = 5

// Firstly, get a pointer to the signal function
const signal = Module.findExportByName(null, 'signal');
let breakpoints = {}
if (signal === undefined){
    console.log('No libc?')
}else{
    // Get the native signal function
    const _signal = new NativeFunction(signal, 'void', ['int', 'pointer']);

    var buffer = Memory.alloc(8)
    Memory.protect(buffer, 8, 'rwx');

    // Use frida to create a new signal handler
    Interceptor.replace(buffer, new NativeCallback((_) => {
        Thread.sleep(30) // You'd make this much lower and loop it
        return
    }, 'void', ['int']))
    _signal(SIGTRAP, buffer)

    Interceptor.attach(Module.findExportByName(null, 'puts'), {
        onEnter:function (){
            Module.findExportByName(null, 'puts').writeU8(0xCC)
        }
    })
}

Faux Software Breakpoints

A read|write|execute breakpoint can be created on the symbol main, as follows:

// Assume we can find the entryPoint
// Please note the ptr(parseInt(...)) calls; without you will get `Error: expected a range object or an array of range objects`
const entryPoint = ptr(parseInt(DebugSymbol.fromName('main')))

let breakpoints = {}

// Breakpoint that's triggered when the memory is read|write|executed (single use)
MemoryAccessMonitor.enable({base: entryPoint, size: 1}, {
    onAccess: function (details) {
        breakpoints[entryPoint] = false
				// Use details.operation to filter
        console.log(`Breakpoint at ${DebugSymbol.fromAddress(entryPoint)} triggered`)
        while (breakpoints[entryPoint] === false) {
            Thread.sleep(1)
        }
    }
})

// Hook based breakpoint (works until the hook is removed)
Interceptor.attach(Module.getExportByName(null, 'puts'), {
    onEnter(args) {
        console.log(`Breakpoint at ${DebugSymbol.fromAddress(this.context.pc)} triggered`)
        breakpoints[this.context.pc] = false
        while (breakpoints[this.context.pc] === false) {
            Thread.sleep(1)
        }
    }
});

Stacktrace || Backtrace

To list the path of execution a program took everytime open was invoked, try the following:

Interceptor.attach(Module.findExportByName(null, "open"), {
  onEnter: function(args) {
    this.flag = false;
    var filename = Memory.readCString(ptr(args[0]));
    console.log('filename =', filename)
    if (filename.endsWith(".xml")) {
      this.flag = true;
      // Here is an example of how to throw a native backtrace
      var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).
                        map(DebugSymbol.fromAddress).join("\n\t")
      console.log("file name [" + Memory.readCString(ptr(args[0])) + "]:")
      console.log(backtrace)
    }
  },
  onLeave: function(retval) {
    if (this.flag) // passed from onEnter
      console.warn("\nretval: " + retval);
  }
});

We can also do a forward trace using Stalker, this can get a little complicated

Interceptor.attach(DebugSymbol.fromName("main").address, {
    onEnter: function(args){
        Stalker.follow(this.threadId,
            {
                events: { call: true },
                /*
                    Since we are tracing calls, events in this case represent
                    any function call
                */
                onReceive: function (events) {
                    console.log("Stalker Calls:")
                    let calls = Stalker.parse(events, {annotate: true})
                    calls.forEach(event => {
                        console.log(DebugSymbol.fromAddress(ptr(event[2])))
                        })
                    },
            }
        )
    },
    onLeave: function(ret){
        Stalker.unfollow(this.threadId)
    }
})

The output should be fairly readable:

0x55e662c82165 a.out!createParser
0x55e662c82139 a.out!parser_getArgCount
...
0x55e662c8214f a.out!parser_getEntry
...
0x55e662c8214f a.out!parser_getEntry
...
0x55e662c8214f a.out!parser_getEntry

I used the following driver on debian - for whatever reason I couldn’t resolve debug symbols on MacOS:

#include <stdio.h>
#include <unistd.h>

 void parser_getArgCount() {
    puts("There are 3 args");
}

 void parser_getEntry() {
    puts("Returning entry");
}

 void createParser(){
    puts("Creating parser");
    parser_getArgCount();
    parser_getEntry();
    parser_getEntry();
    parser_getEntry();
}

int main() {
    createParser();

}

Attaching to unexported symbols

let symbol = ptr(DebugSymbol.fromName('check_zxcvbn').address
Interceptor.attach(
    symbol, 
    {OnEnter: ..., OnLeave: ...}
)

Reading from a byte buffer

readByteArray(n) reads n bytes from memory, but is returned as a JS ByteArray. In order to index this byte buffer, it needs to be typecast to a typed byte array as seen below:

let buffer = new Uint8Array(args[0].readByteArray(64));
console.log(buffer[0])

Misc

Kotlin Continuations

It’s common to see the use of a Continuation<T> and it can be frustrating if you want to test something that requires a parameter like this.

A continuation has the following definition:

/**
 * Interface representing a continuation after a suspension point that returns a value of type `T`.
 */
@SinceKotlin("1.3")
public interface Continuation<in T> {
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext

    /**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */
    public fun resumeWith(result: Result<T>)
}

This example uses the following code:

import kotlinx.coroutines.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

suspend fun fetchData(): String = suspendCoroutine { continuation ->
    // Simulate fetching data asynchronously
    Thread {
        Thread.sleep(1000)
        continuation.resume("Data fetched")
    }.start()
}

fun main() = runBlocking {
    println("Start")
    val data = fetchData()
    println(data)
    println("End")
}

To hook this, we need to create our own continuation, the following example illustrates this:

Java.perform(() => {
    const hContinuation = Java.use('kotlin.coroutines.Continuation'); 
    Java.registerClass({
      name: 'com.constbox.Continuation', 
      implements: [hContinuation],
      methods: {
        resumeWith: function() {
            console.log("Continuation resumed")
        },
        getContext: function(){
            return null
        }
      }
    });
  
    const customImpl = Java.use('com.constbox.Continuation');
    const customImplInstance = customImpl.$new();

    const hMainActivity = Java.use("com.example.myapplication.MainActivity")
    hMainActivity.fetchData.implementation = function(){
        return this.fetchData(customImplInstance)
    }
})

It does nothing, which is fine for what I’m using it for. If you wanted to return a specific value, something like this might work:

hMainActivity.fetchData.implementation = function(oldContinuation){
        oldContinuation.resumeWith("Foobar")
        return this.fetchData(customImplInstance)
    }

Again, YMMV, I looked at this very briefly over a weekend.

Hooking classes when a custom class loader is used

See mame82’s post about this.

function getClassHandle(name) {
   return new Promise((resolve, reject) => {
       Java.perform(() => {
           const loaders = Java.enumerateClassLoadersSync()
           let found = false

           // Try to get a handle using each of the class loaders
           for (let i = 0; i < loaders.length; i++) {
               const loader = loaders[i]
               const factory = Java.ClassFactory.get(loader)

               try {
                   const klassHandle = factory.use(name)
                   resolve(klassHandle)
                   found = true
                   break
               } catch {

               }
           }
           if (found == false) {
               reject()
           }
       })
   })
}


// Example Usage
getClassHandle("java.lang.String").then(javaString => {
   javaString.toString.implementation = function(){

   }
})


// Await example

async function main(){
   let javaString = await getClassHandle("java.lang.String")
   javaString.toString.implementation = function(){

   }
}
main()

Something that is super useful about this, is that you can use it with frida-trace -Uf example.com -j 'com.lateloaded.Class!method' -S prep.js. Where prep.js only needs to use getClassHandle on one of the classes that you know is injected by something like dagger. Now frida-trace will find this class when it starts :)