Our vulnerability detection platform had discovered a new Type Confusion vulnerability, which affects the latest version of all Acrobat Readers (Acrobat DC, Acrobat Reader DC, Acrobat 2017, Acrobat Reader 2017, Acrobat XI, Reader XI). CVE-2017-16379/CY-2017-011
In the heart of this vulnerability, is the question “How do we check the type of a void*?”.
We all know that void pointers are by definition typeless, may point to any object, and any object may be cast to it. But surely, after we’ve cast an object pointer to a void pointer, it’s still possible somehow to detect its origin type, right? Not quite.
Once an object is cast to a void pointer, it’s absolutely impossible to detect its origin type. So, how do we check the type of a void*? The answer is that we don’t. And when you try, what you get is a vulnerability.
The root cause of this vulnerability is a function named IsAVIconBundleRec6 in AcroRd32.dll, which receives a void pointer and tries to determine its type using magic (constant) values.
This is not only impossible, but as a consequence created a type confusion vulnerability that may be exploited to perform remote code execution in multiple Adobe Reader products.
The function assumes that if an object starts with a specific magic value, then it’s an AVIconBundleRec6 object.
Specifically, the function tests the following conditions:
1. The object is a valid readable pointer.
2. The object’s bytes 0-7 are the magic value “CIVAUBNO”.
3. The object’s bytes 8-11 are a version number (must be >= 0x5000).
In most cases, the function receives a valid pointer to a AVIconBundleRec6 object and identifies it successfully without any problems.
But sometimes, the function receives an object of type HBITMAP.
Contrary to most Windows handles, which normally are valid pointers (e.g. HMODULE), HBITMAP may be any value between 0x0-0xffffffff, and is not considered as a valid pointer.
Because HBITMAP can be any value between 0x0-0xffffffff, sometimes it may unintentionally point to a valid memory location, causing test #1 to pass (the test at 0x601141D2).
Once test #1 was passed, the type confusion occurs as the function confuses the type of the HBITMAP and assumes it’s a valid pointer, even though it’s not.
So when is the function called with an object of type HBITMAP, and can it be controlled by an attacker? It seems that there is a group of icon resources in AcroRd32Res.dll that are loaded by the renderer as HBITMAP objects. These HBITMAP objects are then sent to IsAVIconBundleRec6 as arguments each time they are rendered (i.e. multiple times per second).
The renderer loads the icon from the resources when it’s being rendered for the first time.
The renderer only loads it one time, the second attempt to render the icon will use the HBITMAP that was loaded from the first attempt (i.e. the renderer caches the HBITMAP).
This means that if an attacker is able to render one of these icons, the renderer will load it as a HBITMAP object and call IsAVIconBundleRec6 with it multiple times per second.
If the HBITMAP object unintentionally points to a valid memory location, it will be treated as a pointer by IsAVIconBundleRec6 causing the type confusion.
In order to exploit this vulnerability we need to:
- Create an HBITMAP that points to a memory location under our control.
- Invoke IsAVIconBundleRec6 with the HBITMAP as its argument.
- Satisfy the tests done by IsAVIconBundleRec6, causing it to return the value true (i.e. causing it to approve that the HBITMAP, which points to a memory location under our control, is a valid AVIconBundleRec6 object).
- Once the function approved the HBITMAP, it will start using it as a valid AVIconBundleRec6 object, including using the AVIconHandler member (a vtable-like pointer) of the AVIconBundleRec6 object to invoke function calls. An attacker can take over the vtable-like member resulting in full control over EIP the next time a function gets invoked.
In order to create an HBITMAP object, all we need to do is render one of the icons listed above.
This can be done by creating a PDF file that contains the icon on its opening page.
Once the icon gets rendered, the renderer will load the icon resource and create an HBITMAP object for it.
This HBITMAP object can contain any value between 0x0-0xffffffff.
By spraying the heap with blocks under our control, we have a good statistical chance that the HBITMAP will point to a block under our control.
Because every icon is loaded as HBITMAP only once, by rendering all the icons we have 8 different HBITMAP values, each pointing to a random location. If at least one of them points to a memory location under our control we can proceed with the exploitation.
We finish step 1 by having 8 random values, with one or more of them hopefully pointing to a memory location under our control.
Invoking IsAVIconBundleRec6 with the 8 HBITMAP objects happens automatically by the renderer multiple times per second.
If the HBITMAP doesn’t satisfy all the tests done IsAVIconBundleRec6, the function fails gracefully. This means that as long as the icons are being rendered, IsAVIconBundleRec6 will be called again and again with these 8 HBITMAP values.
Even if our initial heap spray missed one of the HBITMAPs, we can continue spraying and releasing the heap, multiple times per second, in the hope to catch one of the HBITMAP objects. There is no need to fear a crash as any failed attempt would simply fail gracefully.
As long as at least one HBITMAP points to a valid user mode address, we will eventually manage to “catch” it. If all the HBITMAPs point to memory locations that cannot be controlled by us (e.g. kernel space, memory mapped for an image) the attack will fail.
All we need to do satisfy the checks done by IsAVIconBundleRec6 is to make sure the heap spray contains memory blocks that adhere to the AVIconBundleRec6 object structure (i.e. start with “CIVAUBNO”).
Once IsAVIconBundleRec6 is fooled to believe our block of memory is a valid AVIconBundleRec6 object, it will use the AVIconHandler member to invoke a function call (this happens soon after IsAVIconBundleRec6 is called). As we control the entire object, including the AVIconHandler member, we can replace it resulting in full control over EIP the next time a function gets invoked.
Proof of Concept
A proof of concept reproducing the vulnerability (not the exploit) can be supplied on request.
A void pointer, is a void pointer. By definition, it’s typeless.
If you really need to work with void pointers and would like to know the pointer’s type, simply add another argument for the type, or wrap the pointer in a custom struct with an additional member for the type.
Any attempt to work around this fact would result in a buggy application at best, and exploitable vulnerability in the worst case.
If you’ve been wondering what are the 8 icons that are responsible for this vulnerability, they’re: