CVE-2018-0798及利用样本分析
1.本篇文章系Gcow安全团队复眼小组的ERFZE师傅原创,未经许可禁止转载.2.本篇文章从CVE-2018-0798漏洞的基础分析入手,分析了其漏洞分成因.以及演示了蔓灵花APT组织以及某样本组织在实际攻击活动之中所使用的样本的超级详细的分析,十分值得样本分析人员以及刚刚接触本漏洞的分析人员学习.3.本篇文章大约2700多字,103张图片,预计阅读时间9分钟.
0x01 漏洞描述
•成因:EQNEDT32.exe
在解析Matrix record时,并未检查长度,从而造成栈溢出。无论打不打CVE-2017-11882补丁都可以成功触发,使得攻击者可以通过刻意构造的数据内容及长度覆盖栈上的函数返回地址,从而劫持程序流程。•影响版本:Microsoft Office 2007, Microsoft Office 2010, Microsoft Office 2013,Microsoft Office 2016•POC:CVE-2018-0798—— https://github.com/houjingyi233/office-exploit-case-study/blob/master/CVE-2017-11882%26CVE-2018-0802%26CVE-2018-0798/cve-2018-0802%20poc%20with%20comments.rtf
0x02 漏洞分析
笔者复现及分析环境:Windows 7 Service Pack 1、Microsoft Office 2010、x64Dbg、IDA 7.0(EQNEDT32.exe已打CVE-2017-11882补丁,但笔者分析时关闭了ASLR)
0x02.1 静态分析
漏洞位于sub_443E34
内:
图片1 sub_443E34
其调用了两次sub_443F6C
,但sub_443F6C
在复制数据时并未检查传递进来的参数:
图片2 sub_443F6C
其中的数据长度可通过a1
控制,具体计算方法是(2 * a1 + 9) >> 3
,而其目的地址是由sub_443E34
传递过来位于其开辟栈空间内的局部变量(int
型):
图片3 栈
如此一来,便可通过精心构造的数据,覆盖sub_443E34
函数的返回地址,进而控制执行流。
0x02.2 动态调试
POC地址已于上文给出。直接于sub_443E34
处设断,成功断下后,直接执行到调用sub_443F6C
前查看其传递参数:
图片4 传递参数
跟进查看,可以看到其计算后的实际复制数据长度:
图片5 计算后的复制长度
跟进其调用的sub_416352
可以查看要复制数据:
图片6 要复制数据
直接执行到sub_443F6C
结束处,可以看到:
图片7 已覆盖栈上的数据
sub_443E34
再次调用sub_443F6C
,其执行流程同上:
图片8 传递参数
图片9 计算后的复制长度
图片10 要复制数据
可以看到,已经覆盖栈上sub_443E34
的返回地址,劫持了执行流:
图片11 已覆盖栈上的数据
回到sub_443E34
,直接执行到结束处:
图片12 sub_443E34结束处
通过ROP跳转到WinExec()
:
图片13 ROP
成功弹出计算器:
图片14 calc
0x03 Bitter组织某样本分析
样本名称:Urgent Action.docx
样本MD5:02C2A68CE9A35F5F0E1B3456E09D6CC9
通过远程模板注入的方式下载一RTF格式文档:
图片15 远程URL
使用WinHex查看,确为RTF格式:
图片16 WinHex查看
添加.rtf
后缀后打开文档。直接来到sub_443E34
调用sub_443F6C
处:
图片17 调用sub_443F6C及传递参数
此次调用sub_443F6C
并未发生溢出,其复制数据长度如下:
图片18 复制数据长度
复制内容:
图片19 复制内容
其第二次调用sub_443F6C
,发生溢出:
图片20 复制数据长度
复制内容:
图片21 复制内容
接下来直接执行到sub_443E34
结束处,可以看到其劫持执行流:
图片22 sub_443E34结束处
通过ROP跳转到Shellcode:
图片23 ROP
Shellcode如下:
图片24 Shellcode
下面开始分析其功能。首先是计算跳转地址:
图片25 跳转
跳转之后,通过与计算出的EAX值比较的方式移动指针指向要复制的Shellcode,复制后跳转到Shellcode上执行:
图片26 复制Shellcode
通过PEB手动符号解析定位到kernel32.dll
:
图片27 定位kernel32.dll
定位kernel32.dll
中的GetProcAddress()
函数:
图片28 寻址GetProcAddress()
跳转后执行GetProcAddress()
:
图片29 call调用
图片30 获取CreateDirectory调用地址
之后通过call
调用的形式给CreateDirectory()
传递参数:
图片31 调用CreateDirectory
于C盘创建一名为Temp的文件夹。获取LoadLibrary()
调用地址:
图片32 获取LoadLibrary调用地址
之后在call
调用的同时传递参数:
图片33 call调用并传递参数
图片34 LoadLibrary(urlmon.dll)
接着再次call
调用,先修正内存中的字符串,接着获取URLDownloadToFile()
函数调用地址:
图片35 修正字符串
图片36 获取URLDownloadToFile()函数调用地址
通过两次call
调用来给URLDownloadToFile()
函数传递参数:
图片37 传递参数
图片38 未修正的参数
图片39 修正参数
之后调用URLDownloadToFile()
函数从http://maq[.]com[.]pk/wehs下载文件到创建的Temp文件夹内,文件名为`smss`:
图片40 URLDownloadToFile
call
调用的同时向GetProcAddress()
传递参数,获取MoveFile()
调用地址:
图片41 获取MoveFile()调用地址
两处call
调用向MoveFile()
传递参数:
图片42 调用MoveFile
将smss
重新命名为smss.exe
。之后获取LoadLibrary()
调用地址:
图片43 获取LoadLibrary调用地址
call
调用的同时传递参数:
图片44 LoadLibrary(shell32.dll)
获取ShellExecute()
调用地址:
图片45 获取ShellExecute()调用地址
通过三次call
调用来给ShellExecute()
传递参数,最后调用之:
图片46 调用ShellExecute
接下来执行的smss.exe
,非本文重点,故不分析。
0x04 xx组织某样本分析
写文章截图的时候中途断过两次,故前后文某些地址(使用这些地址只是为了方便说明)不对应,望读者谅解。另,此样本在打了CVE-2017-11882补丁的机器上无法被成功利用。
直接定位到漏洞触发点:
图片47 第一次调用sub_443F6
图片48 复制数据长度
图片49 复制内容
第二次调用sub_443F6
过程如下:
图片50 第二次调用sub_443F6
图片51 复制数据长度
图片52 复制内容
可以看到,栈上函数返回地址已经被覆盖:
图片53 已覆盖栈上的数据
但其并未直接执行到sub_443E34
结束处,而是通过给其后调用的函数传参,再次执行sub_443E34
(其调用函数的具体功能可结合IDA进行分析):
图片54 调用sub_4428F0
图片55 调用sub_437C9D
图片56 调用sub_416352
图片57 调用sub_43A78F
图片58 再次执行sub_443E34
下面来看第二次执行sub_443E34
时调用sub_443F6
的情况:
图片59 第三次调用sub_443F6
图片60 复制数据长度
图片61 复制内容
图片62 第四次调用sub_443F6
图片63 复制数据长度
图片64 复制内容
直接执行到sub_443E34
结束处:
图片65 sub_443E34结束处
其后执行流程:
图片66 其后执行流程
此处的jmp 2911D4
值得说明一下,2911D4
后20字节是在调用sub_4428F0
时由qmemcpy((void *)(v5 + 50), a4, 20u);
复制而来,其中源地址是0x18F3EC(可见图片54)。
计算接下来的跳转地址:
图片67 计算跳转地址
跳转到解密Shellcode部分:
图片68 跳转到解密Shellcode
解密Shellcode:
图片69 解密Shellcode
其后执行流程见下图(图中序号仅为表明顺序,并无他意):
图片70 定位msvcrt.dll
手动符号解析定位msvcrt.dll
(由cmp
语句比较的ASCII码可计算出)。
图片71 定位kerner32.dll
手动符号解析定位kerner32.dll
(图中序号接上一张图片)
之后其调用的sub_299122
如下:
图片72 sub_299122
通过遍历msvcrt.dll
的输入表查找GetProcAddress
,它并非调用kernel32.dll
的GetProcAddress()
,而是ntdll.dll
的LdrGetProcedureAddress()
:
图片73 LdrGetProcedureAddress
再一次调用sub_299122
,此次查找的是VirtualProtect()
:
图片74 VirtualProtect()
图片75 其后执行流程
图片76 保存到栈中局部变量
调用GetProcAddress()
返回msvcrt.clearerr
的地址:
图片77 GetProcAddress
调用VirtualProtect()
修改msvcrt.clearerr
页属性为0x40(PAGE_EXECUTE_READWRITE),大小是0x50:
图片78 VirtualProtect
对msvcrt.clearerr
进行Inline Hook,修改指令长度为0x50,这解释了之前的修改页属性操作:
图片79 Inline Hook
其实msvcrt.clearerr
要实现的功能与sub_6492C6
相同(详见图片77、78):
图片80 VirtualProtect
调用VirtualProtect()
修改msvcrt.clearerr
页属性为0x20(PAGE_EXECUTE_READ),大小是0x50。
图片81 GetProcAddress(GetTempPath)
将返回的调用地址加5后,通过遍历msvcrt.dll
的输入表查找CreateFile
:
图片82 查找CreateFile
图片83 GetProcAddress(GetFileSize)
此次是查找VirtualAlloc
:
图片84 查找VirtualAlloc
接下来所查找函数不一一截图,依次是ReadFile
、WriteFile
、CloseHandle
、CreateProcess
、GetModuleFileName
、ResumeThread
、TerminateProcess
。
图片85 GetProcess(GetThreadContext)
其后传递给GetProcess的参数不再一一截图,依次是ReadProcessMemory
、VirtualQueryEx
、VirtualProtectEx
、GetModuleHandle
、VirtualAllocEx
、WriteProcessMemory
、SetThreadContext
、ZwUnmapViewOfSection
。
调用GetTempPath()
:
图片86 GetTempPath
之后将其于临时文件夹内释放的文件名拼接到路径后:
图片87 拼接路径
打开该文件:
图片88 CreateFile
其后行为不再一一截图,依次是GetFileSize
(获取该大小)、VirtualAlloc(0,0x3E000,0x3000,0x40)
(分配空间)、ReadFile
(读取文件到分配的空间内)。
解密内存中的文件内容:
图片89 解密
遍历文件句柄,找到符合下列条件的文件:
图片90 遍历
图片91 遍历结果
调用VirtualAlloc
分配空间并写入内容(并非解密后的文件内容):
图片92 分配空间
图片93 WriteFile
图片94 CloseHandle
复制解密后的文件内容:
图片95 复制文件内容
之后创建一同名进程:
图片96 创建进程
图片97 创建完成
其后的部分行为见下图:
图片98 GetThreadContext
图片99 ReadProcessMemory
图片100 VirtualQueryEx
将解密后的文件内容写入创建的进程:
图片101 写入到创建的进程
最终,结束原进程:
图片102 结束原来的进程
解密后的文件会在临时目录释放两个文件[白加黑]并运行白exe以执行下一步的恶意行为:
图片103 释放文件
0x05 参考链接
•手把手教你复现office公式编辑器内的第三个漏洞——https://www.anquanke.com/post/id/94841