Merge pull request #2003 from elicn/dev

Misc. Python binding re-arrangements
This commit is contained in:
2024-09-21 18:43:02 +08:00
committed by GitHub
8 changed files with 172 additions and 68 deletions

View File

@@ -27,3 +27,5 @@ echo "=========================="
python3 ./shellcode.py python3 ./shellcode.py
echo "==========================" echo "=========================="
python3 ./sample_ctl.py python3 ./sample_ctl.py
echo "=========================="
python3 ./sample_network_auditing.py

View File

@@ -1,8 +1,14 @@
import sys import sys as _sys
if sys.version_info[0] == 2: from .unicorn_const import (
UC_VERSION_MAJOR as __MAJOR,
UC_VERSION_MINOR as __MINOR,
UC_VERSION_PATCH as __PATCH
)
__version__ = "%u.%u.%u" % (__MAJOR, __MINOR, __PATCH)
if _sys.version_info.major == 2:
from .unicorn_py2 import * from .unicorn_py2 import *
else: else:
from .unicorn_py3 import * from .unicorn_py3 import *
__version__ = "%u.%u.%u" % (uc.UC_VERSION_MAJOR, uc.UC_VERSION_MINOR, uc.UC_VERSION_PATCH)

View File

@@ -1,4 +0,0 @@
# from .arm import UcRegCP
# from .arm64 import UcRegCP64
# from .intel import UcRegFPR, UcRegMMR, UcRegMSR
# from .types import UcReg128, UcReg256, UcReg512, UcReg, UcLargeReg, UcTupledReg

View File

@@ -1,21 +1,20 @@
# AArch32 classes and structures. """AArch32 classes and structures.
# """
# @author elicn # @author elicn
from typing import Any, Tuple from typing import Any, Tuple
import ctypes import ctypes
from .. import Uc # traditional unicorn imports
from .. import arm_const as const from unicorn import arm_const as const
# newly introduced unicorn imports
from ..unicorn import Uc
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]
def _structure_repr(self):
return "%s(%s)" % (self.__class__.__name__, ", ".join("%s=%s" % (k, getattr(self, k)) for (k, _) in self._fields_))
class UcRegCP(UcTupledReg[ARMCPReg]): class UcRegCP(UcTupledReg[ARMCPReg]):
"""ARM coprocessors registers for instructions MRC, MCR, MRRC, MCRR """ARM coprocessors registers for instructions MRC, MCR, MRRC, MCRR
@@ -36,7 +35,6 @@ class UcRegCP(UcTupledReg[ARMCPReg]):
def value(self) -> int: def value(self) -> int:
return self.val return self.val
__repr__ = _structure_repr
class UcAArch32(Uc): class UcAArch32(Uc):
"""Unicorn subclass for ARM architecture. """Unicorn subclass for ARM architecture.
@@ -83,3 +81,5 @@ class UcAArch32(Uc):
else: else:
self._reg_write(reg_id, reg_cls, value) self._reg_write(reg_id, reg_cls, value)
__all__ = ['UcRegCP', 'UcAArch32']

View File

@@ -1,16 +1,17 @@
# AArch64 classes and structures. """AArch64 classes and structures.
# """
# @author elicn # @author elicn
from typing import Any, Callable, NamedTuple, Tuple from typing import Any, Callable, NamedTuple, Tuple
import ctypes import ctypes
from .. import Uc, UcError # traditional unicorn imports
from .. import arm64_const as const from unicorn import arm64_const as const
from unicorn.unicorn_const import UC_ERR_ARG, UC_HOOK_INSN
from ..unicorn import uccallback # newly introduced unicorn imports
from unicorn import UC_ERR_ARG, UC_HOOK_INSN from ..unicorn import Uc, UcError, uccallback
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]
@@ -124,3 +125,5 @@ class UcAArch64(Uc):
else: else:
self._reg_write(reg_id, reg_cls, value) self._reg_write(reg_id, reg_cls, value)
__all__ = ['UcRegCP64', 'UcAArch64']

