Mastering Frida: A Guide to Fixing Early Instrumentation in Gadget Mode

Introduction

One of the challenges faced in Frida’s Gadget mode is an issue where the process you’re injecting into resumes immediately after injecting the JavaScript. This behavior prevents early instrumentation, which can be crucial for certain analysis tasks. In this blog post, I will guide you through a workaround to fix this issue.

When it comes to dynamic analysis and instrumentation of applications, Frida stands out as an indispensable tool in the kit of developers, reverse engineers, and security researchers alike. Its Gadget mode, in particular, offers unparalleled flexibility, allowing users to inject custom scripts into their target applications seamlessly. However, there’s a snag in the system that’s been tripping up professionals—early instrumentation.

Imagine setting up a meticulous script, ready to unravel the inner workings from the very inception of a process only to find that Frida has already allowed the application to resume before your instrumentation could catch the critical initial functions. This challenge has been particularly pronounced with the latest release, version 16.1.11, posing a significant obstacle to those needing to hook functions at the outset of program execution.

This blog post is forged from the need for a solution—crafted for persistence, it’s a beacon of hope for those grappling with this issue. Herein, we’ll walk through the intricacies of Frida’s Gadget mode and unveil a strategic approach to modify the resume_on_attach flag, a crucial step in gaining the control you require for early instrumentation. By the end of this guide, you’ll have mastered the method to hold the execution reins, ensuring your scripts are primed to hook at the starting line.

Join me as we delve into this technical deep-dive, transforming a point of frustration into a triumph of technical prowess.

Choosing Gadget Mode over Server Mode

In gernal, Frida provides 2 modes for injection, Sever mode and Dadget mode. I am debug an ARM32 program on an embedded patlform. And I tested, the server mode is not very reliable , may because the limit RAM on the dev board, and the dev board may reboot after I start frida-server, It’s very annonying, So I select Gadget mode,and reboot issue goes away. Frida offers two distinct modes for script injection: Server Mode and Gadget Mode. When working with ARM32 programs on embedded platforms, one must consider system constraints. My experience revealed that Server Mode could be unstable, potentially due to the limited RAM available on the development board. This instability often resulted in the board rebooting after initiating frida-server, which was disruptive to the debugging process.

Given these challenges, I opted for Gadget Mode. This alternative proved to be more stable under the same conditions, eliminating the frustrating reboot issues. Gadget Mode’s lightweight footprint makes it better suited for environments with resource limitations, thereby providing a more seamless and reliable debugging experience for ARM32 embedded systems.

The Solution: Modifying resume_on_attach

The root of this issue lies within the default behavior of Frida’s Gadget mode, which is controlled by the resume_on_attach field of the ControlChannel class. This field dictates whether the process should resume immediately after Frida attaches to it.

Step by Step Fix

Here’s a step-by-step guide to addressing this issue:

1. Modifying the ControlChannel Class

Locate the ControlChannel class definition within the frida-core/lib/gadget/gadget.vala file. Search for the resume_on_attach field and change its default value to false.

1
2
3
4
class ControlChannel {
private bool resume_on_attach = false; // Change from true to false
// ...
}

2. Compile Frida with Modified Code

After making this change, recompile Frida to incorporate the modification. Ensure you have all required dependencies and follow the standard build instructions provided in the Frida documentation. This github repository provides docker images for compiling Frida.

3. Using the Modified libGadget.so

Replace the original libGadget.so with the newly compiled version. With this change, the process will no longer resume immediately, allowing your JavaScript to hook functions at the very start of the execution.

4. Utilizing Python Script for Process Resumption in Frida Gadget Mode

While working in Frida Gadget mode, a common approach to inject a JavaScript payload into the target process would involve using the frida command from the Frida-tools package, like so:

1
frida -H <Board IP address> -n Gadget -l <Javascript file>

Executing this command initiates the injection, but it doesn’t automatically resume the process once the JavaScript file is loaded. Attempts to resume the process manually with %resume in the Frida REPL might not yield success.

To overcome this hurdle, I developed a Python script—a more reliable solution. The script not only injects the JavaScript code but also effectively resumes the process afterwards. Below is the functional part of the script where the device.resume(pid); command is used to resume the process:

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
# Python script to inject JavaScript and resume process in Frida Gadget mode
import frida

# Establish a connection to the target device
device = frida.get_device_manager().add_remote_device("<Board IP address>")

# Specify the target process identifier (replace 'pid' with the actual process ID)
pid = 'pid'

# Path to the JavaScript file you want to inject
js_file_path = "<Javascript file>"

# Read the JavaScript code from the file
with open(js_file_path, 'r') as file:
js_code = file.read()

# Inject the JavaScript code into the process
session = device.attach(pid)
script = session.create_script(js_code)

# Load and execute the JavaScript code
script.load()

# Resume the process to continue its execution with the injected code
device.resume(pid)

print("JavaScript injected and process resumed.")

By employing this script, we ensure that the injection and process flow are controlled as expected, thus maintaining the stability of the debugging session.

Conclusion

This blog post addressed early instrumentation challenges in Frida’s Gadget mode and provided a solution by modifying the resume_on_attach flag. Gadget mode was recommended over Server mode for ARM32 programs on embedded platforms due to its stability. A Python script was introduced for injecting JavaScript and resuming the process, ensuring a controlled debugging session. Overall, this post offers a solution for fixing early instrumentation in Frida, empowering developers and researchers to gain insights into application behavior from the beginning.

Inline hook with Frida (Thumb version)

Interceptor.attach in Frida only can hook one function. So the first argument, target, should be a function address. In my opinion, inline hook just let users can hook arbitrary instruction at any address. In this article, I will try to introduce a method to implement inline hook using Frida. and only implement for Thumb binary so far.

Method

To implement inline hook, we need to do the following thing:

  • Save original bytes at hook target
  • Write a hook handle function in .so file in C/C++.
  • Write a trampoline code in Thumb. And put original bytes into trampoline code.
  • Put a BL instruction at hook target.

I will describe every stage in detail, and for convenience, I will user the following terminology:

Term Description
hook_ptr The address of hook target, same as the target argument in Interceptor.attach
hook_fun_ptr The address of hook handle function, we define this function in .so file
tempoline_ptr The address of trampoline code

So if we set every code correctly, once CPU reaches hook_ptr, CPU will call trampoline code as a function. And the trampoline code will call hook handle function and execute the original instruction copy from hook_ptr. And we can do anything we want in hook handle function.

Save original bytes at hook_ptr

We will put a BL instruction at this address, so we need to save original bytes at first. In Thumb, a BL instruction occupies 4 bytes. So we need to save at least 4 bytes. It may include 1-2 Thumb instructions at hook_ptr, and we must be careful in selection of a hook_ptr.

  • Avoid odd hook_ptr
  • Do not select a hook_ptr at a middle of a Thumb instruction
  • Do not select a hook_ptr at a 2 bytes Thumb instruction followed by a 4 bytes Thumb instruction

I dissembled libMyGame.so with the following command:

1
arm-linux-gnueabihf-objdump -S /tmp/libMyGame.so | tee /tmp/libMyGame.s

And, there are 2 files named libMyGame.so in this game. We should use libMyGame.so file in the path lib/armeabi-v7a. Because this file is according to Thumb version. I selected hook_ptr at 0x267782:

