APatch部署及KPM实现InlineHook及SyscallHook技术
字数 7040
更新时间 2026-03-07 10:36:35

APatch部署及KPM实现InlineHook与SyscallHook技术详解

概述

本文档旨在系统性地讲解如何在Android设备上部署APatch,并利用其KPM(Kernel Patch Module)内核模块框架,实现针对内核函数和系统调用的Hook技术。主要内容涵盖从准备工作到最终调试的完整流程,并深入解析InlineHook与SyscallHook的核心实现原理。

第一章:APatch部署与Root获取

本章节介绍获取Android设备Root权限所需的完整APatch部署流程。

1.1 准备工作

在开始部署APatch前,需确保满足以下条件并准备好必要工具。

必需工具

  1. APatch Manager: APatch管理器应用。
  2. 原厂boot.img: 从设备官方固件包中提取的boot镜像文件。
  3. ADB工具: Android Debug Bridge,用于与设备通信。
  4. Fastboot工具: 用于刷写boot镜像。
  5. 交叉编译工具链: 用于编译KPM模块(如 aarch64-none-elf-gcc)。

环境要求

  1. 解锁Bootloader: 目标Android设备的Bootloader必须已解锁。
  2. 已安装ADB和Fastboot驱动: 在电脑上安装好设备的ADB和Fastboot驱动。
  3. 内核模块支持: 设备的Android内核需支持模块加载功能。

驱动安装

确保设备通过USB连接电脑后能被ADB和Fastboot正确识别。以Windows为例,在设备管理器中更新Android设备的驱动程序。安装完毕后,在命令行执行 fastboot devices 确认设备连接正常。

1.2 卸载Magisk

如果您之前使用Magisk作为root方案,必须完全卸载它才能部署APatch。

  1. 步骤一:卸载Magisk应用

    • 在手机上打开Magisk Manager应用。
    • 选择“卸载” -> “完全卸载”,按照提示完成操作。
  2. 步骤二:验证卸载成功

    • 通过ADB连接设备:adb shell
    • 尝试获取Root权限:su
    • 卸载成功标志:
      • su命令提示“inaccessible or not found”或权限被拒绝。
      • Magisk Manager无法启动。
      • 已安装的Root应用程序无法正常工作。

1.3 APatch安装部署

通过APatch Manager进行安装是主要途径。

  1. 通过APatch Manager安装

    • 安装APatch Manager: 从GitHub下载最新版APK(Releases · bmax121/APatch)并在设备上安装、运行。
    • 修补Boot镜像:
      • 在APatch Manager主界面,点击“修补内核”。
      • 选择从官方固件包中提取的原厂 boot.img文件。
      • APatch将生成一个修补后的镜像文件(如 apatched_patched.img),请导出到电脑本地目录。
  2. 刷入修补后的镜像

    • 将设备重启至Fastboot模式:adb reboot bootloader
    • 刷入修补后的镜像:fastboot flash boot apatched_patched.img
    • 重启设备:fastboot reboot

1.4 验证APatch安装

安装完成后,需验证APatch工作是否正常。

  • APatch Manager状态: 打开APatch Manager应用,应能正常显示版本信息。
  • Root权限验证: 在ADB Shell中使用 su 命令,应能成功获取Root权限,显示为 APatch Superuser

第二章:KPM模块编译

KPM(Kernel Patch Module)是一种可动态加载的内核模块,用于实现内核级Hook。本章介绍在Linux和Windows环境下编译KPM模块的方法。

2.1 Linux环境编译

以Ubuntu/Debian系统为例。

  1. 安装交叉编译工具链

    sudo apt update
    sudo apt install gcc-aarch64-linux-gnu
    # 下载ARM GNU bare-metal工具链
    cd /tmp
    wget https://developer.arm.com/-/media/Files/downloads/gnu/11.2-2022.02/binrel/gcc-arm-11.2-2022.02-x86_64-aarch64-none-elf.tar.xz
    # 解压到/opt
    sudo tar -xf gcc-arm-11.2-2022.02-x86_64-aarch64-none-elf.tar.xz -C /opt/arm-tools/
    
  2. 设置编译环境

    # 下载KernelPatch源码
    git clone https://github.com/bmax121/KernelPatch
    cd KernelPatch/kpms/demo-hello
    # 设置环境变量
    export PATH=/opt/arm-tools/gcc-arm-11.2-2022.02-x86_64-aarch64-none-elf/bin:$PATH
    export TARGET_COMPILE=aarch64-none-elf-
    
  3. 编译KPM模块

    make
    

    编译成功后会生成 hello.kpm 文件。

