Research

Breaking CFI: Exploiting CVE-2015-5122 using COOP.

By Oshri Sela and Shlomi Levin
April 11, 2018

Introduction.

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.

The Vulnerability.

CVE-2015-5122 is a use-after-free vulnerability that was used by Hacking Team to exploit Adobe Flash Player (<= 18.0.0.203). 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.

CFI Constraints.

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”.

COOP.

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, ATL::CComControl::CreateControlWindow and CLibrariesFolderBase::v_AreAllLibraries in order.

vfgadget #1.

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 CreateControlWindow.
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:

Thunk data:
c7 44 24 04 [DWORD thisPointer] e9 [DWORD WndProc]

disas:
mov DWORD PTR [esp+0x4], thisPointer
jmp WndProc

More information about the ATLThunk can be found here.
Note, this method is relevant to Windows 7 Build 7601 SP1 as mentioned here, and is supposed to be changed in a later ATL version.

What’s really interesting with this vfgadget is that the thunk is written to a page that is allocated by VirtualAlloc with 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 EXECUTE_READWRITE):

1

Other considerations regarding this vfgadget are:

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 ATL::CComControl::CreateControlWindow.

vfgadget #2.

To find such a vfgadget we searched shell32.dll with an IDA script with the following constraints:

  1. It’s a virtual function (it is xref’d from a vtable)
  2. It has one indirect call that involves registers
  3. It receives 3 arguments like our Magic method (detected by “retn X” opcode)
  4. It passes 2 arguments to the indirect called function (detected by subtracting the pops count from the pushes count)
  5. It’s smaller than 0x30 bytes – after all, we don’t want a long and complex vfgadget.

We looked over the script result and chose CLibrariesFolderBase::v_AreAllLibraries.
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).

2

Combining it all together.

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 CreateControlWindow vfgadget:

// 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);

3

When our COOP flow finishes, we can read the created READWRITE_EXECUTE allocated page pointer. It will be stored at 0x5c offset from the “this”:

// 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

4

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.

Additional Thoughts.

There are several ways to implement exploits using the COOP technique. During the research we also found 5 vfgadgets in flash DLL (18.0.0.203) that do the following:

  1. Create 2 directories, under any path of our choice.
  2. Write a file to a path under the name “digest.s”.
  3. Call MoveFileEx API 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).
  4. Call SetCurrentDirectory API with a controllable path parameter. We can use it to set the process’s current directory to the path containing our payload file.
  5. Call LoadLibrary to “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.

Conclusion.

We have successfully demonstrated how COOP can be used to circumvent modern CFI solutions by conforming to the constraints mentioned above

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
[10] : https://www.blackhat.com/docs/us-16/materials/us-16-Oh-The-Art-of-Reverse-Engineering-Flash-Exploits-wp.pdf

Share the joy
Stay a step ahead

Research & News.

Asset 5
Case Study

Case Study: Advanced Attack Analysis

Uri Ahronovich

Perception Point’s platform recently caught an advanced threat directed at one of our customers within a Microsoft Word file.

Asset 5
Article

Why Relays, AVs, & Sandboxes just aren’t enough anymore

By Jonathan Levy

Over the years organizations have implemented multi-layered approaches to protect their email, due to the fact that no single solution has provided sufficient results.

Asset 5
Research

Breaking CFI: Exploiting CVE-2015-5122 using COOP

By Oshri Sela & Shlomi Levin

In this series of posts we’re going to demonstrate how modern CFI implementations can be circumvented.

Show More
Contact Us

Schedule a trial today.

Yes, I would like to receive email communications from Perception Point. I understand I can unsubscribe at any time.