APatch部署及KPM实现InlineHook与SyscallHook技术详解
概述
本文档旨在系统性地讲解如何在Android设备上部署APatch,并利用其KPM(Kernel Patch Module)内核模块框架,实现针对内核函数和系统调用的Hook技术。主要内容涵盖从准备工作到最终调试的完整流程,并深入解析InlineHook与SyscallHook的核心实现原理。
第一章:APatch部署与Root获取
本章节介绍获取Android设备Root权限所需的完整APatch部署流程。
1.1 准备工作
在开始部署APatch前,需确保满足以下条件并准备好必要工具。
必需工具
- APatch Manager: APatch管理器应用。
- 原厂boot.img: 从设备官方固件包中提取的boot镜像文件。
- ADB工具: Android Debug Bridge,用于与设备通信。
- Fastboot工具: 用于刷写boot镜像。
- 交叉编译工具链: 用于编译KPM模块(如
aarch64-none-elf-gcc)。
环境要求
- 解锁Bootloader: 目标Android设备的Bootloader必须已解锁。
- 已安装ADB和Fastboot驱动: 在电脑上安装好设备的ADB和Fastboot驱动。
- 内核模块支持: 设备的Android内核需支持模块加载功能。
驱动安装
确保设备通过USB连接电脑后能被ADB和Fastboot正确识别。以Windows为例,在设备管理器中更新Android设备的驱动程序。安装完毕后,在命令行执行 fastboot devices 确认设备连接正常。
1.2 卸载Magisk
如果您之前使用Magisk作为root方案,必须完全卸载它才能部署APatch。
-
步骤一:卸载Magisk应用
- 在手机上打开Magisk Manager应用。
- 选择“卸载” -> “完全卸载”,按照提示完成操作。
-
步骤二:验证卸载成功
- 通过ADB连接设备:
adb shell - 尝试获取Root权限:
su - 卸载成功标志:
su命令提示“inaccessible or not found”或权限被拒绝。- Magisk Manager无法启动。
- 已安装的Root应用程序无法正常工作。
- 通过ADB连接设备:
1.3 APatch安装部署
通过APatch Manager进行安装是主要途径。
-
通过APatch Manager安装
- 安装APatch Manager: 从GitHub下载最新版APK(Releases · bmax121/APatch)并在设备上安装、运行。
- 修补Boot镜像:
- 在APatch Manager主界面,点击“修补内核”。
- 选择从官方固件包中提取的原厂
boot.img文件。 - APatch将生成一个修补后的镜像文件(如
apatched_patched.img),请导出到电脑本地目录。
-
刷入修补后的镜像
- 将设备重启至Fastboot模式:
adb reboot bootloader - 刷入修补后的镜像:
fastboot flash boot apatched_patched.img - 重启设备:
fastboot reboot
- 将设备重启至Fastboot模式:
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系统为例。
-
安装交叉编译工具链
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/ -
设置编译环境
# 下载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- -
编译KPM模块
make编译成功后会生成
hello.kpm文件。
2.2 Windows环境编译
Windows环境编译需要配置GNU工具链和Makefile。
- 安装工具链: 下载ARM GNU Toolchain并将其
bin目录添加到系统PATH环境变量中。 - 配置Makefile: 修改项目中的
Makefile文件,将TARGET_COMPILE变量指向你的工具链路径,例如:TARGET_COMPILE=F:\Program Files\Arm\GNUToolchain\bin\aarch64-none-elf- - 执行编译: 在包含
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加载。
- 推送KPM模块到设备
adb push hello.kpm /storage/emulated/0/Download - 在APatch Manager中加载
- 打开APatch Manager,进入“模块”页面。
- 点击“从存储加载”,然后在文件选择器中选择推送好的
hello.kpm文件。 - 等待加载完成提示。
- 验证模块加载
- 通过ADB Shell查看内核日志,确认模块已加载:
adb shell su dmesg | grep -i kp- 日志中应出现与
hello.c中pr_info打印的初始化信息。
第四章:KPM模块实现InlineHook
InlineHook是一种通过直接修改目标函数的指令来实现Hook的技术。
4.1 整体架构
该InlineHook框架支持两种模式:
- 直接钩子 (Direct Hook): 用自定义的替换函数(
replace_addr)完全替换原函数(origin_addr)。 - 钩子链 (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()核心步骤:- 地址验证与初始化: 检查所有相关地址的有效性,并备份原函数开始的若干条指令(默认12条,48字节)。
- 生成跳板代码 (Trampoline): 生成一段从原函数位置跳转到替换函数的指令。
hook->tramp_insts_len = branch_from_to(hook->tramp_insts, hook->origin_addr, hook->replace_addr); - 指令重定位 (Instruction Relocation): 这是最复杂的一步。由于ARM64指令多为PC相对寻址(如B、BL、ADRP等),当我们将这些指令从原位置(即将被覆盖)复制到新位置(重定位区)时,其编码中的偏移值需要重新计算,以确保跳转目标地址正确。代码会遍历备份的指令,对需要重定位的指令类型进行识别和修复。
- 生成返回跳转: 在重定位代码的最后,生成跳转指令,使其在执行完重定位的指令后,能够正确返回原函数中被覆盖指令之后的位置继续执行。
-
hook_install()安装钩子:- 修改内存保护: 通过修改内核页表项(Page Table Entry),临时将目标函数所在内存页的权限从“只读”改为“可写”。
- 刷新TLB (Translation Lookaside Buffer): 确保页表修改立即生效。
- 写入跳转指令: 将
hook_prepare()中生成的跳板代码(trampoline instructions)写入到原函数的起始位置,覆盖其原有指令。 - 刷新指令缓存 (I-Cache): 由于CPU有指令缓存,写入新指令后必须刷新,否则CPU可能执行旧的、缓存的指令。
- 恢复内存保护: 将内存页权限恢复为“只读”。
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 部署调试
- 编译InlineHook模块(如
make)。 - 在APatch Manager中加载生成的
.kpm文件。 - 查看内核日志验证挂钩是否成功:
dmesg | grep "inline" dmesg | grep -i kp
第五章:KPM模块实现SyscallHook
SyscallHook(系统调用挂钩)是Hook内核系统调用表(sys_call_table)中函数指针的技术。
5.1 整体架构分析
SyscallHook模块通常包含以下部分:
- 模块接口:
KPM_INIT,KPM_CTL0,KPM_EXIT等宏定义的初始化和控制函数。 - Hook回调函数: 用户定义的
before和after处理函数。 - 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_syscalln或inline_hook_syscalln安装对特定系统调用(如__NR_openat)的Hook。
-
Hook安装核心流程 (
fp_hook_wrap):- 地址有效性检查: 确认要Hook的系统调用地址有效。
- 获取或创建Hook链: 每个被Hook的系统调用地址对应一个
fp_hook_chain_t结构体,用于管理多个回调。如果是首次Hook该地址,则分配并初始化此结构体。 - 准备Transit函数: 根据系统调用的参数数量,选择对应的
transit函数(如_fp_transit4用于4个参数的调用)。transit函数被预先编译好,放置在特定的代码段。 - 执行实际Hook: 调用
fp_hook()函数,用指向transit函数的指针替换sys_call_table中对应的原始函数指针,并备份原始指针。 - 向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),包含参数、返回值、是否跳过原函数等字段。 - 正向遍历执行所有状态为
READY的before回调函数。 - 如果所有
before回调都没有设置skip_origin标志,则调用备份的原始系统调用函数。 - 反向遍历执行所有状态为
READY的after回调函数。
- 准备参数结构体(
- 返回最终结果。
- 动态定位: 使用
-
实际的Hook/Unhook操作 (
fp_hook):- 修改页表权限: 获取目标函数指针地址的页表项,临时去除其“只读”属性(
~PTE_RDONLY),并设置脏位(PTE_DBM)。 - 刷新TLB。
- 替换指针: 将备份指针(
backup)指向原函数指针,然后将目标地址处的值替换为新的函数指针(replace)。 - 插入内存屏障 (
dsb(ish)) 确保写操作完成。 - 恢复页表权限并再次刷新TLB。
- 修改页表权限: 获取目标函数指针地址的页表项,临时去除其“只读”属性(
5.3 关键技术详解
- 多Hook链管理机制: 使用
fp_hook_chain_t结构体管理一个系统调用上的多个回调。states数组使用状态机(EMPTY,BUSY,READY)来安全地进行并发操作。 - 参数传递机制: 针对不同的参数数量,预定义了多个
transit函数(_fp_transit0,_fp_transit4,_fp_transit8,_fp_transit12),以高效地传递参数。 - 执行流程:
- 用户态程序发起系统调用(如
openat)。 - 内核通过
sys_call_table[__NR_openat]查找处理函数。 - 由于该表项已被替换,控制流转到对应的
transit函数。 transit函数执行所有before回调。transit函数(可选地)调用原始系统调用处理函数。transit函数执行所有after回调。- 返回结果给用户态。
- 用户态程序发起系统调用(如
5.4 部署调试
- 编译SyscallHook模块。
- 在APatch Manager中加载模块。
- 触发被Hook的系统调用(例如执行一个会打开文件的操作)。
- 查看内核日志验证Hook是否生效:
dmesg | grep "syscall" dmesg | grep "function pointer hook"
第六章:常见问题与注意事项
6.1 常见问题解决
-
问题1: APatch安装失败,设备无法启动
- 解决:
- 确认使用的
boot.img与设备型号和Android版本完全匹配。 - 尝试在Fastboot模式下清除缓存:
fastboot erase cache。 - 刷回原始
boot.img并重试。
- 确认使用的
- 解决:
-
问题2: KPM模块编译错误,提示架构不匹配
- 解决:
- 确保交叉编译工具链(
TARGET_COMPILE)设置正确,指向aarch64-none-elf-系列工具。 - 检查内核头文件路径在Makefile中配置是否正确。
- 在Linux环境下,可尝试文档中提供的ARM GNU bare-metal工具链。
- 确保交叉编译工具链(
- 解决:
-
问题3: 模块加载失败,APatch Manager报错
- 解决:
- 检查设备内核版本是否与KPM模块兼容。
- 验证
.kpm文件是否完整,尝试重新编译。 - 通过
adb logcat | grep -i apatch查看更详细的错误日志。
- 解决:
6.2 注意事项
- 备份要求: 强烈建议在操作前备份原始
boot.img。可以在Fastboot模式下使用dd命令提取,或直接从官方固件包获取。 - 测试环境: 首次测试KPM模块时,应在备用机或模拟器上进行,避免模块崩溃导致主力设备变砖。
- 恢复方法: 如果出现问题,可通过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