Skip to content

Commit bdf30dd

Browse files
committed
Land rapid7#5374, --smallest option in msfvenom
2 parents 0fc8abc + 4487369 commit bdf30dd

19 files changed

+76
-62
lines changed

lib/msf/core/payload_generator.rb

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class PayloadGenerator
6161
# @!attribute platform
6262
# @return [String] The platform to build the payload for
6363
attr_accessor :platform
64+
# @!attribute smallest
65+
# @return [Boolean] Whether or not to find the smallest possible output
66+
attr_accessor :smallest
6467
# @!attribute space
6568
# @return [Fixnum] The maximum size in bytes of the payload
6669
attr_accessor :space
@@ -95,6 +98,7 @@ class PayloadGenerator
9598
# @option opts [Hash] :datastore (see #datastore)
9699
# @option opts [Msf::Framework] :framework (see #framework)
97100
# @option opts [Boolean] :cli (see #cli)
101+
# @option opts [Boolean] :smallest (see #smallest)
98102
# @raise [KeyError] if framework is not provided in the options hash
99103
def initialize(opts={})
100104
@add_code = opts.fetch(:add_code, '')
@@ -113,12 +117,20 @@ def initialize(opts={})
113117
@stdin = opts.fetch(:stdin, nil)
114118
@template = opts.fetch(:template, '')
115119
@var_name = opts.fetch(:var_name, 'buf')
120+
@smallest = opts.fetch(:smallest, false)
116121
@encoder_space = opts.fetch(:encoder_space, @space)
117122

118123
@framework = opts.fetch(:framework)
119124

120125
raise ArgumentError, "Invalid Payload Selected" unless payload_is_valid?
121126
raise ArgumentError, "Invalid Format Selected" unless format_is_valid?
127+
128+
# In smallest mode, override the payload @space & @encoder_space settings
129+
if @smallest
130+
@space = 0
131+
@encoder_space = 1.gigabyte
132+
end
133+
122134
end
123135

124136
# This method takes the shellcode generated so far and adds shellcode from
@@ -199,24 +211,36 @@ def encode_payload(shellcode)
199211
encoder_list = get_encoders
200212
if encoder_list.empty?
201213
cli_print "No encoder or badchars specified, outputting raw payload"
202-
shellcode
203-
else
204-
cli_print "Found #{encoder_list.count} compatible encoders"
205-
encoder_list.each do |encoder_mod|
206-
cli_print "Attempting to encode payload with #{iterations} iterations of #{encoder_mod.refname}"
207-
begin
208-
encoder_mod.available_space = @encoder_space
209-
return run_encoder(encoder_mod, shellcode.dup)
210-
rescue ::Msf::EncoderSpaceViolation => e
211-
cli_print "#{encoder_mod.refname} failed with #{e.message}"
212-
next
213-
rescue ::Msf::EncodingError => e
214-
cli_print "#{encoder_mod.refname} failed with #{e.message}"
215-
next
216-
end
214+
return shellcode
215+
end
216+
217+
results = {}
218+
219+
cli_print "Found #{encoder_list.count} compatible encoders"
220+
encoder_list.each do |encoder_mod|
221+
cli_print "Attempting to encode payload with #{iterations} iterations of #{encoder_mod.refname}"
222+
begin
223+
encoder_mod.available_space = @encoder_space unless @smallest
224+
results[encoder_mod.refname] = run_encoder(encoder_mod, shellcode.dup)
225+
break unless @smallest
226+
rescue ::Msf::EncoderSpaceViolation => e
227+
cli_print "#{encoder_mod.refname} failed with #{e.message}"
228+
next
229+
rescue ::Msf::EncodingError => e
230+
cli_print "#{encoder_mod.refname} failed with #{e.message}"
231+
next
217232
end
233+
end
234+
235+
if results.keys.length == 0
218236
raise ::Msf::EncodingError, "No Encoder Succeeded"
219237
end
238+
239+
# Return the shortest encoding of the payload
240+
chosen_encoder = results.keys.sort{|a,b| results[a].length <=> results[b].length}.first
241+
cli_print "#{chosen_encoder} chosen with final size #{results[chosen_encoder].length}"
242+
243+
results[chosen_encoder]
220244
end
221245

