An inlinehook library using Frida
This article introduces an inline hook library using Frida, and you can download all codes about this at this link. And I only implement it on ARM64 architecture.
Concepts
In my opinion, inlinehook
just like put a breakpoint in debugger. Users can hook at any instructions, and executes any codes once the hook is triggered.
How to implement it
Basic steps
- Put a jump code at the hook point, to jump to our trampoline code.
- Trampoline code does the following:
- Save current registers.
- Invoke hook handler function.
- Restore all registers.
- Execute instructions at hook point.
- Jump back to next instruction after the hook point.
Implementation using Frda
How to add hook
Before add inline hook, user should prepare the following things:
- Hook pointWe can add hooks at any instruction.
1
const hook_ptr = <Address of the instruction will be hooked> ;
- Hook handle functionThis function has two arguments, all are of type NativePointer. The first argument is named
1
2
3const frida_fun = new NativeCallback(function(para1:NativePointer, sp:NativePointer){
console.log('para1', para1, 'sp', sp);
},'void',['pointer','pointer'])para1
, and we can assign it when we add hook, the second argument is namedsp
, of value in thesp
register, we can access values in the stack by this argument. - Memory for trampoline codeWe can allocate a new memory for trampoline code. And because allocated memory is usually far from the hook point, so we have to use a long jump instruction at the hook point. Long jump instruction usually occupies more bytes. For using near jump instruction, we can try to find a cave near the hook point, and use the found cave to store trampoline code.
1
const trampoline_ptr = Memory.alloc(trampoline_len)
Warning: when we allocate memory, do not use a local variable for the returned pointer, use a global variable instead. Javascript’s garbage collection mechanism will free this memory automatically once the program is out of the local variable’s scope, and will crash the process.
- Parameter1 pass to hook handle function We can pass a parameter to the hook handle function, and this parameter is of type NativePointer
1
const para1 = <parameter1 pass to hook handle function>;
Now invoke InlineHooker.inlineHookPatch
to add a inline hook, just as follows
1 | let sz = InlineHooker.inlineHookPatch(trampoline_ptr,hook_ptr, hook_fun_ptr, para1); |
This function return a number, indicating the length of the trampoline code.
Code relocation
For inline hook, we need to move some instructions from one address to another. And we need to rewrite some instructions, just like B/BL instruction in ARM64 architecture.
I am using the code in this link for rewriting instructions. I port the C code in this file to Typescript code. This file is my ported code. ARM64 instruction set is relatively easy, and Thumb/Arm32 is too complex, we may port its code in the future.
This file exports this method to rewrite code
1 | export let sh_a64_rewrite=(buf:NativePointer, inst:number, pc:NativePointer ):number=> |
This method has 3 arguments
- buf, the target address of current instruction
- inst, current instruction
- pc, the source address of current instruction
This method also return a variable of type number, to indicate how many instructions has written to target address. We actually need to rewrite several instructions for one B instruction to avoid change its behavior.
Conclusion
I wrote an inline hook library using Frida. Inline Hooking is not very easy, specially in some architecture, I only implemented on ARM64, and will do more work on other architecture.