2.2 Windows环境编译

Windows环境编译需要配置GNU工具链和Makefile。

  1. 安装工具链: 下载ARM GNU Toolchain并将其 bin 目录添加到系统 PATH 环境变量中。
  2. 配置Makefile: 修改项目中的 Makefile 文件,将 TARGET_COMPILE 变量指向你的工具链路径,例如:
    TARGET_COMPILE=F:\Program Files\Arm\GNUToolchain\bin\aarch64-none-elf-
    
  3. 执行编译: 在包含 Makefile 的目录打开命令行(如MSYS2或Git Bash),执行 make 命令。

2.3 示例KPM模块代码(hello.c)

hello.c 展示了一个KPM模块的基础结构,包含初始化、控制和退出回调。

static long hello_init(const char *args, const char *event, void *__user reserved)
{
    pr_info("kpm hello init, event: %s, args: %s\n", event, args);
    return 0;
}
static long hello_control0(const char *args, char *__user out_msg, int outlen)
{
    pr_info("kpm hello control0, args: %s\n", args);
    char echo[64] = "echo: ";
    strncat(echo, args, 48);
    compat_copy_to_user(out_msg, echo, sizeof(echo));
    return 0;
}
static long hello_exit(void *__user reserved)
{
    pr_info("kpm hello exit\n");
    return 0;
}
// 使用宏定义模块入口点
KPM_INIT(hello_init);
KPM_CTL0(hello_control0);
KPM_EXIT(hello_exit);

第三章:KPM模块加载

编译好的KPM模块需要推送到设备并由APatch Manager加载。

  1. 推送KPM模块到设备
    adb push hello.kpm /storage/emulated/0/Download
    
  2. 在APatch Manager中加载
    • 打开APatch Manager,进入“模块”页面。
    • 点击“从存储加载”,然后在文件选择器中选择推送好的 hello.kpm 文件。
    • 等待加载完成提示。
  3. 验证模块加载
    • 通过ADB Shell查看内核日志,确认模块已加载:
    adb shell
    su
    dmesg | grep -i kp
    
    • 日志中应出现与 hello.cpr_info 打印的初始化信息。

第四章:KPM模块实现InlineHook

InlineHook是一种通过直接修改目标函数的指令来实现Hook的技术。

4.1 整体架构

该InlineHook框架支持两种模式:

  1. 直接钩子 (Direct Hook): 用自定义的替换函数(replace_addr)完全替换原函数(origin_addr)。
  2. 钩子链 (Hook Chain): 允许在目标函数执行前(before)和/或执行后(after)安装多个回调函数。

4.2 钩子安装核心流程

安装一个InlineHook主要涉及两个关键函数:hook_prepare()hook_install()

  • 调用流程

    hook_err_t err = hook_wrap2((void *)add, before_add, after_add, 0);
    // 内部调用: hook_wrap() -> hook_prepare() -> hook_install()
    
  • hook_prepare() 核心步骤:

    1. 地址验证与初始化: 检查所有相关地址的有效性,并备份原函数开始的若干条指令(默认12条,48字节)。
    2. 生成跳板代码 (Trampoline): 生成一段从原函数位置跳转到替换函数的指令。
      hook->tramp_insts_len = branch_from_to(hook->tramp_insts, hook->origin_addr, hook->replace_addr);
      
    3. 指令重定位 (Instruction Relocation): 这是最复杂的一步。由于ARM64指令多为PC相对寻址(如B、BL、ADRP等),当我们将这些指令从原位置(即将被覆盖)复制到新位置(重定位区)时,其编码中的偏移值需要重新计算,以确保跳转目标地址正确。代码会遍历备份的指令,对需要重定位的指令类型进行识别和修复。
    4. 生成返回跳转: 在重定位代码的最后,生成跳转指令,使其在执行完重定位的指令后,能够正确返回原函数中被覆盖指令之后的位置继续执行。
  • hook_install() 安装钩子:

    1. 修改内存保护: 通过修改内核页表项(Page Table Entry),临时将目标函数所在内存页的权限从“只读”改为“可写”。
    2. 刷新TLB (Translation Lookaside Buffer): 确保页表修改立即生效。
    3. 写入跳转指令: 将 hook_prepare() 中生成的跳板代码(trampoline instructions)写入到原函数的起始位置,覆盖其原有指令。
    4. 刷新指令缓存 (I-Cache): 由于CPU有指令缓存,写入新指令后必须刷新,否则CPU可能执行旧的、缓存的指令。
    5. 恢复内存保护: 将内存页权限恢复为“只读”。

