成因:EQNEDT32.exe
在解析Matrix record时,并未检查长度,从而造成栈溢出。无论打不打CVE-2017-11882补丁都可以成功触发,使得攻击者可以通过刻意构造的数据内容及长度覆盖栈上的函数返回地址,从而劫持程序流程。
影响版本:Microsoft Office 2007, Microsoft Office 2010, Microsoft Office 2013,Microsoft Office 2016
POC:CVE-2018-0798
笔者复现及分析环境:Windows 7 Service Pack 1、Microsoft Office 2010、x64Dbg、IDA 7.0(EQNEDT32.exe已打CVE-2017-11882补丁,但笔者分析时关闭了ASLR)
漏洞位于sub_443E34
内:
其调用了两次sub_443F6C
,但sub_443F6C
在复制数据时并未检查传递进来的参数:
其中的数据长度可通过a1
控制,具体计算方法是(2 * a1 + 9) >> 3
,而其目的地址是由sub_443E34
传递过来位于其开辟栈空间内的局部变量(int
型):
如此一来,便可通过精心构造的数据,覆盖sub_443E34
函数的返回地址,进而控制执行流。
POC地址已于上文给出。直接于sub_443E34
处设断,成功断下后,直接执行到调用sub_443F6C
前查看其传递参数:
跟进查看,可以看到其计算后的实际复制数据长度:
跟进其调用的sub_416352
可以查看要复制数据:
直接执行到sub_443F6C
结束处,可以看到:
sub_443E34
再次调用sub_443F6C
,其执行流程同上:
可以看到,已经覆盖栈上sub_443E34
的返回地址,劫持了执行流:
回到sub_443E34
,直接执行到结束处:
通过ROP跳转到WinExec()
:
成功弹出计算器:
样本名称:Urgent Action.docx
样本MD5:02C2A68CE9A35F5F0E1B3456E09D6CC9
通过远程模板注入的方式下载一RTF格式文档:
使用WinHex查看,确为RTF格式:
添加.rtf
后缀后打开文档。直接来到sub_443E34
调用sub_443F6C
处:
此次调用sub_443F6C
并未发生溢出,其复制数据长度如下:
复制内容:
其第二次调用sub_443F6C
,发生溢出:
复制内容:
接下来直接执行到sub_443E34
结束处,可以看到其劫持执行流:
通过ROP跳转到Shellcode:
Shellcode如下:
下面开始分析其功能。首先是计算跳转地址:
跳转之后,通过与计算出的EAX值比较的方式移动指针指向要复制的Shellcode,复制后跳转到Shellcode上执行:
通过PEB手动符号解析定位到kernel32.dll
:
定位kernel32.dll
中的GetProcAddress()
函数:
跳转后执行GetProcAddress()
:
之后通过call
调用的形式给CreateDirectory()
传递参数:
于C盘创建一名为Temp的文件夹。获取LoadLibrary()
调用地址:
之后在call
调用的同时传递参数:
接着再次call
调用,先修正内存中的字符串,接着获取URLDownloadToFile()
函数调用地址:
通过两次call
调用来给URLDownloadToFile()
函数传递参数:
之后调用URLDownloadToFile()
函数从http://maq[.]com[.]pk/wehs下载文件到创建的Temp文件夹内,文件名为smss
:
call
调用的同时向GetProcAddress()
传递参数,获取MoveFile()
调用地址:
两处call
调用向MoveFile()
传递参数:
将smss
重新命名为smss.exe
。之后获取LoadLibrary()
调用地址:
call
调用的同时传递参数:
获取ShellExecute()
调用地址:
通过三次call
调用来给ShellExecute()
传递参数,最后调用之:
接下来执行的smss.exe
,非本文重点,故不分析。
写文章截图的时候中途断过两次,故前后文某些地址(使用这些地址只是为了方便说明)不对应,望读者谅解。
另,此样本在打了CVE-2017-11882补丁的机器上无法被成功利用。
直接定位到漏洞触发点:
第二次调用sub_443F6
过程如下:
可以看到,栈上函数返回地址已经被覆盖:
但其并未直接执行到sub_443E34
结束处,而是通过给其后调用的函数传参,再次执行sub_443E34
(其调用函数的具体功能可结合IDA进行分析):
下面来看第二次执行sub_443E34
时调用sub_443F6
的情况:
直接执行到sub_443E34
结束处:
其后执行流程:
此处的jmp 2911D4
值得说明一下,2911D4
后20字节是在调用sub_4428F0
时由qmemcpy((void *)(v5 + 50), a4, 20u);
复制而来,其中源地址是0x18F3EC(可见图片54)。
计算接下来的跳转地址:
跳转到解密Shellcode部分:
解密Shellcode:
其后执行流程见下图(图中序号仅为表明顺序,并无他意):
手动符号解析定位msvcrt.dll
(由cmp
语句比较的ASCII码可计算出)。
手动符号解析定位kerner32.dll
(图中序号接上一张图片)
之后其调用的sub_299122
如下:
通过遍历msvcrt.dll
的输入表查找GetProcAddress
,它并非调用kernel32.dll
的GetProcAddress()
,而是ntdll.dll
的LdrGetProcedureAddress()
:
再一次调用sub_299122
,此次查找的是VirtualProtect()
:
调用GetProcAddress()
返回msvcrt.clearerr
的地址:
调用VirtualProtect()
修改msvcrt.clearerr
页属性为0x40(PAGE_EXECUTE_READWRITE),大小是0x50:
对msvcrt.clearerr
进行Inline Hook,修改指令长度为0x50,这解释了之前的修改页属性操作:
其实msvcrt.clearerr
要实现的功能与sub_6492C6
相同(详见图片77、78):
调用VirtualProtect()
修改msvcrt.clearerr
页属性为0x20(PAGE_EXECUTE_READ),大小是0x50。
将返回的调用地址加5后,通过遍历msvcrt.dll
的输入表查找CreateFile
:
此次是查找VirtualAlloc
:
接下来所查找函数不一一截图,依次是ReadFile
、WriteFile
、CloseHandle
、CreateProcess
、GetModuleFileName
、ResumeThread
、TerminateProcess
。
其后传递给GetProcess的参数不再一一截图,依次是ReadProcessMemory
、VirtualQueryEx
、VirtualProtectEx
、GetModuleHandle
、VirtualAllocEx
、WriteProcessMemory
、SetThreadContext
、ZwUnmapViewOfSection
。
调用GetTempPath()
:
之后将其于临时文件夹内释放的文件名拼接到路径后:
打开该文件:
其后行为不再一一截图,依次是GetFileSize
(获取该大小)、VirtualAlloc(0,0x3E000,0x3000,0x40)
(分配空间)、ReadFile
(读取文件到分配的空间内)。
解密内存中的文件内容:
遍历文件句柄,找到符合下列条件的文件:
调用VirtualAlloc
分配空间并写入内容(并非解密后的文件内容):
复制解密后的文件内容:
之后创建一同名进程:
其后的部分行为见下图:
将解密后的文件内容写入创建的进程:
最终,结束原进程:
解密后的文件会在临时目录释放两个文件("白加黑")并运行之: