如何在4小时或更短的时间内取消0天

2020-07-10 02:10:23

美国太平洋标准时间2020年5月23日下午3点,针对iOS 13.5(发布时的最新签名版本)发布了un0ver越狱,该版本使用零日漏洞和严重混淆。到了晚上7点,我发现了漏洞,并通知了苹果公司。到凌晨1点,我给苹果发了一份POC和我的分析报告。这篇文章将带你踏上这段旅程。

我想找出un0ver中使用的漏洞,并迅速将其报告给Apple,以证明混淆漏洞对防止漏洞落入坏人手中没有多大帮助。

下载并解压缩了un0ver IPA之后,我将主可执行文件加载到RDA中进行查看。不幸的是,二进制文件被严重混淆,所以静态地查找错误超出了我的能力范围。

接下来,我将un0ver应用程序加载到运行iOS13.2.3的iPod Touch7上,尝试运行漏洞攻击。浏览应用程序界面并不意味着用户可以控制使用哪个漏洞来利用该设备,所以我希望un0ver只支持0天,而不是在iOS13.3及更低版本上使用OOB_TIMESTAMP漏洞。

当我单击“越狱”按钮时,我突然想到:我以前编写过几次内核漏洞攻击,我知道大多数基于内存损坏的漏洞攻击都有某种程度上的临界区,在此期间内核状态已被损坏,如果其余漏洞攻击不继续,系统将不稳定。所以,我心血来潮,双击主页按钮,调出了应用程序切换器,并关闭了un0ver应用程序。

死机(CPU 1调用者0xfffffff020e75424):";释放后使用区域缓存元素!元素0xffffffe0033ac810在开始时已损坏;应为0x87be6c0681be12b8,但找到0xffffe003059d90;金丝雀0x784193e68284daa8;区域0xfffffff021415fa8(kalloc.16)";

这看起来很有希望!我收到一条紧急消息,说kalloc.16分配区域(大小最大为16字节的通用分配)中存在释放后使用。然而,这可能是内存损坏的症状,而不是内存损坏的根源(甚至可能是诱饵!)。为了进一步调查,我需要分析回溯。

在等待IDA处理iPod内核缓存的同时,我又尝试了几个即兴实验。由于许多利用漏洞使用Mach端口作为基本原语,因此我编写了一个应用程序,它将搅乱ipc.ports区域,创建碎片并混淆自由列表。当我后来运行un0ver应用程序时,该漏洞仍然有效,这表明它可能不依赖于Mach端口分配的堆疏导。

接下来,由于死机日志提到了kalloc.16,我决定编写一个应用程序,在un0ver利用期间在后台持续分配和释放kalloc.16。当时的想法是,如果un0ver依赖于重新分配kalloc.16分配,那么我的应用程序可能会抢占那个位置,这可能会导致利用策略失败,并可能导致内核恐慌。果不其然,当我的应用程序在后台敲打kalloc.16时,触摸越狱按钮立即引起了内核恐慌。

作为一个理智的检查,我试着把我的应用程序换成一个不同的区域,kalloc.32,而不是kalloc.16。这一次利用漏洞成功运行,这表明kalloc.16确实是利用漏洞使用的关键分配区。

最后,一旦IDA完成了对iPod kernelcache的分析,我就开始对从死机日志中收集的堆栈跟踪进行符号化。

对lio_listio()的调用立即引起了我的注意。不久前,我刚刚完成了对最近iOS内核漏洞的调查,我碰巧记得lio_listio()是基于LightSpeed的漏洞攻击中使用的易受攻击的syscall。我重读了Synacktiv的博客文章以了解该bug,并立即发现了另一条信息:在LightSpeed竞赛中双重释放的目标对象是一个驻留在kalloc.16中的aio_lio_context对象。此外,un0ver应用程序中的大量线程进一步支持争用条件的想法。

在这一点上,我觉得我有足够的证据联系苹果,初步分析表明这个漏洞是LightSpeed,要么是一个变体,要么是一个回归。

接下来,我想通过编写POC来触发问题来确认错误。我尝试了LightSpeed博客帖子中显示的原始PoC,但运行了一分钟后,它还没有出现恐慌。这让我觉得0day可能是最初的LightSpeed bug的一个变体。

为了了解更多信息,我开始了两行调查:查看XNU源代码以尝试发现错误,并使用checkkra1n/pongoOS修补内核缓存中的lio_listio(),然后运行漏洞攻击。从来源来看,我根本看不到最初的漏洞是如何修复的,这对我来说是没有意义的。因此,我转而将精力集中在内核补丁上。

引导打了补丁的kernelcache很棘手,但由于checkm8的原因是可行的。我下载了checkkra1n并将iPod引导到了pongoOS shell中。使用来自pongoOS repo的示例作为指南,我创建了一个可加载的pongo模块,该模块将禁用checkkra1n内核补丁,并应用我自己的补丁。(我禁用了checkkra1n内核补丁,因为我担心un0ver会检测到checkkra1n并采取反分析措施。)

