#-------------------------------------------------------------------------------------------------------
# $Id$
# $Revision$
#-------------------------------------------------------------------------------------------------------
  
#-------------------------------------------------------------------------------------------------------
# Name: Passive Mapping Program
# Description: Discovery, Scanning, and Fingerprinting via Broadcast and Multicast Traffic
# Author: Gregory Pickett at gregory.pickett[at]hellfiresecurity[dot]com
# Version: 1.10
# GPLv3 License applies to this code
#-------------------------------------------------------------------------------------------------------

#-------------------------------------------------------------------------------------------------------
# Many thanks, Carlos Perez at carlos_perez[at]darkoperator.com
# You provided the code necessary to collect the packets needed for analysis
#-------------------------------------------------------------------------------------------------------

################## Variable Declarations ##################

@client = client

# Interval for Recording Packets
rec_time = 30

# Interface ID
int_id = nil

# List Interfaces
list_int = nil

# Log Folder
log_dest = nil
@exec_opts = Rex::Parser::Arguments.new(
	"-h"  => [ false, "Help menu."],
	"-t"  => [ true,  "Time interval in seconds between collection of packets, default 30 seconds."],
	"-i"  => [ true,  "Interface ID number where all packet capture will be done."],
	"-li" => [ false, "List interfaces that can be used for capture."],
	"-l"  => [ true,  "Specify an alternate folder to save PCAP file."],
	"-v"  => [ false, "Verbose mode which outputs enumerated Name Records and Notifications."],
	"-s"  => [ true,  "Amount of time spent in seconds to capture and profile on the remote network."],
	"-w"  => [ false,  "Write to a file instead of the database."]
)
meter_type = client.platform

# Operational Modes
@verbose = false
output = "database"

# Counts for Enumeration
@maxhosts = 255
@maxports = 60
@maxrecords = 60
@maxnotifications = 60	

# Seconds for Fingerprinting Collection
seconds = 300

# Type Field of Query and Answer
T_A = 1								# IPv4 host address
T_NS = 2							# Authoritative server
T_CNAME = 5							# Canonical name
T_SOA = 6							# Start of authority zone
T_PTR = 12							# Domain name pointer
T_MX = 15							# Mail routing information
T_TXT = 16							# Arbitrary human-readable text
T_AAAA = 28							# IPv6 host address
T_SRV = 33							# Services
T_ANY = 255							# Any

# Port Record Structure				
class PORT_DATA

	# Port values
	attr_accessor :portvalue, :portprotocol

	# Intialize port values
	def initialize

		@portvalue = 0
		@portprotocol = 0

	end

end

# Name Record (mDNS) Structure
class RECORD_DATA

	# Record values
	attr_accessor :name, :type, :rdata

	# Intialize record values
	def initialize

		@name = "0"
		@type = 0
		@rdata = "0"

	end

end

# Host Record Structure
class Host

	# Host attributes
	attr_accessor :address, :name, :type, :make, :model, :osname, :osversion, :hostports, :hostrecords, :hostnotifications

	# Intialize host attributes
	def initialize

		@address = "0"
		@name = "0"
		@type = "0"
		@make = "0"
		@model = "0"
		@osname = "0"
		@osversion = "0"
		@hostports = Array.new(60) { PORT_DATA.new }
		@hostrecords = Array.new(60) { RECORD_DATA.new }
		@hostnotifications = Array.new(60) { "0" }

	end
	
end	

# Host Records for Enumeration
@hosts = Array.new(@maxhosts) { Host.new }

# Socket and Buffer Settings
@szMultiAddress = "224.0.0.251"				# Multicast address for Multicast DNS
@iPortNumber = 5353					# Port number for Multicast DNS
@iMaxBuffer = 65536					# Maximum buffer size

# Definitions
recordtype = T_SRV					# Record type for TCP ports

################## Function and Subroutines ##################

# Usage Message Function
#-------------------------------------------------------------------------------
def usage
	print_line "Meterpreter Script for profiling a remote network via"
	print_line "packet capture on a remote target host."
	print_line(@exec_opts.usage)
	raise Rex::Script::Completed
end

# Wrong Meterpreter Version Message Function
#-------------------------------------------------------------------------------
def wrong_meter_version(meter = meter_type)
	print_error("#{meter} version of Meterpreter is not supported with this Script!")
	raise Rex::Script::Completed
end

# Function for creating log folder and returning log parameter
#-------------------------------------------------------------------------------
def log_file(log_path = nil)
	#Get hostname
	host = @client.sys.config.sysinfo["Computer"]

	# Create Filename info to be appended to downloaded files
	filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S")

	# Create a directory for the logs
	if log_path
		logs = ::File.join(log_path, 'logs', 'pmap', host + filenameinfo )
	else
		logs = ::File.join(Msf::Config.log_directory, "scripts", 'pmap', host + filenameinfo )
	end

	# Create the log directory
	::FileUtils.mkdir_p(logs)

	#logfile name
	logfile = logs + ::File::Separator + host + filenameinfo + ".raw"
	return Rex::FileUtils.clean_path(logfile)
end

# Function for Starting Capture
#-------------------------------------------------------------------------------
def startsniff(interface_id)
	begin
		#Load Sniffer module
		@client.core.use("sniffer")
		print_status("Starting Packet capture on interface #{interface_id}")
		#starting packet capture with a buffer size of 200,000 packets
		@client.sniffer.capture_start(interface_id, 200000)
		print_good("Packet capture started")
	rescue ::Exception => e
		print_status("Error Starting Packet Capture: #{e.class} #{e}")
		raise Rex::Script::Completed
	end
end

# Function for Recording Captured Packets
#-------------------------------------------------------------------------------
def packetrecord(packtime,logfile,intid,seconds)
	begin
		rec = 1
		print_status("Packets being saved in to #{logfile}")
		print_status("Packet capture interval is #{packtime} Seconds")
		
		start_time = ::Time.now
		# Inserting Packets every number of seconds specified
		while rec == 1
			fd = ::File.new(logfile, 'ab')
			# Flushing Buffers
			res = @client.sniffer.capture_dump(intid)
			bytes_all = res[:bytes] || 0
			bytes_got = 0
			bytes_pct = 0
			while (bytes_all > 0)
				res = @client.sniffer.capture_dump_read(intid,1024*512)
				bytes_got += res[:bytes]
				pct = ((bytes_got.to_f / bytes_all.to_f) * 100).to_i
				if(pct > bytes_pct)
					bytes_pct = pct
				end
				break if res[:bytes] == 0
				fd.write(res[:data])
			end
			fd.close

			if (::Time.now-start_time) > seconds
			
				print("\n")
				print_good("Stopping Packet sniffer...")
				@client.sniffer.capture_stop(intid)
				break			
			
			end
			
			# Go to sleep until next collection cycle
			sleep(2)
			sleep(packtime.to_i)

		end	
			
	rescue::Exception => e
		print_status("Error During Packet Capture: #{e.class} #{e}")
		raise Rex::Script::Completed
	end
end

