首页
社区
课程
招聘
[原创]CVE-2020-0674漏洞分析笔记
发表于: 2020-5-14 10:27 14710

[原创]CVE-2020-0674漏洞分析笔记

2020-5-14 10:27
14710

CVE-2020-0674是360和Google在2020年初抓到的一个IE 0day,它是一个位于jscript.dll模块的UAF(释放后重用)漏洞。最近,该漏洞的一份完整利用代码在github被公布,笔者花了一些时间对此进行了分析。

该漏洞的成因为:若Array.sort()被调用时传入一个比较函数,jscript内部没有将此比较函数的两个参数加入GC,导致可以在对象被释放后得到悬垂指针。笔者去年曾分析过一个此类漏洞,当时就预测此类漏洞后面还会出现。

我们一起来看一下这个漏洞的PoC:

笔者所用分析环境如下:

为IE开启页堆,在调试器中打开上述PoC,可以观察到如下崩溃:

很明显,崩溃的原因是jscript!CScriptRuntime::TypeOf在解引用一个VARIANT指针时,发现该VARIANT已经被释放,属于典型的UAF。

下面跟随笔者一起来调试一下利用代码。

代码中首先通过这个UAF漏洞来泄露一个指针,相关原理笔者已经在CVE-2018-8353那篇文章中进行描述,唯一不同的是本次涉及的是64位下的偏移和对象。

要理解这里的释放后重用,首先要了解相关对象。

首先,当new Object()时,jscript会在内存中申请一个VARIANT,64位下每个VARIANT所占内存为0x18字节,结构如下:

这些VARIANT设计上属于GC对象,所以会申请在一个大的GcBlock中,GcBlock结构如下:

64位下每个GcBlock大小为0x970,可以由如下公式计算得出:

这些Object VARIANT在内存中排列如下:

上面的VARIANT对象在GcBlock中的布局可能不太直观,笔者其进行着色如下:

利用此类UAF漏洞的思路是申请大量VARIANT对象,然后进行释放,这样就会得到一个个空闲的大小为0x970左右的堆块,这些堆块会被回收到低碎片堆中,接着迅速重用这些堆块。

那么,如何重用这些空闲堆块呢?

这里再补充一些前置知识,在为一个jscript对象添加第一个成员变量时,若成员变量的长度超过一定阈值,jscript会调用NameList::FCreateVval去申请特定大小的内存,64位下,具体的申请操作在NoRelAlloc::PvAlloc中完成,这里直接将计算公式概括如下(具体细节读者可以自行逆向上述两个函数):

当alloc_size为0x970时,我们就可以重用之前回收的GcBlock内存块。此时对应的x=569。

重用后的内存内排列着一个个代表属性的结构与属性名称,具体结构定义如下:

第一轮UAF泄露的就是某个Property偏移0x28处的一个next指针,这个next指针指向下一个Property的首地址。

如果在重占位后,通过第1个至第x-1个属性名的名称和长度来控制这块重用的内存。将第x-1个Property的hash构造为5,并且通过设置第x个属性,让第x-1个Property的next指针指向第x个对象的Property,就可以借助伪造的VARIANT读取第x个对象的Property指针,从而实现信息泄露。

利用代码中为每个Object对象定义了4个属性,目的是泄露第4个属性的Property指针。我们来看一下重用后的相应内存:

上图第一个黄色高亮的是被某一个悬垂指针错误读取的Object VARIANT,可以看到由于Type域正好与第3个Property的hash重合,Type被解释为了5,当前VARIANT被解释为double类型,并且这个VARIANT内的Value值恰好为指向第4个Property的next指针。青色高亮的是第4个属性的Property结构。红色高亮的是第4个属性的的name。

遍历之前保存的悬垂指针,通过以下代码判断是否到找到一个上述这种VARIANT:

找到后,读取对应的Value并将其转换为32位整数,这样就泄露得到了一个Property指针。

泄露第4个属性的Property指针后,利用代码紧接着用类似的方法泄露该属性的值,此时需要读取泄露的Property指针首部对应的VARIANT,方法是重新设计第一个属性的名称,并用其进行内存布局,随后再次借助之前的方法来进行读取,如下:

