Kernel su

发布时间 2024-01-04 20:03:14作者: 梦过无声

管中窥豹

重要的kprobe和队列

$ grep -rn INIT_WORK ./
./uid_observer.c:137:	INIT_WORK(&ksu_update_uid_work, do_update_uid);
./ksud.c:563:	INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook);
./ksud.c:564:	INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook);
./ksud.c:565:	INIT_WORK(&stop_input_hook_work, do_stop_input_hook);
./allowlist.c:499:	INIT_WORK(&ksu_save_work, do_save_allow_list);
./allowlist.c:500:	INIT_WORK(&ksu_load_work, do_load_allow_list);

$ grep -wrn register_kprobe
core_hook.c:630:	rc = register_kprobe(&prctl_kp);
core_hook.c:637:	rc = register_kprobe(&renameat_kp);
sucompat.c:206:	ret = register_kprobe(&execve_kp);
sucompat.c:208:	ret = register_kprobe(&newfstatat_kp);
sucompat.c:210:	ret = register_kprobe(&faccessat_kp);
Binary file built-in.o matches
ksud.c:554:	ret = register_kprobe(&execve_kp);
ksud.c:557:	ret = register_kprobe(&vfs_read_kp);
ksud.c:560:	ret = register_kprobe(&input_handle_event_kp);

主流程

int __init kernelsu_init(void)
{
#ifdef CONFIG_KSU_DEBUG
    pr_alert("*************************************************************");
    pr_alert("**     NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE    **");
    pr_alert("**                                                         **");
    pr_alert("**         You are running KernelSU in DEBUG mode          **");
    pr_alert("**                                                         **");
    pr_alert("**     NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE    **");
    pr_alert("*************************************************************");
#endif

    ksu_core_init();

    ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);

    ksu_allowlist_init();

    ksu_uid_observer_init();

#ifdef CONFIG_KPROBES
    ksu_enable_sucompat();
    ksu_enable_ksud();
#else
    pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html");
#endif

    return 0;
}

ksu_core_init

ksu_core_init --> ksu_kprobe_init


__maybe_unused int ksu_kprobe_init(void)                                                                                                                                                   
{
    int rc = 0;
    rc = register_kprobe(&prctl_kp);

    if (rc) {
        pr_info("prctl kprobe failed: %d.\n", rc);
        return rc;
    }
    
    rc = register_kprobe(&renameat_kp);
    pr_info("renameat kp: %d\n", rc);

    return rc;
}

注册kprobe,分别是renameatprctl 两个函数,这两函数有什么特别的?

static int renameat_handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
    // https://elixir.bootlin.com/linux/v5.12-rc1/source/include/linux/fs.h
    struct renamedata *rd = PT_REGS_PARM1(regs);
    struct dentry *old_entry = rd->old_dentry;
    struct dentry *new_entry = rd->new_dentry;
#else
    struct dentry *old_entry = (struct dentry *)PT_REGS_PARM2(regs);
    struct dentry *new_entry = (struct dentry *)PT_REGS_CCALL_PARM4(regs);
#endif

    return ksu_handle_rename(old_entry, new_entry);
}

static struct kprobe renameat_kp = {
    .symbol_name = "vfs_rename",
    .pre_handler = renameat_handler_pre,
};

直接看renameat_handler_pre 的实现,当/system/packages.list重命名时候,通过文件/data/system/packages.list 更新uid

int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry)
{
     ...

    // /data/system/packages.list.tmp -> /data/system/packages.list
    if (strcmp(new_dentry->d_iname, "packages.list")) {
        return 0;
    }

    char path[128];
    char *buf = dentry_path_raw(new_dentry, path, sizeof(path));
    if (IS_ERR(buf)) {
        pr_err("dentry_path_raw failed.\n");
        return 0;
    }

    if (strcmp(buf, "/system/packages.list")) {
        return 0;
    }                                                                                                                                                                                      
    pr_info("renameat: %s -> %s, new path: %s\n", old_dentry->d_iname,
        new_dentry->d_iname, buf);

    update_uid(); // 当/system/packages.list重命名时候,触发更新uid
    return 0;
}

再看看prctl的kprobe干了些什么?

int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
             unsigned long arg4, unsigned long arg5)
{
    ...
    if (arg2 == CMD_BECOME_MANAGER) { 
        // 这个应该是自定义的CMD吧,让他的kernel su 管理APP具有高权限
    
    }


    // all other cmds are for 'root manager'
    if (!is_manager()) {
        last_failed_uid = current_uid().val;
        return 0;
    }


    if (arg2 == CMD_GRANT_ROOT) {
        if (is_allow_su()) {
            pr_info("allow root for: %d\n", current_uid().val);
            escape_to_root();
            if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
                pr_err("grant_root: prctl reply error\n");
            }
        }
        return 0;
    }

    ...

    if (arg2 == CMD_SET_SEPOLICY) {
        if (0 != current_uid().val) {
            return 0;
        }
        if (!handle_sepolicy(arg3, arg4)) {
            if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
                pr_err("sepolicy: prctl reply error\n");
            }
        }
        return 0;
    }

}

is_manager函数判断是不是管理APP的请求,其他APP的请求全部丢掉, 然后处理管理APP的各种CMD,包括SEPOLICY,获取具有ROOT的全部名单,同意ROOT等等. 这里抽取同意ROOT看看

