_ _
(_) | |
__ ____ __ _ _ _ _ __ ___ _ __ _ __ ___ | |_
\ \ / /\ \/ /| || | | || '_ ` _ \ | '_ \ | '_ \ / _ \| __|
\ V / > < | || |_| || | | | | || |_) |_ | | | || __/| |_
\_/ /_/\_\| | \__,_||_| |_| |_|| .__/(_)|_| |_| \___| \__|
_/ | | |
|__/ |_|
/---------------------------------------------------------------------------------------\
|>...................[ 通用防护漏洞CVE-2014-6332的设计思路 ]...................<|
|>......................[ by nEINEI/vxjump.net ]......................<|
|>......................[ 2014-12-24 ]......................<|
\>...................... [ neineit_at_gmail.com ] ......................
[目录]
[0x01] .简介
[0x02] .基本信息
[0x03] .防护设计
[0x04] .其它
[0x01] .简介
漏洞CVE-2014-6332是整个2014年最神奇的2个漏洞之一,另外一个是CVE-2014-4114沙虫漏洞。
关于这个漏洞的分析已经有很多文章了,请参考:
http://technet.microsoft.com/security/bulletin/MS14-064
https://securityintelligence.com/ibm-x-force-researcher-finds-significant-vulnerability-
in-microsoft-windows/#.VGsLdPmUcWY
http://xteam.baidu.com/?p=104
http://blog.vulnhunt.com/index.php/2014/11/18/about_cve-2014-6332/
简单来说就是一个Windows OLE 中的漏洞可能允许远程执行代码,这是个整数溢出漏洞,
我们着重讨论的是如何设计通用的防护手段,防止CVE-2014-6332的利用。
[0x02] .基本信息
这个漏洞发生的模块是在oleaut32.dll,且影响十分广泛包括:
windows XP/Win Server2003/ VISTA /Win Server2008/Win7/Windows server2008R2/WIN8
+ WIN8.1 +RT8.1/WIN Server2012、R2.
这里我们以Windows7 为例讨论这个问题,其它平台类似。
触发漏洞的最后一个版本:
Image name: oleaut32.dll
Browse all global symbols functions data
Timestamp: Tue Jan 07 15:43:26 2014 (52CC911E)
CheckSum: 0008ACDC
ImageSize: 00087000
File version: 6.3.9600.16506
Product version: 6.3.9600.16506
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 2.0 Dll
File date: 00000000.00000000
Translations: 0409.04e4
CompanyName: Microsoft Corporation
InternalName: OLEAUT32.DLL
ProductVersion: 6.3.9600.16506
FileVersion: 6.3.9600.16506
首先明确一个讨论前容易混淆的地方,
漏洞CVE-2014-6332是否等于DVE(数据虚拟执行)技术,是否是GodMode方式攻击利用?
CVE-2014-6332是个整数溢出漏洞和具体技术无关
DVE技术是有yuange提出的一个利用思路,主要是讲不局限于二进制角度看待漏洞利用技术,对更高层面,
维度的输入数据进行利用,这些数据可以控制代码执行,打破原有条件分支,执行流程并最终获得任意代
码执行的过程。这主要是操作系统针对漏洞防护都发生在二进制层面,DEP +ASLR,甚至是EMET的mitigation技
术等等都是在这个层面,如果攻击者的操作过程发生在上一个层面如JS,VB脚本层面,那么这些系统级的所
有防护将不具有任何作用。在这之前yuange也提出CPU SEH,这不是硬件层面的SHE概念,我也不确定其要表
达的确切含义,但从CVE-2014-6332的PoC中我们可以看到如何通过构造的数据使程序走入漏洞利用理想情况
下的分支,这些异常的分支,执行的代码都是”人为操纵的SEH“的表现形式,yuange所谓的”安全就是个条件句“
GodMode攻击方式是安全系统的存在一个防护开关SafeMode,它隐藏在被攻击系统的某个模块当中,如果攻击者
可以在获得少量控制权前找到这个开关标志,并且关闭它,那么就可以绕过了所有这些安全缓解手段了。公开
谈论利用这个技术的人是yuange,tombkeeper,James Forshaw(Google Project Zero成员),guhe。
最后,由yuange提供的PoC代码中综合的利用DVE,GodMode思路,使得这个漏洞利用成为影响范围最广
(IE5~IE11),最稳定(不需要heapspary,info leak ,ROP...)的漏洞利用技术。
先看一个PoC,运行这个页面代码将Crash,
开始程序定义一个动态数值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漏洞利用技巧是一个高超的技术实现,融入很多奇思妙想。虽然对防御者来说并不需要太多的
代码来实现保护,但显然攻击技术已经在更高的层面上展开,而防御技术显然还是在“补墙”,既便我们有多种
思路的防护手法。