iOS jailbreak details revealed: dangerous user-mode read-only memory, interpretation of the topic from Tencent Cohen Lab Black Hat USA 2018

ios
mp-weixin-qq-com

#1

首先GPU会映射一个128个元素的stampArray给kernel,kernel也会把这块内存映射到用户态。与此同时,内核也维护了一个stamp address array,用于保存每个channel的stamp地址:

值得注意的是,GPU每处理完一个绘图指令后,都会将对应channel的stamp值加1。另一方面,对于每个绘图指令,都会有一个期望stamp值,这个值被封装于IOAccelEvent对象中:

系统通过简单的比较expectStamp于当前channel的stamp值就可以确定这个绘图指令是否已经完成。为了提高处理效率,部分IOAccelEvent对象会被映射到用户态,属性只读。

在介绍完了所有这些机制性的问题后,我们来介绍两个用于Jailbreak的关键漏洞。漏洞1存在于DMA映射模块,先前提到,系统的内存属性mapOption会被传入底层DART的代码中,然而在iOS 10以及早期的iOS 11版本中,这个mapOption参数被下层的DART转换所忽略:

所有操作系统中的虚拟地址,都会被映射成IOSpace中允许读写的内存:

之后我们介绍第二个漏洞,这个漏洞存在于苹果图形模块中。在IOAccelResource对象创建过程中,一个IOAccelClientShareRO对象会被映射到用户态作为只读内存,这个对象包含4个IOAccelEvent对象:

在IOAccelResource对象销毁过程中,testEvent函数会被执行,用于测试IOAccelResource对应的绘图指令是否已经被GPU处理完成:

在这个代码逻辑中,由于内核充分信任这块IOAccelEvent内存不会被用户态程序篡改(因为是只读映射),因此并没有对channelIndex做边界检查。这虽然在绝大多数情况下是安全的,但如果我们配合漏洞1,在用户态直接修改这块只读内存,就会导致可信边界被彻底破坏,从而造成m_stampAddressArray的越界读以及m_inlineArray的越界写:

最后,我们讨论两个漏洞的利用。要利用这两个漏洞并不容易,因为我们需要找到一种内存布局方法,让m_stampAddressArray以及m_inlineArray这两个数组的越界值都可控。但因为这两个数组在系统启动初期就已经分配,而且这两个数组的元素大小并不相同,因此布局并不容易。

经过研究,我们发现,只有通过指定大index以及合理的内核对喷,才能实现这样的布局。因为在iPhone7设备中,用户态应用可以喷射大概350MB的内存,并且在m_stampAddressArray以及m_inlineArray初始化后,会有额外50MB的内存消耗,因此我们需要使得index满足以下两个条件:

  1. index ∗ 24 < 350MB + 50MB

  2. index ∗ 8 > 50MB

也就是说index的值需要在[0x640000, 0x10AAAAA]这个范围内,可以使得两个数组的越界值极大概率在我们可控的喷射内存内:

然后,下一个问题就是,我们是否能够任意地址读以及任意地址写。对于任意地址读似乎不是什么问题,因为m_stampAddressArray的元素大小是8字节,可以通过指定任意index到达任意内存地址。但任意地址写需要研究,因为m_inlineArray的元素大小是24字节,只有一个field可以用于越界写,所以不是每个内存地址都可以被写到:

在这种情况下,我们退求其次,如果能实现对于一个页中的任意偏移值进行写操作,那么也可以基本达到我们的要求。在这里,我们需要通过同余定理来实现:

因为:

0xc000 ≡ 0(mod0x4000)

因此对于任意整数n,满足:

n ∗ 0x800 ∗ 24 ≡ 0(mod0x4000)

由于0x4000 / 24 * 0xF0 / 16 = 0x27f6,我们得到:

0xF0 + 0x27f6 ∗ 24 + n ∗ 0x800 ∗ 24 ≡ 0(mod0x4000)

最后我们得出结论,如果需要通过越界m_inlineArray写到一个页的前8字节,需要满足:

index = 0x27f6 + n ∗ 0x800

而如果需要到达任意页内偏移,假设要到达偏移m,则index需要满足条件:

index = 0x27f6 + 0x2ab ∗ m/8 + n ∗ 0x800

与之前得出的index范围结论相结合,我们最终选择了index值0x9e6185:

然后我们通过以下几个步骤进行漏洞利用,在第一个布局中,我们得出Slot B与Slot C的index值:

随之我们将slot B填入相同大小的AGXGLContext对象,然后再次利用漏洞泄露出其vtable的第四位:

最后我们通过将Slot C释放并填入AGXGLContext对象,将其原本0x568偏移的AGXAccelerator对象改为我们可控的内存值,实现代码执行:

最后,整个利用流程总结如下:

Original links

腾讯科恩实验室Black Hat USA 2018议题解读|iOS越狱细节揭秘:危险的用户态只读内存