Deep Analysis of CVE-2014-0502 – A Double Free Story

A lot has already been said about CVE-2014-0502, the Adobe Flash Player zero-day that was part of a targeted attack that infected several nonprofit organizations' websites. Several interesting aspects of the exploit were covered in various blog posts; including its use of a .gif image file containing shellcode, OS language and version checking for custom ROP gadgets, etc.

After spending quite some time investigating the file ourselves, we felt that the vulnerability was in itself very interesting and a thorough analysis of it would be interesting to share. Today we finally found some time to write it all up, so here it goes!

The vulnerability is a double-free vulnerability caused by a bug in how shared objects are handled by Adobe Flash Player. The following piece of code is responsible for triggering the vulnerability:

001- ActionScript

What are Workers in flash?

Not too long ago Adobe added support for background threads, which are represented in ActionScript as "Workers." The purpose of a worker is to give the developer the possibility of running simultaneous tasks without freezing the Flash application. In simple words, this is an abstraction layer to support multi-threading within Flash.

What is a Shared Object?

Adobe's documentation includes the following details about shared objects:

[1] "A local shared object, sometimes called a "Flash cookie," is a data file that can be created on your computer by the sites you visit. Shared objects are most often used to enhance your web-browsing experience. For example, by allowing you to personalize the look of a website that you frequently visit." (Adobe)

[2] "When an application closes, shared objects are flushed, or written to a disk. You can also call the flush() method to explicitly write data to a disk." (Adobe)

Shared Objects give the ability to save offline data generated by the application. This data will remain available the next time the user loads the application.

What Actually Happens?

During the termination of a worker (Adobe Flash Player instance), all shared objects are (obviously) destroyed, but the shared object's data is flushed to the disk immediately before the objects are destroyed. The destructor procedure of a shared object first calls an "Exit" function that performs two important checks:

  1. Check for the "Pending Flush" flag – which indicates whether or not there is data that needs to be flushed to disk. In our sample the ActionScript code sobj.data.log='data…' turns the "Pending Flush" flag on.
  2. Check the domain's "Maximum Storage" settings (if not explicitly set for the hosting domain, this will be 100KB by default).

If the first check passes and the process tries to flush the data (to the path %appdata%\ Macromedia\Flash Player\#SharedObjects on disk), it frees the object block when finished whether it succeeds or not. Just before it proceeds to the freeing part, Adobe Flash Player performs garbage collection and decides that the "record" shared object is no longer in use (due to the fact that other related objects were freed.) It therefore starts another free operation whilst the first free operation is still in process, once again the "Exit" function (responsible for flushing) is executed. The first check is passed again since the "Pending Flush" flag is still on and the code attempts to flush the file to disk. This operation fails, since we exceed the 100KB per domain limit. Due to this failure the "Exit" function finishes without clearing the "Pending Flush" flag. Next, still in the inner free operation, the heap block that stored our "record" shared object is freed resulting in a double free.

Deeper Analysis

Here you can see the stack trace of the crash reproduced in our lab environment:

002-crash

From the crash we can figure out that the EAX register is supposed to point to the freed object's vftable(virtual functions table) and call the function at offset 0x8. By looking at this code in IDAPro we can see that the ESI register holds a reference to the freed object itself.

003-1 - UAF

After adapting the sample (the ROP part) to lead the execution to the 0xCCCCCCC address and setting a breakpoint at the crash: bp Flash32_12_0_0_44+0x102f4b, we ran the modified sample.

We noticed that we hit our breakpoint a couple of times before it crashes, in particular (as shown in the screenshot below) the exact same object – the "record" object we created in ActionScript (as a result of the line SharedObject.getLocal("record")) is used first to executes a function from its vftable in a legitimate way. Following that, the player crashes for attempting to execute the same instruction when the object was already freed.

The screenshot below shows both hits of our breakpoint. The ESI register is pointing to the same object ("record") in both cases. You can clearly see that on the second hit the object has already been freed:

004-crash breakpoints

Ok so now we know which object was freed, but what caused it?

In order to understand what caused the bug, let's have another look at Adobe's documentation for shared objects:

"Whenan application closes, shared objectsare flushed, or written to a disk. You can also call the flush() method to explicitly write data to a disk." (Adobe)

This means that a shared object will basically try to flush data to the disk only when the shared object is destroyed, unless you explicitly use the flush() method.

After analyzing the functions from the crash call stack, we noticed that the crash was a result of the "record" shared object's destructor code, which was triggered by the termination of the worker. This means that before freeing the object the destructor calls a function that will eventually cause a crash (here we can assume that this function is somehow related to flushing the shared object before it is destroyed).

With that in mind, let's have a look at the function before our program crashed:

005-CrashIDAPro2

The diagram above shows that this "flushing" function will not proceed unless the "record" object has a "Pending Flush" flag up (offset 0xc1).

In this sample, the "record" object has data pending to be flushed so this flag will be up upon reaching the code in the diagram above. Just after the first check of the flag, Adobe Flash Player will perform garbage collection and determine that the "record" shared object is no longer in use (as described above). This causes an additional call to the destructor of the "record" object from within the current destructor. This flow explains why the breakpoint discussed earlier is hit twice.

At this point the destruction process continues and attempts to flush the data to disk as well as clear the "Pending Flush" flag. This attempt fails because the data exceeds the maximum allowed size for this domain (100KB by default), and therefore the flag stays up. Once the inner destructor finishes the failed flushing attempt it will free the "record" object but leave the "Pending Flush" flag up.

The following screenshot shows the double free flow caused by the inner destructor:

006-double free with callstack

This specific flow will result in the "Pending Flush" flag being up when the object was already freed, and therefore Adobe Flash Player will try to execute a function from a dereferenced pointer otherwise known as remote code execution.

An attacker can easily create a specially crafted flash file and use this vulnerability to control the execution flow and deliver a payload of their choice.

We hope that you enjoyed this analysis, and that you could follow the described flow even though the lack of symbols makes the analysis a bit more complex.

As always, remember to keep your Adobe Flash Player up-to-date in order to protect yourself.

Trustwave's Secure Web Gateway costumers are protected against this attack with the latest Security Update.

This blog post was co-authored by Ben Hayak and Anat Davidi.

Trustwave reserves the right to review all comments in the discussion below. Please note that for security and other reasons, we may not approve comments containing links.