溺的文档
KernelPatch · 第 17 篇 / 共 22 篇

kpatch 用户工具详解

2026-06-29 · 阅读 0

概述

kpatch 是 KernelPatch 的用户空间命令行工具,提供与内核 KernelPatch 交互的所有功能。

位置user_deprecated/ 目录
主要文件

  • main.c - 主程序和命令分发
  • kpatch.c/h - SuperCall 封装
  • su.c/h - SU 功能实现
  • kpm.c/h - KPM 管理功能

编译产物kpatch 可执行文件


主程序架构

main.c - 命令分发

// user_deprecated/main.c:63-174
int main(int argc, char **argv)
{
    strcat(program_name, argv[0]);
    
    if (argc == 1) usage(EXIT_FAILURE);
    
    // ============ 第一个参数:SuperKey ============
    
    key = argv[1];
    strcat(program_name, " <SUPERKEY>");
    
    if (argc == 2) {
        if (!strcmp(argv[1], "-v") || !(strcmp(argv[1], "--version"))) {
            fprintf(stdout, "%x\n", version());
        } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
            usage(EXIT_SUCCESS);
        } else {
            usage(EXIT_FAILURE);
        }
        return 0;
    }
    
    // ============ 验证 SuperKey ============
    
    if (!key[0]) error(-EINVAL, 0, "invalid superkey");
    if (strnlen(key, SUPERCALL_KEY_MAX_LEN) >= SUPERCALL_KEY_MAX_LEN) 
        error(-EINVAL, 0, "superkey too long");
    
    
    // ============ 第二个参数:命令 ============
    
    const char *scmd = argv[2];
    int cmd = -1;
    
    struct {
        const char *scmd;
        int cmd;
    } cmd_arr[] = {
        { "hello", SUPERCALL_HELLO },
        { "kpver", SUPERCALL_KERNELPATCH_VER },
        { "kver", SUPERCALL_KERNEL_VER },
        { "key", 'K' },
        { "su", 's' },
        { "kpm", 'k' },
        { "bootlog", 'l' },
        { "panic", '.' },
        { "reload-cfg", 'r' },      // [v2.0 新增] 重新加载配置
        { "event", 'e' },           // [v2.0 新增] 触发事件
#ifdef ANDROID
        { "sumgr", 'm' },
        { "android_user", 'a' },
#endif
    };
    
    for (int i = 0; i < sizeof(cmd_arr) / sizeof(cmd_arr[0]); i++) {
        if (strcmp(scmd, cmd_arr[i].scmd)) continue;
        cmd = cmd_arr[i].cmd;
        break;
    }
    
    if (cmd < 0) error(-EINVAL, 0, "Invalid command: %s!\n", scmd);
    
    
    // ============ 命令分发 ============
    
    switch (cmd) {
    case SUPERCALL_HELLO:
        hello(key);
        return 0;
        
    case SUPERCALL_KERNELPATCH_VER:
        kpv(key);
        return 0;
        
    case SUPERCALL_KERNEL_VER:
        kv(key);
        return 0;
        
    case 's':  // su
        strcat(program_name, " su");
        return su_main(argc - 2, argv + 2);
        
    case 'K':  // key
        strcat(program_name, " key");
        return skey_main(argc - 2, argv + 2);
        
    case 'k':  // kpm
        strcat(program_name, " kpm");
        return kpm_main(argc - 2, argv + 2);
        
    case 'l':  // bootlog
        bootlog(key);
        break;
        
    case '.':  // panic
        panic(key);
        break;
        
#ifdef ANDROID
    case 'm':  // sumgr
        strcat(program_name, " sumgr");
        return sumgr_main(argc - 2, argv + 2);
#endif
    
    default:
        fprintf(stderr, "Invalid command: %s!\n", scmd);
        return -EINVAL;
    }
    
    return 0;
}

SuperCall 通信机制

v2.0 更新:supercmd 现在通过 truncate 路径工作。

supercmd 通过 truncate 路径

KernelPatch 使用一种巧妙的通信机制:用户空间工具通过 truncate 系统调用向特定路径写入命令,内核端的 KernelPatch hook 拦截该系统调用并解析命令。

