328 lines
9.3 KiB
Ruby
328 lines
9.3 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
require 'rex/poly/machine'
|
|
|
|
module Rex
|
|
|
|
module Encoder
|
|
|
|
class BloXor < Msf::Encoder
|
|
|
|
def initialize( *args )
|
|
super
|
|
@machine = nil
|
|
@blocks_out = []
|
|
@block_size = 0
|
|
end
|
|
|
|
#
|
|
#
|
|
#
|
|
def decoder_stub( state )
|
|
|
|
if( not state.decoder_stub )
|
|
@blocks_out = []
|
|
@block_size = 0
|
|
|
|
# XXX: It would be ideal to use a random block size but unless we know the maximum size our final encoded
|
|
# blob can be we should instead start with the smallest block size and go up to avoid generating
|
|
# anything too big (if we knew the max size we could try something smaller if we generated a blob too big)
|
|
#block_sizes = (1..state.buf.length).to_a.shuffle
|
|
#block_sizes.each do | len |
|
|
|
|
1.upto( state.buf.length ) do | len |
|
|
|
|
# For now we ignore all odd sizes to help with performance (The rex poly machine
|
|
# doesnt have many load/store primitives that can handle byte sizes efficiently)
|
|
if( len % 2 != 0 )
|
|
next
|
|
end
|
|
|
|
blocks, size = compute_encoded( state, len )
|
|
if( blocks and size )
|
|
|
|
# We sanity check that the newly generated block ammount and the block size
|
|
# are not in the badchar list when converted into a hex form. Helps speed
|
|
# things up a great deal when generating a decoder stub later as these
|
|
# values may be used throughout.
|
|
|
|
if( not number_is_valid?( state, blocks.length - 1 ) or not number_is_valid?( state, ~( blocks.length - 1 ) ) )
|
|
next
|
|
end
|
|
|
|
if( not number_is_valid?( state, size ) or not number_is_valid?( state, ~size ) )
|
|
next
|
|
end
|
|
|
|
@blocks_out = blocks
|
|
@block_size = size
|
|
|
|
break
|
|
end
|
|
end
|
|
|
|
raise RuntimeError, "Unable to generate seed block." if( @blocks_out.empty? )
|
|
|
|
state.decoder_stub = compute_decoder( state )
|
|
end
|
|
|
|
state.decoder_stub
|
|
end
|
|
|
|
#
|
|
#
|
|
#
|
|
def encode_block( state, data )
|
|
|
|
buffer = ''
|
|
|
|
@blocks_out.each do | block |
|
|
buffer << block.pack( 'C*' )
|
|
end
|
|
|
|
buffer
|
|
end
|
|
|
|
protected
|
|
|
|
#
|
|
# Is a number in its byte form valid against the badchars?
|
|
#
|
|
def number_is_valid?( state, number )
|
|
size = 'C'
|
|
if( number > 0xFFFF )
|
|
size = 'V'
|
|
elsif( number > 0xFF )
|
|
size = 'v'
|
|
end
|
|
return Rex::Text.badchar_index( [ number ].pack( size ), state.badchars ).nil?
|
|
end
|
|
|
|
#
|
|
# Calculate Shannon's entropy.
|
|
#
|
|
def entropy( data )
|
|
entropy = 0.to_f
|
|
(0..255).each do | byte |
|
|
freq = data.to_s.count( byte.chr ).to_f / data.to_s.length
|
|
if( freq > 0 )
|
|
entropy -= freq * Math.log2( freq )
|
|
end
|
|
end
|
|
return entropy / 8
|
|
end
|
|
|
|
#
|
|
# Compute the encoded blocks (and associated seed)
|
|
#
|
|
def compute_encoded( state, len )
|
|
|
|
blocks_in = ::Array.new
|
|
|
|
input = '' << state.buf
|
|
|
|
block_padding = ( input.length % len ) > 0 ? len - ( input.length % len ) : 0
|
|
|
|
if( block_padding > 0 )
|
|
0.upto( block_padding-1 ) do
|
|
input << [ rand( 255 ) ].pack( 'C' )
|
|
end
|
|
end
|
|
|
|
while( input.length > 0 )
|
|
blocks_in << input[0..len-1].unpack( 'C*' )
|
|
input = input[len..input.length]
|
|
end
|
|
|
|
seed = compute_seed( blocks_in, len, block_padding, state.badchars.unpack( 'C*' ) )
|
|
|
|
if( not seed )
|
|
return [ nil, nil ]
|
|
end
|
|
|
|
blocks_out = [ seed ]
|
|
|
|
blocks_in.each do | block |
|
|
blocks_out << compute_block( blocks_out.last, block )
|
|
end
|
|
|
|
return [ blocks_out, len ]
|
|
end
|
|
|
|
#
|
|
# Generate the decoder stub which is functionally equivalent to the following:
|
|
#
|
|
# source = &end;
|
|
# dest = source + BLOCK_SIZE;
|
|
# counter = BLOCK_COUNT * ( BLOCK_SIZE / chunk_size );
|
|
# do
|
|
# {
|
|
# encoded = *(CHUNK_SIZE *)dest;
|
|
# dest += chunk_size;
|
|
# decoded = *(CHUNK_SIZE *)source;
|
|
# *(CHUNK_SIZE *)source = decoded ^ encoded;
|
|
# source += chunk_size;
|
|
# } while( --counter );
|
|
#
|
|
# end:
|
|
#
|
|
def compute_decoder( state )
|
|
|
|
@machine.create_variable( 'source' )
|
|
@machine.create_variable( 'dest' )
|
|
@machine.create_variable( 'counter' )
|
|
@machine.create_variable( 'encoded' )
|
|
@machine.create_variable( 'decoded' )
|
|
|
|
chunk_size = Rex::Poly::Machine::BYTE
|
|
if( @machine.native_size() == Rex::Poly::Machine::QWORD )
|
|
if( @block_size % Rex::Poly::Machine::QWORD == 0 )
|
|
chunk_size = Rex::Poly::Machine::QWORD
|
|
elsif( @block_size % Rex::Poly::Machine::DWORD == 0 )
|
|
chunk_size = Rex::Poly::Machine::DWORD
|
|
elsif( @block_size % Rex::Poly::Machine::WORD == 0 )
|
|
chunk_size = Rex::Poly::Machine::WORD
|
|
end
|
|
elsif( @machine.native_size() == Rex::Poly::Machine::DWORD )
|
|
if( @block_size % Rex::Poly::Machine::DWORD == 0 )
|
|
chunk_size = Rex::Poly::Machine::DWORD
|
|
elsif( @block_size % Rex::Poly::Machine::WORD == 0 )
|
|
chunk_size = Rex::Poly::Machine::WORD
|
|
end
|
|
elsif( @machine.native_size() == Rex::Poly::Machine::WORD )
|
|
if( @block_size % Rex::Poly::Machine::WORD == 0 )
|
|
chunk_size = Rex::Poly::Machine::WORD
|
|
end
|
|
end
|
|
|
|
# Block 1 - Set the source variable to the address of the start block
|
|
@machine.create_block_primitive( 'block1', 'set', 'source', 'location' )
|
|
|
|
# Block 2 - Set the source variable to the address of the 1st encoded block
|
|
@machine.create_block_primitive( 'block2', 'add', 'source', 'end' )
|
|
|
|
# Block 3 - Set the destingation variable to the value of the source variable
|
|
@machine.create_block_primitive( 'block3', 'set', 'dest', 'source' )
|
|
|
|
# Block 4 - Set the destingation variable to the address of the 2nd encoded block
|
|
@machine.create_block_primitive( 'block4', 'add', 'dest', @block_size )
|
|
|
|
# Block 5 - Sets the loop counter to the number of blocks to process
|
|
@machine.create_block_primitive( 'block5', 'set', 'counter', ( ( @block_size / chunk_size ) * (@blocks_out.length - 1) ) )
|
|
|
|
# Block 6 - Set the encoded variable to the byte pointed to by the dest variable
|
|
@machine.create_block_primitive( 'block6', 'load', 'encoded', 'dest', chunk_size )
|
|
|
|
# Block 7 - Increment the destination variable by one
|
|
@machine.create_block_primitive( 'block7', 'add', 'dest', chunk_size )
|
|
|
|
# Block 8 - Set the decoded variable to the byte pointed to by the source variable
|
|
@machine.create_block_primitive( 'block8', 'load', 'decoded', 'source', chunk_size )
|
|
|
|
# Block 9 - Xor the decoded variable with the encoded variable
|
|
@machine.create_block_primitive( 'block9', 'xor', 'decoded', 'encoded' )
|
|
|
|
# Block 10 - store the newly decoded byte
|
|
@machine.create_block_primitive( 'block10', 'store', 'source', 'decoded', chunk_size )
|
|
|
|
# Block 11 - Increment the source variable by one
|
|
@machine.create_block_primitive( 'block11', 'add', 'source', chunk_size )
|
|
|
|
# Block 12 - Jump back up to the outer_loop block while the counter variable > 0
|
|
@machine.create_block_primitive( 'block12', 'loop', 'counter', 'block6' )
|
|
|
|
# Try to generate the decoder stub...
|
|
decoder = @machine.generate
|
|
|
|
if( not decoder )
|
|
raise RuntimeError, "Unable to generate decoder stub."
|
|
end
|
|
|
|
decoder
|
|
end
|
|
|
|
#
|
|
# Compute the seed block which will successfully decode all proceeding encoded
|
|
# blocks while ensuring the encoded blocks do not contain any badchars.
|
|
#
|
|
def compute_seed( blocks_in, block_size, block_padding, badchars )
|
|
seed = []
|
|
redo_bytes = []
|
|
|
|
0.upto( block_size-1 ) do | index |
|
|
|
|
seed_bytes = (0..255).sort_by do
|
|
rand()
|
|
end
|
|
|
|
seed_bytes.each do | seed_byte |
|
|
|
|
next if( badchars.include?( seed_byte ) )
|
|
|
|
success = true
|
|
|
|
previous_byte = seed_byte
|
|
|
|
if( redo_bytes.length < 256 )
|
|
redo_bytes = (0..255).sort_by do
|
|
rand()
|
|
end
|
|
end
|
|
|
|
blocks_in.each do | block |
|
|
|
|
decoded_byte = block[ index ]
|
|
|
|
encoded_byte = previous_byte ^ decoded_byte
|
|
|
|
if( badchars.include?( encoded_byte ) )
|
|
# the padding bytes we added earlier can be changed if they are causing us to fail.
|
|
if( block == blocks_in.last and index >= (block_size-block_padding) )
|
|
if( redo_bytes.empty? )
|
|
success = false
|
|
break
|
|
end
|
|
block[ index ] = redo_bytes.shift
|
|
redo
|
|
end
|
|
|
|
success = false
|
|
break
|
|
end
|
|
|
|
previous_byte = encoded_byte
|
|
end
|
|
|
|
if( success )
|
|
seed << seed_byte
|
|
break
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
if( seed.length == block_size )
|
|
return seed
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
#
|
|
# Compute the next encoded block by xoring the previous
|
|
# encoded block with the next decoded block.
|
|
#
|
|
def compute_block( encoded, decoded )
|
|
block = []
|
|
0.upto( encoded.length-1 ) do | index |
|
|
block << ( encoded[ index ] ^ decoded[ index ] )
|
|
end
|
|
return block
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|