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
Print Memory
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 :)