Some notes on exploit development
There are two types of frustration as an exploit developer when you are facing a malware with a zero day or a public proof of concept that generally kick in on the first day or two.The first one is a classic: spending hours navigating the darkest corners of the Internet, looking for a the right trial version of the vulnerable application. The more obscure the software is, the harder of a time you will have, I know dumb stack overflows that took more time to find the server than to exploit.
Luckily, this was not our case since we are exploiting Adobe Reader and you can easily find all the versions.
The second problem and the most common while reversing Chinese malware is the low percentage of success they often have. You are able to crash the vulnerable software, of course, and this is generally enough in our line of business to demonstrate the weakness. But you also want to understand what techniques they use to gain control and compare them against yours, and this requires their exploit to actually work.
In the case of the Acroform XFA bug, no matter how much we try with different environment and versions, the heap layout was never massaged correctly to allow the exploit to work.
This leaves an open question we generally have (and we have so many theories... yes, some of them involve aliens!). Why are the Chinese offensive teams not investing a couple more weeks on the heap layout, like we did, and to dramatically improve the reliability of their exploits?
In malware design, reliability == more computer owned == more money.
At the same time, from an OPSEC perspective: reliability == stealthiness. And stealthiness means you don't loose your zero day and your investment is worth more over a long term.
Technical description
The vulnerability lies in the AcroForm.api module when handling Adobe XFA Forms in a particular way. The exploit uses an XFA form with 1024 fields like this:We first need to get UI (user interface) objects from the XFA form, and from within the UI objects the choiceList objects. We need to create 2 arrays with these objects:
These arrays will be used during the whole exploitation process. The code in charge of triggering the vulnerability is:
var node = xfa.resolveNode ("xfa[0].form[0].form1[0].#pageSet[0].page1[0].#subform[0].field0[0].#ui"); node.oneOfChild = choiceListNodes.pop();
Everytime the oneOfChild attribute of a node is set with one of the choiceListNodes node, the vulnerability is triggered. When creating a new "XFAObject" of size 0x40, there is an access outside the bounds of the object on "XFAObject"+44, using uninitialized data. At "XFAObject"+44 there is a pointer to some structure we will describe later. If the pointer is null nothing happens but if we are able to control that uninitialized data after the "XFAObject", we will trigger an info-leak or trigger code execution.
When the pointer is not null, an structure like the following one is accessed:
---- 0x0 Vtable pointer | 0x4 RefCount ---> 0x8 Destructor's address
This structure is accessed twice during the vulnerability trigger. Since we are in control of the RefCount and the "Vtable", if our RefCount is bigger than two, then we can use the bug as a decrement primitive, otherwise when the RefCount gets to zero, the object's destructor will be called.
We are in control of this memory so we can pretty much control what is going to be executed.
With the right heap layout set, we will a get a string followed by an object object, so with our decrement magic we decrement the null terminator and obtain the vtable address right from our object.
Infoleak running on a Windows 7 |
Sounds simple right? Wrong.
We can read the vtable, but we can only read it correctly if all bytes of the vtable are below 0x7f. So if any of the bytes is greater than 0x7f, we use the pointer decrement primitive to get that byte below 0x7f.
Pseudo-Code:
This gave us the ability to be version agnostic, and follow our mantra "an harcoded-less exploit is a happy exploit".
At this point, it was time to get into the second stage of our exploit which is sandbox bypassing. The Chinese exploit was dropping a DLL with the code. We decided that invading the hard-drive was a bad practice and so with a little help with our Python-based assembler (MOSDEF) embedded in CANVAS we decide it to embed the code into the exploit itself.
And so, we decided it was time to bypass the sandbox. But that my friends, will be for our next blogpost entry.
Keep in touch!
David and Enrique