CVE-2012-0774 Investigation CVE-2012-0774 的漏洞通报和《漏洞战争》对该漏洞的描述都仅指出了这是一个整数溢出漏洞,没有更多关于漏洞成因的信息
Integer overflow in Adobe Reader and Acrobat 9.x before 9.5.1 and 10.x before 10.1.3 allows attackers to execute arbitrary code via a crafted TrueType font. Adobe Reader 和 Adobe Acrobat 9.5.2 之前的 9.x 版本和 10.1.13 之前的 10.x 版本中存在整数溢出漏洞,攻击者可利用该漏洞借助特制的 TrueType 字体执行任意代码
经分析,CVE-2012-0774 的成因为 Adobe Reader 的 TTF 渲染器在对 TTF 中的 MINDEX 图元指令进行解释执行时,会基于从 glyf 图元数据表中读取的索引值计算解释器虚拟机的堆栈的位偏移值,却未对计算出的位偏移值进行整数溢出的检测。索引值将作为后续内存复制的循环次数,位偏移值将作为该内存复制的起点。当索引值较大使得位偏移值的计算发生整数溢出时,内存复制的起点会向高地址方向偏移,而内存复制的循环次数或者说内存复制的数据数量不会发生改变,程序发生溢出,进而导致 RCE
影响范围:
Adobe Reader 9.x , x < 5.2 Adobe Acrobat 9.x , x < 5.2 Adobe Acrobat 10.x , x < 1.3
TTF 图元指令 TTF 中有几个片段负责格式化存储字体字形的描述,其中一个片段是字节码语言,即图元指令,主要位于用于存放图元轮廓定义以及网格调整指令的图元数据表 glyf 中。图元指令由 TTF 渲染器中的一个解释器执行。这个解释器是一个基于堆栈的虚拟机。
每个图元的头结构如下
1 2 3 4 5 6 7 8 typedef struct { WORD numberOfContours; FWord xMin; FWord yMin; FWord xMax; FWord yMax; } GlyphHeader;
在头结构之后是一些关于图元的描述信息:
1 2 3 4 5 6 USHORT endPtsOfContours[n]; USHORT instructionlength; BYTE instruction[i]; BYTE flags[]; BYTE xCoordinates[]; BYTE yCoordinates[];
漏洞分析 调试运行 Adobe Reader 并打开样本,程序因触发 Access Violation 异常而中断,异常触发原因为程序试图向只读地址写入数据。根据调试信息得到 Crash Point 位于 CoolType.dll 中,栈回溯确认崩溃函数为 sub_8007E48 函数,使用 IDA 打开 CoolType.dll 并定位到 sub_8007E48 函数,其反汇编代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 .text:08007E48 .text:08007E48 ; =============== S U B R O U T I N E ======================================= .text:08007E48 .text:08007E48 .text:08007E48 ; int __cdecl sub_8007E48(int) .text:08007E48 sub_8007E48 proc near ; CODE XREF: .text:08006DFE↑p .text:08007E48 ; sub_807B0EF+48↓p .text:08007E48 ; DATA XREF: .data:funcs_807B137↓o .text:08007E48 .text:08007E48 arg_0= dword ptr 4 .text:08007E48 .text:08007E48 A1 30 27 27 08 mov eax, dword_8272730 .text:08007E4D 8B 0D 3C 27 27 08 mov ecx, dword_827273C .text:08007E53 53 push ebx .text:08007E54 56 push esi .text:08007E55 8B 31 mov esi, [ecx] .text:08007E57 8D 50 FC lea edx, [eax-4] .text:08007E5A 57 push edi .text:08007E5B 3B D6 cmp edx, esi .text:08007E5D 72 45 jb short loc_8007EA4 .text:08007E5D .text:08007E5F 8B B9 54 01 00 00 mov edi, [ecx+154h] .text:08007E65 3B D7 cmp edx, edi .text:08007E67 73 3B jnb short loc_8007EA4 .text:08007E67 .text:08007E69 83 C0 FC add eax, 0FFFFFFFCh .text:08007E6C 8B 10 mov edx, [eax] .text:08007E6E 8B DA mov ebx, edx .text:08007E70 C1 E3 02 shl ebx, 2 .text:08007E73 8B C8 mov ecx, eax .text:08007E75 2B CB sub ecx, ebx .text:08007E77 3B CE cmp ecx, esi .text:08007E79 72 29 jb short loc_8007EA4 .text:08007E79 .text:08007E7B 3B CF cmp ecx, edi .text:08007E7D 73 25 jnb short loc_8007EA4 .text:08007E7D .text:08007E7F 8B 39 mov edi, [ecx] .text:08007E81 85 D2 test edx, edx .text:08007E83 7E 0F jle short loc_8007E94 .text:08007E83 .text:08007E85 .text:08007E85 loc_8007E85: ; CODE XREF: sub_8007E48+47↓j .text:08007E85 4A dec edx .text:08007E86 8D 71 04 lea esi, [ecx+4] .text:08007E89 8B 1E mov ebx, [esi] .text:08007E8B 89 19 mov [ecx], ebx //! Crash Point .text:08007E8D 8B CE mov ecx, esi .text:08007E8F 75 F4 jnz short loc_8007E85 .text:08007E8F .text:08007E91 83 E8 04 sub eax, 4 .text:08007E91 .text:08007E94 .text:08007E94 loc_8007E94: ; CODE XREF: sub_8007E48+3B↑j .text:08007E94 89 38 mov [eax], edi .text:08007E96 83 C0 04 add eax, 4 .text:08007E99 A3 30 27 27 08 mov dword_8272730, eax .text:08007E9E 8B 44 24 10 mov eax, [esp+0Ch+arg_0] .text:08007EA2 EB 0F jmp short loc_8007EB3 .text:08007EA2 .text:08007EA4 ; --------------------------------------------------------------------------- .text:08007EA4 .text:08007EA4 loc_8007EA4: ; CODE XREF: sub_8007E48+15↑j .text:08007EA4 ; sub_8007E48+1F↑j .text:08007EA4 ; sub_8007E48+31↑j .text:08007EA4 ; sub_8007E48+35↑j .text:08007EA4 A1 88 27 27 08 mov eax, dword_8272788 .text:08007EA9 C7 05 84 27 27 08 10 11 00 00 mov dword_8272784, 1110h .text:08007EA9 .text:08007EB3 .text:08007EB3 loc_8007EB3: ; CODE XREF: sub_8007E48+5A↑j .text:08007EB3 5F pop edi .text:08007EB4 5E pop esi .text:08007EB5 5B pop ebx .text:08007EB6 C3 retn .text:08007EB6 .text:08007EB6 sub_8007E48 endp
其反编译代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 int __cdecl sub_8007E48 (int a1) { unsigned int v1; _DWORD *v2; int v3; int *v4; int v5; int result; if ( (unsigned int )(dword_8272730 - 4 ) < *(_DWORD *)dword_827273C || (v1 = *(_DWORD *)(dword_827273C + 0x154 ), dword_8272730 - 4 >= v1) || (v2 = (_DWORD *)(dword_8272730 - 4 ), v3 = *(_DWORD *)(dword_8272730 - 4 ), v4 = (int *)(dword_8272730 - 4 - 4 * v3), (unsigned int )v4 < *(_DWORD *)dword_827273C) || (unsigned int )v4 >= v1 ) { result = dword_8272788; dword_8272784 = 0x1110 ; } else { v5 = *v4; if ( v3 > 0 ) { do { --v3; *v4 = v4[1 ]; ++v4; } while ( v3 ); --v2; } *v2 = v5; dword_8272730 = (int )(v2 + 1 ); return a1; } return result; }
为方便阅读, 变量重命名后的漏洞函数反编译代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 int __cdecl vulFunction (int a1) { unsigned int high; _DWORD *end; int len; int *start; int top; int result; if ( (unsigned int )(a - 4 ) < *(_DWORD *)b || (high = *(_DWORD *)(b + 0x154 ), a - 4 >= high) || (end = (_DWORD *)(a - 4 ), len = *(_DWORD *)(a - 4 ), start = (int *)(a - 4 - 4 * len), (unsigned int )start < *(_DWORD *)b) || (unsigned int )start >= high ) { result = dword_8272788; dword_8272784 = 0x1110 ; } else { top = *start; if ( len > 0 ) { do { --len; *start = start[1 ]; ++start; } while ( len ); --end; } *end = top; a = (int )(end + 1 ); return a1; } return result; }
使用 LLM 辅助阅读了一下这段代码,其功能为对一个范围的数据进行循环前移,具体来说内存中从 a - 4 - 4 * len 到 a - 4 的每个 dword 都将往低地址移动 4 字节,其中 len 为内存中 a - 4 处指向的值
静态分析能够发现 4 * len 的计算结果没有进行整数溢出检验。通过调试进行动态分析时则能够发现,在处理样本时,len 的值被读取为 0x40000001,而在乘以 4 之后寄存器中保存的运算结果变成了 0x00000004,因此接下来内存复制的起点将会往高地址偏移 0x40000001 - 0x00000004,然后依旧进行 0x40000001 个数据的循环前移,导致溢出而触发 Access Violation
查看 vulFunction 的交叉引用能够发现该函数位于一个函数数组中,该函数数组通过 ecx 作为偏移量跳转到对应函数,在该处下条件记录断点,发现崩溃时此处的 ecx 为 0x26。由于漏洞是由 TTF 文件触发的,在样本中搜索 0x26 发现它出现在 glyf 表中,推测其为图元指令
针对 0x26 其实是图元指令的分析说实话非常玄学。漏洞战争通过一个非常小众且现在已经不再找得到的工具分析样本时发现 TTF 文件解析出错而判断问题出现在样本的 glyf 表中,并结合文档和此时发现的函数数组猜测出了 0x26 应该是图元指令;LarryS 师傅查看了 len 变量附近的数据块发现了有些他觉得很异常的 0x41 并怀疑这些数据是人为设置的因而在样本中搜索了 0x41 然后发现这些 0x41 都出现在 glyf 表中并对 0x41 所处的最后一个 SimpleGlyph 中的所有指令进行了逆向发现了 vulFunction 是 0x26 图元指令。我个人也没有什么比较好的思路,因此在这里只能采取这种有些从结果推过程的做法进行分析
结合 TTF 文档可得知 0x26 对应图元指令 MINDEX。MINDEX 指令可以从栈中弹出一个值 index,并利用这个值作为一个索引进入栈区。index 处的值被移到栈顶,栈中原有的值依次向下移动以填补移动产生的空间。由于所有的栈操作都以 4 字节为单位,Adobe Reader 的解释器会对索引值乘以 4 以得到需要进行的偏移值。可以发现该指令的功能确实与 vulFunction 一致。由于没有对这个乘法运算进行整数溢出的检测,如果 0x40000001 <= index <= 0x7fffffff ,程序将发生溢出
样本中的 0x40000001 为 glyf 表中最后一个 SimpleGlyph 中写入的指令经过一系列运算得出,这点可以根据 len 的动态污点分析证实,属于是纯体力活,在此不过多赘述
漏洞利用 该漏洞尚未发现通用的漏洞利用方式,令人感慨
漏洞修复 Patch 后的 VulFunction 会对虚拟栈上数据差值和 0xFFFFFFFC 相与后的值进行检测,以防止整数溢出
Reference NVD - CVE-2012-0774 CVE - CVE-2012-0774 Adobe - APSB12-08:Prenotification Security Advisory for Adobe Reader and Acrobat Github - CVE-2012-0774 漏洞战争