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.