View File

@@ -1,16 +1,17 @@
# Intel architecture classes and structures. """Intel architecture classes and structures.
# """
# @author elicn # @author elicn
from typing import Any, Callable, Tuple from typing import Any, Callable, Sequence, Tuple
import ctypes import ctypes
from .. import Uc, UcError # traditional unicorn imports
from .. import x86_const as const from unicorn import x86_const as const
from unicorn.unicorn_py3 import uccallback
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
from ..unicorn import Uc, UcError, uccallback
from .types import uc_engine, UcTupledReg, UcReg128, UcReg256, UcReg512 from .types import uc_engine, UcTupledReg, UcReg128, UcReg256, UcReg512
X86MMRReg = Tuple[int, int, int, int] X86MMRReg = Tuple[int, int, int, int]
@@ -36,6 +37,9 @@ class UcRegMMR(UcTupledReg[X86MMRReg]):
class UcRegMSR(UcTupledReg[X86MSRReg]): class UcRegMSR(UcTupledReg[X86MSRReg]):
"""Intel Model Specific Register
"""
_fields_ = ( _fields_ = (
('rid', ctypes.c_uint32), ('rid', ctypes.c_uint32),
('val', ctypes.c_uint64) ('val', ctypes.c_uint64)
@@ -47,6 +51,9 @@ class UcRegMSR(UcTupledReg[X86MSRReg]):
class UcRegFPR(UcTupledReg[X86FPReg]): class UcRegFPR(UcTupledReg[X86FPReg]):
"""Intel Floating Point Register
"""
_fields_ = ( _fields_ = (
('mantissa', ctypes.c_uint64), ('mantissa', ctypes.c_uint64),
('exponent', ctypes.c_uint16) ('exponent', ctypes.c_uint16)
@@ -176,3 +183,11 @@ class UcIntel(Uc):
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)) self._reg_write(const.UC_X86_REG_MSR, UcRegMSR, (msr_id, value))
def reg_read_batch(self, reg_ids: Sequence[int]) -> Tuple:
reg_types = [UcIntel.__select_reg_class(rid) or self._DEFAULT_REGTYPE for rid in reg_ids]
return self._reg_read_batch(reg_ids, reg_types)
__all__ = ['UcRegMMR', 'UcRegMSR', 'UcRegFPR', 'UcIntel']

View File

@@ -1,5 +1,5 @@
# Common types and structures. """Common types and structures.
# """
# @author elicn # @author elicn
from abc import abstractmethod from abc import abstractmethod
@@ -15,9 +15,6 @@ uc_hook_h = ctypes.c_size_t
VT = TypeVar('VT', bound=Tuple[int, ...]) VT = TypeVar('VT', bound=Tuple[int, ...])
def _structure_repr(self):
return "%s(%s)" % (self.__class__.__name__, ", ".join("%s=%s" % (k, getattr(self, k)) for (k, _) in self._fields_))
class UcReg(ctypes.Structure): class UcReg(ctypes.Structure):
"""A base class for composite registers. """A base class for composite registers.
@@ -41,7 +38,6 @@ class UcReg(ctypes.Structure):
pass pass
_repr_ = _structure_repr
class UcTupledReg(UcReg, Generic[VT]): class UcTupledReg(UcReg, Generic[VT]):
"""A base class for registers whose values are represented as a set """A base class for registers whose values are represented as a set
@@ -85,12 +81,24 @@ class UcLargeReg(UcReg):
class UcReg128(UcLargeReg): class UcReg128(UcLargeReg):
"""Large register holding a 128-bit value.
"""
_fields_ = [('qwords', ctypes.c_uint64 * 2)] _fields_ = [('qwords', ctypes.c_uint64 * 2)]
class UcReg256(UcLargeReg): class UcReg256(UcLargeReg):
"""Large register holding a 256-bit value.
"""
_fields_ = [('qwords', ctypes.c_uint64 * 4)] _fields_ = [('qwords', ctypes.c_uint64 * 4)]
class UcReg512(UcLargeReg): class UcReg512(UcLargeReg):
"""Large register holding a 512-bit value.
"""
_fields_ = [('qwords', ctypes.c_uint64 * 8)] _fields_ = [('qwords', ctypes.c_uint64 * 8)]
__all__ = ['uc_err', 'uc_engine', 'uc_context', 'uc_hook_h', 'UcReg', 'UcTupledReg', 'UcLargeReg', 'UcReg128', 'UcReg256', 'UcReg512']

View File

@@ -1,3 +1,7 @@
"""New and improved Unicorn Python bindings by elicn
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, Iterable, Iterator, Mapping, MutableMapping, Optional, Sequence, Tuple, Type, TypeVar
@@ -5,13 +9,10 @@ import ctypes
import functools import functools
import weakref import weakref
from unicorn import x86_const, arm_const, arm64_const, unicorn_const as uc from unicorn import unicorn_const as uc
from .arch.types import * from .arch.types import uc_err, uc_engine, uc_context, uc_hook_h, UcReg
__version__ = f'{uc.UC_VERSION_MAJOR}.{uc.UC_VERSION_MINOR}.{uc.UC_VERSION_PATCH}' # __version__ = f'{uc.UC_VERSION_MAJOR}.{uc.UC_VERSION_MINOR}.{uc.UC_VERSION_PATCH}'
def _structure_repr(self):
return "%s(%s)" % (self.__class__.__name__, ", ".join("%s=%s" % (k, getattr(self, k)) for (k, _) in self._fields_))
class _uc_mem_region(ctypes.Structure): class _uc_mem_region(ctypes.Structure):
@@ -23,9 +24,8 @@ class _uc_mem_region(ctypes.Structure):
@property @property
def value(self) -> Tuple[int, int, int]: def value(self) -> Tuple[int, int, int]:
return tuple(getattr(self, fname) for fname, _ in self._fields_) return tuple(getattr(self, fname) for fname, *_ in self._fields_)
__repr__ = _structure_repr
class uc_tb(ctypes.Structure): class uc_tb(ctypes.Structure):
""""TranslationBlock """"TranslationBlock
@@ -36,8 +36,6 @@ class uc_tb(ctypes.Structure):
('icount', ctypes.c_uint16), ('icount', ctypes.c_uint16),
('size', ctypes.c_uint16) ('size', ctypes.c_uint16)
) )
__repr__ = _structure_repr
def __load_uc_lib() -> ctypes.CDLL: def __load_uc_lib() -> ctypes.CDLL:
@@ -97,30 +95,30 @@ def __load_uc_lib() -> ctypes.CDLL:
lib_locations = [ lib_locations = [
os.getenv('LIBUNICORN_PATH'), os.getenv('LIBUNICORN_PATH'),
pkg_resources.resource_filename(__name__, 'lib'), pkg_resources.resource_filename(__name__, 'lib'),
PurePath(inspect.getfile(__load_uc_lib)).parent.parent / 'lib', PurePath(inspect.getfile(__load_uc_lib)).parent / 'lib',
'', '',
"/usr/local/lib/" if sys.platform == 'darwin' else '/usr/lib64', r'/usr/local/lib' if sys.platform == 'darwin' else r'/usr/lib64',
] + [PurePath(p) / 'unicorn' / 'lib' for p in sys.path] # lazymio: ??? why PATH ?? ] + [PurePath(p) / 'unicorn' / 'lib' for p in sys.path]
# filter out None elements # filter out None elements
lib_locations = tuple(Path(loc) for loc in lib_locations if loc is not None) lib_locations = tuple(Path(loc) for loc in lib_locations if loc is not None)
lib_name = { lib_name = {
'cygwin' : 'cygunicorn.dll', 'cygwin': 'cygunicorn.dll',
'darwin' : 'libunicorn.2.dylib', 'darwin': 'libunicorn.2.dylib',
'linux' : 'libunicorn.so.2', 'linux': 'libunicorn.so.2',
'linux2': 'libunicorn.so.2', 'linux2': 'libunicorn.so.2',
'win32' : 'unicorn.dll' 'win32': 'unicorn.dll'
}.get(platform, "libunicorn.so") }.get(platform, "libunicorn.so")
def __attempt_load(libname: str): def __attempt_load(libname: str):
T = TypeVar('T') T = TypeVar('T')
def __pick_first_valid(iter: Iterable[T]) -> Optional[T]: def __pick_first_valid(it: Iterable[T]) -> Optional[T]:
"""Iterate till encountering a non-None element """Iterate till encountering a non-None element and return it.
""" """
return next((elem for elem in iter if elem is not None), None) return next((elem for elem in it if elem is not None), None)
return __pick_first_valid(_load_lib(loc, libname) for loc in lib_locations) return __pick_first_valid(_load_lib(loc, libname) for loc in lib_locations)
@@ -156,6 +154,7 @@ def __set_lib_prototypes(lib: ctypes.CDLL) -> None:
__set_prototype('uc_errno', uc_err, uc_engine) __set_prototype('uc_errno', uc_err, uc_engine)
__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_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)
@@ -174,6 +173,7 @@ def __set_lib_prototypes(lib: ctypes.CDLL) -> None:
__set_prototype('uc_context_size', ctypes.c_size_t, uc_engine) __set_prototype('uc_context_size', ctypes.c_size_t, uc_engine)
__set_prototype('uc_context_reg_read', uc_err, uc_context, ctypes.c_int, ctypes.c_void_p) __set_prototype('uc_context_reg_read', uc_err, uc_context, ctypes.c_int, ctypes.c_void_p)
__set_prototype('uc_context_reg_write', uc_err, uc_context, ctypes.c_int, ctypes.c_void_p) __set_prototype('uc_context_reg_write', uc_err, uc_context, ctypes.c_int, ctypes.c_void_p)
__set_prototype('uc_context_reg_read_batch', uc_err, uc_context, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_void_p), ctypes.c_int)
__set_prototype('uc_context_free', uc_err, uc_context) __set_prototype('uc_context_free', uc_err, uc_context)
__set_prototype('uc_mem_regions', uc_err, uc_engine, ctypes.POINTER(ctypes.POINTER(_uc_mem_region)), ctypes.POINTER(ctypes.c_uint32)) __set_prototype('uc_mem_regions', uc_err, uc_engine, ctypes.POINTER(ctypes.POINTER(_uc_mem_region)), ctypes.POINTER(ctypes.c_uint32))
# https://bugs.python.org/issue42880 # https://bugs.python.org/issue42880
@@ -332,11 +332,33 @@ class RegStateManager:
raise NotImplementedError raise NotImplementedError
def _reg_read(self, reg_id: int, regtype, *args): def _do_reg_read_batch(self, reglist, vallist, count) -> int:
"""Private batch register read implementation.
Must be implemented by the mixin object
"""
raise NotImplementedError
def _do_reg_write_batch(self, reglist, count) -> int:
"""Private batch register write implementation.
Must be implemented by the mixin object
"""
raise NotImplementedError
@staticmethod
def __get_reg_read_arg(regtype: Type, *args):
return regtype(*args)
@staticmethod
def __get_reg_write_arg(regtype: Type, value):
return regtype.from_value(value) if issubclass(regtype, UcReg) else regtype(value)
def _reg_read(self, reg_id: int, regtype: Type, *args):
"""Register read helper method. """Register read helper method.
""" """
reg = regtype(*args) reg = self.__get_reg_read_arg(regtype, *args)
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:
@@ -348,12 +370,30 @@ class RegStateManager:
"""Register write helper method. """Register write helper method.
""" """
reg = regtype.from_value(value) if issubclass(regtype, UcReg) else regtype(value) reg = self.__get_reg_write_arg(regtype, 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:
"""Batch register read helper method.
"""
assert len(reg_ids) == len(reg_types)
count = len(reg_ids)
reg_list = (ctypes.c_int * count)(*reg_ids)
val_list = [rtype() for rtype in reg_types]
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))
if status != uc.UC_ERR_OK:
raise UcError(status)
return tuple(v.value for v in val_list)
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.
@@ -380,6 +420,32 @@ class RegStateManager:
self._reg_write(reg_id, self._DEFAULT_REGTYPE, value) self._reg_write(reg_id, self._DEFAULT_REGTYPE, value)
def reg_read_batch(self, reg_ids: Sequence[int]) -> Tuple:
"""Read a sequence of architectural registers.
Args:
reg_ids: a sequence of register identifiers (architecture-specific enumeration)
Returns: a tuple of registers values (register-specific format)
Raises: `UcError` in case of invalid register id
"""
reg_types = [self._DEFAULT_REGTYPE for _ in range(len(reg_ids))]
return self._reg_read_batch(reg_ids, reg_types)
def reg_write_batch(self, reg_info: Sequence[Tuple[int, Any]]) -> None:
"""Write a sequece of architectural registers.
Args:
regs_info: a sequence of tuples consisting of register identifiers and values
Raises: `UcError` in case of invalid register id or value format
"""
# TODO
...
def ucsubclass(cls): def ucsubclass(cls):
"""Uc subclass decorator. """Uc subclass decorator.
@@ -457,7 +523,7 @@ class Uc(RegStateManager):
""" """
def __wrapped() -> Type[Uc]: def __wrapped() -> Type[Uc]:
archmod = importlib.import_module(f'.arch.{pkgname}', f'unicorn.unicorn_py3') archmod = importlib.import_module(f'.arch.{pkgname}', 'unicorn.unicorn_py3')
return getattr(archmod, clsname) return getattr(archmod, clsname)
@@ -591,6 +657,13 @@ class Uc(RegStateManager):
return uclib.uc_reg_write(self._uch, reg_id, reg_obj) return uclib.uc_reg_write(self._uch, reg_id, reg_obj)
def _do_reg_read_batch(self, reglist, vallist, count) -> int:
"""Private batch register read implementation.
Do not call directly.
"""
return uclib.uc_reg_read_batch(self._uch, reglist, vallist, count)
########################### ###########################
# Memory management # # Memory management #
########################### ###########################
@@ -967,11 +1040,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:
if not (op and (op & ~0b11) == 0): assert nargs and (nargs & ~0b1111) == 0, f'nargs must not exceed value of 15 (got {nargs})'
raise ValueError("Op should be 0, 1, or 2") assert op and (op & ~0b11) == 0, f'op must not exceed value of 3 (got {op})'
if not (nargs and (nargs & ~0b1111) == 0):
raise ValueError("Max number of arguments is 16")
return (op << 30) | (nargs << 26) | ctl return (op << 30) | (nargs << 26) | ctl
@@ -1135,6 +1205,12 @@ class UcContext(RegStateManager):
return uclib.uc_context_reg_write(self._context, reg_id, reg_obj) return uclib.uc_context_reg_write(self._context, reg_id, reg_obj)
def _do_reg_read_batch(self, reglist, vallist, count) -> int:
"""Private batch register read implementation.
"""
return uclib.uc_context_reg_read_batch(self._context, reglist, vallist, count)
# Make UcContext picklable # Make UcContext picklable
def __getstate__(self): def __getstate__(self):
return bytes(self), self.size, self.arch, self.mode return bytes(self), self.size, self.arch, self.mode
@@ -1163,8 +1239,6 @@ UC_MMIO_READ_TYPE = Callable[[Uc, int, int, Any], int]
UC_MMIO_WRITE_TYPE = Callable[[Uc, int, int, int, Any], None] 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', 'uc_version', 'uc_arch_supported', 'version_bind', 'UcError', 'uc', 'debug', 'uccallback',
'x86_const', 'arm_const', 'arm64_const'
]