Hi everyone,
Back from holidays I was informed about a zero-day exploit (CVE-2011-0609) in the wild (now patched) targeting Adobe Flash, it seems that criminals never take holidays!
As we had a copy of the SWF malware sample embedded in Excel files, it was a great opportunity to go deeper into Flash's JIT code and test recent Haifei Li's [1] methods to exploit Flash vulnerabilities on Windows 7 and to bypass ASLR/DEP via IEEE-754.
In this blog, we will share our binary analysis of the vulnerability and how we achieved a reliable exploitation on Windows 7 with ASLR/DEP bypass.
1. Technical Analysis of the Vulnerability
Based on 'crsenvironscan.xls' and 'addLabel.swf' spotted by Bugix [2], it was not that difficult to get a simplified repro. Actually, it seemed that the one who designed this exploit did not even care to simplify his proof-of-concept since more than 100 differences existed between the safe ABC section and the evil one.
After having loaded the FLA source used to compile 'addLabel.swf', we figured out that the root cause of the vulnerability came from an invalid jump location read from an 'if' statement.
By specifically manipulating jump sequences in an Action Script byte code, it is possible to force the JIT code to do an "object confusion". Specifically, it is possible to generate a valid byte code in which the same property or method could be accessed by two different objects.
The following harmless code will be used to demonstrate the vulnerability:
package poc {
public class safe {
public function bla():ByteArray {
return new ByteArray();
}
public function safe() {
var tl:ByteArray = (1 == 0) ? bla() : (1 == 0) ? bla() : bla();
var t:String = "AAAAAAAAAA&AAAAAAAAAAAAA";
t.length;
}
}
}
|
First of all, let's see how the AS3 is compiled:
function poc::safe():*
{
0 getlocal0
1 pushscope
2 pushnull
3 coerce flash.utils::ByteArray
5 setlocal1
6 pushnull
7 coerce_s
8 setlocal2
9 getlocal0
10 constructsuper (0) // var tl:ByteArray
12 pushbyte 1
14 pushbyte 0
16 ifne L1 //if (1 == 0)
20 findpropstrict bla
22 callproperty bla (0)
25 coerce flash.utils::ByteArray // tl = bla()
27 jump L2
L1:
31 pushbyte 1
33 pushbyte 0
35 ifne L3 //if (1 == 0)
39 findpropstrict bla
41 callproperty bla (0)
44 coerce flash.utils::ByteArray // tl = bla()
46 jump L2
L3:
50 findpropstrict bla
52 callproperty bla (0)
55 coerce flash.utils::ByteArray // tl = bla()
L2:
57 coerce flash.utils::ByteArray
59 setlocal1
60 pushstring "AAAAA&AAAA"
62 setlocal2
63 getlocal2
64 getproperty length // t.length
66 pop
67 returnvoid
}
|
As we can see, label L2 can only be reached after having executed "coerce flash.utils::ByteArray" which pushes a ByteArray object on the stack.
Now let's change the jump target at line 46 and make it point to line 62. This gives the following result:
39 findpropstrict bla
41 callproperty bla (0)
44 coerce flash.utils::ByteArray
46 jump L4
L3:
50 findpropstrict bla
52 callproperty bla (0)
55 coerce flash.utils::ByteArray
L2:
57 coerce flash.utils::ByteArray
59 setlocal1
60 pushstring "AAAAA&AAAA"
L4:
62 setlocal2
63 getlocal2
64 getproperty length
66 pop
67 returnvoid
|
As we can see, if Flash reaches line 39, it pushes a "ByteArray" object on the stack and jumps to line 62 where it calls the "length" property. This modification is accepted by the Verifier since "String" and "ByteArray" objects share indeed a "length" property. This would have failed for example with the "ByteArray.endian" property since "String" objects do not implement such a property.
The atom confusion typically occurs here. The resulting JIT code to call the "length" property is actually designed for a "String" object, and not for a "ByteArray" object. Let's see now how "String" and "ByteArray" objects are represented in memory.
The "String" object represented on Figure 1 begins with a dword pointing to a VTable and contains a pointer to the string at offset +8. Notice also that its length is recorded at offset +10h:
Figure 1 - String object representation
A "ByteArray" object is more complex (Figure 2). The data contained in the array can be found by dereferencing the pointer at offset +10h and then dereferencing the pointer at +24h. The length of the object is this time recorded at offset +44h:
Figure 2 - ByteArray object representation
As a result, Flash does not use the same JIT code to get the value of the "length" property. Figure 3 shows the code getting the length of a String. Basically, Flash pushes the String object to the stack and then reads the dword at offset 10h (not shown here):
Figure 3 - Call for String.length
Figure 4 shows the lines needed to get "ByteArray.length". This piece of code is different because Flash dereferences at offset +8 a pointer to an object to control the call at 0x0187CD74:
Figure 4 - Call for ByteArray.length
As a result, if an object confusion occurs between the "ByteArray" and the "String" objects, two different behaviours may occur. If the JIT code is designed for a "String" object, "call eax" returns the pointer located at offset +10h, which equals 0x01345118 in this particular case. This can then be used to disclose a pointer to an attacker. On the other hand, if the JIT code is designed for a "ByteArray" object, the following occurs (Figure 5):
Figure 5 - Call for ByteArray.length with object confusion
2. Advanced Exploitation on Windows 7 (ASLR/DEP Bypass via IEEE-754)
How to exploit this issue on Windows 7 and bypass DEP and ASLR? Not so easy, even if you attended Haifei Li's talk at CanSecWest or saw his slides.
The first step consists in reading the pointer at offset +10h in the "ByteArray" object. In the next example, this pointer equals 0x0518C0B8 (see Figure 6). Since this pointer is located in the Flash heap, it cannot be directly used to disclose the Flash module base address.
However the object at this location has at least two interesting pointers. The first one (offset 0) points to the ".rdata" section of "Flash10n_ocx". Knowing this value is enough to get the module base address, since it is located at +0x00555710 in "Flash10n_ocx". Besides, the pointer at offset +24h points to the content of the "ByteArray", so knowing this value makes the attacker knows where is its shellcode.
Figure 6 - Data located at 0x0518C0B8
How to read data at 0x0518C0B8? Here comes the trick of IEEE-754. A Number object can be instantiated by the following line:
var n:Number = new Number(123.456);
|
By doing so, Flash builds an IEEE-754 representation of 123.456 in memory which is then encoded on 8 bytes. Moreover, the next syntax makes Flash build a Number according to the content of the object submitted:
var o;
var n:Number = new Number(o);
|
As a result, if the exploit writer manages to confuse Flash by using 0x0518C0B8 instead of an object, it becomes possible to read the 8 bytes located at 0x0518C0B8 (p):
var o = p;
var z:Number = new Number(o); // z is the IEEE-754 representation of 04 B9 57 08 04 B9 57 10!
var b:ByteArray = new ByteArray();
b.writeDouble(z);
var res:uint;
res = b[4]*0x1000000 + b[5]*0x10000 + b[6]*0x100 + b[7];
this.flashBase = res - 0x555710; // return Flash10n.ocx base address
|
Obviously, the same can be done with the pointer at offset +24h, to read the address of the byte array.
Using this memory disclosure method, we have created a highly reliable exploit for Windows 7 (and Vista/XP) with ASLR and DEP bypass, and without heap spraying or a statically loaded module, and without JavaScript.
© Copyright VUPEN Security
Previous Research Blog Entries
2010-12-21: Technical Analysis of Exim "string_vformat()" Remote Buffer Overflow Vulnerability
2010-10-18: Technical Analysis of the Windows Win32K.sys Keyboard Layout Stuxnet Exploit
2010-09-09: Technical Analysis of the Adobe Acrobat / Reader Buffer Overflow 0-Day Exploit