// 通信流程:
// 1. 用户空间构造命令字符串
// 2. 调用 truncate(path, length) 或 ftruncate
// 3. KernelPatch 的 hook 拦截 truncate 系统调用
// 4. 检查路径是否匹配特殊前缀
// 5. 解析命令并执行
// 6. 通过返回值传递结果

// 路径格式:
//   /dev/kp/<superkey>/<command>[/args...]
//
// 示例:
//   truncate("/dev/kp/mykey123/hello", 0)
//   truncate("/dev/kp/mykey123/kpver", 0)

为什么使用 truncate?

  • truncate 是标准 POSIX 系统调用,不需要特殊权限
  • 路径参数可以携带任意长度的命令数据
  • 不需要额外的设备节点或 proc/sys 接口
  • 即使在 SELinux enforcing 模式下也能工作
  • 对抗侧信道攻击的效果优于传统的 ioctl 方式

SuperCall 封装函数

基础命令

// user_deprecated/kpatch.c:22-56

// 验证 KernelPatch 是否安装
void hello(const char *key)
{
    long ret = sc_hello(key);
    if (ret == SUPERCALL_HELLO_MAGIC) {
        fprintf(stdout, "%s\n", SUPERCALL_HELLO_ECHO);
    }
}

// 获取 KernelPatch 版本
void kpv(const char *key)
{
    uint32_t kpv = sc_kp_ver(key);
    fprintf(stdout, "%x\n", kpv);
}

// 获取内核版本
void kv(const char *key)
{
    uint32_t kv = sc_k_ver(key);
    fprintf(stdout, "%x\n", kv);
}

// 获取启动日志
void bootlog(const char *key)
{
    sc_bootlog(key);
}

// 触发内核 panic(调试用)
void panic(const char *key)
{
    sc_panic(key);
}

SuperCall 底层实现

// user/supercall.h

#define __NR_supercall 500  // 实际号码由内核决定

static inline long sc_hello(const char *key)
{
    return syscall(__NR_supercall, SUPERCALL_HELLO, 0, 0, 0, key);
}

static inline uint32_t sc_kp_ver(const char *key)
{
    return (uint32_t)syscall(__NR_supercall, SUPERCALL_KERNELPATCH_VER, 0, 0, 0, key);
}

static inline void sc_bootlog(const char *key)
{
    syscall(__NR_supercall, SUPERCALL_BOOTLOG, 0, 0, 0, key);
}

SU 功能 - su_main()

命令行解析

// user_deprecated/su.c:140-240
int su_main(int argc, char **argv)
{
    int opt;
    struct option longopts[] = {
        { "command", required_argument, NULL, 'c' },
        { "help", no_argument, NULL, 'h' },
        { "login", no_argument, NULL, 'l' },
        { "preserve-environment", no_argument, NULL, 'p' },
        { "shell", required_argument, NULL, 's' },
        { "version", no_argument, NULL, 'v' },
        { "context", required_argument, NULL, 'Z' },
        { "mount-master", no_argument, NULL, 'M' },
        { NULL, 0, NULL, 0 }
    };
    
    bool preserve_env = false;
    bool login = false;
    char const *shell = NULL;
    char const *command = NULL;
    char *scontext = NULL;
    
    // 解析参数
    while ((opt = getopt_long(argc, argv, "c:hlmps:VvZ:M", longopts, NULL)) != -1) {
        switch (opt) {
        case 'c':
            command = optarg;
            break;
        case 'h':
            usage_su(EXIT_SUCCESS);
            break;
        case 'l':
            login = true;
            break;
        case 'p':
            preserve_env = true;
            break;
        case 's':
            shell = optarg;
            break;
        case 'Z':
            scontext = optarg;
            break;
        case 'M':
            // mount master
            break;
        // ...
        }
    }
    
    
    // ============ 调用 SuperCall 提升权限 ============
    
    struct su_profile profile = {
        .uid = getuid(),
        .to_uid = 0,  // root
    };
    
    if (scontext) {
        strncpy(profile.scontext, scontext, sizeof(profile.scontext) - 1);
    } else {
        // 默认上下文(Android)
        strcpy(profile.scontext, "u:r:su:s0");
    }
    
    int ret = sc_su(key, &profile);
    if (ret) {
        error(-ret, 0, "su failed");
        return EXIT_FAILURE;
    }
    
    
    // ============ 执行 Shell 或命令 ============
    
    if (!shell) shell = DEFAULT_SHELL;
    
    char *exec_args[argc + 1];
    if (command) {
        exec_args[0] = (char *)shell;
        exec_args[1] = "-c";
        exec_args[2] = (char *)command;
        exec_args[3] = NULL;
    } else {
        exec_args[0] = (char *)shell;
        exec_args[1] = NULL;
    }
    
    // 设置环境变量
    if (!preserve_env) {
        xsetenv("PATH", DEFAULT_ROOT_PATH);
        xsetenv("HOME", "/root");
        xsetenv("USER", "root");
    }
    
    // 执行
    execvp(shell, exec_args);
    
    // 如果到这里,说明 exec 失败
    error(0, errno, "failed to exec %s", shell);
    return EXIT_FAILURE;
}

