CVE-2020-9715: Exploiting a Use-After-Free in Adobe Reader

September 03, 2020 | Abdul-Aziz Hariri and Mat Powell

It’s a great feeling when you wake up in the morning to the smell of a fresh pour-over coffee and find a nice 0-day waiting for you in the queue. That’s my typical day-to-day morning at the ZDI. I won’t lie - some of the submissions can be disappointing but, on the other hand, a lot of the submissions we get are excellent. 

One such example that is worthy of discussing today is an Adobe Reader submission by the great Mark Yason. The submission was composed of a detailed analysis of a Use-After-Free vulnerability along with an exploit that achieves code execution in the renderer process.

The vulnerability

When constructing Data "ESObjects" (EScript objects associated with JavaScript objects), the pointers to allocated Data ESObjects are stored in an object cache so that when a Data ESObject with the same associated PDDoc structure and name needs to be constructed, the Data ESObject pointer from the cache is used instead of performing a new allocation.

The key to this object cache is a PDDoc object address plus the name of the Data ESObject:

Object cache illustration:

The name string has the following ESString structure:

The issue is that when the Data ESObject is freed, the ESObject pointer in the object cache is not removed and therefore becomes stale:

The issue arises because when the ESObject pointer is added in the object cache, the type of the name ESString used is ANSI. However, when deleting an ESObject pointer entry in the object cache, the type of the name ESString used when searching for an entry to delete is Unicode. As a result, the ESObject pointer entry in the object cache is not found, and so is never deleted.

Proof of Concept

The following trace shows that a cache entry is added for Data ESObject (40434fb8) with keys PDDoc (2710cbc0) and Name (3daaffe8). Notice that the name is in ANSI:

Next is a trace that shows when the cache entry is deleted for Data ESObject (40434fb8) with keys PDDoc (2710cbc0) and Name (253b4fe8). Notice that the name is in Unicode:

The following trace illustrates a freed object being returned from a Data ESObject function call (40434fb8):

Finally, the re-use is triggered which results in the following crash:

The Exploit

The technique used to exploit this vulnerability is the classic ArrayBuffer byteLength corruption, which was demonstrated and used in many previous exploits. One of the other interesting pieces of this exploit was the heap spray technique that was also used by @Fluoroacetate’s Adobe Reader Pwn2Own attempt in 2020. The technique reminds me of the older IE Use-After-Free exploits that operated by spraying objects in predictable addresses.

The steps involved in the exploit 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()

Putting it all together

Adobe patched this particular issue some time ago. Acrobat and Reader continue to have a broad attack surface. There’s no doubt this will continue to be a fruitful area of research both for us at ZDI and security researchers submitting to our program. 

You can find us on Twitter at @AbdHariri and @mrpowell. Follow the team for the latest in exploit techniques and security patches.