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

Android 特定功能详解

2026-06-29 · 阅读 0

概述

KernelPatch 为 Android 平台提供了丰富的专用功能,包括 SELinux 绕过、SU 权限管理、Trusted Manager 签名验证、APD 集成和 LSM 监听等。

核心文件

  • kernel/patch/android/userd.c(~1700 行)- Android 核心实现(Trusted Manager、init.rc 注入、LSM hook、包配置)
  • kernel/patch/android/sepolicy_flags.c - SELinux 策略标志修复
  • kernel/patch/common/sucompat.c - SU 兼容层与排除机制
  • user/supercmd.c - 用户空间 supercmd 命令(含 reload-cfg、event)
  • user_deprecated/android/ - 用户空间 Android 功能

主要功能(0.13.2):

  1. SELinux 绕过和策略修复
  2. Trusted Manager APK 签名验证(全新)
  3. SU 权限管理器(sumgr,KStorage 后端)
  4. APD 守护进程集成(替代已删除的 user_init.sh)
  5. init.rc 多源注入(替代单一 atrace.rc hook)
  6. LSM hook 监听 packages.list 变更(全新)
  7. AP 包配置加载(全新)
  8. JNI 接口(apjni)

重要变更(0.13.x)user_init.sh 已被完全移除,所有用户空间初始化改由 APD(/data/adb/apd)直接执行。认证机制从纯 SuperKey 扩展为 SuperKey + Trusted Manager UID + su_allow_uid 三层体系。


SELinux 绕过

sepolicy_flags_fix() - 策略标志修复

// kernel/patch/android/sepolicy_flags.c:50-64
int android_sepolicy_flags_fix()
{
    unsigned long policydb_write_addr = kallsyms_lookup_name("policydb_write");
    
    if (likely(policydb_write_addr)) {
        hook_err_t err = hook_wrap2((void *)policydb_write_addr, 
                                    before_policydb_write, 
                                    after_policydb_write, 0);
        
        if (unlikely(err != HOOK_NO_ERR)) {
            log_boot("hook policydb_write_addr: %llx, error: %d\n", 
                     policydb_write_addr, err);
            return -1;
        }
    }
    
    return 0;
}

Hook policydb_write

// kernel/patch/android/sepolicy_flags.c:25-48
static void before_policydb_write(hook_fargs2_t *args, void *udata)
{
    struct _policy_file *fp = (struct _policy_file *)args->arg1;
    args->local.data0 = (uint64_t)fp->data;  // 保存数据指针
}

static void after_policydb_write(hook_fargs2_t *args, void *udata)
{
    struct _policydb *p = (struct _policydb *)args->arg0;
    char *data = (char *)args->local.data0;
    
    if (!args->ret) {  // 写入成功
        __le32 *config = (__le32 *)(data + POLICYDB_CONFIG_OFFSET);
        __le32 before_config = *config;
        
        // 检查并添加 Android netlink 路由标志
        bool android_netlink_route_exists = 
            before_config & POLICYDB_CONFIG_ANDROID_NETLINK_ROUTE;
        bool android_netlink_getneigh_exists = 
            before_config & POLICYDB_CONFIG_ANDROID_NETLINK_GETNEIGH;
        
        if (p->android_netlink_route == 1 && !android_netlink_route_exists) {
            *config |= POLICYDB_CONFIG_ANDROID_NETLINK_ROUTE;
        }
        if (p->android_netlink_getneigh == 1 && !android_netlink_getneigh_exists) {
            *config |= POLICYDB_CONFIG_ANDROID_NETLINK_GETNEIGH;
        }
    }
}

问题背景

  • Android 13+ 引入了新的 SELinux 策略标志
  • 如果策略文件缺少这些标志,某些网络功能会失败
  • 此 hook 自动添加缺失的标志

Trusted Manager 签名验证(0.13.2 全新机制)

概念

0.13.2 引入了 Trusted Manager 机制,通过硬编码的 SHA256 证书摘要验证 APK 签名,自动识别可信的管理器应用(如 APatch Manager)。经过签名验证的管理器应用无需 SuperKey 即可获得 is_authed 权限。

硬编码可信包列表

// kernel/patch/android/userd.c:67-95
static const struct trusted_package {
    const char *package_name;
    const uint8_t cert_digest[TRUSTED_MANAGER_DIGEST_LEN];  // SHA256_BLOCK_SIZE = 32
} trusted_packages[] = {
    {
        .package_name = "me.bmax.apatch",
        .cert_digest = { 0xd7, 0x1d, 0xad, 0xc0, ... }  // 32 字节 SHA256
    },
    {
        .package_name = "com.example.apatch",
        .cert_digest = { 0xe5, 0x11, 0x33, 0x12, ... }
    },
};

APK 签名验证流程

Trusted Manager 验证流程
    |
    v
