# -*- coding: binary -*-

module Rex
module Arch

#
# everything here is mostly stole from vlad's perl x86 stuff
#

module X86

  #
  # Register number constants
  #
  EAX = AL = AX = ES = 0
  ECX = CL = CX = CS = 1
  EDX = DL = DX = SS = 2
  EBX = BL = BX = DS = 3
  ESP = AH = SP = FS = 4
  EBP = CH = BP = GS = 5
  ESI = DH = SI =      6
  EDI = BH = DI =      7

  REG_NAMES32 = [ 'eax', 'ecx', 'edx', 'ebx', 'esp', 'ebp', 'esi', 'edi' ]

  REG_NAMES16 = [ 'ax', 'cx', 'dx', 'bx', 'sp', 'bp', 'si', 'di' ]

  REG_NAMES8L = [ 'al', 'cl', 'dl', 'bl', nil, nil, nil, nil ]

  # Jump tp a specific register
  def self.jmp_reg(str)
    reg = reg_number(str)
    _check_reg(reg)
    "\xFF" + [224 + reg].pack('C')
  end

  #
  # Generate a LOOP instruction (Decrement ECX and jump short if ECX == 0)
  #
  def self.loop(offset)
    "\xE2" + pack_lsb(rel_number(offset, -2))
  end

  #
  # This method returns the opcodes that compose a jump instruction to the
  # supplied relative offset.
  def self.jmp(addr)
    "\xe9" + pack_dword(rel_number(addr))
  end

  #
  # This method adds/subs a packed long integer
  #
  def self.dword_adjust(dword, amount=0)
    pack_dword(dword.unpack('V')[0] + amount)
  end

  #
  # This method returns the opcodes that compose a tag-based search routine
  #
  def self.searcher(tag)
    "\xbe" + dword_adjust(tag,-1)+  # mov esi, Tag - 1
    "\x46" +                        # inc esi
    "\x47" +                        # inc edi (end_search:)
    "\x39\x37" +                    # cmp [edi],esi
    "\x75\xfb" +                    # jnz 0xa (end_search)
    "\x46" +                        # inc esi
    "\x4f" +                        # dec edi (start_search:)
    "\x39\x77\xfc" +                # cmp [edi-0x4],esi
    "\x75\xfa" +                    # jnz 0x10 (start_search)
    jmp_reg('edi')                  # jmp edi
  end

  #
  # Generates a buffer that will copy memory immediately following the stub
  # that is generated to be copied to the stack
  #
  def self.copy_to_stack(len)
    # four byte align
    len = (len + 3) & ~0x3

    stub =
      "\xeb\x0f"+                # jmp _end
      push_dword(len)+           # push n
      "\x59"+                    # pop ecx
      "\x5e"+                    # pop esi
      "\x29\xcc"+                # sub esp, ecx
      "\x89\xe7"+                # mov edi, esp
      "\xf3\xa4"+                # rep movsb
      "\xff\xe4"+                # jmp esp
      "\xe8\xec\xff\xff\xff"     # call _start

    stub
  end

  #
  # This method returns the opcodes that compose a short jump instruction to
  # the supplied relative offset.
  #
  def self.jmp_short(addr)
    "\xeb" + pack_lsb(rel_number(addr, -2))
  end

  #
  # This method returns the opcodes that compose a relative call instruction
  # to the address specified.
  #
  def self.call(addr)
    "\xe8" + pack_dword(rel_number(addr, -5))
  end

  #
  # This method returns a number offset to the supplied string.
  #
  def self.rel_number(num, delta = 0)
    s = num.to_s

    case s[0, 2]
      when '$+'
        num = s[2 .. -1].to_i
      when '$-'
        num = -1 * s[2 .. -1].to_i
      when '0x'
        num = s.hex
      else
        delta = 0
    end

    return num + delta
  end

  #
  # This method returns the number associated with a named register.
  #
  def self.reg_number(str)
    return self.const_get(str.upcase)
  end

  #
  # This method returns the register named associated with a given register
  # number.
  #
  def self.reg_name32(num)
    _check_reg(num)
    return REG_NAMES32[num].dup
  end

  #
  # This method generates the encoded effective value for a register.
  #
  def self.encode_effective(shift, dst)
    return (0xc0 | (shift << 3) | dst)
  end

  #
  # This method generates the mod r/m character for a source and destination
  # register.
  #
  def self.encode_modrm(dst, src)
    _check_reg(dst, src)
    return (0xc0 | src | dst << 3).chr
  end

  #
  # This method generates a push byte instruction.
  #
  def self.push_byte(byte)
    # push byte will sign extend...
    if byte < 128 && byte >= -128
      return "\x6a" + (byte & 0xff).chr
    end
    raise ::ArgumentError, "Can only take signed byte values!", caller()
  end

  #
  # This method generates a push word instruction.
  #
  def self.push_word(val)
    return "\x66\x68" + pack_word(val)
  end

  #
  # This method generates a push dword instruction.
  #
  def self.push_dword(val)
    return "\x68" + pack_dword(val)
  end

  #
  # This method generates a pop dword instruction into a register.
  #
  def self.pop_dword(dst)
    _check_reg(dst)
    return (0x58 | dst).chr
  end

  #
  # This method generates an instruction that clears the supplied register in
  # a manner that attempts to avoid bad characters, if supplied.
  #
  def self.clear(reg, badchars = '')
    _check_reg(reg)
    return set(reg, 0, badchars)
  end

  #
  # This method generates the opcodes that set the low byte of a given
  # register to the supplied value.
  #
  def self.mov_byte(reg, val)
    _check_reg(reg)
    # chr will raise RangeError if val not between 0 .. 255
    return (0xb0 | reg).chr + val.chr
  end

  #
  # This method generates the opcodes that set the low word of a given
  # register to the supplied value.
  #
  def self.mov_word(reg, val)
    _check_reg(reg)
    if val < 0 || val > 0xffff
      raise RangeError, "Can only take unsigned word values!", caller()
    end
    return "\x66" + (0xb8 | reg).chr + pack_word(val)
  end

  #
  # This method generates the opcodes that set the a register to the
  # supplied value.
  #
  def self.mov_dword(reg, val)
    _check_reg(reg)
    return (0xb8 | reg).chr + pack_dword(val)
  end

  #
  # This method is a general way of setting a register to a value.  Depending
  # on the value supplied, different sets of instructions may be used.
  #
  # TODO: Make this moderatly intelligent so it chain instructions by itself
    #   (ie. xor eax, eax + mov al, 4 + xchg ah, al)
  def self.set(dst, val, badchars = '')
    _check_reg(dst)

    # If the value is 0 try xor/sub dst, dst (2 bytes)
    if(val == 0)
      opcodes = Rex::Text.remove_badchars("\x29\x2b\x31\x33", badchars)
      if !opcodes.empty?
        return opcodes[rand(opcodes.length)].chr + encode_modrm(dst, dst)
      end
# TODO: SHL/SHR
# TODO: AND
    end

    # try push BYTE val; pop dst (3 bytes)
    begin
      return _check_badchars(push_byte(val) + pop_dword(dst), badchars)
    rescue ::ArgumentError, ::RuntimeError, ::RangeError
    end

    # try clear dst, mov BYTE dst (4 bytes)
    begin
      # break if val == 0
      return _check_badchars(clear(dst, badchars) + mov_byte(dst, val), badchars)
    rescue ::ArgumentError, ::RuntimeError, ::RangeError
    end

    # try mov DWORD dst (5 bytes)
    begin
      return _check_badchars(mov_dword(dst, val), badchars)
    rescue ::ArgumentError, ::RuntimeError, ::RangeError
    end

    # try push DWORD, pop dst (6 bytes)
    begin
      return _check_badchars(push_dword(val) + pop_dword(dst), badchars)
    rescue ::ArgumentError, ::RuntimeError, ::RangeError
    end

    # try clear dst, mov WORD dst (6 bytes)
    begin
      # break if val == 0
      return _check_badchars(clear(dst, badchars) + mov_word(dst, val), badchars)
    rescue ::ArgumentError, ::RuntimeError, ::RangeError
    end

    raise RuntimeError, "No valid set instruction could be created!", caller()
  end

  #
  # Builds a subtraction instruction using the supplied operand
  # and register.
  #
  def self.sub(val, reg, badchars = '', add = false, adjust = false, bits = 0)
    opcodes = []
    shift   = (add == true) ? 0 : 5

    if (bits <= 8 and val >= -0x7f and val <= 0x7f)
      opcodes <<
        ((adjust) ? '' : clear(reg, badchars)) +
        "\x83" +
        [ encode_effective(shift, reg) ].pack('C') +
        [ val.to_i ].pack('C')
    end

    if (bits <= 16 and val >= -0xffff and val <= 0)
      opcodes <<
        ((adjust) ? '' : clear(reg, badchars)) +
        "\x66\x81" +
        [ encode_effective(shift, reg) ].pack('C') +
        [ val.to_i ].pack('v')
    end

    opcodes <<
      ((adjust) ? '' : clear(reg, badchars)) +
      "\x81" +
      [ encode_effective(shift, reg) ].pack('C') +
      [ val.to_i ].pack('V')

    # Search for a compatible opcode
    opcodes.each { |op|
      begin
        _check_badchars(op, badchars)
      rescue
        next
      end

      return op
    }

    if opcodes.empty?
      raise RuntimeError, "Could not find a usable opcode", caller()
    end
  end

  #
  # This method generates the opcodes equivalent to subtracting with a
  # negative value from a given register.
  #
  def self.add(val, reg, badchars = '', adjust = false, bits = 0)
    sub(val, reg, badchars, true, adjust, bits)
  end

  #
  # This method wrappers packing a short integer as a little-endian buffer.
  #
  def self.pack_word(num)
    [num].pack('v')
  end

  #
  # This method wrappers packing an integer as a little-endian buffer.
  #
  def self.pack_dword(num)
    [num].pack('V')
  end

  #
  # This method returns the least significant byte of a packed dword.
  #
  def self.pack_lsb(num)
    pack_dword(num)[0,1]
  end

  #
  # This method adjusts the value of the ESP register by a given amount.
  #
  def self.adjust_reg(reg, adjustment)
    if (adjustment > 0)
      sub(adjustment, reg, '', false, false, 32)
    else
      add(adjustment, reg, '', true, 32)
    end
  end

  def self._check_reg(*regs) # :nodoc:
    regs.each { |reg|
      if reg > 7 || reg < 0
        raise ArgumentError, "Invalid register #{reg}", caller()
      end
    }
    return nil
  end

  def self._check_badchars(data, badchars) # :nodoc:
    idx = Rex::Text.badchar_index(data, badchars)
    if idx
      raise RuntimeError, "Bad character at #{idx}", caller()
    end
    return data
  end

  #
  # This method returns an array of 'safe' FPU instructions
  #
  def self.fpu_instructions
    fpus = []

    0xe8.upto(0xee) { |x| fpus << "\xd9" + x.chr }
    0xc0.upto(0xcf) { |x| fpus << "\xd9" + x.chr }
    0xc0.upto(0xdf) { |x| fpus << "\xda" + x.chr }
    0xc0.upto(0xdf) { |x| fpus << "\xdb" + x.chr }
    0xc0.upto(0xc7) { |x| fpus << "\xdd" + x.chr }

    fpus << "\xd9\xd0"
    fpus << "\xd9\xe1"
    fpus << "\xd9\xf6"
    fpus << "\xd9\xf7"
    fpus << "\xd9\xe5"

    # This FPU instruction seems to fail consistently on Linux
    #fpus << "\xdb\xe1"

    fpus
  end

  #
  # This method returns an array containing a geteip stub, a register, and an offset
  # This method will return nil if the getip generation fails
  #
  def self.geteip_fpu(badchars)

    #
    # Default badchars to an empty string
    #
    badchars ||= ''

    #
    # Bail out early if D9 is restricted
    #
    return nil if badchars.index("\xd9")

    #
    # Create a list of FPU instructions
    #
    fpus = *self.fpu_instructions
    bads = []
    badchars.each_byte  do |c|
      fpus.each do |str|
        bads << str if (str.index(c.chr))
      end
    end
    bads.each { |str| fpus.delete(str) }
    return nil if fpus.length == 0

    #
    # Create a list of registers to use for fnstenv
    #
    dsts = []
    0.upto(7) do |c|
      dsts << c if (not badchars.index( (0x70+c).chr ))
    end

    if (dsts.include?(ESP) and badchars.index("\x24"))
      dsts.delete(ESP)
    end

    return nil if dsts.length == 0

    #
    # Grab a random FPU instruction
    #
    fpu = fpus[ rand(fpus.length) ]

    #
    # Grab a random register from dst
    #
    while(dsts.length > 0)
      buf = ''
      dst = dsts[ rand(dsts.length) ]
      dsts.delete(dst)

      # If the register is not ESP, copy ESP
      if (dst != ESP)
        next if badchars.index( (0x70 + dst).chr )

        if !(badchars.index("\x89") or badchars.index( (0xE0+dst).chr ))
          buf << "\x89" + (0xE0 + dst).chr
        else
          next if badchars.index("\x54")
          next if badchars.index( (0x58+dst).chr )
          buf << "\x54" + (0x58 + dst).chr
        end
      end

      pad = 0
      while (pad < (128-12) and badchars.index( (256-12-pad).chr))
        pad += 4
      end

      # Give up on finding a value to use here
      if (pad == (128-12))
        return nil
      end

      out = buf + fpu + "\xd9" + (0x70 + dst).chr
      out << "\x24" if dst == ESP
      out << (256-12-pad).chr

      regs = [*(0..7)]
      while (regs.length > 0)
        reg = regs[ rand(regs.length) ]
        regs.delete(reg)
        next if reg == ESP
        next if badchars.index( (0x58 + reg).chr )

        # Pop the value back out
        0.upto(pad / 4) { |c| out << (0x58 + reg).chr }

        # Fix the value to point to self
        gap = out.length - buf.length

        return [out, REG_NAMES32[reg].upcase, gap]
      end
    end

    return nil
  end

end

end end

