From 419e65324f6a38c1313f549f57601a622eadee45 Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Fri, 11 Dec 2015 09:51:34 +0100 Subject: [PATCH] Add 32bits register support in simple_x64 --- native_exec/simple_x64.py | 130 +++++++++++++++++++++++++++------ native_exec/test_simple_x64.py | 30 +++++++- 2 files changed, 137 insertions(+), 23 deletions(-) diff --git a/native_exec/simple_x64.py b/native_exec/simple_x64.py index d87085d..7562999 100644 --- a/native_exec/simple_x64.py +++ b/native_exec/simple_x64.py @@ -109,6 +109,11 @@ reg_order = ['RAX', 'RCX', 'RDX', 'RBX', 'RSP', 'RBP', 'RSI', 'RDI'] new_reg_order = ['R8', 'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15'] x64_regs = reg_order + new_reg_order +registers_32_bits = {'R15D': 'R15', 'R14D': 'R14', 'ESP': 'RSP', 'R9D': 'R9', +'EDI': 'RDI', 'R11D': 'R11', 'R8D': 'R8', 'R10D': 'R10', 'EAX': 'RAX', +'R13D': 'R13', 'EBP': 'RBP', 'R12D': 'R12', 'EDX': 'RDX', 'EBX': 'RBX', +'ESI': 'RSI', 'ECX': 'RCX'} + x64_segment_selectors = {'CS': CSPrefix, 'DS': DSPrefix, 'ES': ESPrefix, 'SS': SSPrefix, 'FS': FSPrefix, 'GS': GSPrefix} @@ -117,7 +122,7 @@ class X64(object): @staticmethod def is_reg(name): try: - return (name.upper() in reg_order) or X64.is_new_reg(name) + return (name.upper() in reg_order) or X64.is_new_reg(name) or X64.is_32b_reg(name) except AttributeError: # Not a string return False @@ -128,6 +133,13 @@ class X64(object): except AttributeError: # Not a string return False + @staticmethod + def is_32b_reg(name): + try: + return name.upper() in registers_32_bits + except AttributeError: # Not a string + return False + @staticmethod def is_mem_acces(data): return isinstance(data, mem_access) @@ -236,11 +248,11 @@ class X64RegisterSelector(object): def accept_arg(self, args, instr_state): x = args[0] try: - return (1, self.reg_opcode[x.upper()], None) + return (1, self.reg_opcode[x.upper()], BitArray(8, [0, 1, 0, 0 ,1 , 0, 0, 0])) except (KeyError, AttributeError): pass try: - return (1, self.new_reg_opcode[x.upper()], BitArray.from_int(8, 0x41)) + return (1, self.new_reg_opcode[x.upper()], BitArray(8, [0, 1, 0, 0 ,1 , 0, 0, 1])) except (KeyError, AttributeError): return (None, None, None) @@ -253,16 +265,20 @@ class X64RegisterSelector(object): class FixedRegister(object): - def __init__(self, register): + def __init__(self, register, is_64_bit_register=True): self.reg = register.upper() + self.is_64_bit_register = is_64_bit_register def accept_arg(self, args, instr_state): x = args[0] + rex = None if isinstance(x, str) and x.upper() == self.reg: - return 1, BitArray(0, []), None + if self.is_64_bit_register: + rex = BitArray.from_int(8, 0x48) + return 1, BitArray(0, []), rex return None, None, None -RegisterRax = lambda: FixedRegister('RAX') +RegisterRax = lambda: FixedRegister('RAX', is_64_bit_register=True) class RawBits(BitArray): @@ -422,42 +438,109 @@ class SubModRM(object): self.rex = BitArray(8, "01000000") self.is_rex_needed = False self.direction = 0 + # 32/64 bits data operation + self.is_32bits_operation = None + # 32/64 bits addressing operation + self.is_32bits_addressing = None + + def setup_as_32bit_operation(self): + if self.is_32bits_operation == False: + raise ValueError("Size mismatch") + self.is_32bits_operation = True + + def setup_as_64bit_operation(self): + if self.is_32bits_operation == True: + raise ValueError("Size mismatch") + self.is_32bits_operation = False + + def setup_as_32bits_addressing(self): + if self.is_32bits_addressing == False: + raise ValueError("Addressing size mismatch") + self.is_32bits_addressing = True + + def setup_as_64bits_addressing(self): + if self.is_32bits_addressing == True: + raise ValueError("Addressing size mismatch") + self.is_32bits_addressing = False def setup_reg_as_register(self, name): + if name in registers_32_bits: + name = registers_32_bits[name] + self.setup_as_32bit_operation() + else: + self.is_rex_needed = True + self.rex[4] = 1 + self.setup_as_64bit_operation() + self.reg = X64RegisterSelector.get_reg_bits(name) if X64.is_new_reg(name): self.is_rex_needed = True self.rex[5] = 1 def setup_rm_as_register(self, name): + if name in registers_32_bits: + name = registers_32_bits[name] + self.setup_as_32bit_operation() + else: + self.rex[4] = 1 + self.is_rex_needed = True + self.setup_as_64bit_operation() + self.rm = X64RegisterSelector.get_reg_bits(name) if X64.is_new_reg(name): self.is_rex_needed = True self.rex[7] = 1 + def setup_rm_as_mem_base(self, name): + if name in registers_32_bits: + name = registers_32_bits[name] + self.setup_as_32bits_addressing() + else: + self.setup_as_64bits_addressing() + + self.rm = X64RegisterSelector.get_reg_bits(name) + if X64.is_new_reg(name): + self.is_rex_needed = True + self.rex[7] = 1 + + def setup_sib_base_rex(self, baseregister): + if baseregister in registers_32_bits: + baseregister = registers_32_bits[baseregister] + self.setup_as_32bits_addressing() + else: + self.setup_as_64bits_addressing() + if X64.is_new_reg(baseregister): self.is_rex_needed = True self.rex[7] = 1 return X64RegisterSelector.get_reg_bits(baseregister) def setup_sib_index_rex(self, indexregister): + if indexregister in registers_32_bits: + indexregister = registers_32_bits[indexregister] + self.setup_as_32bits_addressing() + else: + self.setup_as_64bits_addressing() + if X64.is_new_reg(indexregister): self.is_rex_needed = True self.rex[6] = 1 return X64RegisterSelector.get_reg_bits(indexregister) -class ModRM_REG64__REG64(SubModRM): +class ModRM_REG__REG(SubModRM): + """handle Reg32 and Reg64""" @classmethod def match(cls, arg1, arg2): - return (X64.is_reg(arg1) or X64.is_new_reg(arg1)) and (X64.is_reg(arg2) or X64.is_new_reg(arg2)) + return X64.is_reg(arg1) and X64.is_reg(arg2) def __init__(self, arg1, arg2, reversed, instr_state): - super(ModRM_REG64__REG64, self).__init__() + super(ModRM_REG__REG, self).__init__() + self.mod = BitArray(2, "11") self.is_rex_needed = True - self.rex[4] = 1 + #self.rex[4] = 1 # Setup by setup_reg_as_register or setup_rm_as_register self.setup_reg_as_register(arg2) self.setup_rm_as_register(arg1) self.direction = 0 @@ -466,7 +549,7 @@ class ModRM_REG64__REG64(SubModRM): class ModRM_REG64__MEM(SubModRM): @classmethod def match(cls, arg1, arg2): - return (X64.is_reg(arg1) or X64.is_new_reg(arg1)) and X64.is_mem_acces(arg2) + return X64.is_reg(arg1) and X64.is_mem_acces(arg2) def __init__(self, arg1, arg2, reversed, instr_state): super(ModRM_REG64__MEM, self).__init__() @@ -494,9 +577,12 @@ class ModRM_REG64__MEM(SubModRM): self.is_rex_needed = True self.rex[4] = 1 self.setup_reg_as_register(arg1) - self.setup_rm_as_register(arg2.base) + self.setup_rm_as_mem_base(arg2.base) + #self.setup_rm_as_register(arg2.base) self.compute_displacement(arg2.disp) self.direction = not reversed + if self.is_32bits_addressing == True: + instr_state.prefixes.append(AddressSizeOverride) return # FIRE UP THE SIB # Handle no base and base == EBP special case @@ -514,6 +600,8 @@ class ModRM_REG64__MEM(SubModRM): if not arg2.base: self.mod = BitArray(2, "00") self.direction = not reversed + if self.is_32bits_addressing == True: + instr_state.prefixes.append(AddressSizeOverride) def compute_displacement(self, displacement, force_displacement=0): if not displacement and not force_displacement: @@ -565,7 +653,7 @@ class Slash(object): raise ValueError("Missing arg for Slash") # Reuse all the MODRm logique with the reg as our self.reg # The sens of param is strange I need to fix the `reversed` logique - arg_consum, value, rex = ModRM([ModRM_REG64__REG64, ModRM_REG64__MEM], has_direction_bit=False).accept_arg(args[:1] + [self.reg] + args[1:], instr_state) + arg_consum, value, rex = ModRM([ModRM_REG__REG, ModRM_REG64__MEM], has_direction_bit=False).accept_arg(args[:1] + [self.reg] + args[1:], instr_state) if value is None: return arg_consum, value, rex return arg_consum - 1, value, rex @@ -575,7 +663,7 @@ instr_state = collections.namedtuple('instr_state', ['previous', 'prefixes']) class Instruction(object): encoding = [] - default_rex = BitArray(8, "") + default_rex = BitArray.from_int(8, 0x40) def __init__(self, *initial_args): for type_encoding in self.encoding: @@ -583,8 +671,8 @@ class Instruction(object): res = [] prefix = [] full_rex = self.default_rex - if hasattr(self, "default_32_bits") and self.default_32_bits: - full_rex = BitArray.from_int(8, 0x48) + #if hasattr(self, "default_32_bits") and self.default_32_bits: + # full_rex = BitArray.from_int(8, 0x48) for element in type_encoding: arg_consum, value, rex = element.accept_arg(args, instr_state(res, prefix)) if arg_consum is None: @@ -598,7 +686,7 @@ class Instruction(object): continue self.prefix = prefix self.value = sum(res, BitArray(0, "")) - if any(full_rex.array): + if str(full_rex.dump()) != "\x40": self.value = full_rex + self.value return raise ValueError("Cannot encode <{0} {1}>:(".format(type(self).__name__, initial_args)) @@ -664,7 +752,7 @@ class Add(Instruction): default_32_bits = True encoding = [(RawBits.from_int(8, 0x05), RegisterRax(), Imm32()), (RawBits.from_int(8, 0x81), Slash(0), Imm32()), - (RawBits.from_int(8, 0x01), ModRM([ModRM_REG64__REG64, ModRM_REG64__MEM]))] + (RawBits.from_int(8, 0x01), ModRM([ModRM_REG__REG, ModRM_REG64__MEM]))] class Sub(Instruction): @@ -754,7 +842,7 @@ class Lea(Instruction): class Mov(Instruction): default_32_bits = True - encoding = [(Mov_RAX_OFF64(),), (Mov_OFF64_RAX(),), (RawBits.from_int(8, 0x89), ModRM([ModRM_REG64__REG64, ModRM_REG64__MEM])), + encoding = [(Mov_RAX_OFF64(),), (Mov_OFF64_RAX(),), (RawBits.from_int(8, 0x89), ModRM([ModRM_REG__REG, ModRM_REG64__MEM])), (RawBits.from_int(5, 0xb8 >> 3), X64RegisterSelector(), Imm64())] @@ -762,12 +850,12 @@ class Cmp(Instruction): default_32_bits = True encoding = [(RawBits.from_int(8, 0x3d), RegisterRax(), Imm32()), (RawBits.from_int(8, 0x81), Slash(7), Imm32()), - (RawBits.from_int(8, 0x3b), ModRM([ModRM_REG64__REG64, ModRM_REG64__MEM]))] + (RawBits.from_int(8, 0x3b), ModRM([ModRM_REG__REG, ModRM_REG64__MEM]))] class Xor(Instruction): default_32_bits = True - encoding = [(RawBits.from_int(8, 0x31), ModRM([ModRM_REG64__REG64, ModRM_REG64__MEM]))] + encoding = [(RawBits.from_int(8, 0x31), ModRM([ModRM_REG__REG, ModRM_REG64__MEM]))] class Nop(Instruction): diff --git a/native_exec/test_simple_x64.py b/native_exec/test_simple_x64.py index 00df47c..6de9fb9 100644 --- a/native_exec/test_simple_x64.py +++ b/native_exec/test_simple_x64.py @@ -1,4 +1,5 @@ import capstone +import simple_x64 as x64 from simple_x64 import * disassembleur = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64) @@ -12,12 +13,22 @@ mnemonic_name_exception = {'movabs': 'mov'} class TestInstr(object): - def __init__(self, instr_to_test, immediat_accepted=None): + def __init__(self, instr_to_test, immediat_accepted=None, must_fail=None): self.instr_to_test = instr_to_test self.immediat_accepted = immediat_accepted + self.must_fail = must_fail def __call__(self, *args): - res = bytes(self.instr_to_test(*args).get_code()) + try: + res = bytes(self.instr_to_test(*args).get_code()) + except ValueError as e: + if self.must_fail == True: + return True + else: + raise + else: + if self.must_fail: + raise ValueError("Instruction did not failed as expected") capres_list = disas(res) if len(capres_list) != 1: raise AssertionError("Trying to disas an instruction resulted in multiple disassembled instrs") @@ -101,6 +112,7 @@ TestInstr(Mov)('RAX', mem('[0x1122334455667788]')) TestInstr(Mov)('RAX', mem('gs:[0x1122334455667788]')) TestInstr(Mov)('RAX', mem('gs:[0x60]')) TestInstr(Mov)('RCX', 0x1122334455667788) +TestInstr(Mov)('R8', 0x1122334455667788) TestInstr(Mov)('RCX', -1) TestInstr(Mov, immediat_accepted=-1)('RCX', 0xffffffffffffffff) TestInstr(Mov)(mem('gs:[0x1122334455667788]'), 'RAX') @@ -113,6 +125,20 @@ TestInstr(Cpuid)() TestInstr(Xchg)('RAX', 'RSP') assert Xchg('RAX', 'RCX').get_code() == Xchg('RCX', 'RAX').get_code() +# 32 / 64 bits register mixing +TestInstr(Mov)('ECX', 'EBX') +TestInstr(Mov)('RCX', mem('[EBX]')) +TestInstr(Mov)('RCX', mem('[EBX + EBX]')) +TestInstr(Mov)('RCX', mem('[ESP + EBX + 0x10]')) +TestInstr(Mov)('ECX', mem('[ESP + EBX + 0x10]')) +TestInstr(Mov)('ECX', mem('[RBX + RCX + 0x10]')) + +TestInstr(Mov, must_fail=True)('RCX', 'ECX') +TestInstr(Mov, must_fail=True)('RCX', mem('[ECX + RCX]')) +TestInstr(Mov, must_fail=True)('RCX', mem('[RBX + ECX]')) +TestInstr(Mov, must_fail=True)('ECX', mem('[ECX + RCX]')) +TestInstr(Mov, must_fail=True)('ECX', mem('[RBX + ECX]')) + code = MultipleInstr() code += Nop() code += Rep + Nop()