Look Mom, I dont use Shellcode议题Exploit复现 – 作者:elli0tn0phacker

*本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。

*本文作者:elli0tn0phacker,本文属于FreeBuf原创奖励计划,未经许可禁止转载

0x00 背景

Look Mom, I don’t use Shellcode是Moritz Jodeit在2016 HITB GSEC上的一个议题。议题详细介绍了在64位Windows10上利用IE11的漏洞实现RCE,并进一步绕过EPM和EMET5.5等一系列Mitigation的技术细节。值得注意的是该议题介绍的技术内容获得了2016年MSRC Mitigation Bypass Bounty program十万美元的最高奖金。
image.png

image.png

笔者在学习了议题的PPT后尝试了exploit的复现,其中遇到不少坑,最终还是实现了Win10下的RCE。这里记录下整个复现过程,与大家分享,不过水平有限,文中错误之处恳请斧正。

0x01 漏洞成因

议题中使用的漏洞是Typed Array Neutering漏洞 (CVE-2016-3210)。这是一个品相非常好的UAF漏洞。这里使用了HTML5的特性web worker来触发漏洞。传统的JavaScript是在单线程环境中工作的,在一个Context中无法同时运行多个js脚本,web worker提供了JavaScript多线程运行环境,主线程通过worker可以创建一个不含DOM操作的worker线程用来做数据处理,通过postMessage向worker线程发送数据,被发送的数据会将所有权从当前的Context转移到Worker线程的Context。

该漏洞的成因是主线程使用postMessage将ArrayBuffer传给Worker线程后,ArrayBuffer会在主线程的Context中被释放,然而创建这个ArrayBuffer的TypedArray仍然保留了被释放的ArrayBuffer的指针,从而造成UAF。

首先分析PPT里的PoC: 

image.png开启HPA和UST,在IE11中打开PoC,很遗憾在我的环境里并没有Crash!写数据不太好发现问题,尝试换成读数据分析没有复现Crash的原因:
image.png可以看到这里读取的数据显示的是undefined,PoC中定义的Int8Array大小为0x42,这里尝试访问索引为0x4141的数组元素,结果显示为undefined,可以猜测索引越界的Int8Array在我的调试环境不会尝试去读Int8Array保存的ArrayBuffer数据,这里其实也可以用正常的js代码测试,结果也是一样的。

通过分析jscript9.dll可以知道访问数组元素会调用jscript9! Js::JavascriptOperators::OP_GetElementI函数,在windbg中对这个函数下断点,分析为什么PoC不会Crash。

再次加载PoC,命中断点jscript9! Js::JavascriptOperators::OP_GetElementI后单步运行到图示处:image.png可以看到这里有一个比较运算,比较的双方正是数组元素索引0x4141和TypedArray长度0x42,如果数组索引大于等于TypedArray的长度,就会跳转到刚才弹出undefined的分支,这里尝试修改TypedArray的长度为0x4142看看运行情况:
image.png可以看到没有进入刚才弹出undefined的分支,并且IE Crash了,所以我们只需要修改PoC保证TypedArray的Index小于TypedArray的大小即可,最终修改的PoC如下:
image.png再次加载PoC,IE Crash,分析Crash现场:
image.png显然在boom函数中 运行到array[0x41] = 0x42时,array保存的ArrayBuffer已经被worker.postMessage(0,[array.buffer])释放了,导致array[0x41]的内存读取失败。 

目前为止,我们的得到的信息有:

1)通过worker.postMessage我们可以释放TypedArray的ArrayBuffer的内存;

2)TypedArray仍保持被释放ArrayBuffer内存的引用,并且被释放内存大小脚本可控;

因此通过这个漏洞,我们可以获得一个悬挂指针TypedArray,指向内存的大小脚本可控,我们可以填充期望的数据结构到这块内存,并通过悬挂指针来读写这片内存,最终任意地址读写。

0x02 任意地址读写

Jscript9.dll中一般使用TypedArray作为任意地址读写的利用对象,通过修改TypedArray+0x1C的length和+0×20的ArrayBufferAddress就可以实现用户态任意地址读写(可以参考笔者之前的文章:https://www.freebuf.com/vuls/188558.html)。

PPT中的方法可以简述为如下步骤:

1)通过触发漏洞可以获得一个0xb8 Bytes大小的悬挂指针,使用同样分配在CRT堆的LargeHeapBlock占位(LargeHeapBlock是IE Custom Heap 维护大块内存的数据结构);LargeHeapBlock在Unlink的时候存在一个未受保护的双向链表卸载操作,通过corrupt这个LargeHeapBlock前向指针和后向指针可以实现任意2个DWORD的修改;

image.png

2)通过HeapSpray进行期望内存的排布,通过读取LargeHeapBlock+0x8的数据泄露IE custom Heap中里某个JavascriptArray的Data区域地址,再通过1)的写操作corrupt JavascriptArray Data区域的长度字段,从而实现Array的越界读写; 

image.png

3)通过越界读写JavascriptArray修改期望的TypedArray,最终实现任意地址读写; 

image.png

可惜的是,笔者在第一步尝试复现LargeHeapBlock的Unlink操作的时候就遇到的问题,无法实现DWORD写入,因此这里笔者尝试换了一种比较暴力的方式实现任意地址读写:

