Own your Android! Yet Another Universal Root CVE-2015-3636 (中文翻译) (2)

来源:互联网 发布:每天编程一小时网站 编辑:程序博客网 时间:2024/06/06 07:10

Exploitation

目标

到现在我们弄明白了典型的UAF漏洞和一个用户空间悬垂的文件描述符,指向了内核中可以被攻击者达到的PING sock对象。接下来,我们将会展示我们的方法,来重写sock 对象以及通过复用这个PING sock对象来调用一些东西。在那之后,我们将可以执行内核中的任意代码,并最终达到在android设备上进行特权提升。

在实践中,我们使用socket对象的close函数。当close(sockfd)被调用的时候,内核最终会执行下图中的代码
release
内核调用inet_release来释放内核中的由sockfd联系的socket对象。而在这个函数中,它调用了sk->sk_prot->close()。

实际上,sk_prot是一个sk结构体类型的成员,它指向一个确定数量的函数指针。至于sk有什么函数指针,取决于它的协议类型,包括TCP, UDP, PING等等。

如果已经被释放的PING sock对象sk的内容被我们完全控制的数据重新填充,那么sk->sk_prot将完全被我们控制。它可以由用户空间的虚拟地址来确定。之后,函数指针sk->sk_prot->close的地址将会被控制。如果PAN(Privileged Access never)没有被内核采用,那么我们将最终可以控制内核上下文中的PC 寄存器。实际上,PAN没有被目前市场上任何android设备所采用,所以我们这里不需要考虑它。

总的来说,两种重要的因素需要我们考虑。其一是有漏洞的sock对象应该稳定且可靠的被重新填充,另外一个则是重新填充的内容应该完全被我们所控制。

重新填充

在这个root exploit当中最困难的就是用我们所想要的合适的数据来重写已经释放的sock对象,以及我们需要保证整个过程是稳定可靠的,来保证一个为其他android用户所写的成功的root工具的用户体验。 当然,我们使用的这个独特的且没有被文档记录过的利用方法中,可靠的且准确的重新填充已经释放的对象是闪光点。

考虑linux内核采用的堆管理机制,现在它使用了SLAB/SLUB分配器来有效的分配内核对象。

在内核中,不同的SLAB被创造给不同的对象,毫无疑问我们使用的有漏洞的PING sock对象的PING缓存也被创建了。 这样的分离在用户程序中也经常可见,比如Windows系统中IE的Isolated Heap,和Chrome的PartitionAlloc等等。当在内核中面对这些,攻击者想要用A类型的对象来占据一片曾经是不同于A的类型B的对象的空间是不太容易的。

另外一个带来不确定性的因素是由于linux内核对于多线程的支持。一个系统上(比如android设备)并行地运行上百个任务是很常见的,并且在每一个任务的执行过程中可能会导致内核中对象的分配和取消分配,最终会影响到内核中的堆分布。然而,一个可以预测的堆分布对于已知的UAF利用来说是十分重要的。

如果系统支持SLUB分配器那么事情就好办多了。而这正是市面上大多数android设备的情况。如果有漏洞的对象,比如PING sock,占据512或者1024的空间,那么重新填充就会简单很多。因为SLUB分配器趋向于把相同大小的对象放在一个SLAB缓存里,这意味着如果一个由漏洞的对象的大小为512,那么它将会和其他kmalloc-512对象一起放在一个SLAB中。这样被释放的有漏洞的对象的内容将可以通过影响malloc-512对象完全被控制。实际上,kmalloc-412对象在用户程序中是可以被创建的。一种方法是使用sendmmsg.在sendmmsg的执行过程当中,内核将会使用kmmaloc来分配一个内核中的缓存区来临时存储我们的传输数据包。传输数据的大小可以被我们所确定,并且它可以在这种情况下被设定为512。而且缓冲区的内容也可以被我们完全掌控,因为它只是我们想要通过sendmmsg来传输的数据。整个过程如下图所示。
通过sendmmsg控制内容
注意一下这种方法是UAF对象的一种极好的重新填充方法,但是它有一个严重的限制:有漏洞的对象应该是常用的SLAB所有的大小。换句话说,它的大小必须是可以被内核的kmalloc分配的大小。比如在一些android设备上,PING sock对象的大小是576,是在512和1024之间的,这样的话,这种方法就无效了。

理论上说,用kmalloc-size(kmalloc大小)的对象来重新填充内核中的任何对象都是可能的。这是利用了当整个SLAB都是空闲的时候它将被内核为了以后的使用而回收的规则。基于此,我们可以首先spray我们的PING sock对象来占据一些其他内核对象都没存储的SLAB。然后我们将尝试通过触发UAF漏洞来释放他们。一些完全被释放的SLAB将会产生。之后我们通过sendmmsg分配一定数量的传输缓冲区,大小为512。我们就由可能让这些缓冲区占据PING sock对象曾经占据的SLAB,重新填充就完成了。(如下图所示)
未对齐情况
然而这种方法十分难以被控制,并且有很大的不确定性。我们不能确切的知道哪些传输缓冲区存在了曾经存过PING sock对象的SLAB里,这样就使得整个root 利用过程不稳定且不可靠了。

