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 point
    1
    const hook_ptr = <Address of the instruction will be hooked> ;
    We can add hooks at any instruction.
  • Hook handle function
    1
    2
    3
    const frida_fun = new NativeCallback(function(para1:NativePointer, sp:NativePointer){
    console.log('para1', para1, 'sp', sp);
    },'void',['pointer','pointer'])
    This function has two arguments, all are of type NativePointer. The first argument is named para1, and we can assign it when we add hook, the second argument is named sp, of value in the sp register, we can access values in the stack by this argument.
  • Memory for trampoline code
    1
    const trampoline_ptr = Memory.alloc(trampoline_len)
    We 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.

    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
    1
    const para1 = <parameter1 pass to hook handle function>;
    We can pass a parameter to the hook handle function, and this parameter is of type NativePointer

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.