void escape_to_root(void)
{
    struct cred *cred;

    cred = (struct cred *)__task_cred(current);

    if (cred->euid.val == 0) {                                                                                                                                                             
        pr_warn("Already root, don't escape!\n");
        return;
    }
    // 参见 init_default_profiles 函数
    struct root_profile *profile = ksu_get_root_profile(cred->uid.val);

    cred->uid.val = profile->uid;
    cred->suid.val = profile->uid;
    cred->euid.val = profile->uid;   //struct cred 不太懂, 但这里应该就是给ROOT了
    cred->fsuid.val = profile->uid;  

    cred->gid.val = profile->gid;
    cred->fsgid.val = profile->gid;
    cred->sgid.val = profile->gid;
    cred->egid.val = profile->gid;
    ...
}

ksu_allowlist_init

初始化一个列表和工作队列,重要文件#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist"

void ksu_allowlist_init(void)                                                                                                                                                              
{   
    int i;
    
    BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
    BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
        
    for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
        allow_list_arr[i] = -1;
    
    INIT_LIST_HEAD(&allow_list);

    INIT_WORK(&ksu_save_work, do_save_allow_list);
    INIT_WORK(&ksu_load_work, do_load_allow_list);
    
    init_default_profiles();
} 

ksu_uid_observer_init

初始化工作队列,前面update_uid 就是触发此函数,更新UID

int ksu_uid_observer_init()         
{                                   
    INIT_WORK(&ksu_update_uid_work, do_update_uid);
    return 0;
}

ksu_enable_ksud

void ksu_enable_ksud()
{
#ifdef CONFIG_KPROBES
    int ret;

    ret = register_kprobe(&execve_kp);
    pr_info("ksud: execve_kp: %d\n", ret);

    ret = register_kprobe(&vfs_read_kp);
    pr_info("ksud: vfs_read_kp: %d\n", ret);

    ret = register_kprobe(&input_handle_event_kp);
    pr_info("ksud: input_handle_event_kp: %d\n", ret);

    INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook);
    INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook);                                                                                                                                
    INIT_WORK(&stop_input_hook_work, do_stop_input_hook);
#endif
}

execve_kp主要是找init和app_process这两个执行的执行时机,在中间插入一些操作

pply_kernelsu_rules();
init_second_stage_executed = true;
ksu_android_ns_fs_check();
void apply_kernelsu_rules()
{
    ...
    ksu_allow(db, "kernel", "adb_data_file", "dir", ALL);
    ksu_allow(db, "kernel", "adb_data_file", "file", ALL);
    // we may need to do mount on shell
    ksu_allow(db, "kernel", "shell_data_file", "file", ALL);
    // we need to read /data/system/packages.list
    ksu_allow(db, "kernel", "kernel", "capability", "dac_override");
}

vfs_read_kp主要是为了将KERNEL_SU_RC写给init进程,这也是kusd启动时机

static const char KERNEL_SU_RC[] =
    "\n"

    "on post-fs-data\n"
    "    start logd\n"
    // We should wait for the post-fs-data finish
    "    exec u:r:su:s0 root -- " KSUD_PATH " post-fs-data\n"
    "\n"

    "on nonencrypted\n"
    "    exec u:r:su:s0 root -- " KSUD_PATH " services\n"
    "\n"

    "on property:vold.decrypt=trigger_restart_framework\n"
    "    exec u:r:su:s0 root -- " KSUD_PATH " services\n"
    "\n"

    "on property:sys.boot_completed=1\n"
    "    exec u:r:su:s0 root -- " KSUD_PATH " boot-completed\n"
    "\n"

    "\n";


    size_t ret = copy_to_user(buf, KERNEL_SU_RC, rc_count);
    if (ret) {
        pr_err("copy ksud.rc failed: %zu\n", ret);
        return 0;
    }          

最后input_handle_event_kp,这个我实在不懂为什么要hook KEY_VOLUMEDOWN

int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
                  int *value)
{
#ifndef CONFIG_KPROBES
    if (!ksu_input_hook) {
        return 0;
    }
#endif
    if (*type == EV_KEY && *code == KEY_VOLUMEDOWN) {
        int val = *value;
        pr_info("KEY_VOLUMEDOWN val: %d\n", val);
        if (val) {
            // key pressed, count it
            volumedown_pressed_count += 1;
            if (is_volumedown_enough(volumedown_pressed_count)) {
                stop_input_hook();
            }
        }
    }

    return 0;
}                                                                                                                                                                                          

总结

  • ksu_core_init

    1. 注册prctl的kprobe,与管理APP通信,来控制各种CMD (核心功能,查询ROOT应用,提权ROOT都是通过它)
    2. 注册renameat的kprobe, 主要是监听/data/system/packages.list的变化来更新app uid,并保存
  • ksu_allowlist_init 创建工作队列和管理ROOT的链表

  • ksu_uid_observer_init 初始化更新UID队列

  • ksu_enable_ksud 注册3个korobe

    1. execve_kp主要是找init和app_process这两个执行的执行时机,在中间插入一些操作,比如添加 kernel su rule
    2. vfs_read_kp主要是为了将KERNEL_SU_RC写给init进程,让ksud开机启动 (真骚)
    3. input_handle_event_kp不明白为什么这么做