4.3 链式钩子特殊处理

对于钩子链模式,替换地址(replace_addr)指向的是一个中转函数(transit),而非用户直接提供的回调函数。

  • Transit函数机制:
    • transit 函数负责管理一个回调函数链表(钩子链)。
    • 当被Hook的函数被调用时,控制流先转到 transit 函数。
    • transit 函数依次执行所有安装的“前回调(before)”函数。
    • 然后,transit 函数可以选择性地调用原始函数(如果所有 before 回调都没有要求跳过)。
    • 最后,transit 函数反向依次执行所有“后回调(after)”函数。
    • transit 函数通过一个特殊的 NOP 指令标记来定位其所属的 hook_chain_t 结构体,从而获取回调函数数组。

4.4 关键技术解析

  • PC相对地址处理: 使用 relo_in_tramp() 函数计算指令被移动到重定位区后的新地址。
  • 指令编码解码: 使用位操作宏(如 bits32, sign64_extend)来编码和解码ARM64指令的各个字段,特别是跳转指令的立即数字段。
  • 并发安全: 钩子链使用状态机(CHAIN_ITEM_STATE_*)和内存屏障指令(dsb(ish))来保证在多个线程同时添加或移除钩子时的数据一致性。

4.5 内存布局示例

安装InlineHook后,内存的典型布局如下:

原函数地址 (origin_addr):
  [被覆盖的跳转指令,跳转到 replace_addr 或 transit 函数]
  ...
原函数剩余代码...

跳板代码 (Trampoline):
  [加载 replace_addr 到寄存器并跳转的指令]
  [replace_addr 的地址数据]

重定位区 (Relocation Area):
  [从 origin_addr 备份过来的指令,但PC相对地址已被修复]
  [跳回 origin_addr + N 继续执行的指令]
  [返回目标地址]

4.6 部署调试

  1. 编译InlineHook模块(如 make)。
  2. 在APatch Manager中加载生成的 .kpm 文件。
  3. 查看内核日志验证挂钩是否成功:
    dmesg | grep "inline"
    dmesg | grep -i kp
    

第五章:KPM模块实现SyscallHook

SyscallHook(系统调用挂钩)是Hook内核系统调用表(sys_call_table)中函数指针的技术。

5.1 整体架构分析

SyscallHook模块通常包含以下部分:

  • 模块接口: KPM_INIT, KPM_CTL0, KPM_EXIT 等宏定义的初始化和控制函数。
  • Hook回调函数: 用户定义的 beforeafter 处理函数。
  • Hook底层实现: 负责实际替换 sys_call_table 项的函数(fp_hook)以及管理多个回调的 transit 函数。

5.2 关键代码详细解析

  • Hook初始化流程 (syscall_hook_demo_init):

    • 解析传入的参数(例如决定使用函数指针Hook还是InlineHook)。
    • 通过 kallsyms_lookup_name 查找所需的内核符号(如 __task_pid_nr_ns 用于获取进程信息)。
    • 根据参数调用 fp_hook_syscallninline_hook_syscalln 安装对特定系统调用(如 __NR_openat)的Hook。
  • Hook安装核心流程 (fp_hook_wrap):

    1. 地址有效性检查: 确认要Hook的系统调用地址有效。
    2. 获取或创建Hook链: 每个被Hook的系统调用地址对应一个 fp_hook_chain_t 结构体,用于管理多个回调。如果是首次Hook该地址,则分配并初始化此结构体。
    3. 准备Transit函数: 根据系统调用的参数数量,选择对应的 transit 函数(如 _fp_transit4 用于4个参数的调用)。transit 函数被预先编译好,放置在特定的代码段。
    4. 执行实际Hook: 调用 fp_hook() 函数,用指向 transit 函数的指针替换 sys_call_table 中对应的原始函数指针,并备份原始指针。
    5. 向Hook链添加项: 在 fp_hook_chain_t 结构体的状态数组中找到一个空闲槽位(CHAIN_ITEM_STATE_EMPTY),将其状态设为 BUSY,存入用户回调函数和用户数据,再将其状态设为 READY。整个过程使用内存屏障保证可见性。
  • Transit函数机制 (_fp_transit4):

    • 动态定位: 使用 adr 汇编指令获取当前指令地址(this_va)。
    • 逆向搜索: 从 this_va 向前查找一个特殊的 ARM64_NOP 指令,该指令作为 fp_hook_chain_t 结构体中 transit 数组的结束标记。
    • 获取Hook链: 通过 container_of 宏,利用找到的 NOP 地址计算出它所属的 fp_hook_chain_t 结构体的起始地址。
    • 执行回调:
      • 准备参数结构体(hook_fargs4_t),包含参数、返回值、是否跳过原函数等字段。
      • 正向遍历执行所有状态为 READYbefore 回调函数。
      • 如果所有 before 回调都没有设置 skip_origin 标志,则调用备份的原始系统调用函数。
      • 反向遍历执行所有状态为 READYafter 回调函数。
    • 返回最终结果。
  • 实际的Hook/Unhook操作 (fp_hook):

    1. 修改页表权限: 获取目标函数指针地址的页表项,临时去除其“只读”属性(~PTE_RDONLY),并设置脏位(PTE_DBM)。
    2. 刷新TLB
    3. 替换指针: 将备份指针(backup)指向原函数指针,然后将目标地址处的值替换为新的函数指针(replace)。
    4. 插入内存屏障 (dsb(ish)) 确保写操作完成。
    5. 恢复页表权限并再次刷新TLB

