Make stage encoding possible

* Fixes a bug in shikata where input greater than 0xffff length would
  still use 16-bit counter
* Short circuits finding bad xor keys if there are no bad characters to
  avoid
* Fixes huge performance issue with large inputs to xor-based encoders
  due to the use of String#+ instead of String#<< in a loop. It now
  takes ~3 seconds on modern hardware to encode a 750kB buffer with
  shikata where it used to take more than 10 minutes. The decoding side
  takes a similar amount of time and will increase the wait between
  sending the second stage and opening a usable session by several
  seconds.

I believe this addresses the intent of pull request 905

[See #905]
bug/bundler_fix
James Lee 2013-01-13 21:07:39 -06:00
parent 4703a6f737
commit b3b68c1b90
5 changed files with 47 additions and 13 deletions

View File

@ -308,7 +308,12 @@ class Encoder < Module
while (offset < state.buf.length)
block = state.buf[offset, decoder_block_size]
state.encoded += encode_block(state,
# Append here (String#<<) instead of creating a new string with
# String#+ because the allocations kill performance with large
# buffers. This isn't usually noticeable on most shellcode, but
# when doing stage encoding on meterpreter (~750k bytes) the
# difference is 2 orders of magnitude.
state.encoded << encode_block(state,
block + ("\x00" * (decoder_block_size - block.length)))
offset += decoder_block_size

View File

@ -26,6 +26,9 @@ class Msf::Encoder::Xor < Msf::Encoder
# Finds keys that are incompatible with the supplied bad character list.
#
def find_bad_keys(buf, badchars)
# Short circuit if there are no badchars
return super if badchars.length == 0
bad_keys = Array.new(decoder_key_size) { Hash.new }
byte_idx = 0

View File

@ -1,5 +1,6 @@
# -*- coding: binary -*-
require 'msf/core'
require 'msf/core/option_container'
###
#
@ -8,6 +9,17 @@ require 'msf/core'
###
module Msf::Payload::Stager
def initialize(info={})
super
register_advanced_options(
[
Msf::OptBool.new("EnableStageEncoding", [ false, "Encode the second stage payload", false ]),
Msf::OptString.new("StageEncoder", [ false, "Encoder to use if EnableStageEncoding is set", nil ]),
], Msf::Payload::Stager)
end
#
# Sets the payload type to a stager.
#
@ -65,6 +77,11 @@ module Msf::Payload::Stager
true
end
def encode_stage?
# Convert to string in case it hasn't been normalized
!!(datastore['EnableStageEncoding'].to_s == "true")
end
#
# Generates the stage payload and substitutes all offsets.
#
@ -75,8 +92,8 @@ module Msf::Payload::Stager
# Substitute variables in the stage
substitute_vars(p, stage_offsets) if (stage_offsets)
# Encode the stage of stage encoding is enabled
#p = encode_stage(p)
# Encode the stage if stage encoding is enabled
p = encode_stage(p)
return p
end
@ -101,14 +118,15 @@ module Msf::Payload::Stager
p = (self.stage_prefix || '') + p
end
sending_msg = "Sending #{encode_stage? ? "encoded ":""}stage"
sending_msg << " (#{p.length} bytes)"
# The connection should always have a peerhost (even if it's a
# tunnel), but if it doesn't, erroring out here means losing the
# session, so make sure it does, just to be safe.
if conn.respond_to? :peerhost
print_status("Sending stage (#{p.length} bytes) to #{conn.peerhost}")
else
print_status("Sending stage (#{p.length} bytes)")
sending_msg << " to #{conn.peerhost}"
end
print_status(sending_msg)
# Send the stage
conn.put(p)
@ -146,15 +164,20 @@ module Msf::Payload::Stager
# Encodes the stage prior to transmission
def encode_stage(stg)
return stg unless encode_stage?
# If DisableStageEncoding is set, we do not encode the stage
return stg if datastore['DisableStageEncoding'] =~ /^(y|1|t)/i
if datastore["StageEncoder"].nil? or datastore["StageEncoder"].empty?
stage_enc_mod = nil
else
stage_enc_mod = datastore["StageEncoder"]
end
# Generate an encoded version of the stage. We tell the encoding system
# to save edi to ensure that it does not get clobbered.
encp = Msf::EncodedPayload.create(
self,
'Raw' => stg,
'Encoder' => stage_enc_mod,
'SaveRegisters' => ['edi'],
'ForceEncode' => true)

View File

@ -2365,6 +2365,7 @@ class Core
return option_values_payloads() if opt.upcase == 'PAYLOAD'
return option_values_targets() if opt.upcase == 'TARGET'
return option_values_nops() if opt.upcase == 'NOPS'
return option_values_encoders() if opt.upcase == 'StageEncoder'
end
# Well-known option names specific to auxiliaries

View File

@ -111,10 +111,10 @@ protected
# Clear the counter register
clear_register = Rex::Poly::LogicalBlock.new('clear_register',
"\x31\xc9",
"\x29\xc9",
"\x33\xc9",
"\x2b\xc9")
"\x31\xc9", # xor ecx,ecx
"\x29\xc9", # sub ecx,ecx
"\x33\xc9", # xor ecx,ecx
"\x2b\xc9") # sub ecx,ecx
# Initialize the counter after zeroing it
init_counter = Rex::Poly::LogicalBlock.new('init_counter')
@ -126,8 +126,10 @@ protected
if (length <= 255)
init_counter.add_perm("\xb1" + [ length ].pack('C'))
else
elsif (length <= 65536)
init_counter.add_perm("\x66\xb9" + [ length ].pack('v'))
else
init_counter.add_perm("\xb9" + [ length ].pack('V'))
end
# Key initialization block