上图中青色高亮的是第二轮UAF后,某个被重用的0x970内存块中的第一个Property,黄色和灰色高亮的是依次排列的、用来布局的VARIANT,每个VARIANT的Type为0x80,为间接引用,需要解一次指针。

从上图可以看到,解一次指针后,读取的VARIANT即为整型,里面存储了所需的leak_offset值,这里为1。

从上图可以看到,第一轮UAF时伪造的VARIANT还在,07c72ee0即为第一次信息泄露得到的指针。

有了leak_offset后,就可以对overlay_backup[leak_offset]存储的单个对象进行释放和重用,利用代码在此基础上封装了一个rewrite函数,rewrite函数只对overlay_backup[leak_offset]所代表的对象进行UAF来重用内存:

利用代码接着借助rewrite函数,将第一次泄露指针的那块0x970大小内存重新进行内存布局。这次对重占位对象的第4个属性的name进行了改写,使其变为一个VARIANT。并且借助前面的思路,将leak_lower+0x40这个地址构造为Type为0x80的VARIANT,通过第一个属性的name进行大量内存布局,接着利用和之前一样的方法读取第4个Property的name处存储的VARIANT,保存到fakeobj_var:

这样,当后面通过rewrite将第4个Property的name改写为具有其他Type的VARIANT或者具有不同Value值的VARIANT时,就可以通过fakeobj_var来进行相应操作。

随后,利用代码封装了一个read_pointer函数,里面借助rewrite将第4个Property的name改写为一个字符串类型(Type=8)的VARIANT:

此时fakeobj_var就代表一个字符串类型的VARIANT,字符串类型的VARIANT的Value值存储的是一个BSTR对象的字符串首地址,即一个BSTR结构偏移+0x08的地方.

而64位下BSTR的完整结构如下:

所以可以借助BSTR的length域来读取任意地址。

接着看一下利用代码封装的read_byte函数:

这里任意地址读取的思路是这样的:

调用 string.length 方法读取 BSTR 字符串长度时,会进入 jscript!StringProxyObj::Length 函数,在函数内部会取出 BSTR 的长度,然后除以2:

如果我们对待读数据先乘以2,相当于右移1位,数据缺失1 bit,读出来之后无法还原。

所以这里用了一些技巧,以 read_byte 为例,为了准确读取数据,代码中采用的办法是要读取 addr,则将 addr + 2 传入,此时待读取的 byte 左移了 16 位,随后 jscript!StringProxyObj::Length 内部为了除以2,帮我们右移了 1 位,所以数据返回后还得手动右移 15 位,最后取出最低的一个字节即可。read_word、read_dword、read_qword 同理。

封装完任意地址读取函数后,利用代码又在其基础上封装了一个任意对象地址读取函数addrof:

addrof借助两次read_dword实现对象地址泄露。

第一次read_dword将任意对象赋值给第4个属性,这样会导致一个引用对象(Type=0x80)的VARIANT被写到第4个Property的var内(参考下面的日志,可以看到保存到第4个Property处的VARIANT类型为0x80),并将第4个Property的name改写为一个字符串型(Type=8)的VARIANT,VARIANT的值设置为leak_lower + 8。这样就把被引用的VARIANT地址给读取出来,即下面日志中的08a9ece8。

第二次read_dword针对第一次read_dword读取的值再解一次引用,从而将真正的对象地址读取出来。

封装完上述这些功能函数后,接下来的操作就比较常规了:new一个object,泄露这个对象的首地址,从首地址中读取虚表指针,通过虚表指针获取jscript基址。紧接着从jscript的导入表中获取msvcrt和kernel32的基址,再从msvcrt的导入表中获取ntdll的基址,随后从kernel32的导出表获得WinExec函数地址,从ntdll的导出表中获取NtContinue函数地址,供后面使用。

由于后面借助NtContinue函数进行代码执行时,需要为伪造的_CONTEXT结构提供一个正确的Native栈地址,所以这里还要泄露一个Native栈地址,操作比较常规:

64位基本知识点如下,这里不再过多说明:

利用代码接下来伪造jscript!NameTbl对象和jscript!NameTbl对象的虚表,并将虚表内的第28项(此项原先为 jscript!ObjEvtHandler::FPersist函数地址)改写为ntdll!NtContinue函数的地址。

