mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
9bb2f6761d
Added control for empty passed argument and moved windows folder out for easier importing.
1187 lines
38 KiB
Python
1187 lines
38 KiB
Python
import sys
|
|
import collections
|
|
import struct
|
|
import binascii
|
|
|
|
# py3
|
|
is_py3 = (sys.version_info.major >= 3)
|
|
if is_py3:
|
|
basestring = str
|
|
int_types = int
|
|
else:
|
|
int_types = (int, long)
|
|
|
|
class BitArray(object):
|
|
def __init__(self, size, bits):
|
|
self.size = size
|
|
if len(bits) > size:
|
|
raise ValueError("size > len(bits)")
|
|
|
|
bits_list = []
|
|
for bit in bits:
|
|
x = int(bit)
|
|
if x not in [0, 1]:
|
|
raise ValueError("Not expected bits value {0}".format(x))
|
|
bits_list.append(x)
|
|
|
|
self.array = bits_list
|
|
if size > len(self.array):
|
|
self.array = ([0] * (size - len(self.array))) + self.array
|
|
|
|
def copy(self):
|
|
new = type(self)(0, "")
|
|
new.size = self.size
|
|
new.array = list(self.array)
|
|
return new
|
|
|
|
def dump(self):
|
|
res = []
|
|
for i in range(self.size // 8):
|
|
c = 0
|
|
for x in (self.array[i * 8: (i + 1) * 8]):
|
|
c = (c << 1) + x
|
|
res.append(c)
|
|
return bytearray((res))
|
|
|
|
def __getitem__(self, slice):
|
|
return self.array[slice]
|
|
|
|
def __setitem__(self, slice, value):
|
|
self.array[slice] = value
|
|
return True
|
|
|
|
def __repr__(self):
|
|
return repr(self.array)
|
|
|
|
def __add__(self, other):
|
|
if not isinstance(other, BitArray):
|
|
return NotImplemented
|
|
return BitArray(self.size + other.size, self.array + other.array)
|
|
|
|
def to_int(self):
|
|
return int("".join([str(i) for i in self.array]), 2)
|
|
|
|
@classmethod
|
|
def from_string(cls, str_base):
|
|
l = []
|
|
for c in bytearray(reversed(str_base)):
|
|
for i in range(8):
|
|
l.append(c & 1)
|
|
c = c >> 1
|
|
return cls(len(str_base) * 8, list(reversed(l)))
|
|
|
|
@classmethod
|
|
def from_int(cls, size, x):
|
|
if x < 0:
|
|
x = x & ((2 ** size) - 1)
|
|
return cls(size, bin(x)[2:])
|
|
|
|
|
|
# Prefix
|
|
class Prefix(object):
|
|
PREFIX_VALUE = None
|
|
|
|
def __init__(self, next=None):
|
|
self.next = next
|
|
|
|
def __add__(self, other):
|
|
return type(self)(other)
|
|
|
|
def get_code_py3(self):
|
|
return bytes([self.PREFIX_VALUE]) + self.next.get_code()
|
|
|
|
def get_code(self):
|
|
return chr(self.PREFIX_VALUE) + self.next.get_code()
|
|
|
|
if is_py3:
|
|
get_code = get_code_py3
|
|
|
|
|
|
def create_prefix(name, value):
|
|
prefix_type = type(name + "Type", (Prefix,), {'PREFIX_VALUE': value})
|
|
return prefix_type()
|
|
|
|
LockPrefix = create_prefix('LockPrefix', 0xf0)
|
|
Repne = create_prefix('Repne', 0xf2)
|
|
Rep = create_prefix('Rep', 0xf3)
|
|
SSPrefix = create_prefix('SSPrefix', 0x36)
|
|
CSPrefix = create_prefix('CSPrefix', 0x2e)
|
|
DSPrefix = create_prefix('DSPrefix', 0x3e)
|
|
ESPrefix = create_prefix('ESPrefix', 0x26)
|
|
FSPrefix = create_prefix('FSPrefix', 0x64)
|
|
GSPrefix = create_prefix('GSPrefix', 0x65)
|
|
OperandSizeOverride = create_prefix('OperandSizeOverride', 0x66)
|
|
AddressSizeOverride = 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']
|
|
|
|
x86_segment_selectors = {'CS': CSPrefix, 'DS': DSPrefix, 'ES': ESPrefix, 'SS': SSPrefix,
|
|
'FS': FSPrefix, 'GS': GSPrefix}
|
|
|
|
# Man intel -> Sreg (Vol 2.a 3-6)
|
|
x86_segment_selectors_number = {
|
|
"ES": "000",
|
|
"CS": "001",
|
|
"SS": "010",
|
|
"DS": "011",
|
|
"FS": "100",
|
|
"GS": "101",
|
|
}
|
|
|
|
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 is_seg_reg(name):
|
|
try:
|
|
return name.upper() in x86_segment_selectors_number
|
|
except AttributeError:
|
|
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):
|
|
"""Creates a 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, prefix)
|
|
|
|
|
|
def deref(disp):
|
|
"""Create a memory access for an immediate value ``Ex: [0x42424242]``"""
|
|
return create_displacement(disp=disp)
|
|
|
|
|
|
def mem(data):
|
|
"""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("]")):
|
|
if data[2] != ":":
|
|
raise ValueError("mem acces expect <[EXPR]> or <seg:[EXPR]")
|
|
prefix_name = data[:2].upper()
|
|
if prefix_name not in x86_segment_selectors:
|
|
raise ValueError("Unknow segment selector {0}".format(prefix_name))
|
|
prefix = prefix_name
|
|
data = data[3:]
|
|
if not (data.startswith("[") and data.endswith("]")):
|
|
raise ValueError("mem acces expect <[EXPR]> or <seg:[EXPR]")
|
|
# A l'arrache.. j'aime pas le parsing de trucs
|
|
data = data[1:-1]
|
|
items = data.split("+")
|
|
parsed_items = {'prefix': prefix}
|
|
for item in items:
|
|
item = item.strip()
|
|
# Index * scale
|
|
if "*" in item:
|
|
if 'index' in parsed_items:
|
|
raise ValueError("Multiple index / index*scale in mem expression <{0}>".format(data))
|
|
sub_items = item.split("*")
|
|
if len(sub_items) != 2:
|
|
raise ValueError("Invalid item <{0}> in mem access".format(item))
|
|
index, scale = sub_items
|
|
index, scale = index.strip(), scale.strip()
|
|
if not X86.is_reg(index):
|
|
raise ValueError("Invalid index <{0}> in mem access".format(index))
|
|
if X86.reg_size(index) == 16:
|
|
raise NotImplementedError("16bits modrm")
|
|
try:
|
|
scale = int(scale, 0)
|
|
except ValueError:
|
|
raise ValueError("Invalid scale <{0}> in mem access".format(scale))
|
|
parsed_items['scale'] = scale
|
|
parsed_items['index'] = index
|
|
else:
|
|
# displacement / base / index alone
|
|
if X86.is_reg(item):
|
|
if X86.reg_size(item) == 16:
|
|
raise NotImplementedError("16bits modrm")
|
|
if 'base' not in parsed_items:
|
|
parsed_items['base'] = item
|
|
continue
|
|
# Already have base + index -> cannot avec another register in expression
|
|
if 'index' in parsed_items:
|
|
raise ValueError("Multiple index / index*scale in mem expression <{0}>".format(data))
|
|
parsed_items['index'] = item
|
|
continue
|
|
try:
|
|
disp = int(item, 0)
|
|
except ValueError:
|
|
raise ValueError("Invalid base/index or displacement <{0}> in mem access".format(item))
|
|
if 'disp' in parsed_items:
|
|
raise ValueError("Multiple displacement in mem expression <{0}>".format(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_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, args, instr_state):
|
|
x = args[0]
|
|
try:
|
|
return (1, self.reg_opcode[x.upper()])
|
|
except (KeyError, AttributeError):
|
|
return (None, None)
|
|
|
|
@classmethod
|
|
def get_reg_bits(cls, name):
|
|
return cls.reg_opcode[name.upper()]
|
|
|
|
|
|
# Instruction Parameters
|
|
class FixedRegister(object):
|
|
def __init__(self, register):
|
|
self.reg = register.upper()
|
|
|
|
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, args, instr_state):
|
|
return (0, self.copy())
|
|
|
|
|
|
# Immediat value logic
|
|
# All 8/16 bits stuff are sign extended
|
|
class ImmediatOverflow(ValueError):
|
|
pass
|
|
|
|
|
|
def accept_as_8immediat(x):
|
|
try:
|
|
return struct.pack("<b", x)
|
|
except struct.error:
|
|
raise ImmediatOverflow("8bits signed Immediat overflow")
|
|
|
|
|
|
def accept_as_unsigned_8immediat(x):
|
|
try:
|
|
return struct.pack("<B", x)
|
|
except struct.error:
|
|
raise ImmediatOverflow("8bits signed Immediat overflow")
|
|
|
|
|
|
def accept_as_16immediat(x):
|
|
try:
|
|
return struct.pack("<h", x)
|
|
except struct.error:
|
|
raise ImmediatOverflow("16bits signed Immediat overflow")
|
|
|
|
|
|
def accept_as_unsigned_16immediat(x):
|
|
try:
|
|
return struct.pack("<H", x)
|
|
except struct.error:
|
|
raise ImmediatOverflow("16bits unsigned Immediat overflow")
|
|
|
|
def accept_as_32immediat(x):
|
|
try:
|
|
return struct.pack("<i", x)
|
|
except struct.error:
|
|
pass
|
|
try:
|
|
return struct.pack("<I", x)
|
|
except struct.error:
|
|
raise ImmediatOverflow("32bits signed Immediat overflow")
|
|
|
|
|
|
class Imm8(object):
|
|
def accept_arg(self, args, instr_state):
|
|
try:
|
|
x = int(args[0])
|
|
except (ValueError, TypeError):
|
|
return (None, None)
|
|
try:
|
|
imm8 = accept_as_8immediat(x)
|
|
except ImmediatOverflow:
|
|
return None, None
|
|
return (1, BitArray.from_string(imm8))
|
|
|
|
class UImm8(object):
|
|
def accept_arg(self, args, instr_state):
|
|
try:
|
|
x = int(args[0])
|
|
except (ValueError, TypeError):
|
|
return (None, None)
|
|
try:
|
|
imm8 = accept_as_unsigned_8immediat(x)
|
|
except ImmediatOverflow:
|
|
return None, None
|
|
return (1, BitArray.from_string(imm8))
|
|
|
|
|
|
class Imm16(object):
|
|
def accept_arg(self, args, instr_state):
|
|
try:
|
|
x = int(args[0])
|
|
except (ValueError, TypeError):
|
|
return (None, None)
|
|
try:
|
|
imm16 = accept_as_16immediat(x)
|
|
except ImmediatOverflow:
|
|
return None, None
|
|
return (1, BitArray.from_string(imm16))
|
|
|
|
class UImm16(object):
|
|
def accept_arg(self, args, instr_state):
|
|
try:
|
|
x = int(args[0])
|
|
except (ValueError, TypeError):
|
|
return (None, None)
|
|
try:
|
|
imm16 = accept_as_unsigned_16immediat(x)
|
|
except ImmediatOverflow:
|
|
return None, None
|
|
return (1, BitArray.from_string(imm16))
|
|
|
|
|
|
class Imm32(object):
|
|
def accept_arg(self, args, instr_state):
|
|
try:
|
|
x = int(args[0])
|
|
except (ValueError, TypeError):
|
|
return (None, None)
|
|
try:
|
|
imm32 = accept_as_32immediat(x)
|
|
except ImmediatOverflow:
|
|
return None, None
|
|
return (1, BitArray.from_string(imm32))
|
|
|
|
class SegmentSelectorAbsoluteAddr(object):
|
|
def accept_arg(self, args, instr_state):
|
|
sizess, datass = UImm16().accept_arg(args, instr_state)
|
|
if sizess is None:
|
|
return None, None
|
|
sizeabs, dataabs = Imm32().accept_arg(args[1:], instr_state)
|
|
if sizeabs is None:
|
|
return None, None
|
|
return (sizess + sizeabs, dataabs + datass)
|
|
|
|
|
|
|
|
class ModRM(object):
|
|
def __init__(self, sub_modrm, accept_reverse=True, has_direction_bit=True):
|
|
self.accept_reverse = accept_reverse
|
|
self.has_direction_bit = has_direction_bit
|
|
self.sub = sub_modrm
|
|
|
|
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
|
|
if sub.match(arg1, arg2):
|
|
d = sub(arg1, arg2, 0, instr_state)
|
|
if self.has_direction_bit:
|
|
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, instr_state)
|
|
if self.has_direction_bit:
|
|
instr_state.previous[0][-2] = d.direction
|
|
return (2, d.mod + d.reg + d.rm + d.after)
|
|
return (None, None)
|
|
|
|
|
|
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, 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, "")
|
|
self.direction = 0
|
|
|
|
class ModRM_REG__SEGREG(object):
|
|
@classmethod
|
|
def match(cls, arg1, arg2):
|
|
return X86.is_reg(arg1) and X86.is_seg_reg(arg2)
|
|
|
|
def __init__(self, arg1, arg2, reversed, instr_state):
|
|
self.mod = BitArray(2, "11")
|
|
self.rm = X86RegisterSelector.get_reg_bits(arg1)
|
|
self.reg = BitArray(3, x86_segment_selectors_number[arg2.upper()])
|
|
self.after = BitArray(0, "")
|
|
self.direction = reversed
|
|
|
|
|
|
class ModRM_REG__MEM(object):
|
|
|
|
@classmethod
|
|
def match(cls, arg1, arg2):
|
|
return X86.is_reg(arg1) and X86.is_mem_acces(arg2)
|
|
|
|
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 : 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.setup_reg_as_register(arg1, instr_state)
|
|
self.rm = BitArray(3, "101")
|
|
try:
|
|
self.after = BitArray.from_string(accept_as_32immediat(arg2.disp))
|
|
except ImmediatOverflow:
|
|
raise ImmediatOverflow("Interger32 overflow for displacement {0}".format(hex(arg2.disp)))
|
|
self.direction = not reversed
|
|
return
|
|
# Those registers cannot be addressed without SIB
|
|
# 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.setup_reg_as_register(arg1, instr_state)
|
|
self.rm = X86RegisterSelector.get_reg_bits(arg2.base)
|
|
self.compute_displacement(arg2.disp)
|
|
self.direction = not reversed
|
|
return
|
|
# FIRE UP THE SIB
|
|
# Handle no base and base == EBP special case
|
|
if not arg2.base:
|
|
force_displacement = 4
|
|
elif arg2.base.upper() == "EBP":
|
|
force_displacement = 1
|
|
else:
|
|
force_displacement = 0
|
|
|
|
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
|
|
if not arg2.base:
|
|
self.mod = BitArray(2, "00")
|
|
self.direction = not reversed
|
|
|
|
def compute_displacement(self, displacement, force_displacement=0):
|
|
if not displacement and not force_displacement:
|
|
self.mod = BitArray(2, "00")
|
|
self.after = BitArray(0, "")
|
|
return
|
|
# Pack in a byte
|
|
try:
|
|
v = accept_as_8immediat(displacement)
|
|
except ImmediatOverflow:
|
|
v = None
|
|
if v is not None and force_displacement <= 1:
|
|
self.mod = BitArray(2, "01")
|
|
self.after = BitArray.from_string(v)
|
|
return
|
|
# Pack in a dword
|
|
try:
|
|
v = accept_as_32immediat(displacement)
|
|
except ImmediatOverflow:
|
|
v = None
|
|
if v is not None and force_displacement <= 4:
|
|
self.mod = BitArray(2, "10")
|
|
self.after = BitArray.from_string(v)
|
|
return
|
|
raise ValueError("Displacement {0} is too big".format(hex(displacement)))
|
|
|
|
def compute_sib(self, mem_access):
|
|
scale = {1: 0, 2: 1, 4: 2, 8: 3}
|
|
if mem_access.index is None:
|
|
return BitArray(2, "00") + BitArray(3, "100") + X86RegisterSelector.get_reg_bits(mem_access.base)
|
|
if mem_access.scale not in scale:
|
|
raise ValueError("Invalid scale for mem access <{0}>".format(mem_access.scale))
|
|
if mem_access.base is None:
|
|
return BitArray.from_int(2, scale[mem_access.scale]) + X86RegisterSelector.get_reg_bits(mem_access.index) + BitArray(3, "101")
|
|
return BitArray.from_int(2, scale[mem_access.scale]) + X86RegisterSelector.get_reg_bits(mem_access.index) + X86RegisterSelector.get_reg_bits(mem_access.base)
|
|
|
|
|
|
class Slash(object):
|
|
"No idea for the name: represent the modRM for single args + encoding in reg (/7 in cmp in man intel)"
|
|
|
|
def __init__(self, reg_num):
|
|
"reg = 7 for /7"
|
|
self.reg = x86_regs[reg_num]
|
|
|
|
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(args[:1] + [self.reg] + args[1:], instr_state)
|
|
if value is None:
|
|
return arg_consum, value
|
|
return arg_consum - 1, value
|
|
|
|
class ControlRegisterModRM(object):
|
|
def __init__(self, writecr = False):
|
|
self.writecr = writecr
|
|
|
|
def accept_arg(self, args, instr_state):
|
|
writecr = self.writecr
|
|
if len(args) < 2:
|
|
return None, None
|
|
reg = args[writecr]
|
|
cr = args[not writecr]
|
|
if not isinstance(cr, str):
|
|
return None, None
|
|
if not cr.lower().startswith("cr"):
|
|
return None, None
|
|
try:
|
|
cr_number = int(cr[2:], 10)
|
|
except ValueError as e:
|
|
raise ValueError("Invalid ControlRegister {0}".format(cr))
|
|
if cr_number > 7:
|
|
raise ValueError("Invalid ControlRegister {0}".format(cr))
|
|
|
|
modrm_params = [reg, x86_regs[cr_number]] + args[2:]
|
|
return ModRM([ModRM_REG__REG], has_direction_bit=False).accept_arg(modrm_params, instr_state)
|
|
|
|
|
|
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):
|
|
# print(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(args, instr_state(res, prefix))
|
|
if arg_consum is None:
|
|
break
|
|
res.append(value)
|
|
del args[:arg_consum]
|
|
else: # if no break
|
|
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):
|
|
# print(self.value)
|
|
prefix_opcode = b"".join(chr(p.PREFIX_VALUE) for p in self.prefix)
|
|
return prefix_opcode + bytes(self.value.dump())
|
|
|
|
def get_code_py3(self):
|
|
prefix_opcode = b"".join(bytes([p.PREFIX_VALUE]) for p in self.prefix)
|
|
return prefix_opcode + bytes(self.value.dump())
|
|
|
|
if is_py3:
|
|
get_code = get_code_py3
|
|
|
|
#def __add__(self, other):
|
|
# res = MultipleInstr()
|
|
# res += self
|
|
# res += other
|
|
# return res
|
|
|
|
def __mul__(self, value):
|
|
if not isinstance(value, int_types):
|
|
return NotImplemented
|
|
res = MultipleInstr()
|
|
for i in range(value):
|
|
res += self
|
|
return res
|
|
|
|
|
|
# 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]
|
|
if isinstance(arg, str) and arg[0] == ":":
|
|
return DelayedJump(cls, arg)
|
|
return super(JmpType, cls).__new__(cls)
|
|
|
|
|
|
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 Call(JmpType):
|
|
encoding = [(RawBits.from_int(8, 0xe8), JmpImm32(5)),
|
|
(RawBits.from_int(8, 0xff), Slash(2)),
|
|
(RawBits.from_int(8, 0x9a), SegmentSelectorAbsoluteAddr())]
|
|
|
|
class Jmp(JmpType):
|
|
encoding = [(RawBits.from_int(8, 0xeb), JmpImm8(2)),
|
|
(RawBits.from_int(8, 0xe9), JmpImm32(5)),
|
|
(RawBits.from_int(8, 0xea), SegmentSelectorAbsoluteAddr())]
|
|
|
|
|
|
class Jz(JmpType):
|
|
encoding = [(RawBits.from_int(8, 0x74), JmpImm8(2)),
|
|
(RawBits.from_int(16, 0x0f84), JmpImm32(6))]
|
|
|
|
|
|
Je = Jz
|
|
|
|
|
|
class Jnz(JmpType):
|
|
encoding = [(RawBits.from_int(8, 0x75), JmpImm8(2)),
|
|
(RawBits.from_int(16, 0x0f85), JmpImm32(6))]
|
|
|
|
|
|
class Jbe(JmpType):
|
|
encoding = [(RawBits.from_int(8, 0x76), JmpImm8(2)),
|
|
(RawBits.from_int(16, 0x0f86), JmpImm32(6))]
|
|
|
|
|
|
class Jnb(JmpType):
|
|
encoding = [(RawBits.from_int(8, 0x73), JmpImm8(2)),
|
|
(RawBits.from_int(16, 0x0f83), JmpImm32(6))]
|
|
|
|
|
|
class Push(Instruction):
|
|
encoding = [(RawBits.from_int(5, 0x50 >> 3), X86RegisterSelector()),
|
|
(RawBits.from_int(8, 0x68), Imm32()),
|
|
(RawBits.from_int(8, 0xff), Slash(6))]
|
|
|
|
|
|
class Pop(Instruction):
|
|
encoding = [(RawBits.from_int(5, 0x58 >> 3), X86RegisterSelector())]
|
|
|
|
|
|
class Dec(Instruction):
|
|
encoding = [(RawBits.from_int(5, 0x48 >> 3), X86RegisterSelector())]
|
|
|
|
|
|
class Inc(Instruction):
|
|
encoding = [(RawBits.from_int(5, 0x40 >> 3), X86RegisterSelector()),
|
|
(RawBits.from_int(8, 0xff), Slash(0))]
|
|
|
|
|
|
class Add(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x05), RegisterEax(), Imm32()),
|
|
(RawBits.from_int(8, 0x81), Slash(0), Imm32()),
|
|
(RawBits.from_int(8, 0x01), ModRM([ModRM_REG__REG, ModRM_REG__MEM]))]
|
|
|
|
class And(Instruction):
|
|
default_32_bits = True
|
|
encoding = [(RawBits.from_int(8, 0x25), RegisterEax(), Imm32()),
|
|
(RawBits.from_int(8, 0x81), Slash(4), Imm32()),
|
|
(RawBits.from_int(8, 0x21), ModRM([ModRM_REG__REG, ModRM_REG__MEM]))]
|
|
|
|
|
|
class Or(Instruction):
|
|
default_32_bits = True
|
|
encoding = [(RawBits.from_int(8, 0x0d), RegisterEax(), Imm32()),
|
|
(RawBits.from_int(8, 0x81), Slash(1), Imm32()),
|
|
(RawBits.from_int(8, 0x09), ModRM([ModRM_REG__REG, ModRM_REG__MEM]))]
|
|
|
|
|
|
class Sub(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x2D), RegisterEax(), Imm32()),
|
|
(RawBits.from_int(8, 0x81), Slash(5), Imm32()),
|
|
(RawBits.from_int(8, 0x29), ModRM([ModRM_REG__REG, ModRM_REG__MEM]))]
|
|
|
|
|
|
class Mov(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x89), ModRM([ModRM_REG__REG, ModRM_REG__MEM])),
|
|
(RawBits.from_int(8, 0xc7), Slash(0), Imm32()),
|
|
(RawBits.from_int(5, 0xB8 >> 3), X86RegisterSelector(), Imm32()),
|
|
(RawBits.from_int(8, 0x8C), ModRM([ModRM_REG__SEGREG])),
|
|
(RawBits.from_int(16, 0x0f20), ControlRegisterModRM(writecr=False)),
|
|
(RawBits.from_int(16, 0x0f22), ControlRegisterModRM(writecr=True))]
|
|
|
|
|
|
class Movsb(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xa4),)]
|
|
|
|
|
|
class Movsd(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xa5),)]
|
|
|
|
|
|
class Pushfd(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x9c),)]
|
|
|
|
|
|
class Pushad(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x60),)]
|
|
|
|
|
|
class Popfd(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x9d),)]
|
|
|
|
|
|
class Popad(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x61),)]
|
|
|
|
|
|
class Lea(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x8d), ModRM([ModRM_REG__MEM], accept_reverse=False, has_direction_bit=False))]
|
|
|
|
|
|
class Cmp(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x3d), RegisterEax(), Imm32()),
|
|
(RawBits.from_int(8, 0x81), Slash(7), Imm32()),
|
|
(RawBits.from_int(8, 0x3b), ModRM([ModRM_REG__REG, ModRM_REG__MEM]))]
|
|
|
|
|
|
class Test(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xf7), Slash(0), Imm32()),
|
|
(RawBits.from_int(8, 0x85), ModRM([ModRM_REG__REG, ModRM_REG__MEM], has_direction_bit=False))]
|
|
|
|
|
|
class Out(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xee), FixedRegister('DX'), FixedRegister('AL')),
|
|
(RawBits.from_int(16, 0x66ef), FixedRegister('DX'), FixedRegister('AX')), # Fuck-it hardcoded prefix for now
|
|
(RawBits.from_int(8, 0xef), FixedRegister('DX'), FixedRegister('EAX'))]
|
|
|
|
|
|
class In(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xec), FixedRegister('AL'), FixedRegister('DX')),
|
|
(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 Xor(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x31), ModRM([ModRM_REG__REG]))]
|
|
|
|
|
|
class Xchg(Instruction):
|
|
encoding = [(RawBits.from_int(5, 0x90 >> 3), RegisterEax(), X86RegisterSelector()), (RawBits.from_int(5, 0x90 >> 3), X86RegisterSelector(), RegisterEax())]
|
|
|
|
|
|
class Rol(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xC1), Slash(0), Imm8())]
|
|
|
|
class Ror(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xC1), Slash(1), Imm8())]
|
|
|
|
class Shr(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xC1), Slash(5), Imm8())]
|
|
|
|
class Shl(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xC1), Slash(4), Imm8())]
|
|
|
|
class Cpuid(Instruction):
|
|
encoding = [(RawBits.from_int(16, 0x0fa2),)]
|
|
|
|
|
|
class Ret(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xc3),),
|
|
(RawBits.from_int(8, 0xc2), UImm16())]
|
|
|
|
|
|
class ScasB(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xAE),)]
|
|
|
|
class ScasW(Instruction):
|
|
encoding = [(RawBits.from_int(16, 0x66AF),)]
|
|
|
|
class ScasD(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xAF),)]
|
|
|
|
|
|
class StosB(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xAA),)]
|
|
|
|
class StosW(Instruction):
|
|
encoding = [(RawBits.from_int(16, 0x66AB),)]
|
|
|
|
class StosD(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xAB),)]
|
|
|
|
|
|
class CmpsB(Instruction):
|
|
default_32_bits = True
|
|
encoding = [(RawBits.from_int(8, 0xa6),)]
|
|
|
|
|
|
class CmpsW(Instruction):
|
|
default_32_bits = True
|
|
encoding = [(RawBits.from_int(16, 0x66A7),)]
|
|
|
|
|
|
class CmpsD(Instruction):
|
|
default_32_bits = True
|
|
encoding = [(RawBits.from_int(8, 0xa7),)]
|
|
|
|
|
|
class Nop(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0x90),)]
|
|
|
|
class Not(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xF7), Slash(2))]
|
|
|
|
class Retf(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xcb),)]
|
|
|
|
class Int(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xcd), UImm8())]
|
|
|
|
class Int3(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xcc),)]
|
|
|
|
class Iret(Instruction):
|
|
encoding = [(RawBits.from_int(8, 0xcf),)]
|
|
|
|
|
|
class _NopArtifact(Nop):
|
|
"""Special NOP used in shellcode reduction"""
|
|
pass
|
|
|
|
|
|
class Byte(Instruction):
|
|
"""Output a raw byte"""
|
|
encoding = [(UImm8(),)]
|
|
|
|
|
|
class Raw(Instruction):
|
|
"""Output raw data"""
|
|
def __init__(self, *initial_args):
|
|
if len(initial_args) != 1:
|
|
raise ValueError("raw 'opcode' only accept one argument")
|
|
# Accept space
|
|
self.data = binascii.unhexlify(initial_args[0].replace(" ", ""))
|
|
|
|
def get_code(self):
|
|
return self.data
|
|
|
|
|
|
|
|
|
|
class Label(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
def JmpAt(addr):
|
|
code = MultipleInstr()
|
|
code += Push(addr)
|
|
code += Ret()
|
|
return code
|
|
|
|
|
|
class MultipleInstr(object):
|
|
JUMP_SIZE = 6
|
|
|
|
def __init__(self, init_instrs=()):
|
|
self.instrs = {}
|
|
self.labels = {}
|
|
self.expected_labels = {}
|
|
# List of all labeled jump already resolved
|
|
# Will be used for 'relocation'
|
|
self.computed_jump = []
|
|
self.size = 0
|
|
for i in init_instrs:
|
|
self += i
|
|
|
|
def get_code(self):
|
|
if self.expected_labels:
|
|
raise ValueError("Unresolved labels: {0}".format(self.expected_labels.keys()))
|
|
return b"".join([x[1].get_code() for x in sorted(self.instrs.items())])
|
|
|
|
def add_instruction(self, instruction):
|
|
if isinstance(instruction, Label):
|
|
return self.add_label(instruction)
|
|
# Change DelayedJump to LabeledJump ?
|
|
if isinstance(instruction, DelayedJump):
|
|
return self.add_delayed_jump(instruction)
|
|
if isinstance(instruction, (Instruction, Prefix)):
|
|
self.instrs[self.size] = instruction
|
|
self.size += len(instruction.get_code())
|
|
return
|
|
raise ValueError("Don't know what to do with {0} of type {1}".format(instruction, type(instruction)))
|
|
|
|
def add_label(self, label):
|
|
if label.name not in self.expected_labels:
|
|
# Label that have no jump before definition
|
|
# Just registed the address of the label
|
|
self.labels[label.name] = self.size
|
|
return
|
|
# Label with jmp before definition
|
|
# Lot of stuff todo:
|
|
# Find all delayed jump that refer to this jump
|
|
# Replace them with real jump
|
|
# If size of jump < JUMP_SIZE: relocate everything we can
|
|
# Update expected_labels
|
|
for jump_to_label in self.expected_labels[label.name]:
|
|
if jump_to_label.offset in self.instrs:
|
|
raise ValueError("WTF REPLACE EXISTING INSTR...")
|
|
distance = self.size - jump_to_label.offset
|
|
real_jump = jump_to_label.type(distance)
|
|
self.instrs[jump_to_label.offset] = real_jump
|
|
self.computed_jump.append((jump_to_label.offset, self.size))
|
|
for i in range(self.JUMP_SIZE - len(real_jump.get_code())):
|
|
self.instrs[jump_to_label.offset + len(real_jump.get_code()) + i] = _NopArtifact()
|
|
del self.expected_labels[label.name]
|
|
self.labels[label.name] = self.size
|
|
if not self.expected_labels:
|
|
# No more un-resolved label (for now): time to reduce the shellcode
|
|
self._reduce_shellcode()
|
|
|
|
def add_delayed_jump(self, jump):
|
|
dst = jump.label
|
|
if dst in self.labels:
|
|
# Jump to already defined labels
|
|
# Nothing fancy: get offset of label and jump to it !
|
|
distance = self.size - self.labels[dst]
|
|
jump_instruction = jump.type(-distance)
|
|
self.computed_jump.append((self.size, self.labels[dst]))
|
|
return self.add_instruction(jump_instruction)
|
|
# Jump to undefined label
|
|
# Add label to expected ones
|
|
# Add jump info -> offset of jump | type
|
|
# Reserve space for call !
|
|
jump.offset = self.size
|
|
self.expected_labels.setdefault(dst, []).append(jump)
|
|
self.size += self.JUMP_SIZE
|
|
return
|
|
|
|
def _reduce_shellcode(self):
|
|
to_remove = [offset for offset, instr in self.instrs.items() if type(instr) == _NopArtifact]
|
|
while to_remove:
|
|
self._remove_nop_artifact(to_remove[0])
|
|
# _remove_nop_artifact will change the offsets of the nop
|
|
# Need to refresh these offset
|
|
to_remove = [offset for offset, instr in self.instrs.items() if type(instr) == _NopArtifact]
|
|
|
|
def _remove_nop_artifact(self, offset):
|
|
# Remove a NOP from the shellcode
|
|
for src, dst in self.computed_jump:
|
|
# Reduce size of Jump over the nop (both sens)
|
|
if src < offset < dst or dst < offset < src:
|
|
old_jmp = self.instrs[src]
|
|
old_jump_size = len(old_jmp.get_code())
|
|
if src < offset < dst:
|
|
new_jmp = type(old_jmp)(dst - src - 1)
|
|
else:
|
|
new_jmp = type(old_jmp)(dst - src + 1)
|
|
new_jmp_size = len(new_jmp.get_code())
|
|
if new_jmp_size > old_jump_size:
|
|
raise ValueError("Wtf jump of smaller size is bigger.. ABORT")
|
|
self.instrs[src] = new_jmp
|
|
# Add other _NopArtifact if jump instruction size is reduced
|
|
for i in range(old_jump_size - new_jmp_size):
|
|
self.instrs[src + new_jmp_size + i] = _NopArtifact()
|
|
|
|
# dec offset of all Label after the NOP
|
|
for name, labeloffset in self.labels.items():
|
|
if labeloffset > offset:
|
|
self.labels[name] = labeloffset - 1
|
|
|
|
# dec offset of all instr after the NOP
|
|
new_instr = {}
|
|
for instroffset, instr in self.instrs.items():
|
|
if instroffset == offset:
|
|
continue
|
|
if instroffset > offset:
|
|
instroffset -= 1
|
|
new_instr[instroffset] = instr
|
|
self.instrs = new_instr
|
|
# Update all computed jump
|
|
new_computed_jump = []
|
|
for src, dst in self.computed_jump:
|
|
if src > offset:
|
|
src -= 1
|
|
if dst > offset:
|
|
dst -= 1
|
|
new_computed_jump.append((src, dst))
|
|
self.computed_jump = new_computed_jump
|
|
# dec size of the shellcode
|
|
self.size -= 1
|
|
|
|
def merge_shellcode(self, other):
|
|
shared_labels = set(self.labels) & set(other.labels)
|
|
if shared_labels:
|
|
raise ValueError("Cannot merge shellcode: shared labels {0}".format(shared_labels))
|
|
for offset, instr in sorted(other.instrs.items()):
|
|
for label_name in [name for name, label_offset in other.labels.items() if label_offset == offset]:
|
|
self.add_instruction(Label(label_name))
|
|
self.add_instruction(instr)
|
|
|
|
def __iadd__(self, other):
|
|
if isinstance(other, MultipleInstr):
|
|
self.merge_shellcode(other)
|
|
elif isinstance(other, basestring):
|
|
self.assemble(other)
|
|
else:
|
|
self.add_instruction(other)
|
|
return self
|
|
|
|
def assemble(self, code):
|
|
for instr in assemble_instructions_generator(code):
|
|
self.add_instruction(instr)
|
|
|
|
|
|
def split_in_instruction(str):
|
|
for line in str.split("\n"):
|
|
if not line:
|
|
continue
|
|
for instr in line.split(";"):
|
|
if not instr:
|
|
continue
|
|
yield instr.strip()
|
|
|
|
def assemble_instructions_generator(str):
|
|
for instr in split_in_instruction(str):
|
|
data = instr.split(" ", 1)
|
|
mnemo, args_raw = data[0], data[1:]
|
|
try:
|
|
instr_object = globals()[mnemo.capitalize()]
|
|
except:
|
|
raise ValueError("Unknow mnemonic <{0}>".format(mnemo))
|
|
|
|
args = []
|
|
if args_raw:
|
|
for arg in args_raw[0].split(","):
|
|
arg = arg.strip()
|
|
if (arg[0] == "[" or arg[2:4] == ":[") and arg[-1] == "]":
|
|
arg = mem(arg)
|
|
else:
|
|
try:
|
|
arg = int(arg, 0)
|
|
except ValueError:
|
|
pass
|
|
args.append(arg)
|
|
yield instr_object(*args)
|
|
|
|
def assemble(str):
|
|
"""Play test"""
|
|
shellcode = MultipleInstr()
|
|
shellcode += str
|
|
return shellcode.get_code()
|
|
|
|
def shellcode(str):
|
|
shellcode = MultipleInstr()
|
|
shellcode += str
|
|
return shellcode
|
|
|
|
# IDA : import windows.native_exec.simple_x86 as x86
|
|
# IDA testing
|
|
|
|
try:
|
|
import midap
|
|
import idc
|
|
in_IDA = True
|
|
except ImportError:
|
|
in_IDA = False
|
|
|
|
|
|
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):
|
|
idc.PatchByte(idc.MinEA() + i, 0)
|
|
|
|
s = test_code()
|
|
|
|
def tst():
|
|
reset()
|
|
midap.here(idc.MinEA()).write(s.get_code())
|
|
idc.MakeFunction(idc.MinEA())
|