ZDI-21-171: Getting Information Disclosure in Adobe Reader Through the ID Tag
February 18, 2021 | Mat PowellSometimes the only thing between you and a successful exploit is an information leak. While I see my fair share of information disclosure bugs on the job, it’s not every day that I see one that is so clean and elegant. Then again, it’s not every day I get the privilege of looking at some of Mark Yason's stellar research. This blog covers one such information leak Mark submitted to the program and recently patched by Adobe.
Let’s talk about ZDI-21-171, but first, here’s a quick video showing the bug in action.
The Vulnerability
The issue exists due to the way Adobe Reader handles the ID tag within the PDF trailer. The problem is that when processing the array values for the ID tag, the application does not anticipate anything over 0x100 bytes. With this knowledge and some JavaScript in hand, an attacker can leverage this to disclose the base address of Annots.api.
What exactly are we talking about?
If you pop open a PDF document in an editor, chances are that at the bottom, you’ll see a File Trailer that looks something like this:
According to Adobe’s documentation, the trailer consists of one or more key-value pairs.
The key-value pair of interest is ID, which is “an array of two strings constituting a file identifier for the file.” (See section 9.3, “File Identifiers” in the document referenced above.) Our proof of concept is shown below and contains overly long array values:
What happens when the application encounters an ID key in a file trailer? During the parsing of the ID key, Reader will call a function that will return the size of the ID array values and uses that value to populate the following structure:
This is best illustrated by the following pseudocode:
The f_AcroDocGetFileID
method returns the actual size of the file ID in the PDF even if the passed buffer argument is NULL and the buffer size argument is smaller than the actual size of the file ID. This value is then used to set the originalIDLen
and modifiedIDLen
properties without any check if the value is greater than 0x100 bytes.
Following this in the debugger, we can see that the parameters on the stack align with what we’re seeing in the debugger.
When the function returns, the structure looks like this:
Note the returned size in @eax. The issue here is that the return value was not checked to determine if it is greater than 0x100 before storing the value in this->originalIDLen
and this->modifiedIDLen
.
At this point, the structure is setup with invalid length values. This comes into play later with a call to Collab.documentToStream()
, which invokes a memcpy
call. This is shown below:
When the application tries to copy the originalID into a heap-based buffer, it uses the new 0x400 bytes size instead of the expected 0x100 bytes and allows a user to leak data from the stack. The result is a stack-based buffer out-of-bounds read can be leveraged to disclose the base address of Annots.api through the Collab.documentToStream()
API call.
Wrapping up
Adobe Reader is a common target for attackers since the PDF format is so ubiquitous. While this blog covers an info disclosure bug, Adobe recently patched this along with other vulnerabilities that could allow remote code execution, including one bug that was being actively exploited. Getting code execution on modern applications typically requires multiple steps, and leaking memory addresses is often the first step towards a full exploit chain. Combine this bug with something that allows code execution and a sandbox escape, and you could have a full compromise. You should definitely apply the security patch to all affected systems as soon as possible.
You can find me on Twitter at @mrpowell and be sure to follow the team for the latest in exploit techniques and security patches.