trigger_exec函数首部,利用代码将第4个Property的name伪造为一个Type=0x81的对象,将Value设为伪造的jscript!NameTbl对象,并将对象的虚表指针(对象的第一个8字节)设为伪造的虚表。

trigger_exec函数最后对fakeobj_var调用typeof函数,触发虚函数调用,劫持控制流到NtContinue,并将伪造的对象作为参数传入rcx:

弹出计算器,实现代码执行~

这个漏洞到这里就分析完了,关于这个漏洞笔者有如下思考:

这个漏洞的利用代码直接给出了将一个jscript的此类UAF漏洞转化为RCE的能力,换个角度思考,jscript模块中之前出现的那些类似的UAF漏洞,都可以通过这种方法实现RCE,而这些漏洞之前被人关注得并不多。

纵观这个jscript漏洞的分析过程,由于此利用是国外安全研究员独立编写,所以与最初的在野0day利用代码并不一致,但是这份代码更具阅读性,并且在利用过程上和前几年被广泛讨论的vbscript漏洞异曲同工,都是借助伪造或改写VARIANT的Type来实现类型混淆。

https://github.com/maxpl0it/CVE-2020-0674-Exploit

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0674

Garbage Collection Internals of JScript

CVE-2018-8353漏洞分析笔记

CVE-2017-11906 && CVE-2017-11907 组合漏洞分析笔记

利用WPAD/PAC与JScript实现Windows 10远程代码执行

aPAColypse now: Exploiting Windows 10 in a Local Network with WPAD/PAC and JScript

 
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-8-5 15:17 被银雁冰编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (8)
雪    币: 38
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢lz分享。双星还是来了  啥时候有另一个ff的呢? 哈哈
2020-5-14 14:05
0
雪    币: 29187
活跃值: (63432)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
3
文章写的很工整,有理有据
2020-5-15 10:23
0
雪    币: 0
活跃值: (34)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
自己试着写 x86版本的 exp时,到 rewrite()函数时,GC会 crash,它把之前伪造的 0x80对象中的 type(0x80)当作对象 this指针了。脑壳痛
2020-5-27 17:29
0
雪    币: 261
活跃值: (64)
能力值: ( LV7,RANK:111 )
在线值:
发帖
回帖
粉丝
5
WooodsHole 自己试着写 x86版本的 exp时,到 rewrite()函数时,GC会 crash,它把之前伪造的 0x80对象中的 type(0x80)当作对象 this指针了。脑壳痛[em_36]
我在写x86也遇到同样的问题了,感觉要逆jscript.dll看看x86到底咋不一样的...
2020-6-8 21:08
0
雪    币: 3475
活跃值: (7764)
能力值: ( LV4,RANK:41 )
在线值:
发帖
回帖
粉丝
6
权哥牛逼
2020-6-9 18:15
0
雪    币: 192
活跃值: (136)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7

32位下GC崩溃的原因:对total执行Scavenge导致的。具体会分成两步:

1. CIndexedNameList::ScavengeRoots,这个函数会将GcBlcok中的VAR执行unmark(&F7FF)的操作,因为total中指向的VAR是被我们重占用的,这个unmark的操作就修改了第二个Vval的next域;

2. NameList::ScavengeRoots,这个函数会通过Vval的next域进行遍历,next域被修改了(&F7FF),这刚好指向了一段0x80的VAR上了(开启了LFH,能命中喷射的0x80),然后就是访问地址0x80造成崩溃

2020-8-18 11:54
0
雪    币: 261
活跃值: (64)
能力值: ( LV7,RANK:111 )
在线值:
发帖
回帖
粉丝
8
ty1337 32位下GC崩溃的原因:对total执行Scavenge导致的。具体会分成两步:1. CIndexedNameList::ScavengeRoots,这个函数会将GcBlcok中的VAR执行unmar ...
看到文章循迹找来,哈哈!
2020-8-21 19:17
0
雪    币: 192
活跃值: (136)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
ReeHeiHei 看到文章循迹找来,哈哈!
嗯,学习的同时赚点稿费,大佬可以去看看CVE-2019-1367也是一样的洞,但实现了任意地址写,https://blog.confiant.com/internet-explorer-cve-2019-1367-exploitation-part-2-8143242b5780?gi=7897ffc317f0
2020-8-21 23:33
0
游客
登录 | 注册 方可回帖
返回
//