/
core.rb
593 lines (509 loc) · 15.1 KB
/
core.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
# -*- coding: binary -*-
require 'set'
require 'rex/post/hwbridge'
module Rex
module Post
module HWBridge
module Ui
###
#
# Core hwbridge client commands that provide only the required set of
# commands for having a functional hwbridge client<->hardware instance.
#
###
class Console::CommandDispatcher::Core
include Console::CommandDispatcher
#
# Initializes an instance of the core command set using the supplied shell
# for interactivity.
#
def initialize(shell)
super
self.extensions = []
self.bgjobs = []
self.bgjob_id = 0
# keep a lookup table to refer to transports by index
@transport_map = {}
end
@@irb_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ],
"-e" => [ true, "Expression to evaluate." ])
@@load_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help menu." ])
#
# List of supported commands.
#
def commands
c = {
"?" => "Help menu",
"background" => "Backgrounds the current session",
"exit" => "Terminate the hardware bridge session",
"help" => "Help menu",
"irb" => "Drop into irb scripting mode",
"load" => "Load one or more meterpreter extensions",
"run" => "Executes a meterpreter script or Post module",
"bgrun" => "Executes a meterpreter script as a background thread",
"bgkill" => "Kills a background meterpreter script",
"bglist" => "Lists running background scripts",
"sessions" => "Quickly switch to another session",
"status" => "Fetch bridge status information",
"specialty" => "Hardware devices specialty",
"reset" => "Resets the device (NOTE: on some devices this is a FULL FACTORY RESET)",
"reboot" => "Reboots the device (usually only supported by stand-alone devices)",
"load_custom_methods" => "Loads custom HW commands if any"
}
if msf_loaded?
c["info"] = "Displays information about a Post module"
end
c
end
def name
"Core"
end
def cmd_sessions_help
print_line('Usage: sessions <id>')
print_line
print_line('Interact with a different session Id.')
print_line('This works the same as calling this from the MSF shell: sessions -i <session id>')
print_line
end
def cmd_sessions(*args)
if args.length.zero? || args[0].to_i.zero?
cmd_sessions_help
elsif args[0].to_s == client.name.to_s
print_status("Session #{client.name} is already interactive.")
else
print_status("Backgrounding session #{client.name}...")
# store the next session id so that it can be referenced as soon
# as this session is no longer interacting
client.next_session = args[0]
client.interacting = false
end
end
def cmd_background_help
print_line "Usage: background"
print_line
print_line "Stop interacting with this session and return to the parent prompt"
print_line
end
def cmd_background
print_status "Backgrounding session #{client.name}..."
client.interacting = false
end
#
# Terminates the hwbridge
#
def cmd_exit(*args)
print_status("Shutting down the hardware bridge...")
shell.stop
end
alias cmd_quit cmd_exit
def cmd_irb_help
print_line "Usage: irb"
print_line
print_line "Execute commands in a Ruby environment"
print @@irb_opts.usage
end
#
# Runs the IRB scripting shell
#
def cmd_irb(*args)
expressions = []
# Parse the command options
@@irb_opts.parse(args) do |opt, idx, val|
case opt
when '-e'
expressions << val
when '-h'
return cmd_irb_help
end
end
session = client
framework = client.framework
if expressions.empty?
print_status("Starting IRB shell")
print_status("The 'client' variable holds the hwbridge client\n")
Rex::Ui::Text::IrbShell.new(binding).run
else
expressions.each { |expression| eval(expression, binding) }
end
end
def cmd_info_help
print_line 'Usage: info <module>'
print_line
print_line 'Prints information about a post-exploitation module'
print_line
end
#
# Show info for a given Post module.
#
# See also +cmd_info+ in lib/msf/ui/console/command_dispatcher/core.rb
#
def cmd_info(*args)
return unless msf_loaded?
if args.length != 1 || args.include?('-h')
cmd_info_help
return
end
module_name = args.shift
mod = client.framework.modules.create(module_name);
if mod.nil?
print_error 'Invalid module: ' << module_name
end
if mod
print_line(::Msf::Serializer::ReadableText.dump_module(mod))
mod_opt = ::Msf::Serializer::ReadableText.dump_options(mod, ' ')
print_line("\nModule options (#{mod.fullname}):\n\n#{mod_opt}") if mod_opt && mod_opt.length > 0
end
end
def cmd_info_tabs(*args)
return unless msf_loaded?
tab_complete_postmods
end
def cmd_status_help
print_line("Usage: status")
print_line
print_line "Retrieves the devices current status and statistics"
end
#
# Get the HW bridge devices status
#
def cmd_status(*args)
if args.length > 0
cmd_status_help
return true
end
status = client.get_status
stats = client.get_statistics
if status.has_key? 'operational'
op = 'Unknown'
op = 'Yes' if status['operational'] == 1
op = 'No' if status['operational'] == 2
print_status("Operational: #{op}")
end
print_status("Device: #{status['device_name']}") if status.key? 'device_name'
print_status("FW Version: #{status['fw_version']}") if status.key? 'fw_version'
print_status("HW Version: #{status['hw_version']}") if status.key? 'hw_version'
print_status("Uptime: #{stats['uptime']} seconds") if stats.key? 'uptime'
print_status("Packets Sent: #{stats['packet_stats']}") if stats.key? 'packet_stats'
print_status("Last packet Sent: #{::Time.at(stats['last_request'])}") if stats.key? 'last_request'
print_status("Voltage: #{stats['voltage']}") if stats.key? 'voltage' and not stats['voltage'] == 'not supported'
end
def cmd_specialty_help
print_line("Usage: specialty")
print_line
print_line "Simple helper function to see what the devices specialty is"
end
#
# Get the Hardware specialty
#
def cmd_specialty(*args)
if args.length > 0
cmd_specialty_help
return true
end
print_line client.exploit.hw_specialty.to_s
end
def cmd_reset_help
print_line("Resets the device. In some cases this can be used to perform a factory reset")
print_line
end
#
# Performs a device reset or factory reset
#
def cmd_reset(*args)
if args.length > 0
cmd_reset_help
return
end
client.reset
end
def cmd_reboot_help
print_line("Reboots the device. This command typically only works on independent devices that")
print_line("are not attached to a laptop or other system")
print_line
end
#
# Perform a device reboot
#
def cmd_reboot(*args)
if args.length > 0
cmd_reboot_help
return
end
client.reboot
end
def cmd_load_custom_methods_help
print_line("Usage: load_custom_methods")
print_line
print_line "Checks to see if there are any custom HW commands and loads as"
print_line "interactive commands in your session."
end
#
# Loads custom methods if any exist
#
def cmd_load_custom_methods(*args)
if args.length > 0
cmd_load_custom_methods_help
return true
end
res = client.get_custom_methods
if res.has_key? 'Methods'
cmd_load("custom_methods")
self.shell.dispatcher_stack.each do |dispatcher|
if dispatcher.name =~ /custom methods/i
dispatcher.load_methods(res['Methods'])
end
end
print_status("Loaded #{res['Methods'].size} method(s)")
else
print_status("Not supported")
end
end
def cmd_load_help
print_line("Usage: load ext1 ext2 ext3 ...")
print_line
print_line "Loads a hardware extension module or modules."
print_line @@load_opts.usage
end
#
# Loads one or more meterpreter extensions.
#
def cmd_load(*args)
if args.length.zero?
args.unshift("-h")
end
@@load_opts.parse(args) { |opt, idx, val|
case opt
when '-h'
cmd_load_help
return true
end
}
# Load each of the modules
args.each { |m|
md = m.downcase
if extensions.include?(md)
print_error("The '#{md}' extension has already been loaded.")
next
end
print("Loading extension #{md}...")
begin
# Use the remote side, then load the client-side
#if (client.core.use(md) == true)
client.add_extension(md) # NOTE: Doesn't work, going to use core instead
add_extension_client(md)
#end
rescue
print_line
log_error("Failed to load extension: #{$!}")
next
end
print_line("success.")
}
return true
end
def cmd_run_help
print_line "Usage: run <script> [arguments]"
print_line
print_line "Executes a ruby script or Metasploit Post module in the context of the"
print_line "hardware bridge session. Post modules can take arguments in var=val format."
print_line "Example: run post/foo/bar BAZ=abcd"
print_line
end
#
# Executes a script in the context of the hwbridge session.
#
def cmd_run(*args)
if args.length.zero?
cmd_run_help
return true
end
# Get the script name
begin
script_name = args.shift
# First try it as a Post module if we have access to the Metasploit
# Framework instance. If we don't, or if no such module exists,
# fall back to using the scripting interface.
if msf_loaded? && mod = client.framework.modules.create(script_name)
original_mod = mod
reloaded_mod = client.framework.modules.reload_module(original_mod)
unless reloaded_mod
error = client.framework.modules.module_load_error_by_path[original_mod.file_path]
print_error("Failed to reload module: #{error}")
return
end
opts = (args + [ "SESSION=#{client.sid}" ]).join(',')
reloaded_mod.run_simple(
#'RunAsJob' => true,
'LocalInput' => shell.input,
'LocalOutput' => shell.output,
'OptionStr' => opts
)
else
# the rest of the arguments get passed in through the binding
client.execute_script(script_name, args)
end
rescue => e
print_error("Error in script: #{script_name}")
elog("Error in script: #{script_name}", error: e)
end
end
def cmd_run_tabs(str, words)
tabs = []
if !words[1] || !words[1].match(/^\//)
begin
if msf_loaded?
tabs = tab_complete_postmods
end
[ # We can just use Meterpreters script path
::Msf::Sessions::HWBridge.script_base,
::Msf::Sessions::HWBridge.user_script_base
].each do |dir|
next unless ::File.exist? dir
tabs += ::Dir.new(dir).find_all { |e|
path = dir + ::File::SEPARATOR + e
::File.file?(path) && ::File.readable?(path)
}
end
rescue Exception
end
end
return tabs.map { |e| e.sub(/\.rb$/, '') }
end
#
# Executes a script in the context of the hardware bridge session in the background
#
def cmd_bgrun(*args)
if args.length.zero?
print_line(
"Usage: bgrun <script> [arguments]\n\n" +
"Executes a ruby script in the context of the hardware bridge session.")
return true
end
jid = self.bgjob_id
self.bgjob_id += 1
# Get the script name
self.bgjobs[jid] = Rex::ThreadFactory.spawn("HWBridgeBGRun(#{args[0]})-#{jid}", false, jid, args) do |myjid,xargs|
::Thread.current[:args] = xargs.dup
begin
# the rest of the arguments get passed in through the binding
script_name = args.shift
client.execute_script(script_name, args)
rescue ::Exception => e
print_error("Error in script: #{script_name}")
elog("Error in script #{script_name}", error: e)
end
self.bgjobs[myjid] = nil
print_status("Background script with Job ID #{myjid} has completed (#{::Thread.current[:args].inspect})")
end
print_status("Executed HWBridge with Job ID #{jid}")
end
#
# Map this to the normal run command tab completion
#
def cmd_bgrun_tabs(*args)
cmd_run_tabs(*args)
end
#
# Kill a background job
#
def cmd_bgkill(*args)
if args.length.zero?
print_line("Usage: bgkill [id]")
return
end
args.each do |jid|
jid = jid.to_i
if self.bgjobs[jid]
print_status("Killing background job #{jid}...")
self.bgjobs[jid].kill
self.bgjobs[jid] = nil
else
print_error("Job #{jid} was not running")
end
end
end
#
# List background jobs
#
def cmd_bglist(*args)
self.bgjobs.each_index do |jid|
if self.bgjobs[jid]
print_status("Job #{jid}: #{self.bgjobs[jid][:args].inspect}")
end
end
end
def cmd_info_help
print_line 'Usage: info <module>'
print_line
print_line 'Prints information about a post-exploitation module'
print_line
end
@@client_extension_search_paths = [ ::File.join(Rex::Root, "post", "hwbridge", "ui", "console", "command_dispatcher") ]
def self.add_client_extension_search_path(path)
@@client_extension_search_paths << path unless @@client_extension_search_paths.include?(path)
end
def self.client_extension_search_paths
@@client_extension_search_paths
end
protected
attr_accessor :extensions # :nodoc:
attr_accessor :bgjobs, :bgjob_id # :nodoc:
CommDispatcher = Console::CommandDispatcher
#
# Loads the client extension specified in mod
#
def add_extension_client(mod)
loaded = false
klass = nil
self.class.client_extension_search_paths.each do |path|
path = ::File.join(path, "#{mod}.rb")
klass = CommDispatcher.check_hash(path)
if klass.nil?
old = CommDispatcher.constants
next unless ::File.exist? path
if require(path)
new = CommDispatcher.constants
diff = new - old
next if diff.empty?
klass = CommDispatcher.const_get(diff[0])
CommDispatcher.set_hash(path, klass)
loaded = true
break
else
print_error("Failed to load client script file: #{path}")
return false
end
else
# the klass is already loaded, from a previous invocation
loaded = true
break
end
end
unless loaded
print_error("Failed to load client portion of #{mod}.")
return false
end
# Enstack the dispatcher
self.shell.enstack_dispatcher(klass)
# Insert the module into the list of extensions
self.extensions << mod
end
def tab_complete_postmods
tabs = client.framework.modules.post.module_refnames.each { | name |
mod = client.framework.modules.post.create(name)
if mod && mod.session_compatible?(client)
mod.fullname.dup
else
nil
end
}
# nils confuse readline
tabs.compact
end
end
end
end
end
end