# Function for Processing Captured Packets
#-------------------------------------------------------------------------------
def main_server(logfile)
	begin
		rec = 1
		print_status("Packets being parsed and the data stored from #{logfile}")
		
		fd = ::File.new(logfile, 'rb')

		# TODO: reorder packets based on the ID (only an issue if the buffer wraps)
		while(true)
			# Take first 20 characters for the capture header
			capture_header = fd.read(20)
			break if not capture_header
			idh,idl,thi,tlo,len = capture_header.unpack('N5')

			# Take next 14 characters for the ethernet header
			ethernet_header = fd.read(14)
			break if not ethernet_header			

			# Now unpack them :)
			eph = ethernet_header.unpack('C6C6v')
			d_mac = eph[0].to_s(16) + ":" + eph[1].to_s(16) + ":" + eph[2].to_s(16) + ":" + eph[3].to_s(16) + ":" + eph[4].to_s(16) + ":" + eph[5].to_s(16)
			s_mac = eph[6].to_s(16) + ":" + eph[7].to_s(16) + ":" + eph[8].to_s(16)+ ":" + eph[9].to_s(16) + ":" + eph[10].to_s(16) + ":" + eph[11].to_s(16)
			protocol = eph[12]

			# Read Group Address bit
 			group_bit = eph[0] & 1

			# Only process broadcast and multicast (Group Address)
			if group_bit == 1 then

				# Only process IP
				if protocol != 8 then

					# If not IP, then process next packet
					pkt = fd.read(len-14)

				else

					# Take next 20 characters for the ip header
					ip_header = fd.read(20)
					break if not ip_header			

					# Now unpack them :)
					iph = ip_header.unpack('!CCnSSCCSC4C4')

					version_ihl = iph[0]
					version = version_ihl >> 4
					ihl = (version_ihl & 0xF) * 4
					ipl = iph[2]

					protocol = iph[6]
					s_addr = iph[8].to_s + "." + iph[9].to_s + "." + iph[10].to_s + "." + iph[11].to_s
					d_addr = iph[12].to_s + "." + iph[13].to_s + "." + iph[14].to_s + "." + iph[15].to_s
			
					# If IP header has options, move past them
					if ihl.to_i > 20 then
						iho = fd.read(ihl-20)
					end

					# Pass address for host enumeration
					enumeratehost(s_addr)

					# Process UDP
					if protocol == 17 then

						# Take next 8 characters for the udp header
						udp_header = fd.read(8)

						# Now unpack them :)
						udph = udp_header.unpack('!nnnn')

						s_port = udph[0]
						d_port = udph[1]
						udpd_length = udph[2] - 8
					
						data = fd.read(udpd_length)
					
						# Port matching to find open UDP ports
						if s_port == d_port then
					
							# Pass port for port enumeration
							enumerateport(s_addr, s_port, 17)
					
						# Unique port combinations to find other open UDP ports (Special)
						elsif d_port == 68 and s_port == 67 then
					
							# Pass port for port enumeration
							enumerateport(s_addr, s_port, 17)
					
						end

				                # SSDP for fingerprinting
				                if d_port == 1900

							if data =~ /NOTIFY/

								# Notifications are enumerated for fingerprinting
								parsessdp(s_addr, data)

							end			

				                end

						# mDNS to find open TCP ports
						if d_addr == @szMultiAddress and d_port == @iPortNumber then

							# Decode data portion of the packet
							message = Resolv::DNS::Message.decode(data)

							# Process the packet if response
							if message.qr == 1

								message.each_answer { |name, ttl, data|

									# Process answers accordingly
									case data.to_s
									when /Generic::Type1_Class/, /IN::A/
										rr_type = T_A
									when /Generic::Type2_Class/, /IN::NS/
										rr_type = T_NS
									when /Generic::Type5_Class/, /IN::CNAME/
										rr_type = T_CNAME
									when /Generic::Type6_Class/, /IN::SOA/
										rr_type = T_SOA
									when /Generic::Type12_Class/, /IN::PTR/
										rr_type = T_PTR
									when /Generic::Type15_Class/, /IN::MX/
										rr_type = T_MX
									when /Generic::Type16_Class/, /IN::TXT/
										rr_type = T_TXT
									when /Generic::Type28_Class/, /IN::AAAA/
										rr_type = T_AAAA
									when /Generic::Type33_Class/, /IN::SRV/
										rr_type = T_SRV

										# Unpack Resource Record data
										rr_data = data.data.unpack('!nnn')
										tcp_port = rr_data[2]

										# Pass port for port enumeration
										enumerateport(s_addr, tcp_port, 6)

									when /Generic::Type255_Class/, /IN::ANY/
										rr_type = T_ANY
									else
										rr_type = T_ANY
									end

									if rr_type == T_SRV or rr_type == T_A  or rr_type == T_TXT

										# Name Record is enumerated for fingerprinting
										enumeraterecord(s_addr, name, rr_type, data.data)

									end

								}
								message.each_additional { |name, ttl, data|
					
									# Process additional accordingly
									case data.to_s
									when /Generic::Type1_Class/, /IN::A/
										rr_type = T_A
									when /Generic::Type2_Class/, /IN::NS/
										rr_type = T_NS
									when /Generic::Type5_Class/, /IN::CNAME/
										rr_type = T_CNAME
									when /Generic::Type6_Class/, /IN::SOA/
										rr_type = T_SOA
									when /Generic::Type12_Class/, /IN::PTR/
										rr_type = T_PTR
									when /Generic::Type15_Class/, /IN::MX/
										rr_type = T_MX
									when /Generic::Type16_Class/, /IN::TXT/
										rr_type = T_TXT
									when /Generic::Type28_Class/, /IN::AAAA/
										rr_type = T_AAAA
									when /Generic::Type33_Class/, /IN::SRV/
										rr_type = T_SRV

										# Unpack Resource Record data
										rr_data = data.data.unpack('!nnn')
										tcp_port = rr_data[2]

										# Pass port for port enumeration
										enumerateport(s_addr, tcp_port, 6)

									when /Generic::Type255_Class/, /IN::ANY/
										rr_type = T_ANY
									else
										rr_type = T_ANY
									end

									if rr_type == T_SRV or rr_type == T_A  or rr_type == T_TXT

										# Name Record is enumerated for fingerprinting
										enumeraterecord(s_addr, name, rr_type, data.data)

									end

								}

							end

						end

						# Read past any remaining trailer
						data = fd.read(len-14-ihl-8-udpd_length)

					else

						# There is nothing to process
						data = fd.read(len-14-ihl)

					end

				end

			else

				# If not broadcast or multicast, then process next packet
				pkt = fd.read(len-14)

			end

		end
		fd.close

	rescue::Exception => e
		print_status("Error During Enumeration: #{e.class} #{e}")
		raise Rex::Script::Completed
	end
end

# Function for Listing Interfaces
# ------------------------------------------------------------------------------
def int_list()
	@client.core.use("sniffer")
	ifaces = @client.sniffer.interfaces()

	print_line()

	ifaces.each do |i|
		print_line(sprintf("%d - '%s' ( type:%d mtu:%d usable:%s dhcp:%s wifi:%s )",
				i['idx'], i['description'],
				i['type'], i['mtu'], i['usable'], i['dhcp'], i['wireless'])
		)
	end

	print_line()
	raise Rex::Script::Completed
