-
-
[翻译]365天后:使用公开工具发现并利用Safari Bugs
-
发表于: 2018-11-24 21:01 5144
-
在之前的研究发布之后,我们收到许多关于fuzzing步骤的问题。这也是为什么这次我们要在下面发布对WebKitGTK+代码的修改位置和详细的构建指令。补丁文件可以在这里找到。注意补丁是针对WebKitGTK+ 2.20.2的,在其它版本可能无法工作。
一旦准备好WebKitGTK+的代码,在WebKitGTK+目录使用下面的命令构建带ASan的版本:
作者:Google Project Zero团队Ivan Fratric
原文链接:
https://googleprojectzero.blogspot.com/2018/10/365-days-later-finding-and-exploiting.html
校对:银雁冰
考虑到在之前的研究中,Safari,也即Webkit(它的DOM引擎),比其它浏览器情况更糟糕,我们决定在一年后使用相同的方法、相同的工具看看情况有没有改观。
测试步骤
和之前的研究一样,我们首先对WebKitGTK+进行了fuzzing,然后在MAC中运行的Apple Safari上测试每个得到的奔溃样例。因为WebKitGTK+和Safari使用相同的DOM引擎,这使得fuzzing过程得以简化,而且fuzzing过程可以在一台普通的linux机器上进行。在本研究中,使用的是WebKitGTK+的2.20.2版本,可以在这里下载到。为了优化fuzzing过程,对WebKitGTK+进行了一些修改:
- 修改使得可以使用ASan (Address Sanitizer)构建WebKitGTK+
- 修改window.alert()的实现以立即调用垃圾回收过程而不是显示一个消息窗口。这对运行没影响,因为window.alert()在fuzzing时不会被调用
- 通常,由于WebKit采用多进程,当一个DOM bug引起一个崩溃时,只有web进程崩溃,主进程将继续运行。我们增加了代码来监控一个web进程,如果web进程崩溃,代码就以相同状态将主进程崩溃。
- 生成修改后的二进制文件
在之前的研究发布之后,我们收到许多关于fuzzing步骤的问题。这也是为什么这次我们要在下面发布对WebKitGTK+代码的修改位置和详细的构建指令。补丁文件可以在这里找到。注意补丁是针对WebKitGTK+ 2.20.2的,在其它版本可能无法工作。
一旦准备好WebKitGTK+的代码,在WebKitGTK+目录使用下面的命令构建带ASan的版本:
export CC=/usr/bin/clang export CXX=/usr/bin/clang++ export CFLAGS="-fsanitize=address" export CXXFLAGS="-fsanitize=address" export LDFLAGS="-fsanitize=address" export ASAN_OPTIONS="detect_leaks=0" mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=. -DCMAKE_SKIP_RPATH=ON -DPORT=GTK -DLIB_INSTALL_DIR=./lib -DUSE_LIBHYPHEN=OFF -DENABLE_MINIBROWSER=ON -DUSE_SYSTEM_MALLOC=ON -DENABLE_GEOLOCATION=OFF -DENABLE_GTKDOC=OFF -DENABLE_INTROSPECTION=OFF -DENABLE_OPENGL=OFF -DENABLE_ACCELERATED_2D_CANVAS=OFF -DENABLE_CREDENTIAL_STORAGE=OFF -DENABLE_GAMEPAD_DEPRECATED=OFF -DENABLE_MEDIA_STREAM=OFF -DENABLE_WEB_RTC=OFF -DENABLE_PLUGIN_PROCESS_GTK2=OFF -DENABLE_SPELLCHECK=OFF -DENABLE_VIDEO=OFF -DENABLE_WEB_AUDIO=OFF -DUSE_LIBNOTIFY=OFF -DENABLE_SUBTLE_CRYPTO=OFF -DUSE_WOFF2=OFF -Wno-dev .. make -j 4 mkdir -p libexec/webkit2gtk-4.0 cp bin/WebKit*Process libexec/webkit2gtk-4.0/
如果你是第一次进行构建,cmake/make步骤将提示丢失依赖项,你需要安装它们。许多对DOM fuzzing不是很重要的特性都要通过-DENABLE标记取消了。这主要为我们省去了安装相关依赖项的麻烦,但在某下情况下需要创建更加“可移植”的构建。构建完成后,进行fuzzing和创建一个Domato样例一样简单,首先以如下方式运行二进制文件:
ASAN_OPTIONS=detect_leaks=0,exitcode=42 ASAN_SYMBOLIZER_PATH=/path/to/llvm-symbolizer LD_LIBRARY_PATH=./lib ./bin/webkitfuzz /path/to/sample <timeout>
等待退出代码42(根据上面的命令行和我们对WebKitGTK+代码的修改,这表明发生了ASan崩溃)。收集崩溃之后,在真实的Mac机器上对最新WebKit源代码创建了ASan构建。简单运行如下即可:
./Tools/Scripts/set-webkit-configuration --release --asan ./Tools/Scripts/build-webkit
从WebKitGTK+获得的每个崩溃在报告给Apple之前都在Mac构建上进行了测试。
结果
迭代1000.000.000次fuzz后(与一年前一样),我最终给Apple报告了9个bugs。去年,我估计购买执行这些迭代的计算能力需要1000美元,今年也差不多,这对很多潜在的攻击者来说都支付得起。
找到的Bug整理在下表中。所有bug在文章发布之日前都已经被修复了。
找到的Bug整理在下表中。所有bug在文章发布之日前都已经被修复了。
Project Zero bug ID | CVE | Type | Affected Safari 11.1.2 |
Older than 6 months |
Older than 1 year |
1593 | CVE-2018-4197 | UAF | YES | YES | NO |
1594 | CVE-2018-4318 | UAF | NO | NO | NO |
1595 | CVE-2018-4317 | UAF | NO | YES | NO |
1596 | CVE-2018-4314 | UAF | YES | YES | NO |
1602 | CVE-2018-4306 | UAF | YES | YES | NO |
1603 | CVE-2018-4312 | UAF |
NO
|
NO | NO |
1604 | CVE-2018-4315 | UAF | YES | YES | NO |
1609 | CVE-2018-4323 | UAF | YES | YES | NO |
1610 | CVE-2018-4328 | OOB read | YES | YES | YES |
UAF = use-after-free. OOB = out-of-bounds
如表中所示,9个发现的bug中有6个影响Apple Safari的发布版本,这直接影响到Safari的用户。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
发现这些结果后,我想看看这些bug在WebKit代码中已经存在多久了。为了检测这个,所有bug在6个多月前(WebKitGTK+ 2.19.6)和一年前(WebKitGTK+ 2.16.6)的WebKitGTK+版本上进行了测试。
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
- 安全漏洞持续被引入WebKit的基础代码中;
- 这些bug中的许多在被网络安全专家捕获前就已经被引入到产品中了。
尽管对于任何像DOM引擎这样迭代迅速的软件来说,a)都比较常见,然而b)似乎表明:在发布前,需要投入更多计算资源到fuzzing和/或审计中。
利用
为了证明这些bug确实会导致浏览器被控制,我决定为其中一个写一个exploit。这里的目的不是写一个稳定的、复杂的exploit,高级攻击者都不喜欢选择公开工具发现的bug,这些bug的生命周期相对短。然而,如果一个有写exploit能力的人使用这些bug,比如进行恶意软件传播,就算使用一个不稳定的exploit也将造成很大损害。
在影响Safari 发布版本的6个bug以外,我选择我认为最容易利用的——一个UAF,不像发现的其它UAF,这个漏洞的释放对象不在隔离堆中——一个最近引入WebKit的防护机制,该机制使得UAF利用更困难。
在影响Safari 发布版本的6个bug以外,我选择我认为最容易利用的——一个UAF,不像发现的其它UAF,这个漏洞的释放对象不在隔离堆中——一个最近引入WebKit的防护机制,该机制使得UAF利用更困难。
我们先测试下我们要利用的bug。这个问题是SVGAnimateElementBase::resetAnimatedType()函数内的一个UAF。浏览这个函数的代码,你可以看到,首先,函数在下面这行获得了一个指向SVGAnimatedTypeAnimator对象的原始指针:
SVGAnimatedTypeAnimator* animator = ensureAnimator();
然后,到函数结束,animator对象被用于获得(除非已经存在一个)一个指向SVGAnimatedType对象的指针:
m_animatedType = animator->constructFromString(baseValue);
问题在于,在这两行代码运行之间,攻击者控制的JavaScript代码能够获得运行。特别地,这可以发生在调用computeCSSPropertyValue()期间。JavaScript代码可以导致SVGAnimateElementBase::resetAnimatedPropertyType()被调用,该函数将删除animator对象。因此,在释放的animator对象上会调用constructFromString()函数——一个典型的UAF场景,至少第一眼看上去如此。在这个bug中还有一些其他东西,我们之后讨论。
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
<body onload="setTimeout(go, 100)"> <svg id="svg"> <animate id="animate" attributeName="fill" /> </svg> <div id="inputParent" onfocusin="handler()"> <input id="input"> </div> <script> function handler() { animate.setAttribute('attributeName','fill'); } function go() { input.autofocus = true; inputParent.after(inputParent); svg.setCurrentTime(1); } </script> </body>
在这里,svg.setCurrentTime()导致调用resetAnimatedType(),反过来,基于之前的DOM变化,引起JavaScript事件处理例程被调用。在事件处理例程中,通过重置animator对象的attributeName属性(导致调用SVGAnimateElementBase::resetAnimatedPropertyType())将对象删除。
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
- 在对象结构上下文以外调用虚拟方法是个危险的行为。例如,如果你可以调用某个对象的析构函数,它的成员会在对象还存活时就被释放掉。这时,我们就可以将一个UAF问题转换成另一个UAF问题,也许这另一个更好利用。
- 当constructFromString()有一个类型为string的参数,如果另一虚方法(用于替换的对象的虚方法)需要一个其它类型的参数,我们可以在输入参数上引起一个类型混淆。如果其它虚方法相比constructFromString()需要更多的参数,这些参数将是未初始化状态,这也可以产生利用条件。
- 如果constructFromString()返回的是SVGAnimatedType类型的指针,用于替换的虚方法返回的是其它类型,这可以在返回值上产生类型混淆。而且,如果该虚方法不返回任何值,则返回值是未初始化状态。
- 如果被释放对象的vtables和用于替换的对象大小不同,调用被释放对象的vtable指针将导致越界读到其它对象的vtable,导致调用到第三个类的虚函数。
在本次利用中,我们选择第三种,但要做些变化。要理解变化是什么,让我们更详细地检视下SVGAnimateElementBase类:它实现了SVG元素<animate>的大多数功能。SVG<animate>元素用于赋予另一个元素某个属性。例如,使一个SVG图像有如下元素:
<animate attributeName="x" from="0" to="100" dur="10s" />
将导致目标元素的X坐标(默认是其父元素)在10秒内从0增长到100。我们可以使用<animate>元素来激活各种CSS或XML属性,具体由<animate>元素的attributeName属性来决定。
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
AnimatedPropertyType m_animatedPropertyType;
AnimatedPropertyType是所有可能类型的枚举。其它两个需要注意的成员变量是:
std::unique_ptr<SVGAnimatedTypeAnimator> m_animator; std::unique_ptr<SVGAnimatedType> m_animatedType;
这里的m_animator就是UAF对象,m_animatedType由(可能被释放的)m_animator对象创建。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
这个bug的一个有趣的地方是,就算animator对象没有被释放bug依然存在。在这种情况下,这个bug变成了一个类型混淆:要触发它,只需在JavaScript回调中修改<animate>元素的m_animatedPropertyType为一个不同的类型(稍后我们详细检视这个过程)。这在我们办公室中引发了该bug是否应该称为UAF的讨论,或者这是完全不同的一类bug,UAF只是它的一个特征。
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
- 我们以type为A的<animate>元素开始。m_animatedPropertyType、m_animator和m_animatedType都和type为A相匹配。
- resetAnimatedType()在被调用并接收一个type为A的animator指针(这里)。
- resetAnimatedType()在调用computeCSSPropertyValue()(这里),这会触发一个JavaScript回调
- 在JavaScript回调中,通过改变attributeName属性修改<animate>元素的类型为B。这导致调用SVGAnimateElementBase::resetAnimatedPropertyType()。在这个函数内,m_animatedType和m_animator被删除,同时m_animatedPropertyType 的attributeName在被设置为B(这里)。现在,m_animatedType和m_animator为空,m_animatedPropertyType是B。
- 然后返回到resetAnimatedType(),此时我们仍然有一个本地变量animator,它仍然指向(已被释放但仍然可用)类型为A的animator。
- m_animatedType基于被释放的animator创建(这里)。此时,m_animatedType是类型A,m_animatedPropertyType是B,m_animator是空。
- resetAnimatedType()函数返回,指向被释放的类型为A的animator本地变量丢失,无法再找回。
- 最后,resetAnimatedType()再次被调用。此时m_animator仍然为空,但是m_animatedPropertyType是B,在这里它将创建类型为B的m_animator。
- 当m_animatedType非空时,无需重新创建它,我们只需在这里通过调用m_animatedType->setValueAsString()初始化它。现在我们有了类型为B的m_animatedPropertyType,类型为B的m_animator和类型为A的m_animatedType。
- 在某个时刻,animated属性的值被计算。这发生在SVGAnimateElementBase::calculateAnimatedValue()中调用m_animator->calculateAnimatedValue(..., m_animatedType)这一行。此时,在m_animator(类型B)和m_animatedType(类型A)之间存在类型不匹配。然而,由于类型不匹配在正常情况下也发生,animator不检查参数(这里可能有些调试用的assert,但在release版本中什么都没有)的类型就尝试将计算后的类型为B的animated值写到类型为A的SVGAnimatedType内。
- 计算完animated的值后,以字符串方式读出它并设置到相应的CSS属性中。问题就发生在这里。
真正的类型混淆只发生在步骤10,在这里我们将实际类型为B的SVGAnimatedType写到类型为A的SVGAnimatedType内。其余和m_animatedType的交互都不危险,只是以字符串方式获取和设置值,这些操作不管实际类型是什么都是安全的。
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
- 因为stroke-miterlimit必须为正数,我们只能再次以32位浮点形式从堆泄露数据。
- SVGLengthValue是由一个32位浮点连着一个表示单位的枚举类型组成。当在步骤11以字符串方式读SVGLengthValue,如果单位值有效,它将被附加在数字后面(如100px)。如果我们尝试设置一个类似的字符串到stroke-miterlimit属性将失败。因此,我们想读取的堆值的下一个字节必须被解释为无效的单位(在这种情况下,当以字符串方式读取SVGLengthValue时,单位不会附加在后面)。
注意这些限制在进行非对齐读操作时也有效。
现在我们有了个没多少用的读操作,那么我们读出了些什么?做这些的全部目的是对抗ASLR,我们需要读到一个可执行模块的指针。在通常利用中,可以通过读堆中某对象的vtable指针达到目的。然而,在MacOS中,vtable指针指向一个不包含相应模块可执行代码的独立的内存区域。所以不能读出vtable指针而需要读出一个函数指针。
我们最后选择在堆喷射中使用VTTRegion对象。VTTRegion对象包含一个定时器,它包含一个指向函数对象的指针,这个对象(在这种情况下)包含一个指向VTTRegion::scrollTimerFired()的函数指针。因此,我们可以使用VTTRegion对象喷射内存(这在Mac Mini中花大约10秒)然后搜索堆喷后的内存得到函数指针。
这给了我们绕过ASLR的能力,但下一阶段需要的是payload地址(ROP链和shellcode)。我们通过下面的步骤获得它:
现在我们有了个没多少用的读操作,那么我们读出了些什么?做这些的全部目的是对抗ASLR,我们需要读到一个可执行模块的指针。在通常利用中,可以通过读堆中某对象的vtable指针达到目的。然而,在MacOS中,vtable指针指向一个不包含相应模块可执行代码的独立的内存区域。所以不能读出vtable指针而需要读出一个函数指针。
我们最后选择在堆喷射中使用VTTRegion对象。VTTRegion对象包含一个定时器,它包含一个指向函数对象的指针,这个对象(在这种情况下)包含一个指向VTTRegion::scrollTimerFired()的函数指针。因此,我们可以使用VTTRegion对象喷射内存(这在Mac Mini中花大约10秒)然后搜索堆喷后的内存得到函数指针。
这给了我们绕过ASLR的能力,但下一阶段需要的是payload地址(ROP链和shellcode)。我们通过下面的步骤获得它:
- 在堆喷射中找到一个VTTRegion对象
- 通过在堆喷射过程中设置VTTRegion.height属性为一个递增的索引,我们可以识别出读到的是第几个VTTRegion。
- 设置VTTRegion对象的VTTRegion.id属性为payload。
- 读出VTTRegion.id指针。
export CC=/usr/bin/clang export CXX=/usr/bin/clang++ export CFLAGS="-fsanitize=address" export CXXFLAGS="-fsanitize=address" export LDFLAGS="-fsanitize=address" export ASAN_OPTIONS="detect_leaks=0" mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=. -DCMAKE_SKIP_RPATH=ON -DPORT=GTK -DLIB_INSTALL_DIR=./lib -DUSE_LIBHYPHEN=OFF -DENABLE_MINIBROWSER=ON -DUSE_SYSTEM_MALLOC=ON -DENABLE_GEOLOCATION=OFF -DENABLE_GTKDOC=OFF -DENABLE_INTROSPECTION=OFF -DENABLE_OPENGL=OFF -DENABLE_ACCELERATED_2D_CANVAS=OFF -DENABLE_CREDENTIAL_STORAGE=OFF -DENABLE_GAMEPAD_DEPRECATED=OFF -DENABLE_MEDIA_STREAM=OFF -DENABLE_WEB_RTC=OFF -DENABLE_PLUGIN_PROCESS_GTK2=OFF -DENABLE_SPELLCHECK=OFF -DENABLE_VIDEO=OFF -DENABLE_WEB_AUDIO=OFF -DUSE_LIBNOTIFY=OFF -DENABLE_SUBTLE_CRYPTO=OFF -DUSE_WOFF2=OFF -Wno-dev .. make -j 4 mkdir -p libexec/webkit2gtk-4.0 cp bin/WebKit*Process libexec/webkit2gtk-4.0/
如果你是第一次进行构建,cmake/make步骤将提示丢失依赖项,你需要安装它们。许多对DOM fuzzing不是很重要的特性都要通过-DENABLE标记取消了。这主要为我们省去了安装相关依赖项的麻烦,但在某下情况下需要创建更加“可移植”的构建。构建完成后,进行fuzzing和创建一个Domato样例一样简单,首先以如下方式运行二进制文件:
ASAN_OPTIONS=detect_leaks=0,exitcode=42 ASAN_SYMBOLIZER_PATH=/path/to/llvm-symbolizer LD_LIBRARY_PATH=./lib ./bin/webkitfuzz /path/to/sample <timeout>
等待退出代码42(根据上面的命令行和我们对WebKitGTK+代码的修改,这表明发生了ASan崩溃)。收集崩溃之后,在真实的Mac机器上对最新WebKit源代码创建了ASan构建。简单运行如下即可:
./Tools/Scripts/set-webkit-configuration --release --asan ./Tools/Scripts/build-webkit
从WebKitGTK+获得的每个崩溃在报告给Apple之前都在Mac构建上进行了测试。
结果
迭代1000.000.000次fuzz后(与一年前一样),我最终给Apple报告了9个bugs。去年,我估计购买执行这些迭代的计算能力需要1000美元,今年也差不多,这对很多潜在的攻击者来说都支付得起。
找到的Bug整理在下表中。所有bug在文章发布之日前都已经被修复了。
找到的Bug整理在下表中。所有bug在文章发布之日前都已经被修复了。
Project Zero bug ID | CVE | Type | Affected Safari 11.1.2 |
Older than 6 months |
Older than 1 year |
1593 | CVE-2018-4197 | UAF | YES | YES | NO |
1594 | CVE-2018-4318 | UAF | NO | NO | NO |
1595 | CVE-2018-4317 | UAF | NO | YES | NO |
1596 | CVE-2018-4314 | UAF | YES | YES | NO |
1602 | CVE-2018-4306 | UAF | YES | YES | NO |
1603 | CVE-2018-4312 | UAF |
NO
|
NO | NO |
1604 | CVE-2018-4315 | UAF | YES | YES | NO |
1609 | CVE-2018-4323 | UAF | YES | YES | NO |
1610 | CVE-2018-4328 | OOB read | YES | YES | YES |
UAF = use-after-free. OOB = out-of-bounds
如表中所示,9个发现的bug中有6个影响Apple Safari的发布版本,这直接影响到Safari的用户。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
发现这些结果后,我想看看这些bug在WebKit代码中已经存在多久了。为了检测这个,所有bug在6个多月前(WebKitGTK+ 2.19.6)和一年前(WebKitGTK+ 2.16.6)的WebKitGTK+版本上进行了测试。
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
- 安全漏洞持续被引入WebKit的基础代码中;
- 这些bug中的许多在被网络安全专家捕获前就已经被引入到产品中了。
尽管对于任何像DOM引擎这样迭代迅速的软件来说,a)都比较常见,然而b)似乎表明:在发布前,需要投入更多计算资源到fuzzing和/或审计中。
利用
为了证明这些bug确实会导致浏览器被控制,我决定为其中一个写一个exploit。这里的目的不是写一个稳定的、复杂的exploit,高级攻击者都不喜欢选择公开工具发现的bug,这些bug的生命周期相对短。然而,如果一个有写exploit能力的人使用这些bug,比如进行恶意软件传播,就算使用一个不稳定的exploit也将造成很大损害。
在影响Safari 发布版本的6个bug以外,我选择我认为最容易利用的——一个UAF,不像发现的其它UAF,这个漏洞的释放对象不在隔离堆中——一个最近引入WebKit的防护机制,该机制使得UAF利用更困难。
在影响Safari 发布版本的6个bug以外,我选择我认为最容易利用的——一个UAF,不像发现的其它UAF,这个漏洞的释放对象不在隔离堆中——一个最近引入WebKit的防护机制,该机制使得UAF利用更困难。
我们先测试下我们要利用的bug。这个问题是SVGAnimateElementBase::resetAnimatedType()函数内的一个UAF。浏览这个函数的代码,你可以看到,首先,函数在下面这行获得了一个指向SVGAnimatedTypeAnimator对象的原始指针:
SVGAnimatedTypeAnimator* animator = ensureAnimator();
然后,到函数结束,animator对象被用于获得(除非已经存在一个)一个指向SVGAnimatedType对象的指针:
m_animatedType = animator->constructFromString(baseValue);
问题在于,在这两行代码运行之间,攻击者控制的JavaScript代码能够获得运行。特别地,这可以发生在调用computeCSSPropertyValue()期间。JavaScript代码可以导致SVGAnimateElementBase::resetAnimatedPropertyType()被调用,该函数将删除animator对象。因此,在释放的animator对象上会调用constructFromString()函数——一个典型的UAF场景,至少第一眼看上去如此。在这个bug中还有一些其他东西,我们之后讨论。
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
<body onload="setTimeout(go, 100)"> <svg id="svg"> <animate id="animate" attributeName="fill" /> </svg> <div id="inputParent" onfocusin="handler()"> <input id="input"> </div> <script> function handler() { animate.setAttribute('attributeName','fill'); } function go() { input.autofocus = true; inputParent.after(inputParent); svg.setCurrentTime(1); } </script> </body>
在这里,svg.setCurrentTime()导致调用resetAnimatedType(),反过来,基于之前的DOM变化,引起JavaScript事件处理例程被调用。在事件处理例程中,通过重置animator对象的attributeName属性(导致调用SVGAnimateElementBase::resetAnimatedPropertyType())将对象删除。
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
- 在对象结构上下文以外调用虚拟方法是个危险的行为。例如,如果你可以调用某个对象的析构函数,它的成员会在对象还存活时就被释放掉。这时,我们就可以将一个UAF问题转换成另一个UAF问题,也许这另一个更好利用。
- 当constructFromString()有一个类型为string的参数,如果另一虚方法(用于替换的对象的虚方法)需要一个其它类型的参数,我们可以在输入参数上引起一个类型混淆。如果其它虚方法相比constructFromString()需要更多的参数,这些参数将是未初始化状态,这也可以产生利用条件。
- 如果constructFromString()返回的是SVGAnimatedType类型的指针,用于替换的虚方法返回的是其它类型,这可以在返回值上产生类型混淆。而且,如果该虚方法不返回任何值,则返回值是未初始化状态。
- 如果被释放对象的vtables和用于替换的对象大小不同,调用被释放对象的vtable指针将导致越界读到其它对象的vtable,导致调用到第三个类的虚函数。
在本次利用中,我们选择第三种,但要做些变化。要理解变化是什么,让我们更详细地检视下SVGAnimateElementBase类:它实现了SVG元素<animate>的大多数功能。SVG<animate>元素用于赋予另一个元素某个属性。例如,使一个SVG图像有如下元素:
<animate attributeName="x" from="0" to="100" dur="10s" />
将导致目标元素的X坐标(默认是其父元素)在10秒内从0增长到100。我们可以使用<animate>元素来激活各种CSS或XML属性,具体由<animate>元素的attributeName属性来决定。
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
AnimatedPropertyType m_animatedPropertyType;
AnimatedPropertyType是所有可能类型的枚举。其它两个需要注意的成员变量是:
std::unique_ptr<SVGAnimatedTypeAnimator> m_animator; std::unique_ptr<SVGAnimatedType> m_animatedType;
这里的m_animator就是UAF对象,m_animatedType由(可能被释放的)m_animator对象创建。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
这个bug的一个有趣的地方是,就算animator对象没有被释放bug依然存在。在这种情况下,这个bug变成了一个类型混淆:要触发它,只需在JavaScript回调中修改<animate>元素的m_animatedPropertyType为一个不同的类型(稍后我们详细检视这个过程)。这在我们办公室中引发了该bug是否应该称为UAF的讨论,或者这是完全不同的一类bug,UAF只是它的一个特征。
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
- 我们以type为A的<animate>元素开始。m_animatedPropertyType、m_animator和m_animatedType都和type为A相匹配。
- resetAnimatedType()在被调用并接收一个type为A的animator指针(这里)。
- resetAnimatedType()在调用computeCSSPropertyValue()(这里),这会触发一个JavaScript回调
- 在JavaScript回调中,通过改变attributeName属性修改<animate>元素的类型为B。这导致调用SVGAnimateElementBase::resetAnimatedPropertyType()。在这个函数内,m_animatedType和m_animator被删除,同时m_animatedPropertyType 的attributeName在被设置为B(这里)。现在,m_animatedType和m_animator为空,m_animatedPropertyType是B。
- 然后返回到resetAnimatedType(),此时我们仍然有一个本地变量animator,它仍然指向(已被释放但仍然可用)类型为A的animator。
- m_animatedType基于被释放的animator创建(这里)。此时,m_animatedType是类型A,m_animatedPropertyType是B,m_animator是空。
- resetAnimatedType()函数返回,指向被释放的类型为A的animator本地变量丢失,无法再找回。
- 最后,resetAnimatedType()再次被调用。此时m_animator仍然为空,但是m_animatedPropertyType是B,在这里它将创建类型为B的m_animator。
- 当m_animatedType非空时,无需重新创建它,我们只需在这里通过调用m_animatedType->setValueAsString()初始化它。现在我们有了类型为B的m_animatedPropertyType,类型为B的m_animator和类型为A的m_animatedType。
- 在某个时刻,animated属性的值被计算。这发生在SVGAnimateElementBase::calculateAnimatedValue()中调用m_animator->calculateAnimatedValue(..., m_animatedType)这一行。此时,在m_animator(类型B)和m_animatedType(类型A)之间存在类型不匹配。然而,由于类型不匹配在正常情况下也发生,animator不检查参数(这里可能有些调试用的assert,但在release版本中什么都没有)的类型就尝试将计算后的类型为B的animated值写到类型为A的SVGAnimatedType内。
- 计算完animated的值后,以字符串方式读出它并设置到相应的CSS属性中。问题就发生在这里。
真正的类型混淆只发生在步骤10,在这里我们将实际类型为B的SVGAnimatedType写到类型为A的SVGAnimatedType内。其余和m_animatedType的交互都不危险,只是以字符串方式获取和设置值,这些操作不管实际类型是什么都是安全的。
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
- 因为stroke-miterlimit必须为正数,我们只能再次以32位浮点形式从堆泄露数据。
- SVGLengthValue是由一个32位浮点连着一个表示单位的枚举类型组成。当在步骤11以字符串方式读SVGLengthValue,如果单位值有效,它将被附加在数字后面(如100px)。如果我们尝试设置一个类似的字符串到stroke-miterlimit属性将失败。因此,我们想读取的堆值的下一个字节必须被解释为无效的单位(在这种情况下,当以字符串方式读取SVGLengthValue时,单位不会附加在后面)。
注意这些限制在进行非对齐读操作时也有效。
现在我们有了个没多少用的读操作,那么我们读出了些什么?做这些的全部目的是对抗ASLR,我们需要读到一个可执行模块的指针。在通常利用中,可以通过读堆中某对象的vtable指针达到目的。然而,在MacOS中,vtable指针指向一个不包含相应模块可执行代码的独立的内存区域。所以不能读出vtable指针而需要读出一个函数指针。
我们最后选择在堆喷射中使用VTTRegion对象。VTTRegion对象包含一个定时器,它包含一个指向函数对象的指针,这个对象(在这种情况下)包含一个指向VTTRegion::scrollTimerFired()的函数指针。因此,我们可以使用VTTRegion对象喷射内存(这在Mac Mini中花大约10秒)然后搜索堆喷后的内存得到函数指针。
这给了我们绕过ASLR的能力,但下一阶段需要的是payload地址(ROP链和shellcode)。我们通过下面的步骤获得它:
现在我们有了个没多少用的读操作,那么我们读出了些什么?做这些的全部目的是对抗ASLR,我们需要读到一个可执行模块的指针。在通常利用中,可以通过读堆中某对象的vtable指针达到目的。然而,在MacOS中,vtable指针指向一个不包含相应模块可执行代码的独立的内存区域。所以不能读出vtable指针而需要读出一个函数指针。
我们最后选择在堆喷射中使用VTTRegion对象。VTTRegion对象包含一个定时器,它包含一个指向函数对象的指针,这个对象(在这种情况下)包含一个指向VTTRegion::scrollTimerFired()的函数指针。因此,我们可以使用VTTRegion对象喷射内存(这在Mac Mini中花大约10秒)然后搜索堆喷后的内存得到函数指针。
这给了我们绕过ASLR的能力,但下一阶段需要的是payload地址(ROP链和shellcode)。我们通过下面的步骤获得它:
- 在堆喷射中找到一个VTTRegion对象
- 通过在堆喷射过程中设置VTTRegion.height属性为一个递增的索引,我们可以识别出读到的是第几个VTTRegion。
- 设置VTTRegion对象的VTTRegion.id属性为payload。
- 读出VTTRegion.id指针。
ASAN_OPTIONS=detect_leaks=0,exitcode=42 ASAN_SYMBOLIZER_PATH=/path/to/llvm-symbolizer LD_LIBRARY_PATH=./lib ./bin/webkitfuzz /path/to/sample <timeout>
等待退出代码42(根据上面的命令行和我们对WebKitGTK+代码的修改,这表明发生了ASan崩溃)。收集崩溃之后,在真实的Mac机器上对最新WebKit源代码创建了ASan构建。简单运行如下即可:
./Tools/Scripts/set-webkit-configuration --release --asan ./Tools/Scripts/build-webkit
从WebKitGTK+获得的每个崩溃在报告给Apple之前都在Mac构建上进行了测试。
结果
迭代1000.000.000次fuzz后(与一年前一样),我最终给Apple报告了9个bugs。去年,我估计购买执行这些迭代的计算能力需要1000美元,今年也差不多,这对很多潜在的攻击者来说都支付得起。
找到的Bug整理在下表中。所有bug在文章发布之日前都已经被修复了。
找到的Bug整理在下表中。所有bug在文章发布之日前都已经被修复了。
Project Zero bug ID | CVE | Type | Affected Safari 11.1.2 |
Older than 6 months |
Older than 1 year |
1593 | CVE-2018-4197 | UAF | YES | YES | NO |
1594 | CVE-2018-4318 | UAF | NO | NO | NO |
1595 | CVE-2018-4317 | UAF | NO | YES | NO |
1596 | CVE-2018-4314 | UAF | YES | YES | NO |
1602 | CVE-2018-4306 | UAF | YES | YES | NO |
1603 | CVE-2018-4312 | UAF |
NO
|
NO | NO |
1604 | CVE-2018-4315 | UAF | YES | YES | NO |
1609 | CVE-2018-4323 | UAF | YES | YES | NO |
1610 | CVE-2018-4328 | OOB read | YES | YES | YES |
UAF = use-after-free. OOB = out-of-bounds
如表中所示,9个发现的bug中有6个影响Apple Safari的发布版本,这直接影响到Safari的用户。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
发现这些结果后,我想看看这些bug在WebKit代码中已经存在多久了。为了检测这个,所有bug在6个多月前(WebKitGTK+ 2.19.6)和一年前(WebKitGTK+ 2.16.6)的WebKitGTK+版本上进行了测试。
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
- 安全漏洞持续被引入WebKit的基础代码中;
- 这些bug中的许多在被网络安全专家捕获前就已经被引入到产品中了。
./Tools/Scripts/set-webkit-configuration --release --asan ./Tools/Scripts/build-webkit
从WebKitGTK+获得的每个崩溃在报告给Apple之前都在Mac构建上进行了测试。
结果
迭代1000.000.000次fuzz后(与一年前一样),我最终给Apple报告了9个bugs。去年,我估计购买执行这些迭代的计算能力需要1000美元,今年也差不多,这对很多潜在的攻击者来说都支付得起。
找到的Bug整理在下表中。所有bug在文章发布之日前都已经被修复了。
找到的Bug整理在下表中。所有bug在文章发布之日前都已经被修复了。
Project Zero bug ID | CVE | Type | Affected Safari 11.1.2 |
Older than 6 months |
Older than 1 year |
1593 | CVE-2018-4197 | UAF | YES | YES | NO |
1594 | CVE-2018-4318 | UAF | NO | NO | NO |
1595 | CVE-2018-4317 | UAF | NO | YES | NO |
1596 | CVE-2018-4314 | UAF | YES | YES | NO |
1602 | CVE-2018-4306 | UAF | YES | YES | NO |
1603 | CVE-2018-4312 | UAF |
NO
|
NO | NO |
1604 | CVE-2018-4315 | UAF | YES | YES | NO |
1609 | CVE-2018-4323 | UAF | YES | YES | NO |
1610 | CVE-2018-4328 | OOB read | YES | YES | YES |
UAF = use-after-free. OOB = out-of-bounds
如表中所示,9个发现的bug中有6个影响Apple Safari的发布版本,这直接影响到Safari的用户。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
发现这些结果后,我想看看这些bug在WebKit代码中已经存在多久了。为了检测这个,所有bug在6个多月前(WebKitGTK+ 2.19.6)和一年前(WebKitGTK+ 2.16.6)的WebKitGTK+版本上进行了测试。
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
- 安全漏洞持续被引入WebKit的基础代码中;
- 这些bug中的许多在被网络安全专家捕获前就已经被引入到产品中了。
UAF = use-after-free. OOB = out-of-bounds
如表中所示,9个发现的bug中有6个影响Apple Safari的发布版本,这直接影响到Safari的用户。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
虽然9或6(看你怎么算)比一年前的17少很多,但这也是数量惊人的,如果统计fuzzer一年以来所有公开的bug,结果将更多。
发现这些结果后,我想看看这些bug在WebKit代码中已经存在多久了。为了检测这个,所有bug在6个多月前(WebKitGTK+ 2.19.6)和一年前(WebKitGTK+ 2.16.6)的WebKitGTK+版本上进行了测试。
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
结果很有趣,大多数bug在WebKit代码中已经存在超过6个月,其中有一个存在超过一年。在这里我强调一下,在过去一年(在上一篇blog和这一篇之间)中,我使用相同的fuzzing方法已经报告了14个bugs。不幸的是,无法知道那14个bug有多少还存活,其中有多少在这次fuzzing中也被发现了。有可能新发现的bug就是之前发现的,但不要使用提供的针对老版本的POC来尝试触发,因为DOM中存在一些与此功能无关的代码修改,我没有研究这方面的概率。
然而,就算我们假设之前报告的所有bug到现在为止都还存活,这仍然表明
- 安全漏洞持续被引入WebKit的基础代码中;
- 这些bug中的许多在被网络安全专家捕获前就已经被引入到产品中了。
尽管对于任何像DOM引擎这样迭代迅速的软件来说,a)都比较常见,然而b)似乎表明:在发布前,需要投入更多计算资源到fuzzing和/或审计中。
利用
为了证明这些bug确实会导致浏览器被控制,我决定为其中一个写一个exploit。这里的目的不是写一个稳定的、复杂的exploit,高级攻击者都不喜欢选择公开工具发现的bug,这些bug的生命周期相对短。然而,如果一个有写exploit能力的人使用这些bug,比如进行恶意软件传播,就算使用一个不稳定的exploit也将造成很大损害。
在影响Safari 发布版本的6个bug以外,我选择我认为最容易利用的——一个UAF,不像发现的其它UAF,这个漏洞的释放对象不在隔离堆中——一个最近引入WebKit的防护机制,该机制使得UAF利用更困难。
在影响Safari 发布版本的6个bug以外,我选择我认为最容易利用的——一个UAF,不像发现的其它UAF,这个漏洞的释放对象不在隔离堆中——一个最近引入WebKit的防护机制,该机制使得UAF利用更困难。
我们先测试下我们要利用的bug。这个问题是SVGAnimateElementBase::resetAnimatedType()函数内的一个UAF。浏览这个函数的代码,你可以看到,首先,函数在下面这行获得了一个指向SVGAnimatedTypeAnimator对象的原始指针:
SVGAnimatedTypeAnimator* animator = ensureAnimator();
然后,到函数结束,animator对象被用于获得(除非已经存在一个)一个指向SVGAnimatedType对象的指针:
m_animatedType = animator->constructFromString(baseValue);
问题在于,在这两行代码运行之间,攻击者控制的JavaScript代码能够获得运行。特别地,这可以发生在调用computeCSSPropertyValue()期间。JavaScript代码可以导致SVGAnimateElementBase::resetAnimatedPropertyType()被调用,该函数将删除animator对象。因此,在释放的animator对象上会调用constructFromString()函数——一个典型的UAF场景,至少第一眼看上去如此。在这个bug中还有一些其他东西,我们之后讨论。
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
<body onload="setTimeout(go, 100)"> <svg id="svg"> <animate id="animate" attributeName="fill" /> </svg> <div id="inputParent" onfocusin="handler()"> <input id="input"> </div> <script> function handler() { animate.setAttribute('attributeName','fill'); } function go() { input.autofocus = true; inputParent.after(inputParent); svg.setCurrentTime(1); } </script> </body>
在这里,svg.setCurrentTime()导致调用resetAnimatedType(),反过来,基于之前的DOM变化,引起JavaScript事件处理例程被调用。在事件处理例程中,通过重置animator对象的attributeName属性(导致调用SVGAnimateElementBase::resetAnimatedPropertyType())将对象删除。
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
- 在对象结构上下文以外调用虚拟方法是个危险的行为。例如,如果你可以调用某个对象的析构函数,它的成员会在对象还存活时就被释放掉。这时,我们就可以将一个UAF问题转换成另一个UAF问题,也许这另一个更好利用。
- 当constructFromString()有一个类型为string的参数,如果另一虚方法(用于替换的对象的虚方法)需要一个其它类型的参数,我们可以在输入参数上引起一个类型混淆。如果其它虚方法相比constructFromString()需要更多的参数,这些参数将是未初始化状态,这也可以产生利用条件。
- 如果constructFromString()返回的是SVGAnimatedType类型的指针,用于替换的虚方法返回的是其它类型,这可以在返回值上产生类型混淆。而且,如果该虚方法不返回任何值,则返回值是未初始化状态。
- 如果被释放对象的vtables和用于替换的对象大小不同,调用被释放对象的vtable指针将导致越界读到其它对象的vtable,导致调用到第三个类的虚函数。
在本次利用中,我们选择第三种,但要做些变化。要理解变化是什么,让我们更详细地检视下SVGAnimateElementBase类:它实现了SVG元素<animate>的大多数功能。SVG<animate>元素用于赋予另一个元素某个属性。例如,使一个SVG图像有如下元素:
<animate attributeName="x" from="0" to="100" dur="10s" />
将导致目标元素的X坐标(默认是其父元素)在10秒内从0增长到100。我们可以使用<animate>元素来激活各种CSS或XML属性,具体由<animate>元素的attributeName属性来决定。
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
AnimatedPropertyType m_animatedPropertyType;
AnimatedPropertyType是所有可能类型的枚举。其它两个需要注意的成员变量是:
std::unique_ptr<SVGAnimatedTypeAnimator> m_animator; std::unique_ptr<SVGAnimatedType> m_animatedType;
这里的m_animator就是UAF对象,m_animatedType由(可能被释放的)m_animator对象创建。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
这个bug的一个有趣的地方是,就算animator对象没有被释放bug依然存在。在这种情况下,这个bug变成了一个类型混淆:要触发它,只需在JavaScript回调中修改<animate>元素的m_animatedPropertyType为一个不同的类型(稍后我们详细检视这个过程)。这在我们办公室中引发了该bug是否应该称为UAF的讨论,或者这是完全不同的一类bug,UAF只是它的一个特征。
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
- 我们以type为A的<animate>元素开始。m_animatedPropertyType、m_animator和m_animatedType都和type为A相匹配。
- resetAnimatedType()在被调用并接收一个type为A的animator指针(这里)。
- resetAnimatedType()在调用computeCSSPropertyValue()(这里),这会触发一个JavaScript回调
- 在JavaScript回调中,通过改变attributeName属性修改<animate>元素的类型为B。这导致调用SVGAnimateElementBase::resetAnimatedPropertyType()。在这个函数内,m_animatedType和m_animator被删除,同时m_animatedPropertyType 的attributeName在被设置为B(这里)。现在,m_animatedType和m_animator为空,m_animatedPropertyType是B。
- 然后返回到resetAnimatedType(),此时我们仍然有一个本地变量animator,它仍然指向(已被释放但仍然可用)类型为A的animator。
- m_animatedType基于被释放的animator创建(这里)。此时,m_animatedType是类型A,m_animatedPropertyType是B,m_animator是空。
- resetAnimatedType()函数返回,指向被释放的类型为A的animator本地变量丢失,无法再找回。
- 最后,resetAnimatedType()再次被调用。此时m_animator仍然为空,但是m_animatedPropertyType是B,在这里它将创建类型为B的m_animator。
- 当m_animatedType非空时,无需重新创建它,我们只需在这里通过调用m_animatedType->setValueAsString()初始化它。现在我们有了类型为B的m_animatedPropertyType,类型为B的m_animator和类型为A的m_animatedType。
- 在某个时刻,animated属性的值被计算。这发生在SVGAnimateElementBase::calculateAnimatedValue()中调用m_animator->calculateAnimatedValue(..., m_animatedType)这一行。此时,在m_animator(类型B)和m_animatedType(类型A)之间存在类型不匹配。然而,由于类型不匹配在正常情况下也发生,animator不检查参数(这里可能有些调试用的assert,但在release版本中什么都没有)的类型就尝试将计算后的类型为B的animated值写到类型为A的SVGAnimatedType内。
- 计算完animated的值后,以字符串方式读出它并设置到相应的CSS属性中。问题就发生在这里。
真正的类型混淆只发生在步骤10,在这里我们将实际类型为B的SVGAnimatedType写到类型为A的SVGAnimatedType内。其余和m_animatedType的交互都不危险,只是以字符串方式获取和设置值,这些操作不管实际类型是什么都是安全的。
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
- 因为stroke-miterlimit必须为正数,我们只能再次以32位浮点形式从堆泄露数据。
- SVGLengthValue是由一个32位浮点连着一个表示单位的枚举类型组成。当在步骤11以字符串方式读SVGLengthValue,如果单位值有效,它将被附加在数字后面(如100px)。如果我们尝试设置一个类似的字符串到stroke-miterlimit属性将失败。因此,我们想读取的堆值的下一个字节必须被解释为无效的单位(在这种情况下,当以字符串方式读取SVGLengthValue时,单位不会附加在后面)。
注意这些限制在进行非对齐读操作时也有效。
现在我们有了个没多少用的读操作,那么我们读出了些什么?做这些的全部目的是对抗ASLR,我们需要读到一个可执行模块的指针。在通常利用中,可以通过读堆中某对象的vtable指针达到目的。然而,在MacOS中,vtable指针指向一个不包含相应模块可执行代码的独立的内存区域。所以不能读出vtable指针而需要读出一个函数指针。
我们最后选择在堆喷射中使用VTTRegion对象。VTTRegion对象包含一个定时器,它包含一个指向函数对象的指针,这个对象(在这种情况下)包含一个指向VTTRegion::scrollTimerFired()的函数指针。因此,我们可以使用VTTRegion对象喷射内存(这在Mac Mini中花大约10秒)然后搜索堆喷后的内存得到函数指针。
这给了我们绕过ASLR的能力,但下一阶段需要的是payload地址(ROP链和shellcode)。我们通过下面的步骤获得它:
现在我们有了个没多少用的读操作,那么我们读出了些什么?做这些的全部目的是对抗ASLR,我们需要读到一个可执行模块的指针。在通常利用中,可以通过读堆中某对象的vtable指针达到目的。然而,在MacOS中,vtable指针指向一个不包含相应模块可执行代码的独立的内存区域。所以不能读出vtable指针而需要读出一个函数指针。
我们最后选择在堆喷射中使用VTTRegion对象。VTTRegion对象包含一个定时器,它包含一个指向函数对象的指针,这个对象(在这种情况下)包含一个指向VTTRegion::scrollTimerFired()的函数指针。因此,我们可以使用VTTRegion对象喷射内存(这在Mac Mini中花大约10秒)然后搜索堆喷后的内存得到函数指针。
这给了我们绕过ASLR的能力,但下一阶段需要的是payload地址(ROP链和shellcode)。我们通过下面的步骤获得它:
- 在堆喷射中找到一个VTTRegion对象
- 通过在堆喷射过程中设置VTTRegion.height属性为一个递增的索引,我们可以识别出读到的是第几个VTTRegion。
- 设置VTTRegion对象的VTTRegion.id属性为payload。
- 读出VTTRegion.id指针。
SVGAnimatedTypeAnimator* animator = ensureAnimator();
然后,到函数结束,animator对象被用于获得(除非已经存在一个)一个指向SVGAnimatedType对象的指针:
m_animatedType = animator->constructFromString(baseValue);
问题在于,在这两行代码运行之间,攻击者控制的JavaScript代码能够获得运行。特别地,这可以发生在调用computeCSSPropertyValue()期间。JavaScript代码可以导致SVGAnimateElementBase::resetAnimatedPropertyType()被调用,该函数将删除animator对象。因此,在释放的animator对象上会调用constructFromString()函数——一个典型的UAF场景,至少第一眼看上去如此。在这个bug中还有一些其他东西,我们之后讨论。
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
<body onload="setTimeout(go, 100)"> <svg id="svg"> <animate id="animate" attributeName="fill" /> </svg> <div id="inputParent" onfocusin="handler()"> <input id="input"> </div> <script> function handler() { animate.setAttribute('attributeName','fill'); } function go() { input.autofocus = true; inputParent.after(inputParent); svg.setCurrentTime(1); } </script> </body>
在这里,svg.setCurrentTime()导致调用resetAnimatedType(),反过来,基于之前的DOM变化,引起JavaScript事件处理例程被调用。在事件处理例程中,通过重置animator对象的attributeName属性(导致调用SVGAnimateElementBase::resetAnimatedPropertyType())将对象删除。
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
- 在对象结构上下文以外调用虚拟方法是个危险的行为。例如,如果你可以调用某个对象的析构函数,它的成员会在对象还存活时就被释放掉。这时,我们就可以将一个UAF问题转换成另一个UAF问题,也许这另一个更好利用。
- 当constructFromString()有一个类型为string的参数,如果另一虚方法(用于替换的对象的虚方法)需要一个其它类型的参数,我们可以在输入参数上引起一个类型混淆。如果其它虚方法相比constructFromString()需要更多的参数,这些参数将是未初始化状态,这也可以产生利用条件。
- 如果constructFromString()返回的是SVGAnimatedType类型的指针,用于替换的虚方法返回的是其它类型,这可以在返回值上产生类型混淆。而且,如果该虚方法不返回任何值,则返回值是未初始化状态。
- 如果被释放对象的vtables和用于替换的对象大小不同,调用被释放对象的vtable指针将导致越界读到其它对象的vtable,导致调用到第三个类的虚函数。
在本次利用中,我们选择第三种,但要做些变化。要理解变化是什么,让我们更详细地检视下SVGAnimateElementBase类:它实现了SVG元素<animate>的大多数功能。SVG<animate>元素用于赋予另一个元素某个属性。例如,使一个SVG图像有如下元素:
<animate attributeName="x" from="0" to="100" dur="10s" />
将导致目标元素的X坐标(默认是其父元素)在10秒内从0增长到100。我们可以使用<animate>元素来激活各种CSS或XML属性,具体由<animate>元素的attributeName属性来决定。
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
AnimatedPropertyType m_animatedPropertyType;
AnimatedPropertyType是所有可能类型的枚举。其它两个需要注意的成员变量是:
std::unique_ptr<SVGAnimatedTypeAnimator> m_animator; std::unique_ptr<SVGAnimatedType> m_animatedType;
这里的m_animator就是UAF对象,m_animatedType由(可能被释放的)m_animator对象创建。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
这个bug的一个有趣的地方是,就算animator对象没有被释放bug依然存在。在这种情况下,这个bug变成了一个类型混淆:要触发它,只需在JavaScript回调中修改<animate>元素的m_animatedPropertyType为一个不同的类型(稍后我们详细检视这个过程)。这在我们办公室中引发了该bug是否应该称为UAF的讨论,或者这是完全不同的一类bug,UAF只是它的一个特征。
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
- 我们以type为A的<animate>元素开始。m_animatedPropertyType、m_animator和m_animatedType都和type为A相匹配。
- resetAnimatedType()在被调用并接收一个type为A的animator指针(这里)。
- resetAnimatedType()在调用computeCSSPropertyValue()(这里),这会触发一个JavaScript回调
- 在JavaScript回调中,通过改变attributeName属性修改<animate>元素的类型为B。这导致调用SVGAnimateElementBase::resetAnimatedPropertyType()。在这个函数内,m_animatedType和m_animator被删除,同时m_animatedPropertyType 的attributeName在被设置为B(这里)。现在,m_animatedType和m_animator为空,m_animatedPropertyType是B。
- 然后返回到resetAnimatedType(),此时我们仍然有一个本地变量animator,它仍然指向(已被释放但仍然可用)类型为A的animator。
- m_animatedType基于被释放的animator创建(这里)。此时,m_animatedType是类型A,m_animatedPropertyType是B,m_animator是空。
- resetAnimatedType()函数返回,指向被释放的类型为A的animator本地变量丢失,无法再找回。
- 最后,resetAnimatedType()再次被调用。此时m_animator仍然为空,但是m_animatedPropertyType是B,在这里它将创建类型为B的m_animator。
- 当m_animatedType非空时,无需重新创建它,我们只需在这里通过调用m_animatedType->setValueAsString()初始化它。现在我们有了类型为B的m_animatedPropertyType,类型为B的m_animator和类型为A的m_animatedType。
- 在某个时刻,animated属性的值被计算。这发生在SVGAnimateElementBase::calculateAnimatedValue()中调用m_animator->calculateAnimatedValue(..., m_animatedType)这一行。此时,在m_animator(类型B)和m_animatedType(类型A)之间存在类型不匹配。然而,由于类型不匹配在正常情况下也发生,animator不检查参数(这里可能有些调试用的assert,但在release版本中什么都没有)的类型就尝试将计算后的类型为B的animated值写到类型为A的SVGAnimatedType内。
- 计算完animated的值后,以字符串方式读出它并设置到相应的CSS属性中。问题就发生在这里。
真正的类型混淆只发生在步骤10,在这里我们将实际类型为B的SVGAnimatedType写到类型为A的SVGAnimatedType内。其余和m_animatedType的交互都不危险,只是以字符串方式获取和设置值,这些操作不管实际类型是什么都是安全的。
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
- 因为stroke-miterlimit必须为正数,我们只能再次以32位浮点形式从堆泄露数据。
- SVGLengthValue是由一个32位浮点连着一个表示单位的枚举类型组成。当在步骤11以字符串方式读SVGLengthValue,如果单位值有效,它将被附加在数字后面(如100px)。如果我们尝试设置一个类似的字符串到stroke-miterlimit属性将失败。因此,我们想读取的堆值的下一个字节必须被解释为无效的单位(在这种情况下,当以字符串方式读取SVGLengthValue时,单位不会附加在后面)。
m_animatedType = animator->constructFromString(baseValue);
问题在于,在这两行代码运行之间,攻击者控制的JavaScript代码能够获得运行。特别地,这可以发生在调用computeCSSPropertyValue()期间。JavaScript代码可以导致SVGAnimateElementBase::resetAnimatedPropertyType()被调用,该函数将删除animator对象。因此,在释放的animator对象上会调用constructFromString()函数——一个典型的UAF场景,至少第一眼看上去如此。在这个bug中还有一些其他东西,我们之后讨论。
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
在最新的Safari中已经通过在computeCSSPropertyValue()函数中不触发JavaScript回调修复了这个bug。取代回调函数,在之后会调用该函数的事件处理函数。补丁可以在这里看到。
一个简单的POC如下:
<body onload="setTimeout(go, 100)"> <svg id="svg"> <animate id="animate" attributeName="fill" /> </svg> <div id="inputParent" onfocusin="handler()"> <input id="input"> </div> <script> function handler() { animate.setAttribute('attributeName','fill'); } function go() { input.autofocus = true; inputParent.after(inputParent); svg.setCurrentTime(1); } </script> </body>
在这里,svg.setCurrentTime()导致调用resetAnimatedType(),反过来,基于之前的DOM变化,引起JavaScript事件处理例程被调用。在事件处理例程中,通过重置animator对象的attributeName属性(导致调用SVGAnimateElementBase::resetAnimatedPropertyType())将对象删除。
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
- 在对象结构上下文以外调用虚拟方法是个危险的行为。例如,如果你可以调用某个对象的析构函数,它的成员会在对象还存活时就被释放掉。这时,我们就可以将一个UAF问题转换成另一个UAF问题,也许这另一个更好利用。
- 当constructFromString()有一个类型为string的参数,如果另一虚方法(用于替换的对象的虚方法)需要一个其它类型的参数,我们可以在输入参数上引起一个类型混淆。如果其它虚方法相比constructFromString()需要更多的参数,这些参数将是未初始化状态,这也可以产生利用条件。
- 如果constructFromString()返回的是SVGAnimatedType类型的指针,用于替换的虚方法返回的是其它类型,这可以在返回值上产生类型混淆。而且,如果该虚方法不返回任何值,则返回值是未初始化状态。
- 如果被释放对象的vtables和用于替换的对象大小不同,调用被释放对象的vtable指针将导致越界读到其它对象的vtable,导致调用到第三个类的虚函数。
<body onload="setTimeout(go, 100)"> <svg id="svg"> <animate id="animate" attributeName="fill" /> </svg> <div id="inputParent" onfocusin="handler()"> <input id="input"> </div> <script> function handler() { animate.setAttribute('attributeName','fill'); } function go() { input.autofocus = true; inputParent.after(inputParent); svg.setCurrentTime(1); } </script> </body>
在这里,svg.setCurrentTime()导致调用resetAnimatedType(),反过来,基于之前的DOM变化,引起JavaScript事件处理例程被调用。在事件处理例程中,通过重置animator对象的attributeName属性(导致调用SVGAnimateElementBase::resetAnimatedPropertyType())将对象删除。
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
既然constructFromString()函数是SVGAnimatedType类的一个虚方法,这个漏洞为我们提供了一个调用一个已释放对象的一个虚方法的原语。
在出现ASLR之前,这样一个漏洞可以通过用我们控制的数据替换被释放的对象来立刻利用,只要伪造好被释放对象的虚函数表,当虚函数被调用时,执行流程就会被重定向到攻击者的ROP链。但由于ASLR的出现,我们无法事先知道进程内任意可执行模块的地址。
传统的绕过方法是结合一个UAF bug和一个可以泄露可执行模块地址的信息泄露bug。但在这里有个问题:我们发现的这些bug中,没有可以用于此目的的信息泄露bug。一个不太有受虐倾向的研究员可以继续fuzz直到找到一个合适的信息泄露bug。但是,除找到合适的bug外,我有意将自己限制在已经发现的这些bug内,结果导致利用这个bug的时间大部分都花在了如何将bug变成一个信息泄露上。
前面提到,我们拥有的原语是调用一个被释放对象的一个虚方法。不绕过ASLR的话,我们除了引起一个崩溃以外唯一能做的就是替换被释放的对象为另一个有虚表的对象,这样,当调用一个虚方法时,调用的是另一个对象的虚方法。大多数时候,这也会调用一个有效对象的虚方法,不会引起有趣的事情。但是,在以下一些场景中进行该操作会产生有趣的结果:
- 在对象结构上下文以外调用虚拟方法是个危险的行为。例如,如果你可以调用某个对象的析构函数,它的成员会在对象还存活时就被释放掉。这时,我们就可以将一个UAF问题转换成另一个UAF问题,也许这另一个更好利用。
- 当constructFromString()有一个类型为string的参数,如果另一虚方法(用于替换的对象的虚方法)需要一个其它类型的参数,我们可以在输入参数上引起一个类型混淆。如果其它虚方法相比constructFromString()需要更多的参数,这些参数将是未初始化状态,这也可以产生利用条件。
- 如果constructFromString()返回的是SVGAnimatedType类型的指针,用于替换的虚方法返回的是其它类型,这可以在返回值上产生类型混淆。而且,如果该虚方法不返回任何值,则返回值是未初始化状态。
- 如果被释放对象的vtables和用于替换的对象大小不同,调用被释放对象的vtable指针将导致越界读到其它对象的vtable,导致调用到第三个类的虚函数。
在本次利用中,我们选择第三种,但要做些变化。要理解变化是什么,让我们更详细地检视下SVGAnimateElementBase类:它实现了SVG元素<animate>的大多数功能。SVG<animate>元素用于赋予另一个元素某个属性。例如,使一个SVG图像有如下元素:
<animate attributeName="x" from="0" to="100" dur="10s" />
将导致目标元素的X坐标(默认是其父元素)在10秒内从0增长到100。我们可以使用<animate>元素来激活各种CSS或XML属性,具体由<animate>元素的attributeName属性来决定。
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
AnimatedPropertyType m_animatedPropertyType;
AnimatedPropertyType是所有可能类型的枚举。其它两个需要注意的成员变量是:
std::unique_ptr<SVGAnimatedTypeAnimator> m_animator; std::unique_ptr<SVGAnimatedType> m_animatedType;
这里的m_animator就是UAF对象,m_animatedType由(可能被释放的)m_animator对象创建。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
这个bug的一个有趣的地方是,就算animator对象没有被释放bug依然存在。在这种情况下,这个bug变成了一个类型混淆:要触发它,只需在JavaScript回调中修改<animate>元素的m_animatedPropertyType为一个不同的类型(稍后我们详细检视这个过程)。这在我们办公室中引发了该bug是否应该称为UAF的讨论,或者这是完全不同的一类bug,UAF只是它的一个特征。
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
- 我们以type为A的<animate>元素开始。m_animatedPropertyType、m_animator和m_animatedType都和type为A相匹配。
- resetAnimatedType()在被调用并接收一个type为A的animator指针(这里)。
- resetAnimatedType()在调用computeCSSPropertyValue()(这里),这会触发一个JavaScript回调
- 在JavaScript回调中,通过改变attributeName属性修改<animate>元素的类型为B。这导致调用SVGAnimateElementBase::resetAnimatedPropertyType()。在这个函数内,m_animatedType和m_animator被删除,同时m_animatedPropertyType 的attributeName在被设置为B(这里)。现在,m_animatedType和m_animator为空,m_animatedPropertyType是B。
- 然后返回到resetAnimatedType(),此时我们仍然有一个本地变量animator,它仍然指向(已被释放但仍然可用)类型为A的animator。
- m_animatedType基于被释放的animator创建(这里)。此时,m_animatedType是类型A,m_animatedPropertyType是B,m_animator是空。
- resetAnimatedType()函数返回,指向被释放的类型为A的animator本地变量丢失,无法再找回。
- 最后,resetAnimatedType()再次被调用。此时m_animator仍然为空,但是m_animatedPropertyType是B,在这里它将创建类型为B的m_animator。
- 当m_animatedType非空时,无需重新创建它,我们只需在这里通过调用m_animatedType->setValueAsString()初始化它。现在我们有了类型为B的m_animatedPropertyType,类型为B的m_animator和类型为A的m_animatedType。
- 在某个时刻,animated属性的值被计算。这发生在SVGAnimateElementBase::calculateAnimatedValue()中调用m_animator->calculateAnimatedValue(..., m_animatedType)这一行。此时,在m_animator(类型B)和m_animatedType(类型A)之间存在类型不匹配。然而,由于类型不匹配在正常情况下也发生,animator不检查参数(这里可能有些调试用的assert,但在release版本中什么都没有)的类型就尝试将计算后的类型为B的animated值写到类型为A的SVGAnimatedType内。
- 计算完animated的值后,以字符串方式读出它并设置到相应的CSS属性中。问题就发生在这里。
真正的类型混淆只发生在步骤10,在这里我们将实际类型为B的SVGAnimatedType写到类型为A的SVGAnimatedType内。其余和m_animatedType的交互都不危险,只是以字符串方式获取和设置值,这些操作不管实际类型是什么都是安全的。
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
- 因为stroke-miterlimit必须为正数,我们只能再次以32位浮点形式从堆泄露数据。
- SVGLengthValue是由一个32位浮点连着一个表示单位的枚举类型组成。当在步骤11以字符串方式读SVGLengthValue,如果单位值有效,它将被附加在数字后面(如100px)。如果我们尝试设置一个类似的字符串到stroke-miterlimit属性将失败。因此,我们想读取的堆值的下一个字节必须被解释为无效的单位(在这种情况下,当以字符串方式读取SVGLengthValue时,单位不会附加在后面)。
<animate attributeName="x" from="0" to="100" dur="10s" />
将导致目标元素的X坐标(默认是其父元素)在10秒内从0增长到100。我们可以使用<animate>元素来激活各种CSS或XML属性,具体由<animate>元素的attributeName属性来决定。
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
有趣的地方是,这些属性可以是不同的类型。例如,我们可以使用<animate>元素来激活一个元素的X坐标,此时属性的类型是SVGLengthValue (number + unit),或者我们可以使用它来激活元素的fill属性,此时属性的类型是Color。
在一个SVGAnimateElementBase类中,animated属性的类型可以通过一个如下声明的成员变量来获得:
AnimatedPropertyType m_animatedPropertyType;
AnimatedPropertyType是所有可能类型的枚举。其它两个需要注意的成员变量是:
std::unique_ptr<SVGAnimatedTypeAnimator> m_animator; std::unique_ptr<SVGAnimatedType> m_animatedType;
这里的m_animator就是UAF对象,m_animatedType由(可能被释放的)m_animator对象创建。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
这个bug的一个有趣的地方是,就算animator对象没有被释放bug依然存在。在这种情况下,这个bug变成了一个类型混淆:要触发它,只需在JavaScript回调中修改<animate>元素的m_animatedPropertyType为一个不同的类型(稍后我们详细检视这个过程)。这在我们办公室中引发了该bug是否应该称为UAF的讨论,或者这是完全不同的一类bug,UAF只是它的一个特征。
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
- 我们以type为A的<animate>元素开始。m_animatedPropertyType、m_animator和m_animatedType都和type为A相匹配。
- resetAnimatedType()在被调用并接收一个type为A的animator指针(这里)。
- resetAnimatedType()在调用computeCSSPropertyValue()(这里),这会触发一个JavaScript回调
- 在JavaScript回调中,通过改变attributeName属性修改<animate>元素的类型为B。这导致调用SVGAnimateElementBase::resetAnimatedPropertyType()。在这个函数内,m_animatedType和m_animator被删除,同时m_animatedPropertyType 的attributeName在被设置为B(这里)。现在,m_animatedType和m_animator为空,m_animatedPropertyType是B。
- 然后返回到resetAnimatedType(),此时我们仍然有一个本地变量animator,它仍然指向(已被释放但仍然可用)类型为A的animator。
- m_animatedType基于被释放的animator创建(这里)。此时,m_animatedType是类型A,m_animatedPropertyType是B,m_animator是空。
- resetAnimatedType()函数返回,指向被释放的类型为A的animator本地变量丢失,无法再找回。
- 最后,resetAnimatedType()再次被调用。此时m_animator仍然为空,但是m_animatedPropertyType是B,在这里它将创建类型为B的m_animator。
- 当m_animatedType非空时,无需重新创建它,我们只需在这里通过调用m_animatedType->setValueAsString()初始化它。现在我们有了类型为B的m_animatedPropertyType,类型为B的m_animator和类型为A的m_animatedType。
- 在某个时刻,animated属性的值被计算。这发生在SVGAnimateElementBase::calculateAnimatedValue()中调用m_animator->calculateAnimatedValue(..., m_animatedType)这一行。此时,在m_animator(类型B)和m_animatedType(类型A)之间存在类型不匹配。然而,由于类型不匹配在正常情况下也发生,animator不检查参数(这里可能有些调试用的assert,但在release版本中什么都没有)的类型就尝试将计算后的类型为B的animated值写到类型为A的SVGAnimatedType内。
- 计算完animated的值后,以字符串方式读出它并设置到相应的CSS属性中。问题就发生在这里。
AnimatedPropertyType m_animatedPropertyType;
AnimatedPropertyType是所有可能类型的枚举。其它两个需要注意的成员变量是:
std::unique_ptr<SVGAnimatedTypeAnimator> m_animator; std::unique_ptr<SVGAnimatedType> m_animatedType;
这里的m_animator就是UAF对象,m_animatedType由(可能被释放的)m_animator对象创建。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
这个bug的一个有趣的地方是,就算animator对象没有被释放bug依然存在。在这种情况下,这个bug变成了一个类型混淆:要触发它,只需在JavaScript回调中修改<animate>元素的m_animatedPropertyType为一个不同的类型(稍后我们详细检视这个过程)。这在我们办公室中引发了该bug是否应该称为UAF的讨论,或者这是完全不同的一类bug,UAF只是它的一个特征。
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
- 我们以type为A的<animate>元素开始。m_animatedPropertyType、m_animator和m_animatedType都和type为A相匹配。
- resetAnimatedType()在被调用并接收一个type为A的animator指针(这里)。
- resetAnimatedType()在调用computeCSSPropertyValue()(这里),这会触发一个JavaScript回调
- 在JavaScript回调中,通过改变attributeName属性修改<animate>元素的类型为B。这导致调用SVGAnimateElementBase::resetAnimatedPropertyType()。在这个函数内,m_animatedType和m_animator被删除,同时m_animatedPropertyType 的attributeName在被设置为B(这里)。现在,m_animatedType和m_animator为空,m_animatedPropertyType是B。
- 然后返回到resetAnimatedType(),此时我们仍然有一个本地变量animator,它仍然指向(已被释放但仍然可用)类型为A的animator。
- m_animatedType基于被释放的animator创建(这里)。此时,m_animatedType是类型A,m_animatedPropertyType是B,m_animator是空。
- resetAnimatedType()函数返回,指向被释放的类型为A的animator本地变量丢失,无法再找回。
- 最后,resetAnimatedType()再次被调用。此时m_animator仍然为空,但是m_animatedPropertyType是B,在这里它将创建类型为B的m_animator。
- 当m_animatedType非空时,无需重新创建它,我们只需在这里通过调用m_animatedType->setValueAsString()初始化它。现在我们有了类型为B的m_animatedPropertyType,类型为B的m_animator和类型为A的m_animatedType。
- 在某个时刻,animated属性的值被计算。这发生在SVGAnimateElementBase::calculateAnimatedValue()中调用m_animator->calculateAnimatedValue(..., m_animatedType)这一行。此时,在m_animator(类型B)和m_animatedType(类型A)之间存在类型不匹配。然而,由于类型不匹配在正常情况下也发生,animator不检查参数(这里可能有些调试用的assert,但在release版本中什么都没有)的类型就尝试将计算后的类型为B的animated值写到类型为A的SVGAnimatedType内。
- 计算完animated的值后,以字符串方式读出它并设置到相应的CSS属性中。问题就发生在这里。
std::unique_ptr<SVGAnimatedTypeAnimator> m_animator; std::unique_ptr<SVGAnimatedType> m_animatedType;
这里的m_animator就是UAF对象,m_animatedType由(可能被释放的)m_animator对象创建。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
SVGAnimatedTypeAnimator(m_animator的类型)是一个包含所有AnimatedPropertyType可能取的子类值的超类,例如SVGAnimatedBooleanAnimator、SVGAnimatedColorAnimator。SVGAnimatedType(m_animatedType的类型)是一个变量,它包含一个类型和一个该类型所有可能取值的联合(具体取决于类型)。
有个重点要注意,通常m_animator的子类和m_animatedType的类型应该和m_animatedPropertyType匹配。例如,如果m_animatedPropertyType是AnimatedBoolean,则m_animatedType变量的类型也应该相同,同时m_animator则应该是SVGAnimatedBooleanAnimator类的实例。
那么为什么不应当让所有这些类型都匹配呢?既然m_animator在这里基于m_animatedPropertyType创建,而m_animatedType在这里由m_animator创建。等一下,这就是漏洞发生的地方!
与其用一个完全不同的对象替换被释放的animator对象,并引起一个SVGAnimatedType类和其它类间的类型混淆,我们可以用另一个animator子类替换被释放的animator,并将type=A的SVGAnimatedType类混淆为type=B的。
这个bug的一个有趣的地方是,就算animator对象没有被释放bug依然存在。在这种情况下,这个bug变成了一个类型混淆:要触发它,只需在JavaScript回调中修改<animate>元素的m_animatedPropertyType为一个不同的类型(稍后我们详细检视这个过程)。这在我们办公室中引发了该bug是否应该称为UAF的讨论,或者这是完全不同的一类bug,UAF只是它的一个特征。
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
注意,animator对象在<animate>元素的类型改变时都会被释放,这给在哪里利用这个bug造成了一个有趣的情况(你选择如何调用它),除了用另一个类型的对象替换被释放的对象,我们可以使用相同类型的对象替换被释放的对象来确保类型没有被替换。基于WebKit的内存分配工作方式,大多数时候发生的情况是——只有在内存页满了时,在一个内存页分配新对象才会替换已存在的老对象。而且,在WebKit释放对象时也不会破坏这一点,这使得我们在释放一个对象后也依旧可以正常使用它。
现在让我们来检视下这个类型混淆如何工作以及它会带来什么影响:
- 我们以type为A的<animate>元素开始。m_animatedPropertyType、m_animator和m_animatedType都和type为A相匹配。
- resetAnimatedType()在被调用并接收一个type为A的animator指针(这里)。
- resetAnimatedType()在调用computeCSSPropertyValue()(这里),这会触发一个JavaScript回调
- 在JavaScript回调中,通过改变attributeName属性修改<animate>元素的类型为B。这导致调用SVGAnimateElementBase::resetAnimatedPropertyType()。在这个函数内,m_animatedType和m_animator被删除,同时m_animatedPropertyType 的attributeName在被设置为B(这里)。现在,m_animatedType和m_animator为空,m_animatedPropertyType是B。
- 然后返回到resetAnimatedType(),此时我们仍然有一个本地变量animator,它仍然指向(已被释放但仍然可用)类型为A的animator。
- m_animatedType基于被释放的animator创建(这里)。此时,m_animatedType是类型A,m_animatedPropertyType是B,m_animator是空。
- resetAnimatedType()函数返回,指向被释放的类型为A的animator本地变量丢失,无法再找回。
- 最后,resetAnimatedType()再次被调用。此时m_animator仍然为空,但是m_animatedPropertyType是B,在这里它将创建类型为B的m_animator。
- 当m_animatedType非空时,无需重新创建它,我们只需在这里通过调用m_animatedType->setValueAsString()初始化它。现在我们有了类型为B的m_animatedPropertyType,类型为B的m_animator和类型为A的m_animatedType。
- 在某个时刻,animated属性的值被计算。这发生在SVGAnimateElementBase::calculateAnimatedValue()中调用m_animator->calculateAnimatedValue(..., m_animatedType)这一行。此时,在m_animator(类型B)和m_animatedType(类型A)之间存在类型不匹配。然而,由于类型不匹配在正常情况下也发生,animator不检查参数(这里可能有些调试用的assert,但在release版本中什么都没有)的类型就尝试将计算后的类型为B的animated值写到类型为A的SVGAnimatedType内。
- 计算完animated的值后,以字符串方式读出它并设置到相应的CSS属性中。问题就发生在这里。
真正的类型混淆只发生在步骤10,在这里我们将实际类型为B的SVGAnimatedType写到类型为A的SVGAnimatedType内。其余和m_animatedType的交互都不危险,只是以字符串方式获取和设置值,这些操作不管实际类型是什么都是安全的。
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
注意,尽管<animate>元素支持XML属性和CSS属性animating,我们也只能在CSS属性上进行上面的操作,因为XML属性的处理代码与此不同。我们可以操作的CSS属性的列表可以在这里被找到。
那么,我们如何使用这类混淆来进行信息泄露?最初的想法是让A为某种数字类型并且B为字符串类型。在这种情况下,当写操作上发生类型混淆时,一个字符串指针被写到一个数字上,然后我们在步骤11读取它。但这里有个问题(许多类型组合中也有):步骤11读的值必须是一个在当前animated属性上下文中有效的CSS属性,否则无法正确设置它的值,我们也无法读取它。例如,我们无法找到一个字符串CSS属性(在上面的列表中)能接收类似1.4e-45的值。
更好的方法是,因为步骤11的限制,替换一种数字类型为另一种数字类型。我们在这方面取得了一些成功,当A为FloatRect,B为SVGLengthListValues,这是个SVGLengthValue的向量值。像上述一样,这导致一个向量指针被当做FloatRect类型写入。这有时能成功暴露一个堆地址。为什么是有时?因为我们唯一可用的CSS属性SVGLengthListValues是stroke-dasharray的,stroke-dasharray只能接收正数。因此,如果我们想泄露的堆地址的低32位看上去像一个负的浮点数(如最高位为1),我们就无法暴露那个地址。这可以通过向堆喷射2GB的数据来解决,这样低32位堆地址开始变为正数。如果无论如何我们都需要堆喷射,我们可以用另一种方法。
我们最终选择的方式是A为SVGLengthListValues(stroke-dasharray的CSS属性),B为float(stroke-miterlimit的CSS属性)。这种类型混淆用一个浮点数覆写了一个SVGLengthValue向量的低32位。
在我们触发这个类型混淆之前,我们需要使用大约4GB的数据喷射堆(这在现代计算机上可以实现),当我们把堆地址从0x000000XXXXXXXXXX变为0x000000XXYYYYYYYY时可以获得比较高的成功率,最终的地址仍然是一个有效的堆地址,特别是YYYYYYYY很大时。用这种方法,我们可以在0x000000XX00000000+任意偏移地址处暴露非完全随机的数据。
为什么是非完全随机?因为仍然存在一些限制:
- 因为stroke-miterlimit必须为正数,我们只能再次以32位浮点形式从堆泄露数据。
- SVGLengthValue是由一个32位浮点连着一个表示单位的枚举类型组成。当在步骤11以字符串方式读SVGLengthValue,如果单位值有效,它将被附加在数字后面(如100px)。如果我们尝试设置一个类似的字符串到stroke-miterlimit属性将失败。因此,我们想读取的堆值的下一个字节必须被解释为无效的单位(在这种情况下,当以字符串方式读取SVGLengthValue时,单位不会附加在后面)。
注意这些限制在进行非对齐读操作时也有效。
现在我们有了个没多少用的读操作,那么我们读出了些什么?做这些的全部目的是对抗ASLR,我们需要读到一个可执行模块的指针。在通常利用中,可以通过读堆中某对象的vtable指针达到目的。然而,在MacOS中,vtable指针指向一个不包含相应模块可执行代码的独立的内存区域。所以不能读出vtable指针而需要读出一个函数指针。
我们最后选择在堆喷射中使用VTTRegion对象。VTTRegion对象包含一个定时器,它包含一个指向函数对象的指针,这个对象(在这种情况下)包含一个指向VTTRegion::scrollTimerFired()的函数指针。因此,我们可以使用VTTRegion对象喷射内存(这在Mac Mini中花大约10秒)然后搜索堆喷后的内存得到函数指针。
这给了我们绕过ASLR的能力,但下一阶段需要的是payload地址(ROP链和shellcode)。我们通过下面的步骤获得它:
现在我们有了个没多少用的读操作,那么我们读出了些什么?做这些的全部目的是对抗ASLR,我们需要读到一个可执行模块的指针。在通常利用中,可以通过读堆中某对象的vtable指针达到目的。然而,在MacOS中,vtable指针指向一个不包含相应模块可执行代码的独立的内存区域。所以不能读出vtable指针而需要读出一个函数指针。
我们最后选择在堆喷射中使用VTTRegion对象。VTTRegion对象包含一个定时器,它包含一个指向函数对象的指针,这个对象(在这种情况下)包含一个指向VTTRegion::scrollTimerFired()的函数指针。因此,我们可以使用VTTRegion对象喷射内存(这在Mac Mini中花大约10秒)然后搜索堆喷后的内存得到函数指针。
这给了我们绕过ASLR的能力,但下一阶段需要的是payload地址(ROP链和shellcode)。我们通过下面的步骤获得它:
- 在堆喷射中找到一个VTTRegion对象
- 通过在堆喷射过程中设置VTTRegion.height属性为一个递增的索引,我们可以识别出读到的是第几个VTTRegion。
- 设置VTTRegion对象的VTTRegion.id属性为payload。
- 读出VTTRegion.id指针。
我们现在准备第二次触发漏洞,这次是为了代码执行。这次就是典型的UAF利用场景了:我们用控制的数据重写被释放的SVGAnimatedTypeAnimator对象。
随着最近Apple对于攻击者能控制的大多数数据类型(如字符串、数组等)引入了gigacage(一大块独立的内存),gigacage不再是个试验性的功能。然而,向量内容仍然分配在主堆中。通过找到一个内容被我们完全控制的向量,我们得以绕过堆的限制。最终我选择的是当调用TypedArray.set()从一个JavaScript类型数组复制到另一个类型数组时使用的临时向量。这是个临时向量,意味着它在使用后会立刻被删除,但是,基于webkit内存分配算法,这并不是太大的问题。与其他稳定性改进一样,找到一个更持久可控的分配方式的任务就留给读者了。:-)此时,在JavaScript事件处理例程中,我们将被释放的SVGAnimatedTypeAnimator替换为一个前8个字节被设置为ROP链+shellcode payload的向量。ROP链就是最基本的,但使用的栈pivot gadget(或者,在这里个例子里,gadgets)有一个很有趣的地方。在我们的场景下,被释放对象的虚函数以下面方式被调用:
call qword ptr [rax+10h]
这里rax指向我们的payload。rsi指向被释放的对象(这个我们现在也能控制)。在ROP中我们第一件想做的事是控制栈,但我们没有能够找到能完成下述功能的gadgets:
mov rsp, rax; ret; push rax; pop rsp; ret; xchg rax, rsp; ret;
我最终将栈置换拆分成两个gadgets:
push rax; mov rax, [rsi], call [rax + offset];
第一个gadget将payload地址压入栈,而且这很常见,毕竟这就是原始虚函数被调用的方式(除了push rax,它可以以其它指令收尾)。第二个gadget可以是:
pop whatever; pop rsp; ret;
第一个pop将返回地址弹出栈,第二个pop最终将可控制的值弹出并保存到rsp。这种gadget很少见,但仍然是比之前提到的栈pivot更为常见,至少在我们的二进制文件中。
最终的ROP链是(记住从偏移0x10开始读):
最终的ROP链是(记住从偏移0x10开始读):
[address of pop; pop; pop; ret] 0 [address of push rax; mov rax, [rsi], call [rax+0x28]]; 0 [address of pop; ret] [address of pop rbp; pop rsp; ret;] [address of pop rdi; ret] 0 [address of pop rsi; ret] shellcode length [address of pop rdx; ret] PROT_EXEC + PROT_READ + PROT_WRITE [address of pop rcx; ret] MAP_ANON + MAP_PRIVATE [address of pop r8; pop rbp; ret] -1 0 [address of pop r9; ret] 0 [address of mmap] [address of push rax; pop rdi; ret] [address of push rsp; pop rbp; ret] [address of push rbp; pop rax; ret] [address of add rax, 0x50; pop rbp; ret] 0 [address of push rax; pop rsi; pop rbp; ret] 0 [address of pop rdx; ret] shellcode length [address of memcpy] [address of jmp rax;] 0 shellcode
ROP链调用
mmap(0, shellcode_length, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANON + MAP_PRIVATE, -1, 0)
然后计算shellcode地址并将其复制到mmap()返回的地址内,之后shellcode就被调用了。在我们的例子中,shellcode只是一串int 3指令,当到达shellcode,Safari将崩溃。如果附加了调试器,可以看到像命中断点一样成功到达shellcode。
Process 5833 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0) frame #0: 0x00000001b1b83001 -> 0x1b1b83001: int3 0x1b1b83002: int3 0x1b1b83003: int3 0x1b1b83004: int3 Target 0: (com.apple.WebKit.WebContent) stopped.
在现实中,shellcode可以是第二阶段利用来逃脱Safari沙箱或者将问题变成通常的XSS的payload,盗取跨域数据。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
IOS最新缓解措施的影响
这个利用的一个有趣方面是,因为平台缺少控制流缓解措施,在Mac OS的Safari上可以以一种非常传统的方法来写(信息泄露+ROP)。
在最新的移动硬件和iOS 12上,它们这个利用写完之后发布,Apple使用PAC(Pointer Authentication Codes)引入了一项控制流缓解措施。尽管现在我没有计划在现有情况再写一个版本的利用,但讨论如何修改利用来避免被这个缓解措施影响是件有趣的事情。
call qword ptr [rax+10h]
这里rax指向我们的payload。rsi指向被释放的对象(这个我们现在也能控制)。在ROP中我们第一件想做的事是控制栈,但我们没有能够找到能完成下述功能的gadgets:
mov rsp, rax; ret; push rax; pop rsp; ret; xchg rax, rsp; ret;
我最终将栈置换拆分成两个gadgets:
push rax; mov rax, [rsi], call [rax + offset];
第一个gadget将payload地址压入栈,而且这很常见,毕竟这就是原始虚函数被调用的方式(除了push rax,它可以以其它指令收尾)。第二个gadget可以是:
pop whatever; pop rsp; ret;
第一个pop将返回地址弹出栈,第二个pop最终将可控制的值弹出并保存到rsp。这种gadget很少见,但仍然是比之前提到的栈pivot更为常见,至少在我们的二进制文件中。
最终的ROP链是(记住从偏移0x10开始读):
最终的ROP链是(记住从偏移0x10开始读):
[address of pop; pop; pop; ret] 0 [address of push rax; mov rax, [rsi], call [rax+0x28]]; 0 [address of pop; ret] [address of pop rbp; pop rsp; ret;] [address of pop rdi; ret] 0 [address of pop rsi; ret] shellcode length [address of pop rdx; ret] PROT_EXEC + PROT_READ + PROT_WRITE [address of pop rcx; ret] MAP_ANON + MAP_PRIVATE [address of pop r8; pop rbp; ret] -1 0 [address of pop r9; ret] 0 [address of mmap] [address of push rax; pop rdi; ret] [address of push rsp; pop rbp; ret] [address of push rbp; pop rax; ret] [address of add rax, 0x50; pop rbp; ret] 0 [address of push rax; pop rsi; pop rbp; ret] 0 [address of pop rdx; ret] shellcode length [address of memcpy] [address of jmp rax;] 0 shellcode
ROP链调用
mmap(0, shellcode_length, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANON + MAP_PRIVATE, -1, 0)
然后计算shellcode地址并将其复制到mmap()返回的地址内,之后shellcode就被调用了。在我们的例子中,shellcode只是一串int 3指令,当到达shellcode,Safari将崩溃。如果附加了调试器,可以看到像命中断点一样成功到达shellcode。
Process 5833 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0) frame #0: 0x00000001b1b83001 -> 0x1b1b83001: int3 0x1b1b83002: int3 0x1b1b83003: int3 0x1b1b83004: int3 Target 0: (com.apple.WebKit.WebContent) stopped.
在现实中,shellcode可以是第二阶段利用来逃脱Safari沙箱或者将问题变成通常的XSS的payload,盗取跨域数据。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
IOS最新缓解措施的影响
这个利用的一个有趣方面是,因为平台缺少控制流缓解措施,在Mac OS的Safari上可以以一种非常传统的方法来写(信息泄露+ROP)。
在最新的移动硬件和iOS 12上,它们这个利用写完之后发布,Apple使用PAC(Pointer Authentication Codes)引入了一项控制流缓解措施。尽管现在我没有计划在现有情况再写一个版本的利用,但讨论如何修改利用来避免被这个缓解措施影响是件有趣的事情。
mov rsp, rax; ret; push rax; pop rsp; ret; xchg rax, rsp; ret;
我最终将栈置换拆分成两个gadgets:
push rax; mov rax, [rsi], call [rax + offset];
第一个gadget将payload地址压入栈,而且这很常见,毕竟这就是原始虚函数被调用的方式(除了push rax,它可以以其它指令收尾)。第二个gadget可以是:
pop whatever; pop rsp; ret;
第一个pop将返回地址弹出栈,第二个pop最终将可控制的值弹出并保存到rsp。这种gadget很少见,但仍然是比之前提到的栈pivot更为常见,至少在我们的二进制文件中。
最终的ROP链是(记住从偏移0x10开始读):
最终的ROP链是(记住从偏移0x10开始读):
[address of pop; pop; pop; ret] 0 [address of push rax; mov rax, [rsi], call [rax+0x28]]; 0 [address of pop; ret] [address of pop rbp; pop rsp; ret;] [address of pop rdi; ret] 0 [address of pop rsi; ret] shellcode length [address of pop rdx; ret] PROT_EXEC + PROT_READ + PROT_WRITE [address of pop rcx; ret] MAP_ANON + MAP_PRIVATE [address of pop r8; pop rbp; ret] -1 0 [address of pop r9; ret] 0 [address of mmap] [address of push rax; pop rdi; ret] [address of push rsp; pop rbp; ret] [address of push rbp; pop rax; ret] [address of add rax, 0x50; pop rbp; ret] 0 [address of push rax; pop rsi; pop rbp; ret] 0 [address of pop rdx; ret] shellcode length [address of memcpy] [address of jmp rax;] 0 shellcode
ROP链调用
mmap(0, shellcode_length, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANON + MAP_PRIVATE, -1, 0)
然后计算shellcode地址并将其复制到mmap()返回的地址内,之后shellcode就被调用了。在我们的例子中,shellcode只是一串int 3指令,当到达shellcode,Safari将崩溃。如果附加了调试器,可以看到像命中断点一样成功到达shellcode。
Process 5833 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0) frame #0: 0x00000001b1b83001 -> 0x1b1b83001: int3 0x1b1b83002: int3 0x1b1b83003: int3 0x1b1b83004: int3 Target 0: (com.apple.WebKit.WebContent) stopped.
在现实中,shellcode可以是第二阶段利用来逃脱Safari沙箱或者将问题变成通常的XSS的payload,盗取跨域数据。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
IOS最新缓解措施的影响
这个利用的一个有趣方面是,因为平台缺少控制流缓解措施,在Mac OS的Safari上可以以一种非常传统的方法来写(信息泄露+ROP)。
在最新的移动硬件和iOS 12上,它们这个利用写完之后发布,Apple使用PAC(Pointer Authentication Codes)引入了一项控制流缓解措施。尽管现在我没有计划在现有情况再写一个版本的利用,但讨论如何修改利用来避免被这个缓解措施影响是件有趣的事情。
push rax; mov rax, [rsi], call [rax + offset];
第一个gadget将payload地址压入栈,而且这很常见,毕竟这就是原始虚函数被调用的方式(除了push rax,它可以以其它指令收尾)。第二个gadget可以是:
pop whatever; pop rsp; ret;
第一个pop将返回地址弹出栈,第二个pop最终将可控制的值弹出并保存到rsp。这种gadget很少见,但仍然是比之前提到的栈pivot更为常见,至少在我们的二进制文件中。
最终的ROP链是(记住从偏移0x10开始读):
最终的ROP链是(记住从偏移0x10开始读):
[address of pop; pop; pop; ret] 0 [address of push rax; mov rax, [rsi], call [rax+0x28]]; 0 [address of pop; ret] [address of pop rbp; pop rsp; ret;] [address of pop rdi; ret] 0 [address of pop rsi; ret] shellcode length [address of pop rdx; ret] PROT_EXEC + PROT_READ + PROT_WRITE [address of pop rcx; ret] MAP_ANON + MAP_PRIVATE [address of pop r8; pop rbp; ret] -1 0 [address of pop r9; ret] 0 [address of mmap] [address of push rax; pop rdi; ret] [address of push rsp; pop rbp; ret] [address of push rbp; pop rax; ret] [address of add rax, 0x50; pop rbp; ret] 0 [address of push rax; pop rsi; pop rbp; ret] 0 [address of pop rdx; ret] shellcode length [address of memcpy] [address of jmp rax;] 0 shellcode
ROP链调用
mmap(0, shellcode_length, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANON + MAP_PRIVATE, -1, 0)
然后计算shellcode地址并将其复制到mmap()返回的地址内,之后shellcode就被调用了。在我们的例子中,shellcode只是一串int 3指令,当到达shellcode,Safari将崩溃。如果附加了调试器,可以看到像命中断点一样成功到达shellcode。
Process 5833 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0) frame #0: 0x00000001b1b83001 -> 0x1b1b83001: int3 0x1b1b83002: int3 0x1b1b83003: int3 0x1b1b83004: int3 Target 0: (com.apple.WebKit.WebContent) stopped.
在现实中,shellcode可以是第二阶段利用来逃脱Safari沙箱或者将问题变成通常的XSS的payload,盗取跨域数据。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
IOS最新缓解措施的影响
这个利用的一个有趣方面是,因为平台缺少控制流缓解措施,在Mac OS的Safari上可以以一种非常传统的方法来写(信息泄露+ROP)。
在最新的移动硬件和iOS 12上,它们这个利用写完之后发布,Apple使用PAC(Pointer Authentication Codes)引入了一项控制流缓解措施。尽管现在我没有计划在现有情况再写一个版本的利用,但讨论如何修改利用来避免被这个缓解措施影响是件有趣的事情。
pop whatever; pop rsp; ret;
第一个pop将返回地址弹出栈,第二个pop最终将可控制的值弹出并保存到rsp。这种gadget很少见,但仍然是比之前提到的栈pivot更为常见,至少在我们的二进制文件中。
最终的ROP链是(记住从偏移0x10开始读):
最终的ROP链是(记住从偏移0x10开始读):
[address of pop; pop; pop; ret] 0 [address of push rax; mov rax, [rsi], call [rax+0x28]]; 0 [address of pop; ret] [address of pop rbp; pop rsp; ret;] [address of pop rdi; ret] 0 [address of pop rsi; ret] shellcode length [address of pop rdx; ret] PROT_EXEC + PROT_READ + PROT_WRITE [address of pop rcx; ret] MAP_ANON + MAP_PRIVATE [address of pop r8; pop rbp; ret] -1 0 [address of pop r9; ret] 0 [address of mmap] [address of push rax; pop rdi; ret] [address of push rsp; pop rbp; ret] [address of push rbp; pop rax; ret] [address of add rax, 0x50; pop rbp; ret] 0 [address of push rax; pop rsi; pop rbp; ret] 0 [address of pop rdx; ret] shellcode length [address of memcpy] [address of jmp rax;] 0 shellcode
ROP链调用
mmap(0, shellcode_length, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANON + MAP_PRIVATE, -1, 0)
然后计算shellcode地址并将其复制到mmap()返回的地址内,之后shellcode就被调用了。在我们的例子中,shellcode只是一串int 3指令,当到达shellcode,Safari将崩溃。如果附加了调试器,可以看到像命中断点一样成功到达shellcode。
Process 5833 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0) frame #0: 0x00000001b1b83001 -> 0x1b1b83001: int3 0x1b1b83002: int3 0x1b1b83003: int3 0x1b1b83004: int3 Target 0: (com.apple.WebKit.WebContent) stopped.
在现实中,shellcode可以是第二阶段利用来逃脱Safari沙箱或者将问题变成通常的XSS的payload,盗取跨域数据。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
IOS最新缓解措施的影响
这个利用的一个有趣方面是,因为平台缺少控制流缓解措施,在Mac OS的Safari上可以以一种非常传统的方法来写(信息泄露+ROP)。
在最新的移动硬件和iOS 12上,它们这个利用写完之后发布,Apple使用PAC(Pointer Authentication Codes)引入了一项控制流缓解措施。尽管现在我没有计划在现有情况再写一个版本的利用,但讨论如何修改利用来避免被这个缓解措施影响是件有趣的事情。
[address of pop; pop; pop; ret] 0 [address of push rax; mov rax, [rsi], call [rax+0x28]]; 0 [address of pop; ret] [address of pop rbp; pop rsp; ret;] [address of pop rdi; ret] 0 [address of pop rsi; ret] shellcode length [address of pop rdx; ret] PROT_EXEC + PROT_READ + PROT_WRITE [address of pop rcx; ret] MAP_ANON + MAP_PRIVATE [address of pop r8; pop rbp; ret] -1 0 [address of pop r9; ret] 0 [address of mmap] [address of push rax; pop rdi; ret] [address of push rsp; pop rbp; ret] [address of push rbp; pop rax; ret] [address of add rax, 0x50; pop rbp; ret] 0 [address of push rax; pop rsi; pop rbp; ret] 0 [address of pop rdx; ret] shellcode length [address of memcpy] [address of jmp rax;] 0 shellcode
ROP链调用
mmap(0, shellcode_length, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANON + MAP_PRIVATE, -1, 0)
然后计算shellcode地址并将其复制到mmap()返回的地址内,之后shellcode就被调用了。在我们的例子中,shellcode只是一串int 3指令,当到达shellcode,Safari将崩溃。如果附加了调试器,可以看到像命中断点一样成功到达shellcode。
Process 5833 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0) frame #0: 0x00000001b1b83001 -> 0x1b1b83001: int3 0x1b1b83002: int3 0x1b1b83003: int3 0x1b1b83004: int3 Target 0: (com.apple.WebKit.WebContent) stopped.
在现实中,shellcode可以是第二阶段利用来逃脱Safari沙箱或者将问题变成通常的XSS的payload,盗取跨域数据。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
该利用在Mac OS 10.13.6上测试成功(构建版本17G65)。如果你仍然在使用这个版本,你最好更新一下。完全的利用见这里。
IOS最新缓解措施的影响
这个利用的一个有趣方面是,因为平台缺少控制流缓解措施,在Mac OS的Safari上可以以一种非常传统的方法来写(信息泄露+ROP)。
在最新的移动硬件和iOS 12上,它们这个利用写完之后发布,Apple使用PAC(Pointer Authentication Codes)引入了一项控制流缓解措施。尽管现在我没有计划在现有情况再写一个版本的利用,但讨论如何修改利用来避免被这个缓解措施影响是件有趣的事情。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2018-11-26 16:00
被0sWalker编辑
,原因:
赞赏
他的文章
看原图
赞赏
雪币:
留言: