# -*- 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
