CVE-2015-7084是由于IORegistryIterator没有考虑用户态多线程同时调用的情况,引起Race Condition,可导致任意代码执行。漏洞存在于XNU版本3248.20.55之前的内核上,即Mac OS X 10.11.2、iOS 9.2、watchOS 2.1、tvOS 9.1之前的系统版本上。官方修复公告https://support.apple.com/en-us/HT205637。

0x01 漏洞背景

IORegistryIterator是用于XNU内核中用于遍历访问IO Registry Entry的一个类。在XNU版本3248.20.55之前的内核上,即Mac OS X 10.11.2、iOS 9.2、watchOS 2.1、tvOS 9.1之前的系统版本上,在操作IORegistryIterator时缺少锁机制,用户态进程通过多线程调用引起Race Condition,最终可实现任意代码执行。这个漏洞是由Google Project Zero的Ian Beer报告,CVE编号CVE-2015-7084。

0x02 漏洞分析

Ian Beer在https://code.google.com/p/google-security-research/issues/detail?id=598中给出了漏洞的说明,以及一份导致Double Free的PoC代码。

is_io_registry_iterator_exit_entry是IORegistryIteratorExitEntry对应的内核接口,会调用IORegistryIterator::exitEntry函数。

1 2 3 4 5 6 7 8 9 /* Routine io_registry_iterator_exit */ kern_return_t is_io_registry_iterator_exit_entry(     io_object_t iterator ) {     bool    didIt;     CHECK( IORegistryIterator, iterator, iter );     didIt = iter->exitEntry();     return( didIt ? kIOReturnSuccess : kIOReturnNoDevice ); }

.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool IORegistryIterator::exitEntry( void ) {     IORegCursor *   gone; …       if( where != &start) {       gone = where;   // Race Condition         where = gone->next;         IOFree( gone, sizeof(IORegCursor));  // gone可能被释放两次         return( true);     } else         return( false); … }

但是由于缺乏锁的保护,通过多线程调用IORegistryIteratorExitEntry,导致gone指向的内存区域被释放两次,引起崩溃。示意图如下:

0x03 漏洞利用

由于Double Free不易利用,Pangu Team在其博客文章https://blog.pangu.io/race_condition_bug_92/中提出了另外一种思路,可以稳定地利用Race Condition实现任意代码执行。下面将对这种思路进行具体的分析,在已知Kernel Slide的情况下,在Mac OS X 10.11上实现提权。

1. 攻击流程

通过观察操作IORegistryIterator的函数enterEntry,发现其包含向单向链表插入节点的操作,如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void IORegistryIterator::enterEntry( const IORegistryPlane * enterPlane ) {     IORegCursor *   prev;       prev = where;     where = (IORegCursor *) IOMalloc( sizeof(IORegCursor));     assert( where);       if( where) {         where->iter = 0;         where->next = prev; //在链表头部插入新的where节点,where->next指向旧where         where->current = prev->current;         plane = enterPlane;     } }

而IORegistryIterator::exitEntry中,包含移除单向链表头部节点的操作,并且释放移除的节点内存。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool IORegistryIterator::exitEntry( void ) {     IORegCursor *   gone; …       if( where != &start) {       gone = where;         where = gone->next; //从链表头部移除当前where节点         IOFree( gone, sizeof(IORegCursor)); //释放移除的节点内存区域         return( true);     } else         return( false); … }

在两个线程中分别调用IORegistryIteratorEnterEntry和IORegistryIteratorExitEntry,在特定的执行序列下,可能导致enterEntry在执行where->next = prev;时,prev指向的where区域已经被exitEntry的IOFree释放,就会导致where->next指向被释放的内存。

那么,当第二次调用exitEntry时,就会使得where指向被释放的内存,这块内存通过Heap Spray可以控制其中的内容。

1 2 3 4 5 6 7 8 9 bool IORegistryIterator::exitEntry( void ) { …     if( where != &start) {       gone = where; //where->next已指向被释放的区域       where = gone->next; //where指向被释放的区域     } … }

最后,第三次调用exitEntry时,where->iter可控,通过映射用户空间内存iter对象虚表,可实现任意代码执行。

1 2 3 4 5 6 7 8 9 bool IORegistryIterator::exitEntry( void ) { …     if( where->iter) {  // where->iter可控     where->iter->release();  //可通过构造虚表,执行任意代码     where->iter = 0;     } … }

攻击流程示意图如下:

2. Heap Spray