5.3 关键技术详解

  • 多Hook链管理机制: 使用 fp_hook_chain_t 结构体管理一个系统调用上的多个回调。states 数组使用状态机(EMPTY, BUSY, READY)来安全地进行并发操作。
  • 参数传递机制: 针对不同的参数数量,预定义了多个 transit 函数(_fp_transit0, _fp_transit4, _fp_transit8, _fp_transit12),以高效地传递参数。
  • 执行流程:
    1. 用户态程序发起系统调用(如 openat)。
    2. 内核通过 sys_call_table[__NR_openat] 查找处理函数。
    3. 由于该表项已被替换,控制流转到对应的 transit 函数。
    4. transit 函数执行所有 before 回调。
    5. transit 函数(可选地)调用原始系统调用处理函数。
    6. transit 函数执行所有 after 回调。
    7. 返回结果给用户态。

5.4 部署调试

  1. 编译SyscallHook模块。
  2. 在APatch Manager中加载模块。
  3. 触发被Hook的系统调用(例如执行一个会打开文件的操作)。
  4. 查看内核日志验证Hook是否生效:
    dmesg | grep "syscall"
    dmesg | grep "function pointer hook"
    

第六章:常见问题与注意事项

6.1 常见问题解决

  • 问题1: APatch安装失败,设备无法启动

    • 解决:
      1. 确认使用的 boot.img 与设备型号和Android版本完全匹配。
      2. 尝试在Fastboot模式下清除缓存:fastboot erase cache
      3. 刷回原始 boot.img 并重试。
  • 问题2: KPM模块编译错误,提示架构不匹配

    • 解决:
      1. 确保交叉编译工具链(TARGET_COMPILE)设置正确,指向 aarch64-none-elf- 系列工具。
      2. 检查内核头文件路径在Makefile中配置是否正确。
      3. 在Linux环境下,可尝试文档中提供的ARM GNU bare-metal工具链。
  • 问题3: 模块加载失败,APatch Manager报错

    • 解决:
      1. 检查设备内核版本是否与KPM模块兼容。
      2. 验证 .kpm 文件是否完整,尝试重新编译。
      3. 通过 adb logcat | grep -i apatch 查看更详细的错误日志。

6.2 注意事项

  1. 备份要求: 强烈建议在操作前备份原始 boot.img。可以在Fastboot模式下使用 dd 命令提取,或直接从官方固件包获取。
  2. 测试环境: 首次测试KPM模块时,应在备用机或模拟器上进行,避免模块崩溃导致主力设备变砖。
  3. 恢复方法: 如果出现问题,可通过Fastboot刷回原始 boot.img 来恢复。在极端情况下,可能还需要清除用户数据:fastboot erase userdata此操作会清除所有数据)。

第七章:参考资源

  • 官方文档:
    • APatch 安装教程 - APatch 中文网
    • APatch 安装指南 | APatch Docs
  • 社区资源:
    • Magisk转APatch教程
    • KPM模块开发指南
  • 工具下载:
    • APatch Manager: https://github.com/bmax121/APatch/releases
    • 交叉编译工具链: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
    • KernelPatch源码: https://github.com/bmax121/kernelpatch
相似文章
相似文章
 全屏