KPM 管理 - kpm_main()

命令结构

// user_deprecated/kpm.c:80-200
int kpm_main(int argc, char **argv)
{
    if (argc < 2) usage(EXIT_FAILURE);
    
    const char *scmd = argv[1];
    int cmd = -1;
    
    struct {
        const char *scmd;
        int cmd;
    } cmd_arr[] = {
        { "load", KPM_CMD_LOAD },
        { "unload", KPM_CMD_UNLOAD },
        { "list", KPM_CMD_LIST },
        { "info", KPM_CMD_INFO },
        { "control", KPM_CMD_CONTROL },
        { "help", 0 },
    };
    
    // 查找命令
    for (int i = 0; i < sizeof(cmd_arr) / sizeof(cmd_arr[0]); i++) {
        if (strcmp(scmd, cmd_arr[i].scmd)) continue;
        cmd = cmd_arr[i].cmd;
        break;
    }
    
    if (cmd < 0) usage(EXIT_FAILURE);
    
    
    // ============ 命令处理 ============
    
    switch (cmd) {
    
    // 加载模块
    case KPM_CMD_LOAD: {
        if (argc < 3) error(-EINVAL, 0, "no kpm path");
        
        const char *path = argv[2];
        const char *args = argc > 3 ? argv[3] : "";
        
        int ret = sc_kpm_load(key, path, args);
        if (ret < 0) {
            error(-ret, 0, "load kpm failed");
            return EXIT_FAILURE;
        }
        
        fprintf(stdout, "load kpm %s success\n", path);
        break;
    }
    
    // 卸载模块
    case KPM_CMD_UNLOAD: {
        if (argc < 3) error(-EINVAL, 0, "no kpm name");
        
        const char *name = argv[2];
        int ret = sc_kpm_unload(key, name);
        if (ret < 0) {
            error(-ret, 0, "unload kpm failed");
            return EXIT_FAILURE;
        }
        
        fprintf(stdout, "unload kpm %s success\n", name);
        break;
    }
    
    // 列出所有模块
    case KPM_CMD_LIST: {
        int nums = sc_kpm_nums(key);
        if (nums <= 0) {
            fprintf(stdout, "no kpm loaded\n");
            return 0;
        }
        
        char buf[4096];
        int ret = sc_kpm_list(key, buf, sizeof(buf));
        if (ret < 0) {
            error(-ret, 0, "list kpm failed");
            return EXIT_FAILURE;
        }
        
        fprintf(stdout, "%s", buf);
        break;
    }
    
    // 查看模块信息
    case KPM_CMD_INFO: {
        if (argc < 3) error(-EINVAL, 0, "no kpm name");
        
        const char *name = argv[2];
        char buf[2048];
        
        int ret = sc_kpm_info(key, name, buf, sizeof(buf));
        if (ret < 0) {
            error(-ret, 0, "get kpm info failed");
            return EXIT_FAILURE;
        }
        
        fprintf(stdout, "%s", buf);
        break;
    }
    
    // 控制模块
    case KPM_CMD_CONTROL: {
        if (argc < 3) error(-EINVAL, 0, "no kpm name");
        
        const char *name = argv[2];
        const char *args = argc > 3 ? argv[3] : "";
        char out_msg[1024];
        
        int ret = sc_kpm_control(key, name, args, out_msg, sizeof(out_msg));
        if (ret < 0) {
            error(-ret, 0, "control kpm failed");
            return EXIT_FAILURE;
        }
        
        fprintf(stdout, "%s\n", out_msg);
        break;
    }
    
    case 0:  // help
        usage(EXIT_SUCCESS);
        break;
        
    default:
        usage(EXIT_FAILURE);
    }
    
    return 0;
}

