前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入分析一个Pwn2Own的优质Webkit漏洞

深入分析一个Pwn2Own的优质Webkit漏洞

作者头像
FB客服
发布2019-12-11 15:28:18
7890
发布2019-12-11 15:28:18
举报
文章被收录于专栏:FreeBufFreeBuf

今年的Pwn2Own比赛刚刚结束,在Pwn2Own温哥华站的比赛中,Fluoroacetate团队所使用的一个WebKit漏洞成功吸引了我的注意。这个漏洞是一个价值五万五千美金的漏洞利用链的一部分,在这篇文章中,我将会对这个漏洞进行深入分析,并对漏洞进行验证和研究。

当然了,在开始深入分析之前,我们先把该漏洞的概念验证PoC提供给大家:

首先,我们需要对受该漏洞影响的WebKit版本进行编译,即Safari v12.0.3版本,根据苹果的版本发布信息,该版本对应的是修订版v240322。

代码语言:javascript
复制
svn checkout -r 240322 https://svn.webkit.org/repository/webkit/trunk webkit_ga_asan

我们可以使用AddressSanitizer(ASAN)来完成编译操作,它可以允许我们在发生内存崩溃的时候第一时间检测到错误信息。

代码语言:javascript
复制
ZDIs-Mac:webkit_ga_asan zdi$ Tools/Scripts/set-webkit-configuration --asan      ZDIs-Mac:webkit_ga_asan zdi$ Tools/Scripts/build-webkit # --jsc-only can be used here which should be enough

我们将使用11db来进行调试,因为macOS本身就自带了这个工具。由于PoC中没有包含任何的呈现代码,因此我们需要在11db中使用JavaScriptCore(JSC)来执行它。为了在11db中执行jsc,我们需要调用它的二进制代码文件,而不是之前的脚本run-jsc。这个文件可以从 WebKitBuild/Release/jsc路径获取,并且需要正确设置环境变量。

代码语言:javascript
复制
env DYLD_FRAMEWORK_PATH=/Users/zdi/webkit_ga_asan/WebKitBuild/Release

我们可以在11db中运行这条命令,或者把它放在一个文本文件中,然后传递到11db -s中。

代码语言:javascript
复制
ZDIs-Mac:webkit_ga_asan zdi$ cat lldb_cmds.txt    env     DYLD_FRAMEWORK_PATH=/Users/zdi/webkit_ga_asan/WebKitBuild/Release    r

好的,接下来,我们可以开始调试了:

我们可以看到,代码在0x6400042d1d29处发生了崩溃:mov qword ptr [rcx + 8*rsi], r8,经分析后我们确认为越界写入所导致的内存崩溃。栈追踪分析显示,它发生在虚拟机环境中,也就是编译过程或者JITed代码出了问题。我们还注意到的rsi索引,它包含了0x20000040,这个数字我们在PoC中是有见过的。

这个数字是bigarr! 的大小,即NUM_SPREAD_ARGS * sizeof(a)。为了查看JITed代码,我们可以设置JSC_dumpDFGDisassembly环境变量,这样jsc就可以跳转到DFG和FTL的编译代码了。

代码语言:javascript
复制
ZDIs-Mac:webkit_ga_asan zdi$ JSC_dumpDFGDisassembly=true lldb -s lldb_cmds.txt WebKitBuild/Release/jsc ~/poc3.js

这将丢弃掉大量无关的代码集,那我们应该如何确定相关代码呢?

我们知道崩溃事件发生在0x6400042d1d29处:mov qword ptr [rcx + 8*rsi], r8。那我们为何不尝试搜索这个地址呢?

没错,我们在DFG中找到了:

代码在使用DFG JIT的分布操作符来创建一个新数组时,调用了NewArrayWithSpread方法,整个行为发生在gen_func生成的一个函数f中,调用行为发生在一个循环中。

在对源代码进行分析后,我们发现了Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp文件中的SpeculativeJIT::compileNewArrayWithSpread函数。这是DFG代码的起始位置,启动代码意味着将JIT生成的机器代码写入内存以供以后执行。

我们可以通过查看compileNewArrayWithSpread方法来理解其中的机器代码。我们看到compileAllocateNewArrayWithSize()负责分配具有特定大小的新数组,它的第三个参数sizeGPR将作为第二个参数传递给emitAllocateButterfly(),这意味着它将为数组分配一个新的butterfly(包含JS对象值的内存空间)。如果您不熟悉JSObject的butterfly,可以点击https://liveoverflow.com/the-butterfly-of-jsobject-browser-0x02/了解更多信息。

跳转到EnITalListAtButoFuffE(),我们看到大小参数siZeGPR向左移动3位(乘以8),然后添加到常数sieof(IndexingHeader)。

方便起见,我们需要将实际的机器代码与我们在这个函数中的C++代码相匹配。m_jit字段的类型是JITCompiler。