find_trusted_manager_apk_path()
    |-- 在 /data/app/ 下查找 APK 文件
    |    |-- Pre-Android 11: /data/app/<pkg>-<N>/base.apk
    |    +-- Android 11+:    /data/app/~~<hash>/<pkg>-<hash>/base.apk
    |
    v
apk_matches_trusted_signature()
    |-- 解析 APK 的签名块
    |    |-- 支持 APK Signature Scheme v2
    |    |-- 支持 APK Signature Scheme v3
    |    +-- 支持 APK Signature Scheme v3.1
    |
    v
cert_der_matches_trusted_digest()
    |-- 计算证书 DER 的 SHA256
    |-- 与硬编码的 cert_digest 比较
    |
    v
lookup_package_list_uid()
    |-- 解析 /data/system/packages.list
    |-- 查找包名对应的 UID
    |
    v
设置 trusted_manager_uid = UID

证书验证核心函数

// kernel/patch/android/userd.c:191-203
static int cert_der_matches_trusted_digest(const uint8_t *cert_der, int cert_len,
                                           const uint8_t *trusted_digest)
{
    uint8_t computed_digest[SHA256_BLOCK_SIZE];
    sha256_hash(cert_der, cert_len, computed_digest);
    
    return memcmp(computed_digest, trusted_digest, SHA256_BLOCK_SIZE) == 0;
}

// kernel/patch/android/userd.c:294-417
static int apk_matches_trusted_signature(const char *apk_path,
                                         const uint8_t *trusted_digest)
{
    // 1. 打开 APK 文件(ZIP 格式)
    // 2. 定位 APK Signing Block(在 Central Directory 之前)
    // 3. 解析签名块,尝试 v3.1 -> v3 -> v2 方案
    // 4. 提取证书 DER(最大 APK_CERT_MAX_LENGTH = 4096 字节)
    // 5. 调用 cert_der_matches_trusted_digest() 验证
    return matches;
}

APK 路径查找

// kernel/patch/android/userd.c:761-901
static int find_trusted_manager_apk_path(const char *pkg_name, char *out_path, int path_len)
{
    // 方法 1:直接目录扫描
    // Pre-Android 11 格式
    snprintf(path, sizeof(path), "/data/app/%s-%d/base.apk", pkg_name, N);
    
    // Android 11+ 格式(随机化目录名)
    // /data/app/~~<random_hash>/<pkg_name>-<random_hash>/base.apk
    // 需要遍历 /data/app/ 下的 ~~* 目录
    
    // 方法 2:从 packages.xml 解析(回退)
    return find_apk_from_packages_xml(pkg_name, out_path, path_len);
}

UID 查找

// kernel/patch/android/userd.c:419-496
static int lookup_package_list_uid(const char *pkg_name)
{
    // 解析 /data/system/packages.list
    // 格式:<package_name> <uid> <debuggable> <data_dir> <seinfo> <gids>
    // 查找匹配 pkg_name 的行,返回 uid
    
    struct file *f = filp_open("/data/system/packages.list", O_RDONLY, 0);
    // ... 逐行解析
    return uid;
}

is_trusted_manager_uid_android()

// kernel/patch/android/userd.c
int is_trusted_manager_uid_android(uid_t uid)
{
    // 与缓存的 trusted_manager_uid 比较
    return (uid != 0) && (uid == trusted_manager_uid);
}

该函数被 kernel/patch/common/supercall.c 中的 is_trusted_manager_uid() 调用,作为 supercall 三层认证的第二层。详见 10-syscall与supercall.md


SU 权限管理器 - sumgr

核心概念

sumgr (SU Manager) 允许管理哪些 UID 可以获得 root 权限。

三种认证路径(0.13.2):

  1. SuperKey 直接 SU:通过 SuperKey 获取 root
  2. Trusted Manager:经 APK 签名验证的管理器应用
  3. UID 白名单:通过包配置或 sumgr 授权的 UID

sumgr 命令

# 授予 UID 权限
kpatch key sumgr grant <uid> [to_uid] [scontext]

# 撤销权限
kpatch key sumgr revoke <uid>

# 列出所有授权的 UID
kpatch key sumgr list

# 查看 UID 的配置
kpatch key sumgr profile <uid>

内核实现(KStorage 后端)

0.13.2 中 SU 白名单使用 KStorage(内核存储)作为后端,替代了 0.12.0 的简单链表:

// kernel/patch/common/sucompat.c

// UID 白名单操作(KStorage 后端,KSTORAGE_SU_LIST_GROUP gid=0)
int su_add_allow_uid(uid_t uid, uid_t to_uid, const char *scontext)
{
    struct su_profile profile = {
        .uid = uid,
        .to_uid = to_uid,
    };
    strncpy(profile.scontext, scontext, SUPERCALL_SCONTEXT_LEN - 1);
    
    // 写入 KStorage
    return write_kstorage(su_kstorage_gid, uid, &profile, 0, sizeof(profile), true);
}

