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
echo "=========================="
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 *
else:
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
from typing import Any, Tuple
import ctypes
from .. import Uc
from .. import arm_const as const
# traditional unicorn imports
from unicorn import arm_const as const
# newly introduced unicorn imports
from ..unicorn import Uc
from .types import UcTupledReg, UcReg128
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]):
"""ARM coprocessors registers for instructions MRC, MCR, MRRC, MCRR
@@ -36,7 +35,6 @@ class UcRegCP(UcTupledReg[ARMCPReg]):
def value(self) -> int:
return self.val
__repr__ = _structure_repr
class UcAArch32(Uc):
"""Unicorn subclass for ARM architecture.
@@ -83,3 +81,5 @@ class UcAArch32(Uc):
else:
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
from typing import Any, Callable, NamedTuple, Tuple
import ctypes
from .. import Uc, UcError
from .. import arm64_const as const
# traditional unicorn imports
from unicorn import arm64_const as const
from unicorn.unicorn_const import UC_ERR_ARG, UC_HOOK_INSN
from ..unicorn import uccallback
from unicorn import UC_ERR_ARG, UC_HOOK_INSN
# newly introduced unicorn imports
from ..unicorn import Uc, UcError, uccallback
from .types import uc_engine, UcTupledReg, UcReg128
ARM64CPReg = Tuple[int, int, int, int, int, int]
@@ -124,3 +125,5 @@ class UcAArch64(Uc):
else:
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
from typing import Any, Callable, Tuple
from typing import Any, Callable, Sequence, Tuple
import ctypes
from .. import Uc, UcError
from .. import x86_const as const
from unicorn.unicorn_py3 import uccallback
# traditional unicorn imports
from unicorn import x86_const as const
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
X86MMRReg = Tuple[int, int, int, int]
@@ -36,6 +37,9 @@ class UcRegMMR(UcTupledReg[X86MMRReg]):
class UcRegMSR(UcTupledReg[X86MSRReg]):
"""Intel Model Specific Register
"""
_fields_ = (
('rid', ctypes.c_uint32),
('val', ctypes.c_uint64)
@@ -47,6 +51,9 @@ class UcRegMSR(UcTupledReg[X86MSRReg]):
class UcRegFPR(UcTupledReg[X86FPReg]):
"""Intel Floating Point Register
"""
_fields_ = (
('mantissa', ctypes.c_uint64),
('exponent', ctypes.c_uint16)
@@ -176,3 +183,11 @@ class UcIntel(Uc):
def msr_write(self, msr_id: int, value: int) -> None:
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
from abc import abstractmethod
@@ -15,9 +15,6 @@ uc_hook_h = ctypes.c_size_t
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):
"""A base class for composite registers.
@@ -41,7 +38,6 @@ class UcReg(ctypes.Structure):
pass
_repr_ = _structure_repr
class UcTupledReg(UcReg, Generic[VT]):
"""A base class for registers whose values are represented as a set
@@ -85,12 +81,24 @@ class UcLargeReg(UcReg):
class UcReg128(UcLargeReg):
"""Large register holding a 128-bit value.
"""
_fields_ = [('qwords', ctypes.c_uint64 * 2)]
class UcReg256(UcLargeReg):
"""Large register holding a 256-bit value.
"""
_fields_ = [('qwords', ctypes.c_uint64 * 4)]
class UcReg512(UcLargeReg):
"""Large register holding a 512-bit value.
"""
_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 typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Mapping, MutableMapping, Optional, Sequence, Tuple, Type, TypeVar
@@ -5,13 +9,10 @@ import ctypes
import functools
import weakref
from unicorn import x86_const, arm_const, arm64_const, unicorn_const as uc
from .arch.types import *
from unicorn import unicorn_const as uc
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}'
def _structure_repr(self):
return "%s(%s)" % (self.__class__.__name__, ", ".join("%s=%s" % (k, getattr(self, k)) for (k, _) in self._fields_))
# __version__ = f'{uc.UC_VERSION_MAJOR}.{uc.UC_VERSION_MINOR}.{uc.UC_VERSION_PATCH}'
class _uc_mem_region(ctypes.Structure):
@@ -23,9 +24,8 @@ class _uc_mem_region(ctypes.Structure):
@property
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):
""""TranslationBlock
@@ -36,8 +36,6 @@ class uc_tb(ctypes.Structure):
('icount', ctypes.c_uint16),
('size', ctypes.c_uint16)
)
__repr__ = _structure_repr
def __load_uc_lib() -> ctypes.CDLL:
@@ -97,30 +95,30 @@ def __load_uc_lib() -> ctypes.CDLL:
lib_locations = [
os.getenv('LIBUNICORN_PATH'),
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',
] + [PurePath(p) / 'unicorn' / 'lib' for p in sys.path] # lazymio: ??? why PATH ??
r'/usr/local/lib' if sys.platform == 'darwin' else r'/usr/lib64',
] + [PurePath(p) / 'unicorn' / 'lib' for p in sys.path]
# filter out None elements
lib_locations = tuple(Path(loc) for loc in lib_locations if loc is not None)
lib_name = {
'cygwin' : 'cygunicorn.dll',
'darwin' : 'libunicorn.2.dylib',
'linux' : 'libunicorn.so.2',
'cygwin': 'cygunicorn.dll',
'darwin': 'libunicorn.2.dylib',
'linux': 'libunicorn.so.2',
'linux2': 'libunicorn.so.2',
'win32' : 'unicorn.dll'
'win32': 'unicorn.dll'
}.get(platform, "libunicorn.so")
def __attempt_load(libname: str):
T = TypeVar('T')
def __pick_first_valid(iter: Iterable[T]) -> Optional[T]:
"""Iterate till encountering a non-None element
def __pick_first_valid(it: Iterable[T]) -> Optional[T]:
"""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)
@@ -156,6 +154,7 @@ def __set_lib_prototypes(lib: ctypes.CDLL) -> None:
__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_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_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)
@@ -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_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_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_mem_regions', uc_err, uc_engine, ctypes.POINTER(ctypes.POINTER(_uc_mem_region)), ctypes.POINTER(ctypes.c_uint32))
# https://bugs.python.org/issue42880
@@ -332,11 +332,33 @@ class RegStateManager:
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.
"""
reg = regtype(*args)
reg = self.__get_reg_read_arg(regtype, *args)
status = self._do_reg_read(reg_id, ctypes.byref(reg))
if status != uc.UC_ERR_OK:
@@ -348,12 +370,30 @@ class RegStateManager:
"""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))
if status != uc.UC_ERR_OK:
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):
"""Read architectural register value.
@@ -380,6 +420,32 @@ class RegStateManager:
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):
"""Uc subclass decorator.
@@ -457,7 +523,7 @@ class Uc(RegStateManager):
"""
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)
@@ -591,6 +657,13 @@ class Uc(RegStateManager):
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 #
###########################
@@ -967,11 +1040,8 @@ class Uc(RegStateManager):
@staticmethod
def __ctl_encode(ctl: int, op: int, nargs: int) -> int:
if not (op and (op & ~0b11) == 0):
raise ValueError("Op should be 0, 1, or 2")
if not (nargs and (nargs & ~0b1111) == 0):
raise ValueError("Max number of arguments is 16")
assert nargs and (nargs & ~0b1111) == 0, 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})'
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)
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
def __getstate__(self):
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]
__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'
]