Update syscall/funcall implementation (#213)

* Wrap ABI semantics in its own class hierarchy

* Define a model invocation for syscalls and function calls

* Add unit tests for ABI

* Add a common base class for Platform models
This commit is contained in:
Yan 2017-05-09 16:13:08 -04:00 committed by GitHub
parent 3873c3eb5d
commit 3c9653d1d7
9 changed files with 756 additions and 158 deletions

View File

@ -6,10 +6,13 @@ from ..smtlib import Expression, Bool, BitVec, Array, Operators, Constant
from ..memory import MemoryException, FileMap, AnonMap
from ...utils.helpers import issymbolic
from ...utils.emulate import UnicornEmulator
import sys
from functools import wraps
from itertools import islice, imap
import inspect
import sys
import types
import logging
logger = logging.getLogger("CPU")
register_logger = logging.getLogger("REGISTERS")
@ -162,7 +165,133 @@ class RegisterFile(object):
:param register: a register name
'''
return self._alias(register) in self.all_registers
return self._alias(register) in self.all_registers
class Abi(object):
'''
Represents the ability to extract arguments from the environment and write
back a result.
Used for function call and system call models.
'''
def __init__(self, cpu):
'''
:param manticore.core.cpu.Cpu cpu: CPU to initialize with
'''
self._cpu = cpu
def get_arguments(self):
'''
Extract model arguments conforming to `convention`. Produces an iterable
of argument descriptors following the calling convention. A descriptor
is either a string describing a register, or an address (concrete or
symbolic).
:return: iterable returning syscall arguments.
:rtype: iterable
'''
raise NotImplementedError
def write_result(self, result):
'''
Write the result of a model back to the environment.
:param result: result of the model implementation
'''
raise NotImplementedError
def ret(self):
'''
Handle the "ret" semantics of the ABI, i.e. reclaiming stack space,
popping PC, etc.
A null operation by default.
'''
return
def values_from(self, base):
'''
A reusable generator for increasing pointer-sized values from an address
(usually the stack).
'''
word_bytes = self._cpu.address_bit_size / 8
while True:
yield base
base += word_bytes
def invoke(self, model, prefix_args=None, varargs=False):
'''
Invoke a callable `model` as if it was a native function. If `varargs`
is true, model receives a single argument that is a generator for
function arguments. Pass a tuple of arguments for `prefix_args` you'd
like to precede the actual arguments.
:param callable model: Python model of the function
:param tuple prefix_args: Parameters to pass to model before actual ones
:param bool varargs: Whether the function expects a variable number of arguments
:return: The result of calling `model`
'''
prefix_args = prefix_args or ()
spec = inspect.getargspec(model)
if spec.varargs:
logger.warning("ABI: A vararg model must be a unary function.")
nargs = len(spec.args) - len(prefix_args)
# If the model is a method, we need to account for `self`
if inspect.ismethod(model):
nargs -= 1
def resolve_argument(arg):
if isinstance(arg, str):
return self._cpu.read_register(arg)
else:
return self._cpu.read_int(arg)
# Create a stream of resolved arguments from argument descriptors
descriptors = self.get_arguments()
argument_iter = imap(resolve_argument, descriptors)
try:
if varargs:
result = model(*(prefix_args + (argument_iter,)))
else:
argument_tuple = prefix_args + tuple(islice(argument_iter, nargs))
result = model(*argument_tuple)
except ConcretizeArgument as e:
assert e.argnum >= len(prefix_args), "Can't concretize a constant arg"
idx = e.argnum - len(prefix_args)
# Arguments were lazily computed in case of varargs, so recompute here
descriptors = self.get_arguments()
src = next(islice(descriptors, idx, idx+1))
msg = 'Concretizing due to model invocation'
if isinstance(src, str):
raise ConcretizeRegister(src, msg)
else:
raise ConcretizeMemory(src, self._cpu.address_bit_size, msg)
else:
if result is not None:
self.write_result(result)
self.ret()
return result
class SyscallAbi(Abi):
'''
A system-call specific ABI.
'''
def syscall_number(self):
'''
Extract the index of the invoked syscall.
:return: int
'''
raise NotImplementedError
############################################################################
# Abstract cpu encapsulating common cpu methods used by models and executor.
@ -205,7 +334,7 @@ class Cpu(object):
def __setstate__(self, state):
Cpu.__init__(self, state['regfile'], state['memory'])
self._icount = state['icount']
return
return
@property
def icount(self):
@ -453,10 +582,6 @@ class Cpu(object):
implementation(*instruction.operands)
self._icount+=1
@abstractmethod
def get_syscall_description(self):
pass
def emulate(self, instruction):
'''
If we could not handle emulating an instruction, use Unicorn to emulate

View File

@ -1,6 +1,6 @@
import struct
import sys
from .abstractcpu import Cpu, RegisterFile, Operand
from .abstractcpu import Abi, SyscallAbi, Cpu, RegisterFile, Operand
from .abstractcpu import SymbolicPCException, InvalidPCException, Interruption
from .abstractcpu import instruction as abstract_instruction
from .register import Register
@ -253,6 +253,42 @@ class Armv7RegisterFile(RegisterFile):
return ('R0','R1','R2','R3','R4','R5','R6','R7','R8','R9','R10','R11','R12','R13','R14','R15','APSR')
class Armv7LinuxSyscallAbi(SyscallAbi):
'''
ARMv7 Linux system call ABI
'''
# EABI standards:
# syscall # is in R7
# arguments are passed in R0-R6
# retval is passed in R0
def syscall_number(self):
return self._cpu.R7
def get_arguments(self):
for i in range(6):
yield 'R{}'.format(i)
def write_result(self, result):
self._cpu.R0 = result
class Armv7CdeclAbi(Abi):
'''
ARMv7 Cdecl function call ABI
'''
def get_arguments(self):
# First four passed via R0-R3, then on stack
for reg in ('R0', 'R1', 'R2', 'R3'):
yield reg
for address in self.values_from(self._cpu.STACK):
yield address
def write_result(self, result):
self._cpu.R0 = result
def ret(self):
self._cpu.PC = self._cpu.LR
class Armv7Cpu(Cpu):
'''
Cpu specialization handling the ARMv7 architecture.
@ -280,6 +316,7 @@ class Armv7Cpu(Cpu):
state['_force_next'] = self._force_next
return state
def __setstate__(self, state):
super(Armv7Cpu, self).__setstate__(state)
self._last_flags = state['_last_flags']

View File

@ -1,5 +1,5 @@
from .x86 import AMD64Cpu, I386Cpu
from .arm import Armv7Cpu
from .x86 import AMD64Cpu, I386Cpu, AMD64LinuxSyscallAbi, I386LinuxSyscallAbi, I386CdeclAbi, SystemVAbi
from .arm import Armv7Cpu, Armv7CdeclAbi, Armv7LinuxSyscallAbi
class CpuFactory(object):
_cpus = {
@ -12,3 +12,26 @@ class CpuFactory(object):
def get_cpu(mem, machine):
return CpuFactory._cpus[machine](mem)
@staticmethod
def get_function_abi(cpu, os, machine):
if os == 'linux' and machine == 'i386':
return I386CdeclAbi(cpu)
elif os == 'linux' and machine == 'amd64':
return SystemVAbi(cpu)
elif os == 'linux' and machine == 'armv7':
return Armv7CdeclAbi(cpu)
else:
return NotImplementedError("OS and machine combination not supported: {}/{}".format(os, machine))
@staticmethod
def get_syscall_abi(cpu, os, machine):
if os == 'linux' and machine == 'i386':
return I386LinuxSyscallAbi(cpu)
elif os == 'linux' and machine == 'amd64':
return AMD64LinuxSyscallAbi(cpu)
elif os == 'linux' and machine == 'armv7':
return Armv7LinuxSyscallAbi(cpu)
else:
return NotImplementedError("OS and machine combination not supported: {}/{}".format(os, machine))

View File

@ -1,15 +1,10 @@
from capstone import *
from capstone.x86 import *
from .abstractcpu import Cpu, RegisterFile, Operand, SANE_SIZES, instruction
from .abstractcpu import SymbolicPCException, InvalidPCException, Interruption, Sysenter, Syscall, ConcretizeRegister, ConcretizeArgument
import sys
import struct
import types
import weakref
from functools import wraps, partial
from .abstractcpu import Abi, SyscallAbi, Cpu, RegisterFile, Operand, instruction
from .abstractcpu import Interruption, Sysenter, Syscall, ConcretizeRegister, ConcretizeArgument
from functools import wraps
import collections
from ..smtlib import *
from ..memory import MemoryException
from ...utils.helpers import issymbolic
import logging
logger = logging.getLogger("CPU")
@ -754,17 +749,6 @@ class X86Cpu(Cpu):
if address+offset in cache:
del cache[address+offset]
def get_syscall_description(self):
# Syscall number is in RAX
# Arguments are in RDI, RSI, RDX, R10, R8 and R9
# Return is in RAX
index = self.RAX
arguments = [ self.RDI, self.RSI, self.RDX, self.R10, self.R8, self.R9 ]
def writeResult(result, self=self):
self.RAX = result
return (index, arguments, writeResult)
def canonicalize_instruction_name(self, instruction):
#MOVSD
if instruction.opcode[0] in (0xa4, 0xa5):
@ -5636,99 +5620,104 @@ class X86Cpu(Cpu):
################################################################################
#Calling conventions
class ABI:
'''IA32 Calling conventions
https://en.wikipedia.org/wiki/X86_calling_conventions
class I386LinuxSyscallAbi(SyscallAbi):
'''
@staticmethod
def cdecl(function):
'''C declaration
Subroutine arguments are passed on the stack.
Integer values and memory addresses are returned in the EAX register
'''
argcount = function.func_code.co_argcount - 1
assert argcount >= 0
def cdecl_function(model):
cpu = model.current
base = cpu.STACK+4 #skip ret address
arguments = [ cpu.read_int(base + (i*4), 32) for i in xrange(argcount) ]
try:
cpu.EAX = function(model, *arguments)
except ConcretizeArgument as cae:
assert 0 <= cae.argnum < argcount
# concretize here
mem_addr = base+cae.argnum*4
raise ConcretizeMemory(mem_addr, 32, "Concretizing Function Argument", 'MINMAX')
i386 Linux system call ABI
'''
def syscall_number(self):
return self._cpu.EAX
cpu.EIP = cpu.pop(32)
return cdecl_function
def get_arguments(self):
for reg in ('EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'EBP'):
yield reg
@staticmethod
def stdcall(function):
'''Standard calling convention
Subroutine arguments are passed on the stack.
Callee is responsible for cleaning up the stack.
Return values are stored in the EAX register.
'''
argcount = function.func_code.co_argcount - 1
assert argcount >= 0
def stdcall_function(model):
cpu = model.current
# skip saved EIP on stack
base = cpu.STACK+4
arguments = [ cpu.read_int(base+(pos*4), 32) for pos in xrange(argcount) ]
try:
cpu.EAX = function(model, *arguments)
except ConcretizeArgument as cae:
assert 0 <= cae.argnum < argcount
# concretize here
mem_addr = base+cae.argnum*4
raise ConcretizeMemory(mem_addr, 32, "Concretizing Function Argument", 'MINMAX')
def write_result(self, result):
self._cpu.EAX = result
cpu.EIP = cpu.pop(32)
cpu.STACK += argcount*4
return stdcall_function
class AMD64LinuxSyscallAbi(SyscallAbi):
'''
AMD64 Linux system call ABI
'''
@staticmethod
def thiscall(function):
pass
#TODO(yan): Floating point or wide arguments that deviate from the norm are
# not yet supported.
@staticmethod
def vectorcall(function):
pass
def syscall_number(self):
return self._cpu.RAX
'''AMD64 Calling conventions '''
@staticmethod
def systemV(function):
'''System V AMD64 calling convention
The first six integer or pointer arguments are passed in registers:
RDI, RSI, RDX, RCX, R8, and R9,
Additional arguments are passed on the stack.
Return value is stored in RAX.[16]:22
'''
argcount = function.func_code.co_argcount - 1
assert argcount >= 0
def argument(cpu):
yield cpu.RDI
yield cpu.RSI
yield cpu.RDX
yield cpu.RCX
yield cpu.R8
yield cpu.R9
stack = cpu.STACK+8
while True:
yield cpu.read_int(stack,64)
stack += 8
def systemV_function(model):
cpu = model.current
arguments = [ next(argument(cpu)) for _ in xrange(argcount) ]
cpu.RAX = function(cpu, *arguments)
cpu.RIP = cpu.pop(64)
return systemV_function
def get_arguments(self):
for reg in ('RDI', 'RSI', 'RDX', 'R10', 'R8', 'R9'):
yield reg
def write_result(self, result):
self._cpu.RAX = result
class I386CdeclAbi(Abi):
'''
i386 cdecl function call semantics
'''
def get_arguments(self):
base = self._cpu.STACK + self._cpu.address_bit_size / 8
for address in self.values_from(base):
yield address
def write_result(self, result):
self._cpu.EAX = result
def ret(self):
self._cpu.EIP = self._cpu.pop(self._cpu.address_bit_size)
class I386StdcallAbi(Abi):
'''
x86 Stdcall function call convention. Callee cleans up the stack.
'''
def __init__(self, cpu):
super(I386StdcallAbi, self).__init__(cpu)
self._arguments = 0
def get_arguments(self):
base = self._cpu.STACK + self._cpu.address_bit_size / 8
for address in self.values_from(base):
self._arguments += 1
yield address
def write_result(self, result):
self._cpu.EAX = result
def ret(self):
self._cpu.EIP = self._cpu.pop(self._cpu.address_bit_size)
word_bytes = self._cpu.address_bit_size / 8
self._cpu.ESP += self._arguments * word_bytes
self._arguments = 0
class SystemVAbi(Abi):
'''
x64 SystemV function call convention
'''
#TODO(yan): Floating point or wide arguments that deviate from the norm are
# not yet supported.
def get_arguments(self):
# First 6 arguments go in registers, rest are popped from stack
reg_args = ('RDI', 'RSI', 'RDX', 'RCX', 'R8', 'R9')
for reg in reg_args:
yield reg
word_bytes = self._cpu.address_bit_size / 8
for address in self.values_from(self._cpu.RSP + word_bytes):
yield address
def write_result(self, result):
# XXX(yan): Can also return in rdx for wide values.
self._cpu.RAX = result
def ret(self):
self._cpu.RIP = self._cpu.pop(self._cpu.address_bit_size)
@staticmethod
def msx64(function):
pass
class AMD64Cpu(X86Cpu):
#Config
@ -5837,7 +5826,6 @@ class AMD64Cpu(X86Cpu):
cpu.AL = cpu.read_int(cpu.RBX + Operators.ZEXTEND(cpu.AL, 64), 8)
class I386Cpu(X86Cpu):
#Config
max_instr_width = 15
@ -5845,17 +5833,6 @@ class I386Cpu(X86Cpu):
arch = CS_ARCH_X86
mode = CS_MODE_32
def get_syscall_description(self):
# Syscall number is in RAX
# Arguments are in RDI, RSI, RDX, R10, R8 and R9
# Return is in RAX
index = self.EAX
arguments = [self.EBX, self.ECX, self.EDX, self.ESI, self.EDI, self.EBP]
def writeResult(result, self=self):
self.RAX = result
return (index, arguments, writeResult)
def __init__(self, memory, *args, **kwargs):
'''
Builds a CPU model.

View File

@ -59,7 +59,7 @@ def makeLinux(program, argv, env, concrete_start = ''):
# If any of the arguments or environment refer to symbolic values, re-
# initialize the stack
if any(issymbolic(x) for val in argv + env for x in val):
model.setup_stack(initial_state.cpu, [program] + argv, env)
model.setup_stack([program] + argv, env)
model.input.transmit(concrete_start)
@ -486,19 +486,15 @@ class Manticore(object):
with open(path, 'r') as fnames:
for line in fnames.readlines():
address, cc_name, name = line.strip().split(' ')
cc = getattr(core.cpu.x86.ABI, cc_name)
fmodel = models
name_parts = name.split('.')
importlib.import_module(".models.{}".format(name_parts[0]), 'manticore')
for n in name_parts:
fmodel = getattr(fmodel,n)
assert fmodel != models
logger.debug("[+] Hooking 0x%x %s %s", int(address,0), cc_name, name )
def cb_function(cc, fmodel, state):
cc(fmodel)(state.model)
cb = functools.partial(cb_function, cc, fmodel)
# TODO(yan) this should be a dict
self._model_hooks.setdefault(int(address,0), set()).add(cb)
def cb_function(state):
state.model.invoke_model(fmodel, prefix_args=(state.model,))
self._model_hooks.setdefault(int(address,0), set()).add(cb_function)
def _model_hook_callback(self, state):
pc = state.cpu.PC

View File

@ -9,6 +9,7 @@ from ..core.cpu.abstractcpu import Interruption, Syscall, ConcretizeRegister
from ..core.cpu.cpufactory import CpuFactory
from ..core.memory import SMemory32, SMemory64, Memory32, Memory64
from ..core.smtlib import Operators, ConstraintSet
from ..models.platform import Platform
from elftools.elf.elffile import ELFFile
import logging
import random
@ -256,7 +257,7 @@ class Socket(object):
return len(buf)
class Linux(object):
class Linux(Platform):
'''
A simple Linux Operating System Model.
This class emulates the most common Linux system calls
@ -269,7 +270,7 @@ class Linux(object):
:param list argv: The argv array; not including binary.
:param list envp: The ENV variables.
'''
super(Linux, self).__init__(program)
argv = [] if argv is None else argv
envp = [] if envp is None else envp
@ -303,7 +304,10 @@ class Linux(object):
#Load process and setup socketpairs
arch = {'x86': 'i386', 'x64': 'amd64', 'ARM': 'armv7'}[ELFFile(file(program)).get_machine_arch()]
self.procs = [self._mk_proc(arch)]
cpu = self._mk_proc(arch)
self.procs = [cpu]
self._function_abi = CpuFactory.get_function_abi(cpu, 'linux', arch)
self._syscall_abi = CpuFactory.get_syscall_abi(cpu, 'linux', arch)
self._current = 0
self.load(program)
@ -362,6 +366,8 @@ class Linux(object):
state['auxv'] = self.auxv
state['program'] = self.program
state['syscall_arg_regs'] = self.syscall_arg_regs
state['functionabi'] = self._function_abi
state['syscallabi'] = self._syscall_abi
if hasattr(self, '_arm_tls_memory'):
state['_arm_tls_memory'] = self._arm_tls_memory
return state
@ -410,6 +416,8 @@ class Linux(object):
self.auxv = state['auxv']
self.program = state['program']
self.syscall_arg_regs = state['syscall_arg_regs']
self._function_abi = state['functionabi']
self._syscall_abi = state['syscallabi']
if '_arm_tls_memory' in state:
self._arm_tls_memory = state['_arm_tls_memory']
@ -943,7 +951,6 @@ class Linux(object):
def _is_open(self, fd):
return fd >= 0 and fd < len(self.files) and self.files[fd] is not None
def sys_lseek(self, fd, offset, whence):
'''
lseek - reposition read/write file offset
@ -1485,20 +1492,13 @@ class Linux(object):
}
index, arguments, writeResult = self.current.get_syscall_description()
index = self._syscall_abi.syscall_number()
if index not in syscalls:
raise SyscallNotImplemented(64, index)
func = syscalls[index]
return self._syscall_abi.invoke(syscalls[index])
logger.debug("SYSCALL64: %s %r ", func.func_name
, arguments[:func.func_code.co_argcount])
nargs = func.func_code.co_argcount
result = func(*arguments[:nargs-1])
writeResult(result)
return result
def int80(self):
'''
@ -1540,19 +1540,13 @@ class Linux(object):
0x00000014: self.sys_getpid,
0x000f0005: self.sys_ARM_NR_set_tls,
}
index, arguments, writeResult = self.current.get_syscall_description()
index = self._syscall_abi.syscall_number()
if index not in syscalls:
raise SyscallNotImplemented(64, index)
func = syscalls[index]
logger.debug("int80: %s %r ", func.func_name
, arguments[:func.func_code.co_argcount])
nargs = func.func_code.co_argcount
result = func(*arguments[:nargs-1])
writeResult(result)
return result
return self._syscall_abi.invoke(syscalls[index])
def sys_clock_gettime(self, clock_id, timespec):
logger.info("sys_clock_time not really implemented")
@ -1852,6 +1846,7 @@ class SLinux(Linux):
mem = SMemory32(self.constraints)
else:
mem = SMemory64(self.constraints)
return CpuFactory.get_cpu(mem, arch)
@property

View File

@ -0,0 +1,13 @@
from itertools import islice, imap
import inspect
class Platform(object):
'''
Base class for all operating system models.
'''
def __init__(self, path):
self._path = path
def invoke_model(self, model, prefix_args=None, varargs=False):
self._function_abi.invoke(model, prefix_args, varargs)

View File

@ -4,11 +4,12 @@ import sys, os, struct
from ..core.memory import Memory, MemoryException, SMemory32, Memory32
from ..core.smtlib import Expression, Operators, solver
# TODO use cpu factory
from ..core.cpu.x86 import I386Cpu, Sysenter
from ..core.cpu.x86 import I386Cpu, Sysenter, I386StdcallAbi
from ..core.cpu.abstractcpu import Interruption, Syscall, \
ConcretizeRegister, ConcretizeArgument, IgnoreAPI
from ..core.executor import ForkState, SyscallNotImplemented
from ..utils.helpers import issymbolic
from ..models.platform import Platform
from ..binary.pe import minidump
@ -47,7 +48,7 @@ def toStr(state, value):
value = minmax[0]
return '{:08x}'.format(value)
class Windows(object):
class Windows(Platform):
'''
A simple Windows Operating System Model.
This class emulates some Windows system calls
@ -70,6 +71,8 @@ class Windows(object):
'''
Builds a Windows OS model
'''
super(Windows, self).__init__(path)
self.clocks = 0
self.files = []
self.syscall_trace = []
@ -171,6 +174,7 @@ class Windows(object):
self.running.append(self.procs.index(cpu))
self._function_abi = I386StdcallAbi(self.procs[0])
# open standard files stdin, stdout, stderr
logger.info("Not Opening any file")
@ -193,6 +197,7 @@ class Windows(object):
state['syscall_trace'] = self.syscall_trace
state['files'] = self.files
state['flavor'] = self.flavor
state['function_abi'] = self._function_abi
return state
@ -208,6 +213,7 @@ class Windows(object):
self.syscall_trace = state['syscall_trace']
self.files = state['files']
self.flavor = state['flavor']
self._function_abi = state['function_abi']
def _read_string(self, cpu, buf):
"""

426
tests/test_abi.py Normal file
View File

@ -0,0 +1,426 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
import sys
import shutil
import tempfile
import os
import hashlib
import subprocess
import collections
import time
from manticore import Manticore, issymbolic
from manticore.core.smtlib import BitVecVariable
from manticore.core.cpu.abstractcpu import ConcretizeArgument, ConcretizeRegister, ConcretizeMemory
from manticore.core.cpu.arm import Armv7Cpu, Armv7LinuxSyscallAbi, Armv7CdeclAbi
from manticore.core.cpu.x86 import I386Cpu, AMD64Cpu, I386LinuxSyscallAbi, I386StdcallAbi, I386CdeclAbi, AMD64LinuxSyscallAbi, SystemVAbi
from manticore.core.memory import SMemory32, Memory32, SMemory64
from manticore.core.smtlib import ConstraintSet, Operators
class ABITests(unittest.TestCase):
def setUp(self):
mem32 = SMemory32(ConstraintSet())
mem32.mmap(0x1000, 0x1000, 'rw ')
mem64 = SMemory64(ConstraintSet())
mem64.mmap(0x1000, 0x1000, 'rw ')
self._cpu_arm = Armv7Cpu(mem32)
self._cpu_arm.SP = 0x1080
self._cpu_arm.func_abi = Armv7CdeclAbi(self._cpu_arm)
self._cpu_arm.syscall_abi = Armv7LinuxSyscallAbi(self._cpu_arm)
self._cpu_x86 = I386Cpu(mem32)
self._cpu_x86.ESP = 0x1080
self._cpu_x86.func_abi = I386CdeclAbi(self._cpu_x86)
self._cpu_x86.syscall_abi = I386LinuxSyscallAbi(self._cpu_x86)
self._cpu_x64 = AMD64Cpu(mem64)
self._cpu_x64.RSP = 0x1080
self._cpu_x64.func_abi = SystemVAbi(self._cpu_x64)
self._cpu_x64.syscall_abi = AMD64LinuxSyscallAbi(self._cpu_x64)
def write(mem, where, val, size):
mem[where:where+size/8] = [Operators.CHR(Operators.EXTRACT(val, offset, 8)) for offset in xrange(0, size, 8)]
for val in range(0, 0x100, 4):
write(mem32, 0x1000+val, val, 32)
for val in range(0, 0x100, 8):
write(mem64, 0x1000+val, val, 64)
def test_executor(self):
pass
def test_arm_abi_simple(self):
cpu = self._cpu_arm
for i in range(4):
cpu.write_register('R{}'.format(i), i)
cpu.LR = 0x1234
def test(one, two, three, four):
self.assertEqual(one, 0)
self.assertEqual(two, 1)
self.assertEqual(three, 2)
self.assertEqual(four, 3)
return 34
cpu.func_abi.invoke(test)
# result is correctly captured
self.assertEquals(cpu.R0, 34)
# sp is unchanged
self.assertEquals(cpu.SP, 0x1080)
# returned correctly
self.assertEquals(cpu.PC, cpu.LR)
def test_arm_abi(self):
cpu = self._cpu_arm
for i in range(4):
cpu.write_register('R{}'.format(i), i)
cpu.LR = 0x1234
self.assertEqual(cpu.read_int(cpu.SP), 0x80)
def test(one, two, three, four, five, six, seven):
self.assertEqual(one, 0)
self.assertEqual(two, 1)
self.assertEqual(three, 2)
self.assertEqual(four, 3)
self.assertEqual(five, 0x80)
self.assertEqual(six, 0x84)
self.assertEqual(seven, 0x88)
self.assertEqual(cpu.SP, 0x1080)
return 34
cpu.func_abi.invoke(test)
# result is correctly captured
self.assertEquals(cpu.R0, 34)
# sp is unchanged
self.assertEquals(cpu.SP, 0x1080)
# returned correctly
self.assertEquals(cpu.PC, cpu.LR)
def test_arm_abi_concretize_register(self):
cpu = self._cpu_arm
for i in range(4):
cpu.write_register('R{}'.format(i), i)
previous_r0 = cpu.R0
self.assertEqual(cpu.read_int(cpu.SP), 0x80)
def test(one, two, three, four, five, six):
raise ConcretizeArgument(0)
with self.assertRaises(ConcretizeRegister) as cr:
cpu.func_abi.invoke(test)
self.assertEquals(cpu.R0, previous_r0)
self.assertEquals(cr.exception.reg_name, 'R0')
self.assertEquals(cpu.SP, 0x1080)
def test_arm_abi_concretize_memory(self):
cpu = self._cpu_arm
for i in range(4):
cpu.write_register('R{}'.format(i), i)
previous_r0 = cpu.R0
self.assertEqual(cpu.read_int(cpu.SP), 0x80)
def test(one, two, three, four, five):
raise ConcretizeArgument(4)
with self.assertRaises(ConcretizeMemory) as cr:
cpu.func_abi.invoke(test)
self.assertEquals(cpu.R0, previous_r0)
self.assertEquals(cr.exception.address, cpu.SP)
self.assertEquals(cpu.SP, 0x1080)
def test_i386_cdecl(self):
cpu = self._cpu_x86
base = cpu.ESP
self.assertEqual(cpu.read_int(cpu.ESP), 0x80)
cpu.push(0x1234, cpu.address_bit_size)
def test(one, two, three, four, five):
self.assertEqual(one, 0x80)
self.assertEqual(two, 0x84)
self.assertEqual(three, 0x88)
self.assertEqual(four, 0x8c)
self.assertEqual(five, 0x90)
return 3
cpu.func_abi.invoke(test)
self.assertEquals(cpu.EAX, 3)
self.assertEquals(base, cpu.ESP)
self.assertEquals(cpu.EIP, 0x1234)
def test_i386_stdcall(self):
cpu = self._cpu_x86
base = cpu.ESP
bwidth = cpu.address_bit_size / 8
self.assertEqual(cpu.read_int(cpu.ESP), 0x80)
cpu.push(0x1234, cpu.address_bit_size)
def test(one, two, three, four, five):
self.assertEqual(one, 0x80)
self.assertEqual(two, 0x84)
self.assertEqual(three, 0x88)
self.assertEqual(four, 0x8c)
self.assertEqual(five, 0x90)
return 3
abi = I386StdcallAbi(cpu)
abi.invoke(test)
self.assertEquals(cpu.EAX, 3)
self.assertEquals(base + bwidth * 5, cpu.ESP)
self.assertEquals(cpu.EIP, 0x1234)
def test_i386_stdcall_concretize(self):
cpu = self._cpu_x86
bwidth = cpu.address_bit_size / 8
self.assertEqual(cpu.read_int(cpu.ESP), 0x80)
cpu.push(0x1234, cpu.address_bit_size)
eip = 0xDEADBEEF
base = cpu.ESP
cpu.EIP = eip
def test(one, two, three, four, five):
raise ConcretizeArgument(2)
abi = I386StdcallAbi(cpu)
with self.assertRaises(ConcretizeMemory) as cr:
abi.invoke(test)
# Make sure ESP hasn't changed if exception was raised
self.assertEquals(base, cpu.ESP)
# Make sure EIP hasn't changed (i.e. return value wasn't popped)
self.assertEquals(cpu.EIP, eip)
def test_i386_cdecl_concretize(self):
cpu = self._cpu_x86
base = cpu.ESP
prev_eax = 0xcc
cpu.EAX = prev_eax
self.assertEqual(cpu.read_int(cpu.ESP), 0x80)
cpu.push(0x1234, cpu.address_bit_size)
def test(one, two, three, four, five):
raise ConcretizeArgument(0) # 0x1068
return 3
with self.assertRaises(ConcretizeMemory) as cr:
cpu.func_abi.invoke(test)
# Make sure we're concretizing
self.assertEquals(cr.exception.address, 0x1080)
# Make sure eax is unchanged
self.assertEquals(cpu.EAX, prev_eax)
# Make sure EIP wasn't popped
self.assertEquals(base, cpu.ESP+4)
self.assertNotEquals(cpu.EIP, 0x1234)
def test_i386_vararg(self):
cpu = self._cpu_x86
cpu.push(3, cpu.address_bit_size)
cpu.push(2, cpu.address_bit_size)
cpu.push(1, cpu.address_bit_size)
# save return
cpu.push(0x1234, cpu.address_bit_size)
def test(params):
for val, idx in zip(params, range(1, 4)):
self.assertEqual(val, idx)
cpu.func_abi.invoke(test, varargs=True)
self.assertEquals(cpu.EIP, 0x1234)
def test_amd64_basic_funcall(self):
cpu = self._cpu_x64
cpu.RDI = 1
cpu.RSI = 2
cpu.RDX = 3
cpu.RCX = 4
cpu.R8 = 5
cpu.R9 = 6
cpu.push(0x1234, cpu.address_bit_size)
def test(one, two, three, four, five, six):
self.assertEqual(one, 1)
self.assertEqual(two, 2)
self.assertEqual(three, 3)
self.assertEqual(four, 4)
self.assertEqual(five, 5)
self.assertEqual(six, 6)
cpu.func_abi.invoke(test)
self.assertEqual(cpu.RIP, 0x1234)
def test_amd64_reg_mem_funcall(self):
cpu = self._cpu_x64
cpu.RDI = 1
cpu.RSI = 2
cpu.RDX = 3
cpu.RCX = 4
cpu.R8 = 5
cpu.R9 = 6
cpu.push(0x1234, cpu.address_bit_size)
def test(one, two, three, four, five, six, seven, eight):
self.assertEqual(one, 1)
self.assertEqual(two, 2)
self.assertEqual(three, 3)
self.assertEqual(four, 4)
self.assertEqual(five, 5)
self.assertEqual(six, 6)
self.assertEqual(seven, 0x80)
self.assertEqual(eight, 0x88)
cpu.func_abi.invoke(test)
self.assertEqual(cpu.RIP, 0x1234)
def test_amd64_basic_funcall_concretize(self):
cpu = self._cpu_x64
cpu.push(0x1234, cpu.address_bit_size)
def test(one, two, three, four, five, six):
raise ConcretizeArgument(0)
with self.assertRaises(ConcretizeRegister) as cr:
cpu.func_abi.invoke(test)
# Should not update RIP
self.assertNotEqual(cpu.RIP, 0x1234)
self.assertEquals(cr.exception.reg_name, 'RDI')
def test_amd64_vararg(self):
cpu = self._cpu_x64
cpu.RDI = 0
cpu.RSI = 1
cpu.RDX = 2
# save return
cpu.push(0x1234, cpu.address_bit_size)
def test(params):
for val, idx in zip(params, range(3)):
self.assertEqual(val, idx)
cpu.func_abi.invoke(test, varargs=True)
self.assertEquals(cpu.RIP, 0x1234)
def test_i386_syscall(self):
cpu = self._cpu_x86
cpu.EAX = 5
for idx, reg in enumerate(['EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'EBP']):
cpu.write_register(reg, idx)
def test(one, two, three, four, five, six):
self.assertEqual(one, 0)
self.assertEqual(two, 1)
self.assertEqual(three, 2)
self.assertEqual(four, 3)
self.assertEqual(five, 4)
self.assertEqual(six, 5)
return 34
self.assertEqual(cpu.syscall_abi.syscall_number(), 5)
cpu.syscall_abi.invoke(test)
self.assertEqual(cpu.EAX, 34)
def test_amd64_syscall(self):
cpu = self._cpu_x64
cpu.RAX = 5
for idx, reg in enumerate(['RDI', 'RSI', 'RDX', 'R10', 'R8', 'R9']):
cpu.write_register(reg, idx)
def test(one, two, three, four, five, six):
self.assertEqual(one, 0)
self.assertEqual(two, 1)
self.assertEqual(three, 2)
self.assertEqual(four, 3)
self.assertEqual(five, 4)
self.assertEqual(six, 5)
return 34
self.assertEqual(cpu.syscall_abi.syscall_number(), 5)
cpu.syscall_abi.invoke(test)
self.assertEqual(cpu.RAX, 34)
def test_test_prefix(self):
cpu = self._cpu_x86
cpu.push(2, cpu.address_bit_size)
cpu.push(0x1234, cpu.address_bit_size)
def test(prefix, extracted):
self.assertEquals(prefix, 1)
self.assertEquals(extracted, 2)
cpu.func_abi.invoke(test, prefix_args=(1,))
self.assertEquals(cpu.EIP, 0x1234)
def test_fail_concretize_prefix_arg(self):
cpu = self._cpu_x86
def test(prefix, extracted):
raise ConcretizeArgument(0)
with self.assertRaises(AssertionError) as cr:
cpu.func_abi.invoke(test, prefix_args=(1,))
def test_funcall_method(self):
cpu = self._cpu_x86
cpu.push(2, cpu.address_bit_size)
cpu.push(1, cpu.address_bit_size)
cpu.push(0x1234, cpu.address_bit_size)
class Kls(object):
def method(self, a, b):
return a+b
obj = Kls()
result = cpu.func_abi.invoke(obj.method)
self.assertEquals(result, 3)