另外,PING sock对象在不同设备上的大小是不同的。如果我们需要一种对于所有安卓设备通用的解决方法,那么我们应该不依赖于PING sock对象在这些设备上的大小。

现在我们来看看我们在被释放过的PING sock对象上重新填充来利用这个漏洞的巧妙方法。注意到我们并不关心PING sock在设备上的大小。这次我们甚至不需要用到其他内核对象来完成重新填充工作,而是使用pysmap。

physmap是内核里在[1]里曾经被提到过的。他是一大段内核空间的内存,并且直接将内存从用户空间映射到内核空间来提升系统的性能表现。

这意味着我们可以在用户空间里重复的通过调用mmap来spray数据,然后其中的许多都会直接在内核空间里出现。我们的意图是通过使用用户空间的数据来覆盖已经被释放的PING sock对象,并且很少的东西需要被考虑到。

如下图所示,
physmap
我们可以看到,在内核空间里,physmap和SLAB正常情况被安排在不同的位置。physmap从一个相对高的地址开始。为了使得他们在内核空间的中部相撞(冲突),我们需要做的第一步是spray一些其他的内核对象来使得内核分配器开始在高地址区域分配内核对象,这样就提高了SLAB和physmap之间的内存重用的可能性。这一步被称为抬高(lifting)。在我们针对CVE-2015-3636的root利用中,我们只是抬高PING sock对象自己,因为这样很容易在内核里分配(通过调用socket),并且取消分配(通过close)。

在抬高之后(如下图所示)
抬高之后的内存冲突情况
确定数量的PING sock对象将会被分配,但是这次他们将会作为有漏洞的对象。由于之前的抬高操作,这些PING sock对象将会在内核空间的高地址区域被安放。

在一会的取消分配的过程中,我们正常地取消分配这些为了抬高用的PING sock对象,但是对于有漏洞的那些,我们将会通过触发漏洞来释放(release)他们,这意味着通过一个PING socket来进行两次connection。

然后我们重复的调用mmap,以及用我们想要的东西来在用户空间填充被映射的区域。被spray的数据是8 个双字(dword)一组,每8个双字重复。

一个大问题是,当目标漏洞PING sock对象已经被physmap里的data所覆盖的时候,我们怎样知道什么时候来停止spray呢?为了解决这个问题,在8个双字中,除开一些 在我们达到最终控制的过程中的一些用来控制流程和避免内核崩溃(crash)的关键值,一些特定的实体(entry)已经被之前定义的magic值所填充了。

每次我们完成对确定数量的数据的spray,我们都在目标漏洞PING sock对象上调用ioctl(sockfd, SIOCGSTAMPNS, (struct timespec*))。

如下图所示
信息泄露
它读取出了sk的成员sk_stamp,然后通过调用使用特定的参数调用ioctl,我们可以成功的泄露出一个PING sock对象内的一个固定偏移量的一个双字值。然后可以确定覆盖是否已经完成。这一步确定了我们root过程的可靠性。

当我们已经准确地获得了覆盖的PING sock对象,我们就在悬垂的文件描述符上调用close。如同之前已经叙述过的,内核最终会调用sk->sk_prot->close,然后现在sk->prot已经完全被我们所控制然后它的地址指向一个用户空间的一个完全控制的位置,这样,close的函数指针就被控制了。于是最终,我们控制了内核上下文的PC 寄存器。

注意到我们最终的重新填充方案不依赖于这些android设备的特定配置或是内核的细节,它只是影响了linux内核区域直接映射出来的区域来达到我们的目标。

64位设备

我们上文所述的root利用也适用于android64位设备。主要有两个原因:

A. LIST_POISON2在android64位设备上也是0x200200,也就是可以再用户空间被映射的虚拟地址。对于PC linux 64位来说,这个值是0xdead000000000000,是一个64位值,于是绝对不可以被映射,甚至在64位系统的地址空间里都是超出范围的。于是,如果这个值不可以被映射,那么这个崩溃将在我们试图第二次连接一个PING socket的一定发生,不可避免。

B. physmap已经被证明可以在实际情况中覆盖64位设备的SLAB。 事实上,physmap在64位内核中将整个用户内存映射到内核中,于是spray依然有效。

最终,我们可以通过以上方法,在android 64位设备上控制内核的PC 寄存器。

1 0
原创粉丝点击