Skip to content

Commit 0199e4d

Browse files
author
Tod Beardsley
committed
Land rapid7#3770, resolve random stager bugs
2 parents 0b39c2e + 8aecd5e commit 0199e4d

File tree

8 files changed

+227
-32
lines changed

8 files changed

+227
-32
lines changed

lib/msf/core/encoded_payload.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,18 @@ def encode
158158
next
159159
end
160160

161+
# If the caller explictly requires register preservation, make sure
162+
# that the module in question can handle it. This is mostly used by
163+
# the stage encoder path.
164+
if (reqs['ForceSaveRegisters'] and
165+
reqs['EncoderOptions'] and
166+
(reqs['EncoderOptions']['SaveRegisters'].to_s.length > 0) and
167+
(! self.encoder.preserves_registers?))
168+
wlog("#{pinst.refname}: Encoder #{encoder.refname} does not preserve registers and the caller needs #{reqs['EncoderOptions']['SaveRegisters']} preserved.",
169+
'core', LEV_1)
170+
next
171+
end
172+
161173
# Import the datastore from payload (and likely exploit by proxy)
162174
self.encoder.share_datastore(pinst.datastore)
163175

@@ -224,12 +236,10 @@ def encode
224236
self.encoded = eout
225237
break
226238
}
227-
228239
# If the encoded payload is nil, raise an exception saying that we
229240
# suck at life.
230241
if (self.encoded == nil)
231242
self.encoder = nil
232-
233243
raise NoEncodersSucceededError,
234244
"#{pinst.refname}: All encoders failed to encode.",
235245
caller

lib/msf/core/encoder.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,27 @@ def to_native(buf)
413413
buf
414414
end
415415

416+
#
417+
# Determines whether the encoder can preserve registers at all
418+
#
419+
def preserves_registers?
420+
false
421+
end
422+
423+
#
424+
# A list of registers always modified by the encoder
425+
#
426+
def modified_registers
427+
[]
428+
end
429+
430+
#
431+
# Determines whether the encoder can preserve the stack frame
432+
#
433+
def preserves_stack?
434+
false
435+
end
436+
416437
protected
417438

418439
#

lib/msf/core/payload/stager.rb

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def initialize(info={})
1616
[
1717
Msf::OptBool.new("EnableStageEncoding", [ false, "Encode the second stage payload", false ]),
1818
Msf::OptString.new("StageEncoder", [ false, "Encoder to use if EnableStageEncoding is set", nil ]),
19+
Msf::OptString.new("StageEncoderSaveRegisters", [ false, "Additional registers to preserve in the staged payload if EnableStageEncoding is set", "" ]),
20+
Msf::OptBool.new("StageEncodingFallback", [ false, "Fallback to default encoders or no encoding if the selected StageEncoder is not compatible", true ])
1921
], Msf::Payload::Stager)
2022

2123
end
@@ -92,14 +94,12 @@ def stage_over_connection?
9294
true
9395
end
9496

95-
9697
#
9798
# Whether to use an Encoder on the second stage
9899
#
99100
# @return [Boolean]
100101
def encode_stage?
101-
# Convert to string in case it hasn't been normalized
102-
!!(datastore['EnableStageEncoding'].to_s == "true")
102+
!!(datastore['EnableStageEncoding'])
103103
end
104104

105105
#
@@ -134,7 +134,18 @@ def handle_connection(conn, opts={})
134134
p = generate_stage
135135

136136
# Encode the stage if stage encoding is enabled
137-
p = encode_stage(p)
137+
begin
138+
p = encode_stage(p)
139+
rescue ::RuntimeError
140+
warning_msg = "Failed to stage"
141+
warning_msg << " (#{conn.peerhost})" if conn.respond_to? :peerhost
142+
warning_msg << ": #{$!}"
143+
print_warning warning_msg
144+
if conn.respond_to? :close && !conn.closed?
145+
conn.close
146+
end
147+
return
148+
end
138149

139150
# Give derived classes an opportunity to an intermediate state before
140151
# the stage is sent. This gives derived classes an opportunity to
@@ -196,30 +207,75 @@ def handle_intermediate_stage(conn, payload)
196207
false
197208
end
198209

