class Rex::Poly::Machine
A machine capable of creating a small blob of code in a metamorphic kind of way. Note: this is designed to perform an exhaustive search for a solution and can be slow. If you need a speedier option, the origional Rex::Polly::Block stuff is a better choice.
Constants
- BYTE
- DWORD
- QWORD
- WORD
Public Class Methods
Create a new machine instance.
# File lib/rex/poly/machine/machine.rb, line 351 def initialize( badchars, cpu ) @badchars = badchars @cpu = cpu @reg_available = ::Array.new @reg_consumed = ::Array.new @variables = ::Hash.new @blocks = ::Hash.new @primitives = ::Hash.new @solution = Solution.new _create_primitives @blocks['begin'] = Block.new( 'begin' ) @blocks['begin'] << SymbolicPermutation.new( 'begin', self ) _create_variable( 'temp' ) end
Public Instance Methods
Use METASM to assemble a line of asm using this machines current cpu.
# File lib/rex/poly/machine/machine.rb, line 380 def assemble( asm ) return Metasm::Shellcode.assemble( @cpu, asm ).encode_string end
Does a given block exist?
# File lib/rex/poly/machine/machine.rb, line 558 def block_exist?( name ) return @blocks.include?( name ) end
Get the block next to the target block.
# File lib/rex/poly/machine/machine.rb, line 601 def block_next( target_block ) @blocks.each_key do | current_block | if( block_previous( current_block ) == target_block ) return current_block end end return nil end
Get the offset for a blocks active permutation. This is easy for backward references as they will already have been rendered and their sizes known. For forward references we can't know in advance but the correct value can be known later once the final solution is available and a final pass to generate the raw buffer is made.
# File lib/rex/poly/machine/machine.rb, line 541 def block_offset( name ) if( name == 'end' ) return @solution.offset elsif( @blocks[name] ) @blocks[name].each do | permutation | if( permutation.active ) return permutation.offset end end end # If we are forward referencing a block it will be at least the current solutions offset +1 return @solution.offset + 1 end
Get the block previous to the target block.
# File lib/rex/poly/machine/machine.rb, line 587 def block_previous( target_block ) previous_block = nil @blocks.each_key do | current_block | if( current_block == target_block ) return previous_block end previous_block = current_block end return nil end
Create a block by name and add in its list of permutations.
XXX: this doesnt support the fuzzy order of block dependencies ala the origional rex::poly
# File lib/rex/poly/machine/machine.rb, line 504 def create_block( name, *permutation_sources ) # Sanity check we aren't trying to create one of the reserved symbolic blocks. if( name == 'begin' or name == 'end' ) raise RuntimeError, "Unable to add block, '#{name}' is a reserved block name." end # If this is the first time this block is being created, create the block object to hold the permutation list if( not @blocks[name] ) @blocks[name] = Block.new( name ) end # Now create a new permutation object for every one supplied. permutation_sources.each do | source | @blocks[name] << Permutation.new( name, '', self, source ) end return name end
Create a block which is based on a primitive defined by this machine.
# File lib/rex/poly/machine/machine.rb, line 523 def create_block_primitive( block_name, primitive_name, *args ) # Santiy check this primitive is actually available and is not an internal primitive (begins with an _). if( not @primitives[primitive_name] or primitive_name[0] == "_" ) raise RuntimeError, "Unable to add block, Primitive '#{primitive_name}' is not available." end # Sanity check we aren't trying to create one of the reserved symbolic blocks. if( block_name == 'begin' or block_name == 'end' ) raise RuntimeError, "Unable to add block, '#{block_name}' is a reserved block name." end return _create_block_primitive( block_name, primitive_name, *args ) end
Create a variable by name which will be assigned a register during generation. We can optionally assign a static register value to a variable if needed.
# File lib/rex/poly/machine/machine.rb, line 426 def create_variable( name, reg=nil ) # Sanity check we aren't trying to create one of the reserved variables. if( name == 'temp' ) raise RuntimeError, "Unable to create variable, '#{name}' is a reserved variable name." end return _create_variable( name, reg ) end
Try to generate a solution.
# File lib/rex/poly/machine/machine.rb, line 613 def generate if( @blocks.has_key?( 'end' ) ) @blocks.delete( 'end' ) end @blocks['end'] = Block.new( 'end' ) @blocks['end'] << SymbolicPermutation.new( 'end', self, 1 ) # Mix up the permutation orders for each block and create the tree structure. previous = ::Array.new @blocks.each_value do | block | # Shuffle the order of the blocks permutations. block.shuffle # create the tree by adding the current blocks permutations as children of the previous block. current = ::Array.new block.each do | permutation | permutation.remove_children previous.each do | prev | prev.add_child( permutation ) end current << permutation end previous = current end # Shuffle the order of the available registers @reg_available = @reg_available.shuffle # We must try every permutation of the register orders, so if we fail to # generate a solution we rotate the available registers to try again with # a different order. This ensures we perform and exhaustive search. 0.upto( @reg_available.length - 1 ) do @solution.reset # Start from the root node in the solution space and generate a # solution by traversing the solution space's tree structure. if( @blocks['begin'].solve ) # Return the solutions buffer (perform a last pass to fixup all offsets)... return @solution.buffer end @reg_available.push( @reg_available.shift ) end # :( nil end
Check if a data blob is valid against the badchar list (or perform any other validation here)
# File lib/rex/poly/machine/machine.rb, line 387 def is_valid?( data ) if( data.nil? ) return false end return Rex::Text.badchar_index( data, @badchars ).nil? end
Generate a 8 bit number whoes bytes are valid in this machine.
# File lib/rex/poly/machine/machine.rb, line 418 def make_safe_byte( number=nil ) return _make_safe_number( BYTE, number ) & 0xFF end
Generate a 32 bit number whoes bytes are valid in this machine.
# File lib/rex/poly/machine/machine.rb, line 404 def make_safe_dword( number=nil ) return _make_safe_number( DWORD, number ) & 0xFFFFFFFF end
Generate a 64 bit number whoes bytes are valid in this machine.
# File lib/rex/poly/machine/machine.rb, line 397 def make_safe_qword( number=nil ) return _make_safe_number( QWORD, number ) & 0xFFFFFFFFFFFFFFFF end
Generate a 16 bit number whoes bytes are valid in this machine.
# File lib/rex/poly/machine/machine.rb, line 411 def make_safe_word( number=nil ) return _make_safe_number( WORD, number ) & 0xFFFF end
Overloaded by a subclass to return the maximum native general register size supported.
# File lib/rex/poly/machine/machine.rb, line 373 def native_size nil end
If the temp variable was assigned we release it.
# File lib/rex/poly/machine/machine.rb, line 437 def release_temp_variable if( @variables['temp'] ) regnum = @variables['temp'] # Sanity check the temp variable was actually assigned (it may not have been if the last permutation didnot use it) if( regnum ) # place the assigned register back in the available list for consumption later. @reg_available.push( @reg_consumed.delete( regnum ) ) # unasign the temp vars register @variables['temp'] = nil return true end end return false end
Resolve a given value into either a number literal, a block offset or a variables assigned register.
# File lib/rex/poly/machine/machine.rb, line 575 def resolve_value( value, size=nil ) if( block_exist?( value ) ) return block_offset( value ) elsif( variable_exist?( value ) ) return variable_value( value, size ) end return value.to_i end
Check this solution is still currently valid (as offsets change it may not be).
# File lib/rex/poly/machine/machine.rb, line 479 def solution_is_valid? return self.is_valid?( @solution.buffer ) end
Backtrack one step in the solution and restore the register/variable state.
# File lib/rex/poly/machine/machine.rb, line 494 def solution_pop permutation, @reg_available, @reg_consumed, @variables = @solution.pop @reg_available.push( @reg_available.shift ) end
As the solution advances we save state for each permutation step in the solution. This lets use rewind at a later stage if the solving algorithm wishes to perform some backtracking.
# File lib/rex/poly/machine/machine.rb, line 487 def solution_push( permutation ) @solution.push( permutation, @reg_available, @reg_consumed, @variables ) end
Does a given block exist?
# File lib/rex/poly/machine/machine.rb, line 565 def variable_exist?( name ) return @variables.include?( name ) end
Resolve a variable name into its currently assigned register value.
# File lib/rex/poly/machine/machine.rb, line 455 def variable_value( name, size=nil ) # Sanity check we this variable has been created if( not @variables.has_key?( name ) ) raise RuntimeError, "Unknown register '#{name}'." end # Pull out its current register value if it has been assigned one regnum = @variables[ name ] if( not regnum ) regnum = @reg_available.pop if( not regnum ) raise RuntimeError, "Unable to assign variable '#{name}' a register value, none available." end # and add it to the consumed list so we can track it later @reg_consumed << regnum # and now assign the variable the register @variables[ name ] = regnum end # resolve the register number int a string representation (e.g. 0 in x86 is EAX if size is 32) return _register_value( regnum, size ) end
Protected Instance Methods
Create a block which is based on a primitive defined by this machine.
# File lib/rex/poly/machine/machine.rb, line 742 def _create_block_primitive( block_name, primitive_name, *args ) # If this is the first time this block is being created, create the array to hold the permutation list if( not @blocks[block_name] ) @blocks[block_name] = Block.new( block_name ) end # Now create a new permutation object for every one supplied. @primitives[primitive_name].each do | source | @blocks[block_name] << Permutation.new( block_name, primitive_name, self, source, args ) end return block_name end
# File lib/rex/poly/machine/machine.rb, line 764 def _create_primitive( name, *permutations ) # If this is the first time this primitive is being created, create the array to hold the permutation list if( not @primitives[name] ) @primitives[name] = ::Array.new end # Add in the permutation object (Rex::Poly::Machine::Primitive) for every one supplied. permutations.each do | permutation | @primitives[name] << Primitive.new( permutation ) end end
Overloaded by a subclass to create any primitives available in this machine.
# File lib/rex/poly/machine/machine.rb, line 757 def _create_primitives nil end
Perform the actual variable creation.
# File lib/rex/poly/machine/machine.rb, line 708 def _create_variable( name, reg=nil ) regnum = nil # Sanity check this variable has not already been created. if( @variables[name] ) raise RuntimeError, "Variable '#{name}' is already created." end # If a fixed register is being assigned to this variable then resolve it if( reg ) # Resolve the register name into a register number @reg_available.each do | num | if( _register_value( num ) == reg.downcase ) regnum = num break end end # If an invalid register name was given or the chosen register is not available we must fail. if( not regnum ) raise RuntimeError, "Register '#{reg}' is unknown or unavailable." end # Sanity check another variable isnt assigned this register if( @variables.has_value?( regnum ) ) raise RuntimeError, "Register number '#{regnum}' is already consumed by variable '#{@variables[name]}'." end # Finally we consume the register chosen so we dont select it again later. @reg_consumed << @reg_available.delete( regnum ) end # Create the variable and assign it a register number (or nil if not yet assigned) @variables[name] = regnum return name end
Helper function to generate a number whoes byte representation is valid in this machine (does not contain any badchars for example). Optionally we can supply a number and the resulting addition/subtraction of this number against the newly generated value is also tested for validity. This helps in the assembly primitives which can use these values.
# File lib/rex/poly/machine/machine.rb, line 782 def _make_safe_number( bytes, number=nil ) format = '' if( bytes == BYTE ) format = 'C' elsif( bytes == WORD ) format = 'v' elsif( bytes == DWORD ) format = 'V' elsif( bytes == QWORD ) format = 'Q' else raise RuntimeError, "Invalid size '#{bytes}' used in _make_safe_number." end goodchars = (0..255).to_a @badchars.unpack( 'C*' ).each do | b | goodchars.delete( b.chr ) end while( true ) do value = 0 0.upto( bytes-1 ) do | i | value |= ( (goodchars[ rand(goodchars.length) ] << i*8) & (0xFF << i*8) ) end if( not is_valid?( [ value ].pack(format) ) or not is_valid?( [ ~value ].pack(format) ) ) redo end if( not number.nil? ) if( not is_valid?( [ value + number ].pack(format) ) or not is_valid?( [ value - number ].pack(format) ) ) redo end end break end return value end
Overloaded by a subclass to resolve a register number into a suitable register name for the target architecture. E.g on x64 the register number 0 with size 64 would resolve to RCX. Size is nil by default to indicate we want the default machine size, e.g. 32bit DWORD
on x86 or 64bit QWORD
on x64.
# File lib/rex/poly/machine/machine.rb, line 701 def _register_value( regnum, size=nil ) nil end