Technical Analysis of CVE-2014-0515 Adobe Flash Player Exploit

At the end of April, Kaspersky reported an ITW exploit that was abusing an Adobe Flash Player zero-day vulnerability at the time (CVE-2014-0515). The vulnerability was known to be inside the Pixel Bender parser in Adobe Flash Player. I had time to look deeper into how the vulnerability works and how control of the code is acquired using this vulnerability.

Figure 1 shows the overall structure of the exploit sample I tested. I used the swfdump tool from swftools.  Tag 057 contains binary data that is used as Pixel Bender bytes later in ActionScript Byte Code (tag 052).

 

fig01.png

Figure 1 Overall structure of the exploit SWF file

 

Heap spray

As usual, the exploit starts with a heap spray. Similar to the exploit I discussed in my previous blog on the CVE-2014-1776 IE vulnerability, this one also uses the Vector data type to achieve fine control of memory layout and data. But there are a couple of differences to the method I described with the IE exploit.

 

Laying out memory

First, it sprays the heap with Vector.<int>  with a size of 0x22 (Figure 2). Each Vector.<int> has the following format:

       Vector size (4 bytes) + extra header (4bytes)  + int (4 bytes) array of 0x22

 

The vector is pushed as a member of the Array data type, and the size of the array becomes 0x10000.

 

 fig02.png

Figure 2 Allocating 0x22 size of Vector.<int> array

 

Figure 3 shows the actual memory data when this heap spray occurs. Vector.<int> members are mostly adjacent to each other and have a size of 0x90 (8 bytes header + 4*0x22 bytes of int array).  The first 4 bytes of each member is 0x22 in DWORD, which indicates the Vector size.

 

 fig03.png

Figure 3 Memory raw data for Vector.<int>

 

 

Making holes

Now that the basic layout of the heap spray is done, the exploit tries to shrink the size of each Vector to 0 (Figure 4). The way the Flash internal logic works makes holes between each members. Instead of allocating a new Vector array with size 0, it just re-uses previous memory layout resetting length field to 0.

 

fig04.png

Figure 4 Set vector lengths to 0

 

The result of this length reset is shown in Figure 5. The Vector size has shrunk to 0 and each Vector member uses only 8 bytes of memory, leaving an extra 0x88 bytes free for use. The size of 0x88 is very much intentional here and it becomes important later when the actual exploit happens.

 

 fig05.png

Figure 5 Memory raw data for Vector.<int> after resetting length to 0

 

 

Making bigger holes

 

Aside from making 0x88 bytes of heap spray holes, the exploit tries to make more holes in parts of the heap spray area. (Figure 6) The whole creation works by assigning 0x100 length to some of the Vectors. This makes the existing array in the heap spray area to be freed and allocated in another place.

 

fig06.png

Figure 6 Increasing size of some array members

 

 

Figure 7 shows the memory layout before these holes are created. There are 0x88 bytes of freed memory.

 

fig07.png

Figure 7 Before increasing the array member size

 

 

When this additional hole creation happens, some parts of the heap area look like Figure 8. This makes 0x118 bytes of freed memory area in the heap spray region. I tested with the exploit and this bigger hole is essential to the exploit because during the exploit process, the code uses this empty area rather than creating new heap memory, and it is related to whether the previous smaller holes with 0x88 size are used further in the code.

 

fig08.png

Figure 8 After increasing the array member size

 

The vulnerability – Pixel Bender Parser Issue

 

First memory corruption

The vulnerability is inside the code where the Flash Player parses the Pixel Bender data from the exploit code. When I tested with a debugger, I found that the DWORD value at offset 0xEA of Pixel Bender data is responsible for triggering the vulnerability. (Figure 9)

 

fig09.png

Figure 9 Malformed Pixel Bender data triggers the vulnerability

 