int su_remove_allow_uid(uid_t uid)
{
    return remove_kstorage(su_kstorage_gid, uid);
}

// 检查 UID 是否被允许(RCU 读锁保护)
bool is_su_allow_uid(uid_t uid)
{
    struct su_profile profile;
    int rc = read_kstorage(su_kstorage_gid, uid, &profile, 0, sizeof(profile), false);
    if (rc < 0) return false;
    return profile.uid == uid;
}

// 模块排除检查(KSTORAGE_EXCLUDE_LIST_GROUP gid=1)
int get_ap_mod_exclude(uid_t uid)
{
    int exclude = 0;
    int rc = read_kstorage(exclude_kstorage_gid, uid, &exclude, 0, sizeof(exclude), false);
    if (rc < 0) return 0;
    return exclude;
}

int set_ap_mod_exclude(uid_t uid, int exclude)
{
    return write_kstorage(exclude_kstorage_gid, uid, &exclude, 0, sizeof(exclude), true);
}

用户空间实现

// user_deprecated/android/sumgr.c:50-150
int sumgr_main(int argc, char **argv)
{
    if (argc < 2) usage(EXIT_FAILURE);
    
    const char *scmd = argv[1];
    
    // ============ grant 命令 ============
    
    if (!strcmp(scmd, "grant")) {
        if (argc < 3) error(-EINVAL, 0, "no uid");
        
        uid_t uid = (uid_t)atoi(argv[2]);
        uid_t to_uid = 0;  // 默认 root
        char *scontext = "u:r:su:s0";  // 默认上下文
        
        if (argc > 3) to_uid = (uid_t)atoi(argv[3]);
        if (argc > 4) scontext = argv[4];
        
        struct su_profile profile = {
            .uid = uid,
            .to_uid = to_uid,
        };
        strncpy(profile.scontext, scontext, sizeof(profile.scontext) - 1);
        
        int ret = sc_su_grant_uid(key, &profile);
        if (ret) error(-ret, 0, "grant failed");
        
        fprintf(stdout, "grant uid %d success\n", uid);
        return 0;
    }
    
    
    // ============ revoke 命令 ============
    
    if (!strcmp(scmd, "revoke")) {
        if (argc < 3) error(-EINVAL, 0, "no uid");
        
        uid_t uid = (uid_t)atoi(argv[2]);
        int ret = sc_su_revoke_uid(key, uid);
        if (ret) error(-ret, 0, "revoke failed");
        
        fprintf(stdout, "revoke uid %d success\n", uid);
        return 0;
    }
    
    
    // ============ list 命令 ============
    
    if (!strcmp(scmd, "list")) {
        int nums = sc_su_allow_uid_nums(key);
        if (nums <= 0) {
            fprintf(stdout, "no allowed uid\n");
            return 0;
        }
        
        uid_t uids[nums];
        int ret = sc_su_list_allow_uids(key, uids, nums);
        if (ret < 0) error(-ret, 0, "list failed");
        
        for (int i = 0; i < nums; i++) {
            fprintf(stdout, "%d\n", uids[i]);
        }
        
        return 0;
    }
    
    
    // ============ profile 命令 ============
    
    if (!strcmp(scmd, "profile")) {
        if (argc < 3) error(-EINVAL, 0, "no uid");
        
        uid_t uid = (uid_t)atoi(argv[2]);
        struct su_profile profile;
        
        int ret = sc_su_allow_uid_profile(key, uid, &profile);
        if (ret) error(-ret, 0, "get profile failed");
        
        fprintf(stdout, "uid=%d\n", profile.uid);
        fprintf(stdout, "to_uid=%d\n", profile.to_uid);
        fprintf(stdout, "scontext=%s\n", profile.scontext);
        
        return 0;
    }
    
    usage(EXIT_FAILURE);
    return 0;
}

APD 集成(替代 user_init.sh)

变化说明

0.13.2 中 user_init.sh 已被删除。不再使用 shell 脚本进行初始化,改为通过 init.rc 注入直接调用 APD(APatch Daemon,/data/adb/apd)。

关键路径

  • APD 二进制:/data/adb/apd
  • SELinux 策略工具:/data/adb/ap/bin/magiskpolicy
  • AP 根目录:/data/adb/ap/
  • 日志目录:/data/adb/ap/log/
  • SU 命令:/system/bin/kp(默认)或 /system/bin/truncate

init.rc 注入内容(0.13.2)

user_rc_data 改为直接调用 APD 和 magiskpolicy,不再调用 user_init.sh:

# kernel/patch/android/userd.c:108-130(注入到 /dev/user_init.rc)

on early-init
    exec -- truncate %s event early-init before

on init
    exec -- truncate %s event init before

on late-init
    exec -- truncate %s event late-init before

on post-fs-data
    exec -- truncate su -Z u:r:magisk:s0 exec /data/adb/ap/bin/magiskpolicy --magisk --live
    exec -- truncate su -Z u:r:magisk:s0 exec /data/adb/apd -s %s post-fs-data

on nonencrypted
    exec -- truncate su -Z u:r:magisk:s0 exec /data/adb/apd -s %s services

on property:vold.decrypt=trigger_restart_framework
    exec -- truncate su -Z u:r:magisk:s0 exec /data/adb/apd -s %s services

on property:sys.boot_completed=1
    exec -- truncate su -Z u:r:magisk:s0 exec /data/adb/apd -s %s boot-completed
    exec -- truncate su event boot-completed
    exec -- truncate su -Z u:r:magisk:s0 exec /data/adb/apd uid-listener &
    rm /dev/user_init.rc
    exec -- truncate su -Z u:r:magisk:s0 -c "mv -f /dev/user_init_log/ /data/adb/ap/log/"

APD 生命周期事件

事件 触发时机 说明
post-fs-data 数据分区挂载后 早期初始化,加载包配置
services nonencrypted 或解密后 服务启动阶段
boot-completed sys.boot_completed=1 启动完成
uid-listener boot-completed 后台 后台监听 UID 变更

init.rc 多源注入(0.13.2 变化)

多个候选 RC 文件

0.12.0 仅 hook atrace.rc,0.13.2 改为多个候选 init.rc 路径:

// kernel/patch/android/userd.c:101-130
static const char *ORIGIN_RC_FILES[] = {
    "/system/etc/init/hw/init.rc",
    "/init.rc",
    "/vendor/etc/init/hw/init.target.rc",
};

Hook 机制

通过 hook __NR_openat 系统调用实现 RC 文件替换:

// kernel/patch/android/userd.c:1512-1610

// before_openat: 检查打开的文件路径是否匹配候选 RC 文件
static void before_openat(hook_fargs4_t *args, void *udata)
{
    const char __user *filename = (const char __user *)args->arg1;
    // 如果匹配候选 RC 文件之一,记录标志
}

// after_openat: 将已打开的 fd 重定向到 /dev/user_init.rc
static void after_openat(hook_fargs4_t *args, void *udata)
{
    // 关闭原始 fd,打开 /dev/user_init.rc
    // 该文件包含原始 RC 内容 + 注入的 APD 启动命令
}

注入流程

  1. Hook __NR_openat,拦截 init 进程打开候选 RC 文件
  2. 将原始 RC 内容与 user_rc_data 合并写入 /dev/user_init.rc
  3. 将 fd 重定向到 /dev/user_init.rc
  4. 启动完成后删除 /dev/user_init.rc

LSM Hook 监听(0.13.2 全新)

概念

0.13.2 引入了 LSM(Linux Security Module)hook,通过监听文件重命名操作来检测 packages.list 的更新。这使得 KernelPatch 能在包管理器安装/卸载应用时自动刷新 trusted_manager_uid。

实现

// kernel/patch/android/userd.c:1612-1679

// hook security_path_rename 或 security_inode_rename(回退)
static void refresh_packages_list_tmp_rename(hook_fargs5_t *args, void *udata)
{
    // 提取 dentry 的完整路径
    struct dentry *old_dentry = (struct dentry *)args->arg1;
    char path_buf[256];
    char *path = dentry_path_raw(old_dentry, path_buf, sizeof(path_buf));
    
    // 检查是否为 packages.list.tmp -> packages.list 的重命名
    if (strstr(path, "packages.list.tmp")) {
        // 触发 trusted_manager_uid 刷新
        refresh_trusted_manager_state();
    }
}

// 安装 LSM hook
void install_packages_list_monitor()
{
    unsigned long addr;
    
    // 优先尝试 security_path_rename
    addr = kallsyms_lookup_name("security_path_rename");
    if (addr) {
        hook_wrap5((void *)addr, refresh_packages_list_tmp_rename, 0, 0);
        return;
    }
    
    // 回退到 security_inode_rename
    addr = kallsyms_lookup_name("security_inode_rename");
    if (addr) {
        hook_wrap5((void *)addr, refresh_packages_list_tmp_rename, 0, 0);
    }
}

刷新流程

packages.list.tmp 重命名
    |
    v(LSM hook 触发)
refresh_trusted_manager_state()
    |
    v
重新查找 trusted_manager APK
    |
    v
重新验证 APK 签名
    |
    v
重新查找 UID
    |
    v
更新 trusted_manager_uid

触发时机

  • 包管理器在安装/更新/卸载应用时,会先写入 packages.list.tmp,然后原子性重命名为 packages.list
  • LSM hook 拦截此重命名操作,自动刷新 trusted_manager_uid
  • 这确保了管理器应用更新后,新的 UID 能被即时识别

为何使用 LSM 而非其他方式

  • LSM hook 是内核级别的监听,无需用户态轮询
  • 使用 LSM 可以避免侧信道攻击(0.13.2 变更说明中提到)
  • 对于低版本不支持 LSM 的内核,通过用户态方式处理