26777e: 9a03 ldr r2, [sp, #12]
267780: 6823 ldr r3, [r4, #0]
267782: 4630 mov r0, r6 @ here, our hook_ptr
267784: 429a cmp r2, r3
267786: d001 beq.n 26778c <_ZN7cocos2d11Application10getVersionEv@@Base+0x3c>

This hook_ptr is in the function cocos2d::Application::getVersion. So when call this getVersion function, just like we did in previous post, CPU should hit this hook_ptr. Now, we saved original bytes 30 46 9a 42.
Note: arm-linux-gnueabihf-objdump displays instruction bytes from high address to low address. We also can use hexdump to show instruction byte, it’s in a clearer way. The command is:

1
hexdump -C -n 4 -s $((0x267782)) /tmp/libMyGame.so

The output should be as follows:

00267782 30 46 9a 42 |0F.B|
00267786

Write a hook handle function.

The prototype of the hook handle function is as follows, and it’s in the file, main.cpp

1
extern "C" void hook_fun(void* baseaddress, void* sp);

This function has 2 arguments. The 1st argument, baseaddress, is the base address of the libMyGame.so, and the 2nd argument, sp, is the value int register SP. Trampoline code will pass these 2 arguments to the hook handle function. baseaddress let us can access any data or function in libMyGame.so very easily. Trampoline code will store most CPU registers in the stack, so we can access these register values using sp.

Write trampoline code

This code is in Thumb assembly. I list the code in here, and I put the offset of instructions at the beginning of every line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0x0:	push	{r0, r1, r2, r3, r4, r5, r6, r7}
0x2: push.w {r8, sb, sl, fp, ip, lr}
0x6: mrs r0, apsr
0xa: push {r0}
0xc: nop
0xe: mov r1, sp
0x10: ldr r0, [pc, #0x14] @ --> 0x28
0x12: ldr r4, [pc, #0x18] @ --> 0x2c
0x14: blx r4
0x16: pop {r0}
0x18: msr cpsr_fc, r0
0x1c: pop.w {r8, sb, sl, fp, ip, lr}
0x20: pop {r0, r1, r2, r3, r4, r5, r6, r7}
0x22: nop
0x24: nop
0x26: bx lr
0x28: nop
0x2a: nop
0x2c: nop
0x2e: nop

0x0-0xa, save registers to stack, r0-r12, LR , and CPSR. We can learn more on ARM registers with this page.
0xe, set the 2nd argument,
0x10, set the 1nd argument, we load the 1st argument from 0x28, and we will put the base address of libMyGame.so at 0x28 using Frida.
0x12, load the address of the hook handle function to R4, and we will put hook_fun_ptr at 0x2c using Frida.
0x14, call hook handle function
0x16-0x20, load registers from stack.
0x22-0x24, run original instructions at here. We will put saved original bytes at here using Frida.
0x26, return
0x28-0x2a, We store the base address of libMyGame.so at here.
0x2c-0x2e, We store the address of hook handle function at here.
Whole trampoline code occupies 0x30 bytes.

About selection of trampoline_ptr

We can simple using Memory.alloc for trampoline code. Actually, we use near call instruction. This makes trampoline_ptr can not be too far from hook_ptr. We can find a cave to put our trampoline code in libMyGame.so. CAVE MINER is a great tool for this task. But I just do it manually for this sample case. I use the following command to list all segments in libMyGame.so:

1
readelf -l  /tmp/libMyGame.so

The following is the output snippet:

Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00000034 0x00000034 0x00120 0x00120 R 0x4
INTERP 0x000154 0x00000154 0x00000154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /system/bin/linker]
LOAD 0x000000 0x00000000 0x00000000 0x691630 0x691630 R E 0x1000
LOAD 0x691f38 0x00692f38 0x00692f38 0x36b0c 0x42fac RW 0x1000
DYNAMIC 0x6bc6c8 0x006bd6c8 0x006bd6c8 0x00150 0x00150 RW 0x4

The first load segment is end at 0x691630, and the second load segment is begin at 0x691f38. So cave at 0x691630 is perfect for our tempoline code.

Put a BL instruction at hook target.

We just use a near instruction to call trampoline code at hook_ptr, using thumbwriter in Frida for this.

Summary

I wrote a method in patchutils.js, it’s defined as follows:

1
export function putThumbHookPatch(trampoline_ptr:NativePointer, hook_ptr:NativePointer, hook_fun_ptr:NativePointer, para1:NativePointer, origin_inst?:number[]):number
  • The 1st argument, trampoline_ptr is the address of trampoline code
  • The 2nd argument, hook_ptr is the address of hook target
  • The 3rd argument, hook_fun_ptr is the address of our hook handle function
  • The 4th argument, para1 is the 1st parameter to our hook handle function, it will be assign to base address of libMyGame.so in caller
  • The 5th argument, origin_inst is the saved original bytes at hook_ptr
    And it returns the length of the trampoline code
    I also wrote a test method in inlinehooktest.ts.

Compile & run

I wrote a makefile for compilation.
We can use the following command to compile.

1
$ make build_inlinehooktest

And use the following command to run.

1
$ make run

If everything is OK, we can see the output as follows.

Hello World from so
cocos2d application version:
#################### Hook Begin ##############################
hook function from so
baseaddress 0xc8902000
CPSR 0x600e0010
R8 0x00000000
R9 0xbbdf8c18
R10 0xbbdf8b80
R11 0xbbdf8b90
R12 0xf3811ce8
LR 0xc8b69787
R0 0xbbdf8b70
R1 0x05c43127
R2 0x05c43127
R3 0x05c43127
R4 0xf3813260
R5 0xbbdf8b70
R6 0xdcc854b8
R7 0xbbdf8b88
#################### Hook end ##############################
1.8.12

The hook handle function just do the following,

  • print base address
  • dump values of all registers stored in the stack

Conclusion

This method still has many way to improve. And it only support Thumb. If you have any idea or issues, please feel free to let me know. I’m always glad to hack and discuss with others.
All code is in my github.