end

#---------------------------------------------------------------------------
#              Enumeration Subroutines (for fingerprinting later)
#---------------------------------------------------------------------------

# Enumerate host seen on the network
def enumeratehost (address)

	# Check to see if Host Record already exists for this host (Only proceed if it doesn't exist!)
	if (!hostenumerated(address)) 

		# Go through all Host Records
		for i in 0..(@maxhosts-1)

			# Find the first empty Host Record
			if (@hosts[i].address == "0") 

				# Put the host address in the first empty record so that the record is set aside for that host
				@hosts[i].address = address
				break
			end

		end

	end		

end

# Is this host already enumerated?
def hostenumerated (address)

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# If it runs across a blank record (Or it is at the end of the enumerated hosts), and it still hasn't found the host
		if (@hosts[i].address == "0")

			# ... then the host is not enumerated and it can stop
			break

		end


		# If it finds a Host Record with that address, returns TRUE (Or there is already a Host Record for that host)
		if (@hosts[i].address == address) 

			return true

		end

	end

	# If does not find a Host Record with that address, returns FALSE (Or there is no Host Record already for that host)
	return false	

end

# Enumerate port seen on a host
def enumerateport (address, port, protocol)

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# Find the host with the open port
		if (@hosts[i].address == address) 

			# Check to see if Port Record already exists for this port (Only proceed if it doesn't exist!)
			if (!portenumerated(address, port, protocol)) 

				# Go through all Port Records (for that host)
				for j in 0..(@maxports-1)

					# Find the first empty Port Record
					if ((@hosts[i].hostports[j].portvalue == 0) and (@hosts[i].hostports[j].portprotocol == 0))

						# Put the port information in the first empty record so that the record is set aside for that port
						@hosts[i].hostports[j].portvalue = port
						@hosts[i].hostports[j].portprotocol = protocol
						break

					end

				end
			end

			# Once it finds the particular host it is looking for, it doesn't need to go through the rest
			break

		end

	end		

end

# Is this port already enumerated for this host?
def portenumerated (address, port, protocol)

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# Find the host with the open port
		if (@hosts[i].address == address)

			# Go through all Port Records (for that host)
			for j in 0..(@maxports-1)

				# If it finds a Port Record for that port, returns TRUE (Or there is already a Port Record for that port)
				if ((@hosts[i].hostports[j].portvalue == port) and (@hosts[i].hostports[j].portprotocol == protocol))

					return true

				end
			end

			# Once it finds the particular host it is looking for, it doesn't need to go through the rest
			break

		end

	end

	# If does not find a Port Record for that port, returns FALSE (Or there is no Port Record already for that port)
	return false		

end

# Enumerate record seen from a host
def enumeraterecord (address, name, type, data)

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# Find the host with the available record
		if (@hosts[i].address == address)

			# Check to see if Name Record already exists for this record (Only proceed if it doesn't exist!)
			if (!recordenumerated(address, name, type))

				# Go through all Name Records (for that host)
				for j in 0..(@maxrecords-1)

					# Find the first empty Name Record
					if ((@hosts[i].hostrecords[j].name == "0") and (@hosts[i].hostrecords[j].type == 0))

						# Put the name information in the first empty record
						@hosts[i].hostrecords[j].name = name
						@hosts[i].hostrecords[j].type = type
						@hosts[i].hostrecords[j].rdata = data
						break

					end

				end
			end

			# Once it finds the particular host it is looking for, it doesn't need to go through the rest
			break

		end

	end		

end

# Is this record already enumerated for this host?
def recordenumerated (address, name, type)

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# Find the host with the available record
		if (@hosts[i].address == address)

			# Go through all Name Records (for that host)
			for j in 0..(@maxrecords-1)

				# If it finds a Name Record for that name, returns TRUE (Or there is already a Name Record for that name)
				if ((@hosts[i].hostrecords[j].name == name) and (@hosts[i].hostrecords[j].type == type))

					return true

				end

			end

			# Once it finds the particular host it is looking for, it doesn't need to go through the rest
			break

		end

	end

	# If does not find a Name Record for that name, returns FALSE (Or there is no Name Record already for that name)
	return false		

end

# Extract parameter value from TXT record
def extractparameter(address, txtrecord, fieldname)

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# Find the host with the parameter
		if (@hosts[i].address == address)

			# Go through all Name Records (for that host)
			for j in 0..(@maxrecords-1)

				# Locate the associated TXT record
				if ((@hosts[i].hostrecords[j].name == txtrecord) && (@hosts[i].hostrecords[j].type == T_TXT))

					# Locate the parameter field name
					rdata = @hosts[i].hostrecords[j].rdata
					pch = rdata.index(fieldname)

					if (!pch.nil?)
						
						# Get length of parameter pair
						pairlength = rdata.getbyte(pch - 1)

						# Move pointer past fiend name and equal sign
						pch = pch + fieldname.length + 1

						# Calculate length of parameter value
						valuelength = pairlength - fieldname.length - 1

						# Next, extract field value from string
						token = rdata.slice(pch,valuelength)

					end

				end

			end

			# Once it finds the particular host it is looking for, it doesn't need to go through the rest
			break

		end

	end

	if (token.nil?)

		return "N/A"

	else

		return token

	end