AP 包配置加载(0.13.2 全新)

load_ap_package_config()

// kernel/patch/android/userd.c:1133-1322
int load_ap_package_config()
{
    // 配置文件路径
    const char *config_path = "/data/adb/ap/package_config";
    
    // 打开并逐行解析 CSV
    struct file *f = filp_open(config_path, O_RDONLY, 0);
    if (IS_ERR(f)) return PTR_ERR(f);
    
    int count = 0;
    char line[512];
    
    while (read_line(f, line, sizeof(line)) > 0) {
        // 解析 CSV 字段
        char *pkg, *exclude_str, *allow_str, *uid_str, *to_uid_str, *sctx;
        parse_csv_line(line, &pkg, &exclude_str, &allow_str, 
                       &uid_str, &to_uid_str, &sctx);
        
        uid_t uid = simple_strtoul(uid_str, NULL, 10);
        uid_t to_uid = simple_strtoul(to_uid_str, NULL, 10);
        int exclude = simple_strtol(exclude_str, NULL, 10);
        int allow = simple_strtol(allow_str, NULL, 10);
        
        // 验证 UID 范围
        if (uid > UINT_MAX) continue;
        
        // 处理排除标志
        if (exclude) {
            set_ap_mod_exclude(uid, exclude);
        }
        
        // 处理允许标志
        if (allow) {
            // 截断 sctx 到 SUPERCALL_SCONTEXT_LEN
            su_add_allow_uid(uid, to_uid, sctx);
        }
        
        count++;
    }
    
    filp_close(f, NULL);
    return count;
}

CSV 配置格式

文件路径/data/adb/ap/package_config

格式pkg,exclude,allow,uid,to_uid,sctx

字段 类型 说明
pkg string 包名(解析但未直接使用)
exclude int 排除标志(1=排除该 UID 使用 supercall)
allow int 允许标志(1=允许该 UID 获取 SU)
uid int 应用 UID
to_uid int 目标 UID(通常为 0=root)
sctx string SELinux 上下文(最长 95 字节)

示例

com.example.app,0,1,10123,0,u:r:su:s0
com.malware.app,1,0,10456,0,
com.trusted.tool,0,1,10789,0,u:r:magisk:s0

supercall 集成

包配置可通过 supercall 命令 SUPERCALL_AP_LOAD_PACKAGE_CONFIG (0x100d) 加载,无需任何认证:

// kernel/patch/common/supercall.c:287-290
#ifdef ANDROID
case SUPERCALL_AP_LOAD_PACKAGE_CONFIG:
    return call_ap_load_package_config();
#endif

Execve Hook 与自动 SU

execve/execveat Hook

0.13.2 hook 了 __NR_execve__NR_execveat,用于:

// kernel/patch/android/userd.c:1360-1508

// 1. 检测 init 进程启动阶段
//    - 首次 /system/bin/init 或 /init 执行 -> pre_init_first_stage()
//    - --second-stage 参数或 INIT_SECOND_STAGE=1 -> pre_init_second_stage()

// 2. 检测首个 app_process 启动
//    - app_process/app_process64 首次执行 -> on_first_app_process()
//    - 触发 refresh_trusted_manager_state()

// 3. 自动 SU(Trusted Manager 进程)
//    - 由 trusted_manager_uid 执行的进程自动获得 SU 权限
//    - 使用 all_allow_sctx 作为 SELinux 上下文

supercmd 命令(0.13.2 变化)

新增子命令

0.13.2 在 supercmd 中新增了 reload-cfgevent 子命令:

# 重新加载配置(刷新 trusted_manager + 重新加载包配置)
supercmd su reload-cfg

# 触发事件
supercmd su event <EVENT> [ARGS]

reload-cfg 实现

// user/supercmd.c:464-469
case "reload-cfg":
    refresh_trusted_manager_state();  // 重新验证管理器签名
    load_ap_package_config();         // 重新加载包配置
    break;

event 子命令

// kernel/patch/android/user_event.c:11-31
int report_user_event(const char *event, const char *args)
{
    // 支持的事件
    if (!strcmp(event, "early-init")) {
        // 早期初始化阶段
    } else if (!strcmp(event, "init")) {
        // 初始化阶段
    } else if (!strcmp(event, "late-init")) {
        // 晚期初始化阶段
    } else if (!strcmp(event, "post-fs-data")) {
        // 触发 load_ap_package_config()
        load_ap_package_config();
    } else if (!strcmp(event, "boot-completed")) {
        // 启动完成
    } else if (!strcmp(event, "uid_listener") && 
               !strcmp(args, "package-list-updated")) {
        // 包列表更新,触发 trusted_manager 刷新
        refresh_trusted_manager_state();
    }
    return 0;
}

