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
被银雁冰编辑
,原因: