Kernel su
管中窥豹
重要的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,分别是renameat
和 prctl
两个函数,这两函数有什么特别的?
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
- 注册prctl的kprobe,与管理APP通信,来控制各种CMD (核心功能,查询ROOT应用,提权ROOT都是通过它)
- 注册renameat的kprobe, 主要是监听
/data/system/packages.list
的变化来更新app uid,并保存
-
ksu_allowlist_init 创建工作队列和管理ROOT的链表
-
ksu_uid_observer_init 初始化更新UID队列
-
ksu_enable_ksud 注册3个korobe
execve_kp
主要是找init和app_process这两个执行的执行时机,在中间插入一些操作,比如添加 kernel su rulevfs_read_kp
主要是为了将KERNEL_SU_RC
写给init进程,让ksud开机启动 (真骚)input_handle_event_kp
不明白为什么这么做