metasploit-framework/lib/rex/poly/machine/x86.rb

510 lines
21 KiB
Ruby

# -*- coding: binary -*-
module Rex
module Poly
#
# A subclass to represent a Rex poly machine on the x86 architecture.
#
class MachineX86 < Rex::Poly::Machine
def initialize( badchars='', consume_base_pointer=nil, consume_stack_pointer=true )
super( badchars, Metasm::Ia32.new )
@reg_available << Rex::Arch::X86::EAX
@reg_available << Rex::Arch::X86::EBX
@reg_available << Rex::Arch::X86::ECX
@reg_available << Rex::Arch::X86::EDX
@reg_available << Rex::Arch::X86::ESI
@reg_available << Rex::Arch::X86::EDI
@reg_available << Rex::Arch::X86::EBP
@reg_available << Rex::Arch::X86::ESP
# By default we consume the EBP register if badchars contains \x00. This helps speed
# things up greatly as many instructions opperating on EBP introduce a NULL byte. For
# example, a MOV instruction with EAX as the source operand is as follows:
# 8B08 mov ecx, [eax]
# but the same instruction with EBP as the source operand is as follows:
# 8B4D00 mov ecx, [ebp] ; This is assembled as 'mov ecx, [ebp+0]'
# we can see that EBP is encoded differently with an offset included. We can still
# try to generate a solution with EBP included and \x00 in the badchars list but
# it can take considerably longer.
if( ( consume_base_pointer.nil? and not Rex::Text.badchar_index( "\x00", @badchars ).nil? ) or consume_base_pointer == true )
create_variable( 'base_pointer', 'ebp' )
end
# By default we consume the ESP register to avoid munging the stack.
if( consume_stack_pointer )
create_variable( 'stack_pointer', 'esp' )
end
# discover all the safe FPU instruction we can use.
@safe_fpu_instructions = ::Array.new
Rex::Arch::X86.fpu_instructions.each do | fpu |
if( is_valid?( fpu ) )
@safe_fpu_instructions << fpu
end
end
end
#
# The general purpose registers are 32bit
#
def native_size
Rex::Poly::Machine::DWORD
end
#
# Overload this method to intercept the 'set' primitive with the 'location' keyword
# and create the block with the '_set_variable_location'. We do this to keep a
# consistent style.
#
def create_block_primitive( block_name, primitive_name, *args )
if( primitive_name == 'set' and args.length == 2 and args[1] == 'location' )
_create_block_primitive( block_name, '_set_variable_location', args[0] )
else
super
end
end
#
# XXX: If we have a loop primitive, it is a decent speed bump to force the associated variable
# of the first loop primitive to be assigned as ECX (for the x86 LOOP instruction), this is not
# neccasary but can speed generation up significantly.
#
#def generate
# @blocks.each_value do | block |
# if( block.first.primitive == 'loop' )
# @variables.delete( block.first.args.first )
# create_variable( block.first.args.first, 'ecx' )
# break
# end
# end
# # ...go go go
# super
#end
protected
#
# Resolve a register number into a suitable register name.
#
def _register_value( regnum, size=nil )
value = nil
# we default to a native 32 bits if no size is specified.
if( size.nil? )
size = native_size()
end
if( size == Rex::Poly::Machine::DWORD )
value = Rex::Arch::X86::REG_NAMES32[ regnum ]
elsif( size == Rex::Poly::Machine::WORD )
value = Rex::Arch::X86::REG_NAMES16[ regnum ]
elsif( size == Rex::Poly::Machine::BYTE )
# (will return nil for ESI,EDI,EBP,ESP)
value = Rex::Arch::X86::REG_NAMES8L[ regnum ]
else
raise RuntimeError, "Register number '#{regnum}' (size #{size.to_i}) is unavailable."
end
return value
end
#
# Create the x86 primitives.
#
def _create_primitives
#
# Create the '_set_variable_location' primitive. The first param it the variable to place the current
# blocks location value in.
#
_create_primitive( '_set_variable_location',
::Proc.new do | block, machine, variable |
if( @safe_fpu_instructions.empty? )
raise UnallowedPermutation
end
[
"dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }",
"mov #{machine.variable_value( 'temp' )}, esp",
"fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]",
"pop #{machine.variable_value( variable )}"
]
end,
::Proc.new do | block, machine, variable |
if( @safe_fpu_instructions.empty? )
raise UnallowedPermutation
end
[
"dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }",
"mov #{machine.variable_value( 'temp' )}, esp",
"fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]",
"pop #{machine.variable_value( variable )}"
]
end,
::Proc.new do | block, machine, variable |
if( @safe_fpu_instructions.empty? )
raise UnallowedPermutation
end
[
"dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }",
"push esp",
"pop #{machine.variable_value( 'temp' )}",
"fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]",
"pop #{machine.variable_value( variable )}"
]
end,
::Proc.new do | block, machine, variable |
if( @safe_fpu_instructions.empty? )
raise UnallowedPermutation
end
[
"dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }",
"fnstenv [ esp - 12 ]",
"pop #{machine.variable_value( variable )}"
]
end,
::Proc.new do | block, machine, variable |
[
"call $+5",
"pop #{machine.variable_value( variable )}",
"push #{machine.block_offset( block ) + 5}",
"pop #{machine.variable_value( 'temp' )}",
"sub #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
]
end,
::Proc.new do | block, machine, variable |
[
"db 0xE8, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0",
"pop #{machine.variable_value( variable )}",
"push #{machine.block_offset( block ) + 5}",
"pop #{machine.variable_value( 'temp' )}",
"sub #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
]
end
)
#
# Create the 'loop' primitive. The first param it the counter variable which holds the number of
# times to perform the loop. The second param it the destination block to loop to.
#
_create_primitive( 'loop',
::Proc.new do | block, machine, counter, destination |
if( machine.variable_value( counter ) != Rex::Arch::X86::REG_NAMES32[ Rex::Arch::X86::ECX ] )
# we raise and UndefinedPermutation exception to indicate that untill a valid register (ECX) is
# chosen we simply can't render this. This lets the machine know we can still try to use this
# permutation and at a later stage the requirements (counter==ecx) may be satisfied.
raise UndefinedPermutation
end
offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) )
Rex::Arch::X86.loop( offset )
end,
::Proc.new do | block, machine, counter, destination |
offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) )
[
"dec #{machine.variable_value( counter )}",
"test #{machine.variable_value( counter )}, #{machine.variable_value( counter )}",
# JNZ destination
"db 0x0F, 0x85 dd #{ "0x%08X" % [ offset & 0xFFFFFFFF ] }"
]
end
)
#
# Create the 'xor' primitive. The first param it the variable to xor with the second param value which
# can be either a variable, literal or block offset.
#
_create_primitive( 'xor',
::Proc.new do | block, machine, variable, value |
[
"xor #{machine.variable_value( variable )}, #{machine.resolve_value( value )}"
]
end,
::Proc.new do | block, machine, variable, value |
# a ^ b == (a | b) & ~(a & b)
[
"mov #{machine.variable_value( 'temp' )}, #{machine.variable_value( variable )}",
"or #{machine.variable_value( 'temp' )}, #{machine.resolve_value( value )}",
"and #{machine.variable_value( variable )}, #{machine.resolve_value( value )}",
"not #{machine.variable_value( variable )}",
"and #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
]
end
)
#
# Create the 'goto' primitive. The first param is a destination block to jump to.
#
_create_primitive( 'goto',
::Proc.new do | block, machine, destination |
offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) )
if( ( offset > 0 and offset > 127 ) or ( offset < 0 and offset < -127 ) )
raise UnallowedPermutation
end
[
# short relative jump
"db 0xEB db #{ "0x%02X" % [ offset & 0xFF ] }"
]
end,
::Proc.new do | block, machine, destination |
offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) )
[
# near relative jump
"db 0xE9 dd #{ "0x%08X" % [ offset & 0xFFFFFFFF ] }"
]
end
)
#
# Create the 'add' primitive. The first param it the variable which will be added to the second
# param, which may either be a literal number value, a variables assigned register or a block
# name, in which case the block offset will be used.
#
_create_primitive( 'add',
::Proc.new do | block, machine, variable, value |
if( machine.variable_exist?( value ) )
raise UnallowedPermutation
end
[
"lea #{machine.variable_value( variable )}, [ #{machine.variable_value( variable )} + #{machine.resolve_value( value )} ]"
]
end,
::Proc.new do | block, machine, variable, value |
[
"push #{machine.resolve_value( value )}",
"add #{machine.variable_value( variable )}, [esp]",
"pop #{machine.variable_value( 'temp' )}"
]
end,
::Proc.new do | block, machine, variable, value |
[
"add #{machine.variable_value( variable )}, #{machine.resolve_value( value )}"
]
end,
::Proc.new do | block, machine, variable, value |
if( machine.variable_exist?( value ) )
raise UnallowedPermutation
end
[
"sub #{machine.variable_value( variable )}, #{ "0x%08X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFFFFFF ] }"
]
end
# ::Proc.new do | block, machine, variable, value |
# if( machine.variable_exist?( value ) )
# raise UnallowedPermutation
# end
# [
# "push #{ "0x%08X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFFFFFF ] }",
# "pop #{machine.variable_value( 'temp' )}",
# "not #{machine.variable_value( 'temp' )}",
# "add #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
# ]
# end,
# ::Proc.new do | block, machine, variable, value |
# if( machine.variable_exist?( value ) )
# raise UnallowedPermutation
# end
# [
# "xor #{machine.variable_value( 'temp' )}, #{machine.variable_value( 'temp' )}",
# "mov #{machine.variable_value( 'temp', 16 )}, #{ "0x%04X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFF ] }",
# "not #{machine.variable_value( 'temp', 16 )}",
# "add #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
# ]
# end,
)
#
# Create the 'set' primitive. The first param it the variable which will be set. the second
# param is the value to set the variable to (a variable, block or literal).
#
_create_primitive( 'set',
::Proc.new do | block, machine, variable, value |
if( machine.variable_exist?( value ) )
raise UnallowedPermutation
end
[
"push #{ "0x%08X" % [ ~machine.resolve_value( value ) & 0xFFFFFFFF ] }",
"pop #{machine.variable_value( variable )}",
"not #{machine.variable_value( variable )}"
]
end,
::Proc.new do | block, machine, variable, value |
if( machine.variable_exist?( value ) )
raise UnallowedPermutation
end
if( machine.resolve_value( value, WORD ) > 0xFFFF )
raise UndefinedPermutation
end
[
"xor #{machine.variable_value( variable )}, #{machine.variable_value( variable )}",
"mov #{machine.variable_value( variable, WORD )}, #{ "0x%04X" % [ ~machine.resolve_value( value, WORD ) & 0xFFFF ] }",
"not #{machine.variable_value( variable, WORD )}"
]
end,
::Proc.new do | block, machine, variable, value |
[
"push #{machine.resolve_value( value )}",
"pop #{machine.variable_value( variable )}"
]
end,
::Proc.new do | block, machine, variable, value |
[
"mov #{machine.variable_value( variable )}, #{machine.resolve_value( value )}"
]
end,
::Proc.new do | block, machine, variable, value |
if( machine.variable_exist?( value ) )
raise UnallowedPermutation
end
if( machine.resolve_value( value, WORD ) > 0xFFFF )
raise UndefinedPermutation
end
[
"xor #{machine.variable_value( variable )}, #{machine.variable_value( variable )}",
"mov #{machine.variable_value( variable, WORD )}, #{ "0x%04X" % [ machine.resolve_value( value, WORD ) & 0xFFFF ] }"
]
end,
::Proc.new do | block, machine, variable, value |
if( machine.variable_exist?( value ) )
raise UnallowedPermutation
end
dword = machine.make_safe_dword( machine.resolve_value( value ) )
[
"mov #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword ] }",
"sub #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword - machine.resolve_value( value ) ] }"
]
end,
::Proc.new do | block, machine, variable, value |
if( machine.variable_exist?( value ) )
raise UnallowedPermutation
end
dword = machine.make_safe_dword( machine.resolve_value( value ) )
[
"mov #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword - machine.resolve_value( value ) ] }",
"add #{machine.variable_value( variable )}, #{ "0x%08X" % [ ~dword & 0xFFFFFFFF ] }",
"not #{machine.variable_value( variable )}"
]
end
)
#
# Create the 'load' primitive. The first param it the variable which will be set. The second
# param is the value (either a variable or literal) to load from. the third param is the size
# of the load operation, either DWORD, WORD or BYTE.
#
_create_primitive( 'load',
::Proc.new do | block, machine, variable, value, size |
result = nil
if( size == Rex::Poly::Machine::DWORD )
result = [ "mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]" ]
elsif( size == Rex::Poly::Machine::WORD )
result = [ "movzx #{machine.variable_value( variable )}, word [#{machine.resolve_value( value )}]" ]
elsif( size == Rex::Poly::Machine::BYTE )
result = [ "movzx #{machine.variable_value( variable )}, byte [#{machine.resolve_value( value )}]" ]
else
raise InvalidPermutation
end
result
end,
::Proc.new do | block, machine, variable, value, size |
result = nil
if( size == Rex::Poly::Machine::DWORD )
# we raise and UnallowedPermutation here as this permutation should only satisfy requests for
# sizes of WORD or BYTE, any DWORD requests will be satisfied by the above permutation (otherwise
# we would just be duplicating a 'mov dest, [src]' sequence which is the same as above.
raise UnallowedPermutation
elsif( size == Rex::Poly::Machine::WORD )
result = [
"mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]",
"shl #{machine.variable_value( variable )}, 16",
"shr #{machine.variable_value( variable )}, 16"
]
elsif( size == Rex::Poly::Machine::BYTE )
result = [
"mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]",
"shl #{machine.variable_value( variable )}, 24",
"shr #{machine.variable_value( variable )}, 24"
]
else
raise InvalidPermutation
end
result
end,
::Proc.new do | block, machine, variable, value, size |
result = nil
if( size == Rex::Poly::Machine::DWORD )
result = [
"push [#{machine.resolve_value( value )}]",
"pop #{machine.variable_value( variable )}"
]
elsif( size == Rex::Poly::Machine::WORD )
result = [
"push [#{machine.resolve_value( value )}]",
"pop #{machine.variable_value( variable )}",
"shl #{machine.variable_value( variable )}, 16",
"shr #{machine.variable_value( variable )}, 16"
]
elsif( size == Rex::Poly::Machine::BYTE )
result = [
"push [#{machine.resolve_value( value )}]",
"pop #{machine.variable_value( variable )}",
"shl #{machine.variable_value( variable )}, 24",
"shr #{machine.variable_value( variable )}, 24"
]
else
raise InvalidPermutation
end
result
end
)
#
# Create the 'store' primitive.
#
_create_primitive( 'store',
::Proc.new do | block, machine, variable, value, size |
result = nil
if( size == Rex::Poly::Machine::DWORD )
result = [ "mov [#{machine.variable_value( variable )}], #{machine.resolve_value( value )}" ]
elsif( size == Rex::Poly::Machine::WORD )
result = [ "mov word [#{machine.variable_value( variable )}], #{machine.resolve_value( value, WORD )}" ]
elsif( size == Rex::Poly::Machine::BYTE )
if( machine.resolve_value( value, BYTE ).nil? )
# so long as we cant resolve the variable to an 8bit register value (AL,BL,CL,DL) we must raise
# an UndefinedPermutation exception (this will happen when the variable has been assigned to ESI,
# EDI, EBP or ESP which dont have a low byte representation)
raise UndefinedPermutation
end
result = [ "mov byte [#{machine.variable_value( variable )}], #{machine.resolve_value( value, BYTE )}" ]
else
raise InvalidPermutation
end
result
end,
::Proc.new do | block, machine, variable, value, size |
result = nil
if( size == Rex::Poly::Machine::DWORD )
result = [
"push #{machine.resolve_value( value )}",
"pop [#{machine.variable_value( variable )}]"
]
elsif( size == Rex::Poly::Machine::WORD )
result = [
"push #{machine.resolve_value( value, WORD )}",
"pop word [#{machine.variable_value( variable )}]"
]
else
# we can never do this permutation for BYTE size (or any other size)
raise UnallowedPermutation
end
result
end
)
end
end
end
end