Source code for malduck.disasm

# Copyright (C) 2018 Jurriaan Bremer.
# This file is part of Roach - https://github.com/jbremer/roach.
# See the file 'docs/LICENSE.txt' for copying permission.

import collections
from .py2compat import is_string

__all__ = ["disasm", "insn", "Disassemble", "Instruction", "Operand", "Memory"]

Memory = collections.namedtuple("Memory", ("size", "base", "scale", "index", "disp"))


[docs]class Operand(object): """ Operand object for single :class:`Instruction` """ # These are initialized the first time disasm() is called, see also below. _x86_op_imm = None _x86_op_reg = None _x86_op_mem = None regs = {} sizes = { 1: "byte", 2: "word", 4: "dword", 8: "qword", } def __init__(self, op, x64): self.op = op self.x64 = x64 @property def is_imm(self): """Is it immediate operand?""" return self.op.type == Operand._x86_op_imm @property def is_reg(self): """Is it register operand?""" return self.op.type == Operand._x86_op_reg @property def is_mem(self): """Is it memory operand?""" return self.op.type == Operand._x86_op_mem @property def value(self): """ Returns operand value or displacement value for memory operands :rtype: str or int """ if self.is_imm: return self.op.value.imm if self.is_mem: return self.op.value.mem.disp if self.is_reg: return self.regs[self.op.reg] @property def reg(self): """ Returns register used by operand. For memory operands, returns base register or index register if base is not used. For immediate operands or displacement-only memory operands returns None. :rtype: str """ if self.is_mem: reg = self.op.value.mem.base or self.op.value.mem.index if reg: return self.regs[reg] if self.is_reg: return self.regs[self.op.reg] @property def mem(self): """ Returns :class:`Memory` object for memory operands """ if not self.is_mem: return mem = self.op.value.mem if mem.base: base = self.regs[mem.base] else: base = None if mem.index: index, scale = self.regs[mem.index], mem.scale else: index, scale = None, None return Memory(self.sizes[self.op.size], base, scale, index, mem.disp) def __eq__(self, other): if isinstance(other, Operand): return self.op.type == other.op.type and self.value == other.value if self.is_imm: return self.value == other if is_string(other): other = (other,) if self.is_reg and self.reg in other: return True if self.is_mem and self.reg in other: return True return False def __str__(self): if self.is_imm: if self.x64: return "0x%016x" % (self.value % 2 ** 64) else: return "0x%08x" % (self.value % 2 ** 32) if self.is_reg: return self.reg if self.is_mem: s, m = [], self.mem if m.base: s.append(m.base) if m.index: s.append("%d*%s" % (m.scale, m.index)) if m.disp: s.append("0x%08x" % (m.disp % 2 ** 32)) return "%s [%s]" % (m.size, "+".join(s))
[docs]class Instruction(object): """ Represents single instruction in :class:`Disassemble` short: insn Properties correspond to the following elements of instruction: .. code-block:: python 00400000 imul ecx, edx, 0 [addr] [mnem] [op1], [op2], [op3] Usage example: .. code-block:: python def get_move_value(self, p, hit, *args): # find move value of `mov eax, x` for ins in p.disasmv(hit, 0x100): if ins.mnem == 'mov' and ins.op1.value == 'eax': return ins.op2.value .. seealso:: :py:meth:`malduck.procmem.ProcessMemory.disasmv` """ def __init__(self, mnem=None, op1=None, op2=None, op3=None, addr=None, x64=False): self.insn = None self.mnem = mnem self.operands = op1, op2, op3 self._addr = addr self.x64 = x64 def parse(self, insn): self.insn = insn self.mnem = insn.mnemonic operands = [] for op in insn.operands + [None, None, None]: operands.append(Operand(op, self.x64) if op else None) self.operands = operands[0], operands[1], operands[2] @staticmethod def from_capstone(insn, x64=False): ret = Instruction() ret.x64 = x64 ret.parse(insn) return ret @property def op1(self): """First operand""" return self.operands[0] @property def op2(self): """Second operand""" return self.operands[1] @property def op3(self): """Third operand""" return self.operands[2] @property def addr(self): """Instruction address""" return self._addr or self.insn.address def __eq__(self, other): if not isinstance(other, Instruction): return False if self.mnem != other.mnem or self.addr != other.addr: return False if self.operands == other.operands: return True return False def __str__(self): operands = [] if self.op1 is not None: operands.append(str(self.op1)) if self.op2 is not None: operands.append(str(self.op2)) if self.op3 is not None: operands.append(str(self.op3)) if operands: return "%s %s" % (self.mnem, ", ".join(operands)) return self.mnem
[docs]class Disassemble(object):
[docs] def disassemble(self, data, addr, x64=False): """ Disassembles data from specific address short: disasm :param data: Block of data to disasseble :type data: bytes :param addr: Virtual address of data :type addr: int :param x64: Disassemble in x86-64 mode? :type x64: bool (default=False) :return: Returns list of instructions :rtype: List[:class:`Instruction`] """ import capstone cs = capstone.Cs( capstone.CS_ARCH_X86, capstone.CS_MODE_64 if x64 else capstone.CS_MODE_32 ) cs.detail = True ret = [] for insn in cs.disasm(data, addr): ret.append(Instruction.from_capstone(insn, x64=x64)) return ret
def init_once(self, *args, **kwargs): import capstone.x86 Operand._x86_op_imm = capstone.x86.X86_OP_IMM Operand._x86_op_reg = capstone.x86.X86_OP_REG Operand._x86_op_mem = capstone.x86.X86_OP_MEM # Index the available x86 registers. for _ in dir(capstone.x86): if not _.startswith("X86_REG_"): continue Operand.regs[getattr(capstone.x86, _)] = _.split("_")[2].lower() self.__call__ = self.disassemble return self.__call__(*args, **kwargs) __call__ = init_once
disasm = Disassemble() insn = Instruction