实验环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows XP SP3 | |
虚拟机 | Vmware | |
调试器 | OD | |
反汇编器 | IDA Pro | |
漏洞软件 | Adobe Reader | 版本号:9.3.4 |
静态分析
漏洞点来源于该函数,strcat处将uniqueName的数据复制过去,但没有进行长度校验。
通过PdfStreamDumper工具取出PDF样本中的TTf文件。
官方文档中TTF文件TableEntry的定义:
typedef struct struct_SING{
char tag[4]; // 标签 "SING"
ULONG checkSum; // 校验和
ULONG offset; // 偏移量
ULONG length; // 长度
} TableEntry;
SING表的数据结构:
从TTF文件偏移0x11C处找到SING表数据,其偏移0x10处找到uniqueName域。即下图中58 E0 8D AD处。
执行strcat会从此处开始复制,直到遇到NULL字符为止。
动态调试
0x0012DFD4为dst地址,IDA查看的是260字节大小,复制了23Dh字节,栈溢出。
在0x0803DEAF处,F8会跟丢,所以应该是在此处跳转到了shellcode,F7跟进去。
在此处若F8跟丢,所以 在这里执行shellcode,a1为this指针,v7为虚函数表,(*v7)()为虚函数调用。所以是覆盖虚函数进行的攻击。
动态调试调用的函数是0x0808B116。
然后调用*v18。
F7跟进去,显然这里就是ROP链的开端。
所以覆盖的实际上是虚函数。
到此为止,strcat溢出分析结束。随后分析ROP链。
ROP指令1使得ebp回到了溢出处+0x4处。leave指令相当于mov esp,ebp,pop ebp。
ROP指令2,将esp设置成了0x0C0C0C0C。通过heap spray技术,此处已经布置好了shellcode。
随后执行的ROP指令是
pop ecx
mov [ecx],eax ;eax=12E6D0
pop eax ;eax=CreateFileA
jmp [eax]
创建一个名为ios88591的文件。
之后构造ROP链调用CreateFileMapping函数创建文件内存映射。
再调用MapViewOfFile,将文件映射对象映射到进程内存空间。
最后调用memcpy将真正的shellcode放置到之前映射的内存空间,绕过DEP保护。由于构造的ROP指令是不受ASLR保护的icucnv36.dll模块,因此也可用于绕过ASLR保护。
追根溯源
上述动态调试展示了漏洞样本对该栈溢出的利用,该节分析shellcode的具体功能。
首先call进行了一个跳转。随后类似调用函数对函数栈帧进行了一个分配。函数栈帧空间给的0x248
随后找到fs获取TEB,TEB偏移0x30获取PEB,PEB偏移0xC获取PEB_LDR_DATA,PEB_LDR_DATA偏移0x1C获取InMemoryOrderModuleList的指针。然后获取第一个module,kernel32的加载地址。
在解析kernel32的PE格式。先获取0x3C处的e_lfanew得到NT_HEADER偏移,然后偏移0x78处得到IAMGE_DATA_DIRECTORY。然后获取导出表。获取之后从偏移0x18处获取NumberOfNames,0x20处获取AddressOfNames(导出函数名称数组)。
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 0x0
DWORD TimeDateStamp; // 0x4
WORD MajorVersion; // 0x8
WORD MinorVersion; // 0xA
DWORD Name; // 0xC // 相对于导出表的偏移
DWORD Base; // 0x10
DWORD NumberOfFunctions; // 0x14
DWORD NumberOfNames; // 0x18
DWORD AddressOfFunctions; // 0x1C
DWORD AddressOfNames; // 0x20
DWORD AddressOfNameOrdinals; // 0x24
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
然后倒着找函数,计算hash和传入参数对比。
找到后去AddressOfNameOrdinals找序号,该数组每个元素2字节,所以之前找到的索引要*2计算偏移。最后获取AddressOfFunctions,序号*4计算偏移。找到函数地址。
一共需要找到12个函数地址。
随后循环获取句柄大小。如果文件大小>0x2000,就设置文件指针,读取到栈中,在对比数据看看文件对不对。
当文件大小0x1CAD74时,也就是句柄为打开的pdf文件时,移动文件指针0x1200处,读取8字节到栈中0x0C0C0CFC处。
然后调用GlobalAlloc分配pdf文档大小的数值,内存以0填充。在这后将文件读取。
读取完文件后,从文件0x1210开始读取3个4字节并累加-1,获取异或密钥。与121C开始的数据解密。
解密后文件偏移0x121C处。svrhost.exe开头,然后ZM,是EXE文件头MZ的倒序。
然后拼接一下字符串。C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\svrhost.exe
然后前200个字节两两交换位置。写入上述svrhost.exe文件。
随后winexec执行svrhost.exe。
svrhost.exe执行如下。
首先写入12345到0012FDC4 C:\WINDOWS\system32\setup\hid128.log
然后关闭文件保护,获取SeDebugPrivilege权限。
随后从svrhost.exe获取恶意Url。
http[:]//203.45.50[.]118/monitor/images/mmc_vti0915.gif
http[:]//203.45.80[.]96/monitor/images/mmc_vti0915.gif
http[:]//61.222.31[.]83/monitor/images/mmc_vti0915.gif
再然后关闭Spooler服务,然后删除自身。
接下来找到导入表的FOA,并将所有区块设置属性为可读可写。
之后修改导入表,让spoolsv.exe引用msxml0r.dll中的entryPoint函数。
修改完spool.exe后,修改其文件时间。
然后将spooler服务设置为自动启动服务。再然后写入msxml0r.dll文件,0x90解密,并写入C2服务器地址。
查看msxml0r.dll文件。使用了PeCompact加壳。
后续手动脱壳。
大致上的内容就分析到此处了。
漏洞修复
bindiff分析。
函数多了一个0x104,十进制260,为dst的长度。
看一下自定义函数。
总结
- strcat没有进行长度校验导致的栈溢出。
- 使用覆盖虚函数指针绕过了GS。
- 通过heap spray技术提前布置了shellcode。
- 通过ROP绕过了DEP。
- 通过未开启ASLR的icucnv36.dll构建ROP链绕过了ASLR。
- 通过PEB及hash从kernel32获取了需要的函数。
- 使用异或和交换字符的方式对PE文件进行了隐藏。
- 释放的svchost.exe与系统进程名一致,增强了隐蔽性。
- 提升了权限以便后续操作。
- 关闭系统文件保护,以便修改系统文件。
- 修改文件时间增强隐蔽性。
- 执行完脚本后删除脚本自身。
- 释放的msxml0r.dll进行了解密,并且与系统现有文件名相似。
- 释放的msxml0r.dll加了壳防止逆向分析。
- 通过远程从C2服务器下载,实现了自动更新功能。
- 最后打开正常文件,降低警惕。
文章评论