1 Abusing the Reader’s embedded XFA engine for reliable ExploitationPwning Adobe Reader Abusing the Reader’s embedded XFA engine for reliable Exploitation Sebastian Apelt 2016/04/08
2 Agenda whoami Motivation (Short!) Introduction to XFA XFA InternalsXFA Objects jfCacheManager Exploiting the Reader Demo Conclusion Q&A
3 whoami Sebastian Apelt (@bitshifter123) Co-Founder of siberas in 2009IT-Security Consulting (Pentests, Code Audits, etc.) Research Low-level addict Reverse Engineering, Bughunting, Exploitation > 100 CVEs in all kinds of Products Pwn2Own 2014 (IE11 on Win8.1 x64)
4 Motivation
5 Motivation Fuzzing at siberas Let‘s pwn the Reader @ Pwn2Own 2016!!Unfortunately, no love for Reader this time In 2015: XFA fuzzing on 128 cores Fuzz run yielded thousands of crashes So far ~ 20 Bugs identified as unique (upcoming) Analysis took ages… Let‘s take a look at a typical Reader crash!
6 Motivation (72fc.72ec): Access violation - code c (!!! second chance !!!) eax=69572c30 ebx= ecx=07b2f3cc edx=05658af8 esi=0549e538 edi=07b2f3cc eip=20a29654 esp=0031d8c4 ebp= iopl= nv up ei pl nz na cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl= AcroForm!DllUnregisterServer+0x2f73ce: 20a mov edx,dword ptr [eax] ds:002b:69572c30=???????? Awesome, we have a crash! But no useful function name (DllUnregisterServer??) 0:000> !heap -p -a ecx address 07b2f3cc found in 11a0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 07b24eb0 199c [00] 07b24eb8 0ccd (busy) Offset 0xa514 !? The object holding the bad reference is located in the middle of a huge buffer => Page Heap useless 0:000> kc AcroForm!DllUnregisterServer+0x2f73ce AcroForm!DllUnregisterServer+0x2f7212 AcroForm!DllUnregisterServer+0x2f7504 AcroForm!DllUnregisterServer+0x35f3ae AcroForm!DllUnregisterServer+0x358f50 Stacktrace also not helpful
7 Motivation Adobe Reader => No symbols / RTTI infos!No function names No object / vtable information No meaningful stacktraces Page Heap useless Root cause analysis is very hard without context Complicates crash triaging during fuzz runs
8 Motivation How do we ANALYZE crashes in XFA?How do we EXPLOIT these crashes? Obvious: We need context! We need symbols! No in-depth research about XFA internals so far: Most useful: Writeups about XFA exploit from 2013 (David and Enrique of Immunity Inc, Matthieu Bonetti of Portcullis Labs) Good technical analysis, but only scratching the surface
9 Motivation Write tools to recover contextual information Facilitate:Lower the bar for other researchers! Check https://github.com/siberas in the next days Facilitate: Vulnerability discovery and root cause analysis Crash triaging during fuzz runs Deliver XFA-specific background for exploitation
10 (Short!) Introduction to XFA
11 (Short!) Introduction to XFAXFA: „XML Forms Architecture“ Specification developed by JetForm, later Accelio (acquired by Adobe in 2002) – not a standard Latest version: 3.3 (01/2012): Easy read of 1584 pages. Brings dynamic behavior to the static PDF world: Forms that can dynamically change their layout! Dynamic nature of XFA is powered by Javascript (Spidermonkey 24 since AR DC) XFA not supported by many PDF Readers, yet (Chrome/Chromium, Firefox, Windows,...)
12 (Short!) Introduction to XFAXFA form data itself is an XML-structure embedded in the PDF, a so-called XDP-Packet Javascript embedded in this XDP Executed upon events (e.g. document is fully loaded, user clicks on button, etc.) A practical example…
13 (Short!) Introduction to XFA
14 (Short!) Introduction to XFAXFA spec defines multiple DOMs HUGE attack surface (> 200 objects accessible via JS) config Configuration Options template Tpl DOM: Objects which will be visible in the PDF dataSets XML-Data that can be used to populate fields in the PDF form Template and Data are merged into Form DOM xdp layout Layout DOM makes layout information accessible xdc Device-specific information dataDesc dataDescription DOM: Data schema sourceSet DOM for DB- / WebService-Connections
15 XFA Internals
16 XFA Internals - General ApproachTweet Nice! Some Solaris build seems to have symbols! Newest version which still has symbols: Solaris v9.4.1 We need a reliable heuristic to port symbols in AcroForm.api (module which implements XFA functionality) to newer AR versions
17 XFA Internals - General ApproachProblems: Code is rather old (2012) -> Many Code changes from v9.X to AR DC… Function count: Solaris ~48 K, AR DC ~ 95 K Functions differ even if code stays the same (compiler optimizations like heavy inlining in v9.4.1 screw it up) Tried diffing with Diaphora – Too many false positives Structures, objects and vtable sizes differ (slightly, but enough to make it very hard to create reliable heuristics) etc.
18 XFA Internals - General ApproachApproach: Trying to understand Reader v9.4.1 as much as possible with the help of symbols Find bulletproof ways to recover the most important symbols, i.e. Heap Mgmt functions for the custom allocator Object information
19 XFA Internals - ObjectsWhat do we need to know about objects? How to identify an object in memory Vtable offsets Methods and properties exposed to JavaScript Offsets of the entrypoints for methods / property-getters and -setters Function names of vtable entries
20 XFA Internals - Objects: IdentificationFirst attempt: XFANode::getClassTag Fail! classTags not constant across versions! classTag attribute can be
21 XFA Internals - Objects: Identification
22 XFA Internals - Objects: IdentificationPossible to identify every object by a binary pattern in newer versions of AcroForm.api mov eax, 7C46h retn B8 46 7C C3 Xref to the Type method gives us the vtable offset (RVA) to each object! We can safely identify 334 objects! Not too bad!
23 XFA Internals - ObjectsWhat do we need to know about objects? How to identify an object in memory Vtable offsets Methods and properties exposed to JavaScript Offsets of the entrypoints for methods / property-getters and -setters Function names of vtable entries
24 XFA Internals - ObjectsHow about methods and properties? vtable offset 0x34 References moScriptTable structure Structure contains information about method and property names, function pointers, etc. XFAFieldImpl::moScriptTable
25 XFA Internals - ObjectsXFAFieldImpl::moScriptTable XFAContainerImpl::moScriptTable &„field“ Property-Table Method-Table XFANodeImpl::moScriptTable &„container“ Property-Table Method-Table XFATreeImpl::moScriptTable &„node“ Property-Table Method-Table XFAObjectImpl::moScriptTable &„tree“ Property-Table Method-Table 0x &„object“ Property-Table Method-Table Ptr1 to property-struct Ptr2 to property-struct 0x &„rawValue“ func-ptr setter func-ptr getter Ptr1 to method-struct Ptr2 to method-struct 0x &„addItem“ func-ptr addItem
26 XFA Internals - ObjectsWhat do we need to know about objects? How to identify an object in memory Vtable offsets Methods and properties exposed to JavaScript Offsets of the entrypoints for methods / property-getters and -setters Function names of vtable entries TODO… Not trivial… ;-(
27 XFA Internals - jfCacheManagerMost allocations in AcroForm.api are managed by a custom allocator called jfCacheManager LIFO-style heap manager Data buffers („blocks“) stored in big heap „chunks“ Introduced most likely for performance reasons No security features… No Heap Isolation (see IE, Flash, etc.) No Anti-UAF like MemProtect/MemGC …
28 XFA Internals - jfCacheManagerDisclaimer: Next slides will only cover the relevant details of the memory manager in terms of exploitation! (More in-depth analysis will be covered by a paper which will be released soon)
29 XFA Internals - jfCacheManagerVery simplified version of the jfCacheManager: „Chunk“ (big container)
30 XFA Internals - jfCacheManagerStorage of allocations of size < 0x100 CHUNK (BLOCK-SIZE 0x1) CHUNK (BLOCK-SIZE 0x1) jfCacheManager 0x0 vtable […] 0x8 Ptr to Allocs >= 0x100 0x18 jfMemoryCacheList* size 0x1 size 0x2 size 0xFF 0x x434 . Array of jfMemoryCache* jfMemCache* […] jfMemoryCache jfMemoryCache jfMemCacheList Array of jfMemoryCache* CHUNK (BLOCK-SIZE 0x2) jfMemCacheList jfMemoryCache 0x100 entries Array of jfMemoryCache* CHUNK (BLOCK-SIZE 0xFF) jfMemCacheList jfMemoryCache jfMemoryCache and the chunks will be relevant for exploitation!
31 XFA Internals - jfCacheManagersizeof(chunk) derived from block size: Example: allocation size = 0x64 => chunksize = 26 * (0xc3b3 / 0x64) * 4 = 0xcb20 „So, if I get a crash and I see my object located in a chunk of size 0xcb20, then sizeof(obj) == 0x64?“ Unfortunately not… base_size = 0xc350 // chunksize = ((((size + 3) / 4 ) + 1 ) * ((base_size + size - 1) / size)) * 4
32 XFA Internals - jfCacheManagerjfMemoryCacheLists can manage blocks of multiple sizes => blocks of sizes X and Y can both end up in chunk Z! alloc(X) will be placed in same chunk as alloc(Y) if an allocation for a size Y > X has occured before and size X is in the same „range“ as size Y Ranges reach from 2n to (2n+1-1) (e.g. 0x20 - 0x3f, 0x40 - 0x7f) In short: Does the new block fit into some chunk that we already have? If yes, use that chunk instead of allocating a new one!
33 XFA Internals - jfCacheManagervtable […] 0x8 Ptr to Allocs >= 0x100 0x18 jfMemoryCacheList* size 0x1 0x138 0x1a8 size 0x64 size 0xFF 0x x434 . Object X (size 0x64) String of length Z (size 0x64) Object of size 0x48 fits into chunk with block size 0x64 Object Y (size 0x48) jfMemoryCacheList* size 0x48 Array of jfMemoryCache* jfMemCacheList jfMemoryCache
34 XFA Internals - jfCacheManagerLet‘s take a look at the structures within the chunks and what happens during alloc / free operations…
35 XFA Internals - jfCacheManagerInitial state – All blocks are free Chunk (block size 0x10, chunk size 0xf424) jfMemoryCache 0x00 flink 0x10 0x20 0x30 0x40 0x50 … .. 0x0 block size = 0x10 0x4 max_entries […] 0xc chunk** 0x1C alloc_count = 0 0x20 next_alloc_ptr 0x24 jfCacheMgr* block of size 0x10 next_alloc_ptr points to the block which will be returned with the next allocation flinks form a single linked list separating the data blocks
36 XFA Internals - jfCacheManagerAfter first allocation Chunk (block size 0x10, chunk size 0xf424) jfMemoryCache 0x00 jfMC* AAAA BBBB CCCC 0x10 DDDD flink 0x20 0x30 0x40 0x50 … .. 0x0 block size = 0x10 0x4 max_entries […] 0xc chunk** 0x1C alloc_count = 1 0x20 next_alloc_ptr 0x24 jfCacheMgr* next_alloc_ptr is overwritten with flink flink is overwritten with pointer back to jfMemoryCache allocs_counter is incremented to 1
37 XFA Internals - jfCacheManagerAfter second allocation Chunk (block size 0x10, chunk size 0xf424) jfMemoryCache 0x00 jfMC* AAAA BBBB CCCC 0x10 DDDD EEEE FFFF 0x20 GGGG HHHH flink 0x30 0x40 0x50 … .. 0x0 block size = 0x10 0x4 max_entries […] 0xc chunk** 0x1C alloc_count = 2 0x20 next_alloc_ptr 0x24 jfCacheMgr* next_alloc_ptr is overwritten with flink flink is overwritten with pointer back to jfMemoryCache allocs_counter is incremented to 2
38 XFA Internals - jfCacheManagerAfter third allocation Chunk (block size 0x10, chunk size 0xf424) jfMemoryCache 0x00 jfMC* AAAA BBBB CCCC 0x10 DDDD EEEE FFFF 0x20 GGGG HHHH IIII 0x30 JJJJ KKKK LLLL flink 0x40 0x50 … .. 0x0 block size = 0x10 0x4 max_entries […] 0xc chunk** 0x1C alloc_count = 3 0x20 next_alloc_ptr 0x24 jfCacheMgr* next_alloc_ptr is overwritten with flink flink is overwritten with pointer back to jfMemoryCache allocs_counter is incremented to 3
39 XFA Internals - jfCacheManagerFree second block Chunk (block size 0x10, chunk size 0xf424) jfMemoryCache 0x00 jfMC* AAAA BBBB CCCC 0x10 DDDD flink 0x20 IIII 0x30 JJJJ KKKK LLLL 0x40 0x50 … .. 0x0 block size 0x4 max_entries […] 0xc chunk** 0x1C alloc_count = 2 0x20 next_alloc_ptr 0x24 jfCacheMgr* next_alloc_ptr is overwritten with pointer to free block - 4 jfMC* is overwritten with next_alloc_ptr (becomes flink again) allocs_counter is decremented to 2
40 XFA Internals - jfCacheManagerStill don‘t like the jfCacheManager? Still missing Page Heap? Get offset „jfCacheManager_active“ with XFAnalyze_funcs.py Change byte from 1 to 0 in binary Replace original AcroForm.api You just switched off the jfCacheManager :P
41 Exploiting the Reader
42 Know your Corruption TargetsExploiting the Reader Understand the Bug Understand the Heap Know your Corruption Targets Goals Bypass ASLR by corrupting specific byte(s) to cause a memory leak Find „flexible“ overwrite target No need for a write-what-where (e.g. 0-DWORD write or a partial overwrite to a controlled address should suffice!) Find technique which is fast, reliable and most importantly independant from OS and AR version
43 Hit the jfMemoryCache*Exploiting the Reader Let‘s target the metadata contained within the chunks! Two possibilities: Both methods can be abused create a memory leak! But hitting the flink is the easiest way to go Hit a flink Block is free Triggers when block is allocated Chunk 0x00 jfMC* 0x10 flink 0x20 0x30 0x40 0x50 … Hit the jfMemoryCache* Block is allocated Triggers when block is freed
44 Exploiting the Reader - Hit the flink!Initial situation This is our overwrite target! jfMemoryCache 0x00 flink 0x10 0x20 … 0x0 block size 0x4 max_entries […] 0xc chunk** 0x1C 0x20 next_alloc_ptr 0x24 jfCacheMgr*
45 Exploiting the Reader - Hit the flink!After flink overwrite jfMemoryCache 0x00 „bad flink“ 0x10 flink 0x20 … 0x0 block size 0x4 max_entries […] 0xc chunk** 0x1C 0x20 next_alloc_ptr 0x24 jfCacheMgr* 0x00 Attacker- Controlled Data 0x10 0x20 … Requirement: flink must point to controlled data after overwrite Still very flexible: Doable with nearly any kind of mem corruption! Let‘s see what happens when we allocate the „bad“ block
46 Exploiting the Reader - Hit the flink!After allocation of block with „bad“ flink jfMemoryCache 0x00 jfMC* AAAA BBBB CCCC 0x10 DDDD flink 0x20 … 0x0 block size 0x4 max_entries […] 0xc chunk** 0x1C 1 0x20 next_alloc_ptr 0x24 jfCacheMgr* 0x00 „flink“ 0x10 0x20 … next_alloc_ptr is overwritten with the „bad“ flink flink is overwritten with pointer back to jfMemoryCache Now what happens when we allocate an object of size 0x10…?
47 Exploiting the Reader - Hit the flink!Allocate an object jfMemoryCache 0x00 jfMC* AAAA BBBB CCCC 0x10 DDDD flink 0x20 … 0x0 block size 0x4 max_entries […] 0xc chunk** 0x1C 1 0x20 next_alloc_ptr 0x24 jfCacheMgr* 0x00 jfMC* VTABLE refcount
48 Exploiting the Reader - Hit the flink!As soon as the vtable is in a controlled area you can just read it out The controlled data area can be sprayed with strings or even float arrays as „landing zone“ Set the overwritten float or replace the string with data which will point to your ROP pivot gadget For floats: You can compute their binary represenation after spec IEEE754: E-216 will be 0x deadc0de on the heap GAME OVER!
49 Let‘s have a look at a practical example…Exploiting the Reader Let‘s have a look at a practical example… Exploitation of a 0-DWORD write has been SyScan360 Check out my slides if you‘re interested ;) Setting: A 0-DWORD write primitive to an arbitrary address
50 Exploiting the Reader Let‘s make it harder than 0-DWORD overwriteFor Infiltrate: Let‘s exploit ZDI-CAN-3507 Originally planned for Pwn2Own 2016… Obvious: I can‘t reveal any information about the bug But I can describe the exploit methodology At least the basic steps WARNING: The bug is ugly... But: That makes it a great example to showcase the flexibility of the described flink overwrite technique!
51 Exploiting the Reader - ZDI-CAN-3507Setting: Write primitive of an object-pointer (non-XFA) to an arbitrary address We can only write to an address where we have a 0-DWORD !! cmp [ecx], 0 // ecx is under control! jnz
52 Exploiting the Reader - ZDI-CAN-3507Plan: Bypass ASLR by only triggering the vuln twice First shot to derive information about the heap layout Second shot to attack the flink First part is easy: Hit floating point arrays! We can‘t shoot into heap spray of strings: No 0-DWORD… Push value e-315 into arrays => Results in binary pattern (after spec IEEE754) …
53 Exploiting the Reader - ZDI-CAN-3507First 0x hits a 0-DWORD Array X-2 Array X-1 Array X+1 … First shot will go to 0x , this will be mapped by the array heap spray
54 Exploiting the Reader - ZDI-CAN-3507Successful overwrite gives us base address of Array X AABBCCDD Array X-2 Array X-1 Array X+1 AABBCCDD … …and now we also know base addresses of Arrays X-1, X-2, X+1, X+2,…!
55 Exploiting the Reader - ZDI-CAN-3507Now we need to overwrite a flink A flink is an address, obviously != 0, but we can only write to an address where we have a 0-DW... Solution: Partial overwrite a flink which ends on 00‘s! Let‘s manipulate the flink so that it is shifted into a neighboring float array! When an object allocation with the „bad flink“ occurs, the object (and hence the vtable) is placed into the float array So how do I know where my flinks are in memory? And how do I know in where I can find the chunk that contains the flink ending on 00‘s (our target flink)??
56 Exploiting the Reader - ZDI-CAN-3507Array Buffer Z-1 Array Buffer Z Array Buffer Z+1 FREE IT […] jfMC* Block data Allocate enough jfCache objects to cause allocation of new chunk => Array replaced! jfMC* Block data flink Free buffer flink Free buffer
57 Exploiting the Reader - ZDI-CAN-3507We know the array base address => We know the flink addresses if we replace Array Z! => We know the flink addresses if we replace Array Z+n! Array buffer Z-1 […] jfMC* Block data jfMC* Block data jfMC* Block data jfMC* Block data […] flink Free buffer flink Free buffer flink Free buffer flink Free buffer Now we can find a suitable flink ending on 00‘s => This will be the overwrite target!
58 Exploiting the Reader - ZDI-CAN-3507Knowing the flink addresses we need to search a flink of form 0xXXYY0000 Why not 00? You won‘t shift the flink into the next array! Why not ? Very unlikely to find such a flink! Lower 16 bits of the flink will be overwritten with upper 16 bits of the object pointer Let‘s assume write of object pointer == 0x flink Partial overwrite YYXX YYXX
59 Exploiting the Reader - ZDI-CAN-3507Array Z-1 […] Array Z+1 jfMC* Block data jfMC* Block data flink Free buffer flink Free buffer Partial overwrite: 0xXXYY0000 => 0xXXYY0920 Flink will be shifted 0x920 bytes in this case Flink should be located near to the end of the chunk so that after the overwrite it points to the next Array Z+1!
60 Exploiting the Reader - ZDI-CAN-3507Array X-1 […] Array X+1 jfMC* Block data jfMC* Block data jfMC* VTABLE objdata … flink Free buffer flink Free buffer When the block with the overwritten flink is allocated the data is placed in Array Z+1 If an object is allocated the vtable will be placed there ready to be read => ASLR bypassed! =)
61 Exploiting the Reader - ZDI-CAN-3507And RCE?? Super easy! Locate the vtable pointer by finding the overwritten float value in Array Z+1 Overwrite this float value so that we hit our stack pivot with the next vtable call Reference the object with the overwritten vtable pointer to cause a vtable call and jump into your ROP GAME OVER.
62 Demo
63 Conclusion
64 Conclusion Very easy, but highly effective technique to leak dataNo global RW primitive, but enough to pwn AR Version-independant OS-independant Very fast: From start to pwn in ~ 1 sec possible ZDI-CAN-3507 slow because vuln needs time to trigger Flexible technique which can be used with almost every kind of overwrite (as we have just seen) Custom allocator proves once again to be a perfect target in memory corruption scenarios
65 Thank you for your attention! Q&A