KernelPatch 项目概述
项目简介
KernelPatch 是一个强大的 Linux 内核修补和 hook 框架,其最大特点是:
仅需 stripped kernel image(无源码、无符号表)即可对内核进行修补和 hook
核心特性
- ✅ 无需源码和符号表:通过逆向工程
kallsyms获取所有符号信息 - ✅ 静态和动态 Patch:支持静态修补内核镜像和运行时动态加载模块
- ✅ 完整的 Hook 能力:内核函数 inline hook 和系统调用表 hook
- ✅ Android SU 支持:提供 root 权限管理功能
- ✅ 模块化扩展:通过 KPM (KernelPatch Module) 动态加载功能
支持平台
- 架构:ARM64 (aarch64)
- 内核版本:Linux 3.18 - 6.12(理论上)
- 系统:通用 Linux、Android
- 前置条件:
CONFIG_KALLSYMS=y
项目信息
- 当前版本:0.13.2(参见
version文件) - License:GPL v2.0
- 作者:bmax121
- 推荐项目:如果是 Android 用户,建议使用 APatch
三大核心组件架构
KernelPatch 由三个紧密配合的组件构成:
┌─────────────────────────────────────────────────────────────┐
│ KernelPatch 架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ patch ┌──────────────┐ boot │
│ │ kptools │ ──────> │ Kernel Image │ ──────┐ │
│ │ (用户空间) │ │ + kpimg │ │ │
│ └──────────────┘ └──────────────┘ │ │
│ │ ▼ │
│ │ 解析符号 ┌──────────────┐ │
│ │ 修补镜像 │ kpimg │ │
│ │ 附加 kpimg │ (内核空间) │ │
│ │ └──────────────┘ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ kallsyms │ 提供 SuperCall │
│ │ 逆向引擎 │ │ │
│ └──────────────┘ ▼ │
│ ┌──────────────┐ │
│ │ kpuser │ │
│ │ (用户空间) │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1. kptools - 内核镜像修补工具
位置:tools/ 目录
编译产物:kptools 可执行文件
运行环境:任何支持交叉编译的系统(Windows/Linux/macOS)
主要功能:
kallsyms 逆向解析
- 文件:
tools/kallsym.c - 从二进制内核镜像中提取符号表
- 定位任意内核符号的地址
- 支持本地 kallsyms lookup 和 4.14 内核兼容
- 文件:
内核镜像修补
- 文件:
tools/patch.c - 将 kpimg 附加到内核镜像末尾
- 修改内核入口点
- 填充预设数据(preset)
- 计算
symbol_lookup_anchor用于运行时符号解析
- 文件:
Boot 镜像处理
- 文件:
tools/bootimg.c(0.13.0+ 新增) - 直接修补 boot.img(无需手动解包/重打包)
- 支持 LZ4/XZ/BZ2 压缩格式的内核解压与重压缩
- AVB (Android Verified Boot) footer 处理
- 文件:
镜像管理
- Patch:修补新的内核镜像或 boot.img
- Unpatch:还原原始镜像
- Dump:导出符号表
- List:查看 patch 信息
命令示例:
# 修补内核镜像
kptools -p -i Image -k kpimg -s mykey123 -o Image.patched
# 嵌入 KPM 模块
kptools -p -i Image -k kpimg -s mykey -M hello.kpm -T kpm -o Image.patched
# 导出符号表
kptools -d -i Image > kallsyms.txt
# 还原镜像
kptools -u -i Image.patched -o Image.original
2. kpimg - 内核空间 Patch 镜像
位置:kernel/ 目录
编译产物:kpimg 二进制文件(特殊设计的 ELF)
运行环境:内核空间(EL1)
主要功能:
接管内核启动
- 文件:
kernel/base/setup1.S,kernel/base/map.c,kernel/base/start.c - 劫持内核 boot 流程
- 在
paging_init阶段介入 - 完成所有动态修补
- 文件:
内核 Hook 能力
- 文件:
kernel/base/hook.c,kernel/base/hotpatch.c - Inline hook 任意内核函数
- 系统调用表 hook
- 支持 before/after 回调链
- 通过
stop_machine+ alias page 实现安全的代码热修补
- 文件:
SuperCall 机制
- 文件:
kernel/patch/common/supercall.c - 通过特殊系统调用暴露内核功能
- 支持 SuperKey 和 trusted manager 签名双轨认证
- 提供 SU、KPM 加载、权限管理等接口
- 文件:
KPM 模块系统
- 文件:
kernel/patch/module/module.c - 动态加载内核模块(无需源码树)
- ELF 格式,支持符号解析和重定位
- 文件:
安全绕过
- SELinux bypass(Android)
- KCFI bypass
- 内存保护绕过
符号解析增强
- 文件:
kernel/base/start.c,kernel/base/symbol.c - 支持
symbol_lookup_anchor扫描定位kallsyms_lookup_name - 兼容内核 6.x 新增的
kallsyms_on_each_match_symbolAPI
- 文件:
目录结构:
kernel/
├── base/ # 核心基础设施
│ ├── setup.c/h # 数据结构定义
│ ├── setup1.S # 启动汇编代码(setup_entry)
│ ├── map.c # 内存映射(_paging_init)
│ ├── start.c # 主初始化(start)
│ ├── hook.c # Hook 引擎
│ ├── hotpatch.c # 安全代码热修补(stop_machine)
│ ├── symbol.c # 符号系统
│ ├── predata.c # 预置数据管理
│ └── ...
├── patch/ # Patch 逻辑
│ ├── patch.c # 主 patch 函数
│ ├── common/ # 通用功能(syscall, supercall, 任务管理等)
│ ├── android/ # Android 特定功能(userd, sepolicy_flags)
│ ├── module/ # KPM 加载器
│ └── ksyms/ # 内核符号辅助
├── include/ # 头文件
└── kpimg.lds # 链接脚本
3. kpuser - 用户空间接口库
位置:user/ 目录(新版本)、user_deprecated/ 目录(旧版本)
编译产物:kpatch 可执行文件 + 头文件
运行环境:目标系统的用户空间
主要功能:
SuperCall 封装
- 文件:
user/supercall.h,user_deprecated/kpatch.c - 提供友好的 C 函数接口
- 封装系统调用细节
- 文件:
命令行工具
- SU:权限提升
- KPM:模块管理(加载/卸载/列表/控制)
- Key:SuperKey 管理
- 查询:版本、日志等
Android 扩展
- SU 权限管理器(sumgr)
- JNI 接口(apjni)
- 用户守护进程(android_user)
使用示例:
# 获取 root 权限
kpatch mykey123 su
# 加载 KPM 模块
kpatch mykey123 kpm load /path/to/module.kpm
# 查看已加载模块
kpatch mykey123 kpm list
# 获取 KernelPatch 版本
kpatch mykey123 kpver
工作原理概览
整体流程图
┌─────────────────────────────────────────────────────────────┐
│ 第一阶段:静态修补(用户空间 - kptools) │
└─────────────────────────────────────────────────────────────┘
kptools -p -i Image -k kpimg -s key -o Image.patched
│
├─ 1. 读取并解析 Image (kallsym.c)
│ └─ 逆向工程 kallsyms,获取所有符号
│
├─ 2. 计算布局 (patch.c)
│ ├─ setup_offset = align_ceil(kimg_len, 4K)
│ ├─ start_offset = max(kernel_size, setup_offset + kpimg_len)
│ └─ map_offset = tcp_init_sock 地址
│
├─ 3. 组装修补镜像
│ ├─ 复制原始内核
│ ├─ 附加 kpimg 到 setup_offset
│ ├─ 填充 setup_preset(配置数据)
│ ├─ 备份内核头部到 header_backup
│ └─ 修改入口点:B setup_entry
│
└─ 4. 输出 Image.patched
┌─────────────────────────────────────────────────────────────┐
│ 第二阶段:启动劫持(内核空间 - kpimg setup) │
└─────────────────────────────────────────────────────────────┘
Bootloader 加载 Image.patched
│
├─ setup_entry (setup1.S:358)
│ ├─ 设置临时栈
│ ├─ start_prepare():准备启动数据
│ │ ├─ 复制 kpimg 到 start_offset
│ │ └─ 备份 tcp_init_sock 到 map_backup
│ ├─ map_prepare():劫持 paging_init
│ │ ├─ 备份 paging_init[0] 到 paging_init_backup
│ │ ├─ 写入 B _paging_init 指令
│ │ └─ 覆盖 tcp_init_sock(复制 _map_start)
│ ├─ 恢复内核头部(从 header_backup)
│ └─ 跳转回 _head
│
└─ 内核继续启动...
┌─────────────────────────────────────────────────────────────┐
│ 第三阶段:内存映射(内核空间 - kpimg map) │
└─────────────────────────────────────────────────────────────┘
内核执行到 paging_init()
│
├─ _paging_init (map.c:176)
│ ├─ 分配新的物理内存(memblock_phys_alloc)
│ ├─ 映射 kpimg 到高虚拟地址
│ ├─ 复制 kpimg 到新位置
│ ├─ 恢复 paging_init 原始指令
│ ├─ 调用原始 paging_init()
│ └─ 跳转到 start()
│
└─ 原始 paging_init 正常执行
┌─────────────────────────────────────────────────────────────┐
│ 第四阶段:动态 Patch(内核空间 - kpimg start & patch) │
└─────────────────────────────────────────────────────────────┘
start() 函数执行 (start.c:604)
│
├─ start_init():初始化全局变量(可失败,返回 int)
│ ├─ 通过 symbol_lookup_anchor 或 preset 定位 kallsyms_lookup_name
│ ├─ 定位 printk
│ ├─ 解析 kallsyms_on_each_match_symbol(内核 6.1+)
│ └─ 计算页表参数
│
├─ prot_myself():设置 kpimg 内存保护
│ ├─ text/rodata → R-X
│ ├─ data/bss → RW-
│ ├─ hook 区域 → RWX(1MB)
│ ├─ rw_mem(2MB TLSF)、rox_mem(4MB TLSF)
│ └─ 注册 vmalloc area
│
├─ restore_map():恢复 tcp_init_sock
│ └─ 从 map_backup 恢复原始代码
│
├─ predata_init():初始化预置数据(superkey / root_superkey)
├─ symbol_init():初始化符号系统
│
└─ patch():安装 hooks (patch.c:140)
├─ hotpatch_symbol_init():解析 aarch64_insn_patch_text_nosync
├─ module_init() + syscall_init()
├─ hook panic(用于调试)
├─ 触发 EXTRA_EVENT_PAGING_INIT 事件
├─ hook rest_init(或 cgroup_init)
│ └─ before_rest_init()
│ ├─ hotpatch_init()(alias page 分配)
│ ├─ bypass_kcfi()
│ ├─ bypass_selinux()(hook avc_denied)
│ ├─ task_observer()
│ ├─ supercall_install()(hook __NR_supercall=45)
│ ├─ su_compat_init()
│ ├─ android_sepolicy_flags_fix()
│ └─ android_user_init()(LSM hook + execve hook)
└─ hook kernel_init
└─ before/after_kernel_init()
└─ 加载 Extra KPM 模块(PRE/POST 事件)
┌─────────────────────────────────────────────────────────────┐
│ 第五阶段:运行时(内核空间 + 用户空间) │
└─────────────────────────────────────────────────────────────┘
内核继续正常启动
│
├─ SuperCall 已安装
│ └─ 用户空间可通过特殊系统调用与 KernelPatch 交互
│
├─ Hooks 已激活
│ └─ 监控进程创建、系统调用等
│
├─ Trusted Manager 认证(Android)
│ └─ APK 签名验证 + packages.list 监听
│
└─ 用户空间工具可用
└─ supercmd(truncate)或 kpatch 命令行
与传统 LKM 的对比
| 特性 | KernelPatch | 传统 LKM (Loadable Kernel Module) |
|---|---|---|
| 需要内核源码 | ❌ 否 | ✅ 是 |
| 需要符号表 | ❌ 否 | ✅ 是(通常) |
| 需要 CONFIG_MODULES | ❌ 否 | ✅ 是 |
| 修改方式 | 静态修补镜像 | 运行时 insmod |
| 生效时机 | 内核启动时 | 启动后加载 |
| 持久性 | 镜像级别(永久) | 需要启动脚本 |
| 绕过验证 | ✅ 可以 | ❌ 受限于模块签名 |
| 访问内核内部 | ✅ 完全访问 | ⚠️ 仅导出符号 |
| Hook 能力 | ✅ inline hook + syscall hook | ⚠️ 有限 |
| 内核版本兼容性 | ⚠️ 需要针对版本调整 | ✅ 源码编译自动适配 |
| 安全性 | ⚠️ 完全绕过保护 | ✅ 受内核安全机制约束 |
关键技术点
1. kallsyms 逆向工程
问题:如何在没有符号表的情况下获取内核符号?
解决方案:
- 内核编译时会嵌入
kallsyms数据结构(如果CONFIG_KALLSYMS=y) - 通过特征搜索定位:
- Token table:
"0\x00 1\x00 ... a\x00 b\x00 ..." - Token index:计算理论索引,在镜像中搜索
- Addresses/Offsets:递增序列
- 验证:
vectors符号的 2KB 对齐特征
- Token table:
核心文件:tools/kallsym.c(1151 行)
2. 启动流程劫持
问题:如何在内核启动时介入?
解决方案:
- 第一次劫持:修改内核入口点 →
setup_entry - 第二次劫持:Hook
paging_init→_paging_init - 为什么选择 paging_init:
- 此时页表已初始化,可以操作虚拟内存
- memblock 分配器可用,可以申请内存
- 还在早期阶段,可以 hook 后续的初始化函数
核心文件:kernel/base/setup1.S(369 行)
3. 指令重定位
问题:ARM64 大量使用 PC 相对寻址,如何处理?
解决方案:
- 识别需要重定位的指令:B/BL/BC/ADR/ADRP/LDR literal/CBZ/TBZ 等
- 计算绝对地址:从相对偏移还原目标地址
- 生成跳转序列:使用
LDR + RET实现无限制跳转
核心文件:kernel/base/hook.c, kernel/base/hotpatch.c
4. 内存布局管理
问题:kpimg 应该放在哪里?如何避免与内核冲突?
解决方案:
- 磁盘布局:紧接原始内核(
setup_offset = align_ceil(kimg_len, 4K)) - 运行时布局:动态分配内存(
start_offset ≥ kernel_size) - 临时代码注入:覆盖
tcp_init_sock(启动早期不会调用) - 完成后恢复:从
map_backup恢复原始代码
核心文件:tools/patch.c, kernel/base/start.c
5. SuperCall 通信机制
问题:用户空间如何与内核空间的 KernelPatch 通信?
解决方案:
- 劫持系统调用号 45(
truncate)作为__NR_supercall - 双轨认证:SuperKey 验证 + trusted manager APK 签名验证
- 三层权限:
is_authed(SuperKey/trusted manager) >is_trusted_caller(含 su_allow_uid) > 未认证 - 提供命令接口:
long ret = syscall(__NR_supercall, key, ver_cmd, arg1, arg2, arg3, arg4); - 同时支持 CLI 命令接口(通过
truncate命令 + supercmd 分发)
核心文件:kernel/patch/common/supercall.c, kernel/patch/common/supercmd.c
典型使用场景
场景 1:修补内核实现 root(boot.img 直接修补,推荐)
# 1. 编译 kptools 和 kpimg
cd tools && make && cd ../kernel
export TARGET_COMPILE=aarch64-none-elf-
export ANDROID=1
make
# 2. 提取设备 boot 镜像
adb pull /dev/block/by-name/boot boot.img
# 3. 解包 boot.img(自动解压 LZ4/GZIP/XZ 等压缩内核)
./kptools unpack boot.img # 输出 kernel 文件
# 4. 修补内核
./kptools -p -i kernel -k ../kernel/kpimg -s mykey -o kernel
# 5. 重打包 boot.img(自动重压缩 + AVB footer 更新)
./kptools repack boot.img # 输出 new-boot.img
# 6. 刷入设备
fastboot flash boot new-boot.img
fastboot reboot
场景 1b:传统方式修补(手动解包/打包)
# 提取内核
unpack_bootimg.py --boot_img boot.img
# 修补
./kptools -p -i kernel -k kpimg -s mykey -o kernel.patched
# 重新打包
mkbootimg --kernel kernel.patched --ramdisk ramdisk ... -o boot.patched.img
# 刷入
fastboot flash boot boot.patched.img
场景 2:开发和加载 KPM 模块
# 1. 编写 KPM
cat > my_hook.c << 'EOF'
#include <kpmodule.h>
KPM_NAME("my-hook");
KPM_VERSION("1.0.0");
KPM_LICENSE("GPL v2");
static long my_init(const char *args, const char *event, void *reserved)
{
pr_info("My KPM loaded!\n");
// 可以在这里做 hook
return 0;
}
KPM_INIT(my_init);
EOF
# 2. 编译 KPM
export TARGET_COMPILE=aarch64-none-elf-
aarch64-none-elf-gcc -c my_hook.c -o my_hook.o
aarch64-none-elf-ld -r -T module.lds my_hook.o -o my_hook.kpm
# 3. 加载到运行中的内核
adb push my_hook.kpm /data/local/tmp/
adb shell kpatch mykey kpm load /data/local/tmp/my_hook.kpm
# 4. 验证
adb shell dmesg | grep "My KPM"
场景 3:Hook 系统调用
通过 KPM hook execve 系统调用:
#include <kpmodule.h>
#include <linux/syscall.h>
KPM_NAME("execve-hook");
static void before_execve(hook_fargs3_t *args, void *udata)
{
const char *filename = (const char *)args->arg0;
pr_info("execve: %s\n", filename);
// 可以修改参数或阻止执行
// args->skip_origin = 1; // 跳过原始调用
}
static long hook_init(const char *args, const char *event, void *reserved)
{
// 获取 execve 地址
unsigned long execve_addr = kallsyms_lookup_name("__arm64_sys_execve");
// Hook
hook_err_t err = fp_hook_syscalln(__NR_execve, 0, before_execve, 0, 0);
pr_info("hook execve: %d\n", err);
return 0;
}
KPM_INIT(hook_init);
项目目录结构
KernelPatch/
├── kernel/ # kpimg 源码
│ ├── base/ # 核心基础设施
│ ├── patch/ # Patch 逻辑
│ ├── include/ # 内核空间头文件
│ ├── linux/ # Linux 内核头文件(部分)
│ ├── kpimg.lds # kpimg 链接脚本
│ └── Makefile
│
├── tools/ # kptools 源码
│ ├── kptools.c # 主程序(含 unpack/repack 子命令)
│ ├── kallsym.c # kallsyms 逆向引擎
│ ├── patch.c # 镜像修补
│ ├── bootimg.c # boot.img 解包/重打包(0.13.0+ 新增)
│ ├── image.c # 镜像解析
│ ├── symbol.c # 符号处理(含 symbol_lookup_anchor)
│ ├── kpm.c # KPM 信息提取
│ ├── insn.c # 指令处理
│ ├── lib/ # 压缩库(lz4, xz, bz2, sha)
│ ├── Makefile / CMakeLists.txt
│ └── ...
│
├── user/ # 用户空间接口(新版本)
│ └── supercall.h # SuperCall 头文件
│
├── user_deprecated/ # 用户空间工具(旧版本)
│ ├── main.c # kpatch 主程序
│ ├── kpatch.c/h # SuperCall 封装
│ ├── su.c/h # SU 功能
│ ├── kpm.c/h # KPM 管理
│ ├── android/ # Android 特定功能
│ │ ├── android_user.c # 用户守护进程
│ │ ├── sumgr.c # SU 权限管理
│ │ └── apjni.cpp # JNI 接口
│ └── Makefile / CMakeLists.txt
│
├── kpms/ # KPM 示例模块
│ ├── demo-hello/ # Hello World 示例
│ ├── demo-inlinehook/ # Inline Hook 示例
│ └── demo-syscallhook/ # Syscall Hook 示例
│
├── doc/ # 官方文档
│ ├── en/ # 英文文档
│ └── zh-CN/ # 中文文档
│
├── version # 版本号定义
├── banner # ASCII Logo
├── README.md # 项目说明
└── LICENSE # GPL v2.0 许可证
核心数据结构
preset_t - Patch 配置
// kernel/include/preset.h
typedef struct {
setup_header_t header; // 64 字节头部
setup_preset_t setup; // 配置数据
} preset_t;
typedef struct _setup_header_t {
char magic[8]; // "KP1158"
version_t kp_version; // KernelPatch 版本
config_t config_flags; // 配置标志(Android/Debug)
char compile_time[24]; // 编译时间
} setup_header_t;
typedef struct _setup_preset_t {
version_t kernel_version; // 内核版本
int64_t kimg_size; // 原始内核大小
int64_t kpimg_size; // kpimg 大小
int64_t kernel_size; // 内核运行时大小
int64_t setup_offset; // kpimg 在镜像中的位置
int64_t start_offset; // kpimg 运行时位置
int64_t map_offset; // map 代码注入位置(tcp_init_sock)
int64_t kallsyms_lookup_name_offset;
int64_t paging_init_offset;
map_symbol_t map_symbol; // memblock 函数地址(MAP_SYMBOL_NUM=7)
uint8_t header_backup[8]; // 内核头部备份
uint8_t superkey[64]; // SuperKey
uint8_t root_superkey[32]; // Root SuperKey 哈希(SHA256)
int64_t sprintf_offset; // sprintf 函数偏移(0.13.0+ 新增)
int64_t symbol_lookup_anchor_offset; // 符号扫描锚点偏移(0.13.0+ 新增)
patch_config_t patch_config; // 要 hook 的函数地址
char additional[512]; // 附加信息
} setup_preset_t;
安全考虑
优势
- 完全控制内核执行流
- 可以绕过所有内核安全机制
- 适合安全研究和漏洞挖掘
风险
- 需要物理访问或解锁 bootloader
- 修改内核镜像会破坏验证启动(Verified Boot)
- 滥用可能导致严重安全问题
- 仅用于研究和合法用途
参考资源
官方文档
doc/en/guide.md:英文使用指南doc/en/build.md:编译说明doc/en/inline-hook.md:Inline Hook 教程doc/en/syscall-hook.md:Syscall Hook 教程doc/en/module.md:KPM 开发指南
相关项目
- APatch:Android 专用版本
- vmlinux-to-elf:内核符号解析灵感来源
- android-inline-hook:ARM64 inline hook 代码参考
- tlsf:内存分配器
下一步
本文档提供了 KernelPatch 的整体概览。后续文档将深入解析:
- 02-kptools详解.md:用户空间修补工具的详细实现
- 03-kallsyms逆向工程.md:符号表逆向算法的深入分析
- 06-启动劫持流程.md:kpimg 如何接管内核启动
- 09-hook机制详解.md:inline hook 的完整实现
- 12-kpatch用户工具详解.md:用户空间工具的使用和原理
文档版本:2.0
最后更新:2026-06-26
对应 KernelPatch 版本:0.13.2
评论
- 还没有评论,来说点什么吧。