222246
# This returns a hash for the exe format generation of payloads
@@ -351,7 +375,7 @@ def get_encoders
351375
e.datastore.import_options_from_hash(datastore)
352376
encoders << e if e
353377
end
354-
encoders.sort_by { |my_encoder| my_encoder.rank }.reverse
378+
encoders.select{ |my_encoder| my_encoder.rank != ManualRanking }.sort_by { |my_encoder| my_encoder.rank }.reverse
355379
else
356380
encoders
357381
end

modules/encoders/cmd/echo.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ def encode_block(state, buf)
3434
end
3535

3636
if state.badchars.include?("-")
37-
raise RuntimeError
37+
raise EncodingError
3838
else
3939
# Without an escape character we can't escape anything, so echo
4040
# won't work.
4141
if state.badchars.include?("\\")
42-
raise RuntimeError
42+
raise EncodingError
4343
else
4444
buf = encode_block_bash_echo(state,buf)
4545
end
@@ -68,7 +68,7 @@ def encode_block_bash_echo(state, buf)
6868
if state.badchars.include?("`")
6969
# Last ditch effort, dollar paren
7070
if state.badchars.include?("$") or state.badchars.include?("(")
71-
raise RuntimeError
71+
raise EncodingError
7272
else
7373
buf = "$(/bin/echo -ne #{hex})"
7474
end

modules/encoders/cmd/generic_sh.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def encode_block_perl(state, buf)
6868
state.badchars.unpack('C*') { |c| qot.delete(c.chr) }
6969

7070
# Throw an error if we ran out of quotes
71-
raise RuntimeError if qot.length == 0
71+
raise EncodingError if qot.length == 0
7272

7373
sep = qot[0].chr
7474

@@ -83,7 +83,7 @@ def encode_block_perl(state, buf)
8383
if (state.badchars.match(/\(|\)/))
8484

8585
# No paranthesis...
86-
raise RuntimeError
86+
raise EncodingError
8787
end
8888

8989
cmd << "system\\(pack\\(qq#{sep}H\\*#{sep},qq#{sep}#{hex}#{sep}\\)\\)"
@@ -92,7 +92,7 @@ def encode_block_perl(state, buf)
9292
if (state.badchars.match(/\(|\)/))
9393
if (state.badchars.include?(" "))
9494
# No spaces allowed, no paranthesis, give up...
95-
raise RuntimeError
95+
raise EncodingError
9696
end
9797

9898
cmd << "'system pack qq#{sep}H*#{sep},qq#{sep}#{hex}#{sep}'"
@@ -124,7 +124,7 @@ def encode_block_bash_echo(state, buf)
124124
if (state.badchars.include?("`"))
125125
# Last ditch effort, dollar paren
126126
if (state.badchars.include?("$") or state.badchars.include?("("))
127-
raise RuntimeError
127+
raise EncodingError
128128
else
129129
buf = "$(/bin/echo -ne #{hex})"
130130
end

modules/encoders/cmd/perl.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def encode_block(state, buf)
3535
end
3636

3737
if state.badchars.include?("-")
38-
raise RuntimeError
38+
raise EncodingError
3939
else
4040
buf = encode_block_perl(state,buf)
4141
end
@@ -55,7 +55,7 @@ def encode_block_perl(state, buf)
5555
# Convert spaces to IFS...
5656
if state.badchars.include?(" ")
5757
if state.badchars.match(/[${IFS}]/n)
58-
raise RuntimeError
58+
raise EncodingError
5959
end
6060
cmd.gsub!(/\s/, '${IFS}')
6161
end
@@ -118,7 +118,7 @@ def perl_qq(state, qot, hex)
118118
state.badchars.unpack('C*') { |c| qot.delete(c.chr) }
119119

120120
# Throw an error if we ran out of quotes
121-
raise RuntimeError if qot.length == 0
121+
raise EncodingError if qot.length == 0
122122

123123
sep = qot[0].chr
124124
# Use an explicit length for the H specifier instead of just "H*"

modules/encoders/cmd/printf_php_mq.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def encode_block(state, buf)
5050
(state.badchars.include?("|")) or
5151
# We must have at least ONE of these two..
5252
(state.badchars.include?("x") and state.badchars.include?("0"))
53-
raise RuntimeError
53+
raise EncodingError
5454
end
5555

5656
# Now we build a string of the original payload with bad characters

modules/encoders/mipsbe/byte_xori.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def decoder_stub(state)
4444
# add 4 number of passes for the space reserved for the key, at the end of the decoder stub
4545
# (see commented source)
4646
number_of_passes=state.buf.length+4
47-
raise InvalidPayloadSizeException.new("The payload being encoded is too long (#{state.buf.length} bytes)") if number_of_passes > 32766
47+
raise EncodingError.new("The payload being encoded is too long (#{state.buf.length} bytes)") if number_of_passes > 32766
4848

4949
# 16-bits not (again, see also commented source)
5050
reg_14 = (number_of_passes+1)^0xFFFF

modules/encoders/mipsbe/longxor.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ def decoder_stub(state)
3535

3636
# add one xor operation for the key (see comment below)
3737
number_of_passes=state.buf.length/4+1
38-
raise InvalidPayloadSizeException.new("The payload being encoded is too long (#{state.buf.length} bytes)") if number_of_passes > 10240
39-
raise InvalidPayloadSizeException.new("The payload is not padded to 4-bytes (#{state.buf.length} bytes)") if state.buf.length%4 != 0
38+
raise EncodingError.new("The payload being encoded is too long (#{state.buf.length} bytes)") if number_of_passes > 10240
39+
raise EncodingError.new("The payload is not padded to 4-bytes (#{state.buf.length} bytes)") if state.buf.length%4 != 0
4040

4141
# 16-bits not (again, see below)
4242
reg_14 = (number_of_passes+1)^0xFFFF

modules/encoders/mipsle/byte_xori.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def decoder_stub(state)
4444
# add 4 number of passes for the space reserved for the key, at the end of the decoder stub
4545
# (see commented source)
4646
number_of_passes=state.buf.length+4
47-
raise InvalidPayloadSizeException.new("The payload being encoded is too long (#{state.buf.length} bytes)") if number_of_passes > 32766
47+
raise EncodingError.new("The payload being encoded is too long (#{state.buf.length} bytes)") if number_of_passes > 32766
4848

4949
# 16-bits not (again, see also commented source)
5050
reg_14 = (number_of_passes+1)^0xFFFF

modules/encoders/mipsle/longxor.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ def decoder_stub(state)
3535

3636
# add one xor operation for the key (see comment below)
3737
number_of_passes=state.buf.length/4+1
38-
raise InvalidPayloadSizeException.new("The payload being encoded is too long (#{state.buf.length} bytes)") if number_of_passes > 10240
39-
raise InvalidPayloadSizeException.new("The payload is not padded to 4-bytes (#{state.buf.length} bytes)") if state.buf.length%4 != 0
38+
raise EncodingError.new("The payload being encoded is too long (#{state.buf.length} bytes)") if number_of_passes > 10240
39+
raise EncodingError.new("The payload is not padded to 4-bytes (#{state.buf.length} bytes)") if state.buf.length%4 != 0
4040

4141
# 16-bits not (again, see below)
4242
reg_14 = (number_of_passes+1)^0xFFFF

modules/encoders/php/base64.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def encode_block(state, buf)
2727
# Have to have these for the decoder stub, so if they're not available,
2828
# there's nothing we can do here.
2929
["(",")",".","_","c","h","r","e","v","a","l","b","s","6","4","d","o"].each do |c|
30-
raise EncodeError if state.badchars.include?(c)
30+
raise BadcharError if state.badchars.include?(c)
3131
end
3232

3333
# PHP escapes quotes by default with magic_quotes_gpc, so we use some

0 commit comments

Comments
 (0)