通过 LR 返回地址的隐蔽内联钩子检测

发布: (2025年12月13日 GMT+8 11:15)
3 min read
原文: Dev.to

Source: Dev.to

背景:传统的 Inline Hook 检测为何失效

在 Android 上,传统的 Inline Hook 检测通常依赖于:

  • CRC 或代码哈希校验
  • 比较函数序言字节(例如,检测 LDR/BR 跳板)

这些方法存在主要缺陷:

  • 它们 读取代码页,容易被内存断点追踪。
  • 攻击者可以 钩住检测函数本身
  • 它们的 “签名” 众所周知,容易绕过。

因此,在真实的攻防场景中,这类检测既显眼又高风险。

基于 LR 返回地址的隐蔽 Inline Hook 检测

所提方法 不读取任何指令不扫描内存,且极难被 Frida(或类似框架)绕过。

Inline‑hook 框架(Frida、Dobby、xhook 等)通常会:

  1. 覆写 LR 寄存器。
  2. RET 重定向到存放在 自定义分配的内存页(位于原模块之外)的跳板。

关键观察:
如果函数的 LR 指向的地址不在其所属模块的内存范围内,则该函数被 inline‑hook。

优势

  • 不读取 .text 代码段。
  • 无法通过硬件断点追踪。
  • 支持 ARM64x86_64(Android 与 Windows)。

检测流程

步骤 1 – 获取当前模块范围

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);
    }
}

步骤 2 – 读取链接寄存器 (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;
}

步骤 3 – 检测 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.");
    }
}

反调试加固(可选)

为了让调试几乎不可能,保护代码可以破坏栈并跳转到非法地址:

void anti_debug_crash() {
    uint64_t fp;
    asm volatile("mov %0, x29" : "=r"(fp));

    memset((void*)fp, 0xCC, 2048);          // 覆盖栈内存
    ((void(*)())0x12345678)();              // 跳转到非法地址
}

这会破坏堆栈跟踪,阻碍对保护逻辑的逆向分析。

小结

  • 基于 LR 的 inline hook 检测 不读取代码页,几乎不受断点影响。
  • 能可靠检测 Frida、Dobby、xhook 等跳板机制。
  • 兼容 AndroidWindows,支持 ARM64x86_64 架构。

该技术为移动安全、反作弊系统以及运行时保护提供了新的思路。

Back to Blog

相关文章

阅读更多 »