导语:JavaScriptCore使用clobberizer来决定哪些节点符合CSE(公共子表达式消除)的条件。
JavaScriptCore中常见子表达式的消除
JavaScriptCore使用clobberizer来决定哪些节点符合CSE(公共子表达式消除)的条件。文件Source/JavaScriptCore/dfg/DFGClobberize.h定义了如何对节点进行复制。
让我们看看给定节点是如何使用的示例:
对于Int52Constant类型的节点,clobberizer使用def()函子来评估该节点是否可以被CSE处理。Def()返回 [memory] 位置,它们必须遵守两个规则:
1.要被清除的两个节点之间的每条路径都不会对与所需位置重叠的堆位置执行任何写入操作。简单地说,如果在节点定义的堆位置上不执行任何写入,则会发生两件事:
1.1两个节点满足条件1;
1.2所述位置的值可以通过查看其中一个节点来推断;
2.如果要对加载操作进行CSE处理,则HeapLocation对象足以找到第二个符合条件的节点。
PureValue意味着对于给定的输入,计算的结果总是相同的,就像数学中定义函数(或计算机科学中的纯函数)一样。
漏洞分析
这个漏洞出现在ArithNegate案例中,这个案例用于处理数字的算术否定。漏洞是def()没有考虑是否推测该数字可能溢出,从而导致在两者之间进行漏洞替换。
这可能会导致CSE用一个不检查溢出问题的ArithNegate操作来代替检查溢出问题的ArithNegate操作。被推测为溢出的数字被称为“检查”:它们是运行时保护的,以在检测到溢出时救助优化的代码。
以32位整数为例:INT_MIN是最低的32位有符号整数,等于-2147483648,用二进制表示就是1000000000000000000000000000000000000000b。取消INT_MIN执行2的补码:
讨论的数字是有符号的,因此当设置第31位MSB而不是第32位MSB时,会发生溢出。
JavaScript用户通常希望-2147483648被否定后变为2147483648。如果引擎检测到执行的程序溢出试图重新发送超出范围的数字,通常会进行救援。取消INT_MIN会产生一个大于INT_MAX(2147483647)的数字,如果CSE将检查的整数与未检查的整数混淆,则可能无法检测到该数字。更具体地说,在JavaScriptCore中,否定INT_MIN返回以下结果:
上述漏洞可以概括为一种类型混淆溢出整数的替换。就其本身而言,这只是一个正确性问题,而不是安全性问题。与CSE漏洞结合导致安全漏洞的关键机制是IntegerRangeOptimization,用于删除绑定检查。
IntegerRangeOptimization(或更一般的值范围优化)的思想是删除被认为无用的边界检查。
Math.abs(...) 正确地将节点类型设置为ArithMode::CheckOverflow,并指示带有idx的分析器始终为正。Math.abs 的行为后来被一个未检查的替换。访问索引为-2147483647的数组本身并没有多大用处,但是可以通过导致溢出并获得一个正整数来轻松解决。
漏洞利用
成功利用此漏洞是在攻击者控制的内存区域中执行任意代码。这通常是通过制作JavaScript对象并通过控制后台存储指针(也就是指向数组写入位置的指针)使用它们访问内存位置来完成的。
为了方便起见,攻击者过去使用过ArrayBuffers:这是一种允许读取和写入原始字节的数据类型。2018年,苹果推出了Gigacage,这是一种安全缓解措施,它将ArrayBuffers的后台存储指针隔离在4GB区域,使它们实际上无法被利用。攻击者后来转向了JSArrays ,因为他们的Butterflies 存储原始的、不是很成熟的浮点值。
2019年,苹果公司引入了StructureID Randomisation,试图阻止攻击者手工制作对象。正如我们稍后将看到的那样,这种缓解被证明是相当无效的,因为大多数外接访问漏洞都可以用来泄漏有效的StructureID。
内存布局初始化
考虑到覆盖内存位置的能力受到限制,要实现的第一个目标是稳定的越界访问。让我们分析一下内存布局,它恰好非常稳定。该漏洞声明了构建第一个原语所需的几个变量:
注意使用noCoW来防止CopyOnWriteArray的创建。
这是上一个代码段的内存布局示意图:
butterflies是连续的,因此,越界访问可能允许访问以下butterflies。
触发漏洞
前面讨论的触发函数执行了1000次,这是一个通过尝试多次触发漏洞而不是一次尝试来增加稳定性的有用技巧。
跳出绑定访问后,内存布局将更改如下:
Float_arr现在可以索引4919个元素,其中4912个超出了原始边界。
出于调试目的,以下代码段执行完整性检查,并在出现漏洞时退出。
开发原语:addrof & fakeobj
JIT引擎的利用依赖于构建两个基本原语:addrof和fakeobj。“Address of [object]” 返回JavaScript对象的地址,很容易击败ASLR。“[Create a] fake object” 将内存地址视为JavaScript对象。这两个原语是通过在内存位置上有一个浮动数组和一个对象数组重叠来构建的。
浮点数组存储未装箱的原始浮点值。对象数组将指向对象的指针存储为盒装浮点值。因此,它们都存储浮点值,但根据其索引类型ArrayWithDouble ArrayWithContiguos,它们的用法有所不同。
鉴于漏洞已经被触发,这两个原语定义如下:
另一个用于评估漏洞的正确性的健全检查如下:
StructureID随机绕过
每个JSObject都有一个JSCell类型的标头文件。
标头文件如下所示: (file: Source/JavaScriptCore/runtime/JSCell.h)。
m_structureID是一个用于描述JSObject形状的out- line(意为非内联)对象的标识符。
这些形状描述符在StructureIDTable中建立索引,每个有效的条目都有7个熵位,这些熵位成功地削弱了猜测有效StructureID的能力,从而使内存喷射无法用于此目的。
这让攻击者陷入了两难境地:制作对象需要有效的StructureID,但泄漏StructureID需要正确构造的对象。
幸运的是,这两个断言都不成立,因为有可能使用带有无效StructureID的对象,并且有可能泄漏带有半伪造对象的StructureID。
攻击者很快就想出了规避这种情况的可能策略,根据addrof、fakeobj和JSC特定行为提出了一个通用的绕过方法。这种绕过使用不检查StructureID(如getByVal)的代码路径。
由于canGetIndexQuick不检查有效的StructureID,因此我们可以创建一个具有无效ID的伪对象,并使用它读取正确的对象。我们要泄漏的标头是索引类型为ArrayWithDouble的JSArray使用的标头,因此我们将创建一个:
在创建了一个数组以泄漏标头之后,是时候创建一个带有自定义标头和butterfly的对象了:
泄露StructureID,允许有一个完全受控的、正确构建的fake_arr:
现在,攻击者控制了一个JavaScript对象,该对象可以修改另一个对象(在这个特定实例中为rw_arr)的元数据,他们可以决定后者从何处读取和写入:
Fake_arr[1]设置rw_arr用于读取和写入的内存地址。例如,攻击者可以尝试使用rw_arr读取0x4141414141414141,从而使该地址的进程崩溃。
任意读/写,任意代码执行
获得代码执行是获得RWX内存区域并用所需的shellcode覆盖它的漏洞。进一步的信息留给源代码本身。
完整的漏洞可以在GitHub上找到。
泄漏rwx_addr偶尔会失败,并返回0x7ff8000000000000,导致进程崩溃。可以通过进一步研究来提高可靠性。
本文翻译自:https://shxdow.me/cve-2020-9802/如若转载,请注明原文地址