Support additional API on Python 3 bindings (#2016)

* Styling and commets fixes

* Add errno API support

* Improve OOP approach by adjusting the way reg types are selected

* Leverage new approach to deduplicate reg_read and reg_write code

* Adjust reg_read_batch

* Add support for reg_write_batch

* Adjust x86 MSR accessors

* Turn asserts into descriptive exceptions

* Improve comments and styling

* Fix ARM memcpy neon regression test

* Modify canonicals import

* Introduce ARM CP reg accessors
This commit is contained in:
Eli
2024-10-06 18:14:03 +03:00
committed by GitHub
parent 05e29b4507
commit ac4872be4c
6 changed files with 404 additions and 225 deletions

View File

@@ -2,7 +2,7 @@
""" """
# @author elicn # @author elicn
from typing import Any, Tuple from typing import Tuple, Type
import ctypes import ctypes
@@ -10,7 +10,7 @@ import ctypes
from unicorn import arm_const as const from unicorn import arm_const as const
# newly introduced unicorn imports # newly introduced unicorn imports
from ..unicorn import Uc from ..unicorn import Uc, check_maxbits
from .types import UcTupledReg, UcReg128 from .types import UcTupledReg, UcReg128
ARMCPReg = Tuple[int, int, int, int, int, int, int, int] ARMCPReg = Tuple[int, int, int, int, int, int, int, int]
@@ -40,46 +40,71 @@ class UcAArch32(Uc):
"""Unicorn subclass for ARM architecture. """Unicorn subclass for ARM architecture.
""" """
REG_RANGE_CP = (const.UC_ARM_REG_CP_REG,)
REG_RANGE_Q = range(const.UC_ARM_REG_Q0, const.UC_ARM_REG_Q15 + 1) REG_RANGE_Q = range(const.UC_ARM_REG_Q0, const.UC_ARM_REG_Q15 + 1)
@staticmethod @classmethod
def __select_reg_class(reg_id: int): def _select_reg_class(cls, reg_id: int) -> Type:
"""Select class for special architectural registers. """Select the appropriate class for the specified architectural register.
""" """
reg_class = ( reg_class = (
(UcAArch32.REG_RANGE_Q, UcReg128), (UcAArch32.REG_RANGE_CP, UcRegCP),
(UcAArch32.REG_RANGE_Q, UcReg128)
) )
return next((cls for rng, cls in reg_class if reg_id in rng), None) return next((c for rng, c in reg_class if reg_id in rng), cls._DEFAULT_REGTYPE)
def reg_read(self, reg_id: int, aux: Any = None): # to learn more about accessing aarch32 coprocessor registers, refer to:
# select register class for special cases # https://developer.arm.com/documentation/ddi0601/latest/AArch32-Registers
reg_cls = UcAArch32.__select_reg_class(reg_id)
if reg_cls is None: def cpr_read(self, coproc: int, opc1: int, crn: int, crm: int, opc2: int, el: int, is_64: bool) -> int:
if reg_id == const.UC_ARM_REG_CP_REG: """Read a coprocessor register value.
return self._reg_read(reg_id, UcRegCP, *aux)
else: Args:
# fallback to default reading method coproc : coprocessor to access, value varies between 0 and 15
return super().reg_read(reg_id, aux) opc1 : opcode 1, value varies between 0 and 7
crn : coprocessor register to access (CRn), value varies between 0 and 15
crm : additional coprocessor register to access (CRm), value varies between 0 and 15
opc2 : opcode 2, value varies between 0 and 7
el : the exception level the coprocessor register belongs to, value varies between 0 and 3
is_64 : indicates whether this is a 64-bit register
return self._reg_read(reg_id, reg_cls) Returns: value of coprocessor register
"""
def reg_write(self, reg_id: int, value) -> None: assert check_maxbits(coproc, 4)
# select register class for special cases assert check_maxbits(opc1, 3)
reg_cls = UcAArch32.__select_reg_class(reg_id) assert check_maxbits(crn, 4)
assert check_maxbits(crm, 4)
assert check_maxbits(opc2, 3)
assert check_maxbits(el, 2) # note that unicorn currently supports only EL0 and EL1
if reg_cls is None: return self.reg_read(const.UC_ARM_REG_CP_REG, (coproc, int(is_64), el, crn, crm, opc1, opc2))
if reg_id == const.UC_ARM_REG_CP_REG:
self._reg_write(reg_id, UcRegCP, value)
else: def cpr_write(self, coproc: int, opc1: int, crn: int, crm: int, opc2: int, el: int, is_64: bool, value: int) -> None:
# fallback to default writing method """Write a coprocessor register value.
super().reg_write(reg_id, value)
Args:
coproc : coprocessor to access, value varies between 0 and 15
opc1 : opcode 1, value varies between 0 and 7
crn : coprocessor register to access (CRn), value varies between 0 and 15
crm : additional coprocessor register to access (CRm), value varies between 0 and 15
opc2 : opcode 2, value varies between 0 and 7
el : the exception level the coprocessor register belongs to, value varies between 0 and 3
is_64 : indicates whether this is a 64-bit register
value : value to write
"""
assert check_maxbits(coproc, 4)
assert check_maxbits(opc1, 3)
assert check_maxbits(crn, 4)
assert check_maxbits(crm, 4)
assert check_maxbits(opc2, 3)
assert check_maxbits(el, 2) # note that unicorn currently supports only EL0 and EL1
self.reg_write(const.UC_ARM_REG_CP_REG, (coproc, int(is_64), el, crn, crm, opc1, opc2, value))
else:
self._reg_write(reg_id, reg_cls, value)
__all__ = ['UcRegCP', 'UcAArch32'] __all__ = ['UcRegCP', 'UcAArch32']

View File

@@ -2,7 +2,7 @@
""" """
# @author elicn # @author elicn
from typing import Any, Callable, NamedTuple, Tuple from typing import Any, Callable, NamedTuple, Tuple, Type
import ctypes import ctypes
@@ -11,7 +11,7 @@ from unicorn import arm64_const as const
from unicorn.unicorn_const import UC_ERR_ARG, UC_HOOK_INSN from unicorn.unicorn_const import UC_ERR_ARG, UC_HOOK_INSN
# newly introduced unicorn imports # newly introduced unicorn imports
from ..unicorn import Uc, UcError, uccallback from ..unicorn import Uc, UcError, uccallback, check_maxbits
from .types import uc_engine, UcTupledReg, UcReg128 from .types import uc_engine, UcTupledReg, UcReg128
ARM64CPReg = Tuple[int, int, int, int, int, int] ARM64CPReg = Tuple[int, int, int, int, int, int]
@@ -41,6 +41,8 @@ class UcAArch64(Uc):
"""Unicorn subclass for ARM64 architecture. """Unicorn subclass for ARM64 architecture.
""" """
REG_RANGE_CP = (const.UC_ARM64_REG_CP_REG,)
REG_RANGE_Q = range(const.UC_ARM64_REG_Q0, const.UC_ARM64_REG_Q31 + 1) REG_RANGE_Q = range(const.UC_ARM64_REG_Q0, const.UC_ARM64_REG_Q31 + 1)
REG_RANGE_V = range(const.UC_ARM64_REG_V0, const.UC_ARM64_REG_V31 + 1) REG_RANGE_V = range(const.UC_ARM64_REG_V0, const.UC_ARM64_REG_V31 + 1)
@@ -85,45 +87,62 @@ class UcAArch64(Uc):
return getattr(self, '_Uc__do_hook_add')(htype, fptr, begin, end, insn) return getattr(self, '_Uc__do_hook_add')(htype, fptr, begin, end, insn)
@staticmethod @classmethod
def __select_reg_class(reg_id: int): def _select_reg_class(cls, reg_id: int) -> Type:
"""Select class for special architectural registers. """Select the appropriate class for the specified architectural register.
""" """
reg_class = ( reg_class = (
(UcAArch64.REG_RANGE_Q, UcReg128), (UcAArch64.REG_RANGE_CP, UcRegCP64),
(UcAArch64.REG_RANGE_V, UcReg128) (UcAArch64.REG_RANGE_Q, UcReg128),
(UcAArch64.REG_RANGE_V, UcReg128)
) )
return next((cls for rng, cls in reg_class if reg_id in rng), None) return next((c for rng, c in reg_class if reg_id in rng), cls._DEFAULT_REGTYPE)
def reg_read(self, reg_id: int, aux: Any = None): # to learn more about accessing aarch64 coprocessor registers, refer to:
# select register class for special cases # https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers
reg_cls = UcAArch64.__select_reg_class(reg_id)
if reg_cls is None: def cpr_read(self, op0: int, op1: int, crn: int, crm: int, op2: int) -> int:
if reg_id == const.UC_ARM64_REG_CP_REG: """Read a coprocessor register value.
return self._reg_read(reg_id, UcRegCP64, *aux)
else: Args:
# fallback to default reading method op0 : opcode 0, value varies between 0 and 3
return super().reg_read(reg_id, aux) op1 : opcode 1, value varies between 0 and 7
crn : coprocessor register to access (CRn), value varies between 0 and 15
crm : additional coprocessor register to access (CRm), value varies between 0 and 15
op2 : opcode 2, value varies between 0 and 7
return self._reg_read(reg_id, reg_cls) Returns: value of coprocessor register
"""
def reg_write(self, reg_id: int, value) -> None: assert check_maxbits(op0, 2)
# select register class for special cases assert check_maxbits(op1, 3)
reg_cls = UcAArch64.__select_reg_class(reg_id) assert check_maxbits(crn, 4)
assert check_maxbits(crm, 4)
assert check_maxbits(op2, 3)
if reg_cls is None: return self.reg_read(const.UC_ARM64_REG_CP_REG, (crn, crm, op0, op1, op2))
if reg_id == const.UC_ARM64_REG_CP_REG:
self._reg_write(reg_id, UcRegCP64, value)
else: def cpr_write(self, op0: int, op1: int, crn: int, crm: int, op2: int, value: int) -> None:
# fallback to default writing method """Write a coprocessor register value.
super().reg_write(reg_id, value)
Args:
op0 : opcode 0, value varies between 0 and 3
op1 : opcode 1, value varies between 0 and 7
crn : coprocessor register to access (CRn), value varies between 0 and 15
crm : additional coprocessor register to access (CRm), value varies between 0 and 15
op2 : opcode 2, value varies between 0 and 7
value : value to write
"""
assert check_maxbits(op0, 2)
assert check_maxbits(op1, 3)
assert check_maxbits(crn, 4)
assert check_maxbits(crm, 4)
assert check_maxbits(op2, 3)
self.reg_write(const.UC_ARM64_REG_CP_REG, (crn, crm, op0, op1, op2, value))
else:
self._reg_write(reg_id, reg_cls, value)
__all__ = ['UcRegCP64', 'UcAArch64'] __all__ = ['UcRegCP64', 'UcAArch64']

View File

@@ -2,7 +2,7 @@
""" """
# @author elicn # @author elicn
from typing import Any, Callable, Sequence, Tuple from typing import Any, Callable, Tuple, Type
import ctypes import ctypes
@@ -64,6 +64,8 @@ class UcIntel(Uc):
"""Unicorn subclass for Intel architecture. """Unicorn subclass for Intel architecture.
""" """
REG_RANGE_MSR = (const.UC_X86_REG_MSR,)
REG_RANGE_MMR = ( REG_RANGE_MMR = (
const.UC_X86_REG_IDTR, const.UC_X86_REG_IDTR,
const.UC_X86_REG_GDTR, const.UC_X86_REG_GDTR,
@@ -127,12 +129,13 @@ class UcIntel(Uc):
return getattr(self, '_Uc__do_hook_add')(htype, fptr, begin, end, insn) return getattr(self, '_Uc__do_hook_add')(htype, fptr, begin, end, insn)
@staticmethod @classmethod
def __select_reg_class(reg_id: int): def _select_reg_class(cls, reg_id: int) -> Type:
"""Select class for special architectural registers. """Select the appropriate class for the specified architectural register.
""" """
reg_class = ( reg_class = (
(UcIntel.REG_RANGE_MSR, UcRegMSR),
(UcIntel.REG_RANGE_MMR, UcRegMMR), (UcIntel.REG_RANGE_MMR, UcRegMMR),
(UcIntel.REG_RANGE_FP, UcRegFPR), (UcIntel.REG_RANGE_FP, UcRegFPR),
(UcIntel.REG_RANGE_XMM, UcReg128), (UcIntel.REG_RANGE_XMM, UcReg128),
@@ -140,54 +143,28 @@ class UcIntel(Uc):
(UcIntel.REG_RANGE_ZMM, UcReg512) (UcIntel.REG_RANGE_ZMM, UcReg512)
) )
return next((cls for rng, cls in reg_class if reg_id in rng), None) return next((c for rng, c in reg_class if reg_id in rng), cls._DEFAULT_REGTYPE)
def reg_read(self, reg_id: int, aux: Any = None):
# select register class for special cases
reg_cls = UcIntel.__select_reg_class(reg_id)
if reg_cls is None:
# backward compatibility: msr read through reg_read
if reg_id == const.UC_X86_REG_MSR:
if type(aux) is not int:
raise UcError(UC_ERR_ARG)
value = self.msr_read(aux)
else:
value = super().reg_read(reg_id, aux)
else:
value = self._reg_read(reg_id, reg_cls)
return value
def reg_write(self, reg_id: int, value) -> None:
# select register class for special cases
reg_cls = UcIntel.__select_reg_class(reg_id)
if reg_cls is None:
# backward compatibility: msr write through reg_write
if reg_id == const.UC_X86_REG_MSR:
if type(value) is not tuple or len(value) != 2:
raise UcError(UC_ERR_ARG)
self.msr_write(*value)
return
super().reg_write(reg_id, value)
else:
self._reg_write(reg_id, reg_cls, value)
def msr_read(self, msr_id: int) -> int: def msr_read(self, msr_id: int) -> int:
return self._reg_read(const.UC_X86_REG_MSR, UcRegMSR, msr_id) """Read a model-specific register.
Args:
msr_id: MSR index
Returns: MSR value
"""
return self.reg_read(const.UC_X86_REG_MSR, msr_id)
def msr_write(self, msr_id: int, value: int) -> None: def msr_write(self, msr_id: int, value: int) -> None:
self._reg_write(const.UC_X86_REG_MSR, UcRegMSR, (msr_id, value)) """Write to a model-specific register.
def reg_read_batch(self, reg_ids: Sequence[int]) -> Tuple: Args:
reg_types = [UcIntel.__select_reg_class(rid) or self._DEFAULT_REGTYPE for rid in reg_ids] msr_id: MSR index
value: new MSR value
"""
return self._reg_read_batch(reg_ids, reg_types) self.reg_write(const.UC_X86_REG_MSR, (msr_id, value))
__all__ = ['UcRegMMR', 'UcRegMSR', 'UcRegFPR', 'UcIntel'] __all__ = ['UcRegMMR', 'UcRegMSR', 'UcRegFPR', 'UcIntel']

View File

@@ -3,7 +3,7 @@
# @author elicn # @author elicn
from abc import abstractmethod from abc import abstractmethod
from typing import Generic, Tuple, TypeVar from typing import Any, Generic, Tuple, TypeVar
import ctypes import ctypes
@@ -24,7 +24,7 @@ class UcReg(ctypes.Structure):
@property @property
@abstractmethod @abstractmethod
def value(self): def value(self) -> Any:
"""Get register value. """Get register value.
""" """
@@ -32,7 +32,7 @@ class UcReg(ctypes.Structure):
@classmethod @classmethod
@abstractmethod @abstractmethod
def from_value(cls, value): def from_value(cls, value) -> 'UcReg':
"""Create a register instance from a given value. """Create a register instance from a given value.
""" """
@@ -52,7 +52,11 @@ class UcTupledReg(UcReg, Generic[VT]):
@classmethod @classmethod
def from_value(cls, value: VT): def from_value(cls, value: VT):
assert type(value) is tuple and len(value) == len(cls._fields_) if not isinstance(value, tuple):
raise TypeError(f'got {type(value).__name__} while expecting a tuple')
if len(value) != len(cls._fields_):
raise TypeError(f'got {len(value)} elements while expecting {len(cls._fields_)}')
return cls(*value) return cls(*value)
@@ -72,7 +76,8 @@ class UcLargeReg(UcReg):
@classmethod @classmethod
def from_value(cls, value: int): def from_value(cls, value: int):
assert type(value) is int if not isinstance(value, int):
raise TypeError(f'got {type(value).__name__} while expecting an integer')
mask = (1 << 64) - 1 mask = (1 << 64) - 1
size = cls._fields_[0][1]._length_ size = cls._fields_[0][1]._length_

View File

@@ -3,7 +3,7 @@ based on Nguyen Anh Quynnh's work
""" """
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Mapping, MutableMapping, Optional, Sequence, Tuple, Type, TypeVar from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, Optional, Sequence, Tuple, Type, TypeVar, Union
import ctypes import ctypes
import functools import functools
@@ -43,7 +43,6 @@ def __load_uc_lib() -> ctypes.CDLL:
import inspect import inspect
import os import os
import sys import sys
loaded_dlls = set() loaded_dlls = set()
@@ -91,26 +90,19 @@ def __load_uc_lib() -> ctypes.CDLL:
# - we can get the path to the local libraries by parsing our filename # - we can get the path to the local libraries by parsing our filename
# - global load # - global load
# - python's lib directory # - python's lib directory
canonicals = []
if sys.version_info.minor >= 12:
try:
from importlib import resources from importlib import resources
canonicals.append(
resources.files("unicorn") / 'lib' canonicals = resources.files('unicorn') / 'lib'
) else:
except: import pkg_resources
try:
import pkg_resources canonicals = pkg_resources.resource_filename('unicorn', 'lib')
canonicals.append(
pkg_resources.resource_filename("unicorn", 'lib')
)
except:
# maybe importlib_resources, but ignore for now
pass
lib_locations = [ lib_locations = [
os.getenv('LIBUNICORN_PATH'), os.getenv('LIBUNICORN_PATH'),
] + canonicals + [ canonicals,
PurePath(inspect.getfile(__load_uc_lib)).parent / 'lib', PurePath(inspect.getfile(__load_uc_lib)).parent / 'lib',
'', '',
r'/usr/local/lib' if sys.platform == 'darwin' else r'/usr/lib64', r'/usr/local/lib' if sys.platform == 'darwin' else r'/usr/lib64',
@@ -171,6 +163,7 @@ def __set_lib_prototypes(lib: ctypes.CDLL) -> None:
__set_prototype('uc_reg_read', uc_err, uc_engine, ctypes.c_int, ctypes.c_void_p) __set_prototype('uc_reg_read', uc_err, uc_engine, ctypes.c_int, ctypes.c_void_p)
__set_prototype('uc_reg_write', uc_err, uc_engine, ctypes.c_int, ctypes.c_void_p) __set_prototype('uc_reg_write', uc_err, uc_engine, ctypes.c_int, ctypes.c_void_p)
__set_prototype('uc_reg_read_batch', uc_err, uc_engine, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_void_p), ctypes.c_int) __set_prototype('uc_reg_read_batch', uc_err, uc_engine, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_void_p), ctypes.c_int)
__set_prototype('uc_reg_write_batch', uc_err, uc_engine, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_void_p), ctypes.c_int)
__set_prototype('uc_mem_read', uc_err, uc_engine, ctypes.c_uint64, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t) __set_prototype('uc_mem_read', uc_err, uc_engine, ctypes.c_uint64, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t)
__set_prototype('uc_mem_write', uc_err, uc_engine, ctypes.c_uint64, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t) __set_prototype('uc_mem_write', uc_err, uc_engine, ctypes.c_uint64, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t)
__set_prototype('uc_emu_start', uc_err, uc_engine, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_size_t) __set_prototype('uc_emu_start', uc_err, uc_engine, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_size_t)
@@ -215,6 +208,19 @@ MMIO_READ_CFUNC = ctypes.CFUNCTYPE(ctypes.c_uint64, uc_engine, ctypes.c_uint64,
MMIO_WRITE_CFUNC = ctypes.CFUNCTYPE(None, uc_engine, ctypes.c_uint64, ctypes.c_uint, ctypes.c_uint64, ctypes.c_void_p) MMIO_WRITE_CFUNC = ctypes.CFUNCTYPE(None, uc_engine, ctypes.c_uint64, ctypes.c_uint, ctypes.c_uint64, ctypes.c_void_p)
def check_maxbits(value: int, nbits: int) -> bool:
"""Verify that a certain value may be represented with at most `nbits` bits.
Args:
value : numeric value to check
nbits : max number of bits allowed
Returns: `True` if `value` is represented by at most `nbits` bits, `False` otherwise
"""
return value & ~((1 << nbits) - 1) == 0
class UcError(Exception): class UcError(Exception):
"""Unicorn base exception. """Unicorn base exception.
@@ -310,7 +316,7 @@ def uccallback(uc: Uc, functype: Type[_CFP]):
def decorate(func) -> _CFP: def decorate(func) -> _CFP:
@functools.wraps(func) @functools.wraps(func)
def wrapper(handle: Uc, *args, **kwargs): def wrapper(handle: int, *args, **kwargs):
try: try:
return func(uc, *args, **kwargs) return func(uc, *args, **kwargs)
except Exception as e: except Exception as e:
@@ -334,47 +340,62 @@ class RegStateManager:
_DEFAULT_REGTYPE = ctypes.c_uint64 _DEFAULT_REGTYPE = ctypes.c_uint64
@classmethod
def _select_reg_class(cls, reg_id: int) -> Type:
"""An architecture-specific method that returns the appropriate
value type for a specified reg identifier. All rich `Uc` subclasses
are expected to implement their own
"""
return cls._DEFAULT_REGTYPE
def _do_reg_read(self, reg_id: int, reg_obj) -> int: def _do_reg_read(self, reg_id: int, reg_obj) -> int:
"""Private register read implementation. """Low level register read implementation.
Must be implemented by the mixin object Must be implemented by the mixin object
""" """
raise NotImplementedError raise NotImplementedError
def _do_reg_write(self, reg_id: int, reg_obj) -> int: def _do_reg_write(self, reg_id: int, reg_obj) -> int:
"""Private register write implementation. """Low level register write implementation.
Must be implemented by the mixin object Must be implemented by the mixin object
""" """
raise NotImplementedError raise NotImplementedError
def _do_reg_read_batch(self, reglist, vallist, count) -> int: def _do_reg_read_batch(self, reglist, vallist, count) -> int:
"""Private batch register read implementation. """Low level batch register read implementation.
Must be implemented by the mixin object Must be implemented by the mixin object
""" """
raise NotImplementedError raise NotImplementedError
def _do_reg_write_batch(self, reglist, count) -> int: def _do_reg_write_batch(self, reglist, vallist, count) -> int:
"""Private batch register write implementation. """Low level batch register write implementation.
Must be implemented by the mixin object Must be implemented by the mixin object
""" """
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def __get_reg_read_arg(regtype: Type, *args): def __get_reg_read_arg(reg_type: Type, aux: Any):
return regtype(*args) if aux is None:
return reg_type()
if isinstance(aux, tuple):
return reg_type(*aux)
return reg_type(aux)
@staticmethod @staticmethod
def __get_reg_write_arg(regtype: Type, value): def __get_reg_write_arg(reg_type: Type, value: Any):
return regtype.from_value(value) if issubclass(regtype, UcReg) else regtype(value) return reg_type.from_value(value) if issubclass(reg_type, UcReg) else reg_type(value)
def _reg_read(self, reg_id: int, regtype: Type, *args): def _reg_read(self, reg_id: int, reg_type: Type, aux: Any):
"""Register read helper method. """Register read helper method.
""" """
reg = self.__get_reg_read_arg(regtype, *args) reg = self.__get_reg_read_arg(reg_type, aux)
status = self._do_reg_read(reg_id, ctypes.byref(reg)) status = self._do_reg_read(reg_id, ctypes.byref(reg))
if status != uc.UC_ERR_OK: if status != uc.UC_ERR_OK:
@@ -382,25 +403,32 @@ class RegStateManager:
return reg.value return reg.value
def _reg_write(self, reg_id: int, regtype: Type, value) -> None: def _reg_write(self, reg_id: int, reg_type: Type, value: Any) -> None:
"""Register write helper method. """Register write helper method.
""" """
reg = self.__get_reg_write_arg(regtype, value) reg = self.__get_reg_write_arg(reg_type, value)
status = self._do_reg_write(reg_id, ctypes.byref(reg)) status = self._do_reg_write(reg_id, ctypes.byref(reg))
if status != uc.UC_ERR_OK: if status != uc.UC_ERR_OK:
raise UcError(status, reg_id) raise UcError(status, reg_id)
def _reg_read_batch(self, reg_ids: Sequence[int], reg_types: Sequence[Type]) -> Tuple: def _reg_read_batch(self, read_seq: Sequence[Tuple[int, Type, Any]]) -> Tuple:
"""Batch register read helper method. """Batch register read helper method.
Args:
read_seq: a sequence of 3-tuples containing register identifier, returned
value type and auxiliary data (or `None` if aux is not required)
Returns: a tuple of values
""" """
assert len(reg_ids) == len(reg_types) count = len(read_seq)
reg_ids = (rid for rid, _, _ in read_seq)
reg_info = ((rtype, aux) for _, rtype, aux in read_seq)
count = len(reg_ids)
reg_list = (ctypes.c_int * count)(*reg_ids) reg_list = (ctypes.c_int * count)(*reg_ids)
val_list = [rtype() for rtype in reg_types] val_list = [self.__get_reg_read_arg(rtype, aux) for rtype, aux in reg_info]
ptr_list = (ctypes.c_void_p * count)(*(ctypes.c_void_p(ctypes.addressof(elem)) for elem in val_list)) ptr_list = (ctypes.c_void_p * count)(*(ctypes.c_void_p(ctypes.addressof(elem)) for elem in val_list))
status = self._do_reg_read_batch(reg_list, ptr_list, ctypes.c_int(count)) status = self._do_reg_read_batch(reg_list, ptr_list, ctypes.c_int(count))
@@ -410,6 +438,30 @@ class RegStateManager:
return tuple(v.value for v in val_list) return tuple(v.value for v in val_list)
def _reg_write_batch(self, write_seq: Sequence[Tuple[int, Type, Any]]) -> None:
"""Batch register write helper method.
Args:
write_seq: a sequence of 3-tuples containing register identifier, value type and value
"""
count = len(write_seq)
reg_ids = (rid for rid, _, _ in write_seq)
reg_info = ((rtype, aux) for _, rtype, aux in write_seq)
reg_list = (ctypes.c_int * count)(*reg_ids)
val_list = [self.__get_reg_write_arg(rtype, rval) for rtype, rval in reg_info]
ptr_list = (ctypes.c_void_p * count)(*(ctypes.c_void_p(ctypes.addressof(elem)) for elem in val_list))
status = self._do_reg_write_batch(reg_list, ptr_list, ctypes.c_int(count))
if status != uc.UC_ERR_OK:
raise UcError(status)
#########################
# External facing API #
#########################
def reg_read(self, reg_id: int, aux: Any = None): def reg_read(self, reg_id: int, aux: Any = None):
"""Read architectural register value. """Read architectural register value.
@@ -422,7 +474,9 @@ class RegStateManager:
Raises: `UcError` in case of invalid register id or auxiliary data Raises: `UcError` in case of invalid register id or auxiliary data
""" """
return self._reg_read(reg_id, self._DEFAULT_REGTYPE) reg_type = self._select_reg_class(reg_id)
return self._reg_read(reg_id, reg_type, aux)
def reg_write(self, reg_id: int, value) -> None: def reg_write(self, reg_id: int, value) -> None:
"""Write to architectural register. """Write to architectural register.
@@ -434,34 +488,50 @@ class RegStateManager:
Raises: `UcError` in case of invalid register id or value format Raises: `UcError` in case of invalid register id or value format
""" """
self._reg_write(reg_id, self._DEFAULT_REGTYPE, value) reg_type = self._select_reg_class(reg_id)
def reg_read_batch(self, reg_ids: Sequence[int]) -> Tuple: self._reg_write(reg_id, reg_type, value)
"""Read a sequence of architectural registers.
def reg_read_batch(self, reg_data: Sequence[Union[int, Tuple[int, Any]]]) -> Tuple:
"""Read a sequence of architectural registers. This provides with faster means to
read multiple registers.
Args: Args:
reg_ids: a sequence of register identifiers (architecture-specific enumeration) reg_ids : a sequence of register identifiers (architecture-specific enumeration)
aux : a mapping of reg identifiers and auxiliary data, in case it is required
Returns: a tuple of registers values (register-specific format) Returns: a tuple of registers values (register-specific format)
Raises: `UcError` in case of invalid register id Raises: `UcError` in case of an invalid register id, or an invalid aux data for a
register that requires it
""" """
reg_types = [self._DEFAULT_REGTYPE for _ in range(len(reg_ids))] def __seq_tuple(elem: Union[int, Tuple[int, Any]]) -> Tuple[int, Type, Any]:
reg_id, reg_aux = elem if isinstance(elem, tuple) else (elem, None)
reg_type = self._select_reg_class(reg_id)
return self._reg_read_batch(reg_ids, reg_types) return (reg_id, reg_type, reg_aux)
def reg_write_batch(self, reg_info: Sequence[Tuple[int, Any]]) -> None: return self._reg_read_batch([__seq_tuple(elem) for elem in reg_data])
"""Write a sequece of architectural registers.
def reg_write_batch(self, reg_data: Sequence[Tuple[int, Any]]) -> None:
"""Write a sequece of architectural registers. This provides with faster means to
write multiple registers.
Args: Args:
regs_info: a sequence of tuples consisting of register identifiers and values reg_data: a sequence of register identifiers matched with their designated values
Raises: `UcError` in case of invalid register id or value format Raises: `UcError` in case of invalid register id or value format
""" """
# TODO def __seq_tuple(elem: Tuple[int, Any]) -> Tuple[int, Type, Any]:
... reg_id, reg_val = elem
reg_type = self._select_reg_class(reg_id)
return (reg_id, reg_type, reg_val)
self._reg_write_batch([__seq_tuple(elem) for elem in reg_data])
def ucsubclass(cls): def ucsubclass(cls):
"""Uc subclass decorator. """Uc subclass decorator.
@@ -591,8 +661,8 @@ class Uc(RegStateManager):
# we have to keep a reference to the callbacks so they do not get gc-ed # we have to keep a reference to the callbacks so they do not get gc-ed
# see: https://docs.python.org/3/library/ctypes.html#callback-functions # see: https://docs.python.org/3/library/ctypes.html#callback-functions
self._callbacks: MutableMapping[int, ctypes._FuncPointer] = {} self._callbacks: Dict[int, ctypes._FuncPointer] = {}
self._mmio_callbacks: MutableMapping[Tuple[int, int], Tuple[Optional[MMIO_READ_CFUNC], Optional[MMIO_WRITE_CFUNC]]] = {} self._mmio_callbacks: Dict[Tuple[int, int], Tuple[Optional[MMIO_READ_CFUNC], Optional[MMIO_WRITE_CFUNC]]] = {}
self._hook_exception: Optional[Exception] = None self._hook_exception: Optional[Exception] = None
@@ -617,6 +687,15 @@ class Uc(RegStateManager):
if status != uc.UC_ERR_OK: if status != uc.UC_ERR_OK:
raise UcError(status) raise UcError(status)
@property
def errno(self) -> int:
"""Get last error number.
Returns: error number (see: UC_ERR_*)
"""
return uclib.uc_errno(self._uch)
########################### ###########################
# Emulation controllers # # Emulation controllers #
########################### ###########################
@@ -680,6 +759,13 @@ class Uc(RegStateManager):
return uclib.uc_reg_read_batch(self._uch, reglist, vallist, count) return uclib.uc_reg_read_batch(self._uch, reglist, vallist, count)
def _do_reg_write_batch(self, reglist, vallist, count) -> int:
"""Private batch register write implementation.
Do not call directly.
"""
return uclib.uc_reg_write_batch(self._uch, reglist, vallist, count)
########################### ###########################
# Memory management # # Memory management #
########################### ###########################
@@ -767,20 +853,33 @@ class Uc(RegStateManager):
raise UcError(status) raise UcError(status)
def mmio_map(self, address: int, size: int, def mmio_map(self, address: int, size: int,
read_cb: Optional[UC_MMIO_READ_TYPE], user_data_read: Any, read_cb: Optional[UC_MMIO_READ_TYPE], read_ud: Any,
write_cb: Optional[UC_MMIO_WRITE_TYPE], user_data_write: Any) -> None: write_cb: Optional[UC_MMIO_WRITE_TYPE], write_ud: Any) -> None:
"""Map an MMIO range. This method binds a memory range to read and write accessors
to simulate a hardware device. Unicorn does not allocate memory to back this range.
Args:
address : range base address
size : range size (in bytes)
read_cb : read callback to invoke upon read access. if not specified, reads \
from the mmio range will be silently dropped
read_ud : optinal context object to pass on to the read callback
write_cb : write callback to invoke unpon a write access. if not specified, writes \
to the mmio range will be silently dropped
write_ud : optinal context object to pass on to the write callback
"""
@uccallback(self, MMIO_READ_CFUNC) @uccallback(self, MMIO_READ_CFUNC)
def __mmio_map_read_cb(uc: Uc, offset: int, size: int, key: int) -> int: def __mmio_map_read_cb(uc: Uc, offset: int, size: int, key: int) -> int:
assert read_cb is not None assert read_cb is not None
return read_cb(uc, offset, size, user_data_read) return read_cb(uc, offset, size, read_ud)
@uccallback(self, MMIO_WRITE_CFUNC) @uccallback(self, MMIO_WRITE_CFUNC)
def __mmio_map_write_cb(uc: Uc, offset: int, size: int, value: int, key: int) -> None: def __mmio_map_write_cb(uc: Uc, offset: int, size: int, value: int, key: int) -> None:
assert write_cb is not None assert write_cb is not None
write_cb(uc, offset, size, value, user_data_write) write_cb(uc, offset, size, value, write_ud)
read_cb_fptr = read_cb and __mmio_map_read_cb read_cb_fptr = read_cb and __mmio_map_read_cb
write_cb_fptr = write_cb and __mmio_map_write_cb write_cb_fptr = write_cb and __mmio_map_write_cb
@@ -860,7 +959,7 @@ class Uc(RegStateManager):
def __do_hook_add(self, htype: int, fptr: ctypes._FuncPointer, begin: int, end: int, *args: ctypes.c_int) -> int: def __do_hook_add(self, htype: int, fptr: ctypes._FuncPointer, begin: int, end: int, *args: ctypes.c_int) -> int:
handle = uc_hook_h() handle = uc_hook_h()
# TODO: we do not need a callback counter to reference the callback and user data anymore, # we do not need a callback counter to reference the callback and user data anymore,
# so just pass a dummy value. that value will become the unused 'key' argument # so just pass a dummy value. that value will become the unused 'key' argument
dummy = 0 dummy = 0
@@ -905,7 +1004,7 @@ class Uc(RegStateManager):
def __hook_intr_cb(uc: Uc, intno: int, key: int): def __hook_intr_cb(uc: Uc, intno: int, key: int):
callback(uc, intno, user_data) callback(uc, intno, user_data)
return __hook_intr_cb, return (__hook_intr_cb,)
def __hook_insn(): def __hook_insn():
# each arch is expected to overload hook_add and implement this handler on their own. # each arch is expected to overload hook_add and implement this handler on their own.
@@ -918,35 +1017,35 @@ class Uc(RegStateManager):
def __hook_code_cb(uc: Uc, address: int, size: int, key: int): def __hook_code_cb(uc: Uc, address: int, size: int, key: int):
callback(uc, address, size, user_data) callback(uc, address, size, user_data)
return __hook_code_cb, return (__hook_code_cb,)
def __hook_invalid_mem(): def __hook_invalid_mem():
@uccallback(self, HOOK_MEM_INVALID_CFUNC) @uccallback(self, HOOK_MEM_INVALID_CFUNC)
def __hook_mem_invalid_cb(uc: Uc, access: int, address: int, size: int, value: int, key: int) -> bool: def __hook_mem_invalid_cb(uc: Uc, access: int, address: int, size: int, value: int, key: int) -> bool:
return callback(uc, access, address, size, value, user_data) return callback(uc, access, address, size, value, user_data)
return __hook_mem_invalid_cb, return (__hook_mem_invalid_cb,)
def __hook_mem(): def __hook_mem():
@uccallback(self, HOOK_MEM_ACCESS_CFUNC) @uccallback(self, HOOK_MEM_ACCESS_CFUNC)
def __hook_mem_access_cb(uc: Uc, access: int, address: int, size: int, value: int, key: int) -> None: def __hook_mem_access_cb(uc: Uc, access: int, address: int, size: int, value: int, key: int) -> None:
callback(uc, access, address, size, value, user_data) callback(uc, access, address, size, value, user_data)
return __hook_mem_access_cb, return (__hook_mem_access_cb,)
def __hook_invalid_insn(): def __hook_invalid_insn():
@uccallback(self, HOOK_INSN_INVALID_CFUNC) @uccallback(self, HOOK_INSN_INVALID_CFUNC)
def __hook_insn_invalid_cb(uc: Uc, key: int) -> bool: def __hook_insn_invalid_cb(uc: Uc, key: int) -> bool:
return callback(uc, user_data) return callback(uc, user_data)
return __hook_insn_invalid_cb, return (__hook_insn_invalid_cb,)
def __hook_edge_gen(): def __hook_edge_gen():
@uccallback(self, HOOK_EDGE_GEN_CFUNC) @uccallback(self, HOOK_EDGE_GEN_CFUNC)
def __hook_edge_gen_cb(uc: Uc, cur: ctypes._Pointer[uc_tb], prev: ctypes._Pointer[uc_tb], key: int): def __hook_edge_gen_cb(uc: Uc, cur: ctypes._Pointer[uc_tb], prev: ctypes._Pointer[uc_tb], key: int):
callback(uc, cur.contents, prev.contents, user_data) callback(uc, cur.contents, prev.contents, user_data)
return __hook_edge_gen_cb, return (__hook_edge_gen_cb,)
def __hook_tcg_opcode(): def __hook_tcg_opcode():
@uccallback(self, HOOK_TCG_OPCODE_CFUNC) @uccallback(self, HOOK_TCG_OPCODE_CFUNC)
@@ -956,9 +1055,9 @@ class Uc(RegStateManager):
opcode = ctypes.c_uint64(aux1) opcode = ctypes.c_uint64(aux1)
flags = ctypes.c_uint64(aux2) flags = ctypes.c_uint64(aux2)
return __hook_tcg_op_cb, opcode, flags return (__hook_tcg_op_cb, opcode, flags)
handlers: Mapping[int, Callable[[], Tuple]] = { handlers: Dict[int, Callable[[], Tuple]] = {
uc.UC_HOOK_INTR : __hook_intr, uc.UC_HOOK_INTR : __hook_intr,
uc.UC_HOOK_INSN : __hook_insn, uc.UC_HOOK_INSN : __hook_insn,
uc.UC_HOOK_CODE : __hook_code, uc.UC_HOOK_CODE : __hook_code,
@@ -1034,6 +1133,11 @@ class Uc(RegStateManager):
return result.value return result.value
def context_save(self) -> UcContext: def context_save(self) -> UcContext:
"""Save Unicorn instance internal context.
Returns: unicorn context instance
"""
context = UcContext(self._uch, self._arch, self._mode) context = UcContext(self._uch, self._arch, self._mode)
status = uclib.uc_context_save(self._uch, context.context) status = uclib.uc_context_save(self._uch, context.context)
@@ -1043,12 +1147,24 @@ class Uc(RegStateManager):
return context return context
def context_update(self, context: UcContext) -> None: def context_update(self, context: UcContext) -> None:
"""Update Unicorn instance internal context.
Args:
context : unicorn context instance to copy data from
"""
status = uclib.uc_context_save(self._uch, context.context) status = uclib.uc_context_save(self._uch, context.context)
if status != uc.UC_ERR_OK: if status != uc.UC_ERR_OK:
raise UcError(status) raise UcError(status)
def context_restore(self, context: UcContext) -> None: def context_restore(self, context: UcContext) -> None:
"""Overwrite Unicorn instance internal context.
Args:
context : unicorn context instance to copy data from
"""
status = uclib.uc_context_restore(self._uch, context.context) status = uclib.uc_context_restore(self._uch, context.context)
if status != uc.UC_ERR_OK: if status != uc.UC_ERR_OK:
@@ -1056,8 +1172,8 @@ class Uc(RegStateManager):
@staticmethod @staticmethod
def __ctl_encode(ctl: int, op: int, nargs: int) -> int: def __ctl_encode(ctl: int, op: int, nargs: int) -> int:
assert nargs and (nargs & ~0b1111) == 0, f'nargs must not exceed value of 15 (got {nargs})' assert nargs and check_maxbits(nargs, 4), f'nargs must not exceed value of 15 (got {nargs})'
assert op and (op & ~0b11) == 0, f'op must not exceed value of 3 (got {op})' assert op and check_maxbits(op, 2), f'op must not exceed value of 3 (got {op})'
return (op << 30) | (nargs << 26) | ctl return (op << 30) | (nargs << 26) | ctl
@@ -1178,6 +1294,9 @@ class Uc(RegStateManager):
class UcContext(RegStateManager): class UcContext(RegStateManager):
"""Unicorn internal context.
"""
def __init__(self, h, arch: int, mode: int): def __init__(self, h, arch: int, mode: int):
self._context = uc_context() self._context = uc_context()
self._size = uclib.uc_context_size(h) self._size = uclib.uc_context_size(h)
@@ -1193,18 +1312,31 @@ class UcContext(RegStateManager):
@property @property
def context(self): def context(self):
"""Underlying context data.
Normally this property should not be accessed directly.
"""
return self._context return self._context
@property @property
def size(self) -> int: def size(self) -> int:
"""Underlying context data size.
"""
return self._size return self._size
@property @property
def arch(self) -> int: def arch(self) -> int:
"""Get emulated architecture (see UC_ARCH_* constants).
"""
return self._arch return self._arch
@property @property
def mode(self) -> int: def mode(self) -> int:
"""Get emulated processor mode (see UC_MODE_* constants).
"""
return self._mode return self._mode
# RegStateManager mixin method implementation # RegStateManager mixin method implementation
@@ -1227,11 +1359,17 @@ class UcContext(RegStateManager):
return uclib.uc_context_reg_read_batch(self._context, reglist, vallist, count) return uclib.uc_context_reg_read_batch(self._context, reglist, vallist, count)
def _do_reg_write_batch(self, reglist, vallist, count) -> int:
"""Private batch register write implementation.
"""
return uclib.uc_context_reg_write_batch(self._context, reglist, vallist, count)
# Make UcContext picklable # Make UcContext picklable
def __getstate__(self): def __getstate__(self) -> Tuple[bytes, int, int, int]:
return bytes(self), self.size, self.arch, self.mode return bytes(self), self.size, self.arch, self.mode
def __setstate__(self, state) -> None: def __setstate__(self, state: Tuple[bytes, int, int, int]) -> None:
context, size, arch, mode = state context, size, arch, mode = state
self._context = ctypes.cast(ctypes.create_string_buffer(context, size), uc_context) self._context = ctypes.cast(ctypes.create_string_buffer(context, size), uc_context)
@@ -1243,7 +1381,7 @@ class UcContext(RegStateManager):
self._to_free = False self._to_free = False
def __bytes__(self) -> bytes: def __bytes__(self) -> bytes:
return ctypes.string_at(self.context, self.size) return ctypes.string_at(self._context, self._size)
def __del__(self) -> None: def __del__(self) -> None:
# We need this property since we shouldn't free it if the object is constructed from pickled bytes. # We need this property since we shouldn't free it if the object is constructed from pickled bytes.
@@ -1257,4 +1395,3 @@ UC_MMIO_WRITE_TYPE = Callable[[Uc, int, int, int, Any], None]
__all__ = ['Uc', 'UcContext', 'ucsubclass', 'UcError', 'uc_version', 'version_bind', 'uc_arch_supported', 'debug'] __all__ = ['Uc', 'UcContext', 'ucsubclass', 'UcError', 'uc_version', 'version_bind', 'uc_arch_supported', 'debug']

View File

@@ -1,52 +1,68 @@
from unicorn import * from unicorn import *
from unicorn.arm_const import * from unicorn.arm_const import *
# .text:0001F894 ADD PC, PC, R3
# .text:0001F898 ; --------------------------------------------------------------------------- SHELLCODE = bytes.fromhex(
# .text:0001F898 VLD1.8 {D0}, [R1]! '03 f0 8f e0' # 0001F894 ADD PC, PC, R3
# .text:0001F89C VST1.8 {D0}, [R12]! '0d 07 21 f4' # 0001F898 VLD1.8 {D0}, [R1]!
# .text:0001F8A0 VLD1.8 {D0}, [R1]! '0d 07 0c f4' # 0001F89C VST1.8 {D0}, [R12]!
# .text:0001F8A4 VST1.8 {D0}, [R12]! '0d 07 21 f4' # 0001F8A0 VLD1.8 {D0}, [R1]!
# .text:0001F8A8 VLD1.8 {D0}, [R1]! '0d 07 0c f4' # 0001F8A4 VST1.8 {D0}, [R12]!
# .text:0001F8AC VST1.8 {D0}, [R12]! '0d 07 21 f4' # 0001F8A8 VLD1.8 {D0}, [R1]!
# .text:0001F8B0 VLD1.8 {D0}, [R1]! '0d 07 0c f4' # 0001F8AC VST1.8 {D0}, [R12]!
# .text:0001F8B4 VST1.8 {D0}, [R12]! '0d 07 21 f4' # 0001F8B0 VLD1.8 {D0}, [R1]!
# .text:0001F8B8 VLD1.8 {D0}, [R1]! '0d 07 0c f4' # 0001F8B4 VST1.8 {D0}, [R12]!
# .text:0001F8BC VST1.8 {D0}, [R12]! '0d 07 21 f4' # 0001F8B8 VLD1.8 {D0}, [R1]!
# .text:0001F8C0 VLD1.8 {D0}, [R1]! '0d 07 0c f4' # 0001F8BC VST1.8 {D0}, [R12]!
# .text:0001F8C4 VST1.8 {D0}, [R12]! '0d 07 21 f4' # 0001F8C0 VLD1.8 {D0}, [R1]!
# .text:0001F8C8 VLD1.8 {D0}, [R1]! '0d 07 0c f4' # 0001F8C4 VST1.8 {D0}, [R12]!
# .text:0001F8CC VST1.8 {D0}, [R12]! '0d 07 21 f4' # 0001F8C8 VLD1.8 {D0}, [R1]!
# .text:0001F8D0 TST R2, #4 '0d 07 0c f4' # 0001F8CC VST1.8 {D0}, [R12]!
# .text:0001F8D4 LDRNE R3, [R1],#4 '04 00 12 e3' # 0001F8D0 TST R2, #4
# .text:0001F8D8 STRNE R3, [R12],#4 '04 30 91 14' # 0001F8D4 LDRNE R3, [R1],#4
# .text:0001F8DC MOVS R2, R2,LSL#31 '04 30 8c 14' # 0001F8D8 STRNE R3, [R12],#4
# .text:0001F8E0 LDRHCS R3, [R1],#2 '82 2f b0 e1' # 0001F8DC MOVS R2, R2,LSL#31
# .text:0001F8E4 LDRBNE R1, [R1] 'b2 30 d1 20' # 0001F8E0 LDRHCS R3, [R1],#2
# .text:0001F8E8 STRHCS R3, [R12],#2 '00 10 d1 15' # 0001F8E4 LDRBNE R1, [R1]
# .text:0001F8EC STRBNE R1, [R12] 'b2 30 cc 20' # 0001F8E8 STRHCS R3, [R12],#2
shellcode = [0x3, 0xf0, 0x8f, 0xe0, 0xd, 0x7, 0x21, 0xf4, 0xd, 0x7, 0xc, 0xf4, 0xd, 0x7, 0x21, 0xf4, 0xd, 0x7, 0xc, 0xf4, 0xd, 0x7, 0x21, 0xf4, 0xd, 0x7, 0xc, 0xf4, 0xd, 0x7, 0x21, 0xf4, 0xd, 0x7, 0xc, 0xf4, 0xd, 0x7, 0x21, 0xf4, 0xd, 0x7, 0xc, 0xf4, 0xd, 0x7, 0x21, 0xf4, 0xd, 0x7, 0xc, 0xf4, 0xd, 0x7, 0x21, 0xf4, 0xd, 0x7, 0xc, 0xf4, 0x4, 0x0, 0x12, 0xe3, 0x4, 0x30, 0x91, 0x14, 0x4, 0x30, 0x8c, 0x14, 0x82, 0x2f, 0xb0, 0xe1, 0xb2, 0x30, 0xd1, 0x20, 0x0, 0x10, 0xd1, 0x15, 0xb2, 0x30, 0xcc, 0x20, 0x0, 0x10, 0xcc, 0x15] '00 10 cc 15' # 0001F8EC STRBNE R1, [R12]
base = 0x1F894 )
from_address = 0x1000
to_address = 0x2000 BASE = 0x1F894
cplen = 8 COPY_SRC = 0x1000
bs = b"c8"*cplen COPY_DST = 0x2000
COPY_LEN = 8
bs = b'c8' * COPY_LEN
uc = Uc(UC_ARCH_ARM, UC_MODE_ARM) uc = Uc(UC_ARCH_ARM, UC_MODE_ARM)
uc.mem_map(from_address, 0x1000)
uc.mem_map(to_address, 0x1000)
uc.mem_map(0x1F000, 0x1000)
uc.mem_write(from_address, bs)
uc.mem_write(base, bytes(shellcode))
uc.reg_write(UC_ARM_REG_R12, to_address)
uc.reg_write(UC_ARM_REG_R1, from_address)
uc.reg_write(UC_ARM_REG_R2, cplen)
uc.reg_write(UC_ARM_REG_R3, 0x24)
# enable_vfp
uc.reg_write(UC_ARM_REG_C1_C0_2, uc.reg_read(UC_ARM_REG_C1_C0_2) | (0xf << 20))
uc.reg_write(UC_ARM_REG_FPEXC, 0x40000000)
uc.emu_start(base, base+len(shellcode)) uc.mem_map(COPY_SRC, 0x1000)
fr = uc.mem_read(from_address, len(bs)) uc.mem_map(COPY_DST, 0x1000)
to = uc.mem_read(to_address, len(bs)) uc.mem_map(BASE & ~(0x1000 - 1), 0x1000)
print(f"memcpy result:\nfrom: {bytes(fr)}\nto: {bytes(to)}") uc.mem_write(COPY_SRC, bs)
uc.mem_write(BASE, bytes(SHELLCODE))
uc.reg_write_batch((
(UC_ARM_REG_R12, COPY_DST),
(UC_ARM_REG_R1, COPY_SRC),
(UC_ARM_REG_R2, COPY_LEN),
(UC_ARM_REG_R3, 0x24)
))
# enable_vfp
# coproc=15, is64=0, sec=0, CRn=1, CRm=0, opc1=0, opc2=2
CPACR = (15, 0, 0, 1, 0, 0, 2)
cpacr = uc.reg_read(UC_ARM_REG_CP_REG, CPACR)
uc.reg_write(UC_ARM_REG_CP_REG, CPACR + (cpacr | (0b11 << 20) | (0b11 << 22),))
uc.reg_write(UC_ARM_REG_FPEXC, (0b1 << 30))
uc.emu_start(BASE, BASE + len(SHELLCODE))
src = uc.mem_read(COPY_SRC, len(bs))
dst = uc.mem_read(COPY_DST, len(bs))
print(f'''memcpy result:
from: {bytes(src)}
to: {bytes(dst)}
''')