diff --git a/native_exec/simple_x64.py b/native_exec/simple_x64.py index 305eca7..7af4549 100644 --- a/native_exec/simple_x64.py +++ b/native_exec/simple_x64.py @@ -74,36 +74,113 @@ class BitArray(object): def copy(self): return type(self)(self.size, self.array) -# Rules: bytes only !!!! +# Prefix +class Prefix(object): + PREFIX_VALUE = None + def __init__(self, next=None): + self.next = next -mem_access = collections.namedtuple('mem_access', ['base', 'index', 'scale', 'disp']) + def __add__(self, other): + return type(self)(other) + + def get_code(self): + return chr(self.PREFIX_VALUE) + self.next.get_code() + +def create_prefix(name, value): + prefix_type = type(name + "Type", (Prefix,), {'PREFIX_VALUE' : value}) + setattr(sys.modules[__name__], name, prefix_type()) + +create_prefix('LockPrefix', 0xf0) +create_prefix('Repne', 0xf2) +create_prefix('Rep', 0xf3) +create_prefix('SSPrefix', 0x36) +create_prefix('CSPrefix', 0x2e) +create_prefix('DSPrefix', 0x3e) +create_prefix('ESPrefix', 0x26) +create_prefix('FSPrefix', 0x64) +create_prefix('GSPrefix', 0x65) +create_prefix('OperandSizeOverride', 0x66) +create_prefix('AddressSizeOverride', 0x67) + +mem_access = collections.namedtuple('mem_access', ['base', 'index', 'scale', 'disp', 'prefix']) 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 +x64_segment_selectors = {'CS' : CSPrefix, 'DS' : DSPrefix, 'ES' : ESPrefix, 'SS' : SSPrefix, + 'FS': FSPrefix, 'GS' : GSPrefix} + +class X64(object): + @staticmethod + def is_reg(name): + try: + return (name.upper() in reg_order) or X64.is_new_reg(name) + except AttributeError: # Not a string + return False + + @staticmethod + def is_new_reg(name): + try: + return name.upper() in new_reg_order + except AttributeError: # Not a string + return False + + @staticmethod + def is_mem_acces(data): + return isinstance(data, mem_access) + + @staticmethod + def mem_access_has_only(mem_access, names): + if not X64.is_mem_acces(mem_access): + raise ValueError("mem_access_has_only") + for f in mem_access._fields: + if f != "prefix" and getattr(mem_access, f) and f not in names: + return False + if "base" in names and mem_access.base is None: + return False + return True + + @staticmethod + def to_little_endian(i, size=64): + pack = {8: 'B', 16 : 'H', 32 : 'I', 64 : 'Q'} + s = pack[size] + mask = (1 << size) - 1 + i = i & mask + return struct.unpack("<" + s, struct.pack(">" + s, i))[0] -def create_displacement(base=None, index=None, scale=None, disp=0): +def create_displacement(base=None, index=None, scale=None, disp=0, prefix=None): if index is not None and scale is None: scale = 1 if scale and index is None: raise ValueError("Cannot create displacement with scale and no index") if scale and index.upper() == "RSP": raise ValueError("Cannot create displacement with index == RSP") - return mem_access(base, index, scale, disp) + return mem_access(base, index, scale, disp, prefix) def mem(data): - """Parse a memory access string""" + """Parse a memory access string of format [EXPR] or seg:[EXPR] + EXPR may describe: BASE | INDEX * SCALE | DISPLACEMENT or any combinaison (in this order) + """ if not isinstance(data, str): raise TypeError("mem need a string to parse") data = data.strip() + prefix = None if not (data.startswith("[") and data.endswith("]")): - raise ValueError("mem acces expect <[EXPR]>") + if data[2] != ":": + raise ValueError("mem acces expect <[EXPR]> or or " + s, i))[0] - # Sub ModRM encoding -#class RexByte(object): -# def __init__(self): -# self.is_needed = False -# self.pattern = BitArray(4, "0100") -# self.w = BitArray(1, "0") -# self.r = BitArray(1, "0") -# self.x = BitArray(1, "0") -# self.b = BitArray(1, "0") - - class SubModRM(object): def __init__(self): self.mod = BitArray(2, "") @@ -406,7 +432,7 @@ class ModRM_REG64__REG64(SubModRM): 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)) - def __init__(self, arg1, arg2, reversed): + def __init__(self, arg1, arg2, reversed, instr_state): super(ModRM_REG64__REG64, self).__init__() self.mod = BitArray(2, "11") self.is_rex_needed = True @@ -420,8 +446,10 @@ class ModRM_REG64__MEM(SubModRM): def match(cls, arg1, arg2): return (X64.is_reg(arg1) or X64.is_new_reg(arg1)) and X64.is_mem_acces(arg2) - def __init__(self, arg1, arg2, reversed): + def __init__(self, arg1, arg2, reversed, instr_state): super(ModRM_REG64__MEM, self).__init__() + if arg2.prefix is not None: + instr_state.prefixes.append(x64_segment_selectors[arg2.prefix]) # ARG1 : REG # ARG2 : [MEM] # this encode [rip + disp] @@ -510,16 +538,18 @@ class Slash(object): "reg = 7 for /7" self.reg = reg_order[reg_num] - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): if len(args) < 1: 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(previous, args[:1] + [self.reg] + args[1:]) + 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) if value is None: return arg_consum, value, rex return arg_consum-1, value, rex +instr_state = collections.namedtuple('instr_state', ['previous', 'prefixes']) + class Instruction(object): encoding = [] default_rex = BitArray(8, "") @@ -528,11 +558,12 @@ class Instruction(object): for type_encoding in self.encoding: args = list(initial_args) 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) for element in type_encoding: - arg_consum, value, rex = element.accept_arg(res, args) + arg_consum, value, rex = element.accept_arg(args, instr_state(res, prefix)) if arg_consum is None: break res.append(value) @@ -542,6 +573,7 @@ class Instruction(object): else: # if no break if args: # if still args: fail continue + self.prefix = prefix self.value = sum(res, BitArray(0, "")) if any(full_rex.array): self.value = full_rex + self.value @@ -549,7 +581,8 @@ class Instruction(object): raise ValueError("Cannot encode <{0} {1}>:(".format(type(self).__name__, initial_args)) def get_code(self): - return self.value.dump() + prefix_opcode = b"".join(chr(p.PREFIX_VALUE) for p in self.prefix) + return prefix_opcode + bytes(self.value.dump()) class DelayedJump(object): def __init__(self, type, label): @@ -624,7 +657,7 @@ class JmpImm(object): def __init__(self, sub): self.sub = sub - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): try: jump_size = int(args[0]) except (ValueError, TypeError): @@ -669,9 +702,7 @@ class Jnb(JmpType): class Lea(Instruction): - #default_rex = BitArray(8, "01001000") refuse_reverse = True - #default_32_bits = False encoding = [(RawBits.from_int(8, 0x8d), ModRM([ModRM_REG64__MEM], accept_reverse=False, has_direction_bit=False))] class Mov(Instruction): @@ -860,28 +891,25 @@ try: except ImportError: in_IDA = False - -def test_code(): - s = MultipleInstr() - s += Mov('r8', 'r14') - s += Label(':SUCE') - s += Jnz(':END') - s += Add('r14', 0x12345678) - s += Dec('r9') - s += Dec('rax') - s += Jnz(':END') - s += Mov('r8', 'rdx') - s += Jnz(':END') - s += Mov('r8', 'rdx') - s += Jnz(':SUCE') - s += Mov('r9', 'r10') - s += Label(':END') - s += Ret() - return s - - - if in_IDA: + def test_code(): + s = MultipleInstr() + s += Mov('r8', 'r14') + s += Label(':SUCE') + s += Jnz(':END') + s += Add('r14', 0x12345678) + s += Dec('r9') + s += Dec('rax') + s += Jnz(':END') + s += Mov('r8', 'rdx') + s += Jnz(':END') + s += Mov('r8', 'rdx') + s += Jnz(':SUCE') + s += Mov('r9', 'r10') + s += Label(':END') + s += Ret() + return s + def reset(): idc.MakeUnknown(idc.MinEA(), 0x1000, 0) for i in range(0x1000): diff --git a/native_exec/simple_x86.py b/native_exec/simple_x86.py index 9321900..4fad52e 100644 --- a/native_exec/simple_x86.py +++ b/native_exec/simple_x86.py @@ -61,31 +61,108 @@ class BitArray(object): x = x & ((2 ** size) - 1) return cls(size, bin(x)[2:]) -# Rule: bytes only !!!! +# Prefix +class Prefix(object): + PREFIX_VALUE = None + def __init__(self, next=None): + self.next = next -mem_access = collections.namedtuple('mem_access', ['base', 'index', 'scale', 'disp']) + def __add__(self, other): + return type(self)(other) + + def get_code(self): + return chr(self.PREFIX_VALUE) + self.next.get_code() + +def create_prefix(name, value): + prefix_type = type(name + "Type", (Prefix,), {'PREFIX_VALUE' : value}) + setattr(sys.modules[__name__], name, prefix_type()) + +create_prefix('LockPrefix', 0xf0) +create_prefix('Repne', 0xf2) +create_prefix('Rep', 0xf3) +create_prefix('SSPrefix', 0x36) +create_prefix('CSPrefix', 0x2e) +create_prefix('DSPrefix', 0x3e) +create_prefix('ESPrefix', 0x26) +create_prefix('FSPrefix', 0x64) +create_prefix('GSPrefix', 0x65) +create_prefix('OperandSizeOverride', 0x66) +create_prefix('AddressSizeOverride', 0x67) + +# Main informations about X86 +mem_access = collections.namedtuple('mem_access', ['base', 'index', 'scale', 'disp', 'prefix']) x86_regs = ['EAX', 'ECX', 'EDX', 'EBX', 'ESP', 'EBP', 'ESI', 'EDI'] +x86_16bits_regs = ['AX', 'CX', 'DX', 'BX', 'SP', 'BP', 'SI', 'DI'] -def create_displacement(base=None, index=None, scale=None, disp=0): +x86_segment_selectors = {'CS' : CSPrefix, 'DS' : DSPrefix, 'ES' : ESPrefix, 'SS' : SSPrefix, + 'FS': FSPrefix, 'GS' : GSPrefix} + +class X86(object): + @staticmethod + def is_reg(name): + try: + return name.upper() in x86_regs + x86_16bits_regs + except AttributeError: # Not a string + return False + + @staticmethod + def reg_size(name): + if name.upper() in x86_regs: + return 32 + elif name.upper() in x86_16bits_regs: + return 16 + else: + raise ValueError("Unknow register <{0}>".format(name)) + + @staticmethod + def is_mem_acces(data): + return isinstance(data, mem_access) + + @staticmethod + def mem_access_has_only(mem_access, names): + if not X86.is_mem_acces(mem_access): + raise ValueError("mem_access_has_only") + for f in mem_access._fields: + v = getattr(mem_access, f) + if v and f != 'prefix' and f not in names: + return False + if v is None and f in names: + return False + return True + + +def create_displacement(base=None, index=None, scale=None, disp=0, prefix=None): + """Create an X86 memory access description""" if index is not None and scale is None: scale = 1 if scale and index is None: raise ValueError("Cannot create displacement with scale and no index") if scale and index.upper() == "ESP": raise ValueError("Cannot create displacement with index == ESP") - return mem_access(base, index, scale, disp) + return mem_access(base, index, scale, disp, prefix) def mem(data): - """Parse a memory access string""" + """Parse a memory access string of format [EXPR] or seg:[EXPR] + EXPR may describe: BASE | INDEX * SCALE | DISPLACEMENT or any combinaison (in this order) + """ if not isinstance(data, str): raise TypeError("mem need a string to parse") data = data.strip() + prefix = None if not (data.startswith("[") and data.endswith("]")): - raise ValueError("mem acces expect <[EXPR]>") + if data[2] != ":": + raise ValueError("mem acces expect <[EXPR]> or or in mem access".format(index)) + if X86.reg_size(index) == 16: + raise NotImplementedError("16bits modrm") try: scale = int(scale, 0) except ValueError as e: @@ -108,6 +187,8 @@ def mem(data): else: # displacement / base / index alone if X86.is_reg(item): + if X86.reg_size(item) == 16: + raise NotImplementedError("16bits modrm") if not 'base' in parsed_items: parsed_items['base'] = item continue @@ -125,13 +206,14 @@ def mem(data): parsed_items['disp'] = disp return create_displacement(**parsed_items) +# Helper to get the BitArray associated to a register class X86RegisterSelector(object): size = 3 # bits - reg_order = ['EAX', 'ECX', 'EDX', 'EBX', 'ESP', 'EBP', 'ESI', 'EDI'] - reg_opcode = {v : BitArray.from_int(size=3, x=i) for i, v in enumerate(reg_order)} + reg_opcode = {v : BitArray.from_int(size=3, x=i) for i, v in enumerate(x86_regs)} + reg_opcode.update({v : BitArray.from_int(size=3, x=i) for i, v in enumerate(x86_16bits_regs)}) - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): x = args[0] try: return (1, self.reg_opcode[x.upper()]) @@ -142,28 +224,25 @@ class X86RegisterSelector(object): def get_reg_bits(cls, name): return cls.reg_opcode[name.upper()] -class RegisterEax(object): - def accept_arg(self, previous, args): - x = args[0] - if isinstance(x, str) and x.upper() == 'EAX': - return (1, BitArray(0, [])) - return None, None +## Instruction Parameters class FixedRegister(object): def __init__(self, register): self.reg = register.upper() - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): x = args[0] if isinstance(x, str) and x.upper() == self.reg: return (1, BitArray(0, [])) return None, None +RegisterEax = lambda: FixedRegister('EAX') + class RawBits(BitArray): - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): return (0, self) -# Immediat value logique +# Immediat value logic # All 8/16 bits stuff are sign extended class ImmediatOverflow(ValueError): @@ -192,19 +271,19 @@ def accept_as_32immediat(x): raise ImmediatOverflow("32bits signed Immediat overflow") class Imm8(object): - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): try: x = int(args[0]) except (ValueError, TypeError): return (None, None) try: - imm8 = accept_as_16immediat(x) + imm8 = accept_as_8immediat(x) except ImmediatOverflow: return None, None return (1, BitArray.from_string(imm8)) class Imm16(object): - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): try: x = int(args[0]) except (ValueError, TypeError): @@ -216,7 +295,7 @@ class Imm16(object): return (1, BitArray.from_string(imm16)) class Imm32(object): - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): try: x = int(args[0]) except (ValueError, TypeError): @@ -233,57 +312,37 @@ class ModRM(object): self.has_direction_bit = has_direction_bit self.sub = sub_modrm - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): if len(args) < 2: raise ValueError("Missing arg for modrm") arg1 = args[0] arg2 = args[1] for sub in self.sub: # Problem in reverse sens -> need to fix it - #import pdb;pdb.set_trace() if sub.match(arg1, arg2): - d = sub(arg1, arg2, 0) + d = sub(arg1, arg2, 0, instr_state) if self.has_direction_bit: - previous[0][-2] = d.direction + instr_state.previous[0][-2] = d.direction return (2, d.mod + d.reg + d.rm + d.after) elif self.accept_reverse and sub.match(arg2, arg1): - d = sub(arg2, arg1, 1) + d = sub(arg2, arg1, 1, instr_state) if self.has_direction_bit: - previous[0][-2] = d.direction + instr_state.previous[0][-2] = d.direction return (2, d.mod + d.reg + d.rm + d.after) return (None, None) -class X86(object): - @staticmethod - def is_reg(name): - try: - return name.upper() in x86_regs - except AttributeError: # Not a string - return False - - @staticmethod - def is_mem_acces(data): - return isinstance(data, mem_access) - - @staticmethod - def mem_access_has_only(mem_access, names): - if not X86.is_mem_acces(mem_access): - raise ValueError("mem_access_has_only") - for f in mem_access._fields: - v = getattr(mem_access, f) - if v and f not in names: - return False - if v is None and f in names: - return False - return True class ModRM_REG__REG(object): @classmethod def match(cls, arg1, arg2): return X86.is_reg(arg1) and X86.is_reg(arg2) - def __init__(self, arg1, arg2, reversed): + def __init__(self, arg1, arg2, reversed, instr_state): self.mod = BitArray(2, "11") + if X86.reg_size(arg1) != X86.reg_size(arg2): + raise ValueError("Register size mitmatch between {0} and {1}".format(arg1, arg2)) + if X86.reg_size(arg1) == 16: + instr_state.prefixes.append(OperandSizeOverride) self.reg = X86RegisterSelector.get_reg_bits(arg2) self.rm = X86RegisterSelector.get_reg_bits(arg1) self.after = BitArray(0, "") @@ -294,12 +353,20 @@ class ModRM_REG__MEM(object): def match(cls, arg1, arg2): return X86.is_reg(arg1) and X86.is_mem_acces(arg2) - def __init__(self, arg1, arg2, reversed): + def setup_reg_as_register(self, regname, instr_state): + self.reg = X86RegisterSelector.get_reg_bits(regname) + if X86.reg_size(regname) == 16: + instr_state.prefixes.append(OperandSizeOverride) + + def __init__(self, arg1, arg2, reversed, instr_state): # ARG1 : REG - # ARG2 : [MEM] + # ARG2 : prefix:[MEM] + # Handle prefix: + if arg2.prefix is not None: + instr_state.prefixes.append(x86_segment_selectors[arg2.prefix]) if X86.mem_access_has_only(arg2, ["disp"]): self.mod = BitArray(2, "00") - self.reg = X86RegisterSelector.get_reg_bits(arg1) + self.setup_reg_as_register(arg1, instr_state) self.rm = BitArray(3, "101") try: self.after = BitArray.from_string(accept_as_32immediat(arg2.disp)) @@ -311,7 +378,7 @@ class ModRM_REG__MEM(object): # No index -> no scale -> no SIB FIRE_UP_SIB = (arg2.base and arg2.base.upper() in ["ESP", "EBP"]) or arg2.index if not FIRE_UP_SIB: - self.reg = X86RegisterSelector.get_reg_bits(arg1) + self.setup_reg_as_register(arg1, instr_state) self.rm = X86RegisterSelector.get_reg_bits(arg2.base) self.compute_displacement(arg2.disp) self.direction = not reversed @@ -325,7 +392,7 @@ class ModRM_REG__MEM(object): else: force_displacement = 0 - self.reg = X86RegisterSelector.get_reg_bits(arg1) + self.setup_reg_as_register(arg1, instr_state) self.rm = BitArray(3, "100") self.compute_displacement(arg2.disp, force_displacement) self.after = self.compute_sib(arg2) + self.after @@ -376,25 +443,28 @@ class Slash(object): "reg = 7 for /7" self.reg = x86_regs[reg_num] - def accept_arg(self, previous, args): + def accept_arg(self, args, instr_state): if len(args) < 1: 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 = ModRM([ModRM_REG__REG, ModRM_REG__MEM], has_direction_bit=False).accept_arg(previous, args[:1] + [self.reg] + args[1:]) + arg_consum, value = ModRM([ModRM_REG__REG, ModRM_REG__MEM], has_direction_bit=False).accept_arg(args[:1] + [self.reg] + args[1:], instr_state) if value is None: return arg_consum, value return arg_consum-1, value -class Instruction(object): - encoding = [] +instr_state = collections.namedtuple('instr_state', ['previous', 'prefixes']) +class Instruction(object): + """Base class of instructions, use `encoding` to find a valid way to assemble the instruction""" + encoding = [] def __init__(self, *initial_args): for type_encoding in self.encoding: args = list(initial_args) + prefix = [] res = [] for element in type_encoding: - arg_consum, value = element.accept_arg(res, args) + arg_consum, value = element.accept_arg(args, instr_state(res, prefix)) if arg_consum is None: break res.append(value) @@ -403,18 +473,23 @@ class Instruction(object): if args: # if still args: fail continue self.value = sum(res, BitArray(0, "")) + self.prefix = prefix return raise ValueError("Cannot encode <{0} {1}>:(".format(type(self).__name__, initial_args)) def get_code(self): - return bytes(self.value.dump()) + prefix_opcode = b"".join(chr(p.PREFIX_VALUE) for p in self.prefix) + return prefix_opcode + bytes(self.value.dump()) +# Jump helpers class DelayedJump(object): + """A jump to a label :NAME""" def __init__(self, type, label): self.type = type self.label = label class JmpType(Instruction): + """Dispatcher between a real jump or DelayedJump if parameters is a label""" def __new__(cls, *initial_args): if len(initial_args) == 1: arg = initial_args[0] @@ -422,6 +497,45 @@ class JmpType(Instruction): return DelayedJump(cls, arg) return super(JmpType, cls).__new__(cls, *initial_args) +class JmpImm(object): + """Immediat parameters for Jump instruction + Sub a specified size from the size to jump to `emulate` a jump from the begin address of the instruction""" + accept_as_Ximmediat = None + def __init__(self, sub): + self.sub = sub + + def accept_arg(self, args, instr_state): + try: + jump_size = int(args[0]) + except (ValueError, TypeError): + return (None, None) + jump_size -= self.sub + try: + jmp_imm = self.accept_as_Ximmediat(jump_size) + except ImmediatOverflow: + return (None, None) + return (1, BitArray.from_string(jmp_imm)) + +class JmpImm8(JmpImm): + accept_as_Ximmediat = staticmethod(accept_as_8immediat) + +class JmpImm32(JmpImm): + accept_as_Ximmediat = staticmethod(accept_as_32immediat) + +## Instructions + +class Jmp(JmpType): + encoding = [(RawBits.from_int(8, 0xeb), JmpImm8(2)), + (RawBits.from_int(8, 0xe9), JmpImm32(5))] + +class Jz(JmpType): + encoding = [(RawBits.from_int(8, 0x74), JmpImm8(2)), + (RawBits.from_int(16, 0x0f84), JmpImm32(6))] + +class Jnz(JmpType): + encoding = [(RawBits.from_int(8, 0x75), JmpImm8(2)), + (RawBits.from_int(16, 0x0f85), JmpImm32(6))] + class Push(Instruction): encoding = [(RawBits.from_int(5, 0x50 >> 3), X86RegisterSelector()), (RawBits.from_int(8, 0x68), Imm32())] @@ -466,42 +580,6 @@ class In(Instruction): (RawBits.from_int(16, 0x66ed), FixedRegister('AX'), FixedRegister('DX')), # Fuck-it hardcoded prefix for now (RawBits.from_int(8, 0xed), FixedRegister('EAX'), FixedRegister('DX'))] -class JmpImm(object): - accept_as_Ximmediat = None - def __init__(self, sub): - self.sub = sub - - def accept_arg(self, previous, args): - try: - jump_size = int(args[0]) - except (ValueError, TypeError): - return (None, None) - jump_size -= self.sub - try: - jmp_imm = self.accept_as_Ximmediat(jump_size) - except ImmediatOverflow: - return (None, None) - return (1, BitArray.from_string(jmp_imm)) - -class JmpImm8(JmpImm): - accept_as_Ximmediat = staticmethod(accept_as_8immediat) - -class JmpImm32(JmpImm): - accept_as_Ximmediat = staticmethod(accept_as_32immediat) - - -class Jmp(JmpType): - encoding = [(RawBits.from_int(8, 0xeb), JmpImm8(2)), - (RawBits.from_int(8, 0xe9), JmpImm32(5))] - -class Jz(JmpType): - encoding = [(RawBits.from_int(8, 0x74), JmpImm8(2)), - (RawBits.from_int(16, 0x0f84), JmpImm32(6))] - -class Jnz(JmpType): - encoding = [(RawBits.from_int(8, 0x75), JmpImm8(2)), - (RawBits.from_int(16, 0x0f85), JmpImm32(6))] - class Xor(Instruction): encoding = [(RawBits.from_int(8, 0x31), ModRM([ModRM_REG__REG]))] @@ -680,7 +758,6 @@ class MultipleInstr(object): return self # IDA : import windows.native_exec.simple_x86 as x86 - # IDA testing try: @@ -690,38 +767,16 @@ try: except ImportError: in_IDA = False -#def test_code(): -# s = MultipleInstr() -# s += Mov('EAX', 'EAX') -# s += Mov('EAX', 'EAX') -# s += Jnz(":SUCE") -# s += Mov('EAX', 'EAX') -# s += Cmp("Eax", "ESI") -# s += Jnz(":SUCE") -# s += Mov("ECX", "ECX") -# s += Label(":SUCE") -# s += Jnz(":LOL") -# s += Jnz(":BITE") -# s += Mov("EDX", "EDX") -# s += Label(":LOL") -# s += Mov('EDI', 'EDI') -# s += Label(":BITE") -# s += Mov('EDI', 'EDI') -# s += Jnz(":SUCE") -# s += Push("ECX") -# s += Pop("EAX") -# s += Ret() -# return s - -def test_code(): - s = MultipleInstr() - s += Mov("Eax", "ESI") - s += Inc("Ecx") - s += Dec("edi") - s += Ret() - return s if in_IDA: + def test_code(): + s = MultipleInstr() + s += Mov("Eax", "ESI") + s += Inc("Ecx") + s += Dec("edi") + s += Ret() + return s + def reset(): idc.MakeUnknown(idc.MinEA(), 0x1000, 0) for i in range(0x1000): diff --git a/native_exec/test_simple_x64.py b/native_exec/test_simple_x64.py index 7a5b517..1be0236 100644 --- a/native_exec/test_simple_x64.py +++ b/native_exec/test_simple_x64.py @@ -54,6 +54,12 @@ class TestInstr(object): def compare_mem_access(self, memaccess, capres, cap_op): if cap_op.type != capstone.x86.X86_OP_MEM: raise AssertionError("Expected Memaccess <{0}> got {1}".format(memaccess, cap_op)) + if memaccess.prefix is not None and capres.prefix[1] != x64_segment_selectors[memaccess.prefix].PREFIX_VALUE: + try: + get_prefix = [n for n,x in x64_segment_selectors.items() if x.PREFIX_VALUE == capres.prefix[1]][0] + except IndexError: + get_prefix = None + raise AssertionError("Expected Segment overide <{0}> got {1}".format(memaccess.prefix, get_prefix)) cap_mem = cap_op.mem if memaccess.base is None and cap_mem.base != capstone.x86.X86_REG_INVALID: raise AssertionError("Unexpected memaccess base <{0}>".format(capres.reg_name(cap_mem.base))) @@ -75,8 +81,10 @@ TestInstr(Add)('RAX', mem('[RDI + 0x10]')) TestInstr(Add)('RAX', mem('[RSI + 0x7fffffff]')) TestInstr(Add)('RAX', mem('[RSI + -0x1]')) TestInstr(Add)('RAX', mem('[0x10]')) +TestInstr(Add)('RAX', mem('fs:[0x10]')) TestInstr(Add)('RAX', mem('[RSI + RDI * 2]')) TestInstr(Add)('RAX', mem('[RSI + RDI * 2 + 0x10]')) +TestInstr(Add)('RAX', mem('gs:[RSI + RDI * 2 + 0x10]')) TestInstr(Add)('RAX', mem('[R15 * 8 + 0x10]')) TestInstr(Add)('RAX', mem('[R9 + R8 * 2 + 0x7fffffff]')) TestInstr(Add)('RAX', mem('[R9 + R8 * 2 + -0x80000000]')) @@ -86,11 +94,14 @@ TestInstr(Xor)('R15', mem('[RAX + R8 * 2 + 0x11223344]')) TestInstr(Xor)('RAX', 'RAX') TestInstr(Cmp)('RAX', -1) TestInstr(Lea)('RAX', mem('[RAX + 1]')) +TestInstr(Lea)('RAX', mem('fs:[RAX + 1]')) TestInstr(Mov)('RAX', mem('[0x1122334455667788]')) +TestInstr(Mov)('RAX', mem('gs:[0x1122334455667788]')) +TestInstr(Mov)('RAX', mem('gs:[0x60]')) TestInstr(Mov)('RCX', 0x1122334455667788) TestInstr(Mov)('RCX', -1) TestInstr(Mov, immediat_accepted=-1)('RCX', 0xffffffffffffffff) -TestInstr(Mov)(mem('[0x1122334455667788]'), 'RAX') +TestInstr(Mov)(mem('gs:[0x1122334455667788]'), 'RAX') TestInstr(Push)('R15') TestInstr(Push)(0x42) TestInstr(Push)(-1) diff --git a/native_exec/test_simple_x86.py b/native_exec/test_simple_x86.py index 829d084..1d72af4 100644 --- a/native_exec/test_simple_x86.py +++ b/native_exec/test_simple_x86.py @@ -7,29 +7,29 @@ disassembleur.detail = True def disas(x): return list(disassembleur.disasm(x, 0)) - + class TestInstr(object): def __init__(self, instr_to_test): self.instr_to_test = instr_to_test - + def __call__(self, *args): res = bytes(self.instr_to_test(*args).get_code()) capres_list = disas(res) if len(capres_list) != 1: - raise AssertionError("Trying to disas an instruction resulted in multiple disassembled instrs") + raise AssertionError("Trying to disas an instruction resulted in multiple disassembled instrs") capres = capres_list[0] print("{0} {1}".format(capres.mnemonic, capres.op_str)) if len(res) != len(capres.bytes): raise AssertionError("Not all bytes have been used by the disassembler") self.compare_mnemo(capres) self.compare_args(args, capres) - + def compare_mnemo(self, capres): expected = self.instr_to_test.__name__.lower() if expected != str(capres.mnemonic): raise AssertionError("Expected menmo {0} got {1}".format(expected, str(capres.mnemonic))) return True - + def compare_args(self, args, capres): capres_op = list(capres.operands) if len(args) != len(capres_op): @@ -47,10 +47,16 @@ class TestInstr(object): self.compare_mem_access(op_args, capres, cap_op) else: raise ValueError("Unknow argument {0} of type {1}".format(op_args, type(op_args))) - + def compare_mem_access(self, memaccess, capres, cap_op): if cap_op.type != capstone.x86.X86_OP_MEM: raise AssertionError("Expected Memaccess <{0}> got {1}".format(memaccess, cap_op)) + if memaccess.prefix is not None and capres.prefix[1] != x86_segment_selectors[memaccess.prefix].PREFIX_VALUE: + try: + get_prefix = [n for n,x in x86_segment_selectors.items() if x.PREFIX_VALUE == capres.prefix[1]][0] + except IndexError: + get_prefix = None + raise AssertionError("Expected Segment overide <{0}> got {1}".format(memaccess.prefix, get_prefix)) cap_mem = cap_op.mem if memaccess.base is None and cap_mem.base != capstone.x86.X86_REG_INVALID: raise AssertionError("Unexpected memaccess base <{0}>".format(capres.reg_name(cap_mem.base))) @@ -59,13 +65,13 @@ class TestInstr(object): if memaccess.index is None and cap_mem.index != capstone.x86.X86_REG_INVALID: raise AssertionError("Unexpected memaccess index <{0}>".format(capres.reg_name(cap_mem.base))) if memaccess.index is not None and capres.reg_name(cap_mem.index) != memaccess.index.lower(): - raise AssertionError("Expected mem.index {0} got {1}".format(memaccess.index.lower(), capres.reg_name(cap_mem.index))) + raise AssertionError("Expected mem.index {0} got {1}".format(memaccess.index.lower(), capres.reg_name(cap_mem.index))) if memaccess.scale != cap_mem.scale and not (memaccess.scale is None and cap_mem.scale == 1): raise AssertionError("Expected mem.scale {0} got {1}".format(memaccess.scale, cap_mem.scale)) if memaccess.disp != cap_mem.disp: raise AssertionError("Expected mem.disp {0} got {1}".format(memaccess.disp, cap_mem.disp)) - - + + TestInstr(Mov)('EAX', 'ESP') TestInstr(Mov)('ECX', mem('[EAX]')) TestInstr(Mov)('EDX', mem('[ECX + 0x10]')) @@ -74,6 +80,18 @@ TestInstr(Mov)('EDX', mem('[0x11223344]')) TestInstr(Mov)('EDX', mem('[ESP + EBP * 2 + 0x223344]')) TestInstr(Mov)(mem('[EBP + EBP * 2 + 0x223344]'), 'ESP') TestInstr(Mov)('ESI', mem('[ESI + EDI * 1]')) + +TestInstr(Mov)('EAX', mem('fs:[0x30]')) +TestInstr(Mov)('EDI', mem('gs:[EAX + ECX * 4]')) + +TestInstr(Mov)('AX', 'AX') +TestInstr(Mov)('SI', 'DI') + +TestInstr(Mov)('AX', 'AX') +TestInstr(Mov)('AX', mem('fs:[0x30]')) +TestInstr(Mov)('AX', mem('fs:[EAX + 0x30]')) +TestInstr(Mov)('AX', mem('fs:[EAX + ECX * 4+0x30]')) + TestInstr(Add)('EAX', 8) TestInstr(Add)('EAX', 0xffffffff)