As promised in our previous blog entry for the recent Adobe Reader PDF zero-day attack, we now offer more technical details on this Reader “sandbox-escape” plan. In order to help readers understand what’s going on there, we first need to provide some background.
Adobe Reader’s Sandbox Architecture
The Adobe Reader sandbox consists of two processes: a high-privilege broker process and a sandboxed renderer process; the latter is responsible for rendering the PDF document. Please see Adobe’s ASSET blog for an illustration of the sandbox architecture.
The renderer process has restricted read/write access to the file system, registry, and named objects. Most of the native OS API calls will go through the interprocess communication (IPC) mechanism to the broker process. For example, a native API call (CreateFile) originates from the sandbox process and the broker process eventually takes over as a proxy.
Actually, the Reader sandbox’s IPC is implemented based on Google Chrome’s IPC shared-memory mechanism. The broker process creates a 2MB shared memory for IPC initialization, the handle of the shared memory is duplicated and transferred to the sandboxed process, and all the communications leverage this shared memory.
The API call request from the sandboxed process is stored in an IPC channel buffer (also called CrossCallParams or ActuallCallParams). The structure of the buffer is defined as the following format (from crosscall_params.h):
Here are some explanations for the fields:
- The first 4 bytes, the “tag,” is the opcode for which function is being called
- “IsOnOut” describes the data type of the “in/out” parameter
- “Call return” has 52 bytes. It’s a buffer used to fill the returning data from the IPC server.
- “Params count” indicates the number of the parameters
- The parameter type/offset/size info indicate the actual parameters
The parameter type is an enum type, s defined as follows:
Escaping the Sandbox
The sandbox escape in this zero-day exploit is due to a heap-based overflow vulnerability that occurs when the broker process handles the call request of the native API “GetClipboardFormatNameW.” The tag id for this API is 0×73. Here is the ActuallCallParams (IPC channel buffer) memory structure for the request in the exploit:
As marked by different colors above, the first DWORD is the tag id (0×73), and there are only two parameters for this API call (as indicated by the blue DWORD). The yellow DWORDs are the parameter types: Type 6 means INOUTPTR_TYPE and type 2 means ULONG_TYPE. The red DWORDs are the sizes for these parameters, so the first parameter has 0x9c bytes with the “in/out ptr” type and the second parameter has 4 bytes with the “long” type.
Let’s take a look at the definition of the parameters for the GetClipboardFormatNameW API.
According to the preceding definition, the GetClipboardFormatNameW call would look like this:
GetClipboardFormatNameW(0xc19a, “BBBBBBBBBB……”, 0x9c);
At first sight, this function call looks normal, with nothing malicious. Unfortunately, there are two issues that will lead to a heap overflow condition. First, Adobe Reader allocates the heap memory based on “cchMaxCount,” while the correct size should be “cchMaxCount * sizeof(WCHAR)” as this is a Unicode API. In our case, the allocation size is only 0x9c; that is incorrect. Second, the lower-level native API NtUserGetClipboardFormatName() called by GetClipboardFormatNameW() is using cchMaxCount*sizeof(WCHAR) as its “length” parameter when copying a string to the heap buffer. At this point the heap overrun happens!
There is a trick to trigger this heap overflow: Just pay attention to the first parameter. From the MSDN description, the parameter “format” is used to retrieve the type of the format. So if we can pass in advance a format ID that requires a longer buffer space, then later when the broker calls the GetClipboardFormatNameW() to retrieve the format, it will trigger the overflow.
In this sandbox-escaping exploit, the malware calls RegisterClipboardFormatW() to register a different format name, which is much longer than 0x9c bytes. Finally, an object (vtable) on heap will be overwritten. However, the story is not over yet. In order to achieve reliable exploitation, a heap spray inside the broker process is needed. The attacker did this in a very smart way, he or she leveraged the “HttpSendRequestA” function (tag id 0x5d). See the following dumped memory for this function call request.
Because the fourth parameter (lpOptional) has the type VOIDPTR_TYPE (its address and size are highlighted in red) in the exploit, the attacker passes the buffer size 0x0c800000 (the second red section). Because the size is huge, when the IPC server calls ReadProcessMemory API to read the buffer, the broker process’ heap memory will be sprayed with attacker-controlled data at a predictable memory location.
The ASLR- and DEP-bypassing part is very easy because the module base addresses of the broker process and the sandboxed process are same. The attacker can directly use the ROP code chain to defeat both ASLR and DEP.
Adobe has now released the official patch for these critical vulnerabilities. As always, we strongly suggest that users apply the patch as soon as possible. For McAfee customers, you’ll find our solutions in our previous post.
Thanks again to Bing Sun, Chong Xu, and Haifei Li for their help with this analysis.