To analyze how this memory region breaks Pixel Bender parsing code, I used the dpbj tool, which can be utilized to disassemble a raw Pixel Bender binary format file. The author has released the source code of the tool, aside from just the binary. I found this project very useful, particularly when there was no good public documentation on the internal format of a Pixel Bender binary available. Figure 10 shows the code where the memory area responsible is located.

 

 fig10.png

Figure 10 Pixel Bender code triggers the vulnerability

 

The metadata  of defaultValue is intended to be just 4 bytes long, but the code tries to convert all of its 16 arguments and puts them in the memory array, incurring a memory corruption error. Figure 11 shows how the sel instruction and the defaultValue metadata is saved. The original binary data is parsed and translated into a bytecode that is later used for further operations. The 2nd value from the defaultValue argument overwrites the index value inside the sel instruction’s byte code in memory.

 

fig11.png

Figure 11 Memory corruption

 

The code that overwrites this sel instruction area is shown in Figure 12. The fstp instruction saves a value to a memory location designated by its operand. In this case, the original value of 0x000B3880 is saved directly to 0x000B3880 after endian change. The instruction at 0x167EC63 shows that the memory location will be increased by 0x14. The first defaultValue member falls inside a valid memory location but the values from the 2nd argument are inside the memory of the sel instruction’s byte code area.

 

 fig12.png

Figure 12 Code responsible for memory corruption

 

Second memory corruption

The first memory corruption corrupts the other instruction’s byte code. The corrupt byte code is interpreted and run. Figure 13 shows the code that interprets the byte code op code.

 

fig13.png

Figure 13 Switch on op code

 

Figure 14 shows the code where each instruction’s operand is passed to the set_array_value function.

 

fig14.png

Figure 14 Code setting register values

 

The code that actually performs additional memory corruption is shown in Figure 15. The function is called for each instruction and when the corruption happens, the arg_index value is 0x000B3880 - which is from the corrupted memory area.  The index value is converted to an offset in the memory that is used later to save some data. From the instruction at 0x0167BC78, ecx is a pointer to a data structure on the memory and edi is an offset calculated from arg_index. The Eax value is a little bit tricky. The value is returned from a previous call to sub_1909EE0 which returns a pointer to a memory location.

 

 fig15.png

Figure 15 Memory corruption code

 

An interesting thing happens here. From ecx (base pointer), edi (offset), and eax (value to overwrite), the attacker can control edi for sure. Also, as a consequence of using the previously mentioned heap spray technique, there are a lot of memory holes with size 0x88 on the heap. The data structure pointed to by ecx has a size smaller than 0x88 and there is a strong chance that the pointer will fall in one of the holes on the heap spray area. So, now the attacker can control ecx and edi somehow. But, eax can be a random (but valid) memory location on the heap. The attacker doesn’t have control of it. Here’s an interesting fact; the attacker doesn’t need to control eax. The fact that eax is always a valid pointer makes it a value bigger than some range of values (probably bigger than 0x22+1). Figure 16 shows the memory state just before this memory corruption occurs.

 

 fig16.png

Figure 16 Base address points to one of the heap spray holes with size 0x88

 

 

Figure 17 shows the memory state when the memory corruption happened through the related instructions. You can see that the length field of a[x+1] is overwritten with a pointer value. The value can be random, but it is always bigger than 0x22+1. This makes it possible for the attacker to corrupt the next Vector’s length field.

 

fig17.png

Figure 17 After memory corruption, a[x+1].length will be a huge value

 

Exploitation

 

Additional Vector corruption

Now a[x+1].length becomes a huge value.  It can corrupt additional Vector elements and the exploit can have full access to process memory. It corrupts the next Vector’s length field to 0x40000001. (Figure 18)

 

 fig18.png

Figure 18 ActionScript code to corrupt next vector's size to 0x40000001 (corrupt_index=x+1)

 

Now a[x+2].length becomes 0x40000001. Figure 19 shows the state when this additional corruption happens. From a[x+2], the exploit can use arbitrary index values to access any memory locations.

 

