作者:Kerne7@知道创宇404实验室
时间:2020年6月29日
原文链接:https://paper.seebug.org/1257/
首先根据谷歌博客收集相关CVE-2019-5786漏洞的资料:High CVE-2019-5786: Use-after-free in FileReader,得知是FileReader上的UAF漏洞。
然后查看https://github.com/chromium/chromium/commit/ba9748e78ec7e9c0d594e7edf7b2c07ea2a90449?diff=split上的补丁
对比补丁可以看到DOMArrayBuffer* result = DOMArrayBuffer::Create(raw_data_->ToArrayBuffer())
,操作放到了判断finished_loading后面,返回值也从result变成了array_buffer_result_(result的拷贝)。猜测可能是这个返回值导致的问题。
分析代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D3BhuqKd-1593678610163)(https://images.seebug.org/content/images/2020/06/1c5d1e12-829f-4d3c-9195-441dcbd2bf88.png-w331s)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iz15ALNP-1593678610165)(https://images.seebug.org/content/images/2020/06/df171f7b-a6e7-4c96-88bb-d400370de59d.png-w331s)]
raw_data_->ToArrayBuffer()
可能会返回内部buffer的拷贝,或者是返回一个指向其偏移buffer的指针。
根据MDN中FileReader.readAsArrayBuffer()的描述:
FileReader 接口提供的 readAsArrayBuffer() 方法用于启动读取指定的 Blob 或 File 内容。当读取操作完成时,readyState 变成 DONE(已完成),并触发 loadend 事件,同时 result 属性中将包含一个 ArrayBuffer 对象以表示所读取文件的数据。
FileReader.onprogress事件在处理progress时被触发,当数据过大的时候,onprogress事件会被多次触发。
所以在调用FileReader.result属性的时候,返回的是WTF::ArrayBufferBuilder创建的WTF::ArrayBuffer对象的指针,Blob未被读取完时,指向一个WTF::ArrayBuffer副本,在已经读取完的时候返回WTF::ArrayBufferBuilder创建的WTF::ArrayBuffer自身。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hlGzVHPD-1593678610168)(https://images.seebug.org/content/images/2020/06/e6cfb2b4-18b6-4584-b99e-845f7693b5ed.png-w331s)]
那么在标志finished_loading被置为ture的时候可能已经加载完毕,所以onprogress和onloaded事件中返回的result就可能是同一个result。通过分配给一个worker来释放其中一个result指针就可以使另一个为悬挂指针,从而导致UAF漏洞。
我选择的32位win7环境的Chrome72.0.3626.81版本,可以通过申请1GB的ArrayBuffer,使Chrome释放512MB保留内存,通过异常处理使OOM不会导致crash,然后在这512MB的内存上分配空间。
调用FileReader.readAsArrayBuffer,将触发多个onprogress事件,如果事件的时间安排正确,则最后两个事件可以返回同一个ArrayBuffer。通过释放其中一个指针来释放ArrayBuffer那块内存,后面可以使用另一个悬挂指针来引用这块内存。然后通过将做好标记的JavaScript对象(散布在TypedArrays中)喷洒到堆中来填充释放的区域。
通过悬挂的指针查找做好的标记。通过将任意对象的地址设置为找到的对象的属性,然后通过悬挂指针读取属性值,可以泄漏任意对象的地址。破坏喷涂的TypedArray的后备存储,并使用它来实现对地址空间的任意读写访问。
之后可以加载WebAssembly模块会将64KiB的可读写执行存储区域映射到地址空间,这样的好处是可以免去绕过DEP或使用ROP链就可以执行shellcode。
使用任意读取/写入原语遍历WebAssembly模块中导出的函数的JSFunction对象层次结构,以找到可读写可执行区域的地址。将WebAssembly函数的代码替换为shellcode,然后通过调用该函数来执行它。
通过浏览器访问网页,就会导致执行任意代码
本人在初次调试浏览器的时候遇到了很多问题,在这里列举出一些问题来减少大家走的弯路。
因为chrome是多进程模式,所以在调试的时候会有多个chrome进程,对于刚开始做浏览器漏洞那话会很迷茫不知道该调试那个进程或者怎么调试,可以通过chrome自带的任务管理器来帮我们锁定要附加调试的那个进程ID。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8DvmvZaP-1593678610177)(https://images.seebug.org/content/images/2020/06/ed435040-1dec-4131-ae16-63d8a0121826.png-w331s)]
这里新的标签页的进程ID就是我们在后面要附加的PID。
Chrome调试的时候需要符号,这是google提供的符号服务器(加载符号的时候需要)。在windbg中,您可以使用以下命令将其添加到符号服务器搜索路径,其中c:\Symbols是本地缓存目录:
.sympath + SRV * c:\ Symbols * https://chromium-browser-symsrv.commondatastorage.googleapis.com
因为Chrome的沙箱机制,在调试的过程中需要关闭沙箱才可以执行任意代码。可以在快捷方式中添加no-sandbox
来关闭沙箱。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cdyqEyyT-1593678610178)(https://images.seebug.org/content/images/2020/06/41c222b1-90ca-48cc-b5a2-d73483d3a3de.png-w331s)]
由于这个漏洞机制的原因,可能不是每次都能执行成功,但是我们可以通过多次加载脚本的方式来达到稳定利用的目的。
在github上有chromuim的源码,在分析源码的时候推荐使用sourcegraph这个插件,能够查看变量的定义和引用等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XoLzBUDS-1593678610180)(https://images.seebug.org/content/images/2020/06/5f976067-8c0d-4382-a3da-12921bbdfcd7.png-w331s)]
在需要特定版本Chrome的时候可以自己去build源码或者去网络上寻找chrome历代发行版收集的网站。
在看exp和自己编写的时候需要注意v8引擎的指针问题,v8做了指针压缩,所以在内存中存访的指针可能和实际数据位置地址有出入。
https://www.anquanke.com/post/id/194351
https://blog.exodusintel.com/2019/01/22/exploiting-the-magellan-bug-on-64-bit-chrome-desktop/
https://blog.exodusintel.com/2019/03/20/cve-2019-5786-analysis-and-exploitation/