Merge remote-tracking branch 'eli/uc-py-next' into staged

This commit is contained in:
mio 2024-03-08 21:55:07 +08:00
commit ffd34cbb8c
No known key found for this signature in database
GPG Key ID: DFF27E34A47CB873
9 changed files with 1530 additions and 954 deletions

View File

@ -1,15 +1,29 @@
#!/bin/sh #!/bin/sh
./sample_x86.py python3 ./sample_arm.py
echo "==========================" echo "=========================="
./shellcode.py python3 ./sample_armeb.py
echo "==========================" echo "=========================="
./sample_arm.py python3 ./sample_arm64.py
echo "==========================" echo "=========================="
./sample_arm64.py python3 ./sample_arm64eb.py
echo "==========================" echo "=========================="
./sample_mips.py python3 ./sample_m68k.py
echo "==========================" echo "=========================="
./sample_sparc.py python3 ./sample_mips.py
echo "==========================" echo "=========================="
./sample_m68k.py python3 ./sample_ppc.py
echo "=========================="
python3 ./sample_riscv.py
echo "=========================="
python3 ./sample_s390x.py
echo "=========================="
python3 ./sample_sparc.py
echo "=========================="
python3 ./sample_tricore.py
echo "=========================="
python3 ./sample_x86.py
echo "=========================="
python3 ./shellcode.py
echo "=========================="
python3 ./sample_ctl.py

View File

@ -3,7 +3,6 @@
# By Lazymio(@wtdcode), 2021 # By Lazymio(@wtdcode), 2021
from unicorn import * from unicorn import *
from unicorn.unicorn import UC_HOOK_EDGE_GEN_CB
from unicorn.x86_const import * from unicorn.x86_const import *
from datetime import datetime from datetime import datetime
@ -58,7 +57,7 @@ def test_uc_ctl_tb_cache():
# Now we clear cache for all TBs. # Now we clear cache for all TBs.
for i in range(8): for i in range(8):
uc.ctl_remove_cache(addr + i * 512, addr + i * 512 + 1) uc.ctl_remove_cache(addr + i * 512, addr + i * 512 + 1)
evicted = time_emulation(uc, addr, addr + len(code)) evicted = time_emulation(uc, addr, addr + len(code))
print(f">>> Run time: First time {standard}, Cached: {cached}, Cached evicted: {evicted}") print(f">>> Run time: First time {standard}, Cached: {cached}, Cached evicted: {evicted}")
@ -93,7 +92,7 @@ def test_uc_ctl_exits():
uc.hook_add(UC_HOOK_EDGE_GENERATED, trace_new_edge) uc.hook_add(UC_HOOK_EDGE_GENERATED, trace_new_edge)
# Trace cmp instruction. # Trace cmp instruction.
uc.hook_add(UC_HOOK_TCG_OPCODE, trace_tcg_sub, UC_TCG_OP_SUB, UC_TCG_OP_FLAG_CMP) uc.hook_add(UC_HOOK_TCG_OPCODE, trace_tcg_sub, aux1=UC_TCG_OP_SUB, aux2=UC_TCG_OP_FLAG_CMP)
uc.ctl_exits_enabled(True) uc.ctl_exits_enabled(True)

View File

@ -1,4 +1,6 @@
# Unicorn Python bindings, by Nguyen Anh Quynnh <aquynh@gmail.com> # New and improved Unicorn Python bindings by elicn
# based on Nguyen Anh Quynnh's work
from . import arm_const, arm64_const, mips_const, sparc_const, m68k_const, x86_const, riscv_const, s390x_const, tricore_const from . import arm_const, arm64_const, mips_const, sparc_const, m68k_const, x86_const, riscv_const, s390x_const, tricore_const
from .unicorn_const import * from .unicorn_const import *
from .unicorn import Uc, uc_version, uc_arch_supported, version_bind, debug, UcError, __version__ from .unicorn import Uc, ucsubclass, uc_version, uc_arch_supported, version_bind, debug, UcError, __version__

View File

View File