fig19.png

Figure 19 Memory state after additional Vector length corruption

 

FileReference spraying & corruption

The exploit creates 64 FileReference objects on the heap. (Figure 20)

 

fig20.png

Figure 20 Spraying FileReference objects

 

It searches for the FileReference objects from the heap using some conditions and when it finds one, it overwrites its function pointer. If we say v = a[x+2], then it will assign a v[2] address as the function pointer. The memory area around this fake function table is constructed from ActionScript, as shown in Figure 21.

 

fig21.png

Figure 21 Building a fake FileReference function table and arguments

 

Memory permission change

The exploit can now control data for the FileReference object’s function pointers. The exploit searches for the FileReference object on the heap area by heuristic method and when it locates one, it will replace the function table pointer address to a fake address. (Figure 22)

 

 fig22.png

Figure 22 Modifying FileReference object function table pointer

 

Figure 23 shows the fake function table with fake arguments. The v[7] is the pointer where the cancel method is called. Figure 24 shows the ActionScript code that calls the cancel methods.

 

 fig23.png

Figure 23 Fake FileRefence function table

 

 

fig24.png

Figure 24 Running the cancel method from the fake FileReference function table

 

The code pointed to by v[7] is shown in Figure 25. This function uses an argument from the function table pointer. The eax points to v[2] location when it is called. The instructions at 0x6EB8D5D8 and 0x6EBD5DB show that two arguments from eax-8 (v[0]) and eax-4 (v[1]) are used, which are interpreted as the size and address of the target memory.

 

fig25.png

Figure 25 Code from Flash main binary that is used for v[7]

Further into the call to the call_virtual_protect function, it eventually calls the VirtualProtect API to give execute permission to target memory. By reusing code from the Flash binary itself, the exploit defeats DEP.

 

fig26.png

Figure 26 Code inside call_virtual_protect

 

 

 

Executing shellcode

With the exploit already defeating ASLR by full memory access and defeating DEP by reusing Flash binary code that can change permission on a designated memory location, the rest is relatively easy. The exploit builds the shellcode first (Figure 27), and modifies v[7] to a shellcode address. The v[7] is the function pointer to a cancel method. (Figure 28)

 

fig27.png

Figure 27 Build shellcode

 

 

fig28.png

Figure 28 Replacing the function pointer for the cancel method inside the fake FileReference object and running it

 

When additional cancel methods are called, the fake function table looks like Figure 29. The address pointed to by v[7] has already been made executable by previous calls to the cancel method that calls VirtualProtect API. (Figure 23)

 

fig29.png

Figure 29 Replaced v[7], function pointer for cancel

 

Summary

CVE-2014-0515 is a really interesting vulnerability with the Pixel Bender engine. The metadata parser is expecting fixed-size data, but if you supply larger data intentionally, it can overflow and overwrite the operand byte code of adjacent instructions. This memory corruption leads to further memory corruption involving byte code interpretation. Even if you can only control where to write the data, not what to write, you can still use Vector length corruption to achieve further access to lower level memory. With full access to process memory, ASLR and DEP can be easily defeated. Everything looks very similar to the CVE-2014-1776 exploit we talked about previously. But, the style of the exploit itself is quite different. The way the heap spray is laid out is quite different and the way how it finds gadget and function addresses is different. We might well see similar or variant styles of this exploit in the future.

 

 

Comments
Steve Sims(anon) | ‎05-31-2014 04:25 PM

Awesome write-up. Thanks for posting. It's a really neat bug and cleverly exploited.

Leave a Comment

We encourage you to share your comments on this post. Comments are moderated and will be reviewed
and posted as promptly as possible during regular business hours

To ensure your comment is published, be sure to follow the Community Guidelines.

Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.
Search
Showing results for 
Search instead for 
Do you mean 
About the Author
Twitter: @ohjeongwook .


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation