require 'rex/proto/smb'
require 'rex/proto/dcerpc'
require 'rex/encoder/ndr'

module Msf

###
#
# This mixin provides utility methods for interacting with a SMB/CIFS service on
# a remote machine.  These methods may generally be useful in the context of
# exploitation.  This mixin extends the Tcp exploit mixin. Only one SMB
# service can be accessed at a time using this class.
#
###

module Exploit::Remote::SMB

	include Exploit::Remote::Tcp
	SIMPLE = Rex::Proto::SMB::SimpleClient
	XCEPT  = Rex::Proto::SMB::Exceptions
	CONST  = Rex::Proto::SMB::Constants

	# Alias over the Rex DCERPC protocol modules
	DCERPCPacket   = Rex::Proto::DCERPC::Packet
	DCERPCClient   = Rex::Proto::DCERPC::Client
	DCERPCResponse = Rex::Proto::DCERPC::Response
	DCERPCUUID     = Rex::Proto::DCERPC::UUID
	NDR            = Rex::Encoder::NDR

	# Mini-mixin for making SMBUser/SMBPass/SMBDomain regular options vs advanced
	# Included when the module needs credentials to function
	module Authenticated
		def initialize(info = {})
			super
			register_options(
			[
				OptString.new('SMBUser', [ false, 'The username to authenticate as', '']),
				OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
				OptString.new('SMBDomain',  [ false, 'The Windows domain to use for authentication', 'WORKGROUP']),
			], Msf::Exploit::Remote::SMB::Authenticated)
		end
	end

	def initialize(info = {})
		super

		register_evasion_options(
		[
			OptBool.new('SMB::pipe_evasion',     [ true, 'Enable segmented read/writes for SMB Pipes', false]),
			OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes',  1]),
			OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]),
			OptInt.new('SMB::pipe_read_min_size',  [ true, 'Minimum buffer size for pipe reads',  1]),
			OptInt.new('SMB::pipe_read_max_size',  [ true, 'Maximum buffer size for pipe reads', 1024]),
			OptInt.new('SMB::pad_data_level',  [ true, 'Place extra padding between headers and data (level 0-3)', 0]),
			OptInt.new('SMB::pad_file_level',  [ true, 'Obscure path names used in open/create (level 0-3)', 0]),
			OptInt.new('SMB::obscure_trans_pipe_level',  [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]),

		], Msf::Exploit::Remote::SMB)

		register_advanced_options(
		[
			OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]),
			OptString.new('SMBUser', [ false, 'The username to authenticate as', '']),
			OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
			OptString.new('SMBDomain',  [ false, 'The Windows domain to use for authentication', 'WORKGROUP']),
			OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER'])
		], Msf::Exploit::Remote::SMB)

		register_options(
		[
			Opt::RHOST,
			OptInt.new('RPORT', [ true, 'Set the SMB service port', 445])
		], Msf::Exploit::Remote::SMB)

		register_autofilter_ports([ 139, 445])
		register_autofilter_services(%W{ netbios-ssn microsoft-ds })
	end

	def connect(global=true)

		disconnect() if global

		s = super(global)
		self.sock = s if global

		# Disable direct SMB when SMBDirect has not been set
		# and the destination port is configured as 139
		direct = smb_direct
		if(datastore.default?('SMBDirect') and rport.to_i == 139)
			direct = false
		end

		c = SIMPLE.new(s, direct)

		# setup pipe evasion foo
		if datastore['SMB::pipe_evasion']
			# XXX - insert code to change the instance of the read/write functions to do segmentation
		end

		if (datastore['SMB::pad_data_level'])
			c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level']
		end

		if (datastore['SMB::pad_file_level'])
			c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level']
		end

		if (datastore['SMB::obscure_trans_pipe_level'])
			c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level']
		end

		self.simple = c if global
		c
	end

	# Convert a standard ASCII string to 16-bit Unicode
	def unicode(str)
		Rex::Text.to_unicode(str)
	end

	# This method establishes a SMB session over the default socket
	def smb_login
		simple.login(
			datastore['SMBName'],
			datastore['SMBUser'],
			datastore['SMBPass'],
			datastore['SMBDomain']
		)
		simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
	end

	# This method returns the native operating system of the peer
	def smb_peer_os
		self.simple.client.peer_native_os
	end

	# This method returns the native lanman version of the peer
	def smb_peer_lm
		self.simple.client.peer_native_lm
	end

	# This method opens a handle to an IPC pipe
	def smb_create(pipe)
		self.simple.create_pipe(pipe)
	end

	def smb_hostname
		datastore['SMBName'] || '*SMBSERVER'
	end

	def smb_direct
		datastore['SMBDirect']
	end


	#
	# Fingerprinting methods
	#


	# This method the EnumPrinters() function of the spooler service
	def smb_enumprinters(flags, name, level, blen)
		stub =
			NDR.long(flags) +
			(name ? NDR.uwstring(name) : NDR.long(0)) +
			NDR.long(level) +
			NDR.long(rand(0xffffffff)+1)+
			NDR.long(blen) +
			"\x00" * blen +
			NDR.long(blen)

		handle = dcerpc_handle(
			'12345678-1234-abcd-ef00-0123456789ab', '1.0',
			'ncacn_np', ["\\SPOOLSS"]
		)

		begin
			dcerpc_bind(handle)
			dcerpc.call(0x00, stub)
			return dcerpc.last_response.stub_data
		rescue ::Interrupt
			raise $!
		rescue ::Exception => e
			return nil
		end
	end

	# This method dumps the print provider strings from the spooler
	def smb_enumprintproviders
		resp = smb_enumprinters(8, nil, 1, 0)
		return nil if not resp
		rptr, tmp, blen = resp.unpack("V*")

		resp = smb_enumprinters(8, nil, 1, blen)
		return nil if not resp

		bcnt,pcnt,stat = resp[-12, 12].unpack("VVV")
		return nil if stat != 0
		return nil if pcnt == 0
		return nil if bcnt > blen
		return nil if pcnt < 3

		#
		# The correct way, which leads to invalid offsets :-(
		#
		#providers = []
		#
		#0.upto(pcnt-1) do |i|
		#	flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV")
		#
		#	#desc = read_unicode(resp,8+desc_o).gsub("\x00", '')
		#	#name = read_unicode(resp,8+name_o).gsub("\x00", '')
		#	#comm = read_unicode(resp,8+comm_o).gsub("\x00", '')
		#	#providers << [flags,desc,name,comm]
		#end
		#
		#providers

		return resp

	end

	# This method performs an extensive set of fingerprinting operations
	def smb_fingerprint
		fprint = {}

		# Connect to the server if needed
		if(not self.simple)
			connect()
			smb_login()
		end



		os = 'Unknown'
		sp = ''

		case smb_peer_os()
			when 'Windows NT 4.0'
				os = 'Windows NT 4.0'

			when 'Windows 5.0'
				os = 'Windows 2000'

			when 'Windows 5.1'
				os = 'Windows XP'

			when /Windows XP (\d+) Service Pack (\d+)/
				os = 'Windows XP'
				sp = 'Service Pack ' + $2

			when /Windows Server 2003 (\d+)$/
				os = 'Windows 2003'
				sp = 'No Service Pack'

			when /Windows Server 2003 (\d+) Service Pack (\d+)/
				os = 'Windows 2003'
				sp = 'Service Pack ' + $2

			when /Windows Server 2003 R2 (\d+) Service Pack (\d+)/
				os = 'Windows 2003 R2'
				sp = 'Service Pack ' + $2

			when /Windows Vista \(TM\) (\w+|\w+ \w+) (\d+) Service Pack (\d+)/
				os = 'Windows Vista ' + $1
				sp = 'Service Pack ' + $3

			when /Windows Vista \(TM\) (\w+|\w+ \w+) (\d+)/
				os = 'Windows Vista ' + $1
				sp = '(Build ' + $2 + ')'

			when /Windows Server \(R\) 2008 (([\-\w]+ ){1,4})(\d+) Service Pack (\d+)/
				os = 'Windows 2008 ' + $1.strip
				sp = 'Service Pack ' + $4

			when /Windows Server \(R\) 2008 (([\-\w]+ ){1,4})(\d+)/
				os = 'Windows 2008 ' + $1.strip
				sp = '(Build ' + $3 + ')'

			when /Windows \(R\) Storage Server 2008 (([\-\w]+ ){1,4})(\d+) Service Pack (\d+)/
				os = 'Windows 2008 Storage Server ' + $1.strip
				sp = 'Service Pack ' + $4

			when /Windows \(R\) Storage Server 2008 (([\-\w]+ ){1,4})(\d+)/
				os = 'Windows 2008 Storage Server ' + $1.strip
				sp = '(Build ' + $3 + ')'

			when /Windows 7 (([\-\w]+ ){1,4})(\d+)/
				os = 'Windows 7 ' + $1.strip
				sp = '(Build ' + $3 + ')'

			when /^(Windows.*) Service Pack (\d+)/
				os = $1.strip
				sp = 'Service Pack ' + $2

			when /^(Windows.*) (\d+)/
				os = $1.strip
				sp = '(Build ' + $2 + ')'

			when 'VxWorks'
				os = 'VxWorks'
				sp = smb_peer_lm()

			when 'Unix'
				os = 'Unix'
				sv = smb_peer_lm()
				case sv
					when /Samba\s+(.*)/i
						sp = 'Samba ' + $1
				end
		end


		if (os == 'Windows XP' and sp.length == 0)
			# SRVSVC was blocked in SP2
			begin
				smb_create("\\SRVSVC")
				sp = 'Service Pack 0 / 1'
			rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
				if (e.error_code == 0xc0000022)
					sp = 'Service Pack 2+'
				end
			end
		end

		if (os == 'Windows 2000' and sp.length == 0)
			# LLSRPC was blocked in a post-SP4 update
			begin
				smb_create("\\LLSRPC")
				sp = 'Service Pack 0 - 4'
			rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
				if (e.error_code == 0xc0000022)
					sp = 'Service Pack 4 with MS05-010+'
				end
			end
		end

		#
		# Perform granular XP SP checks if LSARPC is exposed
		#
		if (os == 'Windows XP')

			#
			# Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC
			# Credit to spoonm for first use of unbounded [out] buffers
			#
			handle = dcerpc_handle(
				'4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0',
				'ncacn_np', ["\\BROWSER"]
			)

			begin
				dcerpc_bind(handle)

				stub =
					NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) +
					NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1))  +
					NDR.long(64001) +
					NDR.long(0) +
					NDR.long(0)

				dcerpc.call(0x22, stub)
				sp = "Service Pack 0 / 1"

			rescue ::Interrupt
				raise $!
			rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
			rescue ::Rex::Proto::SMB::Exceptions::ReadPacket
			rescue ::Rex::Proto::DCERPC::Exceptions::Fault
				sp = "Service Pack 2+"
			rescue ::Exception
			end


			#
			# Service Pack 3 fixed information leaks via [unique][out] pointers
			# Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique]
			# Credit:
			#   Pointer leak is well known, but Immunity also covered in a paper
			#   Silent fix of pointer leak in SP3 and detection method by Rhys Kidd
			#
			handle = dcerpc_handle(
				'4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0',
				'ncacn_np', ["\\BROWSER"]
			)

			begin
				dcerpc_bind(handle)

				stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1))
				resp = dcerpc.call(0x1c, stub)

				if(resp and resp[0,4] == "\x00\x00\x02\x00")
					sp = "Service Pack 3"
				else
					if(resp and sp =~ /Service Pack 2\+/)
						sp = "Service Pack 2"
					end
				end

			rescue ::Interrupt
				raise $!
			rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
			rescue ::Rex::Proto::SMB::Exceptions::ReadPacket
			rescue ::Exception
			end
		end


		#
		# Remote language detection via Print Providers
		# Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt
		#

		lang = 'Unknown'

		sigs =
		{
			'English' =>
				[
					Rex::Text.to_unicode('Windows NT Remote Printers'),
					Rex::Text.to_unicode('LanMan Print Services')
				],
			'Spanish' =>
				[
					Rex::Text.to_unicode('Impresoras remotas Windows NT'),
					Rex::Text.to_unicode('Impresoras remotas de Windows NT')
				],
			'Italian' =>
				[
					Rex::Text.to_unicode('Stampanti remote di Windows NT'),
					Rex::Text.to_unicode('Servizi di stampa LanMan')
				],
			'French' =>
				[
					Rex::Text.to_unicode('Imprimantes distantes NT'),
					Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'),
					Rex::Text.to_unicode("Services d'impression LanMan")
				],
			'German' =>
				[
					Rex::Text.to_unicode('Remotedrucker')
				],
			'Portuguese - Brazilian' =>
				[
					Rex::Text.to_unicode('Impr. remotas Windows NT'),
					Rex::Text.to_unicode('Impressoras remotas do Windows NT')
				],
			'Portuguese' =>
				[
					Rex::Text.to_unicode('Imp. remotas do Windows NT')
				],
			'Hungarian' =>
				[
					Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b")
				],
			'Finnish' =>
				[
					Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74")
				],
			'Dutch' =>
				[
					Rex::Text.to_unicode('Externe printers voor NT')
				],
			'Danish' =>
				[
					Rex::Text.to_unicode('Fjernprintere')
				],
			'Swedish' =>
				[
					Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65")
				],
			'Polish' =>
				[
					Rex::Text.to_unicode('Zdalne drukarki')
				],
			'Czech'   =>
				[
					Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79")
				],
			'Turkish' =>
				[
					"\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00"
				],
			'Japanese' =>
				[
					"\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30"
				],
			'Chinese - Traditional' =>
				[
					"\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67"
				],
			'Chinese - Traditional / Taiwan' =>
				[
					"\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a",
				],
			'Korean' =>
				[
					"\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1",
				],
			'Russian' =>
				[
					"\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04",
				],

		}

		begin
			prov = smb_enumprintproviders()
			if(prov)
				sigs.each_key do |k|
					sigs[k].each do |s|
						if(prov.index(s))
							lang = k
							break
						end
						break if lang != 'Unknown'
					end
					break if lang != 'Unknown'
				end

				if(lang == 'Unknown')

					@fpcache ||= {}
					mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4])

					if(not @fpcache[mhash])

						buff = "\n"
						buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n"
						buff << " VERS: $Revision: 9981 $\n"
						buff << " HOST: #{rhost}\n"
						buff << "   OS: #{os}\n"
						buff << "   SP: #{sp}\n"

						prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line|
							next if line.length == 0
							buff << "   FP: #{line}\n"
						end

						prov.split(/\x00\x00+/).each do |line|
							line.gsub!("\x00",'')
							line.strip!
							next if line.length < 6

							buff <<  "  TXT: #{line}\n"
						end

						buff << "*** END FINGERPRINT\n"

						print_line(buff)

						@fpcache[mhash] = true
					end

				end
			end
		rescue ::Interrupt
			raise $!
		rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
		end

		fprint['os']   = os
		fprint['sp']   = sp
		fprint['lang'] = lang

		fprint
	end

	#
	# Accessors
	#

	attr_accessor :simple

end

###
#
# This mixin provides a minimal SMB server
#
###

module Exploit::Remote::SMBServer
	include Exploit::Remote::TcpServer
	CONST = ::Rex::Proto::SMB::Constants
	CRYPT = ::Rex::Proto::SMB::Crypt
	UTILS = ::Rex::Proto::SMB::Utils
	XCEPT = ::Rex::Proto::SMB::Exceptions
	EVADE = ::Rex::Proto::SMB::Evasions

	def initialize(info = {})
		super

		register_options(
			[
				OptPort.new('SRVPORT',    [ true, "The local port to listen on.", 445 ])
			], self.class)
	end

	def setup
		super
		@state = {}
	end

	def on_client_connect(client)
		# print_status("New SMB connection from #{client.peerhost}:#{client.peerport}")
		smb_conn(client)
	end

	def on_client_data(client)
		# print_status("New data from #{client.peerhost}:#{client.peerport}")
		smb_recv(client)
		true
	end

	def on_client_close(client)
		smb_stop(client)
	end

	def smb_conn(c)
		@state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport}
	end

	def smb_stop(c)
		@state.delete(c)
	end

	def smb_recv(c)
		smb = @state[c]
		smb[:data] ||= ''
		smb[:data] << c.get_once

		while(smb[:data].length > 0)

			return if smb[:data].length < 4

			plen = smb[:data][2,2].unpack('n')[0]

			return if smb[:data].length < plen+4

			buff = smb[:data].slice!(0, plen+4)

			pkt_nbs = CONST::NBRAW_PKT.make_struct
			pkt_nbs.from_s(buff)

			# print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}")

			# Check for a NetBIOS name request
			if (pkt_nbs.v['Type'] == 0x81)
				# Accept any name they happen to send

				host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/, '')
				host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/, '')

				smb[:nbdst] = host_dst
				smb[:nbsrc] = host_src

				# print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})")
				c.write("\x82\x00\x00\x00")
				next
			end


			#
			# TODO: Support AndX parameters
			#


			# Cast this to a generic SMB structure
			pkt = CONST::SMB_BASE_PKT.make_struct
			pkt.from_s(buff)

			# Only response to requests, ignore server replies
			if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0)
				print_status("Ignoring server response from #{smb[:name]}")
				next
			end

			cmd = pkt['Payload']['SMB'].v['Command']
			begin
				smb_cmd_dispatch(cmd, c, buff)
			rescue ::Interrupt
				raise $!
			rescue ::Exception => e
				print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}")
				next
			end
		end
	end

	def smb_cmd_dispatch(cmd, c, buff)
		smb = @state[c]
		print_status("Received command #{cmd} from #{smb[:name]}")
	end

	def smb_set_defaults(c, pkt)
		smb = @state[c]
		pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i
		pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i
		pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i
		pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i
	end

end


end