@ -0,0 +1,81 @@
# AArch32 classes and structures.
#
# @author elicn
from typing import Any, Tuple
import ctypes
from .. import Uc
from .. import arm_const as const
from .types import UcTupledReg, UcReg128
ARMCPReg = Tuple[int, int, int, int, int, int, int, int]
class UcRegCP(UcTupledReg[ARMCPReg]):
"""ARM coprocessors registers for instructions MRC, MCR, MRRC, MCRR
"""
_fields_ = (
('cp', ctypes.c_uint32),
('is64', ctypes.c_uint32),
('sec', ctypes.c_uint32),
('crn', ctypes.c_uint32),
('crm', ctypes.c_uint32),
('opc1', ctypes.c_uint32),
('opc2', ctypes.c_uint32),
('val', ctypes.c_uint64)
)
@property
def value(self) -> int:
return self.val
class UcAArch32(Uc):
"""Unicorn subclass for ARM architecture.
"""
REG_RANGE_Q = range(const.UC_ARM_REG_Q0, const.UC_ARM_REG_Q15 + 1)
@staticmethod
def __select_reg_class(reg_id: int):
"""Select class for special architectural registers.
"""
reg_class = (
(UcAArch32.REG_RANGE_Q, UcReg128),
)
return next((cls for rng, cls in reg_class if reg_id in rng), None)
def reg_read(self, reg_id: int, aux: Any = None):
# select register class for special cases
reg_cls = UcAArch32.__select_reg_class(reg_id)
if reg_cls is None:
if reg_id == const.UC_ARM_REG_CP_REG:
return self._reg_read(reg_id, UcRegCP, *aux)
else:
# fallback to default reading method
return super().reg_read(reg_id, aux)
return self._reg_read(reg_id, reg_cls)
def reg_write(self, reg_id: int, value) -> None:
# select register class for special cases
reg_cls = UcAArch32.__select_reg_class(reg_id)
if reg_cls is None:
if reg_id == const.UC_ARM_REG_CP_REG:
self._reg_write(reg_id, UcRegCP, value)
else:
# fallback to default writing method
super().reg_write(reg_id, value)
else:
self._reg_write(reg_id, reg_cls, value)

View File