end

# Parse SSDP payload
def parsessdp (address, payload)

	# Parse the payload
	# Get the headers and Enumerate Notifications
	payload.each_line("\r\n") {|s| enumeratenotification(address, s.chomp("\r\n"))}

end

# Enumerate notification seen from a host
def enumeratenotification (address, notification)

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# Find the host with the available notification
		if (@hosts[i].address == address)

			# Check to see if Notification already exists (Only proceed if it doesn't exist!)
			if (!notificationenumerated(address, notification))

				# Go through all Notifications (for that host)
				for j in 0..(@maxnotifications-1)

					# Find the first empty Notification
					if (@hosts[i].hostnotifications[j] == "0")

						# Put the notification information in the first empty record
						@hosts[i].hostnotifications[j] = notification
						break

					end

				end

			end

			# Once it finds the particular host it is looking for, it doesn't need to go through the rest
			break

		end

	end

end

# Is this notification already enumerated for this host?
def notificationenumerated (address, notification)

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# Find the host with the available notification
		if (@hosts[i].address == address)

			# Go through all Notifications (for that host)
			for j in 0..(@maxnotifications-1)

				# If it finds a Notification for that notify, returns TRUE (Or there is already a Notification for that notify)
				if (@hosts[i].hostnotifications[j] == notification)

					return true

				end

			end

			# Once it finds the particular host it is looking for, it doesn't need to go through the rest
			break

		end

    	end

	# If does not find a Notification for that notify, returns FALSE (Or there is no Notification already for that notify)
	return false

end

#---------------------------------------------------------------------------
#              Profiling Subroutines
#---------------------------------------------------------------------------

# Name all hosts enumerated
def namehost

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# If it runs across a blank record (Or it is at the end of the enumerated hosts),
		if (@hosts[i].address=="0")

			# ... then there are no more hosts to fingerprint and it can stop
			break

		end

		# Go through all Name Records (for that host)
		for j in 0..(@maxrecords-1)

			# Transfer "A" Record into the Name attribute

			# If it runs across a blank record (Or it is at the end of the enumerated names),
			if (@hosts[i].hostrecords[j].name=="0")

				# ... then there are no more names to use for fingerprinting and it can stop
				break

			end

			if (@hosts[i].hostrecords[j].type == T_A)

				@hosts[i].name = @hosts[i].hostrecords[j].name
				break

			end

		end

	end		

end

