安全防护人员早已开发出各种方法来预防各类内存损坏漏洞。不过就算这样,UAF漏洞也很难被防护,原因是它的攻击面太多!由于它无法与源代码中的任何特定模式相关联,因此预防此漏洞类并非易事。在本文中,我将分析 Mozilla Firefox 中的一个UAF漏洞,该漏洞已被命名为 CVE-2022-26381。在撰写本文时,Mozilla 错误条目 1756793 仍然不对公众开放。
什么是UAF漏洞?
当访问指向已释放对象的指针时,就会发生UAF漏洞。它没有任何意义!为什么程序员要释放一个对象,然后再次访问它?
这种情况的发生是由于当今软件的复杂性。例如,一个浏览器有很多组件,每个组件都可以分配不同的对象。它们甚至可以互相传递这些对象以进行处理。当组件使用一个对象时,它可以释放该对象,而其他组件仍然有一个指向该对象的指针。该指针的任何解除引用都可能导致UAF漏洞。
概念验证
让我们先来看看最小化的概念验证,当在最新版本的Mozilla Firefox(97.0.1)上运行时,很有可能会崩溃。这就是 IDA 中崩溃时的示例。它发生在一个循环中:
它从内存中解除引用一个值,然后使用获取的值进行间接调用(虚函数调用)。因此,这被认为是一个远程代码执行漏洞。在解除引用期间使用的“rax”寄存器的值特别有趣:0xE5E5E5E5E5E5E5E5。这是一个神奇的值,Firefox 使用它来“毒化”已释放对象的内存,这样从释放对象获取的值的解除引用将导致崩溃,因为这个值从来都不是有效的内存地址。这有助于检测UAF的发生。
要分析UAF漏洞,就必须获得有关释放对象的更多信息,比如类型、大小、分配位置、释放位置以及随后使用的位置。在 Windows 上,这通常通过使用 GFlags 工具启用高级调试功能来启用各种全局标志来完成。具体来说,它可用于启用 pageheap 并创建用户模式堆栈跟踪以在分配特定对象时捕获堆栈跟踪。不幸的是,这对 Mozilla Firefox 没有帮助,因为 Firefox 有自己的内存管理机制,称为 jemalloc。我们可以获得有关该对象的更多信息的方法是在 ASAN 版本的 Firefox 上运行 PoC。你可以看到如下结果:
我们得到了很多信息。让我们通过检查对象的分配位置来进一步分解它:
让我们通过查看源代码(/builds/worker/checkouts/gecko/layout/svg/SVGObserverUtils.cpp 的第 1164 行)来进一步检查这个问题。你可以下载Firefox 97.0.1的源代码或使用在线版本(注意在线版本的行号可能不匹配,因为它会不断更新):
这是它在编译后发布版本中的样子。因此对象大小为 0x70 (112) 字节,用于在滚动触发的reflow期间存储和跟踪帧的属性。
然后我们想知道它在哪里被释放和重用。ASAN提供了一个很长的堆栈跟踪。仔细观察就会得到一个很好的提示。让我们首先检查当对象被释放时的堆栈跟踪:
现在是随后使用该对象时的堆栈跟踪:
当崩溃发生和启动对象释放时,我们可以在堆栈跟踪中看到" mozilla::SVGRenderingObserverSet::InvalidateAll "函数。这也与 OnNonDOMMutationRenderingChange 函数内部的发布版本的崩溃点相匹配(它表示它已内联在 xul!mozilla::SVGRenderingObserverSet::InvalidateAll 中)。我们现在可以做一个初步的有根据的猜测:当一个对象在“mozilla::SVGRenderingObserverSet::InvalidateAll”函数中循环处理时,就会到达一个释放正在处理的对象的代码路径,从而触发了一个UAF漏洞。
现在我们已经掌握了所有实施过程,就可以通过在Firefox发布的版本上运行PoC一步一步地验证这个假设。
首先,要知道分配对象的地址,以便我们可以监控它。这可以通过设置一个断点来轻松实现,该断点在分配时会打印对象的地址:
然后,让我们看看这些对象是如何在IDA中的“mozilla::SVGRenderingObserverSet::InvalidateAll”函数的循环中被处理的。我们将打印将要被处理的对象的地址,并在随后的虚函数调用中设置了一个断点:
我们运行 PoC,调试器在调用虚函数之前停止。如上所示,分配了两个对象,这两个对象将在循环中处理。首先,处理一个对象并调用“SVGTextPathObserver::OnRenderingChange”函数,最终释放各种分配的对象,包括等待处理的第二个对象!
我们可以在下图中清楚地看到这一点,这是在调用返回后立即拍摄的。正如你所看到的,在处理第一个对象时,第二个对象已经被释放(并且被0xe5毒化):
在第二次迭代中,被释放的对象被加载进行处理,导致有毒值的加载,并导致崩溃:
在针对发布版本运行 PoC 时,我们在解除引用 0xE5E5E5E5E5E5E5E5 期间发生了崩溃。但是,在 ASAN 版本中,它在写入内存时崩溃。为什么有区别?原因如下:
在release(非asan)构建中,当释放一个对象时,它的内存仍然是可访问的(而不是未映射的),因此对该内存的任何读取和写入仍然可正常进行,而不会立即触发崩溃。这就是为什么指令“mov byte ptr [rcx+8], 0”在上图中没有错误的执行。不过,崩溃可能会在更长的时间内发生。在本文的示例中,如果从释放的对象中获取一个值,然后解除引用,那么解除引用可能会导致崩溃。如果释放的对象内容被上面看到的毒值覆盖,则尤其如此。请注意,有可能根本不会发生崩溃,例如,如果只对释放的对象进行读取和写入,而没有对获取的值进行任何解除引用操作,或者有毒的值被不相关的数据覆盖。这意味着,如果我们对发布版本进行模糊处理,就有可能错过一个漏洞。
另一方面,ASAN监视内存上的所有读、写和解除引用,可以尽快捕获此类漏洞。这就是为什么推荐使用ASAN版本进行模糊测试的原因。
漏洞修复
UAF漏洞通常通过将原始指针转换为智能指针或通过更正对象引用计数的管理来修复。这样就可以通过更改引擎中处理连续帧循环的方式来修复它:
总结
开发人员已经花费了大量的精力来消除源代码中与已知模式相关的漏洞,并且他们基本上成功地降低了这些漏洞的影响。然而,有一些类型的漏洞更难预防,UAF就是其中之一。确保对具有一百万行代码的软件中的对象生命周期进行完美管理是极其困难的。
本文翻译自:https://www.zerodayinitiative.com/blog/2022/4/7/cve-2022-26381-gone-by-others-triggering-a-uaf-in-firefox如若转载,请注明原文地址