SuperCall 封装函数详解

sc_su() - SU 权限提升

// user/supercall.h
static inline int sc_su(const char *key, struct su_profile *profile)
{
    return syscall(__NR_supercall, SUPERCALL_SU, profile, 0, 0, key);
}

// 使用示例
struct su_profile profile = {
    .uid = getuid(),
    .to_uid = 0,  // root
};
strcpy(profile.scontext, "u:r:su:s0");

int ret = sc_su("mykey123", &profile);
if (ret == 0) {
    // 现在是 root 了
    system("id");
}

sc_kpm_load() - 加载模块

// user/supercall.h
static inline int sc_kpm_load(const char *key, const char *path, const char *args)
{
    return syscall(__NR_supercall, SUPERCALL_KPM_LOAD, path, args, 0, key);
}

// 使用
sc_kpm_load("mykey", "/data/local/tmp/module.kpm", "debug=1");

sc_kpm_control() - 控制模块

// user/supercall.h
static inline int sc_kpm_control(const char *key, const char *name, 
                                 const char *args, char *out_msg, int outlen)
{
    return syscall(__NR_supercall, SUPERCALL_KPM_CONTROL, 
                   name, args, out_msg, outlen, key);
}

// 使用
char response[256];
sc_kpm_control("mykey", "my-module", "get_status", response, sizeof(response));
printf("Module says: %s\n", response);

SuperKey 管理

skey_main() - Key 管理命令

// user_deprecated/kpatch.c:85-138
int skey_main(int argc, char **argv)
{
    if (argc < 2) usage(EXIT_FAILURE);
    
    const char *scmd = argv[1];
    int cmd = -1;
    
    struct {
        const char *scmd;
        int cmd;
    } cmd_arr[] = {
        { "get", SUPERCALL_SKEY_GET },
        { "set", SUPERCALL_SKEY_SET },
        { "rootkey", SUPERCALL_SKEY_ROOT_ENABLE },
        { "help", 0 },
    };
    
    // 查找命令
    for (int i = 0; i < sizeof(cmd_arr) / sizeof(cmd_arr[0]); i++) {
        if (strcmp(scmd, cmd_arr[i].scmd)) continue;
        cmd = cmd_arr[i].cmd;
        break;
    }
    
    if (cmd < 0) usage(EXIT_FAILURE);
    
    char out_buf[SUPERCALL_KEY_MAX_LEN] = { '\0' };
    
    switch (cmd) {
    
    // 获取当前 SuperKey
    case SUPERCALL_SKEY_GET:
        sc_skey_get(key, out_buf, sizeof(out_buf));
        fprintf(stdout, "%s\n", out_buf);
        break;
    
    // 设置新的 SuperKey
    case SUPERCALL_SKEY_SET:
        if (argc < 3) error(-EINVAL, 0, "no new superkey");
        const char *new_key = argv[2];
        return sc_skey_set(key, new_key);
    
    // 启用/禁用 Root SuperKey 模式
    case SUPERCALL_SKEY_ROOT_ENABLE:
        if (argc < 3) error(-EINVAL, 0, "no enable or disable specified");
        if (!strcmp("enable", argv[2])) {
            sc_skey_root_enable(key, true);
        } else if (!strcmp("disable", argv[2])) {
            sc_skey_root_enable(key, false);
        } else {
            error(-EINVAL, 0, "no enable or disable specified");
        }
        break;
    
    case 0:  // help
        usage(EXIT_SUCCESS);
        
    default:
        usage(EXIT_FAILURE);
    }
    
    return 0;
}

