##
# 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::Auxiliary

	include Msf::Exploit::Remote::Tcp
	include Msf::Auxiliary::Scanner
	include Msf::Auxiliary::Report

	def initialize
		super(
			'Name'        => 'H.323 Version Scanner',
			'Description' => 'Detect H.323 Version.',
			'Author'      => 'hdm',
			'License'     => MSF_LICENSE
		)

		register_options(
			[
				Opt::RPORT(1720),
			], self.class)
	end

	def run_host(ip)

		remote_display    = nil
		remote_product_id = nil
		remote_version_id = nil
		remote_vendor_id  = nil
		remote_protocol   = nil

		begin

		# Wrap this in a timeout to prevent dead services from
		# hanging this thread.
		Timeout.timeout( call_timeout) do

		connect

		caller_name = "SYSTEM\x00"
		h323_id     = Rex::Text.rand_text_alpha(3)
		vendor_id   = Rex::Text.rand_text_alpha(32)
		caller_host = Rex::Socket.source_address( ip )
		caller_port = rand( 32768 ) + 30000
		callee_host = rhost
		callee_port = rport
		conf_guid   = Rex::Text.rand_text(16)
		call_guid   = Rex::Text.rand_text(16)

		pkt_setup = h323_setup_call(caller_name, h323_id, vendor_id, callee_host, callee_port, caller_host, caller_port, conf_guid, call_guid)

		res = sock.put(pkt_setup) rescue nil
		if not res
			disconnect
			return
		end

		cnt = 0
		while( true )
			info = read_packet
			break if not info

			# The remote side of the call disconnected us
			break if info[:type] == @@H323_STATUS_RELEASE_COMPLETE

			remote_display     = info[40].strip if info[40]
			remote_product_id  = info[:product_id].strip if info[:product_id]
			remote_version_id  = info[:version_id].strip if info[:version_id]
			remote_protocol    = info[:protocol_version].strip  if info[:protocol_version]

			if info[:vendor_id] and [nil, "Unknown"].include?( remote_vendor_id )
				remote_vendor_id   = info[:vendor_id].strip
			end

			# Diagnostics
			# print_status("Host: #{rhost}:#{rport} => #{info.inspect}")

			# The remote side of the call was connected (kill it)
			break if info[:type] == @@H323_STATUS_CONNECT

			# Exit if we already received 5 packets from the server
			break if (cnt +=1) > 5

		end

		# Make sure the call was shut down cleanly
		pkt_release = h323_release_call(caller_name, h323_id, vendor_id, callee_host, callee_port, caller_host, caller_port, conf_guid, call_guid)
		sock.put(pkt_release) rescue nil

		# End timeout block
		end

		rescue ::Timeout::Error
		rescue ::Interrupt
			raise $!
		rescue ::Rex::ConnectionError, ::IOError, ::Errno::ECONNRESET, ::Errno::ENOPROTOOPT
		rescue ::Exception
			print_error("#{rhost}:#{rport} #{$!.class} #{$!} #{$!.backtrace}")
		ensure
			disconnect
		end

		if remote_vendor_id
			remote_product_id   = remote_product_id.to_s.gsub(/[^\x20-\x7e]/, '')
			remote_version_id   = remote_version_id.to_s.gsub(/[^\x20-\x7e]/, '')

			banner = "Protocol: #{ remote_protocol }  VendorID: #{ remote_vendor_id }  "

			if remote_version_id and remote_version_id.length > 0
				banner << "VersionID: #{ remote_version_id }  "
			end

			if remote_product_id and remote_product_id.length > 0
				banner << "ProductID: #{ remote_product_id }  "
			end

			if remote_display and remote_display.length > 0
				remote_display = remote_display.to_s.gsub(/[^\x20-\x7e]/, '')
				banner << "DisplayName: #{ remote_display }"
			end

			print_status("#{rhost}:#{rport} #{banner}")
			report_service(:host => rhost, :port => rport, :name => "h323", :info => banner)
		end

	end

	def read_packet
		begin
			::Timeout.timeout( read_timeout ) do
				ver = sock.read(2)
				return if not (ver and ver == "\x03\x00")

				bin = sock.read(2)
				return if not bin

				len = [ bin.unpack("n")[0] - 4, 0 ].max
				return if len == 0

				bin = sock.read(len)
				return if not bin

				f_desc, cref_len = bin.unpack("CC")
				cref_val = bin[2, cref_len]
				f_type = bin[2 + cref_len, 1].unpack("C")[0]

				return { :type => f_type, :call_ref => cref_val }.merge( read_ies(f_type, bin[ 2 + cref_len + 1, bin.length] ) )
			end
		rescue ::Timeout::Error
		end
		nil
	end

	def read_ies(mtype, data)
		r = { }
		i = 0

		while( i < (data.length - 1) )
			ie_type = data[i, 1].unpack("C")[0]
			break if not ie_type

			ie_len  = 0
			ie_data = ""

			case ie_type
				when @@H225_IE_USER_USER
					ie_len  = data[i+1, 2].unpack("n")[0]
					break if not ie_len

					ie_data = data[i+3, ie_len]
					break if not ie_data

					i = i + 3 + ie_len
				else
					ie_len  = data[i+1, 1].unpack("C")[0]
					break if not ie_len

					ie_data = data[i+2, ie_len]
					break if not ie_data

					i = i + 2 + ie_len
			end

			r[ ie_type ] = ie_data

			if ie_type == @@H225_IE_USER_USER
				r.merge!( ( read_user_user(mtype, ie_data) rescue {} ) )
			end
		end
		r
	end


	# This provides a weak method of decoding USER-USER PDUs. These are
	# actually PER-encoded ASN.1, but we take a few shortcuts since PER
	# encoding is such a pain.
	def read_user_user(mtype, data)
		r = {}

		# Identify the embedded version (2/3/4/5/6 commonly found)
		i = data.index("\x00\x08\x91\x4a\x00")
		return r if not i

		# Store the protocol version
		pver = data[i + 5, 1].unpack("C")[0]

		r[:protocol_version] = pver.to_s

		# Bump the index over the version
		i+= 6

		# print_line( Rex::Text.to_hex_dump( data[i, 32] ) )

		# Set a placeholder VendorID so this system will be reported
		r[:vendor_id] = "Unknown"

		# We use the version offset to identify the destination block location
		# This changes slightly based on the type of packet we receive
		case mtype
		when @@H323_STATUS_ALERTING, @@H323_STATUS_PROCEEDING

			if pver == 2 and data[i, 2] == "\x20\x00"
				r[ :vendor_id ] = "0x%.8x" %  ( data[i + 2, 4].unpack("N")[0] rescue 0 )
				return r
			end

			# Find the offset to the VendorID
			if data[i + 1, 1] != "\xc0"
				i+= 7
			end

			# Stop processing if we can't identify a VendorID
			return r if data[i + 1, 1] != "\xc0"

			# Otherwise just add 2 to the offset of the version
			i += 2

		when @@H323_STATUS_CONNECT

			# Bail early in some corner cases
			return r if data[i, 1] == "\x00"

			# Find the offset to the VendorID
			if data[i + 1, 1] != "\xc0"
				i+= 7
			end

			# Stop processing if we can't identify a VendorID
			return r if data[i + 1, 1] != "\xc0"

			i += 2

			return r
		else
			return r
		end

		# Extract the manufacturer ID
		r[ :vendor_id ] = "0x%.8x" %  ( data[i, 4].unpack("N")[0] rescue 0 )
		i+= 4

		# No Product ID / Version ID in versions less than 3 (unless special cased above)
		return r if pver < 3

		# Get the product_id length (-1)
		product_id_length = data[i, 1].unpack("C")[0] + 1
		i+= 1

		# Extract the product ID
		r[ :product_id ] = data[i, product_id_length]
		i+= product_id_length

		# Get the version ID length (-1)
		version_id_length = data[i, 1].unpack("C")[0] + 1
		i+= 1

		# Extract the version ID
		r[ :version_id ] = data[i, version_id_length]

		# Thats it for now

		r
	end

	def read_timeout
		10
	end

	def call_timeout
		30
	end


	@@H225_IE_BEARER_CAP   = 0x04
	@@H225_IE_DISPLAY      = 0x28
	@@H225_IE_USER_USER    = 0x7e  # Yes, really User-user


	@@H323_STATUS_ALERTING          = 0x01
	@@H323_STATUS_PROCEEDING        = 0x02
	@@H323_STATUS_SETUP             = 0x05
	@@H323_STATUS_SETUP_ACK         = 0x0D
	@@H323_STATUS_CONNECT           = 0x07
	@@H323_STATUS_RELEASE_COMPLETE  = 0x5a
	@@H323_STATUS_FACILITY          = 0x62


	def encap_tpkt(ver,data)
		[ ver, 0, data.length + 4 ].pack("CCn") + data
	end

	def encap_q225(desc, cref_value, msg_type, data)
		[ desc, cref_value.length, cref_value, msg_type].pack("CCA*C") + data
	end

	def encap_q225_standard(msg_type, data)
		encap_q225(0x08, [0x733f].pack("n"), msg_type, data)
	end

	def encap_q225_setup(data)
		encap_q225_standard(0x05, data)
	end

	def encap_q225_release(data)
		encap_q225_standard(0x5a, data)
	end

	def create_ie_byte(ie_type, data)
		[ie_type, data.length].pack("CC") + data
	end

	def create_ie_short(ie_type, data)
		[ie_type, data.length].pack("Cn") + data
	end

	def create_ie_bearer_capability(cap = 0x00038893)
		create_ie_byte( @@H225_IE_BEARER_CAP, [cap].pack("N")[0,3] )
	end

	def create_ie_display(name = "DEBUG\x00")
		create_ie_byte( @@H225_IE_DISPLAY, name )
	end

	def create_ie_user_user(data)
		create_ie_short( @@H225_IE_USER_USER, data )
	end

	#
	# This is ugly. Doing it properly requires a PER capable ASN.1 encoder, which is overkill for this task
	#
	def create_user_info(h323_id, vendor_id, callee_host, callee_port, caller_host, caller_port, conf_guid, call_guid)
		buff = "\x05" # Protocol descriminator: X.208/X.209 coded user information

		buff << "\x20\xa8\x06\x00\x08\x91\x4a\x00\x06\x01\x40\x02"

		# H323-ID
		buff << h323_id.unpack("C*").pack("n*")

		buff << "\x22\xc0\x09\x00\x00\x3d\x02\x00\x00\x00\x21"

		# VENDOR: 32 + 2 null bytes
		buff << [vendor_id].pack("Z32") + "\x00\x00"

		buff << "\x00"

		# Remote IP + Remote Port
		buff << ( ::Rex::Socket.addr_aton( callee_host ) + [ callee_port.to_i ].pack("n") )

		buff << "\x00"

		# Conference GUID
		buff << conf_guid

		buff << "\x00\xc5\x1d\x80\x04\x07\x00"

		# Local IP + Port
		buff << ( ::Rex::Socket.addr_aton( caller_host ) + [ caller_port.to_i ].pack("n") )

		buff << "\x11\x00"

		# Call GUID
		buff << call_guid

		buff <<
			"\x82\x49\x10\x47\x40\x00\x00\x06\x04\x01\x00\x4c\x10\xb5" +
			"\x00\x00\x26\x25\x73\x70\x65\x65\x78\x20\x73\x72\x3d\x31" +
			"\x36\x30\x30\x30\x3b\x6d\x6f\x64\x65\x3d\x36\x3b\x76\x62" +
			"\x72\x3d\x6f\x66\x66\x3b\x63\x6e\x67\x3d\x6f\x66\x66\x80" +
			"\x12\x1c\x40\x01\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc6\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc7\x90\x3c\x00\x00\x64\x0c\x10\xb5\x00\x00\x26\x25" +
			"\x73\x70\x65\x65\x78\x20\x73\x72\x3d\x31\x36\x30\x30\x30" +
			"\x3b\x6d\x6f\x64\x65\x3d\x36\x3b\x76\x62\x72\x3d\x6f\x66" +
			"\x66\x3b\x63\x6e\x67\x3d\x6f\x66\x66\x80\x0b\x0d\x40\x01" +
			"\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc7\x48\x31\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09" +
			"\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20" +
			"\x57\x69\x64\x65\x36\x80\x12\x1c\x40\x01\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc6\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc7\xa0\x26\x00\x00\x65\x0c\x10\x09\x00\x00\x3d\x0f" +
			"\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65" +
			"\x36\x80\x0b\x0d\x40\x01\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc7\x50\x1d\x40\x00\x00\x06\x04\x01\x00\x4c\x60\x13" +
			"\x80\x11\x1c\x00\x01\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc6\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc7\x13\x00\x00\x66\x0c\x60\x13\x80\x0b\x0d\x00\x01" +
			"\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc7\x00\x1d\x40\x00\x00\x06\x04\x01\x00\x4c\x20\x13" +
			"\x80\x11\x1c\x00\x01\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc6\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc7\x13\x00\x00\x67\x0c\x20\x13\x80\x0b\x0d\x00\x01" +
			"\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc7\x00\x23\x40\x00\x00\x06\x04\x01\x00\x48\x78\x00" +
			"\x4a\xff\x00\x80\x01\x00\x80\x11\x1c\x00\x02\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc8\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc9\x19\x00\x00\x68\x08\x78\x00\x4a\xff\x00\x80\x01" +
			"\x00\x80\x0b\x0d\x00\x02\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc9\x00\x22\x40\x00\x00\x06\x04\x01\x00\x48\x68\x4a" +
			"\xff\x00\x80\x01\x00\x80\x11\x1c\x00\x02\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc8\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc9\x18\x00\x00\x69\x08\x68\x4a\xff\x00\x80\x01\x00" +
			"\x80\x0b\x0d\x00\x02\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc9\x00\x22\x40\x00\x00\x06\x04\x01\x00\x48\x70\x4a" +
			"\xff\x00\x80\x01\x00\x80\x11\x1c\x00\x02\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc8\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc9\x18\x00\x00\x6a\x08\x70\x4a\xff\x00\x80\x01\x00" +
			"\x80\x0b\x0d\x00\x02\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc9\x00\x2c\x40\x00\x00\x06\x04\x01\x00\x48\xee\x00" +
			"\x00\x20\x9f\xff\x20\x50\x40\x01\x00\x80\x17\x1c\x20\x02" +
			"\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc8\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc9\x80\x04\x48\x08\x8d\x44\x22\x00\x00\x6b\x08\xee" +
			"\x00\x00\x20\x9f\xff\x20\x50\x40\x01\x00\x80\x11\x0d\x20" +
			"\x02\x00" +

			Rex::Socket.addr_aton( caller_host ) +

			"\x13\xc9\x40\x00\x04\x48\x08\x8d\x44\x01\x00\x01\x00\x01" +
			"\x00\x01\x00\x80\xfa\x02\x80\xef\x02\x70\x01\x06\x00\x08" +
			"\x81\x75\x00\x0d\x80\x1a\x80\x01\xf4\x00\x01\x00\x00\x01" +
			"\x00\x00\x01\x00\x04\x02\x05\x00\x48\x08\x8d\x44\x06\x60" +
			"\x01\x00\x01\x80\x0b\x80\x00\x00\x20\x20\xb5\x00\x00\x26" +
			"\x25\x73\x70\x65\x65\x78\x20\x73\x72\x3d\x31\x36\x30\x30" +
			"\x30\x3b\x6d\x6f\x64\x65\x3d\x36\x3b\x76\x62\x72\x3d\x6f" +
			"\x66\x66\x3b\x63\x6e\x67\x3d\x6f\x66\x66\x80\x00\x01\x20" +
			"\x20\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73" +
			"\x34\x20\x57\x69\x64\x65\x36\x80\x00\x02\x20\xc0\xef\x80" +
			"\x00\x03\x20\x40\xef\x80\x00\x04\x08\xf0\x00\x4a\xff\x00" +
			"\x80\x01\x00\x80\x00\x05\x08\xd0\x4a\xff\x00\x80\x01\x00" +
			"\x80\x00\x06\x08\xe0\x4a\xff\x00\x80\x01\x00\x80\x00\x07" +
			"\x09\xdc\x00\x00\x40\x9f\xff\x20\x50\x40\x01\x00\x80\x00" +
			"\x08\x83\x01\x50\x80\x00\x09\x83\x01\x10\x80\x00\x0a\x83" +
			"\x01\x40\x80\x00\x0b\x8a\x0c\x14\x0a\x30\x2d\x31\x36\x2c" +
			"\x33\x32\x2c\x33\x36\x00\x80\x01\x03\x03\x00\x00\x00\x01" +
			"\x00\x02\x00\x03\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00" +
			"\x00\x08\x02\x00\x09\x00\x0a\x00\x0b\x07\x01\x00\x32\x80" +
			"\x96\x61\x41\x02\x80\x01\x80"

		buff
	end

	def create_user_release_info(call_guid)
		"\x05" +
		"\x25\x80\x06\x00\x08\x91\x4a\x00\x05\x01\x11\x00" +
		call_guid +
		"\x02\x80\x01\x00"
	end

	def h323_release_call(caller_name, h323_id, vendor_id, callee_host, callee_port, caller_host, caller_port, conf_guid, call_guid)
		encap_tpkt(3,
			encap_q225_release(
				create_ie_display(caller_name) +
				create_ie_user_user(
					create_user_release_info(call_guid )
				)
			)
		)
	end

	def h323_setup_call(caller_name, h323_id, vendor_id, callee_host, callee_port, caller_host, caller_port, conf_guid, call_guid)
		encap_tpkt(3,
			encap_q225_setup(
				create_ie_bearer_capability() +
				create_ie_display(caller_name) +
				create_ie_user_user(
					create_user_info( h323_id, vendor_id, callee_host, callee_port, caller_host, caller_port, conf_guid, call_guid )
				)
			)
		)
	end
end