210+
#
211+
# Takes an educated guess at the list of registers an encoded stage
212+
# would need to preserve based on the Convention
213+
#
214+
def encode_stage_preserved_registers
215+
module_info['Convention'].to_s.scan(/\bsock([a-z]{3,}+)\b/).
216+
map {|reg| reg.first }.
217+
join(" ")
218+
end
219+
199220
# Encodes the stage prior to transmission
200221
# @return [String] Encoded version of +stg+
201222
def encode_stage(stg)
202223
return stg unless encode_stage?
224+
stage_enc_mod = []
203225

204-
if datastore["StageEncoder"].nil? or datastore["StageEncoder"].empty?
205-
stage_enc_mod = nil
206-
else
207-
stage_enc_mod = datastore["StageEncoder"]
226+
# Handle StageEncoder if specified by the user
227+
if datastore['StageEncoder'].to_s.length > 0
228+
# Allow multiple encoders separated by commas
229+
stage_enc_mod = datastore["StageEncoder"].split(',').map(&:strip).select{|x| x.to_s.length > 0}.uniq
230+
end
231+
232+
# Add automatic encoding as a fallback if needed
233+
if datastore['StageEncodingFallback']
234+
stage_enc_mod << nil
235+
end
236+
237+
# If fallback has been disabled and no encoder was parsed, exit early and rop the session
238+
if stage_enc_mod.length == 0
239+
raise RuntimeError, "StageEncoder is invalid and StageEncodingFallback is disabled"
240+
end
241+
242+
# Allow the user to specify additional registers to preserve
243+
saved_registers = (
244+
datastore['StageEncoderSaveRegisters'].to_s + " "
245+
encode_stage_preserved_registers
246+
).strip
247+
248+
estg = nil
249+
250+
stage_enc_mod.each do |encoder_refname_from_user|
251+
252+
# Generate an encoded version of the stage. We tell the encoding system
253+
# to save certain registers to ensure that it does not get clobbered.
254+
encp = Msf::EncodedPayload.create(
255+
self,
256+
'Raw' => stg,
257+
'Encoder' => encoder_refname_from_user,
258+
'EncoderOptions' => { 'SaveRegisters' => saved_registers },
259+
'ForceSaveRegisters' => true,
260+
'ForceEncode' => true)
261+
262+
if encp.encoder
263+
print_status("Encoded stage with #{encp.encoder.refname}")
264+
estg = encp.encoded
265+
break
266+
end
267+
end
268+
269+
if datastore['StageEncodingFallback'] && estg.nil?
270+
print_warning("StageEncoder failed, falling back to no encoding")
271+
estg = stg
272+
end
273+
274+
unless estg
275+
raise RuntimeError, "Stage encoding failed and StageEncodingFallback is disabled"
208276
end
209277

210-
# Generate an encoded version of the stage. We tell the encoding system
211-
# to save edi to ensure that it does not get clobbered.
212-
encp = Msf::EncodedPayload.create(
213-
self,
214-
'Raw' => stg,
215-
'Encoder' => stage_enc_mod,
216-
'SaveRegisters' => ['edi'],
217-
'ForceEncode' => true)
218-
print_status("Encoded stage with #{encp.encoder.refname}")
219-
220-
# If the encoding succeeded, use the encoded buffer. Otherwise, fall
221-
# back to using the non-encoded stage
222-
encp.encoded || stg
278+
estg
223279
end
224280

225281
# Aliases

lib/rex/arch/x86.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,22 @@ def self.geteip_fpu(badchars)
520520
return nil
521521
end
522522

523+
#
524+
# Parse a list of registers as a space or command delimited
525+
# string and return the internal register IDs as an array
526+
#
527+
def self.register_names_to_ids(str)
528+
register_ids = []
529+
str.to_s.strip.split(/[,\s]/).
530+
map {|reg| reg.to_s.strip.upcase }.
531+
select {|reg| reg.length > 0 }.
532+
uniq.each do |reg|
533+
next unless self.const_defined?(reg.intern)
534+
register_ids << self.const_get(reg.intern)
535+
end
536+
register_ids
537+
end
538+
523539
end
524540

525541
end end

modules/encoders/x86/call4_dword_xor.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ def initialize
2828
# the buffer being encoded
2929
#
3030
def decoder_stub(state)
31+
32+
# Sanity check that saved_registers doesn't overlap with modified_registers
33+
if (modified_registers & saved_registers).length > 0
34+
raise BadGenerateError
35+
end
36+
3137
decoder =
3238
Rex::Arch::X86.sub(-(((state.buf.length - 1) / 4) + 1), Rex::Arch::X86::ECX,
3339
state.badchars) +
@@ -44,4 +50,19 @@ def decoder_stub(state)
4450
return decoder
4551
end
4652

53+
# Indicate that this module can preserve some registers
54+
def preserves_registers?
55+
true
56+
end
57+
58+
# A list of registers always touched by this encoder
59+
def modified_registers
60+
[ Rex::Arch::X86::ECX, Rex::Arch::X86::EAX, Rex::Arch::X86::ESI ]
61+
end
62+
63+
# Convert the SaveRegisters to an array of x86 register constants
64+
def saved_registers
65+
Rex::Arch::X86.register_names_to_ids(datastore['SaveRegisters'])
66+
end
67+
4768
end

modules/encoders/x86/countdown.rb

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,22 @@ def initialize
3030
# being encoded.
3131
#
3232
def decoder_stub(state)
33+
34+
# Sanity check that saved_registers doesn't overlap with modified_registers
35+
if (modified_registers & saved_registers).length > 0
36+
raise BadGenerateError
37+
end
38+
3339
decoder =
3440
Rex::Arch::X86.set(
3541
Rex::Arch::X86::ECX,
3642
state.buf.length - 1,
3743
state.badchars) +
38-
"\xe8\xff\xff\xff" + # call $+4
39-
"\xff\xc1" + # inc ecx
40-
"\x5e" + # pop esi
41-
"\x30\x4c\x0e\x07" + # xor_loop: xor [esi + ecx + 0x07], cl
42-
"\xe2\xfa" # loop xor_loop
44+
"\xe8\xff\xff\xff" + # call $+4
45+
"\xff\xc1" + # inc ecx
46+
"\x5e" + # pop esi
47+
"\x30\x4c\x0e\x07" + # xor_loop: xor [esi + ecx + 0x07], cl
48+
"\xe2\xfa" # loop xor_loop
4349

4450
# Initialize the state context to 1
4551
state.context = 1
@@ -57,4 +63,18 @@ def encode_block(state, block)
5763
[ block.unpack('C')[0] ^ (state.context - 1) ].pack('C')
5864
end
5965

66+
# Indicate that this module can preserve some registers
67+
def preserves_registers?
68+
true
69+
end
70+
71+
# A list of registers always touched by this encoder
72+
def modified_registers
73+
[ Rex::Arch::X86::ECX, Rex::Arch::X86::ESI ]
74+
end
75+
76+
# Convert the SaveRegisters to an array of x86 register constants
77+
def saved_registers
78+
Rex::Arch::X86.register_names_to_ids(datastore['SaveRegisters'])
79+
end
6080
end

modules/encoders/x86/fnstenv_mov.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ def initialize
3131
# being encoded.
3232
#
3333
def decoder_stub(state)
34+
35+
# Sanity check that saved_registers doesn't overlap with modified_registers
36+
if (modified_registers & saved_registers).length > 0
37+
raise BadGenerateError
38+
end
39+
3440
decoder =
3541
Rex::Arch::X86.set(
3642
Rex::Arch::X86::ECX,
@@ -48,4 +54,18 @@ def decoder_stub(state)
4854
return decoder
4955
end
5056

57+
# Indicate that this module can preserve some registers
58+
def preserves_registers?
59+
true
60+
end
61+
62+
# A list of registers always touched by this encoder
63+
def modified_registers
64+
[ Rex::Arch::X86::EBX, Rex::Arch::X86::ECX ]
65+
end
66+
67+
# Convert the SaveRegisters to an array of x86 register constants
68+
def saved_registers
69+
Rex::Arch::X86.register_names_to_ids(datastore['SaveRegisters'])
70+
end
5171
end

0 commit comments

Comments
 (0)