实际使用示例

示例 1:基础使用

# 验证 KernelPatch 是否安装
$ kpatch mykey123 hello
KernelPatch!

# 查看版本
$ kpatch mykey123 kpver
c00

$ kpatch mykey123 kver
50a95

# 查看启动日志
$ kpatch mykey123 bootlog
KP  _  __                    _ ____       _       _     
KP | |/ /___ _ __ _ __   ___| |  _ \ __ _| |_ ___| |__  
...

示例 2:获取 root

# 方法 1:启动 root shell
$ kpatch mykey123 su
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root)

# 方法 2:执行单个命令
$ kpatch mykey123 su -c "cat /data/data/com.example.app/databases/app.db"

# 方法 3:指定 SELinux 上下文(Android)
$ kpatch mykey123 su -Z u:r:magisk:s0

示例 3:KPM 管理

# 加载模块
$ kpatch mykey123 kpm load /data/local/tmp/module.kpm
load kpm /data/local/tmp/module.kpm success

# 列出模块
$ kpatch mykey123 kpm list
kpm-hello-demo
demo-inlinehook
module

# 查看模块信息
$ kpatch mykey123 kpm info module
name=module
version=1.0.0
license=GPL v2
author=myself
description=My custom module
base=ffffffc00b000000
size=4000
state=2

# 控制模块
$ kpatch mykey123 kpm control module "get_status"
echo: get_status

# 卸载模块
$ kpatch mykey123 kpm unload module
unload kpm module success

示例 4:SuperKey 管理

# 获取当前 SuperKey
$ kpatch mykey123 key get
mykey123

# 修改 SuperKey
$ kpatch mykey123 key set new_key_456
# 现在旧 key 失效,必须使用新 key

$ kpatch new_key_456 hello
KernelPatch!

# 启用 Root SuperKey 模式
$ kpatch new_key_456 key rootkey enable
# 现在 SuperKey 以哈希形式存储,更安全

Android 特定功能

sumgr - SU 权限管理器

// user_deprecated/android/sumgr.c
int sumgr_main(int argc, char **argv)
{
    // 管理允许 root 的 UID 列表
    
    // sumgr grant <uid> [to_uid] [scontext]
    // sumgr revoke <uid>
    // sumgr list
    // sumgr profile <uid>
}

使用示例

# 授予 UID 1000 root 权限
$ kpatch mykey sumgr grant 1000 0 u:r:su:s0

# 列出所有被授权的 UID
$ kpatch mykey sumgr list
1000
2000

# 撤销权限
$ kpatch mykey sumgr revoke 1000

编译 kpatch

Makefile 编译

cd user_deprecated
make

# 输出:kpatch

CMake 编译

cd user_deprecated
mkdir build && cd build
cmake ..
make

# 输出:build/kpatch

Android 交叉编译

export ANDROID_NDK=/path/to/ndk
export ANDROID=1

cd user_deprecated
mkdir -p build/android && cd build/android

cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    -DANDROID_PLATFORM=android-33 \
    -DANDROID_ABI=arm64-v8a ../..

cmake --build .

# 输出:libkpatch.so 或 kpatch

部署到设备

Android 设备

# 1. 推送到设备
adb push kpatch /data/local/tmp/
adb shell chmod +x /data/local/tmp/kpatch

# 2. 测试
adb shell /data/local/tmp/kpatch mykey123 hello
KernelPatch!

# 3. 安装到系统(可选)
adb root
adb remount
adb push kpatch /system/bin/
adb shell chmod 755 /system/bin/kpatch

# 4. 创建符号链接(方便使用)
adb shell ln -s /system/bin/kpatch /system/bin/kp
adb shell kp mykey123 kpver

错误处理

常见错误码

// user_deprecated/kpatch.c
switch (ret) {
case 0:
    // 成功
    break;
    
case -EACCES:
    fprintf(stderr, "SuperKey verification failed\n");
    break;
    
case -ENOENT:
    fprintf(stderr, "KPM not found\n");
    break;
    
case -EEXIST:
    fprintf(stderr, "KPM already loaded\n");
    break;
    
case -ENOMEM:
    fprintf(stderr, "Out of memory\n");
    break;
    
case -ENOSYS:
    fprintf(stderr, "Function not supported\n");
    break;
    
default:
    fprintf(stderr, "Unknown error: %d\n", ret);
}

