首页
社区
课程
招聘
[原创] CVE-2018-0798及利用样本分析
发表于: 2020-4-10 13:12 5909

[原创] CVE-2018-0798及利用样本分析

erfze 活跃值
12
2020-4-10 13:12
5909

成因EQNEDT32.exe在解析Matrix record时,并未检查长度,从而造成栈溢出。无论打不打CVE-2017-11882补丁都可以成功触发,使得攻击者可以通过刻意构造的数据内容及长度覆盖栈上的函数返回地址,从而劫持程序流程。

影响版本:Microsoft Office 2007, Microsoft Office 2010, Microsoft Office 2013,Microsoft Office 2016

POCCVE-2018-0798

笔者复现及分析环境:Windows 7 Service Pack 1、Microsoft Office 2010、x64Dbg、IDA 7.0(EQNEDT32.exe已打CVE-2017-11882补丁,但笔者分析时关闭了ASLR)

漏洞位于sub_443E34内:

图片1 sub_443E34

其调用了两次sub_443F6C,但sub_443F6C在复制数据时并未检查传递进来的参数:

图片2 sub_443F6C

其中的数据长度可通过a1控制,具体计算方法是(2 * a1 + 9) >> 3,而其目的地址是由sub_443E34传递过来位于其开辟栈空间内的局部变量(int型):

图片3 栈

如此一来,便可通过精心构造的数据,覆盖sub_443E34函数的返回地址,进而控制执行流。

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

样本名称: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()函数:

跳转后执行GetProcAddress()

图片29 call调用

图片30 获取CreateDirectory调用地址

之后通过call调用的形式给CreateDirectory()传递参数:

图片31 调用CreateDirectory

于C盘创建一名为Temp的文件夹。获取LoadLibrary()调用地址:

图片32 获取LoadLibrary调用地址

之后在call调用的同时传递参数:

图片33 call调用并传递参数

接着再次call调用,先修正内存中的字符串,接着获取URLDownloadToFile()函数调用地址:

图片35 修正字符串

通过两次call调用来给URLDownloadToFile()函数传递参数:

图片37 传递参数

图片38 未修正的参数

图片39 修正参数

之后调用URLDownloadToFile()函数从1a2K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3#2S2M7g2)9#2b7W2)9J5k6g2)9#2c8r3y4G2L8g2)9#2b7W2)9J5k6g2)9#2c8s2m8C8i4K6u0r3N6$3g2Z5M7#2!0q4y4q4!0n7z5q4)9^5b7W2!0q4z5q4!0n7c8q4!0n7c8q4!0q4y4W2)9&6y4W2)9^5y4#2!0q4y4q4!0n7b7W2!0n7y4W2!0q4y4g2)9^5z5q4!0n7x3q4!0q4y4g2)9^5z5q4)9&6b7W2!0q4y4g2!0n7b7W2!0n7b7g2!0q4y4#2)9&6b7g2)9^5y4q4c8W2L8i4m8Q4c8e0k6Q4z5e0k6Q4z5o6N6Q4c8e0c8Q4b7V1u0Q4b7U0k6Q4c8e0g2Q4b7e0c8Q4b7U0W2Q4c8e0g2Q4z5o6k6Q4z5o6g2Q4c8f1k6Q4b7V1y4Q4z5p5y4Q4c8e0k6Q4z5e0k6Q4z5o6N6Q4c8e0c8Q4b7V1u0Q4b7U0k6Q4c8e0g2Q4z5e0m8Q4z5p5c8Q4c8e0c8Q4b7U0S2Q4b7V1p5`.smss

图片40 URLDownloadToFile

call调用的同时向GetProcAddress()传递参数,获取MoveFile()调用地址:

两处call调用向MoveFile()传递参数:

图片42 调用MoveFile

smss重新命名为smss.exe。之后获取LoadLibrary()调用地址:

图片43 获取LoadLibrary调用地址

call调用的同时传递参数:

获取ShellExecute()调用地址:

通过三次call调用来给ShellExecute()传递参数,最后调用之:

图片46 调用ShellExecute

接下来执行的smss.exe,非本文重点,故不分析。

写文章截图的时候中途断过两次,故前后文某些地址(使用这些地址只是为了方便说明)不对应,望读者谅解。
另,此样本在打了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.dllGetProcAddress(),而是ntdll.dllLdrGetProcedureAddress()

图片73 LdrGetProcedureAddress

再一次调用sub_299122,此次查找的是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。

将返回的调用地址加5后,通过遍历msvcrt.dll的输入表查找CreateFile

图片82 查找CreateFile

此次是查找VirtualAlloc

图片84 查找VirtualAlloc

接下来所查找函数不一一截图,依次是ReadFileWriteFileCloseHandleCreateProcessGetModuleFileNameResumeThreadTerminateProcess

其后传递给GetProcess的参数不再一一截图,依次是ReadProcessMemoryVirtualQueryExVirtualProtectExGetModuleHandleVirtualAllocExWriteProcessMemorySetThreadContextZwUnmapViewOfSection

调用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 结束原来的进程

解密后的文件会在临时目录释放两个文件("白加黑")并运行之:

图片103 释放文件


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 5
支持
分享
最新回复 (4)
雪    币: 18
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2020-4-12 10:33
0
雪    币: 33
活跃值: (323)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2020-4-12 11:18
0
雪    币: 83
活跃值: (1092)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
4
hackone
2020-4-13 08:06
0
雪    币: 42947
活跃值: (65767)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
5
感谢分享!
2020-4-13 09:18
0
游客
登录 | 注册 方可回帖
返回