_ _ (_) | | __ ____ __ _ _ _ _ __ ___ _ __ _ __ ___ | |_ \ \ / /\ \/ /| || | | || '_ ` _ \ | '_ \ | '_ \ / _ \| __| \ V / > < | || |_| || | | | | || |_) |_ | | | || __/| |_ \_/ /_/\_\| | \__,_||_| |_| |_|| .__/(_)|_| |_| \___| \__| _/ | | | |__/ |_| /---------------------------------------------------------------------------------------\ |>...................[ 通用防护漏洞CVE-2014-6332的设计思路 ]...................<| |>......................[ by nEINEI/vxjump.net ]......................<| |>......................[ 2014-12-24 ]......................<| \>...................... [ neineit_at_gmail.com ] ...................... On Error Resume Next myarray=Array(1) redim preserve myarray(&h0aaa3333) 开始程序定义一个动态数值myarray,然后重复申请myarray数组为一个很大的值,导致分配失败,但并没有修正 SAFEARRAY结构中的cElements值为原始的值,导致pvData指向的内存并没分配那么大的空间,这将导致 越界访问。动态数据的内部定义格式是 typedef struct tagSAFEARRAY { USHORT cDims; USHORT fFeatures; ULONG cbElements; ULONG cLocks; PVOID pvData; SAFEARRAYBOUND rgsabound[1]; } SAFEARRAY, *LPSAFEARRAY; 发生漏洞的函数是在Oleaut32.dll的 HRESULT __stdcall SafeArrayRedim(SAFEARRAY *psa, SAFEARRAYBOUND *psaboundNew) { // 该函数比较前后分配内存的大小的差异,如果分配成功,返回给调用新的数据结构 SAFEARRAY *point_psa; // esi@1 USHORT fFeatures; // cx@3 __int32 v4; // eax@6 size_t v5; // ebx@6 ULONG ppsa_CElements; // ebx@9 LONG v7; // edi@9 int Old_Size; // eax@9 struct IMalloc *v9; // edi@11 void *v10; // eax@14 int v11; // eax@17 void *v13; // eax@37 ULONG v_ppsa_CElements; // [sp+Ch] [bp-18h]@9 LONG v15; // [sp+10h] [bp-14h]@9 struct IMalloc *v16; // [sp+14h] [bp-10h]@6 int align_fFeatures; // [sp+18h] [bp-Ch]@3 int v18; // [sp+1Ch] [bp-8h]@9 size_t New_Size; // [sp+20h] [bp-4h]@7 SAFEARRAY *psaa; // [sp+2Ch] [bp+8h]@6 SAFEARRAYBOUND *psaboundNewa; // [sp+30h] [bp+Ch]@37 point_psa = psa; if ( psa ) { if ( psaboundNew ) { fFeatures = psa->fFeatures; align_fFeatures = psa->fFeatures & 0x2000; if ( psa->cDims ) { if ( psa->cLocks > 0 || fFeatures & 0x10 ) return 0x8002000D; psaa = 0; v16 = 0; v4 = GetMalloc(&v16); v5 = v4; if ( v4 && v4 < 0 ) return v5; New_Size = SafeArraySize(point_psa); if ( !New_Size || point_psa->pvData ) { ppsa_CElements = point_psa->rgsabound[0].cElements; v7 = point_psa->rgsabound[0].lLbound; point_psa->rgsabound[0] = *psaboundNew; v_ppsa_CElements = ppsa_CElements; v15 = v7; Old_Size = SafeArraySize(point_psa); v18 = Old_Size; if ( Old_Size == -1 ) { point_psa->rgsabound[0].cElements = ppsa_CElements; point_psa->rgsabound[0].lLbound = v7; v5 = 0x8007000E; // 注意这里,分配内存如果失败,指向到了最后,未修正相关数据字段。 } else { v5 = Old_Size - New_Size; if ( Old_Size != New_Size ) { v9 = v16; if ( (v5 & 0x80000000) != 0 && point_psa->fFeatures & 0xF20 ) { if ( align_fFeatures ) { psaa = (SAFEARRAY *)((char *)point_psa->pvData + Old_Size); } else { v10 = v16->lpVtbl->Alloc(v16, -v5); psaa = (SAFEARRAY *)v10; if ( !v10 ) goto LABEL_31; memcpy(v10, (char *)point_psa->pvData + v18, -v5); Old_Size = v18; } } if ( align_fFeatures ) { if ( Old_Size <= New_Size ) goto LABEL_19; v13 = v9->lpVtbl->Alloc(v9, Old_Size); psaboundNewa = (SAFEARRAYBOUND *)v13; if ( v13 ) { memcpy(v13, point_psa->pvData, New_Size); point_psa->pvData = psaboundNewa; point_psa->fFeatures &= 0xDFFFu; goto LABEL_19; } } else { v11 = (int)v9->lpVtbl->Realloc(v9, point_psa->pvData, Old_Size); if ( v11 ) { LABEL_18: point_psa->pvData = (PVOID)v11; LABEL_19: if ( (v5 & 0x80000000) == 0 ) { memset((char *)point_psa->pvData + New_Size, 0, v5); } else { if ( psaa ) ReleaseResources(point_psa, (VARIANTARG *)psaa, -v5, point_psa->fFeatures, point_psa->cbElements); if ( align_fFeatures ) psaa = 0; } v5 = 0; goto LABEL_24; } if ( !v18 ) { v11 = (int)v9->lpVtbl->Alloc(v9, 0); goto LABEL_18; } } point_psa->rgsabound[0].cElements = v_ppsa_CElements; point_psa->rgsabound[0].lLbound = v15; LABEL_31: v5 = 0x8007000E; LABEL_24: if ( psaa ) v9->lpVtbl->Free(v9, psaa); return v5; } } return v5; } } } } return 0x80070057; } 补丁后的这个函数: if ( v7 == -1 || (psab_size < (unsigned int)v7 ? (v16 = v7 - psab_size) : (v16 = psab_size - v7), ULongToLong(v16, &v23)) ) { psaa = 0x8007000E; goto LABEL_51; } ... LABEL_12: if ( !psaa ) return psaa; v6 = v20; LABEL_51: *(_DWORD *)(point_psa + 20) = v6; *(_DWORD *)(point_psa + 16) = v19; return psaa; //修正point_psa结构里面各个字段,不会因为分配大内存失败后,留下很大整数,导致越界范围 } } } } [0x03] .防护设计 思路1 : 是从漏洞根本着手发生漏洞的函数是SafeArrayRedim,那么我们可以hook这个函数来进行几点重要的判断: a)判断是否是漏洞版的模块,如果是HOOK SafeArrayRedim。 b)过滤掉大部分SafeArrayRedim的请求,当存在符合漏洞利用的请求时, ULONG uOldElements = 0; if (psa && psa->rgsabound) { //保留开始的cElements数据 ULONG uOldElements = psa->rgsabound[0].cElements; } //调用真实的SafeArrayRedim函数 result = TrueSafeArrayRedim(psa,psaboundNew); //进行判断 f (uOldElements < 0x8000000) { if(psaboundNew) { if (psa->rgsabound[0].cElements & 0x8000000 && result != S_OK) { printf("found potential attacking!\n"); } } } 优点:代码简单稳定有效。 缺点:存在一定的效率上的差异,需要根据需求优化判断条件,尽量让绝大多数调用请求转向真实的SafeArrayRedim。 //------------------------------------------------------------------------------------------------------------------------ 思路2:从防护SafeMode入手,因为防御这类攻击,我是指最终造成任意代码可读/写,并且预测到SafeMode后可以进行关闭 的利用技术。那么我们需要防护这类攻击。要求条件是在攻击者调用某个关键函数时,我们来检测是否已经关闭了SafeMode. 0:014> u vbscript!VbsCreateObject //利用漏洞SafeMode已经清空0,攻击者会调用CreateObject完成payloads操作。 vbscript!VbsCreateObject: 6f060e97 8bff mov edi,edi 6f060e99 55 push ebp 6f060e9a 8bec mov ebp,esp 6f060e9c 56 push esi 6f060e9d 57 push edi 6f060e9e e86ebfffff call vbscript!CScriptRuntime::GetCurrentOleScript (6f05ce11) 6f060ea3 33f6 xor esi,esi 6f060ea5 85c0 test eax,eax 执行的最终路径 VbsCreateObject ----> CreateObject2 ---> GetObjectFromProgID --- > InSafeMode (这里已经是0,不受IE安全保护执行任意脚本了) unsigned int __stdcall VbsCreateObject(int a1, int a2, int a3){ v6 = VbsCreateObject2(a1, a2, a3); } unsigned int __userpurge VbsCreateObject2(int a1, int a2, int a3){ v5 = TlsGetValue(g_luTls); if ( v5 ){ v6 = *((_DWORD *)v5 + 3); v8 = GetObjectFromProgID(v7, 0, v6, v4, 0, 0); } HRESULT __userpurge GetObjectFromProgID(LPCOLESTR lpszProgID, int a2, int a3, int a4, int a5, DWORD a6){ LOBYTE(v18) = COleScript::InSafeMode(a3); eax=00000001 ebx=00000000 ecx=016fed18 edx=0452c614 esi=00000000 edi=00000000 eip=6bb9ce4d esp=0452c664 ebp=0452c6ec 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!COleScript::InSafeMode: //此时SafeMode已经被修改为0了,我们可以以此为判断。 6bb9ce4d f781740100000b000000 test dword ptr [ecx+174h],0Bh ds:0023:016fee8c=00000000 (SafeMode) 6bb9ce57 6a00 push 0 6bb9ce59 58 pop eax 6bb9ce5a 0f95c0 setne al } 所以,我们可以hook Vbscript.dll模块当中的GetObjectFromProgID , 漏洞利用成功后清空SafeMode为0,那么此时的[esp+4] == ecx (即InSafeMode ecx) 而[ECX+174h]的内容就是SafeMode的值。这个方式可以拦截所有抹掉SafeMode在利用VBscript的攻击。 优点:根本上拦截调用对SafeMode的攻击技术 缺点:GetObjectFromProgID不是一个标准的导出函数,HOOK上需要有些麻烦,可以基于COM hook,或基于offset hook. //------------------------------------------------------------------------------------------------------------------------ 思路3:对应不方便对GetObjectFromProgID进行hook的产品,我们还可以转换思路,由被动寻找SafeMode,变为 对SafeMode地址的主动预测和判断。 首先在VBScript中,SafeMode存在于ColeScript对象当中。问题转换为如何知道ColeScript对象的地址? ColeScript对象由下面代码创建, vbscript!CVBScriptClassFactory::CreateInstance: 6e4fb060 8bff mov edi,edi 6e4fb062 55 push ebp 6e4fb063 8bec mov ebp,esp 6e4fb065 57 push edi 6e4fb066 8b7d14 mov edi,dword ptr [ebp+14h] 6e4fb069 85ff test edi,edi 6e4fb06b 0f84708b0200 je vbscript!CVBScriptClassFactory::CreateInstance+0xd (6e523be1) 6e4fb071 832700 and dword ptr [edi],0 6e4fb074 837d0c00 cmp dword ptr [ebp+0Ch],0 6e4fb078 0f856d8b0200 jne vbscript!CVBScriptClassFactory::CreateInstance+0x1d (6e523beb) 6e4fb078 0f856d8b0200 jne vbscript!CVBScriptClassFactory::CreateInstance+0x1d (6e523beb) 6e4fb07e 56 push esi 6e4fb07f 6800020000 push 200h 6e4fb084 e81190ffff call vbscript!operator new (6e4f409a)//分配堆内存给COleScript对象 … 6e4fb09a ff5014 call dword ptr [eax+14h] 6e4fb09d 50 push eax 6e4fb09e 8bce mov ecx,esi //调用构造函数,SafeMode此时初始为一个非0值 6e4fb0a0 e8c1020000 call vbscript!COleScript::COleScript (6e4fb366) [ecx ->heap 02564aa8] 6e4fb0a5 8bf0 mov esi,eax eax=6e4fb7e8 ebx=00000000 ecx=02564aa8 edx=00287c28 esi=02564aa8 edi=04dccba0 eip=6e4fb0a0 esp=04dccb5c ebp=04dccb6c 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!CVBScriptClassFactory::CreateInstance+0x46: 6e4fb0a0 e8c1020000 call vbscript!COleScript::COleScript (6e4fb366) 0:016> !heap -p -a 2564aa8 address 02564aa8 found in _HEAP @ 280000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 02564aa0 0041 0000 [00] 02564aa8 00200 - (busy) COleScript对象使用CRT私有堆,我们可以看到 msvcrt!malloc+0x5: // the 00280000 is CRT private heap address. 6ff59cf3 833d4c00ff6f00 cmp dword ptr [msvcrt!_crtheap (6fff004c)],0 0:016> dd 6fff004c 6fff004c 00280000 00000000 6fff02c0 00000000 0:016> !heap Index Address Name Debugging options enabled 1: 002a0000 // default heap of process. 2: 00010000 3: 00280000 // the “ColeScript” will be allocated here. 4: 01a20000 5: 01c20000 6: 02760000 7: 02f40000 8: 02ae0000 9: 033f0000 10: 03650000 因为COleScript对象大小仅0x200,根据堆管理器的分配策略我们可以做出一些预测 //在创建ColeScript函数之前,我们先hook DllGetClassObject,然后先分配128个堆对象 for (i = 0 ; i < 128;i++){ //切记使用CRT堆句柄,否则我们分配的对象不会和COleScript对象在同一个堆管理器内 g_HeapArray[i] = HeapAlloc(hpCRT,0,0x200); printf("heap[%d] of process address is :0x%x\n",i+1,(DWORD)g_HeapArray[i]);) } //然后释放这128个堆对象 for (i = 0 ; i < 128;i++){ //释放后进入分配列表,当再有堆分配操作0x200时,刚好会使用我们曾经分配的这些堆地址 if (g_HeapArray[i]){ HeapFree(hp,0,g_HeapArray[i]); } } Heap[1] of process address is :0x280000 heap[2] of process address is :0x2553d88 heap[3] of process address is :0x25579c0 heap[4] of process address is :0x2557180 … Heap[126] of process address is :0x2564698 heap[127] of process address is :0x25648a0 heap[128] of process address is :0x2564aa8 // 一般情况下是128这个位置存放ColeScript对象地址 进而,vbscript!CVBScriptClassFactory::CreateInstance会创建COleScrit对象,刚好会使用我们曾经 分配过的堆对象。 这样,我们就把可以变地址的ColeScript SafeMode标志转换为一个固定地址下判断问题。 我们记录下g_HeapArray所以的堆内存地址,在SafeMode被清空0后,我们hook 某些函数,在这些函数里面 判断g_HeapArray[128] 里面的内容是否是0即可。当然也可能由于一些分配策略导致不在128这个索引上, 那么我们继续搜索1~128这个范围内容即可。 优点:根本上拦截调用对SafeMode的攻击技术 缺点:受内存堆管理的影响,可能导致地址预测失败 针对Javascript的SafeMode拦截相对容易,故不再讨论。 [0x04] .其它 CVE-2014-6332漏洞利用技巧是一个高超的技术实现,融入很多奇思妙想。虽然对防御者来说并不需要太多的 代码来实现保护,但显然攻击技术已经在更高的层面上展开,而防御技术显然还是在“补墙”,既便我们有多种 思路的防护手法。