# Fingerprint all hosts enumerated
def fingerprint

	# Go through all Host Records
	for i in 0..(@maxhosts-1)

		# If it runs across a blank record (Or it is at the end of the enumerated hosts),
		if (@hosts[i].address=="0")

			# ... then there are no more hosts to fingerprint and it can stop
			break

		end

		# Go through all Name Records (for that host)
		for j in 0..(@maxrecords-1)

			# Fingerprint based on available records

			# If it runs across a blank record (Or it is at the end of the enumerated names),
			if (@hosts[i].hostrecords[j].name=="0")

				# ... then there are no more names to use for fingerprinting and it can stop
				break

			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_workstation._tcp.local/))

				@hosts[i].type = "PC"
				@hosts[i].osname = "Linux"
				break

			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_device-info._tcp.local/) and (@hosts[i].hostrecords[j].type == T_TXT))

				@hosts[i].type = "PC"
				@hosts[i].make = "Apple"

				address = @hosts[i].address
				txtrecord = @hosts[i].hostrecords[j].name
				fieldname = "model"

				@hosts[i].model = extractparameter(address, txtrecord, fieldname)
				break

			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_pdl-datastream._tcp.local/))

				if ((@hosts[i].hostrecords[j].name.to_s !~ /@/))
			
					@hosts[i].type = "Printer"

					address = @hosts[i].address
					txtrecord = @hosts[i].hostrecords[j].name
					fieldname = "ty"

					@hosts[i].model = extractparameter(address, txtrecord, fieldname)
					break

				end
					
			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_printer._tcp.local/))

				if ((@hosts[i].hostrecords[j].name.to_s !~ /@/))
			
					@hosts[i].type = "Printer"

					address = @hosts[i].address
					txtrecord = @hosts[i].hostrecords[j].name
					fieldname = "ty"

					@hosts[i].model = extractparameter(address, txtrecord, fieldname)
					break
					
				end

			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_ipp._tcp.local/))

				if ((@hosts[i].hostrecords[j].name.to_s !~ /@/))
			
					@hosts[i].type = "Printer"

					address = @hosts[i].address
					txtrecord = @hosts[i].hostrecords[j].name
					fieldname = "ty"

					@hosts[i].model = extractparameter(address, txtrecord, fieldname)
					break

				end
					
			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_raop._tcp.local/))

				@hosts[i].type = "Wireless Access Point"
				@hosts[i].make = "Apple"
				@hosts[i].model = "Airport"
				break

			end			

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_blackarmor4dinfo._udp.local/))

				@hosts[i].type = "NAS"
				@hosts[i].make = "Seagate"

				address = @hosts[i].address
				txtrecord = @hosts[i].hostrecords[j].name
				fieldname = "DeviceModel"

				@hosts[i].model = extractparameter(address, txtrecord, fieldname)
				break

			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_blackarmor4dconfig._tcp.local/))

				@hosts[i].type = "NAS"
				@hosts[i].make = "Seagate"

				address = @hosts[i].address
				txtrecord = @hosts[i].hostrecords[j].name
				fieldname = "DeviceModel"

				@hosts[i].model = extractparameter(address, txtrecord, fieldname)
				break

			end
			
			if ((@hosts[i].hostrecords[j].name.to_s =~ /_axis-video._tcp.local/))

				@hosts[i].type = "Camera"
				@hosts[i].make = "Axis"
				break

			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_tivo-videos._tcp.local/))

				@hosts[i].type = "DVR"
				@hosts[i].make = "Tivo"
				break

			end

			if ((@hosts[i].hostrecords[j].name.to_s =~ /_apple-mobdev._tcp.local/))

				@hosts[i].type = "iPhone"
				@hosts[i].make = "Apple"
				@hosts[i].osname = "iOS"
				break

			end
			
			pch = @hosts[i].hostrecords[j].name.to_s.downcase
			if ((pch =~ /ipod/) and (@hosts[i].hostrecords[j].type == T_A))

				@hosts[i].type = "iPod"
				@hosts[i].make = "Apple"
				@hosts[i].osname = "iOS"
				break

			end

			pch = @hosts[i].hostrecords[j].name.to_s.downcase
			if ((pch =~ /iphone/) and (@hosts[i].hostrecords[j].type == T_A))

				@hosts[i].type = "iPhone"
				@hosts[i].make = "Apple"
				@hosts[i].osname = "iOS"
				break

			end

			pch = @hosts[i].hostrecords[j].name.to_s.downcase
			if ((pch =~ /ipad/) and (@hosts[i].hostrecords[j].type == T_A))

				@hosts[i].type = "iPad"
				@hosts[i].make = "Apple"
				@hosts[i].osname = "iOS"
				break

			end

		end

		# Go through all Port Records (for that host)
		for j in 0..(@maxports-1)

			# Fingerprint based on open ports

			# If it runs across a blank record (Or it is at the end of the enumerated ports),
			if (((@hosts[i].hostports[j].portvalue == 0) and (@hosts[i].hostports[j].portprotocol == 0)))

				# ... then there are no more ports to use for fingerprinting and it can stop
				break

			end

			if (((@hosts[i].hostports[j].portvalue == 137) and (@hosts[i].hostports[j].portprotocol == 17)) or ((@hosts[i].hostports[j].portvalue == 138) and (@hosts[i].hostports[j].portprotocol == 17)))

				@hosts[i].osname = "Microsoft Windows"
				@hosts[i].type = "PC"

			end

			if ((@hosts[i].hostports[j].portvalue == 47808) and (@hosts[i].hostports[j].portprotocol == 17))

				@hosts[i].type = "SCADA"

			end

		end

		# Go through all Notifications (for that host)
		for j in 0..(@maxnotifications-1)

			# Fingerprint based on notifications

			# If it runs across a blank record (Or it is at the end of the enumerated notifications),
			if (@hosts[i].hostnotifications[j].to_s == "0")

				# ... then there are no more notifications to use for fingerprinting and it can stop
				break

			end

			# Wildcards (Less specific)
			# By placing wildcards first, more specific fingerprints are able to override when encountered later ...

			pch = @hosts[i].hostnotifications[j].to_s
			if (pch =~ /USER-AGENT: SEC_HHP_SAMSUNG GT-I5800/)

				@hosts[i].osname = "Android"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I5800"

			end

			pch = @hosts[i].hostnotifications[j].to_s
			if (pch =~ /Server: Linux\/2.6.35.7-N7000/)

				@hosts[i].osname = "Android"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-N7000"

			end

			pch = @hosts[i].hostnotifications[j].to_s
			if (pch =~ /Server: Linux\/2.6.35.7-I8160/)

				@hosts[i].osname = "Android"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I8160"

			end	
			
			pch = @hosts[i].hostnotifications[j].to_s
			if (pch =~ /Server: Linux\/2.6.35.7-I9100/)

				@hosts[i].osname = "Android"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9100"

			end

			pch = @hosts[i].hostnotifications[j].to_s
			if (pch =~ /USER-AGENT: DLNADOC\/1.50 SEC_HHP_[Mobile]GT-I9300/)

				@hosts[i].osname = "Android"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9300"

			end

			#  Specific
			if (@hosts[i].hostnotifications[j].to_s == "Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0")

				@hosts[i].osname = "Microsoft Windows"
				@hosts[i].osversion = "XP"
				@hosts[i].type = "PC"

			end

			if (@hosts[i].hostnotifications[j].to_s == "SERVER: Windows 7/6.1 UPnP/1.0 Azureus/4.7.2.0")

				@hosts[i].osname = "Microsoft Windows"
				@hosts[i].osversion = "7"
				@hosts[i].type = "PC"

			end

			if (@hosts[i].hostnotifications[j].to_s == "SERVER: Mac OS X/10.7.4 UPnP/1.0 Azureus/4.7.1.2")

				@hosts[i].osname = "Apple OS X"
				@hosts[i].osversion = "10.7.4"
				@hosts[i].make = "Apple"				
				@hosts[i].type = "PC"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-N7000DXKK1-CL706642 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.5"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-N7000"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-N7000DXLC2-CL1039404 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.6"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-N7000"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-I8160XXLD8-CL1100997 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.6"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I8160"

			end
			
			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-I9070DXLD1-CL1068093 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.6"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9070"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-I9100DXKI2-CL564948 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.3"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9100"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-I9100XXKI4-CL611039 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.5"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9100"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-I9100XWKK2-CL726411 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.6"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9100"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-I9100XWKF3-CL276555 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.3"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9100"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/2.6.35.7-I9100XWKI4-CL575468 UPnP/1.0 Samsung DOA/1.5")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "2.3.4"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9100"

			end
			
			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/3.0.15-554452-user UPnP/1.0 CyberLinkJava/1.8")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "4.0.4"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9300"

			end

			if (@hosts[i].hostnotifications[j].to_s == "Server: Linux/3.0.15-611327-user UPnP/1.0 CyberLinkJava/1.8")

				@hosts[i].osname = "Android"
				@hosts[i].osversion = "4.0.4"
				@hosts[i].type = "Phone"
				@hosts[i].make = "Samsung"
				@hosts[i].model = "GT-I9300"

			end

			if (@hosts[i].hostnotifications[j].to_s == "SERVER: Linux/2.6.35 UPnP/1.0 DLNADOC/1.50 INTEL_NMPR/2.0 LGE_DLNA_SDK/1.5.0")

				@hosts[i].type = "TV"
				@hosts[i].make = "LG"

			end

		end

	end		