我的第一个测试只是将无效的指令操作码插入到lio_listio()函数中,以便它在被调用时会死机。令人惊讶的是,设备启动得很好,然后一旦我点击“越狱”按钮,它就会惊慌失措。这意味着un0ver是唯一调用lio_listio()的进程。

接下来,我修补了负责分配aio_lio_context对象的代码,该对象在最初的LightSpeed错误中被双重释放,以便从kalloc.48而不是kalloc.16分配:

其想法是,增加对象的分配大小将导致un0ver的利用策略失败,因为它将尝试用kalloc.16中的替换对象替换意外释放的kalloc.48上下文对象,而这是根本不可能发生的。果不其然,有了这个补丁后,Un0ver在利用内核的步骤停止了,而实际上并没有惊慌失措。

然后,我又运行了几个实验,修补函数中的各个点,以转储传递给lio_listio()的参数和数据缓冲区,以便可以与原始LightSpeed POC中使用的值进行比较。我们的想法是,如果我注意到任何实质性的差异,就会为我指明源代码中的变体的方向。然而,除了字段aio_reqprio被设置为##39;gang';之外,un0ver传递给lio_listio()的参数与原始PoC中的参数没有区别。

在这一点上,看起来0day实际上可能是最初的LightSpeed漏洞本身,而不是一个变体,所以我回到最初的POC,看看它没有触发的原因是否是使用的一种特定技术已经减轻了。负责重新分配kalloc.16分配的代码引起了我的注意:

/*此技术相当慢(等待时间为1ms),并且存在更好的方法*/。

我以前从未见过pol()用作重新分配原语。直觉上,我觉得使用基于Mach端口的重新分配策略更有前途,所以我用从OOB_TIMESTAMP复制的离线Mach端口喷射替换了这段代码。果然,这是在几秒钟内使PoC触发可靠所需的唯一更改。

在我有了一个正常工作的PoC后,我重试了最初的LightSpeed PoC,发现如果运行足够长的时间,它最终会死机。因此,这是另一个可以通过简单的回归测试识别的重新引入的bug案例。

那么,让我们回到源头,看看我们是否能弄清楚到底发生了什么。正如前面提到的,当我第一次检查XNU源代码以查看lio_listio()补丁可能是如何被破坏时,我实际上根本无法确定bug最初是如何修补的。回想起来,这并不遥远。

最初的LightSpeed博客文章很好地描述了这个漏洞,所以我不会在这里重述它;我强烈推荐阅读那篇文章。从高层次来看,错误在于哪个函数释放aio_lio_context对象的语义不清楚,因为执行异步I/O的工作线程和lio_listio()函数本身都可以做到这一点。

正如在文章中提到的,此错误的原始修复只是在可能双重释放的情况下不释放aio_lio_context对象:

一方面,此补丁将潜在的UAF固定在lio_context上。但是另一方面,在修复之前处理的错误案例现在被忽略了……。因此,可以让lio_listio()分配内核永远不会释放的aio_lio_context。这给了我们一个愚蠢的DoS,也会使最近的内核崩溃(包括iOS12)。

至于其他方面,我们将拭目以待,看看苹果是否会费心修复他们用补丁引入的小DOS:D。

事实证明,苹果最终确实决定修复iOS13中的内存泄漏问题……。但在这样做的过程中,他们似乎重新引入了双重自由的竞争条件:

iOS13中的代码与iOS11中的代码并不完全相同,但在语义上是等价的。任何记得并理解最初的LightSpeed bug的人都可以通过查看XNU源代码差异很容易地识别出这是一种回归。任何运行相对简单的回归测试的人都会发现这个问题微不足道。

所以,总结一下:iOS12已经修复了LightSpeed漏洞,但补丁并没有解决根本原因,而只是将竞态条件双重释放变成了内存泄漏。然后,在iOS13中,此内存泄漏被确定为错误,并通过重新引入原始错误修复,同样没有解决问题的根本原因。通过运行博客帖子中的原始PoC,可以很容易地找到这种安全回归。

您可以在Synacktiv博客的后续文章中了解更多关于这种回归的信息。

iOS12.4中的SOCKPUPET回归和iOS13中的LightSpeed回归的结合强烈地表明,苹果没有对至少这些旧的安全漏洞进行有效的回归测试(这些都是非常公开的错误,得到了很多关注)。运行有效的回归测试是基本软件测试的必要条件,也是开发的常见起点。

尽管如此,我还是很高兴苹果在漏洞公开后及时修补了这个问题。这里的现实情况是,攻击者在公共POC发布之前很久就很快发现了这些问题。因此,利用衰退的机会之窗是巨大的。

此外,我试图识别un0ver使用的错误的目的是为了证明混淆并不能阻止攻击者快速武器化利用的漏洞。事实证明,我的分析是幸运的:我编写内核漏洞的经验让我很快找到了找到漏洞的替代策略,而且我碰巧已经熟悉了所使用的特定漏洞,因为我一直在跟踪过去的漏洞。但任何从事利用漏洞攻击苹果用户的人也会拥有同样的优势。