@ -0,0 +1,126 @@
# 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
from ..unicorn import uccallback
from ..unicorn_const import UC_ERR_ARG, UC_HOOK_INSN
from .types import uc_engine, UcTupledReg, UcReg128
ARM64CPReg = Tuple[int, int, int, int, int, int]
HOOK_INSN_SYS_CFUNC = ctypes.CFUNCTYPE(ctypes.c_uint32, uc_engine, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p)
class UcRegCP(UcTupledReg[ARM64CPReg]):
"""ARM64 coprocessors registers for instructions MRS, MSR
"""
_fields_ = (
('crn', ctypes.c_uint32),
('crm', ctypes.c_uint32),
('op0', ctypes.c_uint32),
('op1', ctypes.c_uint32),
('op2', ctypes.c_uint32),
('val', ctypes.c_uint64)
)
@property
def value(self) -> int:
return self.val
class UcAArch64(Uc):
"""Unicorn subclass for ARM64 architecture.
"""
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)
def hook_add(self, htype: int, callback: Callable, user_data: Any = None, begin: int = 1, end: int = 0, aux1: int = 0, aux2: int = 0) -> int:
if htype != UC_HOOK_INSN:
return super().hook_add(htype, callback, user_data, begin, end, aux1, aux2)
insn = ctypes.c_int(aux1)
def __hook_insn_sys():
@uccallback(HOOK_INSN_SYS_CFUNC)
def __hook_insn_sys_cb(handle: int, reg: int, pcp_reg: Any, key: int) -> int:
cp_reg = ctypes.cast(pcp_reg, ctypes.POINTER(UcRegCP)).contents
class CpReg(NamedTuple):
crn: int
crm: int
op0: int
op1: int
op2: int
val: int
cp_reg = CpReg(cp_reg.crn, cp_reg.crm, cp_reg.op0, cp_reg.op1, cp_reg.op2, cp_reg.val)
return callback(self, reg, cp_reg, user_data)
return __hook_insn_sys_cb
handlers = {
const.UC_ARM64_INS_MRS : __hook_insn_sys,
const.UC_ARM64_INS_MSR : __hook_insn_sys,
const.UC_ARM64_INS_SYS : __hook_insn_sys,
const.UC_ARM64_INS_SYSL : __hook_insn_sys
}
handler = handlers.get(insn.value)
if handler is None:
raise UcError(UC_ERR_ARG)
fptr = handler()
return getattr(self, '_Uc__do_hook_add')(htype, fptr, begin, end, insn)
@staticmethod
def __select_reg_class(reg_id: int):
"""Select class for special architectural registers.
"""
reg_class = (
(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)
def reg_read(self, reg_id: int, aux: Any = None):
# select register class for special cases
reg_cls = UcAArch64.__select_reg_class(reg_id)
if reg_cls is None:
if reg_id == const.UC_ARM64_REG_CP_REG:
return self._reg_read(reg_id, UcRegCP, *aux)
else:
# fallback to default reading method
return super().reg_read(reg_id, aux)
return self._reg_read(reg_id, reg_cls)
def reg_write(self, reg_id: int, value) -> None:
# select register class for special cases
reg_cls = UcAArch64.__select_reg_class(reg_id)
if reg_cls is None:
if reg_id == const.UC_ARM64_REG_CP_REG:
self._reg_write(reg_id, UcRegCP, value)
else:
# fallback to default writing method
super().reg_write(reg_id, value)
else:
self._reg_write(reg_id, reg_cls, value)

View File

@ -0,0 +1,178 @@
# Intel architecture classes and structures.
#
# @author elicn
from typing import Any, Callable, Tuple
import ctypes
from .. import Uc, UcError
from .. import x86_const as const
from ..unicorn import uccallback
from ..unicorn_const import UC_ERR_ARG, UC_HOOK_INSN
from .types import uc_engine, UcTupledReg, UcReg128, UcReg256, UcReg512
X86MMRReg = Tuple[int, int, int, int]
X86MSRReg = Tuple[int, int]
X86FPReg = Tuple[int, int]
HOOK_INSN_IN_CFUNC = ctypes.CFUNCTYPE(ctypes.c_uint32, uc_engine, ctypes.c_uint32, ctypes.c_int, ctypes.c_void_p)
HOOK_INSN_OUT_CFUNC = ctypes.CFUNCTYPE(None, uc_engine, ctypes.c_uint32, ctypes.c_int, ctypes.c_uint32, ctypes.c_void_p)
HOOK_INSN_SYSCALL_CFUNC = ctypes.CFUNCTYPE(None, uc_engine, ctypes.c_void_p)
HOOK_INSN_CPUID_CFUNC = ctypes.CFUNCTYPE(ctypes.c_uint32, uc_engine, ctypes.c_void_p)
class UcRegMMR(UcTupledReg[X86MMRReg]):
"""Memory-Management Register for instructions IDTR, GDTR, LDTR, TR.
"""
_fields_ = (
('selector', ctypes.c_uint16), # not used by GDTR and IDTR
('base', ctypes.c_uint64), # handle 32 or 64 bit CPUs
('limit', ctypes.c_uint32),
('flags', ctypes.c_uint32) # not used by GDTR and IDTR
)
class UcRegMSR(UcTupledReg[X86MSRReg]):
_fields_ = (
('rid', ctypes.c_uint32),
('val', ctypes.c_uint64)
)
@property
def value(self) -> int:
return self.val
class UcRegFPR(UcTupledReg[X86FPReg]):
_fields_ = (
('mantissa', ctypes.c_uint64),
('exponent', ctypes.c_uint16)
)
class UcIntel(Uc):
"""Unicorn subclass for Intel architecture.
"""
REG_RANGE_MMR = (
const.UC_X86_REG_IDTR,
const.UC_X86_REG_GDTR,
const.UC_X86_REG_LDTR,
const.UC_X86_REG_TR
)
REG_RANGE_FP = range(const.UC_X86_REG_FP0, const.UC_X86_REG_FP7 + 1)
REG_RANGE_XMM = range(const.UC_X86_REG_XMM0, const.UC_X86_REG_XMM31 + 1)
REG_RANGE_YMM = range(const.UC_X86_REG_YMM0, const.UC_X86_REG_YMM31 + 1)
REG_RANGE_ZMM = range(const.UC_X86_REG_ZMM0, const.UC_X86_REG_ZMM31 + 1)
def hook_add(self, htype: int, callback: Callable, user_data: Any = None, begin: int = 1, end: int = 0, aux1: int = 0, aux2: int = 0) -> int:
if htype != UC_HOOK_INSN:
return super().hook_add(htype, callback, user_data, begin, end, aux1, aux2)
insn = ctypes.c_int(aux1)
def __hook_insn_in():
@uccallback(HOOK_INSN_IN_CFUNC)
def __hook_insn_in_cb(handle: int, port: int, size: int, key: int) -> int:
return callback(self, port, size, user_data)
return __hook_insn_in_cb
def __hook_insn_out():
@uccallback(HOOK_INSN_OUT_CFUNC)
def __hook_insn_out_cb(handle: int, port: int, size: int, value: int, key: int):
callback(self, port, size, value, user_data)
return __hook_insn_out_cb
def __hook_insn_syscall():
@uccallback(HOOK_INSN_SYSCALL_CFUNC)
def __hook_insn_syscall_cb(handle: int, key: int):
callback(self, user_data)
return __hook_insn_syscall_cb
def __hook_insn_cpuid():
@uccallback(HOOK_INSN_CPUID_CFUNC)
def __hook_insn_cpuid_cb(handle: int, key: int) -> int:
return callback(self, user_data)
return __hook_insn_cpuid_cb
handlers = {
const.UC_X86_INS_IN : __hook_insn_in,
const.UC_X86_INS_OUT : __hook_insn_out,
const.UC_X86_INS_SYSCALL : __hook_insn_syscall,
const.UC_X86_INS_SYSENTER : __hook_insn_syscall,
const.UC_X86_INS_CPUID : __hook_insn_cpuid
}
handler = handlers.get(insn.value)
if handler is None:
raise UcError(UC_ERR_ARG)
fptr = handler()
return getattr(self, '_Uc__do_hook_add')(htype, fptr, begin, end, insn)
@staticmethod
def __select_reg_class(reg_id: int):
"""Select class for special architectural registers.
"""
reg_class = (
(UcIntel.REG_RANGE_MMR, UcRegMMR),
(UcIntel.REG_RANGE_FP, UcRegFPR),
(UcIntel.REG_RANGE_XMM, UcReg128),
(UcIntel.REG_RANGE_YMM, UcReg256),
(UcIntel.REG_RANGE_ZMM, UcReg512)
)
return next((cls for rng, cls in reg_class if reg_id in rng), None)
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:
return self._reg_read(const.UC_X86_REG_MSR, UcRegMSR, msr_id)
def msr_write(self, msr_id: int, value: int) -> None:
self._reg_write(const.UC_X86_REG_MSR, UcRegMSR, (msr_id, value))

View File

@ -0,0 +1,92 @@
# Common types and structures.
#
# @author elicn
from abc import abstractmethod
from typing import Generic, Tuple, TypeVar
import ctypes
uc_err = ctypes.c_int
uc_engine = ctypes.c_void_p
uc_context = ctypes.c_void_p
uc_hook_h = ctypes.c_size_t
VT = TypeVar('VT', bound=Tuple[int, ...])
class UcReg(ctypes.Structure):
"""A base class for composite registers.
This class is meant to be inherited, not instantiated directly.
"""
@property
@abstractmethod
def value(self):
"""Get register value.
"""
pass
@classmethod
@abstractmethod
def from_value(cls, value):
"""Create a register instance from a given value.
"""
pass
class UcTupledReg(UcReg, Generic[VT]):
"""A base class for registers whose values are represented as a set
of fields.
This class is meant to be inherited, not instantiated directly.
"""
@property
def value(self) -> VT:
return tuple(getattr(self, fname) for fname, *_ in self.__class__._fields_) # type: ignore
@classmethod
def from_value(cls, value: VT):
assert type(value) is tuple and len(value) == len(cls._fields_)
return cls(*value)
class UcLargeReg(UcReg):
"""A base class for large registers that are internally represented as
an array of multiple qwords.
This class is meant to be inherited, not instantiated directly.
"""
qwords: ctypes.Array
@property
def value(self) -> int:
return sum(qword << (64 * i) for i, qword in enumerate(self.qwords))
@classmethod
def from_value(cls, value: int):
assert type(value) is int
mask = (1 << 64) - 1
size = cls._fields_[0][1]._length_
return cls(tuple((value >> (64 * i)) & mask for i in range(size)))
class UcReg128(UcLargeReg):
_fields_ = [('qwords', ctypes.c_uint64 * 2)]
class UcReg256(UcLargeReg):
_fields_ = [('qwords', ctypes.c_uint64 * 4)]
class UcReg512(UcLargeReg):
_fields_ = [('qwords', ctypes.c_uint64 * 8)]

File diff suppressed because it is too large Load Diff