end

# Display all hosts enumerated and fingerprinted (to the user)
def writehosts
	begin

		#Get hostname
		host = @client.sys.config.sysinfo["Computer"]

		# Create Filename info to be appended to downloaded files
		filenameinfo = "_network_" + ::Time.now.strftime("%Y%m%d.%M%S")

		#logfile name
		filename = host + filenameinfo + ".txt"

		# Open file
		f = File.open(filename,"w")

		# Go through all Host Records
		for i in 0..(@maxhosts-1)

			# If it runs across a blank record (Or it is at the end of the enumerated hosts),
			if (@hosts[i].address=="0")

				# ... then there are no more hosts to display and it can stop
				break

			end

			f.printf("\n---------------------------\n\n")
			f.printf("Address:    %s\n", @hosts[i].address)

			# If this attribute is available, display it
			if (@hosts[i].name!="0")

				f.printf("Name:       %s\n", @hosts[i].name)

			end

			# If this attribute is available, display it
			if (@hosts[i].type!="0")

				f.printf("Type:       %s\n", @hosts[i].type)

			end

			# If this attribute is available, display it
			if (@hosts[i].make!="0")

				f.printf("Make:       %s\n", @hosts[i].make)

			end

			# If this attribute is available, display it
			if (@hosts[i].model!="0")

				f.printf("Model:      %s\n", @hosts[i].model)

			end

			# If this attribute is available, display it
			if (@hosts[i].osname!="0")

				f.printf("OS Name:    %s\n", @hosts[i].osname)

			end

			# If this attribute is available, display it
			if (@hosts[i].osversion!="0")

				f.printf("OS Version: %s\n", @hosts[i].osversion)

			end

			f.printf("\nPorts\n")
			f.printf("-----\n")
			for j in 0..(@maxports-1)

				# If it runs across a blank record (Or it is at the end of the enumerated ports),
				if (@hosts[i].hostports[j].portprotocol==0)

					# ... then there are no more ports to display and it can stop
					break

				end

				if (@hosts[i].hostports[j].portprotocol == 6)

					f.printf("%i (TCP)\n", @hosts[i].hostports[j].portvalue)

				elsif (@hosts[i].hostports[j].portprotocol == 17)

					f.printf("%i (UDP)\n", @hosts[i].hostports[j].portvalue)

				end

			end

			f.printf("\n\n")

			# Verbose Mode prints out all Name Records and Notifications for debugging
			if (@verbose)

				f.printf("\nRecords\n")
				f.printf("-----\n")
				for j in 0..(@maxrecords-1)

					# If it runs across a blank record (Or it is at the end of the enumerated Name Records),
					if (@hosts[i].hostrecords[j].type == 0)

						# ... then there are no more Name Records to display and it can stop
						break

					end

					f.printf("%s , %i\n", @hosts[i].hostrecords[j].name, @hosts[i].hostrecords[j].type)

				end

				f.printf("\n\n")

				f.printf("\nNotifications\n")
				f.printf("-----\n")
				for j in 0..(@maxnotifications-1)

					# If it runs across a blank record (Or it is at the end of the enumerated Notifications),
					if (@hosts[i].hostnotifications[j] == "0")

						# ... then there are no more Notifications to display and it can stop
						break

					end

					f.printf("%s\n", @hosts[i].hostnotifications[j])

				end

				f.printf("\n\n")

			end

		end		


		# Close file
		f.close

	rescue::Exception => e
		print_status("Error During File Output: #{e.class} #{e}")
		raise Rex::Script::Completed
	end

