CFI1 has most certainly set the standard for exploit mitigations, and has inspired many implementations such Microsoft CFG2, Microsoft RFG3, PaX Team’s RAP™4 and Clang’s CFI5.
In this series of posts we’re going to demonstrate how modern CFI implementations can be circumvented.
Specifically in this post we’ll be demonstrating an advanced code reuse technique, Counterfeit Object-Oriented Programming6 (COOP) utilizing an old vulnerability to conform to the theoretical boundaries of CFI.
CVE-2015-5122 is a use-after-free vulnerability that was used by Hacking Team to exploit Adobe Flash Player (<= 126.96.36.199). An analysis of the vulnerability itself can be found here. Note that by leveraging this vulnerability we are able to gain a full read-write primitive to the process memory.
We based our work on Metasploit’s implementation of CVE-2015-5122 which can be found here. In order to achieve a read/write primitive the vulnerability is used to overwrite the
length member of a vector object. The vector object is wrapped with a class named
ExploitByteArray that contains the methods
write(addr, data) and
read(addr) that provide the full read/write primitives. Code execution is gained by defining a fake “magic” method10 in the
Exploiter class, and overriding its virtual function pointer with an address of choice.
The metasploit implementation first calls
VirtualProtect in order to change a sprayed stack-pivot stub’s page protection to
READWRITE_EXECUTE, and then uses the magic method a second time to call the executable stub and commence a ROP chain.
// VirtualProtect the stub with a *reliable* stack pivot eba.write(stack_address + 8 + 0x80 + 28, virtualprotect) eba.write(magic_object, stack_address + 8 + 0x80); // overwrite vtable (needs to be restored) eba.write(magic + 0x1c, stub_address) eba.write(magic + 0x20, 0x10) var args:Array = new Array(0x41) Magic.call.apply(null, args); // Call to our stack pivot and init the rop chain eba.write(stack_address + 8 + 0x80 + 28, stub_address + 8) eba.write(magic_object, stack_address + 8 + 0x80); // overwrite vtable (needs to be restored) eba.write(magic + 0x1c, stack_address + 0x18000) Magic.call.apply(null, null); eba.write(magic_object, magic_table); eba.write(magic + 0x1c, magic_arg0) eba.write(magic + 0x20, magic_arg1)
This exploit implementation is sufficient in bypassing ASLR and DEP protections, however it’s far from being undetectable by modern CFI implementations.
For example, Shadow Stack (backward-edge) based CFI implementations will easily detect the stack pivot and the ROP chain execution as magic function returns to a different address than the address that was pushed on the stack by the original call instruction.
In order to make this exploit undetectable by modern CFI implementations, we must conform to the following constraints as described by Schuster et al7:
We add an additional constraint that applies a fine-grain policy for “critical functions”.
Counterfeit Object-Oriented Programming (COOP) is a new code-reuse technique for C++ applications. This technique relies on the assumption that CFI solutions do not consider C++ semantics, as they don’t check that every virtual function is called by the appropriate virtual callsite9. By creating a counterfeit object with a fake vtable with vptr’s of our choosing, we can invoke any virtual function without triggering CFI because CFI solutions don’t validate that the called virtual function has any connection to the caller object’s class.
First, we need to find virtual functions that perform the operations we plan to do. These functions are called “vfgadgets”.
Second, in order to combine them we need to find a special vfgadget called Main Loop Gadget (ML-G or ML-ARG-G if it passes arguments). This vfgadget contains a loop that iterates through a list of objects and calls a virtual function for each one of them. It’s common to find such a vfgadget in a large C++ application. By filling the counterfeit ML-G object with a fake member list of objects, each object will hold a fake vtable containing a different vfgadget, we can execute different parts of the application’s code without violating the constraints described above. We demonstrate the combination of two vfgadgets,
CLibrariesFolderBase::v_AreAllLibraries in order.
Active Template Library(ATL) is a C++ template library that is used to simplify the programming of COM object in Windows. You can find these templates in some of Windows libraries, including shell32.dll (version 6.1.7601.23403). One of shell32.dll objects is
CComControl, a class that provides methods for creating and managing ATL controls. The important method for us is
According to microsoft’s documentation the method initializes and creates a window object by calling
CWindowImpl::Create and which calls additional internal objects as described below. As every window, it needs to have a
WndProc function – a callback function that will process all the messages that will be sent to the window.
We found out that this function puts a thunk as the
WndProc procedure of the window. This thunk transforms the Windows C callback call into a virtual function call, by overwriting the first
WndProc stack argument (that’s supposed to be the
HANDLE of the window) with a C++ this pointer.
The thunk data and its disassembly:
c7 44 24 04 [DWORD thisPointer] e9 [DWORD WndProc]
mov DWORD PTR [esp+0x4], thisPointer
What’s really interesting with this vfgadget is that the thunk is written to a page that is allocated by
EXECUTE_READWRITE permissions. This
VirtualAlloc call is performed by
ATL::_stdcallthunk::Init as you can see in the following screenshot (0x40 is the
flProtect parameter, meaning
Other considerations regarding this vfgadget are:
ATL::_stdcallthunk::Init, and we have a full control on this check, because it just validates that one of the object’s member is
To summarize, we will create our counterfeit
CComControl object, call the
ATL::CComControl::CreateControlWindow vfgadget, and then use our read/write primitive (
ExploitByteArray) to read the created thunk address. Ultimately giving us a
READWRITE_EXECUTE page to store our shellcode.
Well, almost, the Magic method we use in order to execute the vfgadget passes 3 arguments to virtual function, however
ATL:CComControl::CreateControlWindow receives only 2 arguments which leads to a stack corruption and crashes the process. In order to avoid a stack corruption we use another vfgadget that receives 3 arguments and use it to call
To find such a vfgadget we searched
shell32.dll with an IDA script with the following constraints:
We looked over the script result and chose
This vfgadget is actually a mainloop gadget (ML-ARG-G) that calls the same virtual function (offset 0x4c of the VTABLE) in every iteration and stops the iteration only when the function returns a successful error code (0).
Building our counterfeit objects, the magic method pointer [vtable+0x18] will point to our ML-ARG-G, and the ML-ARG-G inner call vtable’s offset [vtable+0x4c] will point to
// first, we save the current this pointer, to recover it later original_this = eba.read(magic_object); var magic_vtable:uint = magic_object+0x40; // now, lets put a fake vptr at [magic_object] eba.write(magic_object, magic_vtable); // [vtable+0x18] will hold the first vfgadget that will be invoked // it will be the ML-ARG-G we found in shell32. eba.write(magic_vtable + 0x18, ml_arg_g); // [vtable+0x4c] will hold the second vfgadget that will be invoked // It will be the createWindow vfgadget we found in shell32 eba.write(magic_vtable+0x4C, createWindow_g)
Now, invoking the Magic method will perform our COOP flow:
eba.write(magic + 0x1c, 0x0) eba.write(magic + 0x20, magic_object+0x100) var args:Array = new Array(0x41) Magic.call.apply(null, args); eba.write(magic_object, original_this);
When our COOP flow finishes, we can read the created
READWRITE_EXECUTE allocated page pointer. It will be stored at 0x5c offset from the “
// createWindow allocated a page with EXECUTE_READWRITE protection, and stored a pointer to it on magic_object+5C var allocated_address:uint = eba.read(magic_object+0x5c) // get page base address allocated_address = allocated_address&0xFFFFF000
Now, we can simply write our shellcode to the allocated_address, put this address in magic vtable offset, and call the magic method again to achieve code execution.
There are several ways to implement exploits using the COOP technique. During the research we also found 5 vfgadgets in flash DLL (188.8.131.52) that do the following:
MoveFileExAPI as we have a full control on the source and destination parameters (this will rename the file “digest.s” to “atl.dll” which is necessary for the final vfgadget).
SetCurrentDirectoryAPI with a controllable path parameter. We can use it to set the process’s current directory to the path containing our payload file.
LoadLibraryto “atl.dll” which will load our payload dll.
Combining all these vfgadgets together is possible but requires more time due to the delicate nature of COOP. As mentioned before, we found one simple vfgadget that gives us a memory page with
READWRITE_EXECUTE protection so we decided to take the straightforward path in this case.
We have successfully demonstrated how COOP can be used to circumvent modern CFI solutions by conforming to the constraints mentioned above
VirtualAllocwas invoked from a legal offset circumventing EMET’s critical function protection.
In order to mitigate COOP attacks CFI implementations must take language semantics and contextual states into account. As mentioned earlier, in this particular case applying a fine-grained policy that matches virtual call sites to the relevant object’s virtual functions or any other kind of semantic validation should be enough to detect the attack.
[ 1 ] : http://research.microsoft.com/pubs/64250/ccs05.pdf
[ 2 ] : https://msdn.microsoft.com/en-us/library/windows/desktop/mt637065(v=vs.85).aspx
[ 3 ] : https://xlab.tencent.com/en/2016/11/02/return-flow-guard/
[ 4 ] : https://grsecurity.net/rap_announce.php
[ 5 ] : https://clang.llvm.org/docs/ControlFlowIntegrity.html
[ 6 ] : http://syssec.rub.de/media/emma/veroeffentlichungen/2015/03/28/COOP-Oakland15.pdf
[ 7 ] : http://syssec.rub.de/media/emma/veroeffentlichungen/2015/03/28/COOP-Oakland15.pdf
[ 8 ] : http://0xdabbad00.com/wp-content/uploads/2013/11/emet_4_1_uncovered.pdf
[ 9 ] : https://eyalitkin.files.wordpress.com/2017/01/liberation-guard-eyal-itkin.pdf
 : https://www.blackhat.com/docs/us-16/materials/us-16-Oh-The-Art-of-Reverse-Engineering-Flash-Exploits-wp.pdf
In this series of posts we’re going to demonstrate how modern CFI implementations can be circumvented.
How hackers can make variations within one campaign - as seen in a recent customer attack we blocked.
Typically phishing links are sent directly inside the email body, but now attackers are embedding their phishing links inside an email attachment instead.Show More