CVE-2014-3153 分析以及利用
作者:retme 发布时间:September 19, 2014 分类:AndroidSec No Comments
这是两个多月前的一篇笔记,一直没有贴出来。而现在霓虹小兄弟已经把逆向出的代码扔出来很久了。但是写这篇笔记的时候除了towelroot V1以外啥也没有,所以~~当时其实我很快就掌握了利用的细节,主要是一开始我就没想逆向towelroot,直接靠trace定位了利用方法。
转载请注明 http://retme.net/index.php/2014/09/19/cve-2014-3153.html
膜拜geohot~以下是当时的笔记:
一,首先看补丁
https://github.com/torvalds/linux/commit/e9c243a5a6de0be8e584c604d353412584b592f8
if (requeue_pi) {
/*
+ * Requeue PI only works on two distinct uaddrs. This
+ * check is only valid for private futexes. See below.
+ */
+ if (uaddr1 == uaddr2)
+ return -EINVAL;
+
+ /*
补丁要求两个 futex地址不能相同。如果相同会发生什么呢?
二,相关数据结构
实际上每个 futex进入内核中会计算一个 key( get_futex_key)并且被插入哈希表futext_queues, futext_queues的结构如下:
static struct futex_hash_bucket futex_queues[1<<FUTEX_HASHBITS];
static struct futex_hash_bucket *hash_futex(union futex_key *key)
{
u32 hash = jhash2((u32*)&key->both.word,
(sizeof(key->both.word)+sizeof(key->both.ptr))/4,
key->both.offset);
return &futex_queues[hash & ((1 << FUTEX_HASHBITS)-1)];
}
futex_hash_bucket是哈希表中的一个节点,结构如下
struct futex_hash_bucket {
spinlock_t lock;
struct plist_head chain;
};
其内部也是一个自旋锁,和一个队列。 chain 是一个优先级队列,等待线程的优先级越高,该线程在队列中越靠前。
plist_head链表中的成员是futex_q,代表了一个 futex的内核对象
/**
* struct futex_q - The hashed futex queue entry, one per waiting task
* @list: priority-sorted list of tasks waiting on this futex
* @task: the task waiting on the futex
* @lock_ptr: the hash bucket lock
* @key: the key the futex is hashed on
* @pi_state: optional priority inheritance state
* @rt_waiter: rt_waiter storage for use with requeue_pi
* @requeue_pi_key: the requeue_pi target futex key
* @bitset: bitset for the optional bitmasked wakeup
*
* We use this hashed waitqueue, instead of a normal wait_queue_t, so
* we can wake only the relevant ones (hashed queues may be shared).
*
* A futex_q has a woken state, just like tasks have TASK_RUNNING.
* It is considered woken when plist_node_empty(&q->list) || q->lock_ptr == 0.
* The order of wakeup is always to make the first condition true, then
* the second.
*
* PI futexes are typically woken before they are removed from the hash list via
* the rt_mutex code. See unqueue_me_pi().
*/
struct futex_q {
struct plist_node list;
struct task_struct *task;
spinlock_t *lock_ptr;
union futex_key key;
struct futex_pi_state *pi_state;
struct rt_mutex_waiter *rt_waiter;
union futex_key *requeue_pi_key;
u32 bitset;
};
看到了里面与 PI有关的东西,现在还不明白 ,一会儿通过几个函数了解一下
现在只要知道 futex 有 PI 和 non-PI之分, PI futex的 futex_q结构会有额外的几个成员, futex-> pi_state->pi_mutex会是一个rt_mutex ,而 rt_mutex_waiter是等待他的一个结构,通常分配在等待线程的栈上
三 函数执行流程
1. futex_lock_pi
实际上会将一个栈上的 rt_mutex_waiter插入到链表futex_q.pi_state->pi_mutex 中,这是一个rt_mutex的结构
调用流程: futex_lock_pi->rt_mutex_timed_lock-> rt_mutex_timed_fastlock->rt_mutex_slowlock->task_blocks_on_rt_mutex
debug_rt_mutex_init_waiter(&waiter); rt_waiter 是rt_mutex_slowlock 在栈上的临时分配的结构
随后futex_lock_pi->rt_mutex_timed_lock-> rt_mutex_timed_fastlock->rt_mutex_slowlock->__rt_mutex_slowlock
将进入无限等待,除非被唤醒
static int __sched
__rt_mutex_slowlock(struct rt_mutex *lock, int state,
struct hrtimer_sleeper *timeout,
struct rt_mutex_waiter *waiter)
{
int ret = 0;
for (;;) {
/* Try to acquire the lock: */
if (try_to_take_rt_mutex(lock, current, waiter))
break;
/*
* TASK_INTERRUPTIBLE checks for signals and
* timeout. Ignored otherwise.
*/
if (unlikely(state == TASK_INTERRUPTIBLE)) {
/* Signal pending? */
if (signal_pending(current))
ret = -EINTR;
if (timeout && !timeout->task)
ret = -ETIMEDOUT;
if (ret)
break;
}
2.futex_wait_requeue_pi
/*
* The waiter is allocated on our stack, manipulated by the requeue
* code while we sleep on uaddr.
*/
debug_rt_mutex_init_waiter(&rt_waiter);// 临时分配一个rt_waiter,与 futex_lock_pi类似
rt_waiter.task = NULL;
ret = get_futex_key(uaddr2, flags & FLAGS_SHARED, &key2, VERIFY_WRITE);
if (unlikely(ret != 0))
goto out;
q.bitset = bitset;
q.rt_waiter = &rt_waiter; //for use with requeue_pi
q.requeue_pi_key = &key2; //requeue pi target key
if(is_my_process){
printk("[%d] futex_wait_requeue_pi:Prepare to wait on uaddr.\n",
task_pid_vnr(current_task));
futex_dump_futex_q(&q);
}
/*
* Prepare to wait on uaddr. On success, increments q.key (key1) ref
* count.
*//等待从addr1 被唤醒
ret = futex_wait_setup(uaddr, val, flags, &q, &hb);
if (ret)
goto out_key2;
if(is_my_process){
printk("[%d] futex_wait_requeue_pi:before Queue the futex_q.\n",
task_pid_vnr(current_task));
futex_dump_futex_q(&q);
}
/* Queue the futex_q, drop the hb lock, wait for wakeup. */
futex_wait_queue_me(hb, &q, to); //将本线程插入futex2的队列中,这里是将 rt_waiter插入去等待
3 futex_requeue_pi(futex1 ,futex2 )会将futex1上面的 waiter唤醒并插入 futex2
如果这两个值相等,那么唤醒 futex1上的 waiter会使得 futex_wait_queue_me线程被唤醒,但是这个值又会被插入到 futex2中
由于futex_wait_requeue_pi的线程被唤醒并退出,那么 futex2的 rt_mutex队列上面便挂了一个已经被释放掉的 rt_mutex_waiter,这就是内核栈空间的use after free
四。如何利用?
futex_wait_requeue_pi所在的线程内核栈出现的 UAF问题,该线程利用 sendmmsg可以对内核堆栈进行控制
我们选择控制 rt_mutex_waiter结构中,这个结构有两个链表, UAF之后链表将被我们控制
struct rt_mutex_waiter {
struct plist_node list_entry;
struct plist_node pi_list_entry;
struct task_struct *task;
struct rt_mutex *lock;
}
于是我们调用 futex_lock_pi会走到task_blocks_on_rt_mutex 触发一个plist_add操作,造成内核栈信息泄漏,并且给了我们一次机会进行任意地址写
我们选择写内核栈上的 thread_info->addr_limit,一个栈上面的地址将会被写入到 addr_limit,导致我们有了从用户态写内核态的方法
这相当于造出了 CVE-2013-6282,读写任意地址
注意:该方法不能退出进程,否则释放被利用的线程将让内核崩溃