Stealth Inline Hook Detection via LR Return Address
Source: Dev.to
Background: Why Traditional Inline Hook Detection Fails
Inline hook detection on Android traditionally relies on:
- CRC or code‑hash verification
- Comparing function prologue bytes (e.g., detecting
LDR/BRtrampolines)
These methods have major weaknesses:
- They read code pages, making them easily traceable with memory breakpoints.
- Attackers can hook the detection function itself.
- Their “signatures” are well known and easy to bypass.
Consequently, they are visible and high‑risk in real offensive–defensive scenarios.
Stealth Inline Hook Detection via LR Return Address
The proposed method does not read any instructions, does not scan memory, and is extremely difficult for Frida (or similar frameworks) to bypass.
Inline‑hook frameworks (Frida, Dobby, xhook, etc.) typically:
- Overwrite the LR register.
- Redirect
RETto a trampoline stored in a custom‑allocated memory page (outside the original module).
Key observation:
If a function’s LR points outside its module’s memory range, the function is inline‑hooked.
Advantages
- No reads of the
.textcode section. - Cannot be traced via hardware breakpoints.
- Works on ARM64 and x86_64 (Android and Windows).
Detection Pipeline
Step 1 – Retrieve Current Module Range
static uint64_t g_begin = 0;
static uint64_t g_end = 0;
__attribute__((constructor))
static void init_module_range() {
Dl_info info;
if (dladdr((void*)init_module_range, &info)) {
g_begin = (uint64_t)info.dli_fbase;
g_end = g_begin + get_module_size(info.dli_fname);
}
}
Step 2 – Read the Link Register (LR)
__attribute__((always_inline))
uint64_t get_lr() {
uint64_t lr;
asm volatile(
"mov x10, x29 \n"
"ldr %0, [x10, #8]\n"
: "=r"(lr)
:
: "x10"
);
return lr;
}
Step 3 – Detect Inline Hook
void check_inline_hook() {
uint64_t lr = get_lr();
if (lr g_end) {
LOGD("[!] Inline‑hook detected. lr = %llx", lr);
} else {
LOGD("[+] Function not hooked.");
}
}
Anti‑Debug Hardening (Optional)
To make debugging nearly impossible, the protection can corrupt the stack and jump to an invalid address:
void anti_debug_crash() {
uint64_t fp;
asm volatile("mov %0, x29" : "=r"(fp));
memset((void*)fp, 0xCC, 2048); // Overwrite stack memory
((void(*)())0x12345678)(); // Jump to an invalid address
}
This destroys stack traces and hinders reverse engineering of the protection logic.
Summary
- LR‑based inline hook detection does not read code pages and is nearly breakpoint‑proof.
- It reliably detects Frida, Dobby, xhook, and other trampoline mechanisms.
- Works across Android and Windows, on both ARM64 and x86_64 architectures.
The technique opens a new direction for mobile security, anti‑cheat systems, and runtime protection.