事件与 init.rc 的关系

  • init.rc 注入的 exec -- truncate %s event <EVENT> 命令会触发对应事件
  • post-fs-data 事件中自动加载包配置
  • boot-completed 事件后启动 APD uid-listener

JNI 接口 - apjni.cpp

功能

apjni 提供 Java 应用调用 KernelPatch 的接口。

JNI 函数定义

// user_deprecated/android/apjni.cpp
#include <jni.h>
#include "supercall.h"

extern "C" {

// 验证 KernelPatch
JNIEXPORT jboolean JNICALL
Java_com_kernelpatch_KernelPatch_isInstalled(JNIEnv *env, jclass clazz, jstring key)
{
    const char *c_key = env->GetStringUTFChars(key, NULL);
    long ret = sc_hello(c_key);
    env->ReleaseStringUTFChars(key, c_key);
    
    return (ret == SUPERCALL_HELLO_MAGIC) ? JNI_TRUE : JNI_FALSE;
}

// 获取版本
JNIEXPORT jint JNICALL
Java_com_kernelpatch_KernelPatch_getVersion(JNIEnv *env, jclass clazz, jstring key)
{
    const char *c_key = env->GetStringUTFChars(key, NULL);
    uint32_t ver = sc_kp_ver(c_key);
    env->ReleaseStringUTFChars(key, c_key);
    
    return (jint)ver;
}

// SU 权限提升
JNIEXPORT jboolean JNICALL
Java_com_kernelpatch_KernelPatch_su(JNIEnv *env, jclass clazz, 
                                     jstring key, jint uid, jstring scontext)
{
    const char *c_key = env->GetStringUTFChars(key, NULL);
    const char *c_scontext = scontext ? env->GetStringUTFChars(scontext, NULL) : "u:r:su:s0";
    
    struct su_profile profile = {
        .uid = getuid(),
        .to_uid = (uid_t)uid,
    };
    strncpy(profile.scontext, c_scontext, sizeof(profile.scontext) - 1);
    
    int ret = sc_su(c_key, &profile);
    
    env->ReleaseStringUTFChars(key, c_key);
    if (scontext) env->ReleaseStringUTFChars(scontext, c_scontext);
    
    return (ret == 0) ? JNI_TRUE : JNI_FALSE;
}

// 加载 KPM
JNIEXPORT jboolean JNICALL
Java_com_kernelpatch_KernelPatch_loadKpm(JNIEnv *env, jclass clazz, 
                                          jstring key, jstring path, jstring args)
{
    const char *c_key = env->GetStringUTFChars(key, NULL);
    const char *c_path = env->GetStringUTFChars(path, NULL);
    const char *c_args = args ? env->GetStringUTFChars(args, NULL) : "";
    
    int ret = sc_kpm_load(c_key, c_path, c_args);
    
    env->ReleaseStringUTFChars(key, c_key);
    env->ReleaseStringUTFChars(path, c_path);
    if (args) env->ReleaseStringUTFChars(args, c_args);
    
    return (ret >= 0) ? JNI_TRUE : JNI_FALSE;
}

}  // extern "C"

Java 端使用

// Android 应用
public class KernelPatch {
    static {
        System.loadLibrary("kpatch");
    }
    
    public static native boolean isInstalled(String key);
    public static native int getVersion(String key);
    public static native boolean su(String key, int uid, String scontext);
    public static native boolean loadKpm(String key, String path, String args);
}

// 使用
String key = "mykey123";

if (KernelPatch.isInstalled(key)) {
    int version = KernelPatch.getVersion(key);
    Log.i("KP", "Version: " + Integer.toHexString(version));
    
    // 获取 root
    if (KernelPatch.su(key, 0, "u:r:su:s0")) {
        Log.i("KP", "Got root!");
    }
}

SELinux 上下文管理

常见 SELinux 上下文

# SU 上下文(完全权限)
u:r:su:s0

# Magisk 上下文(APD 使用)
u:r:magisk:s0

# System 上下文
u:r:system_app:s0

# Untrusted App 上下文
u:r:untrusted_app:s0

# Platform App 上下文
u:r:platform_app:s0

set_all_allow_sctx() - 全局豁免上下文

// kernel/patch/common/accctl.c:51-69
int set_all_allow_sctx(const char *sctx)
{
    if (!sctx || !sctx[0]) {
        // 清除豁免
        all_allow_sctx[0] = 0;
        all_allow_sid = SECSID_NULL;
        dsb(ish);
        logkfd("clear all allow sconetxt\n");
        return 0;
    }
    
    // 转换上下文为 SID
    int rc = security_secctx_to_secid(sctx, strlen(sctx), &all_allow_sid);
    if (!rc && all_allow_sid != SECSID_NULL) {
        strncpy(all_allow_sctx, sctx, sizeof(all_allow_sctx) - 1);
        all_allow_sctx[sizeof(all_allow_sctx) - 1] = '\0';
        dsb(ish);
        logkfd("set all allow sconetxt: %s, sid: %d\n", all_allow_sctx, all_allow_sid);
    }
    
    return rc;
}