1)由于这个漏洞得到的悬挂指针可以指向脚本可控大小的内存,这里可以考虑控制一块较大的内存块如1MB;

2)通过大量申请和释放CRT堆内存操作触发系统堆的释放操作,使得大块内存被合并释放;

3)通过大量TypedArray的申请操作,让jscript9.dll从上面那个被合并释放内存申请新的IE custom heap,从而有可能会有TypedArray被保存在悬挂指针指向的1MB内存;

4)通过悬挂指针找到一个TypedArray并修改长度实现任意地址读写;内存布局部分的脚本实现如下:

image.png

preheapspray()函数用来在CRT堆申请一百多MB的大块内存,然后申请触发漏洞获得了1MB悬挂指针的内存,释放内存后,再通过heapspray()做期望利用的内存布局,动态调试情况如下:

1)preheapspray申请大块内存:

image.png

2)触发漏洞,释放ia指向的内存,再利用heapspray占位内存: 

image.png

最终我们利用HeapSpray在ia指向的内存区域找到了可以利用的TypedArray,接下来可以在js脚本中利用ia搜索TypedArray,并修改其+0x1c的length属性0x20的ArrayBufferAddress属性即可实现任意地址读写: 

image.png

获得任意地址读写权限后我们仍然需要获得泄露对象地址的方法,这里通过之前的HeapSpray我们可以在0x0x1cee0020处稳定找到一个JavascriptArray的data区,我们可以利用这个JavascriptArray的第一个元素实现对象地址泄露: 

image.png

0x03 执行Shellcode 

因为Win10下CFG保护的存在,通过篡改受CFG保护的对象虚函数指针执行Shellcode的方法不再奏效。但是作者发现存在漏洞的Win10中jscript9.dll的safemode标志并未受hash保护,因此通过修改safemode标志开启Godmode通过ActiveX执行shellcode的方式又可以奏效了: 

image.png

这里我们就可以通过之前获得任意地址读写权限和泄露对象地址的权限来修改safemode,通过ActiveX第一次成功弹出系统的计算器: 

image.png

显然仅仅弹出系统的计算器是不够的,接下来我们需要执行自己的payload,比如让一个PE落地并执行。首先先写一个简单的弹出cmd的win32程序,为了能在目标环境成功运行还需要将Runtime lib修改为MT并将编译优化开到最大减少PE的体积:

image.png

再将生成的PE文件转成base64编码保存在js脚本的payload变量,通过ADODB.Stream保存到Temp路径运行,这样一个自己定义的payload就成功运行起来了: 

image.png

看上去我们的exploit编写工作已经完成了,然而当我们尝试通过访问web server的方式加载这个exploit的时候,发现payload并未如预期出现,打开IE的调试窗口,发现如下错误: 

image.png

看上去我们还需要绕过浏览器的同源策略。通过分析可以知道,当脚本调用bStream.SaveToFile()保存数据的时候,会调用msado15!CStream::SaveToFile函数,msado15!CStream::SaveToFile在保存数据之前会调用 msado15!SecurityCheck做安全检查,我们在msado15!SecurityCheck函数入口点下断点: 

image.png

可以看到这里edx和ecx分别保存了源地址和目的地址,如果将源地址修改为“C:\”的话就可以发现payload.exe成功落地了,现在我们就需要从脚本层面来修改源地址: 

image.png

这时候我们再通过本地文件的方式(注意,这里只能通过本地文件的方式来测试)打开这个exploit。可以发现payload.exe已经成功落地并运行了,但是当我们通过web访问的时候,我们的payload依然不能运行,这就涉及到最后一个问题了,IE sandbox逃逸。 

0x04 IE Sandbox逃逸

Windows10的IE11默认开启Protect Mode, 用Process Explorer查看访问web server的IE render进程会发现render进程是工作在Low Integrity权限下的: 

image.png

low Integrity下的进程在文件操作,创建进程等都会受到权限限制,因此我们无法在low Integrity下保存我们的payload并执行,所以要突破low Integrity的限制。

IE浏览器有着Zone的概念,对于local intranet或者Trusted sites,Protect Mode将不会生效(比如之前我们通过本地文件打开exploit就是Medium Integrity):

image.png

因此这里的思路是将exploit分成两个stage,第一个stage的exploit成功后启动一个web server,然后通过localhost的方式加载第二个stage的exploit,此时第二个stage的exploit将会以Medium Integrity运行,这时候第二个stage的exploit就可以成功下载payload并成功执行:

image.png

最终exploit的效果: 

exploit.gif

当然PPT里还提到谈到了EPM(AppContainer)和EMET的bypass方法,感兴趣的同学可以自己尝试复现。

0x05 参考文献

1)https://labs.bluefrostsecurity.de/files/Look_Mom_I_Dont_Use_Shellcode-WP.pdf

2)https://www.w3schools.com/html/html5_webworkers.asp

3)https://www.blackhat.com/docs/us-14/materials/us-14-Yu-Write-Once-Pwn-Anywhere.pdf

4)https://www.freebuf.com/vuls/188558.html

*本文作者:elli0tn0phacker,本文属于FreeBuf原创奖励计划,未经许可禁止转载

来源:freebuf.com 2018-11-26 09:00:33 by: elli0tn0phacker

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论