# -*- coding: binary -*-
module Msf

###
#
# This module provides service-specific methods for the DCERPC exploit mixin
#
###
module Exploit::Remote::DCERPC_EPM

	# Contact the endpoint mapper of the target host and find the transport
	def dcerpc_endpoint_find_tcp(host, uuid, vers, transport)
		res = dcerpc_endpoint_list()
		return nil if not res

		res.each do |ent|
			if (ent[:uuid] == uuid and ent[:vers] == vers and ent[:prot] == 'tcp')
				return ent[:port]
			end
		end

		nil
	end

	# Contact the endpoint mapper of the target host and find the transport
	def dcerpc_endpoint_find_udp(host, uuid, vers, transport)
		res = dcerpc_endpoint_list()
		return nil if not res

		res.each do |ent|
			if (ent[:uuid] == uuid and ent[:vers] == vers and ent[:prot] == 'udp')
				return ent[:port]
			end
		end

		nil
	end

	# Contact the endpoint mapper and create a hash of all endpoints
	def dcerpc_endpoint_list
		res = []

		print_status("Connecting to the endpoint mapper service...")
		begin
			eps   = nil
			dport = nil

			[135, 593].each do |i|
				dport = i
				begin
					eps = Rex::Socket::Tcp.create(
					'PeerHost'  => rhost,
					'PeerPort'  => dport,
					'Proxies'   => proxies,
					'Context'   =>
						{
							'Msf'        => framework,
							'MsfExploit' => self,
						}
					)

					break

				rescue ::Exception
				end
			end

			if (not eps)
				print_status("Could not connect to the endpoint mapper service")
				return nil
			end

			eph = dcerpc_handle('e1af8308-5d1f-11c9-91a4-08002b14a0fa', '3.0', 'ncacn_ip_tcp', [dport])
			opt = { 'Msf' => framework, 'MsfExploit' => self }
			dce = Rex::Proto::DCERPC::Client.new(eph, eps, opt)

			hnd = nil

			while(true)

				# Placeholders
				info =
				{
					:type => nil,
					:port => nil,
					:host => nil,
					:pipe => nil,
					:prot => nil,
					:uuid => nil,
					:vers => nil,
					:note => nil
				}

				data = nil

				if(not hnd)
					# NULL handle to start with
					data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1].pack("V*")
				else
					# Break the loop early if we get another NULL handle
					break if hnd == [0, 0, 0, 0, 1].pack("V*")

					# Use the existing handle if we already have one
					data = [0, 0, 0, 0, 0].pack("V*") + hnd
				end

				ret = dce.call(2, data)

				if (
					dce.last_response == nil or
					dce.last_response.stub_data == nil or
					dce.last_response.stub_data.length < 40 or
					dce.last_response.stub_data[36,4] == "\xd6\xa0\xc9\x16"
				)
					# break from the parsing loop
					break
				end

				# Record the response data
				buf = dce.last_response.stub_data

				# Record the handle if needed
				hnd = buf[4, 20] if not hnd

				# Parse the response data
				nlen = buf[60, 4].unpack('V')[0]
				if (nlen > 1)
					info[:note] = buf[64, nlen - 1]
				end

				# Align the stub offset
				soff = nlen + 72
				while (soff % 4 != 0)
					soff += 1
				end

				# Determine number of records
				rcnt = buf[soff, 2].unpack('v')[0]
				soff += 2

				# Parse the data from the stack
				1.upto(rcnt) do |i|
					rlen = buf[soff, 2].unpack('v')[0]
					soff += 2

					if (i == 1)
						info[:uuid] = Rex::Proto::DCERPC::UUID.uuid_unpack(buf[soff+1, 16])
						info[:vers] = buf[soff+17,2].unpack('CC').map{|s| s.to_s}.join(".")
					end

					if (i > 3)
						info[:type] = buf[soff, 1].unpack("C*")[0]
					end

					soff += rlen

					xlen = buf[soff, 2].unpack('v')[0]
					soff += 2

					case info[:type]
					when nil

					# TCP
					when 7
						info[:prot] = 'tcp'
						info[:port] = buf[soff, 2].unpack('n')[0]

					# UDP
					when 8
						info[:prot] = 'udp'
						info[:port] = buf[soff, 2].unpack('n')[0]

					# ADDR
					when 9
						info[:host] = buf[soff, 4].unpack('C4').join('.')

					# PIPE
					when 15
						info[:prot] = 'pipe'
						info[:pipe] = buf[soff, xlen].unpack("a*")[0]

					# LRPC
					when 16
						info[:prot] = 'lrpc'
						info[:pipe] = buf[soff, xlen].unpack("a*")[0]

					# NETBIOS
					when 17,24
						info[:host] = buf[soff, xlen].unpack("a*")[0]

					# HTTP
					when 31
						info[:prot] = 'http'
						info[:port] = buf[soff, 2].unpack('n')[0]

					# DYNAMIC?
					when 22
						# not parsed
					else
						print_status("EPM unknown type: #{info[:type]} #{buf[soff, xlen].unpack("H*")[0]}")
					end

					soff += xlen
				end

				info[:pipe].gsub!("\x00", '') if info[:pipe]
				info[:host].gsub!("\x00", '') if info[:host]

				res << info

				# Handle a buggy response from a Likewise server that can result in a loop otherwise
				break if hnd == [0, 0, 0, 0, 0, 0, 0, 0, 0, 1].pack("V*")
			end

		rescue ::Interrupt
			raise $!

		rescue ::Exception => e
			print_status("Could not obtain the endpoint list: #{e}")
			res = nil
		end

		res
	end

end
end