使用

# 设置豁免上下文
kpatch key su -c "echo 'u:r:su:s0' > /sys/kernel/kp/all_allow_sctx"

# 所有切换到 u:r:su:s0 上下文的进程都绕过 SELinux

task_ext - 任务扩展数据

概念

task_ext 是附加到每个 task_struct 的扩展数据,用于存储 KernelPatch 相关信息。

结构定义

// kernel/patch/include/taskext.h
struct task_ext {
    pid_t pid;              // 进程 PID
    int sel_allow;          // SELinux 豁免标志
    int allow_all;          // 全权限标志
    // ... 其他字段
};

获取和使用

// kernel/patch/common/taskob.c
struct task_ext *get_task_ext(struct task_struct *task)
{
    // 从 task_struct 末尾获取扩展数据
    return (struct task_ext *)((uintptr_t)task + task_ext_offset);
}

void init_task_ext(struct task_struct *task)
{
    struct task_ext *ext = get_task_ext(task);
    
    memset(ext, 0, sizeof(struct task_ext));
    ext->pid = task->pid;
    ext->sel_allow = 0;
    ext->allow_all = 0;
}

// Hook copy_process,在新进程创建时初始化
void after_copy_process(hook_fargs12_t *args, void *udata)
{
    struct task_struct *task = (struct task_struct *)args->ret;
    if (task && !IS_ERR(task)) {
        init_task_ext(task);
    }
}

Android 初始化流程(0.13.2 完整启动序列)

内核启动
    |
    v
KernelPatch 初始化
    |-- syscall_init()         安装 syscall hook
    |-- supercall_install()    安装 supercall
    |-- hook execve/execveat   拦截进程创建
    |-- hook openat            拦截 RC 文件打开
    +-- install LSM hooks      监听 packages.list 变更
    |
    v
init 第一阶段 (execve hook 检测)
    |-- pre_init_first_stage()
    |
    v
init 第二阶段 (--second-stage 检测)
    |-- pre_init_second_stage()
    +-- 准备 /dev/user_init.rc(原始 RC + 注入命令)
    |
    v
init 解析 RC 文件 (openat hook 重定向)
    |-- 读取 /dev/user_init.rc 而非原始 RC
    |
    v
on early-init
    |-- supercmd event early-init
    |
    v
on post-fs-data
    |-- magiskpolicy --magisk --live   加载 SELinux 策略
    |-- apd -s <key> post-fs-data     APD 数据分区初始化
    +-- load_ap_package_config()       加载包配置(通过 event)
    |
    v
on nonencrypted / vold.decrypt
    |-- apd -s <key> services          APD 服务启动
    |
    v
首个 app_process 启动 (execve hook 检测)
    |-- on_first_app_process()
    +-- refresh_trusted_manager_state()  首次验证管理器签名
    |
    v
on sys.boot_completed=1
    |-- apd -s <key> boot-completed    APD 启动完成
    |-- supercmd event boot-completed
    |-- apd uid-listener &             后台 UID 监听
    |-- rm /dev/user_init.rc           清理临时文件
    +-- mv logs to /data/adb/ap/log/   转移日志
    |
    v
运行时
    |-- LSM hook 监听 packages.list.tmp 重命名
    +-- 自动刷新 trusted_manager_uid

安全模式(Safe Mode)

概念

安全模式允许在 KernelPatch 出现问题时禁用某些功能。

检测方式(0.13.2)

// kernel/patch/android/userd.c:1681-1701
// Hook input_handle_event() 检测音量键
static int volume_down_count = 0;

void on_input_event(...)
{
    // 检测连续 3 次音量下键按下
    if (is_volume_down_press(event)) {
        volume_down_count++;
        if (volume_down_count >= 3) {
            android_is_safe_mode = 1;
        }
    } else {
        volume_down_count = 0;
    }
}

用户空间查询

# 通过 supercall 查询(需要 trusted_caller)
# SUPERCALL_SU_GET_SAFEMODE (0x2008)

# 或通过 supercmd
supercmd su get-safemode

关键文件与路径参考

配置文件

路径 说明
/data/adb/ap/package_config AP 包配置(CSV 格式)
/data/system/packages.list 系统包列表(UID 查找)
/data/system/packages.list.tmp 临时包列表(LSM 监听目标)
/data/system/packages.xml XML 包数据库(APK 路径回退查找)

二进制文件

路径 说明
/data/adb/apd APatch 守护进程
/data/adb/ap/bin/magiskpolicy SELinux 策略工具
/system/bin/truncate supercmd 路径
/system/bin/kp SU 命令(默认)

临时文件

路径 说明
/dev/user_init.rc 注入的 init.rc(启动后删除)
/dev/user_init_log/ 启动日志(启动后转移)

数据目录

路径 说明
/data/adb/ap/ APatch 根目录
/data/adb/ap/log/ 日志目录
/data/app/ 应用安装目录(APK 签名验证扫描)

实际使用场景

场景 1:Magisk 模块集成

# Magisk 模块结构
module_id/
+-- module.prop
+-- service.sh
+-- system/
    +-- bin/
        +-- kpatch

# service.sh
#!/system/bin/sh
MODDIR=${0%/*}
KEY="module_key_123"

# 等待启动完成
while [ "$(getprop sys.boot_completed)" != "1" ]; do
    sleep 1
done

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

# 授权 shell
$MODDIR/system/bin/kpatch $KEY sumgr grant 2000 0 u:r:su:s0

场景 2:应用集成

// Android 应用
public class RootManager {
    private static final String KEY = "app_key_123";
    
    public static boolean requestRoot() {
        try {
            if (!KernelPatch.isInstalled(KEY)) {
                return false;
            }
            
            return KernelPatch.su(KEY, 0, "u:r:su:s0");
        } catch (Exception e) {
            Log.e("Root", "Failed to get root", e);
            return false;
        }
    }
    
    public static void loadCustomModule() {
        String modulePath = "/data/data/com.example.app/files/module.kpm";
        KernelPatch.loadKpm(KEY, modulePath, "app_mode=1");
    }
}

场景 3:使用 supercmd 管理

# 重新加载配置(trusted_manager + 包配置)
supercmd su reload-cfg

# 触发事件
supercmd su event post-fs-data

# 查看安全模式状态
supercmd su get-safemode

与其他 Root 方案对比

特性 KernelPatch Magisk KernelSU
内核修改 修补镜像 启动时注入 修改内核源码
无需源码
Bootloader 锁定 需解锁 需解锁 需解锁
SELinux 处理 完全绕过 Permissive 部分绕过
模块系统 KPM Magisk 模块 KernelSU 模块
应用隐藏 有限 MagiskHide 有限
OTA 更新 需重新 patch 需重新刷入 需重新编译
管理器认证 APK 签名验证 包名检测 包名检测
包配置 CSV 文件 Magisk Manager DB KernelSU Manager DB

故障排查

问题 1:SELinux 仍然拒绝访问

# 检查 SELinux 状态
$ getenforce
Enforcing

# 检查当前上下文
$ id -Z
u:r:untrusted_app:s0

# 检查是否正确设置
$ kpatch key su
# id -Z
u:r:su:s0  # 应该变为 su

# 如果仍然是 untrusted_app,说明 SELinux 绕过失败

调试

# 查看 avc_denied hook 是否成功
$ adb shell dmesg | grep "hook avc_denied"
KP hook avc_denied: 0  # 0 表示成功

问题 2:UID 授权不生效

# 列出授权的 UID
$ kpatch key sumgr list
2000

# 尝试 SU
$ su  # 假设当前 UID 是 1000
Permission denied

# 原因:UID 1000 未授权
$ kpatch key sumgr grant 1000 0 u:r:su:s0
$ su
# whoami
root  # 成功

问题 3:Trusted Manager 未被识别

# 检查 trusted_manager_uid 是否已设置
# 通常在首个 app_process 启动时自动设置

# 手动刷新
supercmd su reload-cfg

# 检查 APK 是否存在于 /data/app/
ls /data/app/ | grep apatch

# 检查 packages.list 中的 UID
grep "me.bmax.apatch" /data/system/packages.list

问题 4:包配置加载失败

# 检查配置文件是否存在
ls -la /data/adb/ap/package_config

# 检查格式(每行 6 个逗号分隔字段)
cat /data/adb/ap/package_config

# 手动触发重新加载
supercmd su reload-cfg

# 查看内核日志
dmesg | grep "package_config"

总结

Android 特定功能的核心价值(0.13.2)

  1. SELinux 完全绕过:对授权进程透明
  2. Trusted Manager 签名验证:APK 证书级别的管理器认证(全新)
  3. 细粒度权限控制:基于 UID 的三层授权 + 排除机制
  4. APD 原生集成:直接通过 init.rc 注入调用 APD(替代 user_init.sh)
  5. LSM 实时监听:包列表变更时自动刷新 trusted_manager_uid(全新)
  6. CSV 包配置:灵活的 per-package 权限配置(全新)
  7. 应用集成友好:JNI 接口和 supercmd 命令行
  8. 多 RC 文件源:不再局限于单一 RC 文件 hook

适用场景

  • Root 管理应用
  • 安全研究工具
  • 系统定制和优化
  • 应用权限提升

注意事项

  • 需要解锁 Bootloader
  • 会破坏 Verified Boot
  • 仅用于合法用途

下一篇14-备份与恢复机制.md - 详解三大备份的完整数据流


文档版本:2.0
最后更新:2026-06-26
对应代码版本:0.13.2

评论

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

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