end

# Put all hosts enumerated and fingerprinted into the database
def loadhosts
	begin

		# Go through all Host Records
		for i in 0..(@maxhosts-1)


			# If it runs across a blank record (Or it is at the end of the enumerated hosts),
			if (@hosts[i].address=="0")

				# ... then there are no more hosts to load and it can stop
				break

			end

			# Load the host into the database
			host = framework.db.find_or_create_host(:host => @hosts[i].address)

			# If this attribute is available, load it
			if (@hosts[i].name!="0")

				host.name = @hosts[i].name.to_s

			end

			# If this attribute is available, load it
			if (@hosts[i].type!="0")

				host.purpose = @hosts[i].type

			end

			# If this attribute is available, load it
			if (@hosts[i].make!="0")

				host.info = @hosts[i].make

			end

			# If this attribute is available, load it
			if (@hosts[i].model!="0")

				if (@hosts[i].make=="0")

					host.info = @hosts[i].model
			
				else

					host.info = @hosts[i].make + " " + @hosts[i].model

				end

			end

			# If this attribute is available, load it
			if (@hosts[i].osname!="0")

				host.os_name = @hosts[i].osname

			end

			# If this attribute is available, display it
			if (@hosts[i].osversion!="0")

				host.os_flavor = @hosts[i].osversion

			end

			# Commit the available host attributes to the database
			host.state = "alive"
			host.save!

			# Go through all Port Records
			for j in 0..(@maxports-1)

				# If it runs across a blank record (Or it is at the end of the enumerated ports),
				if (@hosts[i].hostports[j].portprotocol==0)

					# ... then there are no more ports to load and it can stop
					break

				end

				# Load the host services into the database
				if (@hosts[i].hostports[j].portprotocol == 6)

					service = host.services.find_or_initialize_by_port_and_proto(@hosts[i].hostports[j].portvalue,"tcp")

				elsif (@hosts[i].hostports[j].portprotocol == 17)

					service = host.services.find_or_initialize_by_port_and_proto(@hosts[i].hostports[j].portvalue,"udp")

				end

				# Commit the available service to the databasee
				service.state = "open"
				service.save!

			end

		end		

	rescue::Exception => e
		print_status("Error During Database Loading: #{e.class} #{e}")
		raise Rex::Script::Completed
	end

end

################## Main ##################
@exec_opts.parse(args) { |opt, idx, val|
	case opt
	when "-h"
		usage
	when "-i"
		int_id = val.to_i
	when "-l"
		log_dest = val
	when "-li"
		int_list
	when "-t"
		rec_time = val
	when "-v"
		@verbose = true
	when "-s"
		seconds = val.to_i
	when "-w"
		output = "file"
	end
}

# Check for version of meterpreter
wrong_meter_version(meter_type) if meter_type !~ /win32|win64/i

if !int_id.nil?
	if not is_uac_enabled? or is_admin?
		raw_file = log_file(log_dest)
		startsniff(int_id)
		packetrecord(rec_time,raw_file,int_id,seconds)
		
		main_server(raw_file)
		namehost
		fingerprint

		if (output == "database")

			loadhosts		

		else

			writehosts

		end
		print_status("Profiling Complete!")	
	
	else
		print_error("UAC or Access Denied")
	end
else
	usage
end