Architecture Overview
Lists currently supported architectures and their features.
Feature Support
Lists the currently available library features for different architectures.
Feature | x86 & x64 | ARM64 |
---|---|---|
Basic Function Hooking | ✅ | ✅ |
Code Relocation | ✅* | ✅ |
Hook Stacking | ✅ | ✅ |
Calling Convention Wrapper Generation | ✅ | ✅ |
Optimal Wrapper Generation | ✅ | ✅ |
Length Disassembler | ✅ | ✅ |
- x86 should work in all cases, but x64 isn't tested against all 5000+ instructions.
Required
Basic Function Hooking
The ability to hook/detour existing application functions.
How to Implement
Implement a code writer by inheriting the Jit<TRegister>
trait
In the writer, implement at least the following operations:
- JumpRelativeOperation.
- JumpAbsoluteOperation [needed if platform doesn't support Targeted Memory Allocation].
Your Platform must also support Permission Change, if it is applicable to your platform.
Length Disassembler
Length disassembly is the ability to determine instruction lengths at a given address.
A length disassembler determines the minimum amount of instructions (in bytes) needed to copy when hooking a function.
/// Disassembles items at `code_address` until the length of instructions
/// is equal to or greater than `min_length`.
///
/// # Returns
/// Returns length of instructions (in bytes) greater than or equal to min_length
fn disassemble_length(code_address: usize, min_length: usize) -> usize
This is done by disassembling the original instructions at code_address
, incrementing a length for each
encountered instruction until length >= min_length
, then returning the result.
Example
For hooking functions, it's necessary to inject a jmp
instruction into the existing code.
For example, given this sequence:
; x86 Assembly
DoMathWithTwoNumbers:
cmp rcx, 0 ; 48 83 F9 00
jg skipAdd ; 7F 0E
mov rax, [rsp + 8] ; 48 8B 44 24 04
mov rax, [rsp + 16] ; 48 8B 4C 24 04
add rax, rcx ; 48 01 C8
ret ; C3
A `5 byte`` relative jump would overwrite the first two instructions, creating:
; x86 Assembly
DoMathWithTwoNumbers:
jmp stub ; E9 XX XX XX XX
<INVALID INSTRUCTION> ; 0E
mov rax, [rsp + 8] ; 48 8B 44 24 04
mov rax, [rsp + 16] ; 48 8B 4C 24 04
add rax, rcx ; 48 01 C8
ret ; C3
When calling the original function again, and thus creating the Reverse Wrapper,
the original instructions overwritten by the jmp
will need to be executed.
To do this, we must know that the original 2 instructions at DoMathWithTwoNumbers
were 6, NOT
5 byte
s in length total. Such that when we copy the original code to Reverse Wrapper
we get
cmp rcx, 0 ; 48 83 F9 00
jg skipAdd ; 7F 0E
and not
cmp rcx, 0 ; 48 83 F9 00
<INVALID INSTRUCTION> ; 7F
With a length disassembler, we are able to safely copy all the bytes needed.
How to Implement
Implement a length disassembler by inheriting the LengthDisassembler
trait.
Use the algorithm described in example.
Code Relocation
Code relocation is the ability to rewrite existing code such that existing instructions using PC/IP relative operands still have valid operands post patching.
Suppose the following x86 code, which was optimised away to accept first parameter in ecx
register:
int DoMathWithTwoNumbers(int operation@ecx, int a, int b) {
if (operation <= 0) {
return a + b;
}
// Omitted Code Here
}
In this case it's possible that there's a jump in the very beginning of the function:
DoMathWithTwoNumbers:
cmp ecx, 0
jg skipAdd # It's greater than 0
mov eax, [esp + {wordSize * 1}] ; Left Parameter
mov ecx, [esp + {wordSize * 2}] ; Right Parameter
add eax, ecx
ret
; Some Omitted Code Here
skipAdd:
; Omitted Code Here
In a scenario like this, the hooking library would overwrite the cmp
and jg
instruction when
it assembles the hook entry ('enter hook'); and when the original
function is called again by your hook the, 'wrapper' would now contain this jg
instruction.
Because jg
is an instruction relative to the current instruction address, the library must be able
to patch and 'relocate' the function to a new address.
Basic code relocation support is needed to stack hooks.
How to Implement
Implement a relocator by CodeRewriter
trait.
There is no 'general strategy' for this, however, here are some pieces of advice:
- Consider looking at the docs for existing relocators (for RISC, ARM64) is a good reference.
- You will need to rewrite all control flow instructions (
branch
etc.) - You will need to rewrite all instructions which are relative to current Instruction Pointer/Program Counter.
- Use disassembler library (if one exists) for your architecture.
Optional (Extras)
Calling Convention Wrapper Generation
The ability to convert between different calling conventions (e.g. cdecl -> stdcall
).
To implement this, you implement a code writer by inheriting the Jit<TRegister>
trait; and
implement the following operations:
Optimized Wrapper Generation
If this is checked, it means the wrappers generate optimal code (to best of knowledge).
While the wrapper generator does most optimisations themselves, in some cases, it may be possible to perform additional optimisations in the JIT/Code Writer side.
For example, the reloaded-hooks
wrapper generator might generate the following sequence of pushes for ARM64:
push x0
push x1
A clever ARM64 compiler however would be able to translate this to:
stp x0, x1, [sp, #-16]!
For some built in optimisations, like this, you can opt into these specialised instructions with JitCapabilities
on your Jit<TRegister>
.
Some others, may be implemented at Jit level instead.
Misc
Hook Stacking
Hook stacking is the ability to hook a function multiple times.
This should work flawlessly out of the box if all of the required elements are implemented.