JITCompiler负责根据数据流图生成JIT代码。它通过委托给jit来实现,后者生成一个宏汇编程序(JITCompiler通过继承关系拥有该程序)。JITCompiler保存编译期间所需信息的引用,并记录链接中使用的信息(例如,要链接的所有调用的列表)。

这意味着我们看到的调用,如m_jit.move()、m_jit.add32()等,是发出程序集的函数。通过跟踪每一个函数,我们将能够将其与C++对应的组件匹配。除了跟踪内存分配的malloc调试功能外,我们还根据自己对Intel程序集的偏好配置11db。

代码语言:javascript
复制
     ZDIs-Mac:~ zdi$ cat ~/.lldbinit          settings set target.x86-disassembly-flavor intel          type format add --format hex long          type format add --format hex "unsigned long"          command script import lldb.macosx.heap          settings set target.env-vars          DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib          settings set target.env-vars MallocStackLogging=1          settings set target.env-vars MallocScribble=1

由于在启用Guard Malloc的情况下正在分配大容量,我们需要设置另一个允许此类分配的环境变量。

代码语言:javascript
复制
ZDIs-Mac:webkit_ga_asan zdi$ cat lldb_cmds.txt          env DYLD_FRAMEWORK_PATH=/Users/zdi/webkit_ga_asan/WebKitBuild/Release env MALLOC_PERMIT_INSANE_REQUESTS=1          r

JSC_dumpDFGDisassembly将以AT&T格式转储程序集,因此我们运行deassembly-s 0x6400042d1c22-c 70可以获得英特尔风格的程序集,结果如下:

让我们尝试匹配emitAllocateButterfly()中的一些代码。查看程序集列表,我们可以匹配以下内容:

接下来分析机器代码,此时需要设置断点。为此,我们在编译之前向jsc.cpp添加了一个dbg()函数。这将有助于在我们需要的时候进入JS代码。编译器报错显示未使用EncodedJSValue JSC_HOST_CALL functionDbg(ExecState*exec)函数中的exec,因此失败。为了解决这个问题,我们只添加了exec->argumentCount();这不会影响执行。

让我们在这里添加dbg(),因为实际的NewArrayWithSpread函数将在创建bigarr期间执行。

再次JSC_dumpDFGDisassembly=true lldb -s lldb_cmds.txt WebKitBuild/Release/jsc ~/poc3.js运行将会导出编译代码:

在bigarr创建之前中断,您可以看到NewArrayWithSpread的机器代码。让我们在函数的开始处放置一个断点并继续执行。

断点生效:

接下来,我们需要仔细分析断点信息:

那么这里到底发生了什么?还记得PoC中的下面这部分信息吗?

mk_arr函数创建一个数组,第一个参数作为大小,第二个参数作为元素。大小为(0x20000000+0x40)/8=0x4000008,这将创建一个大小为0x4000008、元素值为0x41414141410000的数组。i2f函数用于将整数转换为浮点值,以便最终在内存中得到预期值。

我们现在知道rcx指向对象a的butterfly-0x10,因为它的大小是rcx+8,这使得butterfly rcx+0x10。在这段代码的其余部分中,我们看到r8、r10、rdi、r9、rbx、r12和r13都指向对象a的一个副本-具体来说是八个副本,edx不断地添加每个副本的大小。

此时,edx的值变成了0x20000040:

那么这八个a拷贝到了哪里呢?值0x20000040代表的又是什么呢?

重新看看PoC:

这意味f变成了:

f通过扩展NUM_SPREAD_ARGS(8)第一个参数的副本和第二个参数的单个副本来创建数组。用对象a(8*0x04000008)和c(长度1)调用f。当NewArrayWithSpread被调用时,它为8个a和1个c腾出了空间。

最后一步到显示对象c的长度,这使得最终的edx值为0x20000041。

下一步应该是长度的分配,它发生在emitAllocateButterfly()中。

我们注意到shl r8d,0x3的溢出,其中0x20000041被封装到了0x208。当分配大小传递给emitAllocatevariableSize()时,它变为了0x210。

我们看到的越界读取访问冲突发生在mov qword ptr[rcx+8*rsi],r8的以下代码片段中。这个代码片段的问题是用错误的大小0x20000041反向迭代新创建的butterfly,而溢出后的实际大小是0x210。然后,它将每个元素归零,但由于内存中的实际大小远小于0x20000041,因此在ASAN构建中发生了了越界访问冲突。

下面给出的是整个越界访问行为的流程图:

总结

在这篇文章中,我们对WebKit版本v240322中的一个越界访问漏洞进行了深入分析,这个漏洞是一个价值五万五千美金的漏洞利用链中的一部分。Pwn2Own上出现的漏洞往往都是行业内较为优质的漏洞,而本文所分析的这个漏洞也不例外。在日常的漏洞研究过程中,我也希望大家能够学会使用11db,如果大家有更多关于该漏洞的想法,可以直接在我的推特上艾特我(@ziadrb)。希望本文能够给大家提供帮助!

*参考来源:thezdi,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FreeBuf 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档