-
Notifications
You must be signed in to change notification settings - Fork 14.9k
Expand file tree
/
Copy pathpayload.rb
More file actions
731 lines (623 loc) · 19.9 KB
/
payload.rb
File metadata and controls
731 lines (623 loc) · 19.9 KB
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
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
# -*- coding: binary -*-
module Msf
###
#
# This class represents the base class for a logical payload. The framework
# automatically generates payload combinations at runtime which are all
# extended from this Payload as a base class.
#
###
class Payload < Msf::Module
# Platform specific includes
require 'metasploit/framework/compiler/mingw'
# Universal payload includes
##
#
# Payload types
#
##
module Type
#
# Single payload type. These types of payloads are self contained and
# do not go through any staging.
#
Single = (1 << 0)
#
# The stager half of a staged payload. Its responsibility in life is to
# read in the stage and execute it.
#
Stager = (1 << 1)
#
# The stage half of a staged payload. This payload performs whatever
# arbitrary task it's designed to do, possibly making use of the same
# connection that the stager used to read the stage in on, if
# applicable.
#
Stage = (1 << 2)
Adapter = (1 << 3)
end
#
# Creates an instance of a payload module using the supplied information.
#
def initialize(info = {})
super
#
# Gets the Dependencies if the payload requires external help
# to work
#
self.module_info['Dependencies'] = self.module_info['Dependencies'] || []
# If this is an adapted or staged payload but there is no stage information,
# then this is actually a stager + single combination. Set up the
# information hash accordingly.
if (self.class.include?(Msf::Payload::Adapter) || self.class.include?(Msf::Payload::Single)) and self.class.include?(Msf::Payload::Stager)
if self.module_info['Payload']
self.module_info['Stage']['Payload'] = self.module_info['Payload']['Payload'] || ""
self.module_info['Stage']['Assembly'] = self.module_info['Payload']['Assembly'] || ""
self.module_info['Stage']['Offsets'] = self.module_info['Payload']['Offsets'] || {}
elsif !self.module_info['Stage']
self.module_info['Stage'] = {}
self.module_info['Stage']['Payload'] = ""
self.module_info['Stage']['Assembly'] = ""
self.module_info['Stage']['Offsets'] = {}
end
@staged = true
else
@staged = false
end
# Update the module info hash with the connection type
# that is derived from the handler for this payload. This is
# used for compatibility filtering purposes.
self.module_info['ConnectionType'] = connection_type
end
##
#
# Accessors
#
##
#
# Returns MODULE_PAYLOAD to indicate that this is a payload module.
#
def self.type
return Msf::MODULE_PAYLOAD
end
#
# Returns MODULE_PAYLOAD to indicate that this is a payload module.
#
def type
return Msf::MODULE_PAYLOAD
end
#
# Returns the string of bad characters for this payload, if any.
#
def badchars
return self.module_info['BadChars']
end
#
# The list of registers that should be saved by any NOP generators or
# encoders, if possible.
#
def save_registers
return self.module_info['SaveRegisters']
end
#
# Returns the type of payload, either single or staged. Stage is
# the default because singles and stagers are encouraged to include
# the Single and Stager mixin which override the payload_type.
#
def payload_type
return Type::Stage
end
#
# Returns the string version of the payload type
#
def payload_type_s
case payload_type
when Type::Adapter
return "adapter"
when Type::Stage
return "stage"
when Type::Stager
return "stager"
when Type::Single
return "single"
else
return "unknown"
end
end
#
# This method returns whether or not this payload uses staging.
#
def staged?
(@staged or payload_type == Type::Stager or payload_type == Type::Stage)
end
#
# This method returns an optional cached size value
#
def self.cached_size
csize = const_defined?('CachedSize') ? const_get('CachedSize') : nil
if ancestors.include?(Msf::Payload::Stager)
csize_overrides = const_defined?('CachedSizeOverrides') ? const_get('CachedSizeOverrides') : {}
csize = csize_overrides.fetch(self.refname, csize)
end
csize == :dynamic ? nil : csize
end
#
# This method returns whether the payload generates variable-sized output
#
def self.dynamic_size?
csize = const_defined?('CachedSize') ? const_get('CachedSize') : nil
if ancestors.include?(Msf::Payload::Stager)
csize_overrides = const_defined?('CachedSizeOverrides') ? const_get('CachedSizeOverrides') : {}
csize = csize_overrides.fetch(self.refname, csize)
end
csize == :dynamic
end
#
# This method returns an optional cached size value
#
def cached_size
self.class.cached_size
end
#
# This method returns whether the payload generates variable-sized output
#
def dynamic_size?
self.class.dynamic_size?
end
#
# Returns the payload's size. If the payload is staged, the size of the
# first stage is returned.
#
def size
pl = nil
begin
pl = generate()
rescue Metasploit::Framework::Compiler::Mingw::UncompilablePayloadError
rescue NoCompatiblePayloadError
rescue PayloadItemSizeError
end
pl ||= ''
pl.length
end
#
# Returns the raw payload that has not had variable substitution occur.
#
def payload
return module_info['Payload'] ? module_info['Payload']['Payload'] : nil
end
#
# Returns the assembly string that describes the payload if one exists.
#
def assembly
return module_info['Payload'] ? module_info['Payload']['Assembly'] : nil
end
#
# Sets the assembly string that describes the payload
# If this method is used to define the payload, a payload with no offsets will be created
#
def assembly=(asm)
module_info['Payload'] ||= {'Offsets' => {} }
module_info['Payload']['Assembly'] = asm
end
#
# Returns the offsets to variables that must be substitute, if any.
#
def offsets
return module_info['Payload'] ? module_info['Payload']['Offsets'] : nil
end
#
# Returns the compiler dependencies if the payload has one
#
def dependencies
module_info['Dependencies']
end
#
# Returns the staging convention that the payload uses, if any. This is
# used to make sure that only compatible stagers and stages are built
# (where assumptions are made about register/environment initialization
# state and hand-off).
#
def convention
module_info['Convention']
end
#
# Returns the module's connection type, such as reverse, bind, noconn,
# or whatever else the case may be.
#
def connection_type
handler_klass.general_handler_type
end
#
# Returns the method used by the payload to resolve symbols for the purpose
# of calling functions, such as ws2ord.
#
def symbol_lookup
module_info['SymbolLookup']
end
#
# Return the connection associated with this payload, or none if there
# isn't one.
#
def handler_klass
return module_info['Handler'] || Msf::Handler::None
end
#
# Returns the session class that is associated with this payload and will
# be used to create a session as necessary.
#
def session
return module_info['Session']
end
##
#
# Generation & variable substitution
#
##
#
# Generates the payload and returns the raw buffer to the caller.
# @param opts [Hash]
def generate(opts = {})
internal_generate(opts)
end
#
# Generates the payload and returns the raw buffer to the caller,
# handling any post-processing tasks, such as prepended code stubs.
def generate_complete
apply_prepends(generate)
end
#
# Convert raw bytes to metasm-ready 'db' encoding format
# eg. "\x90\xCC" => "db 0x90,0xCC"
#
# @param raw [Array] Byte array to encode.
#
def raw_to_db(raw)
raw.unpack("C*").map {|c| "0x%.2x" % c}.join(",")
end
#
# Substitutes variables with values from the module's datastore in the
# supplied raw buffer for a given set of named offsets. For instance,
# RHOST is substituted with the RHOST value from the datastore which will
# have been populated by the framework.
#
# Supported packing types:
#
# - ADDR (foo.com, 1.2.3.4)
# - ADDR6 (foo.com, fe80::1234:5678:8910:1234)
# - ADDR16MSB, ADD16LSB, ADDR22MSB, ADD22LSB (foo.com, 1.2.3.4)
# Advanced packing types for 16/16 and 22/10 bits substitution. The 16
# bits types uses two offsets indicating where the 16 bits pair will be
# substituted, while the 22 bits types uses two offsets indicating the
# instructions where the 22/10 bits pair will be substituted. Normally
# these are offsets to "sethi" and "or" instructions on SPARC architecture.
# - HEX (0x12345678, "\x41\x42\x43\x44")
# - RAW (raw bytes)
#
def substitute_vars(raw, offsets)
offsets.each_pair { |name, info|
offset, pack = info
# Give the derived class a chance to substitute this variable
next if (replace_var(raw, name, offset, pack) == true)
# Now it's our turn...
if ((val = datastore[name]))
if (pack == 'ADDR')
val = Rex::Socket.resolv_nbo(val)
# Someone gave us a funky address (ipv6?)
if(val.length == 16)
raise RuntimeError, "IPv6 address specified for IPv4 payload."
end
elsif (pack == 'ADDR6')
val = Rex::Socket.resolv_nbo(val)
# Convert v4 to the v6ish address
if(val.length == 4)
nip = "fe80::5efe:" + val.unpack("C*").join(".")
val = Rex::Socket.resolv_nbo(nip)
end
elsif (['ADDR16MSB', 'ADDR16LSB', 'ADDR22MSB', 'ADDR22LSB'].include?(pack))
val = Rex::Socket.resolv_nbo(val)
# Someone gave us a funky address (ipv6?)
if(val.length == 16)
raise RuntimeError, "IPv6 address specified for IPv4 payload."
end
elsif (pack == 'RAW')
# Just use the raw value...
else
# Check to see if the value is a hex string. If so, convert
# it.
if val.kind_of?(String)
if val =~ /^\\x/n
val = [ val.gsub(/\\x/n, '') ].pack("H*").unpack(pack)[0]
elsif val =~ /^0x/n
val = val.hex
end
end
# NOTE:
# Packing assumes integer format at this point, should fix...
val = [ val.to_i ].pack(pack)
end
# Substitute it
if (['ADDR16MSB', 'ADDR16LSB'].include?(pack))
if (offset.length != 2)
raise RuntimeError, "Missing value for payload offset, there must be two offsets."
end
if (pack == 'ADDR16LSB')
val = val.unpack('N').pack('V')
end
raw[offset[0], 2] = val[0, 2]
raw[offset[1], 2] = val[2, 2]
elsif (['ADDR22MSB', 'ADDR22LSB'].include?(pack))
if (offset.length != 2)
raise RuntimeError, "Missing value for payload offset, there must be two offsets."
end
if (pack == 'ADDR22LSB')
val = val.unpack('N').pack('V')
end
hi = (0xfffffc00 & val) >> 10
lo = 0x3ff & val
ins = raw[offset[0], 4]
raw[offset[0], 4] = ins | hi
ins = raw[offset[1], 4]
raw[offset[1], 4] = ins | lo
else
raw[offset, val.length] = val
end
else
wlog("Missing value for payload offset #{name}, skipping.",
'core', LEV_3)
end
}
end
#
# Replaces an individual variable in the supplied buffer at an offset
# using the given pack type. This is here to allow derived payloads
# the opportunity to replace advanced variables.
#
def replace_var(raw, name, offset, pack)
return false
end
##
#
# Shortcut methods for filtering compatible encoders
# and NOP sleds
#
##
#
# Returns the array of compatible encoders for this payload instance.
#
def compatible_encoders
encoders = []
framework.encoders.each_module_ranked(
'Arch' => self.arch, 'Platform' => self.platform) { |name, mod|
encoders << [ name, mod ]
}
return encoders
end
#
# Returns the array of compatible nops for this payload instance.
#
def compatible_nops
nops = []
framework.nops.each_module_ranked(
'Arch' => self.arch) { |name, mod|
nops << [ name, mod ]
}
return nops
end
# Select a reasonable default payload and minimally configure it
# @param [Msf::Module] mod
def self.choose_payload(mod)
compatible_payloads = mod.compatible_payloads(
excluded_platforms: ['Multi'] # We don't want to select a multi payload
).map(&:first)
# XXX: Determine LHOST based on global LHOST, RHOST or an arbitrary internet address
lhost = mod.datastore['LHOST'] || Rex::Socket.source_address(mod.datastore['RHOST'] || '50.50.50.50')
configure_payload = lambda do |payload|
if mod.datastore.is_a?(Msf::DataStore)
payload_defaults = { 'PAYLOAD' => payload }
# Set LHOST if this is a reverse payload
if payload.index('reverse')
payload_defaults['LHOST'] = lhost
end
mod.datastore.import_defaults_from_hash(payload_defaults, imported_by: 'choose_payload')
else
mod.datastore['PAYLOAD'] = payload
# Set LHOST if this is a reverse payload
if payload.index('reverse')
mod.datastore['LHOST'] = lhost
end
end
payload
end
# If there is only one compatible payload, return it immediately
if compatible_payloads.length == 1
return configure_payload.call(compatible_payloads.first)
end
# XXX: This approach is subpar, and payloads should really be ranked!
preferred_payloads = [
# These payloads are generally reliable and common enough in practice
'windows/meterpreter/reverse_tcp', # all 64-bit versions of Windows will also support x86 but the same isn't true
'x64/meterpreter/reverse_tcp', # for Linux so if a 32-bit Windows Meterpreter isn't an option, select any x64
'x86/meterpreter/reverse_tcp', # Meterpreter
'/meterpreter/reverse_tcp',
'/shell/reverse_tcp',
'cmd/unix/reverse_bash',
'cmd/unix/reverse_netcat',
'cmd/windows/powershell_reverse_tcp',
# Fall back on a generic payload to autoselect a specific payload
'generic/shell_reverse_tcp',
'generic/shell_bind_tcp'
]
# XXX: This is not efficient in the slightest
preferred_payloads.each do |type|
payload = compatible_payloads.find { |name| name.end_with?(type) }
next unless payload
return configure_payload.call(payload)
end
nil
end
#
# A placeholder stub, to be overridden by mixins
#
def apply_prepends(raw)
raw
end
##
#
# Event notifications.
#
##
#
# Once an exploit completes and a session has been created on behalf of the
# payload, the framework will call the payload's on_session notification
# routine to allow it to manipulate the session prior to handing off
# control to the user.
#
def on_session(session)
# If this payload is associated with an exploit, inform the exploit
# that a session has been created and potentially shut down any
# open sockets. This allows active exploits to continue hammering
# on a service until a session is created.
if (assoc_exploit)
# Signal that a new session is created by calling the exploit's
# on_new_session handler. The default behavior is to set an
# instance variable, which the exploit will have to check.
begin
assoc_exploit.on_new_session(session)
rescue ::Exception => e
dlog("#{assoc_exploit.refname}: on_new_session handler triggered exception: #{e.class} #{e} #{e.backtrace}", 'core', LEV_1) rescue nil
end
# Set the abort sockets flag only if the exploit is not passive
# and the connection type is not 'find'
if (
(assoc_exploit.exploit_type == Exploit::Type::Remote) and
(assoc_exploit.passive? == false) and
(connection_type != 'find')
)
assoc_exploit.abort_sockets
end
end
end
#
# This attribute holds the string that should be prepended to the buffer
# when it's generated.
#
attr_accessor :prepend
#
# This attribute holds the string that should be appended to the buffer
# when it's generated.
#
attr_accessor :append
#
# This attribute holds the string that should be prepended to the encoded
# version of the payload (in front of the encoder as well).
#
attr_accessor :prepend_encoder
#
# If this payload is associated with an exploit, the assoc_exploit
# attribute will point to that exploit instance.
#
attr_accessor :assoc_exploit
#
# The amount of space available to the payload, which may be nil,
# indicating that the smallest possible payload should be used.
#
attr_accessor :available_space
protected
#
# If the payload has assembly that needs to be compiled, do so now.
#
# Blobs will be cached in the framework's PayloadSet
#
# @see PayloadSet#check_blob_cache
# @param asm [String] Assembly code to be assembled into a raw payload
# @param opts [Hash]
# @return [String] The final, assembled payload
# @raise ArgumentError if +asm+ is blank
def build(asm, off={}, opts = {})
if(asm.nil? or asm.empty?)
raise ArgumentError, "Assembly must not be empty"
end
# Use the refname so blobs can be flushed when the module gets
# reloaded and use the hash value to ensure that we're actually
# getting the right blob for the given assembly.
cache_key = refname + asm.hash.to_s
cache_entry = framework.payloads.check_blob_cache(cache_key)
off.each_pair { |option, val|
if (val[1] == 'RAW')
asm = asm.gsub(/#{option}/){ datastore[option] }
off.delete(option)
end
}
# If there is a valid cache entry, then we don't need to worry about
# rebuilding the assembly
if cache_entry
# Update the local offsets from the cache
off.each_key { |option|
off[option] = cache_entry[1][option]
}
# Return the cached payload blob
return cache_entry[0].dup
end
# Assemble the payload from the assembly
a = opts[:arch] || self.arch
if a.kind_of? Array
a = self.arch.first
end
cpu = case a
when ARCH_X86 then Metasm::Ia32.new
when ARCH_X64 then Metasm::X86_64.new
when ARCH_PPC then Metasm::PowerPC.new
when ARCH_ARMLE then Metasm::ARM.new
when ARCH_MIPSLE then Metasm::MIPS.new(:little)
when ARCH_MIPSBE then Metasm::MIPS.new(:big)
else
elog("Broken payload #{refname} has arch unsupported with assembly: #{module_info["Arch"].inspect}")
elog("Call stack:\n#{caller.join("\n")}")
return ""
end
sc = Metasm::Shellcode.assemble(cpu, asm).encoded
# Calculate the actual offsets now that it's been built
off.each_pair { |option, val|
off[option] = [ sc.offset_of_reloc(option) || val[0], val[1] ]
}
# Cache the payload blob
framework.payloads.add_blob_cache(cache_key, sc.data, off)
# Return a duplicated copy of the assembled payload
sc.data.dup
end
#
# Generate the payload using our local payload blob and offsets
#
def internal_generate(opts = {})
# Build the payload, either by using the raw payload blob defined in the
# module or by actually assembling it
if assembly and !assembly.empty?
raw = build(assembly, offsets, opts)
else
raw = payload.dup
end
# If the payload is generated and there are offsets to substitute,
# do that now.
if (raw and offsets)
substitute_vars(raw, offsets)
end
return raw
end
##
#
# Custom merge operations for payloads
#
##
#
# Merge the name to prefix the existing one and separate them
# with a comma
#
def merge_name(info, val)
if (info['Name'])
info['Name'] = val + ',' + info['Name']
else
info['Name'] = val
end
end
end
end