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, ourhook_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 | 0x0: push {r0, r1, r2, r3, r4, r5, r6, r7} |
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 oflibMyGame.so
in caller - The 5th argument,
origin_inst
is the saved original bytes athook_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.
Inline hook with Frida (Thumb version)
http://mengxipeng1122.github.io/2022/06/29/Inline-hook-with-Frida-Thumb-version/