调试技巧

技巧 1:检查 KernelPatch 状态

# 验证安装
$ kpatch mykey hello
# 如果输出 "KernelPatch!",说明已安装

# 如果无输出或错误:
#   - SuperKey 错误
#   - KernelPatch 未正确安装
#   - 系统调用号不匹配

技巧 2:查看内核日志

# 获取启动日志
$ kpatch mykey bootlog | grep "KP"

# 查看 dmesg
$ adb shell dmesg | grep "KP"
# 或
$ adb shell dmesg | grep "kpm"

技巧 3:验证权限

$ kpatch mykey su
# id
uid=0(root) gid=0(root) groups=0(root)

# 检查 SELinux 上下文
# id -Z
u:r:su:s0

# 检查 capabilities
# cat /proc/self/status | grep Cap
CapInh: 0000003fffffffff
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff

脚本集成

自动化脚本

#!/system/bin/sh

KEY="mykey123"

# 检查 KernelPatch
if ! /system/bin/kpatch $KEY hello >/dev/null 2>&1; then
    echo "KernelPatch not installed"
    exit 1
fi

# 加载模块
/system/bin/kpatch $KEY kpm load /data/adb/modules/my_module.kpm

# 配置权限
/system/bin/kpatch $KEY sumgr grant 1000 0 u:r:su:s0

echo "Setup complete"

init.rc 集成(Android)

# /system/etc/init/kpatch.rc

on post-fs-data
    # 创建目录
    mkdir /data/adb/kp 0755 root root
    
    # 加载模块
    exec /system/bin/kpatch mykey123 kpm load /data/adb/kp/module.kpm
    
on boot
    # 设置权限
    exec /system/bin/kpatch mykey123 sumgr grant 1000 0 u:r:su:s0

新增子命令

v2.0 新增:kpatch 新增了 reload-cfgevent 两个子命令。

reload-cfg - 重新加载配置

reload-cfg 子命令用于在运行时重新加载 KernelPatch 的配置文件,无需重启设备。

# 重新加载配置
$ kpatch mykey123 reload-cfg

# 典型用途:
# 1. 修改 SU 管理器配置后生效
# 2. 更新 KPM 自动加载列表
# 3. 刷新安全策略

工作流程

  1. 用户空间发送 reload-cfg 命令
  2. 内核端 KernelPatch 重新读取并解析 patch_config
  3. 应用新的配置项(如 SU 白名单、安全策略等)
  4. 返回操作结果

event - 触发事件

event 子命令用于手动触发 extra item 事件,可以在运行时加载嵌入在镜像中但尚未被加载的 KPM 模块。

# 触发指定事件
$ kpatch mykey123 event post-fs-data

# 列出可用事件
$ kpatch mykey123 event list

典型用途

  • 在特定的 Android 启动阶段手动触发模块加载
  • 调试 extra item 的事件绑定
  • 延迟加载某些模块到合适的时机

总结

kpatch 工具的核心价值

  1. 友好的 CLI 接口:封装底层 syscall
  2. 完整的功能覆盖:SU、KPM、Key 管理等
  3. 跨平台支持:Linux、Android
  4. 易于集成:可嵌入脚本和自动化流程

命令总结

命令 功能 示例
hello 验证安装 kpatch key hello
kpver KP 版本 kpatch key kpver
su 获取 root kpatch key su
kpm load 加载模块 kpatch key kpm load mod.kpm
kpm unload 卸载模块 kpatch key kpm unload name
kpm list 列出模块 kpatch key kpm list
key get 获取 key kpatch key key get
key set 修改 key kpatch key key set newkey
reload-cfg 重新加载配置 kpatch key reload-cfg
event 触发事件 kpatch key event post-fs-data

下一篇13-Android特定功能.md - Android 平台的专用功能


文档版本:2.0
最后更新:2026-06-26

评论

  • 还没有评论,来说点什么吧。

无需注册或登录,填个昵称即可评论。