From 24e233ec413d68d39695625ca60f6c59908dc0fc Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Thu, 11 Jul 2024 22:20:58 +0200 Subject: [PATCH 001/144] feat: add proper floating-point register support --- .../amd64/amd64_ptrace_register_holder.py | 157 ++++++++++++++++++ .../architectures/amd64/amd64_registers.py | 3 +- libdebug/architectures/register_helper.py | 7 +- libdebug/cffi/ptrace_cffi_build.py | 126 +++++++++++++- libdebug/cffi/ptrace_cffi_source.c | 92 ++++++++++ libdebug/debugger/internal_debugger.py | 34 ++++ libdebug/interfaces/debugging_interface.py | 17 ++ libdebug/ptrace/ptrace_interface.py | 20 ++- libdebug/ptrace/ptrace_register_holder.py | 3 + libdebug/state/thread_context.py | 2 +- 10 files changed, 452 insertions(+), 9 deletions(-) diff --git a/libdebug/architectures/amd64/amd64_ptrace_register_holder.py b/libdebug/architectures/amd64/amd64_ptrace_register_holder.py index 537e1c1a..3e0a246f 100644 --- a/libdebug/architectures/amd64/amd64_ptrace_register_holder.py +++ b/libdebug/architectures/amd64/amd64_ptrace_register_holder.py @@ -115,6 +115,106 @@ def setter(self: Amd64Registers, value: int) -> None: return property(getter, setter, None, name) +def _get_property_fp_xmm0(name: str, index: int) -> property: + def getter(self: Amd64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + return int.from_bytes(self._fp_register_file.xmm0[index].data, "little") + + def setter(self: Amd64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + data = value.to_bytes(16, "little") + self._fp_register_file.xmm0[index].data = data + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_ymm0(name: str, index: int) -> property: + def getter(self: Amd64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + xmm0 = int.from_bytes(self._fp_register_file.xmm0[index].data, "little") + ymm0 = int.from_bytes(self._fp_register_file.ymm0[index].data, "little") + return (ymm0 << 128) | xmm0 + + def setter(self: Amd64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + new_xmm0 = value & ((1 << 128) - 1) + new_ymm0 = value >> 128 + self._fp_register_file.xmm0[index].data = new_xmm0.to_bytes(16, "little") + self._fp_register_file.ymm0[index].data = new_ymm0.to_bytes(16, "little") + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_zmm0(name: str, index: int) -> property: + def getter(self: Amd64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + zmm0 = int.from_bytes(self._fp_register_file.zmm0[index].data, "little") + ymm0 = int.from_bytes(self._fp_register_file.ymm0[index].data, "little") + xmm0 = int.from_bytes(self._fp_register_file.xmm0[index].data, "little") + return (zmm0 << 256) | (ymm0 << 128) | xmm0 + + def setter(self: Amd64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + new_xmm0 = value & ((1 << 128) - 1) + new_ymm0 = (value >> 128) & ((1 << 128) - 1) + new_zmm0 = value >> 256 + self._fp_register_file.xmm0[index].data = new_xmm0.to_bytes(16, "little") + self._fp_register_file.ymm0[index].data = new_ymm0.to_bytes(16, "little") + self._fp_register_file.zmm0[index].data = new_zmm0.to_bytes(32, "little") + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_xmm1(name: str, index: int) -> property: + def getter(self: Amd64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + zmm1 = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") + return zmm1 & ((1 << 128) - 1) + + def setter(self: Amd64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + # We do not clear the upper 384 bits of the register + previous_value = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") + new_value = (previous_value & ~((1 << 128) - 1)) | (value & ((1 << 128) - 1)) + self._fp_register_file.zmm1[index].data = new_value.to_bytes(64, "little") + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_ymm1(name: str, index: int) -> property: + def getter(self: Amd64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + zmm1 = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") + return zmm1 & ((1 << 256) - 1) + + def setter(self: Amd64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + # We do not clear the upper 256 bits of the register + previous_value = self._fp_register_file.zmm1[index] + new_value = (previous_value & ~((1 << 256) - 1)) | (value & ((1 << 256) - 1)) + self._fp_register_file.zmm1[index].data = new_value.to_bytes(64, "little") + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_zmm1(name: str, index: int) -> property: + def getter(self: Amd64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + return int.from_bytes(self._fp_register_file.zmm1[index].data, "little") + + def setter(self: Amd64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + self._fp_register_file.zmm1[index].data = value.to_bytes(64, "little") + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + @dataclass class Amd64PtraceRegisterHolder(PtraceRegisterHolder): """A class that provides views and setters for the registers of an x86_64 process.""" @@ -126,6 +226,7 @@ def provide_regs_class(self: Amd64PtraceRegisterHolder) -> type: def apply_on_regs(self: Amd64PtraceRegisterHolder, target: Amd64Registers, target_class: type) -> None: """Apply the register accessors to the Amd64Registers class.""" target.register_file = self.register_file + target._fp_register_file = self.fp_register_file # If the accessors are already defined, we don't need to redefine them if hasattr(target_class, "rip"): @@ -170,6 +271,20 @@ def apply_on_regs(self: Amd64PtraceRegisterHolder, target: Amd64Registers, targe # setup special registers target_class.rip = _get_property_64("rip") + # setup floating-point registers + # see libdebug/cffi/ptrace_cffi_build.py for the possible values of fp_register_file.type + match self.fp_register_file.type: + case 0: + self._handle_fp_512(target_class) + case 1: + self._handle_fp_896(target_class) + case 2: + self._handle_fp_2696(target_class) + case _: + raise NotImplementedError( + f"Floating-point register file type {self.fp_register_file.type} not available." + ) + def apply_on_thread(self: Amd64PtraceRegisterHolder, target: ThreadContext, target_class: type) -> None: """Apply the register accessors to the thread class.""" target.register_file = self.register_file @@ -190,3 +305,45 @@ def apply_on_thread(self: Amd64PtraceRegisterHolder, target: ThreadContext, targ target_class.syscall_arg3 = _get_property_64("r10") target_class.syscall_arg4 = _get_property_64("r8") target_class.syscall_arg5 = _get_property_64("r9") + + def _handle_fp_512(self, target_class: type) -> None: + """Handle the case where the xsave area is 512 bytes long, which means we just have the xmm registers.""" + for index in range(16): + name = f"xmm{index}" + setattr(target_class, name, _get_property_fp_xmm0(name, index)) + + def _handle_fp_896(self, target_class: type) -> None: + """Handle the case where the xsave area is 896 bytes long, which means we have the xmm and ymm registers.""" + for index in range(16): + name = f"xmm{index}" + setattr(target_class, name, _get_property_fp_xmm0(name, index)) + + for index in range(16): + name = f"ymm{index}" + setattr(target_class, name, _get_property_fp_ymm0(name, index)) + + def _handle_fp_2696(self, target_class: type) -> None: + """Handle the case where the xsave area is 2696 bytes long, which means we have the xmm, ymm and zmm registers.""" + for index in range(16): + name = f"xmm{index}" + setattr(target_class, name, _get_property_fp_xmm0(name, index)) + + for index in range(16): + name = f"ymm{index}" + setattr(target_class, name, _get_property_fp_ymm0(name, index)) + + for index in range(16): + name = f"zmm{index}" + setattr(target_class, name, _get_property_fp_zmm0(name, index)) + + for index in range(16): + name = f"xmm{index + 16}" + setattr(target_class, name, _get_property_fp_xmm1(name, index)) + + for index in range(16): + name = f"ymm{index + 16}" + setattr(target_class, name, _get_property_fp_ymm1(name, index)) + + for index in range(16): + name = f"zmm{index + 16}" + setattr(target_class, name, _get_property_fp_zmm1(name, index)) diff --git a/libdebug/architectures/amd64/amd64_registers.py b/libdebug/architectures/amd64/amd64_registers.py index 17fc7734..a8dbf72a 100644 --- a/libdebug/architectures/amd64/amd64_registers.py +++ b/libdebug/architectures/amd64/amd64_registers.py @@ -16,6 +16,7 @@ class Amd64Registers(Registers): """This class holds the state of the architectural-dependent registers of a process.""" - def __init__(self: Amd64Registers) -> None: + def __init__(self: Amd64Registers, thread_id: int) -> None: """Initializes the Registers object.""" self._internal_debugger = get_global_internal_debugger() + self._thread_id = thread_id diff --git a/libdebug/architectures/register_helper.py b/libdebug/architectures/register_helper.py index 1bec5112..e40c3c6b 100644 --- a/libdebug/architectures/register_helper.py +++ b/libdebug/architectures/register_helper.py @@ -4,8 +4,6 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -from collections.abc import Callable - from libdebug.architectures.amd64.amd64_ptrace_register_holder import ( Amd64PtraceRegisterHolder, ) @@ -15,14 +13,13 @@ def register_holder_provider( register_file: object, - _: Callable[[], object] | None = None, - __: Callable[[object], None] | None = None, + fp_register_file: object, ) -> RegisterHolder: """Returns an instance of the register holder to be used by the `_InternalDebugger` class.""" architecture = libcontext.arch match architecture: case "amd64": - return Amd64PtraceRegisterHolder(register_file) + return Amd64PtraceRegisterHolder(register_file, fp_register_file) case _: raise NotImplementedError(f"Architecture {architecture} not available.") diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 7e8345ad..407b4dde 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -5,10 +5,122 @@ # import platform +from pathlib import Path from cffi import FFI if platform.machine() == "x86_64": + # We need to determine if we have AVX, AVX2, AVX512, etc. + path = Path("/proc/cpuinfo") + + try: + with path.open() as f: + cpuinfo = f.read() + except OSError as e: + raise RuntimeError("Cannot read /proc/cpuinfo. Are you running on Linux?") from e + + if "avx512" in cpuinfo: + fp_regs_struct = """ + struct reg_128 + { + unsigned char data[16]; + }; + + struct reg_256 + { + unsigned char data[32]; + }; + + struct reg_512 + { + unsigned char data[64]; + }; + + struct fp_regs_struct + { + unsigned long type; + unsigned char padding0[32]; + struct reg_128 st[8]; + struct reg_128 xmm0[16]; + unsigned char padding1[96]; + // end of the 512 byte legacy region + unsigned char padding2[64]; + // ymm0 starts at offset 576 + struct reg_128 ymm0[16]; + unsigned char padding3[320]; + // zmm0 starts at offset 1152 + struct reg_256 zmm0[16]; + // zmm1 starts at offset 1664 + struct reg_512 zmm1[16]; + }; + """ + + fpregs_define = """ + #define FPREGS_AVX 2 + """ + elif "avx" in cpuinfo: + fp_regs_struct = """ + struct reg_128 + { + unsigned char data[16]; + }; + + struct reg_256 + { + unsigned char data[32]; + }; + + struct fp_regs_struct + { + unsigned long type; + unsigned char padding0[32]; + struct reg_128 st[8]; + struct reg_128 xmm0[16]; + unsigned char padding1[96]; + // end of the 512 byte legacy region + unsigned char padding2[64]; + // ymm0 starts at offset 576 + struct reg_128 ymm0[16]; + unsigned char padding3[64]; + }; + """ + + fpregs_define = """ + #define FPREGS_AVX 1 + """ + else: + fp_regs_struct = """ + struct reg_128 + { + unsigned char data[16]; + }; + + struct fp_regs_struct + { + unsigned long type; + unsigned char padding0[32]; + struct reg_128 st[8]; + struct reg_128 xmm0[16]; + unsigned char padding1[96]; + }; + """ + + fpregs_define = """ + #define FPREGS_AVX 0 + """ + + if "xsave" not in cpuinfo: + xsave_define = """ + #define XSAVE 0 + """ + + # We don't support non-XSAVE architectures + raise NotImplementedError("XSAVE not supported. Please open an issue on GitHub and include your hardware details.") + else: + xsave_define = """ + #define XSAVE 1 + """ + user_regs_struct = """ struct user_regs_struct { @@ -81,6 +193,7 @@ ffibuilder = FFI() ffibuilder.cdef( user_regs_struct + + fp_regs_struct + """ struct ptrace_hit_bp { int pid; @@ -100,6 +213,7 @@ struct thread { int tid; struct user_regs_struct regs; + struct fp_regs_struct fpregs; int signal_to_forward; struct thread *next; }; @@ -131,6 +245,10 @@ uint64_t ptrace_peekuser(int pid, uint64_t addr); uint64_t ptrace_pokeuser(int pid, uint64_t addr, uint64_t data); + struct fp_regs_struct *get_thread_fp_regs(struct global_state *state, int tid); + void get_fp_regs(struct global_state *state, int tid); + void set_fp_regs(struct global_state *state, int tid); + uint64_t ptrace_geteventmsg(int pid); long singlestep(struct global_state *state, int tid); @@ -158,8 +276,14 @@ with open("libdebug/cffi/ptrace_cffi_source.c") as f: ffibuilder.set_source( "libdebug.cffi._ptrace_cffi", - breakpoint_define + finish_define + f.read(), + fp_regs_struct + + fpregs_define + + xsave_define + + breakpoint_define + + finish_define + + f.read(), libraries=[], + extra_compile_args=["-march=native"], ) if __name__ == "__main__": diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 92d0bc96..3dc80d0e 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -4,6 +4,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. // +#include #include #include #include @@ -11,9 +12,29 @@ #include #include #include +#include #include #include +// Run some static assertions to ensure that the fp types are correct +#ifndef FPREGS_AVX + #error "FPREGS_AVX must be defined" +#endif + +#ifndef XSAVE + #error "XSAVE must be defined" +#endif + +#if (FPREGS_AVX == 0) + _Static_assert(sizeof(struct fp_regs_struct) == 520, "user_fpregs_struct size is not 512 bytes"); +#elif (FPREGS_AVX == 1) + _Static_assert(sizeof(struct fp_regs_struct) == 904, "user_fpregs_struct size is not 832 bytes"); +#elif (FPREGS_AVX == 2) + _Static_assert(sizeof(struct fp_regs_struct) == 2704, "user_fpregs_struct size is not 1024 bytes"); +#else + #error "FPREGS_AVX must be 0, 1 or 2" +#endif + struct ptrace_hit_bp { int pid; uint64_t addr; @@ -32,6 +53,7 @@ struct software_breakpoint { struct thread { int tid; struct user_regs_struct regs; + struct fp_regs_struct fpregs; int signal_to_forward; struct thread *next; }; @@ -48,6 +70,74 @@ struct global_state { _Bool handle_syscall_enabled; }; +struct thread *get_thread(struct global_state *state, int tid) +{ + struct thread *t = state->t_HEAD; + while (t != NULL) { + if (t->tid == tid) return t; + t = t->next; + } + + return NULL; +} + +struct fp_regs_struct *get_thread_fp_regs(struct global_state *state, int tid) +{ + struct thread *t = get_thread(state, tid); + + if (t) { + return &t->fpregs; + } + + return NULL; +} + +void get_fp_regs(struct global_state *state, int tid) +{ + struct thread *t = get_thread(state, tid); + + if (t == NULL) { + perror("Thread not found"); + return; + } + + #if (XSAVE == 0) + + #else + struct iovec iov; + + iov.iov_base = (unsigned char *)(&t->fpregs) + offsetof(struct fp_regs_struct, padding0); + iov.iov_len = sizeof(struct fp_regs_struct) - sizeof(unsigned long); + + if (ptrace(PTRACE_GETREGSET, tid, NT_X86_XSTATE, &iov) == -1) { + perror("ptrace_getregset_xstate"); + } + #endif +} + +void set_fp_regs(struct global_state *state, int tid) +{ + struct thread *t = get_thread(state, tid); + + if (t == NULL) { + perror("Thread not found"); + return; + } + + #if (XSAVE == 0) + + #else + struct iovec iov; + + iov.iov_base = (unsigned char *)(&t->fpregs) + offsetof(struct fp_regs_struct, padding0); + iov.iov_len = sizeof(struct fp_regs_struct) - sizeof(unsigned long); + + if (ptrace(PTRACE_SETREGSET, tid, NT_X86_XSTATE, &iov) == -1) { + perror("ptrace_setregset_xstate"); + } + #endif +} + struct user_regs_struct *register_thread(struct global_state *state, int tid) { // Verify if the thread is already registered @@ -61,6 +151,8 @@ struct user_regs_struct *register_thread(struct global_state *state, int tid) t->tid = tid; t->signal_to_forward = 0; + t->fpregs.type = FPREGS_AVX; + ptrace(PTRACE_GETREGS, tid, NULL, &t->regs); t->next = state->t_HEAD; diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 25a7e386..4a3d0155 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -1296,6 +1296,12 @@ def __threaded_poke_memory(self: InternalDebugger, address: int, data: bytes) -> int_data = int.from_bytes(data, "little") self.debugging_interface.poke_memory(address, int_data) + def __threaded_fetch_fp_registers(self: InternalDebugger, thread_id: int) -> None: + self.debugging_interface.fetch_fp_registers(thread_id) + + def __threaded_flush_fp_registers(self: InternalDebugger, thread_id: int) -> None: + self.debugging_interface.flush_fp_registers(thread_id) + @background_alias(__threaded_peek_memory) def _peek_memory(self: InternalDebugger, address: int) -> bytes: """Reads memory from the process.""" @@ -1347,6 +1353,34 @@ def _poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: self._join_and_check_status() + @background_alias(__threaded_fetch_fp_registers) + def _fetch_fp_registers(self: InternalDebugger, thread_id: int) -> None: + """Fetches the floating point registers of a thread.""" + if not self.instanced: + raise RuntimeError("Process not running, cannot step.") + + self._ensure_process_stopped() + + self.__polling_thread_command_queue.put( + (self.__threaded_fetch_fp_registers, (thread_id,)), + ) + + self._join_and_check_status() + + @background_alias(__threaded_flush_fp_registers) + def _flush_fp_registers(self: InternalDebugger, thread_id: int) -> None: + """Flushes the floating point registers of a thread.""" + if not self.instanced: + raise RuntimeError("Process not running, cannot step.") + + self._ensure_process_stopped() + + self.__polling_thread_command_queue.put( + (self.__threaded_flush_fp_registers, (thread_id,)), + ) + + self._join_and_check_status() + def _enable_antidebug_escaping(self: InternalDebugger) -> None: """Enables the anti-debugging escape mechanism.""" handler = SyscallHandler( diff --git a/libdebug/interfaces/debugging_interface.py b/libdebug/interfaces/debugging_interface.py index f1463eae..87ed2390 100644 --- a/libdebug/interfaces/debugging_interface.py +++ b/libdebug/interfaces/debugging_interface.py @@ -20,6 +20,7 @@ class DebuggingInterface(ABC): """The interface used by `_InternalDebugger` to communicate with the available debugging backends, such as `ptrace` or `gdb`.""" + @abstractmethod def __init__(self: DebuggingInterface) -> None: """Initializes the DebuggingInterface classs.""" @@ -165,3 +166,19 @@ def poke_memory(self: DebuggingInterface, address: int, data: int) -> None: address (int): The address to write. data (int): The value to write. """ + + @abstractmethod + def fetch_fp_registers(self: DebuggingInterface, thread_id: int) -> None: + """Fetches the floating-point registers of the specified thread. + + Args: + thread_id (int): The thread to fetch. + """ + + @abstractmethod + def flush_fp_registers(self: DebuggingInterface, thread_id: int) -> None: + """Flushes the floating-point registers of the specified thread. + + Args: + thread_id (int): The thread to store. + """ diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index fa549081..6ffc391a 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -461,7 +461,9 @@ def register_new_thread(self: PtraceInterface, new_thread_id: int) -> None: new_thread_id, ) - register_holder = register_holder_provider(register_file) + fp_register_file = self.lib_trace.get_thread_fp_regs(self._global_state, new_thread_id) + + register_holder = register_holder_provider(register_file, fp_register_file) with extend_internal_debugger(self._internal_debugger): thread = ThreadContext(new_thread_id, register_holder) @@ -631,6 +633,22 @@ def poke_memory(self: PtraceInterface, address: int, value: int) -> None: error = self.ffi.errno raise OSError(error, errno.errorcode[error]) + def fetch_fp_registers(self: PtraceInterface, thread_id: int) -> None: + """Fetches the floating-point registers of the specified thread. + + Args: + thread_id (int): The thread to fetch. + """ + self.lib_trace.get_fp_regs(self._global_state, thread_id) + + def flush_fp_registers(self: PtraceInterface, thread_id: int) -> None: + """Flushes the floating-point registers of the specified thread. + + Args: + thread_id (int): The thread to flush. + """ + self.lib_trace.set_fp_regs(self._global_state, thread_id) + def _peek_user(self: PtraceInterface, thread_id: int, address: int) -> int: """Reads the memory at the specified address.""" result = self.lib_trace.ptrace_peekuser(thread_id, address) diff --git a/libdebug/ptrace/ptrace_register_holder.py b/libdebug/ptrace/ptrace_register_holder.py index 1f09922a..aad6d592 100644 --- a/libdebug/ptrace/ptrace_register_holder.py +++ b/libdebug/ptrace/ptrace_register_holder.py @@ -25,6 +25,9 @@ class PtraceRegisterHolder(RegisterHolder): register_file: object """The register file of the target process, as returned by ptrace.""" + fp_register_file: object + """The floating-point register file of the target process, as returned by ptrace.""" + def poll(self: PtraceRegisterHolder, target: ThreadContext) -> None: """Poll the register values from the specified target.""" raise NotImplementedError("Do not call this method.") diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index f0b5d193..e8791b26 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -78,7 +78,7 @@ def __init__(self: ThreadContext, thread_id: int, registers: RegisterHolder) -> self._internal_debugger = provide_internal_debugger(self) self._thread_id = thread_id regs_class = registers.provide_regs_class() - self.regs = regs_class() + self.regs = regs_class(thread_id) registers.apply_on_regs(self.regs, regs_class) registers.apply_on_thread(self, ThreadContext) From 069c9f9e77b9c853b227c8788041799f443720f7 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Thu, 11 Jul 2024 22:21:48 +0200 Subject: [PATCH 002/144] test: add test for xmm and ymm register access --- test/binaries/floating_point_896_test | Bin 0 -> 16496 bytes test/run_suite.py | 2 + test/scripts/floating_point_test.py | 90 ++++++++++++++++++++++++++ test/srcs/floating_point_896_test.c | 57 ++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100755 test/binaries/floating_point_896_test create mode 100644 test/scripts/floating_point_test.py create mode 100644 test/srcs/floating_point_896_test.c diff --git a/test/binaries/floating_point_896_test b/test/binaries/floating_point_896_test new file mode 100755 index 0000000000000000000000000000000000000000..4eb630bb1ddf15c70a66b15fcec9516e96ebfb08 GIT binary patch literal 16496 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 literal 0 HcmV?d00001 diff --git a/test/run_suite.py b/test/run_suite.py index 8f48b72a..8e36d4d9 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -20,6 +20,7 @@ from scripts.death_test import DeathTest from scripts.deep_dive_division_test import DeepDiveDivision from scripts.finish_test import FinishTest +from scripts.floating_point_test import FloatingPointTest from scripts.handle_syscall_test import HandleSyscallTest from scripts.hijack_syscall_test import SyscallHijackTest from scripts.jumpout_test import Jumpout @@ -90,6 +91,7 @@ def fast_suite(): suite.addTest(FinishTest("test_exact_breakpoint_return")) suite.addTest(FinishTest("test_heuristic_breakpoint_return")) suite.addTest(FinishTest("test_breakpoint_collision")) + suite.addTest(FloatingPointTest("test_floating_point_reg_access")) suite.addTest(Jumpout("test_jumpout")) suite.addTest(Nlinks("test_nlinks")) suite.addTest(JumpstartTest("test_cursed_ldpreload")) diff --git a/test/scripts/floating_point_test.py b/test/scripts/floating_point_test.py new file mode 100644 index 00000000..25af5392 --- /dev/null +++ b/test/scripts/floating_point_test.py @@ -0,0 +1,90 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest +from pathlib import Path + +from libdebug import debugger + + +class FloatingPointTest(unittest.TestCase): + def test_floating_point_reg_access(self): + # This test is divided into two parts, depending on the current hardware + + # Let's check if we have AVX512 + with Path("/proc/cpuinfo").open() as f: + cpuinfo = f.read() + if "avx512" in cpuinfo: + # Run an AVX512 test + self.test_floating_point_reg_access_avx512() + self.test_floating_point_reg_access_avx() + self.test_floating_point_reg_access_generic() + elif "avx" in cpuinfo: + # Run an AVX test + self.test_floating_point_reg_access_avx() + self.test_floating_point_reg_access_generic() + else: + # Run a generic test + self.test_floating_point_reg_access_generic() + + def test_floating_point_reg_access_avx512(self): + pass + + def test_floating_point_reg_access_avx(self): + d = debugger("binaries/floating_point_896_test") + + d.run() + + bp1 = d.bp(0x40159E) + bp2 = d.bp(0x4015C5) + + d.cont() + + self.assertTrue(bp1.hit_on(d)) + + self.assertTrue(hasattr(d.regs, "xmm0")) + self.assertTrue(hasattr(d.regs, "ymm0")) + self.assertTrue(hasattr(d.regs, "xmm15")) + self.assertTrue(hasattr(d.regs, "ymm15")) + + baseval = int.from_bytes(bytes(list(range(0, 256, 17)) + list(range(16))), "little") + + self.assertEqual(d.regs.ymm0, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm1, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm2, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm3, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm4, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm5, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm6, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm7, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm8, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm9, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm10, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm11, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm12, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm13, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm14, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + self.assertEqual(d.regs.ymm15, baseval) + + d.kill() + + def test_floating_point_reg_access_generic(self): + pass diff --git a/test/srcs/floating_point_896_test.c b/test/srcs/floating_point_896_test.c new file mode 100644 index 00000000..5d1a6960 --- /dev/null +++ b/test/srcs/floating_point_896_test.c @@ -0,0 +1,57 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +int main() +{ + + // load value into floating point register + char value0[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; + __asm__ __volatile__("vmovdqu %0, %%ymm0" : : "m" (value0)); + char value1[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00}; + __asm__ __volatile__("vmovdqu %0, %%ymm1" : : "m" (value1)); + char value2[] = {0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11}; + __asm__ __volatile__("vmovdqu %0, %%ymm2" : : "m" (value2)); + char value3[] = {0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22}; + __asm__ __volatile__("vmovdqu %0, %%ymm3" : : "m" (value3)); + char value4[] = {0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33}; + __asm__ __volatile__("vmovdqu %0, %%ymm4" : : "m" (value4)); + char value5[] = {0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44}; + __asm__ __volatile__("vmovdqu %0, %%ymm5" : : "m" (value5)); + char value6[] = {0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + __asm__ __volatile__("vmovdqu %0, %%ymm6" : : "m" (value6)); + char value7[] = {0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + __asm__ __volatile__("vmovdqu %0, %%ymm7" : : "m" (value7)); + char value8[] = {0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + __asm__ __volatile__("vmovdqu %0, %%ymm8" : : "m" (value8)); + char value9[] = {0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + __asm__ __volatile__("vmovdqu %0, %%ymm9" : : "m" (value9)); + char value10[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}; + __asm__ __volatile__("vmovdqu %0, %%ymm10" : : "m" (value10)); + char value11[] = {0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA}; + __asm__ __volatile__("vmovdqu %0, %%ymm11" : : "m" (value11)); + char value12[] = {0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB}; + __asm__ __volatile__("vmovdqu %0, %%ymm12" : : "m" (value12)); + char value13[] = {0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC}; + __asm__ __volatile__("vmovdqu %0, %%ymm13" : : "m" (value13)); + char value14[] = {0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD}; + __asm__ __volatile__("vmovdqu %0, %%ymm14" : : "m" (value14)); + char value15[] = {0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; + __asm__ __volatile__("vmovdqu %0, %%ymm15" : : "m" (value15)); + + // breakpoint location 1 + __asm__ __volatile__("nop"); + + char value[32]; + __asm__ __volatile__("vmovdqu %%ymm0, %0" : "=m" (value)); + + unsigned long check = *(unsigned long*)value; + + if (check == 0xdeadbeefdeadbeef) { + __asm__ __volatile__("nop"); + } + + return 0; +} From 0eb4575dbe3bc9c595390c1177b06a09bbed85b8 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Thu, 11 Jul 2024 22:37:03 +0200 Subject: [PATCH 003/144] test: add test for legacy xmm access --- test/binaries/floating_point_512_test | Bin 0 -> 16496 bytes test/scripts/floating_point_test.py | 128 +++++++++++++++++++++++++- test/srcs/floating_point_2696_test.c | 0 test/srcs/floating_point_512_test.c | 54 +++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100755 test/binaries/floating_point_512_test create mode 100644 test/srcs/floating_point_2696_test.c create mode 100644 test/srcs/floating_point_512_test.c diff --git a/test/binaries/floating_point_512_test b/test/binaries/floating_point_512_test new file mode 100755 index 0000000000000000000000000000000000000000..a959601261ccbd3a0ca4e73de695b40778035a18 GIT binary patch literal 16496 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 OzeYFSn2+Ey#lHc&{9Duj literal 0 HcmV?d00001 diff --git a/test/scripts/floating_point_test.py b/test/scripts/floating_point_test.py index 25af5392..fe66748b 100644 --- a/test/scripts/floating_point_test.py +++ b/test/scripts/floating_point_test.py @@ -6,6 +6,7 @@ import unittest from pathlib import Path +from random import randint from libdebug import debugger @@ -53,38 +54,163 @@ def test_floating_point_reg_access_avx(self): baseval = int.from_bytes(bytes(list(range(0, 256, 17)) + list(range(16))), "little") self.assertEqual(d.regs.ymm0, baseval) + self.assertEqual(d.regs.xmm0, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm1, baseval) + self.assertEqual(d.regs.xmm1, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm2, baseval) + self.assertEqual(d.regs.xmm2, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm3, baseval) + self.assertEqual(d.regs.xmm3, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm4, baseval) + self.assertEqual(d.regs.xmm4, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm5, baseval) + self.assertEqual(d.regs.xmm5, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm6, baseval) + self.assertEqual(d.regs.xmm6, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm7, baseval) + self.assertEqual(d.regs.xmm7, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm8, baseval) + self.assertEqual(d.regs.xmm8, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm9, baseval) + self.assertEqual(d.regs.xmm9, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm10, baseval) + self.assertEqual(d.regs.xmm10, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm11, baseval) + self.assertEqual(d.regs.xmm11, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm12, baseval) + self.assertEqual(d.regs.xmm12, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm13, baseval) + self.assertEqual(d.regs.xmm13, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm14, baseval) + self.assertEqual(d.regs.xmm14, baseval & ((1 << 128) - 1)) baseval = (baseval >> 8) + ((baseval & 255) << 248) self.assertEqual(d.regs.ymm15, baseval) + self.assertEqual(d.regs.xmm15, baseval & ((1 << 128) - 1)) + + d.regs.ymm0 = 0xDEADBEEFDEADBEEF + + d.cont() + + self.assertTrue(bp2.hit_on(d)) + + for i in range(16): + val = randint(0, 2 ** 256 - 1) + setattr(d.regs, f"ymm{i}", val) + self.assertEqual(getattr(d.regs, f"xmm{i}"), val & ((1 << 128) - 1)) + self.assertEqual(getattr(d.regs, f"ymm{i}"), val) + + d.kill() + + def callback(t, _): + baseval = int.from_bytes(bytes(list(range(0, 256, 17)) + list(range(16))), "little") + for i in range(16): + self.assertEqual(getattr(d.regs, f"xmm{i}"), baseval & ((1 << 128) - 1)) + self.assertEqual(getattr(d.regs, f"ymm{i}"), baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 248) + + t.regs.ymm0 = 0xDEADBEEFDEADBEEF + + d.run() + + d.bp(0x40159E, callback=callback) + bp = d.bp(0x4015C5) + + d.cont() + + self.assertTrue(bp.hit_on(d)) d.kill() def test_floating_point_reg_access_generic(self): - pass + d = debugger("binaries/floating_point_512_test") + + d.run() + + bp1 = d.bp(0x401372) + bp2 = d.bp(0x401399) + + d.cont() + + self.assertTrue(bp1.hit_on(d)) + + self.assertTrue(hasattr(d.regs, "xmm0")) + self.assertTrue(hasattr(d.regs, "xmm15")) + + baseval = int.from_bytes(bytes(list(range(0, 256, 17))), "little") + self.assertEqual(d.regs.xmm0, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm1, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm2, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm3, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm4, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm5, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm6, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm7, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm8, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm9, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm10, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm11, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm12, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm13, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm14, baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + self.assertEqual(d.regs.xmm15, baseval) + + d.regs.xmm0 = 0xDEADBEEFDEADBEEF + + d.cont() + + self.assertTrue(bp2.hit_on(d)) + + for i in range(16): + val = randint(0, 2 ** 128 - 1) + setattr(d.regs, f"xmm{i}", val) + self.assertEqual(getattr(d.regs, f"xmm{i}"), val) + + d.kill() + + def callback(t, _): + baseval = int.from_bytes(bytes(list(range(0, 256, 17))), "little") + for i in range(16): + self.assertEqual(getattr(d.regs, f"xmm{i}"), baseval) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + + t.regs.xmm0 = 0xDEADBEEFDEADBEEF + + d.run() + + d.bp(0x401372, callback=callback) + bp = d.bp(0x401399) + + d.cont() + + self.assertTrue(bp.hit_on(d)) + + d.kill() diff --git a/test/srcs/floating_point_2696_test.c b/test/srcs/floating_point_2696_test.c new file mode 100644 index 00000000..e69de29b diff --git a/test/srcs/floating_point_512_test.c b/test/srcs/floating_point_512_test.c new file mode 100644 index 00000000..75763e39 --- /dev/null +++ b/test/srcs/floating_point_512_test.c @@ -0,0 +1,54 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +int main() +{ + char value0[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + __asm__ __volatile__("vmovdqu %0, %%xmm0" : : "m" (value0)); + char value1[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00}; + __asm__ __volatile__("vmovdqu %0, %%xmm1" : : "m" (value1)); + char value2[] = {0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11}; + __asm__ __volatile__("vmovdqu %0, %%xmm2" : : "m" (value2)); + char value3[] = {0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22}; + __asm__ __volatile__("vmovdqu %0, %%xmm3" : : "m" (value3)); + char value4[] = {0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33}; + __asm__ __volatile__("vmovdqu %0, %%xmm4" : : "m" (value4)); + char value5[] = {0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44}; + __asm__ __volatile__("vmovdqu %0, %%xmm5" : : "m" (value5)); + char value6[] = {0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + __asm__ __volatile__("vmovdqu %0, %%xmm6" : : "m" (value6)); + char value7[] = {0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + __asm__ __volatile__("vmovdqu %0, %%xmm7" : : "m" (value7)); + char value8[] = {0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + __asm__ __volatile__("vmovdqu %0, %%xmm8" : : "m" (value8)); + char value9[] = {0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + __asm__ __volatile__("vmovdqu %0, %%xmm9" : : "m" (value9)); + char value10[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}; + __asm__ __volatile__("vmovdqu %0, %%xmm10" : : "m" (value10)); + char value11[] = {0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA}; + __asm__ __volatile__("vmovdqu %0, %%xmm11" : : "m" (value11)); + char value12[] = {0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB}; + __asm__ __volatile__("vmovdqu %0, %%xmm12" : : "m" (value12)); + char value13[] = {0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC}; + __asm__ __volatile__("vmovdqu %0, %%xmm13" : : "m" (value13)); + char value14[] = {0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD}; + __asm__ __volatile__("vmovdqu %0, %%xmm14" : : "m" (value14)); + char value15[] = {0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; + __asm__ __volatile__("vmovdqu %0, %%xmm15" : : "m" (value15)); + + __asm__ __volatile__("nop"); + + char value[16]; + __asm__ __volatile__("vmovdqu %%xmm0, %0" : "=m" (value)); + + unsigned long check = *(unsigned long*)value; + + if (check == 0xdeadbeefdeadbeef) { + __asm__ __volatile__("nop"); + } + + return 0; +} From 2aae1e18f0beef3deebdb16213a11125e104a0b9 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Thu, 11 Jul 2024 22:48:50 +0200 Subject: [PATCH 004/144] test: add test for avx512 register access --- test/binaries/floating_point_2696_test | Bin 0 -> 16528 bytes test/scripts/floating_point_test.py | 39 +++++++++- test/srcs/floating_point_2696_test.c | 100 +++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100755 test/binaries/floating_point_2696_test diff --git a/test/binaries/floating_point_2696_test b/test/binaries/floating_point_2696_test new file mode 100755 index 0000000000000000000000000000000000000000..341cd3351d7f17c75f78a158a89a70201f3c2359 GIT binary patch literal 16528 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 8) + ((baseval & 255) << 504) + + d.regs.zmm0 = 0xDEADBEEFDEADBEEF + + d.cont() + + self.assertTrue(bp2.hit_on(d)) + + for i in range(32): + val = randint(0, 2 ** 512 - 1) + setattr(d.regs, f"zmm{i}", val) + self.assertEqual(getattr(d.regs, f"zmm{i}"), val) + + d.kill() def test_floating_point_reg_access_avx(self): d = debugger("binaries/floating_point_896_test") diff --git a/test/srcs/floating_point_2696_test.c b/test/srcs/floating_point_2696_test.c index e69de29b..ded32393 100644 --- a/test/srcs/floating_point_2696_test.c +++ b/test/srcs/floating_point_2696_test.c @@ -0,0 +1,100 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +void rotate(char value[64]) +{ + char temp = value[0]; + for (int i = 0; i < 63; i++) { + value[i] = value[i + 1]; + } + value[63] = temp; +} + +int main() +{ + char value[64]; + + for (int i = 0; i < 64; i++) { + value[i] = i; + } + + __asm__ __volatile__("vmovdqu8 %0, %%zmm0" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm1" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm2" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm3" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm4" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm5" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm6" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm7" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm8" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm9" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm10" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm11" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm12" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm13" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm14" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm15" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm16" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm17" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm18" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm19" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm20" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm21" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm22" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm23" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm24" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm25" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm26" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm27" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm28" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm29" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm30" : : "m" (value)); + rotate(value); + __asm__ __volatile__("vmovdqu8 %0, %%zmm31" : : "m" (value)); + + __asm__ __volatile__("nop"); + + char result[64]; + __asm__ __volatile__("vmovdqu8 %%zmm0, %0" : "=m" (result)); + + unsigned long check = *(unsigned long*)result; + + if (check == 0xdeadbeefdeadbeef) { + __asm__ __volatile__("nop"); + } + + return 0; +} From d1ce898748883b0ab51362dc9cf9b74db8f80b6c Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Thu, 11 Jul 2024 23:07:42 +0200 Subject: [PATCH 005/144] fix: add padding to zmm struct, change wrong static assertions --- libdebug/cffi/ptrace_cffi_build.py | 1 + libdebug/cffi/ptrace_cffi_source.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 407b4dde..52fb0950 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -52,6 +52,7 @@ struct reg_256 zmm0[16]; // zmm1 starts at offset 1664 struct reg_512 zmm1[16]; + unsigned char padding4[8]; }; """ diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 3dc80d0e..d2f7cc1b 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -28,9 +28,9 @@ #if (FPREGS_AVX == 0) _Static_assert(sizeof(struct fp_regs_struct) == 520, "user_fpregs_struct size is not 512 bytes"); #elif (FPREGS_AVX == 1) - _Static_assert(sizeof(struct fp_regs_struct) == 904, "user_fpregs_struct size is not 832 bytes"); + _Static_assert(sizeof(struct fp_regs_struct) == 904, "user_fpregs_struct size is not 896 bytes"); #elif (FPREGS_AVX == 2) - _Static_assert(sizeof(struct fp_regs_struct) == 2704, "user_fpregs_struct size is not 1024 bytes"); + _Static_assert(sizeof(struct fp_regs_struct) == 2704, "user_fpregs_struct size is not 2696 bytes"); #else #error "FPREGS_AVX must be 0, 1 or 2" #endif From 1832d72aa15659ebe3191697f4b8b792b61e58b0 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 12 Jul 2024 00:07:32 +0200 Subject: [PATCH 006/144] test: fix function names in floating_point_test They got picked up by pytest even when they weren't supposed to run --- test/scripts/floating_point_test.py | 33 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/test/scripts/floating_point_test.py b/test/scripts/floating_point_test.py index 4b5f9f84..d213ff38 100644 --- a/test/scripts/floating_point_test.py +++ b/test/scripts/floating_point_test.py @@ -18,20 +18,21 @@ def test_floating_point_reg_access(self): # Let's check if we have AVX512 with Path("/proc/cpuinfo").open() as f: cpuinfo = f.read() - if "avx512" in cpuinfo: - # Run an AVX512 test - self.test_floating_point_reg_access_avx512() - self.test_floating_point_reg_access_avx() - self.test_floating_point_reg_access_generic() - elif "avx" in cpuinfo: - # Run an AVX test - self.test_floating_point_reg_access_avx() - self.test_floating_point_reg_access_generic() - else: - # Run a generic test - self.test_floating_point_reg_access_generic() - - def test_floating_point_reg_access_avx512(self): + + if "avx512" in cpuinfo: + # Run an AVX512 test + self.avx512() + self.avx() + self.mmx() + elif "avx" in cpuinfo: + # Run an AVX test + self.avx() + self.mmx() + else: + # Run a generic test + self.mmx() + + def avx512(self): d = debugger("binaries/floating_point_2696_test") d.run() @@ -71,7 +72,7 @@ def test_floating_point_reg_access_avx512(self): d.kill() - def test_floating_point_reg_access_avx(self): + def avx(self): d = debugger("binaries/floating_point_896_test") d.run() @@ -172,7 +173,7 @@ def callback(t, _): d.kill() - def test_floating_point_reg_access_generic(self): + def mmx(self): d = debugger("binaries/floating_point_512_test") d.run() From d643f9abe79748e2d1c0b0c75544758292bc67d6 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 12 Jul 2024 00:16:40 +0200 Subject: [PATCH 007/144] fix: remove compile args optimization It breaks the CI for some reason? Don't ask me --- libdebug/cffi/ptrace_cffi_build.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 52fb0950..2af384b2 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -284,7 +284,6 @@ + finish_define + f.read(), libraries=[], - extra_compile_args=["-march=native"], ) if __name__ == "__main__": From 7d2a9462780ebf8825defd6ec86cdb5e39524b9f Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 12 Jul 2024 21:53:04 +0200 Subject: [PATCH 008/144] docs: add small explanation about floating point registers --- docs/source/basic_features.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/source/basic_features.rst b/docs/source/basic_features.rst index b2435edf..a8482813 100644 --- a/docs/source/basic_features.rst +++ b/docs/source/basic_features.rst @@ -83,8 +83,11 @@ Register Access =============== .. _register-access-paragraph: -libdebug offers a simple register access interface for supported architectures. The registers are accessed through the regs attribute of the debugger object. The field includes both general purpose and special registers, as well as the flags register. Effectively, any register that can be accessed by an assembly instruction, can also be accessed through the regs attribute. The debugger specifically exposes properties of the main thread, including the registers. See :doc:`multithreading` to learn how to access registers and other properties from different threads. +libdebug offers a simple register access interface for supported architectures. The registers are accessed through the `regs`` attribute of the debugger object. The field includes both general purpose and special registers, as well as the flags register. Effectively, any register that can be accessed by an assembly instruction, can also be accessed through the regs attribute. The debugger specifically exposes properties of the main thread, including the registers. See :doc:`multithreading` to learn how to access registers and other properties from different threads. +Floating point and vector registers are available as well. The syntax is identical to the one used for integer registers. +The list of available AVX registers is determined during installation by checking the CPU capabilities, thus special registers, such as `zmm0` to `zmm31`, are available only on CPUs that support the specific ISA extension. +If you believe that your target CPU supports AVX registers, but they are not available during debugging, please file an issue on the GitHub repository and include your precise hardware details, so that we can investigate and resolve the issue. Memory Access ==================================== @@ -291,4 +294,4 @@ You can also access registers after the process has died. This is useful for *po Supported Architectures ======================= -libdebug currently only supports Linux under the x86_64 (AMD64) architecture. Support for other architectures is planned for future releases. Stay tuned. \ No newline at end of file +libdebug currently only supports Linux under the x86_64 (AMD64) architecture. Support for other architectures is planned for future releases. Stay tuned. From d90d137e66f9854a4a74ed887dc52d8597d518f1 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 08:43:08 +0200 Subject: [PATCH 009/144] feat: migrate .arch from libcontext to InternalDebugger --- .../amd64/amd64_stack_unwinder.py | 1 + .../ptrace_hardware_breakpoint_provider.py | 4 +-- .../ptrace_software_breakpoint_patcher.py | 8 ++--- libdebug/architectures/register_helper.py | 4 +-- .../architectures/stack_unwinding_provider.py | 5 +-- .../syscall_hijacking_provider.py | 5 +-- .../builtin/pretty_print_syscall_handler.py | 4 +-- libdebug/debugger/debugger.py | 14 +++++--- libdebug/debugger/internal_debugger.py | 35 ++++++++++++++----- libdebug/debugger/internal_debugger_holder.py | 1 + libdebug/ptrace/ptrace_interface.py | 3 +- libdebug/ptrace/ptrace_status_handler.py | 2 +- libdebug/state/thread_context.py | 4 +-- libdebug/utils/arch_mappings.py | 27 ++++++++++++++ libdebug/utils/debugger_wrappers.py | 4 +-- libdebug/utils/libcontext.py | 20 +++-------- libdebug/utils/print_style.py | 1 + libdebug/utils/syscall_utils.py | 18 +++++----- test/scripts/finish_test.py | 2 +- 19 files changed, 94 insertions(+), 68 deletions(-) create mode 100644 libdebug/utils/arch_mappings.py diff --git a/libdebug/architectures/amd64/amd64_stack_unwinder.py b/libdebug/architectures/amd64/amd64_stack_unwinder.py index a07e077a..489f8a4e 100644 --- a/libdebug/architectures/amd64/amd64_stack_unwinder.py +++ b/libdebug/architectures/amd64/amd64_stack_unwinder.py @@ -13,6 +13,7 @@ if TYPE_CHECKING: from libdebug.state.thread_context import ThreadContext + class Amd64StackUnwinder(StackUnwindingManager): """Class that provides stack unwinding for the x86_64 architecture.""" diff --git a/libdebug/architectures/ptrace_hardware_breakpoint_provider.py b/libdebug/architectures/ptrace_hardware_breakpoint_provider.py index 9be66609..8a91142c 100644 --- a/libdebug/architectures/ptrace_hardware_breakpoint_provider.py +++ b/libdebug/architectures/ptrace_hardware_breakpoint_provider.py @@ -13,17 +13,15 @@ PtraceHardwareBreakpointManager, ) from libdebug.state.thread_context import ThreadContext -from libdebug.utils.libcontext import libcontext def ptrace_hardware_breakpoint_manager_provider( + architecture: str, thread: ThreadContext, peek_user: Callable[[int, int], int], poke_user: Callable[[int, int, int], None], ) -> PtraceHardwareBreakpointManager: """Returns an instance of the hardware breakpoint manager to be used by the `_InternalDebugger` class.""" - architecture = libcontext.arch - match architecture: case "amd64": return Amd64PtraceHardwareBreakpointManager(thread, peek_user, poke_user) diff --git a/libdebug/architectures/ptrace_software_breakpoint_patcher.py b/libdebug/architectures/ptrace_software_breakpoint_patcher.py index 726e6637..0b043a56 100644 --- a/libdebug/architectures/ptrace_software_breakpoint_patcher.py +++ b/libdebug/architectures/ptrace_software_breakpoint_patcher.py @@ -4,15 +4,13 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -from libdebug.utils.libcontext import libcontext - -def software_breakpoint_byte_size() -> int: +def software_breakpoint_byte_size(architecture: str) -> int: """Return the size of a software breakpoint instruction.""" - match libcontext.arch: + match architecture: case "amd64": return 1 case "x86": return 1 case _: - raise ValueError(f"Unsupported architecture: {libcontext.arch}") + raise ValueError(f"Unsupported architecture: {architecture}") diff --git a/libdebug/architectures/register_helper.py b/libdebug/architectures/register_helper.py index 1bec5112..185773f3 100644 --- a/libdebug/architectures/register_helper.py +++ b/libdebug/architectures/register_helper.py @@ -10,17 +10,15 @@ Amd64PtraceRegisterHolder, ) from libdebug.data.register_holder import RegisterHolder -from libdebug.utils.libcontext import libcontext def register_holder_provider( + architecture: str, register_file: object, _: Callable[[], object] | None = None, __: Callable[[object], None] | None = None, ) -> RegisterHolder: """Returns an instance of the register holder to be used by the `_InternalDebugger` class.""" - architecture = libcontext.arch - match architecture: case "amd64": return Amd64PtraceRegisterHolder(register_file) diff --git a/libdebug/architectures/stack_unwinding_provider.py b/libdebug/architectures/stack_unwinding_provider.py index 052e06de..f0a84840 100644 --- a/libdebug/architectures/stack_unwinding_provider.py +++ b/libdebug/architectures/stack_unwinding_provider.py @@ -8,15 +8,12 @@ Amd64StackUnwinder, ) from libdebug.architectures.stack_unwinding_manager import StackUnwindingManager -from libdebug.utils.libcontext import libcontext _amd64_stack_unwinder = Amd64StackUnwinder() -def stack_unwinding_provider() -> StackUnwindingManager: +def stack_unwinding_provider(architecture: str) -> StackUnwindingManager: """Returns an instance of the stack unwinding provider to be used by the `_InternalDebugger` class.""" - architecture = libcontext.arch - match architecture: case "amd64": return _amd64_stack_unwinder diff --git a/libdebug/architectures/syscall_hijacking_provider.py b/libdebug/architectures/syscall_hijacking_provider.py index 4f2886a7..3dfafabc 100644 --- a/libdebug/architectures/syscall_hijacking_provider.py +++ b/libdebug/architectures/syscall_hijacking_provider.py @@ -8,15 +8,12 @@ Amd64SyscallHijacker, ) from libdebug.architectures.syscall_hijacking_manager import SyscallHijackingManager -from libdebug.utils.libcontext import libcontext _amd64_syscall_hijacker = Amd64SyscallHijacker() -def syscall_hijacking_provider() -> SyscallHijackingManager: +def syscall_hijacking_provider(architecture: str) -> SyscallHijackingManager: """Returns an instance of the syscall hijacking provider to be used by the `_InternalDebugger` class.""" - architecture = libcontext.arch - match architecture: case "amd64": return _amd64_syscall_hijacker diff --git a/libdebug/builtin/pretty_print_syscall_handler.py b/libdebug/builtin/pretty_print_syscall_handler.py index c9b204f2..4dca9e41 100644 --- a/libdebug/builtin/pretty_print_syscall_handler.py +++ b/libdebug/builtin/pretty_print_syscall_handler.py @@ -26,8 +26,8 @@ def pprint_on_enter(d: ThreadContext, syscall_number: int, **kwargs: int) -> Non syscall_number (int): the syscall number. **kwargs (bool): the keyword arguments. """ - syscall_name = resolve_syscall_name(syscall_number) - syscall_args = resolve_syscall_arguments(syscall_number) + syscall_name = resolve_syscall_name(d._internal_debugger.arch, syscall_number) + syscall_args = resolve_syscall_arguments(d._internal_debugger.arch, syscall_number) values = [ d.syscall_arg0, diff --git a/libdebug/debugger/debugger.py b/libdebug/debugger/debugger.py index d7437be1..749516ee 100644 --- a/libdebug/debugger/debugger.py +++ b/libdebug/debugger/debugger.py @@ -395,7 +395,10 @@ def syscalls_to_pprint(self: Debugger) -> list[str] | None: if self._internal_debugger.syscalls_to_pprint is None: return None else: - return [resolve_syscall_name(v) for v in self._internal_debugger.syscalls_to_pprint] + return [ + resolve_syscall_name(self._internal_debugger.arch, v) + for v in self._internal_debugger.syscalls_to_pprint + ] @syscalls_to_pprint.setter def syscalls_to_pprint(self: Debugger, value: list[int | str] | None) -> None: @@ -408,7 +411,7 @@ def syscalls_to_pprint(self: Debugger, value: list[int | str] | None) -> None: self._internal_debugger.syscalls_to_pprint = None elif isinstance(value, list): self._internal_debugger.syscalls_to_pprint = [ - v if isinstance(v, int) else resolve_syscall_number(v) for v in value + v if isinstance(v, int) else resolve_syscall_number(self._internal_debugger.arch, v) for v in value ] else: raise ValueError( @@ -427,7 +430,10 @@ def syscalls_to_not_pprint(self: Debugger) -> list[str] | None: if self._internal_debugger.syscalls_to_not_pprint is None: return None else: - return [resolve_syscall_name(v) for v in self._internal_debugger.syscalls_to_not_pprint] + return [ + resolve_syscall_name(self._internal_debugger.arch, v) + for v in self._internal_debugger.syscalls_to_not_pprint + ] @syscalls_to_not_pprint.setter def syscalls_to_not_pprint(self: Debugger, value: list[int | str] | None) -> None: @@ -440,7 +446,7 @@ def syscalls_to_not_pprint(self: Debugger, value: list[int | str] | None) -> Non self._internal_debugger.syscalls_to_not_pprint = None elif isinstance(value, list): self._internal_debugger.syscalls_to_not_pprint = [ - v if isinstance(v, int) else resolve_syscall_number(v) for v in value + v if isinstance(v, int) else resolve_syscall_number(self._internal_debugger.arch, v) for v in value ] else: raise ValueError( diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 25a7e386..f6ec6ac9 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -33,6 +33,7 @@ from libdebug.interfaces.interface_helper import provide_debugging_interface from libdebug.liblog import liblog from libdebug.state.resume_context import ResumeContext +from libdebug.utils.arch_mappings import map_arch from libdebug.utils.debugger_wrappers import ( background_alias, change_state_function_process, @@ -73,6 +74,9 @@ class InternalDebugger: aslr_enabled: bool """A flag that indicates if ASLR is enabled or not.""" + arch: str + """The architecture of the debugged process.""" + argv: list[str] """The command line arguments of the debugged process.""" @@ -162,10 +166,11 @@ def __init__(self: InternalDebugger) -> None: self.pprint_syscalls = False self.pipe_manager = None self.process_id = 0 - self.threads = list() + self.threads = [] self.instanced = False self._is_running = False self.resume_context = ResumeContext() + self._arch = "amd64" self.__polling_thread_command_queue = Queue() self.__polling_thread_response_queue = Queue() @@ -278,6 +283,16 @@ def detach(self: InternalDebugger) -> None: self._join_and_check_status() + @property + def arch(self: InternalDebugger) -> str: + """The architecture of the debugged process.""" + return self._arch + + @arch.setter + def arch(self: InternalDebugger, value: str) -> None: + """The architecture of the debugged process.""" + self._arch = map_arch(value) + @background_alias(_background_invalid_call) def kill(self: InternalDebugger) -> None: """Kills the process.""" @@ -559,7 +574,7 @@ def handle_syscall( Returns: HandledSyscall: The HandledSyscall object. """ - syscall_number = resolve_syscall_number(syscall) if isinstance(syscall, str) else syscall + syscall_number = resolve_syscall_number(self.arch, syscall) if isinstance(syscall, str) else syscall if not isinstance(recursive, bool): raise TypeError("recursive must be a boolean") @@ -569,7 +584,7 @@ def handle_syscall( handler = self.handled_syscalls[syscall_number] if handler.on_enter_user or handler.on_exit_user: liblog.warning( - f"Syscall {resolve_syscall_name(syscall_number)} is already handled by a user-defined handler. Overriding it.", + f"Syscall {resolve_syscall_name(self.arch, syscall_number)} is already handled by a user-defined handler. Overriding it.", ) handler.on_enter_user = on_enter handler.on_exit_user = on_exit @@ -616,22 +631,24 @@ def hijack_syscall( Returns: HandledSyscall: The HandledSyscall object. """ - if set(kwargs) - syscall_hijacking_provider().allowed_args: + if set(kwargs) - syscall_hijacking_provider(self.arch).allowed_args: raise ValueError("Invalid keyword arguments in syscall hijack") if isinstance(original_syscall, str): - original_syscall_number = resolve_syscall_number(original_syscall) + original_syscall_number = resolve_syscall_number(self.arch, original_syscall) else: original_syscall_number = original_syscall - new_syscall_number = resolve_syscall_number(new_syscall) if isinstance(new_syscall, str) else new_syscall + new_syscall_number = ( + resolve_syscall_number(self.arch, new_syscall) if isinstance(new_syscall, str) else new_syscall + ) if original_syscall_number == new_syscall_number: raise ValueError( "The original syscall and the new syscall must be different during hijacking.", ) - on_enter = syscall_hijacking_provider().create_hijacker( + on_enter = syscall_hijacking_provider(self.arch).create_hijacker( new_syscall_number, **kwargs, ) @@ -894,7 +911,7 @@ def enable_pretty_print( """Handles a syscall in the target process to pretty prints its arguments and return value.""" self._ensure_process_stopped() - syscall_numbers = get_all_syscall_numbers() + syscall_numbers = get_all_syscall_numbers(self.arch) for syscall_number in syscall_numbers: # Check if the syscall is already handled (by the user or by the pretty print handler) @@ -1350,7 +1367,7 @@ def _poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: def _enable_antidebug_escaping(self: InternalDebugger) -> None: """Enables the anti-debugging escape mechanism.""" handler = SyscallHandler( - resolve_syscall_number("ptrace"), + resolve_syscall_number(self.arch, "ptrace"), on_enter_ptrace, on_exit_ptrace, None, diff --git a/libdebug/debugger/internal_debugger_holder.py b/libdebug/debugger/internal_debugger_holder.py index 186a307b..14778a73 100644 --- a/libdebug/debugger/internal_debugger_holder.py +++ b/libdebug/debugger/internal_debugger_holder.py @@ -12,6 +12,7 @@ @dataclass class InternalDebuggerHolder: """A holder for internal debuggers.""" + internal_debuggers: WeakKeyDictionary = field(default_factory=WeakKeyDictionary) global_internal_debugger = None internal_debugger_lock = Lock() diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index fa549081..1fffbfcf 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -461,13 +461,14 @@ def register_new_thread(self: PtraceInterface, new_thread_id: int) -> None: new_thread_id, ) - register_holder = register_holder_provider(register_file) + register_holder = register_holder_provider(self._internal_debugger.arch, register_file) with extend_internal_debugger(self._internal_debugger): thread = ThreadContext(new_thread_id, register_holder) self._internal_debugger.insert_new_thread(thread) thread_hw_bp_helper = ptrace_hardware_breakpoint_manager_provider( + self._internal_debugger.arch, thread, self._peek_user, self._poke_user, diff --git a/libdebug/ptrace/ptrace_status_handler.py b/libdebug/ptrace/ptrace_status_handler.py index dbdc0bf2..f535f932 100644 --- a/libdebug/ptrace/ptrace_status_handler.py +++ b/libdebug/ptrace/ptrace_status_handler.py @@ -91,7 +91,7 @@ def _handle_breakpoints(self: PtraceStatusHandler, thread_id: int) -> bool: else: # If the trap was caused by a software breakpoint, we need to restore the original instruction # and set the instruction pointer to the previous instruction. - ip -= software_breakpoint_byte_size() + ip -= software_breakpoint_byte_size(self.internal_debugger.arch) if ip in enabled_breakpoints: # Software breakpoint hit diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index f0b5d193..acb61c38 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -177,7 +177,7 @@ def backtrace(self: ThreadContext) -> list: """Returns the current backtrace of the thread.""" internal_debugger = self._internal_debugger internal_debugger._ensure_process_stopped() - stack_unwinder = stack_unwinding_provider() + stack_unwinder = stack_unwinding_provider(internal_debugger.arch) backtrace = stack_unwinder.unwind(self) maps = internal_debugger.debugging_interface.maps() return [resolve_address_in_maps(x, maps) for x in backtrace] @@ -185,7 +185,7 @@ def backtrace(self: ThreadContext) -> list: def current_return_address(self: ThreadContext) -> int: """Returns the return address of the current function.""" self._internal_debugger._ensure_process_stopped() - stack_unwinder = stack_unwinding_provider() + stack_unwinder = stack_unwinding_provider(self._internal_debugger.arch) return stack_unwinder.get_return_address(self) def step(self: ThreadContext) -> None: diff --git a/libdebug/utils/arch_mappings.py b/libdebug/utils/arch_mappings.py new file mode 100644 index 00000000..f6f67f98 --- /dev/null +++ b/libdebug/utils/arch_mappings.py @@ -0,0 +1,27 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +ARCH_MAPPING = { + "x86": "i386", + "x86_64": "amd64", +} + + +def map_arch(arch: str) -> str: + """Map the architecture to the correct format. + + Args: + arch (str): the architecture to map. + + Returns: + str: the mapped architecture. + """ + if arch in ARCH_MAPPING.values(): + return arch + elif arch in ARCH_MAPPING: + return ARCH_MAPPING[arch] + else: + raise ValueError(f"Architecture {arch} not supported.") diff --git a/libdebug/utils/debugger_wrappers.py b/libdebug/utils/debugger_wrappers.py index 9c57fff8..db6ecae3 100644 --- a/libdebug/utils/debugger_wrappers.py +++ b/libdebug/utils/debugger_wrappers.py @@ -39,9 +39,7 @@ def change_state_function_thread(method: callable) -> callable: """Decorator to perfom control flow checks before executing a method.""" @wraps(method) - def wrapper( - self: InternalDebugger, thread: ThreadContext, *args: ..., **kwargs: ... - ) -> ...: + def wrapper(self: InternalDebugger, thread: ThreadContext, *args: ..., **kwargs: ...) -> ...: if not self.instanced: raise RuntimeError( "Process not running. Did you call run()?", diff --git a/libdebug/utils/libcontext.py b/libdebug/utils/libcontext.py index 22029f33..cb23b997 100644 --- a/libdebug/utils/libcontext.py +++ b/libdebug/utils/libcontext.py @@ -6,6 +6,7 @@ from __future__ import annotations +import platform import sys from contextlib import contextmanager from copy import deepcopy @@ -55,7 +56,6 @@ def __init__(self: LibContext) -> None: self._general_logger = "DEBUG" self._initialized = True - self._arch = "amd64" self._terminal = [] def _set_debug_level_for_all(self: LibContext) -> None: @@ -139,21 +139,9 @@ def general_logger(self: LibContext, value: str) -> None: raise ValueError("general_logger must be a valid logging level") @property - def arch(self: LibContext) -> str: - """Property getter for architecture. - - Returns: - _arch (str): the current architecture. - """ - return self._arch - - @arch.setter - def arch(self: LibContext, value: str) -> None: - """Property setter for arch, ensuring it's a valid architecture.""" - if value in ["amd64"]: - self._arch = value - else: - raise RuntimeError("The specified architecture is not supported") + def platform(self: LibContext) -> str: + """Return the current platform.""" + return platform.machine() @property def terminal(self: LibContext) -> list[str]: diff --git a/libdebug/utils/print_style.py b/libdebug/utils/print_style.py index 9fe884b7..48dca068 100644 --- a/libdebug/utils/print_style.py +++ b/libdebug/utils/print_style.py @@ -4,6 +4,7 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # + class PrintStyle: """Class to define colors for the terminal.""" diff --git a/libdebug/utils/syscall_utils.py b/libdebug/utils/syscall_utils.py index ca4bdc9c..2a38ec69 100644 --- a/libdebug/utils/syscall_utils.py +++ b/libdebug/utils/syscall_utils.py @@ -10,8 +10,6 @@ import requests -from libdebug.utils.libcontext import libcontext - SYSCALLS_REMOTE = "https://syscalls.mebeim.net/db" LOCAL_FOLDER_PATH = (Path.home() / ".cache" / "libdebug" / "syscalls").resolve() @@ -55,9 +53,9 @@ def get_syscall_definitions(arch: str) -> dict: @functools.cache -def resolve_syscall_number(name: str) -> int: +def resolve_syscall_number(architecture: str, name: str) -> int: """Resolve a syscall name to its number.""" - definitions = get_syscall_definitions(libcontext.arch) + definitions = get_syscall_definitions(architecture) for syscall in definitions["syscalls"]: if syscall["name"] == name: @@ -67,9 +65,9 @@ def resolve_syscall_number(name: str) -> int: @functools.cache -def resolve_syscall_name(number: int) -> str: +def resolve_syscall_name(architecture: str, number: int) -> str: """Resolve a syscall number to its name.""" - definitions = get_syscall_definitions(libcontext.arch) + definitions = get_syscall_definitions(architecture) for syscall in definitions["syscalls"]: if syscall["number"] == number: @@ -79,9 +77,9 @@ def resolve_syscall_name(number: int) -> str: @functools.cache -def resolve_syscall_arguments(number: int) -> list[str]: +def resolve_syscall_arguments(architecture: str, number: int) -> list[str]: """Resolve a syscall number to its argument definition.""" - definitions = get_syscall_definitions(libcontext.arch) + definitions = get_syscall_definitions(architecture) for syscall in definitions["syscalls"]: if syscall["number"] == number: @@ -91,8 +89,8 @@ def resolve_syscall_arguments(number: int) -> list[str]: @functools.cache -def get_all_syscall_numbers() -> list[int]: +def get_all_syscall_numbers(architecture: str) -> list[int]: """Retrieves all the syscall numbers.""" - definitions = get_syscall_definitions(libcontext.arch) + definitions = get_syscall_definitions(architecture) return [syscall["number"] for syscall in definitions["syscalls"]] diff --git a/test/scripts/finish_test.py b/test/scripts/finish_test.py index ae120fe3..ce968c82 100644 --- a/test/scripts/finish_test.py +++ b/test/scripts/finish_test.py @@ -229,7 +229,7 @@ def test_heuristic_return_address(self): self.assertEqual(d.regs.rip, C_ADDRESS) - stack_unwinder = stack_unwinding_provider() + stack_unwinder = stack_unwinding_provider(d._internal_debugger.arch) # We need to repeat the check for the three stages of the function preamble From 01ff9ae92bbdf2debc0cc51e27a166582196bb6e Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 08:48:49 +0200 Subject: [PATCH 010/144] feat: autodetect arch from ELF file --- libdebug/debugger/internal_debugger.py | 2 +- libdebug/libdebug.py | 5 +++++ libdebug/utils/arch_mappings.py | 1 + libdebug/utils/elf_utils.py | 16 ++++++++++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index f6ec6ac9..ed70e2a8 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -170,7 +170,7 @@ def __init__(self: InternalDebugger) -> None: self.instanced = False self._is_running = False self.resume_context = ResumeContext() - self._arch = "amd64" + self._arch = map_arch(libcontext.platform) self.__polling_thread_command_queue = Queue() self.__polling_thread_response_queue = Queue() diff --git a/libdebug/libdebug.py b/libdebug/libdebug.py index 55819051..67a473fe 100644 --- a/libdebug/libdebug.py +++ b/libdebug/libdebug.py @@ -7,6 +7,7 @@ from libdebug.debugger.debugger import Debugger from libdebug.debugger.internal_debugger import InternalDebugger +from libdebug.utils.elf_utils import elf_architecture def debugger( @@ -41,6 +42,10 @@ def debugger( internal_debugger.auto_interrupt_on_command = auto_interrupt_on_command internal_debugger.escape_antidebug = escape_antidebug + # If we are attaching, we assume the architecture is the same as the current platform + if argv: + internal_debugger.arch = elf_architecture(argv[0]) + debugger = Debugger() debugger.post_init_(internal_debugger) diff --git a/libdebug/utils/arch_mappings.py b/libdebug/utils/arch_mappings.py index f6f67f98..d4f5d79f 100644 --- a/libdebug/utils/arch_mappings.py +++ b/libdebug/utils/arch_mappings.py @@ -7,6 +7,7 @@ ARCH_MAPPING = { "x86": "i386", "x86_64": "amd64", + "x64": "amd64", } diff --git a/libdebug/utils/elf_utils.py b/libdebug/utils/elf_utils.py index f441040e..2da1735d 100644 --- a/libdebug/utils/elf_utils.py +++ b/libdebug/utils/elf_utils.py @@ -239,3 +239,19 @@ def get_entry_point(path: str) -> int: elf = ELFFile(elf_file) return elf.header.e_entry + + +@functools.cache +def elf_architecture(path: str) -> str: + """Returns the architecture of the specified ELF file. + + Args: + path (str): The path to the ELF file. + + Returns: + str: The architecture of the specified ELF file. + """ + with Path(path).open("rb") as elf_file: + elf = ELFFile(elf_file) + + return elf.get_machine_arch() From 5eb54d2d60b0c32d13fc7ab1b47612833ac0a8e6 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 08:57:30 +0200 Subject: [PATCH 011/144] fix: don't assume system endianness and byte order --- libdebug/debugger/internal_debugger.py | 13 +++++++++---- libdebug/utils/platform_utils.py | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 libdebug/utils/platform_utils.py diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index ed70e2a8..14be9506 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -10,6 +10,7 @@ import functools import os import signal +import sys from pathlib import Path from queue import Queue from signal import SIGKILL, SIGSTOP, SIGTRAP @@ -45,6 +46,7 @@ resolve_symbol_in_maps, ) from libdebug.utils.libcontext import libcontext +from libdebug.utils.platform_utils import get_platform_register_size from libdebug.utils.print_style import PrintStyle from libdebug.utils.signal_utils import ( resolve_signal_name, @@ -200,7 +202,11 @@ def start_up(self: InternalDebugger) -> None: self.start_processing_thread() with extend_internal_debugger(self): self.debugging_interface = provide_debugging_interface() - self.memory = MemoryView(self._peek_memory, self._poke_memory) + self.memory = MemoryView( + self._peek_memory, + self._poke_memory, + unit_size=get_platform_register_size(self.arch), + ) def start_processing_thread(self: InternalDebugger) -> None: """Starts the thread that will poll the traced process for state change.""" @@ -1304,13 +1310,12 @@ def __threaded_migrate_from_gdb(self: InternalDebugger) -> None: def __threaded_peek_memory(self: InternalDebugger, address: int) -> bytes | BaseException: try: value = self.debugging_interface.peek_memory(address) - # TODO: this is only for amd64 - return value.to_bytes(8, "little") + return value.to_bytes(get_platform_register_size(self.arch), sys.byteorder) except BaseException as e: return e def __threaded_poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: - int_data = int.from_bytes(data, "little") + int_data = int.from_bytes(data, sys.byteorder) self.debugging_interface.poke_memory(address, int_data) @background_alias(__threaded_peek_memory) diff --git a/libdebug/utils/platform_utils.py b/libdebug/utils/platform_utils.py new file mode 100644 index 00000000..f83b7e88 --- /dev/null +++ b/libdebug/utils/platform_utils.py @@ -0,0 +1,23 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + + +def get_platform_register_size(arch: str) -> int: + """Get the register size of the platform. + + Args: + arch (str): The architecture of the platform. + + Returns: + int: The register size in bytes. + """ + match arch: + case "amd64": + return 8 + case "aarch64": + return 8 + case _: + raise ValueError(f"Architecture {arch} not supported.") From aa300a4d19544a61db72ef517e8529d16a662e0e Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 17:11:24 +0200 Subject: [PATCH 012/144] feat: add basic support for aarch64 --- .../aarch64/aarch64_ptrace_register_holder.py | 88 +++++++++++++++++++ .../aarch64/aarch64_registers.py | 21 +++++ .../ptrace_software_breakpoint_patcher.py | 4 +- libdebug/architectures/register_helper.py | 5 ++ libdebug/cffi/ptrace_cffi_build.py | 82 +++++++++++++++-- libdebug/cffi/ptrace_cffi_source.c | 4 +- 6 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py create mode 100644 libdebug/architectures/aarch64/aarch64_registers.py diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py new file mode 100644 index 00000000..12e3e5c3 --- /dev/null +++ b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py @@ -0,0 +1,88 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from libdebug.architectures.aarch64.aarch64_registers import Aarch64Registers +from libdebug.ptrace.ptrace_register_holder import PtraceRegisterHolder + +if TYPE_CHECKING: + from libdebug.state.thread_context import ThreadContext + +AARCH64_GP_REGS = ["x", "w"] + + +def _get_property_64(name: str) -> property: + def getter(self: Aarch64Registers) -> int: + self._internal_debugger._ensure_process_stopped() + return getattr(self.register_file, name) + + def setter(self: Aarch64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + setattr(self.register_file, name, value) + + return property(getter, setter, None, name) + + +def _get_property_32(name: str) -> property: + def getter(self: Aarch64Registers) -> int: + self._internal_debugger._ensure_process_stopped() + return getattr(self.register_file, name) & 0xFFFFFFFF + + def setter(self: Aarch64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + return setattr(self.register_file, name, value & 0xFFFFFFFF) + + return property(getter, setter, None, name) + + +@dataclass +class Aarch64PtraceRegisterHolder(PtraceRegisterHolder): + """A class that provides views and setters for the register of an aarch64 process.""" + + def provide_regs_class(self: Aarch64PtraceRegisterHolder) -> type: + """Provide a class to hold the register accessors.""" + return Aarch64Registers + + def apply_on_regs(self: Aarch64PtraceRegisterHolder, target: Aarch64Registers, target_class: type) -> None: + """Apply the register accessors to the Aarch64Registers class.""" + target.register_file = self.register_file + + if hasattr(target_class, "w0"): + return + + for i in range(31): + name_64 = f"w{i}" + name_32 = f"x{i}" + + setattr(target_class, name_64, _get_property_64(name_64)) + setattr(target_class, name_32, _get_property_32(name_32)) + + target_class.pc = _get_property_64("pc") + + def apply_on_thread(self: Aarch64PtraceRegisterHolder, target: ThreadContext, target_class: type) -> None: + """Apply the register accessors to the thread class.""" + target.register_file = self.register_file + + # If the accessors are already defined, we don't need to redefine them + if hasattr(target_class, "instruction_pointer"): + return + + # setup generic "instruction_pointer" property + target_class.instruction_pointer = _get_property_64("pc") + + # setup generic syscall properties + target_class.syscall_number = _get_property_64("x8") + target_class.syscall_return = _get_property_64("x0") + target_class.syscall_arg0 = _get_property_64("x0") + target_class.syscall_arg1 = _get_property_64("x1") + target_class.syscall_arg2 = _get_property_64("x2") + target_class.syscall_arg3 = _get_property_64("x3") + target_class.syscall_arg4 = _get_property_64("x4") + target_class.syscall_arg5 = _get_property_64("x5") diff --git a/libdebug/architectures/aarch64/aarch64_registers.py b/libdebug/architectures/aarch64/aarch64_registers.py new file mode 100644 index 00000000..d3a0ed93 --- /dev/null +++ b/libdebug/architectures/aarch64/aarch64_registers.py @@ -0,0 +1,21 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from dataclasses import dataclass + +from libdebug.data.registers import Registers +from libdebug.debugger.internal_debugger_instance_manager import get_global_internal_debugger + + +@dataclass +class Aarch64Registers(Registers): + """This class holds the state of the architectural-dependent registers of a process.""" + + def __init__(self: Aarch64Registers) -> None: + """Initializes the Registers object.""" + self._internal_debugger = get_global_internal_debugger() diff --git a/libdebug/architectures/ptrace_software_breakpoint_patcher.py b/libdebug/architectures/ptrace_software_breakpoint_patcher.py index 0b043a56..0a5d21be 100644 --- a/libdebug/architectures/ptrace_software_breakpoint_patcher.py +++ b/libdebug/architectures/ptrace_software_breakpoint_patcher.py @@ -10,7 +10,7 @@ def software_breakpoint_byte_size(architecture: str) -> int: match architecture: case "amd64": return 1 - case "x86": - return 1 + case "aarch64": + return 4 case _: raise ValueError(f"Unsupported architecture: {architecture}") diff --git a/libdebug/architectures/register_helper.py b/libdebug/architectures/register_helper.py index 185773f3..fe1ba8ed 100644 --- a/libdebug/architectures/register_helper.py +++ b/libdebug/architectures/register_helper.py @@ -6,6 +6,9 @@ from collections.abc import Callable +from libdebug.architectures.aarch64.aarch64_ptrace_register_holder import ( + Aarch64PtraceRegisterHolder, +) from libdebug.architectures.amd64.amd64_ptrace_register_holder import ( Amd64PtraceRegisterHolder, ) @@ -22,5 +25,7 @@ def register_holder_provider( match architecture: case "amd64": return Amd64PtraceRegisterHolder(register_file) + case "aarch64": + return Aarch64PtraceRegisterHolder(register_file) case _: raise NotImplementedError(f"Architecture {architecture} not available.") diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 7e8345ad..443a244a 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -8,9 +8,12 @@ from cffi import FFI -if platform.machine() == "x86_64": - user_regs_struct = """ - struct user_regs_struct + +architecture = platform.machine() + +if architecture == "x86_64": + ptrace_regs_struct = """ + struct ptrace_regs_struct { unsigned long r15; unsigned long r14; @@ -74,13 +77,80 @@ return 0; // Not a CALL } """ +elif architecture == "aarch64": + ptrace_regs_struct = """ + struct ptrace_regs_struct + { + unsigned long x0; + unsigned long x1; + unsigned long x2; + unsigned long x3; + unsigned long x4; + unsigned long x5; + unsigned long x6; + unsigned long x7; + unsigned long x8; + unsigned long x9; + unsigned long x10; + unsigned long x11; + unsigned long x12; + unsigned long x13; + unsigned long x14; + unsigned long x15; + unsigned long x16; + unsigned long x17; + unsigned long x18; + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long x29; + unsigned long x30; + unsigned long sp; + unsigned long pc; + unsigned long pstate; + }; + """ + + breakpoint_define = """ + #define INSTRUCTION_POINTER(regs) (regs.pc) + #define INSTALL_BREAKPOINT(instruction) ((instruction & 0xFFFFFFFFFFFFFF00) | 0xD4200000) + #define BREAKPOINT_SIZE 4 + #define IS_SW_BREAKPOINT(instruction) (instruction == 0xD4200000) + """ + + finish_define = """ + #define IS_RET_INSTRUCTION(instruction) (instruction == 0xD65F03C0) + + // AARCH64 Architecture specific + int IS_CALL_INSTRUCTION(uint8_t* instr) + { + // Check for direct CALL (BL) + if ((instr[0] & 0xFC) == 0x94) { + return 1; // It's a CALL + } + + // Check for indirect CALL (BLR) + if ((instr[0] & 0xFC) == 0x90) { + return 1; // It's a CALL + } + + return 0; // Not a CALL + } + """ else: raise NotImplementedError(f"Architecture {platform.machine()} not available.") ffibuilder = FFI() ffibuilder.cdef( - user_regs_struct + ptrace_regs_struct + """ struct ptrace_hit_bp { int pid; @@ -99,7 +169,7 @@ struct thread { int tid; - struct user_regs_struct regs; + struct ptrace_regs_struct regs; int signal_to_forward; struct thread *next; }; @@ -143,7 +213,7 @@ struct thread_status *wait_all_and_update_regs(struct global_state *state, int pid); void free_thread_status_list(struct thread_status *head); - struct user_regs_struct* register_thread(struct global_state *state, int tid); + struct ptrace_regs_struct* register_thread(struct global_state *state, int tid); void unregister_thread(struct global_state *state, int tid); void free_thread_list(struct global_state *state); diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 92d0bc96..7cb93e1b 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -31,7 +31,7 @@ struct software_breakpoint { struct thread { int tid; - struct user_regs_struct regs; + struct ptrace_regs_struct regs; int signal_to_forward; struct thread *next; }; @@ -48,7 +48,7 @@ struct global_state { _Bool handle_syscall_enabled; }; -struct user_regs_struct *register_thread(struct global_state *state, int tid) +struct ptrace_regs_struct *register_thread(struct global_state *state, int tid) { // Verify if the thread is already registered struct thread *t = state->t_HEAD; From eb5396c362e58ff1eb127cb03a687579d4d77eb3 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 17:37:01 +0200 Subject: [PATCH 013/144] feat: add register access for aarch64 --- libdebug/cffi/ptrace_cffi_build.py | 9 ++++ libdebug/cffi/ptrace_cffi_source.c | 84 +++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index c8f3a6f6..5540a944 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -158,6 +158,10 @@ }; """ + arch_define = """ + #define ARCH_AMD64 + """ + breakpoint_define = """ #define INSTRUCTION_POINTER(regs) (regs.rip) #define INSTALL_BREAKPOINT(instruction) ((instruction & 0xFFFFFFFFFFFFFF00) | 0xCC) @@ -235,6 +239,10 @@ }; """ + arch_define = """ + #define ARCH_AARCH64 + """ + breakpoint_define = """ #define INSTRUCTION_POINTER(regs) (regs.pc) #define INSTALL_BREAKPOINT(instruction) ((instruction & 0xFFFFFFFFFFFFFF00) | 0xD4200000) @@ -353,6 +361,7 @@ ffibuilder.set_source( "libdebug.cffi._ptrace_cffi", ptrace_regs_struct + + arch_define + fp_regs_struct + fpregs_define + breakpoint_define diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 588533c8..ae7a73f0 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -17,22 +17,24 @@ #include // Run some static assertions to ensure that the fp types are correct -#ifndef FPREGS_AVX - #error "FPREGS_AVX must be defined" -#endif +#ifdef ARCH_AMD64 + #ifndef FPREGS_AVX + #error "FPREGS_AVX must be defined" + #endif -#ifndef XSAVE - #error "XSAVE must be defined" -#endif + #ifndef XSAVE + #error "XSAVE must be defined" + #endif -#if (FPREGS_AVX == 0) - _Static_assert(sizeof(struct fp_regs_struct) == 520, "user_fpregs_struct size is not 512 bytes"); -#elif (FPREGS_AVX == 1) - _Static_assert(sizeof(struct fp_regs_struct) == 904, "user_fpregs_struct size is not 896 bytes"); -#elif (FPREGS_AVX == 2) - _Static_assert(sizeof(struct fp_regs_struct) == 2704, "user_fpregs_struct size is not 2696 bytes"); -#else - #error "FPREGS_AVX must be 0, 1 or 2" + #if (FPREGS_AVX == 0) + _Static_assert(sizeof(struct fp_regs_struct) == 520, "user_fpregs_struct size is not 512 bytes"); + #elif (FPREGS_AVX == 1) + _Static_assert(sizeof(struct fp_regs_struct) == 904, "user_fpregs_struct size is not 896 bytes"); + #elif (FPREGS_AVX == 2) + _Static_assert(sizeof(struct fp_regs_struct) == 2704, "user_fpregs_struct size is not 2696 bytes"); + #else + #error "FPREGS_AVX must be 0, 1 or 2" + #endif #endif struct ptrace_hit_bp { @@ -71,6 +73,36 @@ struct global_state { _Bool handle_syscall_enabled; }; +#ifdef ARCH_AMD64 +unsigned long getregs(int tid, struct ptrace_regs_struct *regs) +{ + return ptrace(PTRACE_GETREGS, tid, NULL, regs); +} + +unsigned long setregs(int tid, struct ptrace_regs_struct *regs) +{ + return ptrace(PTRACE_SETREGS, tid, NULL, regs); +} +#endif + +#ifdef ARCH_AARCH64 +unsigned long getregs(int tid, struct ptrace_regs_struct *regs) +{ + struct iovec iov; + iov.iov_base = regs; + iov.iov_len = sizeof(struct ptrace_regs_struct); + return ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &iov); +} + +unsigned long setregs(int tid, struct ptrace_regs_struct *regs) +{ + struct iovec iov; + iov.iov_base = regs; + iov.iov_len = sizeof(struct ptrace_regs_struct); + return ptrace(PTRACE_SETREGSET, tid, NT_PRSTATUS, &iov); +} +#endif + struct thread *get_thread(struct global_state *state, int tid) { struct thread *t = state->t_HEAD; @@ -154,7 +186,7 @@ struct ptrace_regs_struct *register_thread(struct global_state *state, int tid) t->fpregs.type = FPREGS_AVX; - ptrace(PTRACE_GETREGS, tid, NULL, &t->regs); + getregs(tid, &t->regs); t->next = state->t_HEAD; state->t_HEAD = t; @@ -224,7 +256,7 @@ void ptrace_detach_for_kill(struct global_state *state, int pid) // note that the order is important: the main thread must be detached last while (t != NULL) { // let's attempt to read the registers of the thread - if (ptrace(PTRACE_GETREGS, t->tid, NULL, &t->regs)) { + if (getregs(t->tid, &t->regs)) { // if we can't read the registers, the thread is probably still running // ensure that the thread is stopped tgkill(pid, t->tid, SIGSTOP); @@ -254,7 +286,7 @@ void ptrace_detach_for_migration(struct global_state *state, int pid) while (t != NULL) { // the user might have modified the state of the registers // so we use SETREGS to check if the process is running - if (ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs)) { + if (setregs(t->tid, &t->regs)) { // if we can't read the registers, the thread is probably still running // ensure that the thread is stopped tgkill(pid, t->tid, SIGSTOP); @@ -287,7 +319,7 @@ void ptrace_reattach_from_gdb(struct global_state *state, int pid) fprintf(stderr, "ptrace_attach failed for thread %d: %s\\n", t->tid, strerror(errno)); - if (ptrace(PTRACE_GETREGS, t->tid, NULL, &t->regs)) + if (getregs(t->tid, &t->regs)) fprintf(stderr, "ptrace_getregs failed for thread %d: %s\\n", t->tid, strerror(errno)); @@ -354,7 +386,7 @@ long singlestep(struct global_state *state, int tid) struct thread *t = state->t_HEAD; int signal_to_forward = 0; while (t != NULL) { - if (ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs)) + if (setregs(t->tid, &t->regs)) perror("ptrace_setregs"); if (t->tid == tid) { signal_to_forward = t->signal_to_forward; @@ -371,7 +403,7 @@ int step_until(struct global_state *state, int tid, uint64_t addr, int max_steps // flush any register changes struct thread *t = state->t_HEAD, *stepping_thread = NULL; while (t != NULL) { - if (ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs)) + if (setregs(t->tid, &t->regs)) perror("ptrace_setregs"); if (t->tid == tid) @@ -397,7 +429,7 @@ int step_until(struct global_state *state, int tid, uint64_t addr, int max_steps previous_ip = INSTRUCTION_POINTER(stepping_thread->regs); // update the registers - ptrace(PTRACE_GETREGS, tid, NULL, &stepping_thread->regs); + getregs(tid, &stepping_thread->regs); if (INSTRUCTION_POINTER(stepping_thread->regs) == addr) break; @@ -418,7 +450,7 @@ int prepare_for_run(struct global_state *state, int pid) // flush any register changes struct thread *t = state->t_HEAD; while (t != NULL) { - if (ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs)) + if (setregs(t->tid, &t->regs)) fprintf(stderr, "ptrace_setregs failed for thread %d: %s\\n", t->tid, strerror(errno)); t = t->next; @@ -515,7 +547,7 @@ struct thread_status *wait_all_and_update_regs(struct global_state *state, int p if (t->tid != head->tid) { // If GETREGS succeeds, the thread is already stopped, so we must // not "stop" it again - if (ptrace(PTRACE_GETREGS, t->tid, NULL, &t->regs) == -1) { + if (getregs(t->tid, &t->regs) == -1) { // Stop the thread with a SIGSTOP tgkill(pid, t->tid, SIGSTOP); // Wait for the thread to stop @@ -545,7 +577,7 @@ struct thread_status *wait_all_and_update_regs(struct global_state *state, int p // Update the registers of all the threads t = state->t_HEAD; while (t) { - ptrace(PTRACE_GETREGS, t->tid, NULL, &t->regs); + getregs(t->tid, &t->regs); t = t->next; } @@ -710,7 +742,7 @@ int stepping_finish(struct global_state *state, int tid) previous_ip = INSTRUCTION_POINTER(stepping_thread->regs); // update the registers - ptrace(PTRACE_GETREGS, tid, NULL, &stepping_thread->regs); + getregs(tid, &stepping_thread->regs); current_ip = INSTRUCTION_POINTER(stepping_thread->regs); @@ -739,7 +771,7 @@ int stepping_finish(struct global_state *state, int tid) waitpid(tid, &status, 0); // update the registers - ptrace(PTRACE_GETREGS, tid, NULL, &stepping_thread->regs); + getregs(tid, &stepping_thread->regs); cleanup: // remove any installed breakpoint From ec450f195426b95685d3f1c143a8dd8d15457199 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 18:02:50 +0200 Subject: [PATCH 014/144] feat: add CFFI support for floating point registers --- libdebug/cffi/ptrace_cffi_build.py | 16 +++++++++++- libdebug/cffi/ptrace_cffi_source.c | 42 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 5540a944..2f2f55da 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -195,7 +195,21 @@ } """ elif architecture == "aarch64": - fp_regs_struct = "" + fp_regs_struct = """ + struct reg_128 + { + unsigned long s_0, s_1; + }; + + // /usr/include/aarch64-linux-gnu/asm/ptrace.h + struct fp_regs_struct + { + struct reg_128 vregs[32]; + unsigned int fpsr; + unsigned int fpcr; + unsigned long padding; + } + """ fpregs_define = "" diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index ae7a73f0..e030aaf7 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -125,6 +125,7 @@ struct fp_regs_struct *get_thread_fp_regs(struct global_state *state, int tid) return NULL; } +#ifdef ARCH_AMD64 void get_fp_regs(struct global_state *state, int tid) { struct thread *t = get_thread(state, tid); @@ -170,6 +171,47 @@ void set_fp_regs(struct global_state *state, int tid) } #endif } +#endif + +#ifdef ARCH_AARCH64 +void get_fp_regs(struct global_state *state, int tid) +{ + struct thread *t = get_thread(state, tid); + + if (t == NULL) { + perror("Thread not found"); + return; + } + + struct iovec iov; + + iov.iov_base = (unsigned char *)(&t->fpregs); + iov.iov_len = sizeof(struct fp_regs_struct); + + if (ptrace(PTRACE_GETREGSET, tid, NT_FPREGSET, &iov) == -1) { + perror("ptrace_getregset_xstate"); + } +} + +void set_fp_regs(struct global_state *state, int tid) +{ + struct thread *t = get_thread(state, tid); + + if (t == NULL) { + perror("Thread not found"); + return; + } + + struct iovec iov; + + iov.iov_base = (unsigned char *)(&t->fpregs); + iov.iov_len = sizeof(struct fp_regs_struct); + + if (ptrace(PTRACE_SETREGSET, tid, NT_FPREGSET, &iov) == -1) { + perror("ptrace_setregset_xstate"); + } +} +#endif struct ptrace_regs_struct *register_thread(struct global_state *state, int tid) { From 03385a5dfd4e05417d0d1c011b0cb2e5edd69849 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 18:03:50 +0200 Subject: [PATCH 015/144] fix: include aarch64 search path TODO: find a better way of handling this --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 8d1ffa9f..aaab5243 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ if not ( os.path.isfile("/usr/include/sys/ptrace.h") or os.path.isfile("/usr/include/x86_64-linux-gnu/sys/ptrace.h") + or os.path.isfile("/usr/include/aarch64-linux-gnu/sys/ptrace.h") ): print("Required C libraries not found. Please install ptrace or kernel headers") exit(1) From 4d6a89c67950333d370b1785a38fd70960309ca4 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 18:05:18 +0200 Subject: [PATCH 016/144] fix: add missing comma for aarch64 fpregs struct --- libdebug/cffi/ptrace_cffi_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 2f2f55da..efc866d5 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -208,7 +208,7 @@ unsigned int fpsr; unsigned int fpcr; unsigned long padding; - } + }; """ fpregs_define = "" From fd7c59d733b2e7dbac45db4175eb648451d031fe Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 18:07:47 +0200 Subject: [PATCH 017/144] fix: replace forgotten SETREGS call, change function signatures --- libdebug/cffi/ptrace_cffi_source.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index e030aaf7..9ff8f962 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -74,19 +74,19 @@ struct global_state { }; #ifdef ARCH_AMD64 -unsigned long getregs(int tid, struct ptrace_regs_struct *regs) +int getregs(int tid, struct ptrace_regs_struct *regs) { return ptrace(PTRACE_GETREGS, tid, NULL, regs); } -unsigned long setregs(int tid, struct ptrace_regs_struct *regs) +int setregs(int tid, struct ptrace_regs_struct *regs) { return ptrace(PTRACE_SETREGS, tid, NULL, regs); } #endif #ifdef ARCH_AARCH64 -unsigned long getregs(int tid, struct ptrace_regs_struct *regs) +int getregs(int tid, struct ptrace_regs_struct *regs) { struct iovec iov; iov.iov_base = regs; @@ -94,7 +94,7 @@ unsigned long getregs(int tid, struct ptrace_regs_struct *regs) return ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &iov); } -unsigned long setregs(int tid, struct ptrace_regs_struct *regs) +int setregs(int tid, struct ptrace_regs_struct *regs) { struct iovec iov; iov.iov_base = regs; @@ -226,7 +226,9 @@ struct ptrace_regs_struct *register_thread(struct global_state *state, int tid) t->tid = tid; t->signal_to_forward = 0; +#ifdef ARCH_AMD64 t->fpregs.type = FPREGS_AVX; +#endif getregs(tid, &t->regs); @@ -337,7 +339,7 @@ void ptrace_detach_for_migration(struct global_state *state, int pid) waitpid(t->tid, NULL, 0); // set the registers again, as the first time it failed - ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs); + setregs(t->tid, &t->regs); } // Be sure that the thread will not run during gdb reattachment From 58d57bec9cde8afe7692ee70ef85758c8a743402 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 18:24:41 +0200 Subject: [PATCH 018/144] fix: various minor fixes --- libdebug/architectures/aarch64/aarch64_registers.py | 3 ++- libdebug/architectures/register_helper.py | 2 +- libdebug/utils/arch_mappings.py | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_registers.py b/libdebug/architectures/aarch64/aarch64_registers.py index d3a0ed93..d2609de1 100644 --- a/libdebug/architectures/aarch64/aarch64_registers.py +++ b/libdebug/architectures/aarch64/aarch64_registers.py @@ -16,6 +16,7 @@ class Aarch64Registers(Registers): """This class holds the state of the architectural-dependent registers of a process.""" - def __init__(self: Aarch64Registers) -> None: + def __init__(self: Aarch64Registers, thread_id: int) -> None: """Initializes the Registers object.""" self._internal_debugger = get_global_internal_debugger() + self._thread_id = thread_id diff --git a/libdebug/architectures/register_helper.py b/libdebug/architectures/register_helper.py index 6897efdb..8341d6aa 100644 --- a/libdebug/architectures/register_helper.py +++ b/libdebug/architectures/register_helper.py @@ -23,6 +23,6 @@ def register_holder_provider( case "amd64": return Amd64PtraceRegisterHolder(register_file, fp_register_file) case "aarch64": - return Aarch64PtraceRegisterHolder(register_file) + return Aarch64PtraceRegisterHolder(register_file, fp_register_file) case _: raise NotImplementedError(f"Architecture {architecture} not available.") diff --git a/libdebug/utils/arch_mappings.py b/libdebug/utils/arch_mappings.py index d4f5d79f..bc7a7694 100644 --- a/libdebug/utils/arch_mappings.py +++ b/libdebug/utils/arch_mappings.py @@ -8,6 +8,7 @@ "x86": "i386", "x86_64": "amd64", "x64": "amd64", + "arm64": "aarch64", } @@ -20,6 +21,8 @@ def map_arch(arch: str) -> str: Returns: str: the mapped architecture. """ + arch = arch.lower() + if arch in ARCH_MAPPING.values(): return arch elif arch in ARCH_MAPPING: From 2f803e99e47422d2432117a8b3654483b85dcd63 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 18:38:30 +0200 Subject: [PATCH 019/144] feat: add hardware breakpoint support for aarch64 --- .../aarch64/aarch64_ptrace_hw_bp_helper.py | 136 ++++++++++++++++++ .../ptrace_hardware_breakpoint_provider.py | 5 + libdebug/cffi/ptrace_cffi_source.c | 51 +++++++ 3 files changed, 192 insertions(+) create mode 100644 libdebug/architectures/aarch64/aarch64_ptrace_hw_bp_helper.py diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_hw_bp_helper.py b/libdebug/architectures/aarch64/aarch64_ptrace_hw_bp_helper.py new file mode 100644 index 00000000..34270b00 --- /dev/null +++ b/libdebug/architectures/aarch64/aarch64_ptrace_hw_bp_helper.py @@ -0,0 +1,136 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from libdebug.architectures.ptrace_hardware_breakpoint_manager import ( + PtraceHardwareBreakpointManager, +) +from libdebug.data.breakpoint import Breakpoint +from libdebug.liblog import liblog + +if TYPE_CHECKING: + from collections.abc import Callable + + from libdebug.data.breakpoint import Breakpoint + from libdebug.state.thread_context import ThreadContext + + +AARCH64_DBREGS_OFF = {} + +AARCH64_VALID_SIZES = {1, 2, 4, 8} +AARCH64_DBGREGS_COUNT = 16 +AARCH64_COND_VAL = {"x": 0, "r": 1, "w": 2, "rw": 3} + +# Internally, we check the address & 0x1000 to determine if it's a watchpoint. +# Then we query either the DBG or WVR register. +for i in range(AARCH64_DBGREGS_COUNT): + AARCH64_DBREGS_OFF[f"DBG{i}"] = 0x8 + i * 16 + AARCH64_DBREGS_OFF[f"WVR{i}"] = 0x1000 + 0x8 + i * 16 + + +class Aarch64HardwareBreakpointManager(PtraceHardwareBreakpointManager): + """A hardware breakpoint manager for the aarch64 architecture. + + Attributes: + thread (ThreadContext): The target thread. + peek_user (callable): A function that reads a number of bytes from the target thread registers. + poke_user (callable): A function that writes a number of bytes to the target thread registers. + breakpoint_count (int): The number of hardware breakpoints set. + watchpoint_count (int): The number of hardware watchpoints set. + """ + + def __init__( + self: Aarch64HardwareBreakpointManager, + thread: ThreadContext, + peek_user: Callable[[int, int], int], + poke_user: Callable[[int, int, int], None], + ) -> None: + """Initializes the hardware breakpoint manager.""" + super().__init__(thread, peek_user, poke_user) + + self.breakpoint_registers: dict[str, Breakpoint | None] = {} + self.watchpoint_registers: dict[str, Breakpoint | None] = {} + + for i in range(AARCH64_DBGREGS_COUNT): + self.breakpoint_registers[f"DBG{i}"] = None + self.watchpoint_registers[f"WVR{i}"] = None + + self.watchpoint_count = 0 + + def install_breakpoint(self: Aarch64HardwareBreakpointManager, bp: Breakpoint) -> None: + """Installs a hardware breakpoint at the provided location.""" + if self.breakpoint_count >= AARCH64_DBGREGS_COUNT: + liblog.error(f"Cannot set more than {AARCH64_DBGREGS_COUNT} hardware breakpoints.") + return + + if bp.length not in AARCH64_VALID_SIZES: + raise ValueError(f"Invalid breakpoint length: {bp.length}.") + + if bp.address % 4 != 0: + raise ValueError("Breakpoint address must be 4-byte aligned.") + + if bp.condition == "x": + register = next(reg for reg, bp in self.breakpoint_registers.items() if bp is None) + self.breakpoint_count += 1 + else: + register = next(reg for reg, bp in self.watchpoint_registers.items() if bp is None) + self.watchpoint_count += 1 + + # https://android.googlesource.com/platform/bionic/+/master/tests/sys_ptrace_test.cpp + # https://android.googlesource.com/toolchain/gdb/+/fb3e0dcd57c379215f4c7d1c036bd497f1dccb4b/gdb-7.11/gdb/nat/aarch64-linux-hw-point.c + length = (1 << bp.length) - 1 + enable = 1 + condition = AARCH64_COND_VAL[bp.condition] + control = length << 5 | condition << 3 | enable + + self.poke_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 0, bp.address) + self.poke_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 8, control) + + self.breakpoint_registers[register] = bp + + liblog.debugger(f"Installed hardware breakpoint on register {register}.") + + def remove_breakpoint(self: Aarch64HardwareBreakpointManager, bp: Breakpoint) -> None: + """Removes a hardware breakpoint at the provided location.""" + register = next(reg for reg, b in self.breakpoint_registers.items() if b == bp) + + if register is None: + liblog.error("Breakpoint not found.") + return + + if bp.condition == "x": + self.breakpoint_count -= 1 + else: + self.watchpoint_count -= 1 + + self.poke_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 0, 0) + self.poke_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 8, 0) + + self.breakpoint_registers[register] = None + + liblog.debugger(f"Removed hardware breakpoint on register {register}.") + + def available_breakpoints(self: Aarch64HardwareBreakpointManager) -> int: + """Returns the number of available hardware breakpoints.""" + return AARCH64_DBGREGS_COUNT - self.breakpoint_count + + def is_watchpoint_hit(self: Aarch64HardwareBreakpointManager) -> Breakpoint | None: + """Checks if a watchpoint has been hit. + + Returns: + Breakpoint | None: The watchpoint that has been hit, or None if no watchpoint has been hit. + """ + for register, bp in self.watchpoint_registers.items(): + if bp is None: + continue + + if self.peek_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 8) & 0x1: + return bp + + return None diff --git a/libdebug/architectures/ptrace_hardware_breakpoint_provider.py b/libdebug/architectures/ptrace_hardware_breakpoint_provider.py index 8a91142c..0a17d92e 100644 --- a/libdebug/architectures/ptrace_hardware_breakpoint_provider.py +++ b/libdebug/architectures/ptrace_hardware_breakpoint_provider.py @@ -6,6 +6,9 @@ from collections.abc import Callable +from libdebug.architectures.aarch64.aarch64_ptrace_hw_bp_helper import ( + Aarch64HardwareBreakpointManager, +) from libdebug.architectures.amd64.amd64_ptrace_hw_bp_helper import ( Amd64PtraceHardwareBreakpointManager, ) @@ -25,5 +28,7 @@ def ptrace_hardware_breakpoint_manager_provider( match architecture: case "amd64": return Amd64PtraceHardwareBreakpointManager(thread, peek_user, poke_user) + case "aarch64": + return Aarch64HardwareBreakpointManager(thread, peek_user, poke_user) case _: raise NotImplementedError(f"Architecture {architecture} not available.") diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 9ff8f962..9cb051c2 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -401,6 +401,7 @@ uint64_t ptrace_pokedata(int pid, uint64_t addr, uint64_t data) return ptrace(PTRACE_POKEDATA, pid, (void *)addr, data); } +#ifdef ARCH_AMD64 uint64_t ptrace_peekuser(int pid, uint64_t addr) { // Since the value returned by a successful PTRACE_PEEK* @@ -414,6 +415,56 @@ uint64_t ptrace_pokeuser(int pid, uint64_t addr, uint64_t data) { return ptrace(PTRACE_POKEUSER, pid, addr, data); } +#endif + +#ifdef ARCH_AARCH64 +// peekuser doesn't exist on ARM64, we have to use getregset +#define SIZEOF_STRUCT_HWDEBUG_STATE 8 + (16 * 16) + +uint64_t ptrace_peekuser(int pid, uint64_t addr) +{ + unsigned char *data = malloc(SIZEOF_STRUCT_HWDEBUG_STATE); + memset(data, 0, SIZEOF_STRUCT_HWDEBUG_STATE); + + struct iovec iov; + iov.iov_base = data; + iov.iov_len = SIZEOF_STRUCT_HWDEBUG_STATE; + + unsigned long command = (addr & 0x1000) ? NT_ARM_HW_WATCH : NT_ARM_HW_BREAK; + addr &= ~0x1000; + + ptrace(PTRACE_GETREGSET, pid, command, &iov); + + unsigned long result = *(unsigned long *) (data + addr); + + free(data); + + return result; +} + +uint64_t ptrace_pokeuser(int pid, uint64_t addr, uint64_t data) +{ + unsigned char *data_buffer = malloc(SIZEOF_STRUCT_HWDEBUG_STATE); + memset(data_buffer, 0, SIZEOF_STRUCT_HWDEBUG_STATE); + + struct iovec iov; + iov.iov_base = data_buffer; + iov.iov_len = SIZEOF_STRUCT_HWDEBUG_STATE; + + unsigned long command = (addr & 0x1000) ? NT_ARM_HW_WATCH : NT_ARM_HW_BREAK; + addr &= ~0x1000; + + ptrace(PTRACE_GETREGSET, pid, command, &iov); + + *(unsigned long *) (data_buffer + addr) = data; + + ptrace(PTRACE_SETREGSET, pid, command, &iov); + + free(data_buffer); + + return 0; +} +#endif uint64_t ptrace_geteventmsg(int pid) { From da1cb7d036ce3f0a7ff52472f90c0186f42030f3 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 18:58:14 +0200 Subject: [PATCH 020/144] fix: swap aarch64 register naming conversion --- .../architectures/aarch64/aarch64_ptrace_register_holder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py index 12e3e5c3..1eb6df25 100644 --- a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py +++ b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py @@ -58,8 +58,8 @@ def apply_on_regs(self: Aarch64PtraceRegisterHolder, target: Aarch64Registers, t return for i in range(31): - name_64 = f"w{i}" - name_32 = f"x{i}" + name_64 = f"x{i}" + name_32 = f"w{i}" setattr(target_class, name_64, _get_property_64(name_64)) setattr(target_class, name_32, _get_property_32(name_32)) From f5718ee447df9a633b6ad5e173b5b6abeb28393a Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 19:10:08 +0200 Subject: [PATCH 021/144] fix: overwrite the required number of bytes when setting a software breakpoint on aarch64 --- libdebug/cffi/ptrace_cffi_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index efc866d5..8a9f9ec3 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -259,7 +259,7 @@ breakpoint_define = """ #define INSTRUCTION_POINTER(regs) (regs.pc) - #define INSTALL_BREAKPOINT(instruction) ((instruction & 0xFFFFFFFFFFFFFF00) | 0xD4200000) + #define INSTALL_BREAKPOINT(instruction) ((instruction & 0xFFFFFFFF00000000) | 0xD4200000) #define BREAKPOINT_SIZE 4 #define IS_SW_BREAKPOINT(instruction) (instruction == 0xD4200000) """ From fe2826829b513d5c043eb38a7db54990eb1f8f86 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 14 Jul 2024 23:33:28 +0200 Subject: [PATCH 022/144] feat: migrate hardware breakpoint handling to CFFI This is be needed for aarch64 support. Additionally, this brought a noticeable boost in performance. --- .../amd64/amd64_ptrace_hw_bp_helper.py | 166 ----------- .../ptrace_hardware_breakpoint_provider.py | 34 --- libdebug/cffi/ptrace_cffi_build.py | 21 +- libdebug/cffi/ptrace_cffi_source.c | 280 ++++++++++++++++-- libdebug/ptrace/ptrace_interface.py | 97 +++--- libdebug/ptrace/ptrace_status_handler.py | 2 +- 6 files changed, 345 insertions(+), 255 deletions(-) delete mode 100644 libdebug/architectures/amd64/amd64_ptrace_hw_bp_helper.py delete mode 100644 libdebug/architectures/ptrace_hardware_breakpoint_provider.py diff --git a/libdebug/architectures/amd64/amd64_ptrace_hw_bp_helper.py b/libdebug/architectures/amd64/amd64_ptrace_hw_bp_helper.py deleted file mode 100644 index 30d66747..00000000 --- a/libdebug/architectures/amd64/amd64_ptrace_hw_bp_helper.py +++ /dev/null @@ -1,166 +0,0 @@ -# -# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2023-2024 Roberto Alessandro Bertolini. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from libdebug.architectures.ptrace_hardware_breakpoint_manager import ( - PtraceHardwareBreakpointManager, -) -from libdebug.liblog import liblog - -if TYPE_CHECKING: - from collections.abc import Callable - - from libdebug.data.breakpoint import Breakpoint - from libdebug.state.thread_context import ThreadContext - - -AMD64_DBGREGS_OFF = { - "DR0": 0x350, - "DR1": 0x358, - "DR2": 0x360, - "DR3": 0x368, - "DR4": 0x370, - "DR5": 0x378, - "DR6": 0x380, - "DR7": 0x388, -} -AMD64_DBGREGS_CTRL_LOCAL = {"DR0": 1 << 0, "DR1": 1 << 2, "DR2": 1 << 4, "DR3": 1 << 6} -AMD64_DBGREGS_CTRL_COND = {"DR0": 16, "DR1": 20, "DR2": 24, "DR3": 28} -AMD64_DBGREGS_CTRL_COND_VAL = {"x": 0, "w": 1, "rw": 3} -AMD64_DBGREGS_CTRL_LEN = {"DR0": 18, "DR1": 22, "DR2": 26, "DR3": 30} -AMD64_DBGREGS_CTRL_LEN_VAL = {1: 0, 2: 1, 8: 2, 4: 3} - -AMD64_DBREGS_COUNT = 4 - - -class Amd64PtraceHardwareBreakpointManager(PtraceHardwareBreakpointManager): - """A hardware breakpoint manager for the amd64 architecture. - - Attributes: - thread (ThreadContext): The target thread. - peek_user (callable): A function that reads a number of bytes from the target thread registers. - poke_user (callable): A function that writes a number of bytes to the target thread registers. - breakpoint_count (int): The number of hardware breakpoints set. - """ - - def __init__( - self: Amd64PtraceHardwareBreakpointManager, - thread: ThreadContext, - peek_user: Callable[[int, int], int], - poke_user: Callable[[int, int, int], None], - ) -> None: - """Initializes the hardware breakpoint manager.""" - super().__init__(thread, peek_user, poke_user) - self.breakpoint_registers: dict[str, Breakpoint | None] = { - "DR0": None, - "DR1": None, - "DR2": None, - "DR3": None, - } - - def install_breakpoint(self: Amd64PtraceHardwareBreakpointManager, bp: Breakpoint) -> None: - """Installs a hardware breakpoint at the provided location.""" - if self.breakpoint_count >= AMD64_DBREGS_COUNT: - raise RuntimeError("No more hardware breakpoints available.") - - # Find the first available breakpoint register - register = next(reg for reg, bp in self.breakpoint_registers.items() if bp is None) - liblog.debugger(f"Installing hardware breakpoint on register {register}.") - - # Write the breakpoint address in the register - self.poke_user(self.thread.thread_id, AMD64_DBGREGS_OFF[register], bp.address) - - # Set the breakpoint control register - ctrl = ( - AMD64_DBGREGS_CTRL_LOCAL[register] - | (AMD64_DBGREGS_CTRL_COND_VAL[bp.condition] << AMD64_DBGREGS_CTRL_COND[register]) - | (AMD64_DBGREGS_CTRL_LEN_VAL[bp.length] << AMD64_DBGREGS_CTRL_LEN[register]) - ) - - # Read the current value of the register - current_ctrl = self.peek_user(self.thread.thread_id, AMD64_DBGREGS_OFF["DR7"]) - - # Clear condition and length fields for the current register - current_ctrl &= ~(0x3 << AMD64_DBGREGS_CTRL_COND[register]) - current_ctrl &= ~(0x3 << AMD64_DBGREGS_CTRL_LEN[register]) - - # Set the new value of the register - current_ctrl |= ctrl - - # Write the new value of the register - self.poke_user(self.thread.thread_id, AMD64_DBGREGS_OFF["DR7"], current_ctrl) - - # Save the breakpoint - self.breakpoint_registers[register] = bp - - liblog.debugger(f"Hardware breakpoint installed on register {register}.") - - self.breakpoint_count += 1 - - def remove_breakpoint(self: Amd64PtraceHardwareBreakpointManager, bp: Breakpoint) -> None: - """Removes a hardware breakpoint at the provided location.""" - if self.breakpoint_count <= 0: - raise RuntimeError("No more hardware breakpoints to remove.") - - # Find the breakpoint register - register = next(reg for reg, bp_ in self.breakpoint_registers.items() if bp_ == bp) - - if register is None: - raise RuntimeError("Hardware breakpoint not found.") - - liblog.debugger(f"Removing hardware breakpoint on register {register}.") - - # Clear the breakpoint address in the register - self.poke_user(self.thread.thread_id, AMD64_DBGREGS_OFF[register], 0) - - # Read the current value of the control register - current_ctrl = self.peek_user(self.thread.thread_id, AMD64_DBGREGS_OFF["DR7"]) - - # Clear the breakpoint control register - current_ctrl &= ~AMD64_DBGREGS_CTRL_LOCAL[register] - - # Write the new value of the register - self.poke_user(self.thread.thread_id, AMD64_DBGREGS_OFF["DR7"], current_ctrl) - - # Remove the breakpoint - self.breakpoint_registers[register] = None - - liblog.debugger(f"Hardware breakpoint removed from register {register}.") - - self.breakpoint_count -= 1 - - def available_breakpoints(self: Amd64PtraceHardwareBreakpointManager) -> int: - """Returns the number of available hardware breakpoint registers.""" - return AMD64_DBREGS_COUNT - self.breakpoint_count - - def is_watchpoint_hit(self: Amd64PtraceHardwareBreakpointManager) -> Breakpoint | None: - """Checks if a watchpoint has been hit. - - Returns: - Breakpoint | None: The watchpoint that has been hit, or None if no watchpoint has been hit. - """ - dr6 = self.peek_user(self.thread.thread_id, AMD64_DBGREGS_OFF["DR6"]) - - watchpoint: Breakpoint | None = None - - # Check the DR6 register to see which watchpoint has been hit - if dr6 & 0x1: - watchpoint = self.breakpoint_registers["DR0"] - elif dr6 & 0x2: - watchpoint = self.breakpoint_registers["DR1"] - elif dr6 & 0x4: - watchpoint = self.breakpoint_registers["DR2"] - elif dr6 & 0x8: - watchpoint = self.breakpoint_registers["DR3"] - - if watchpoint is not None and watchpoint.condition == "x": - # It is a breakpoint, we do not care here - watchpoint = None - - return watchpoint diff --git a/libdebug/architectures/ptrace_hardware_breakpoint_provider.py b/libdebug/architectures/ptrace_hardware_breakpoint_provider.py deleted file mode 100644 index 0a17d92e..00000000 --- a/libdebug/architectures/ptrace_hardware_breakpoint_provider.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2023-2024 Roberto Alessandro Bertolini. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -from collections.abc import Callable - -from libdebug.architectures.aarch64.aarch64_ptrace_hw_bp_helper import ( - Aarch64HardwareBreakpointManager, -) -from libdebug.architectures.amd64.amd64_ptrace_hw_bp_helper import ( - Amd64PtraceHardwareBreakpointManager, -) -from libdebug.architectures.ptrace_hardware_breakpoint_manager import ( - PtraceHardwareBreakpointManager, -) -from libdebug.state.thread_context import ThreadContext - - -def ptrace_hardware_breakpoint_manager_provider( - architecture: str, - thread: ThreadContext, - peek_user: Callable[[int, int], int], - poke_user: Callable[[int, int, int], None], -) -> PtraceHardwareBreakpointManager: - """Returns an instance of the hardware breakpoint manager to be used by the `_InternalDebugger` class.""" - match architecture: - case "amd64": - return Amd64PtraceHardwareBreakpointManager(thread, peek_user, poke_user) - case "aarch64": - return Aarch64HardwareBreakpointManager(thread, peek_user, poke_user) - case _: - raise NotImplementedError(f"Architecture {architecture} not available.") diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 8a9f9ec3..244caa2b 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -307,6 +307,15 @@ struct software_breakpoint *next; }; + struct hardware_breakpoint { + uint64_t addr; + int tid; + char enabled; + char type; + char len; + struct hardware_breakpoint *next; + }; + struct thread { int tid; struct ptrace_regs_struct regs; @@ -324,7 +333,8 @@ struct global_state { struct thread *t_HEAD; struct thread *dead_t_HEAD; - struct software_breakpoint *b_HEAD; + struct software_breakpoint *sw_b_HEAD; + struct hardware_breakpoint *hw_b_HEAD; _Bool handle_syscall_enabled; }; @@ -367,6 +377,15 @@ void unregister_breakpoint(struct global_state *state, uint64_t address); void enable_breakpoint(struct global_state *state, uint64_t address); void disable_breakpoint(struct global_state *state, uint64_t address); + + void register_hw_breakpoint(struct global_state *state, int tid, uint64_t address, char type, char len); + void unregister_hw_breakpoint(struct global_state *state, int tid, uint64_t address); + void enable_hw_breakpoint(struct global_state *state, int tid, uint64_t address); + void disable_hw_breakpoint(struct global_state *state, int tid, uint64_t address); + unsigned long get_hit_hw_breakpoint(struct global_state *state, int tid); + int get_remaining_hw_breakpoint_count(struct global_state *state, int tid); + int get_remaining_hw_watchpoint_count(struct global_state *state, int tid); + void free_breakpoints(struct global_state *state); """ ) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 9cb051c2..afc1abd2 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -52,6 +52,15 @@ struct software_breakpoint { struct software_breakpoint *next; }; +struct hardware_breakpoint { + uint64_t addr; + int tid; + char enabled; + char type; + char len; + struct hardware_breakpoint *next; +}; + struct thread { int tid; struct ptrace_regs_struct regs; @@ -69,7 +78,8 @@ struct thread_status { struct global_state { struct thread *t_HEAD; struct thread *dead_t_HEAD; - struct software_breakpoint *b_HEAD; + struct software_breakpoint *sw_b_HEAD; + struct hardware_breakpoint *hw_b_HEAD; _Bool handle_syscall_enabled; }; @@ -103,6 +113,135 @@ int setregs(int tid, struct ptrace_regs_struct *regs) } #endif +#ifdef ARCH_AMD64 + +#define DR_BASE 0x350 +#define DR_SIZE 0x8 +#define CTRL_LOCAL(x) (1 << (2 * x)) +#define CTRL_COND(x) (16 + (4 * x)) +#define CTRL_COND_VAL(x) (x == 'x' ? 0 : (x == 'w' ? 1 : 3)) +#define CTRL_LEN(x) (18 + (4 * x)) +#define CTRL_LEN_VAL(x) (x == 1 ? 0 : (x == 2 ? 1 : (x == 8 ? 2 : 3))) + +void install_hardware_breakpoint(struct hardware_breakpoint *bp) +{ + // find a free debug register + int i; + for (i = 0; i < 4; i++) { + unsigned long address = ptrace(PTRACE_PEEKUSER, bp->tid, DR_BASE + i * DR_SIZE); + + if (!address) + break; + } + + if (i == 4) { + perror("No debug registers available"); + return; + } + + unsigned long ctrl = CTRL_LOCAL(i) | CTRL_COND_VAL(bp->type) << CTRL_COND(i) | CTRL_LEN_VAL(bp->len) << CTRL_LEN(i); + + // read the state from DR7 + unsigned long state = ptrace(PTRACE_PEEKUSER, bp->tid, DR_BASE + 7 * DR_SIZE); + + // reset the state, for good measure + state &= ~(3 << CTRL_COND(i)); + state &= ~(3 << CTRL_LEN(i)); + + // register the breakpoint + state |= ctrl; + + // write the address and the state + ptrace(PTRACE_POKEUSER, bp->tid, DR_BASE + i * DR_SIZE, bp->addr); + ptrace(PTRACE_POKEUSER, bp->tid, DR_BASE + 7 * DR_SIZE, state); +} + + +void remove_hardware_breakpoint(struct hardware_breakpoint *bp) +{ + // find the register + int i; + for (i = 0; i < 4; i++) { + unsigned long address = ptrace(PTRACE_PEEKUSER, bp->tid, DR_BASE + i * DR_SIZE); + + if (address == bp->addr) + break; + } + + if (i == 4) { + perror("Breakpoint not found"); + return; + } + + // read the state from DR7 + unsigned long state = ptrace(PTRACE_PEEKUSER, bp->tid, DR_BASE + 7 * DR_SIZE); + + // reset the state + state &= ~(3 << CTRL_COND(i)); + state &= ~(3 << CTRL_LEN(i)); + + // write the state + ptrace(PTRACE_POKEUSER, bp->tid, DR_BASE + 7 * DR_SIZE, state); + + // clear the address + ptrace(PTRACE_POKEUSER, bp->tid, DR_BASE + i * DR_SIZE, 0); +} + +int is_breakpoint_hit(struct hardware_breakpoint *bp) +{ + unsigned long status = ptrace(PTRACE_PEEKUSER, bp->tid, DR_BASE + 6 * DR_SIZE); + + int index; + if (status & 0x1) + index = 0; + else if (status & 0x2) + index = 1; + else if (status & 0x4) + index = 2; + else if (status & 0x8) + index = 3; + else + return 0; + + unsigned long address = ptrace(PTRACE_PEEKUSER, bp->tid, DR_BASE + index * DR_SIZE); + + if (address == bp->addr) + return 1; + + return 0; +} + +int get_remaining_hw_breakpoint_count(struct global_state *state, int tid) +{ + int i; + for (i = 0; i < 4; i++) { + unsigned long address = ptrace(PTRACE_PEEKUSER, tid, DR_BASE + i * DR_SIZE); + + if (!address) + break; + } + + return 4 - i; +} + +int get_remaining_hw_watchpoint_count(struct global_state *state, int tid) +{ + return get_remaining_hw_breakpoint_count(state, tid); +} +#endif + +#ifdef ARCH_AARCH64 +void install_hardware_breakpoint(struct hardware_breakpoint *bp) +{ + +} + +void remove_hardware_breakpoint(struct hardware_breakpoint *bp) +{ + +} +#endif + struct thread *get_thread(struct global_state *state, int tid) { struct thread *t = state->t_HEAD; @@ -561,7 +700,7 @@ int prepare_for_run(struct global_state *state, int pid) t_hit = 0; uint64_t ip = INSTRUCTION_POINTER(t->regs); - b = state->b_HEAD; + b = state->sw_b_HEAD; while (b != NULL && !t_hit) { if (b->addr == ip) // we hit a software breakpoint on this thread @@ -589,7 +728,7 @@ int prepare_for_run(struct global_state *state, int pid) } // Reset any software breakpoint - b = state->b_HEAD; + b = state->sw_b_HEAD; while (b != NULL) { if (b->enabled) { ptrace(PTRACE_POKEDATA, pid, (void *)b->addr, @@ -677,7 +816,7 @@ struct thread_status *wait_all_and_update_regs(struct global_state *state, int p } // Restore any software breakpoint - struct software_breakpoint *b = state->b_HEAD; + struct software_breakpoint *b = state->sw_b_HEAD; while (b != NULL) { if (b->enabled) { @@ -710,7 +849,7 @@ void register_breakpoint(struct global_state *state, int pid, uint64_t address) ptrace(PTRACE_POKEDATA, pid, (void *)address, patched_instruction); - struct software_breakpoint *b = state->b_HEAD; + struct software_breakpoint *b = state->sw_b_HEAD; while (b != NULL) { if (b->addr == address) { @@ -728,13 +867,13 @@ void register_breakpoint(struct global_state *state, int pid, uint64_t address) // Breakpoints should be inserted ordered by address, increasing // This is important, because we don't want a breakpoint patching another - if (state->b_HEAD == NULL || state->b_HEAD->addr > address) { - b->next = state->b_HEAD; - state->b_HEAD = b; + if (state->sw_b_HEAD == NULL || state->sw_b_HEAD->addr > address) { + b->next = state->sw_b_HEAD; + state->sw_b_HEAD = b; return; } else { - struct software_breakpoint *prev = state->b_HEAD; - struct software_breakpoint *next = state->b_HEAD->next; + struct software_breakpoint *prev = state->sw_b_HEAD; + struct software_breakpoint *next = state->sw_b_HEAD->next; while (next != NULL && next->addr < address) { prev = next; @@ -748,13 +887,13 @@ void register_breakpoint(struct global_state *state, int pid, uint64_t address) void unregister_breakpoint(struct global_state *state, uint64_t address) { - struct software_breakpoint *b = state->b_HEAD; + struct software_breakpoint *b = state->sw_b_HEAD; struct software_breakpoint *prev = NULL; while (b != NULL) { if (b->addr == address) { if (prev == NULL) { - state->b_HEAD = b->next; + state->sw_b_HEAD = b->next; } else { prev->next = b->next; } @@ -768,7 +907,7 @@ void unregister_breakpoint(struct global_state *state, uint64_t address) void enable_breakpoint(struct global_state *state, uint64_t address) { - struct software_breakpoint *b = state->b_HEAD; + struct software_breakpoint *b = state->sw_b_HEAD; while (b != NULL) { if (b->addr == address) { @@ -780,7 +919,7 @@ void enable_breakpoint(struct global_state *state, uint64_t address) void disable_breakpoint(struct global_state *state, uint64_t address) { - struct software_breakpoint *b = state->b_HEAD; + struct software_breakpoint *b = state->sw_b_HEAD; while (b != NULL) { if (b->addr == address) { @@ -792,7 +931,7 @@ void disable_breakpoint(struct global_state *state, uint64_t address) void free_breakpoints(struct global_state *state) { - struct software_breakpoint *b = state->b_HEAD; + struct software_breakpoint *b = state->sw_b_HEAD; struct software_breakpoint *next; while (b != NULL) { @@ -801,7 +940,18 @@ void free_breakpoints(struct global_state *state) b = next; } - state->b_HEAD = NULL; + state->sw_b_HEAD = NULL; + + struct hardware_breakpoint *h = state->hw_b_HEAD; + struct hardware_breakpoint *next_h; + + while (h != NULL) { + next_h = h->next; + free(h); + h = next_h; + } + + state->hw_b_HEAD = NULL; } int stepping_finish(struct global_state *state, int tid) @@ -870,7 +1020,7 @@ int stepping_finish(struct global_state *state, int tid) cleanup: // remove any installed breakpoint - struct software_breakpoint *b = state->b_HEAD; + struct software_breakpoint *b = state->sw_b_HEAD; while (b != NULL) { if (b->enabled) { ptrace(PTRACE_POKEDATA, tid, (void *)b->addr, b->instruction); @@ -880,3 +1030,99 @@ int stepping_finish(struct global_state *state, int tid) return 0; } + +void register_hw_breakpoint(struct global_state *state, int tid, uint64_t address, char type, char len) +{ + struct hardware_breakpoint *b = state->hw_b_HEAD; + + while (b != NULL) { + if (b->addr == address && b->tid == tid) { + perror("Breakpoint already registered"); + return; + } + b = b->next; + } + + b = malloc(sizeof(struct hardware_breakpoint)); + b->addr = address; + b->tid = tid; + b->enabled = 1; + b->type = type; + b->len = len; + + b->next = state->hw_b_HEAD; + state->hw_b_HEAD = b; + + install_hardware_breakpoint(b); +} + +void unregister_hw_breakpoint(struct global_state *state, int tid, uint64_t address) +{ + struct hardware_breakpoint *b = state->hw_b_HEAD; + struct hardware_breakpoint *prev = NULL; + + while (b != NULL) { + if (b->addr == address && b->tid == tid) { + if (prev == NULL) { + state->hw_b_HEAD = b->next; + } else { + prev->next = b->next; + } + + if (b->enabled) { + remove_hardware_breakpoint(b); + } + + free(b); + return; + } + prev = b; + b = b->next; + } +} + +void enable_hw_breakpoint(struct global_state *state, int tid, uint64_t address) +{ + struct hardware_breakpoint *b = state->hw_b_HEAD; + + while (b != NULL) { + if (b->addr == address && b->tid == tid) { + if (!b->enabled) { + install_hardware_breakpoint(b); + } + + b->enabled = 1; + } + b = b->next; + } +} + +void disable_hw_breakpoint(struct global_state *state, int tid, uint64_t address) +{ + struct hardware_breakpoint *b = state->hw_b_HEAD; + + while (b != NULL) { + if (b->addr == address && b->tid == tid) { + if (b->enabled) { + remove_hardware_breakpoint(b); + } + + b->enabled = 0; + } + b = b->next; + } +} + +unsigned long get_hit_hw_breakpoint(struct global_state *state, int tid) +{ + struct hardware_breakpoint *b = state->hw_b_HEAD; + + while (b != NULL) { + if (b->tid == tid && is_breakpoint_hit(b)) { + return b->addr; + } + b = b->next; + } + + return 0; +} \ No newline at end of file diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index d6784f59..6417025d 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -13,9 +13,6 @@ from pathlib import Path from typing import TYPE_CHECKING -from libdebug.architectures.ptrace_hardware_breakpoint_provider import ( - ptrace_hardware_breakpoint_manager_provider, -) from libdebug.architectures.register_helper import register_holder_provider from libdebug.cffi import _ptrace_cffi from libdebug.data.breakpoint import Breakpoint @@ -50,9 +47,6 @@ ) if TYPE_CHECKING: - from libdebug.architectures.ptrace_hardware_breakpoint_manager import ( - PtraceHardwareBreakpointManager, - ) from libdebug.data.memory_map import MemoryMap from libdebug.data.signal_catcher import SignalCatcher from libdebug.data.syscall_handler import SyscallHandler @@ -62,9 +56,6 @@ class PtraceInterface(DebuggingInterface): """The interface used by `_InternalDebugger` to communicate with the `ptrace` debugging backend.""" - hardware_bp_helpers: dict[int, PtraceHardwareBreakpointManager] - """The hardware breakpoint managers (one for each thread).""" - process_id: int | None """The process ID of the debugged process.""" @@ -85,20 +76,18 @@ def __init__(self: PtraceInterface) -> None: self._global_state = self.ffi.new("struct global_state*") self._global_state.t_HEAD = self.ffi.NULL self._global_state.dead_t_HEAD = self.ffi.NULL - self._global_state.b_HEAD = self.ffi.NULL + self._global_state.sw_b_HEAD = self.ffi.NULL + self._global_state.hw_b_HEAD = self.ffi.NULL self.process_id = 0 self.detached = False - self.hardware_bp_helpers = {} - self._disabled_aslr = False self.reset() def reset(self: PtraceInterface) -> None: """Resets the state of the interface.""" - self.hardware_bp_helpers.clear() self.lib_trace.free_thread_list(self._global_state) self.lib_trace.free_breakpoints(self._global_state) @@ -221,7 +210,6 @@ def kill(self: PtraceInterface) -> None: def cont(self: PtraceInterface) -> None: """Continues the execution of the process.""" - # Forward signals to the threads if self._internal_debugger.resume_context.threads_with_signals_to_forward: self.forward_signal() @@ -252,6 +240,7 @@ def cont(self: PtraceInterface) -> None: self._global_state, self.process_id, ) + if result < 0: errno_val = self.ffi.errno raise OSError(errno_val, errno.errorcode[errno_val]) @@ -332,9 +321,9 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None break if not found: - # Check if we have enough hardware breakpoints available - # Otherwise we use a software breakpoint - install_hw_bp = self.hardware_bp_helpers[thread.thread_id].available_breakpoints() > 0 + install_hw_bp = ( + self.lib_trace.get_remaining_hw_breakpoint_count(self._global_state, thread.thread_id) > 0 + ) ip_breakpoint = Breakpoint(last_saved_instruction_pointer, hardware=install_hw_bp) self.set_breakpoint(ip_breakpoint) @@ -398,6 +387,7 @@ def wait(self: PtraceInterface) -> None: self._global_state, self.process_id, ) + cursor = result invalidate_process_cache() @@ -440,6 +430,16 @@ def forward_signal(self: PtraceInterface) -> None: def migrate_to_gdb(self: PtraceInterface) -> None: """Migrates the current process to GDB.""" + # Delete any hardware breakpoint + for bp in self._internal_debugger.breakpoints.values(): + if bp.hardware: + for thread in self._internal_debugger.threads: + self.lib_trace.unregister_hw_breakpoint( + self._global_state, + thread.thread_id, + bp.address, + ) + self.lib_trace.ptrace_detach_for_migration(self._global_state, self.process_id) def migrate_from_gdb(self: PtraceInterface) -> None: @@ -451,10 +451,15 @@ def migrate_from_gdb(self: PtraceInterface) -> None: # We have to reinstall any hardware breakpoint for bp in self._internal_debugger.breakpoints.values(): - if bp.hardware and bp.enabled: - for helper in self.hardware_bp_helpers.values(): - helper.remove_breakpoint(bp) - helper.install_breakpoint(bp) + if bp.hardware: + for thread in self._internal_debugger.threads: + self.lib_trace.register_hw_breakpoint( + self._global_state, + thread.thread_id, + bp.address, + bp.condition[:1].encode(), + chr(bp.length).encode(), + ) def register_new_thread(self: PtraceInterface, new_thread_id: int) -> None: """Registers a new thread. @@ -476,18 +481,17 @@ def register_new_thread(self: PtraceInterface, new_thread_id: int) -> None: thread = ThreadContext(new_thread_id, register_holder) self._internal_debugger.insert_new_thread(thread) - thread_hw_bp_helper = ptrace_hardware_breakpoint_manager_provider( - self._internal_debugger.arch, - thread, - self._peek_user, - self._poke_user, - ) - self.hardware_bp_helpers[new_thread_id] = thread_hw_bp_helper # For any hardware breakpoints, we need to reapply them to the new thread for bp in self._internal_debugger.breakpoints.values(): if bp.hardware: - thread_hw_bp_helper.install_breakpoint(bp) + self.lib_trace.register_hw_breakpoint( + self._global_state, + new_thread_id, + bp.address, + bp.condition[:1].encode(), + chr(bp.length).encode(), + ) def unregister_thread( self: PtraceInterface, @@ -506,9 +510,6 @@ def unregister_thread( self._internal_debugger.set_thread_as_dead(thread_id, exit_code=exit_code, exit_signal=exit_signal) - # Remove the hardware breakpoint manager for the thread - self.hardware_bp_helpers.pop(thread_id) - def _set_sw_breakpoint(self: PtraceInterface, bp: Breakpoint) -> None: """Sets a software breakpoint at the specified address. @@ -553,8 +554,14 @@ def set_breakpoint(self: PtraceInterface, bp: Breakpoint, insert: bool = True) - insert (bool): Whether the breakpoint has to be inserted or just enabled. """ if bp.hardware: - for helper in self.hardware_bp_helpers.values(): - helper.install_breakpoint(bp) + for thread in self._internal_debugger.threads: + self.lib_trace.register_hw_breakpoint( + self._global_state, + thread.thread_id, + bp.address, + bp.condition[:1].encode(), + chr(bp.length).encode(), + ) elif insert: self._set_sw_breakpoint(bp) else: @@ -571,8 +578,12 @@ def unset_breakpoint(self: PtraceInterface, bp: Breakpoint, delete: bool = True) delete (bool): Whether the breakpoint has to be deleted or just disabled. """ if bp.hardware: - for helper in self.hardware_bp_helpers.values(): - helper.remove_breakpoint(bp) + for thread in self._internal_debugger.threads: + self.lib_trace.unregister_hw_breakpoint( + self._global_state, + thread.thread_id, + bp.address, + ) elif delete: self._unset_sw_breakpoint(bp) else: @@ -692,3 +703,17 @@ def _get_event_msg(self: PtraceInterface, thread_id: int) -> int: def maps(self: PtraceInterface) -> list[MemoryMap]: """Returns the memory maps of the process.""" return get_process_maps(self.process_id) + + def get_hit_watchpoint(self: PtraceInterface, thread_id: int) -> Breakpoint: + """Returns the watchpoint that has been hit.""" + address = self.lib_trace.get_hit_hw_breakpoint(self._global_state, thread_id) + + if not address: + return None + + bp = self._internal_debugger.breakpoints[address] + + if bp.condition != "x": + return bp + + return None diff --git a/libdebug/ptrace/ptrace_status_handler.py b/libdebug/ptrace/ptrace_status_handler.py index f535f932..1a3b120f 100644 --- a/libdebug/ptrace/ptrace_status_handler.py +++ b/libdebug/ptrace/ptrace_status_handler.py @@ -106,7 +106,7 @@ def _handle_breakpoints(self: PtraceStatusHandler, thread_id: int) -> bool: # Manage watchpoints if bp is None: - bp = self.ptrace_interface.hardware_bp_helpers[thread_id].is_watchpoint_hit() + bp = self.ptrace_interface.get_hit_watchpoint(thread_id) if bp is not None: liblog.debugger("Watchpoint hit at 0x%x", bp.address) From 4b90f7ca581b8a25e09deffc5b034245df50ef4c Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 10:44:56 +0200 Subject: [PATCH 023/144] fix: reintroduce check on the amount of available breakpoints --- libdebug/ptrace/ptrace_interface.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index 6417025d..26b65b76 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -555,6 +555,14 @@ def set_breakpoint(self: PtraceInterface, bp: Breakpoint, insert: bool = True) - """ if bp.hardware: for thread in self._internal_debugger.threads: + if bp.condition == "x": + remaining = self.lib_trace.get_remaining_hw_breakpoint_count(self._global_state, thread.thread_id) + else: + remaining = self.lib_trace.get_remaining_hw_watchpoint_count(self._global_state, thread.thread_id) + + if not remaining: + raise ValueError("No more hardware breakpoints of this type available") + self.lib_trace.register_hw_breakpoint( self._global_state, thread.thread_id, From b774ed20cef374387ce7c1e4d0d37c4ccf9893d2 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 10:52:38 +0200 Subject: [PATCH 024/144] test: reorder test folder, create new aarch64 directory --- test/aarch64/run_suite.py | 26 ++++++++++++++++++ test/{ => amd64}/CTF/0 | Bin test/{ => amd64}/CTF/1 | Bin test/{ => amd64}/CTF/2 | Bin test/{ => amd64}/CTF/deep-dive-division | Bin test/{ => amd64}/CTF/jumpout | Bin test/{ => amd64}/CTF/vmwhere1 | Bin test/{ => amd64}/CTF/vmwhere1_program | Bin test/{ => amd64}/Makefile | 0 .../{ => amd64}/binaries/antidebug_brute_test | Bin test/{ => amd64}/binaries/attach_test | Bin test/{ => amd64}/binaries/backtrace_test | Bin test/{ => amd64}/binaries/basic_test | Bin test/{ => amd64}/binaries/basic_test_pie | Bin test/{ => amd64}/binaries/benchmark | Bin test/{ => amd64}/binaries/breakpoint_test | Bin test/{ => amd64}/binaries/brute_test | Bin test/{ => amd64}/binaries/catch_signal_test | Bin test/{ => amd64}/binaries/cc_workshop | Bin test/{ => amd64}/binaries/complex_thread_test | Bin .../binaries/executable_section_test | Bin test/{ => amd64}/binaries/finish_test | Bin .../binaries/floating_point_2696_test | Bin .../binaries/floating_point_512_test | Bin .../binaries/floating_point_896_test | Bin test/{ => amd64}/binaries/handle_syscall_test | Bin test/{ => amd64}/binaries/jumpstart_test | Bin test/{ => amd64}/binaries/memory_test | Bin test/{ => amd64}/binaries/memory_test_2 | Bin test/{ => amd64}/binaries/node | Bin test/{ => amd64}/binaries/segfault_test | Bin .../binaries/signals_multithread_det_test | Bin .../binaries/signals_multithread_undet_test | Bin test/{ => amd64}/binaries/speed_test | Bin test/{ => amd64}/binaries/thread_test | Bin test/{ => amd64}/binaries/watchpoint_test | Bin .../dockerfiles/archlinux.Dockerfile | 0 .../archlinux.Dockerfile.dockerignore | 0 .../{ => amd64}/dockerfiles/debian.Dockerfile | 0 .../debian.Dockerfile.dockerignore | 0 .../{ => amd64}/dockerfiles/fedora.Dockerfile | 0 .../fedora.Dockerfile.dockerignore | 0 test/{ => amd64}/dockerfiles/run_tests.sh | 0 .../{ => amd64}/dockerfiles/ubuntu.Dockerfile | 0 .../ubuntu.Dockerfile.dockerignore | 0 .../other_tests/gdb_migration_test.py | 0 test/{ => amd64}/run_containerized_tests.sh | 0 test/{ => amd64}/run_suite.py | 0 test/{ => amd64}/scripts/alias_test.py | 0 .../{ => amd64}/scripts/attach_detach_test.py | 0 test/{ => amd64}/scripts/auto_waiting_test.py | 0 test/{ => amd64}/scripts/backtrace_test.py | 0 test/{ => amd64}/scripts/basic_test.py | 0 test/{ => amd64}/scripts/breakpoint_test.py | 0 test/{ => amd64}/scripts/brute_test.py | 0 .../scripts/builtin_handler_test.py | 0 test/{ => amd64}/scripts/callback_test.py | 0 test/{ => amd64}/scripts/catch_signal_test.py | 0 test/{ => amd64}/scripts/death_test.py | 0 .../scripts/deep_dive_division_test.py | 0 test/{ => amd64}/scripts/finish_test.py | 0 .../scripts/floating_point_test.py | 0 .../scripts/handle_syscall_test.py | 0 .../scripts/hijack_syscall_test.py | 0 test/{ => amd64}/scripts/jumpout_test.py | 0 test/{ => amd64}/scripts/jumpstart_test.py | 0 .../scripts/large_binary_sym_test.py | 0 test/{ => amd64}/scripts/memory_test.py | 0 .../scripts/multiple_debuggers_test.py | 0 test/{ => amd64}/scripts/nlinks_test.py | 0 .../scripts/pprint_syscalls_test.py | 0 .../scripts/signals_multithread_test.py | 0 test/{ => amd64}/scripts/speed_test.py | 0 test/{ => amd64}/scripts/thread_test.py | 0 test/{ => amd64}/scripts/vmwhere1_test.py | 0 test/{ => amd64}/scripts/waiting_test.py | 0 .../scripts/watchpoint_alias_test.py | 0 test/{ => amd64}/scripts/watchpoint_test.py | 0 test/{ => amd64}/srcs/antidebug_brute_test.c | 0 test/{ => amd64}/srcs/attach_test.c | 0 test/{ => amd64}/srcs/backtrace.c | 0 test/{ => amd64}/srcs/basic_test.c | 0 test/{ => amd64}/srcs/basic_test_pie.c | 0 test/{ => amd64}/srcs/benchmark.c | 0 test/{ => amd64}/srcs/breakpoint_test.c | 0 test/{ => amd64}/srcs/brute_test.c | 0 test/{ => amd64}/srcs/catch_signal_test.c | 0 test/{ => amd64}/srcs/complex_thread_test.c | 0 .../srcs/executable_section_test.c | 0 test/{ => amd64}/srcs/finish_test.c | 0 .../srcs/floating_point_2696_test.c | 0 .../srcs/floating_point_512_test.c | 0 .../srcs/floating_point_896_test.c | 0 test/{ => amd64}/srcs/handle_syscall_test.c | 0 test/{ => amd64}/srcs/jumpstart_test.c | 0 .../{ => amd64}/srcs/jumpstart_test_preload.c | 0 test/{ => amd64}/srcs/memory_test.c | 0 test/{ => amd64}/srcs/memory_test_2.c | 0 test/{ => amd64}/srcs/segfault_test.c | 0 .../srcs/signals_multithread_det_test.c | 0 .../srcs/signals_multithread_undet_test.c | 0 test/{ => amd64}/srcs/speed_test.c | 0 test/{ => amd64}/srcs/thread_test.c | 0 test/{ => amd64}/srcs/watchpoint_test.c | 0 test/binaries/jumpstart_test_preload.so | Bin 15944 -> 0 bytes 105 files changed, 26 insertions(+) create mode 100644 test/aarch64/run_suite.py rename test/{ => amd64}/CTF/0 (100%) rename test/{ => amd64}/CTF/1 (100%) rename test/{ => amd64}/CTF/2 (100%) rename test/{ => amd64}/CTF/deep-dive-division (100%) rename test/{ => amd64}/CTF/jumpout (100%) rename test/{ => amd64}/CTF/vmwhere1 (100%) rename test/{ => amd64}/CTF/vmwhere1_program (100%) rename test/{ => amd64}/Makefile (100%) rename test/{ => amd64}/binaries/antidebug_brute_test (100%) rename test/{ => amd64}/binaries/attach_test (100%) rename test/{ => amd64}/binaries/backtrace_test (100%) rename test/{ => amd64}/binaries/basic_test (100%) rename test/{ => amd64}/binaries/basic_test_pie (100%) rename test/{ => amd64}/binaries/benchmark (100%) rename test/{ => amd64}/binaries/breakpoint_test (100%) rename test/{ => amd64}/binaries/brute_test (100%) rename test/{ => amd64}/binaries/catch_signal_test (100%) rename test/{ => amd64}/binaries/cc_workshop (100%) rename test/{ => amd64}/binaries/complex_thread_test (100%) rename test/{ => amd64}/binaries/executable_section_test (100%) rename test/{ => amd64}/binaries/finish_test (100%) rename test/{ => amd64}/binaries/floating_point_2696_test (100%) rename test/{ => amd64}/binaries/floating_point_512_test (100%) rename test/{ => amd64}/binaries/floating_point_896_test (100%) rename test/{ => amd64}/binaries/handle_syscall_test (100%) rename test/{ => amd64}/binaries/jumpstart_test (100%) rename test/{ => amd64}/binaries/memory_test (100%) rename test/{ => amd64}/binaries/memory_test_2 (100%) rename test/{ => amd64}/binaries/node (100%) rename test/{ => amd64}/binaries/segfault_test (100%) rename test/{ => amd64}/binaries/signals_multithread_det_test (100%) rename test/{ => amd64}/binaries/signals_multithread_undet_test (100%) rename test/{ => amd64}/binaries/speed_test (100%) rename test/{ => amd64}/binaries/thread_test (100%) rename test/{ => amd64}/binaries/watchpoint_test (100%) rename test/{ => amd64}/dockerfiles/archlinux.Dockerfile (100%) rename test/{ => amd64}/dockerfiles/archlinux.Dockerfile.dockerignore (100%) rename test/{ => amd64}/dockerfiles/debian.Dockerfile (100%) rename test/{ => amd64}/dockerfiles/debian.Dockerfile.dockerignore (100%) rename test/{ => amd64}/dockerfiles/fedora.Dockerfile (100%) rename test/{ => amd64}/dockerfiles/fedora.Dockerfile.dockerignore (100%) rename test/{ => amd64}/dockerfiles/run_tests.sh (100%) rename test/{ => amd64}/dockerfiles/ubuntu.Dockerfile (100%) rename test/{ => amd64}/dockerfiles/ubuntu.Dockerfile.dockerignore (100%) rename test/{ => amd64}/other_tests/gdb_migration_test.py (100%) rename test/{ => amd64}/run_containerized_tests.sh (100%) rename test/{ => amd64}/run_suite.py (100%) rename test/{ => amd64}/scripts/alias_test.py (100%) rename test/{ => amd64}/scripts/attach_detach_test.py (100%) rename test/{ => amd64}/scripts/auto_waiting_test.py (100%) rename test/{ => amd64}/scripts/backtrace_test.py (100%) rename test/{ => amd64}/scripts/basic_test.py (100%) rename test/{ => amd64}/scripts/breakpoint_test.py (100%) rename test/{ => amd64}/scripts/brute_test.py (100%) rename test/{ => amd64}/scripts/builtin_handler_test.py (100%) rename test/{ => amd64}/scripts/callback_test.py (100%) rename test/{ => amd64}/scripts/catch_signal_test.py (100%) rename test/{ => amd64}/scripts/death_test.py (100%) rename test/{ => amd64}/scripts/deep_dive_division_test.py (100%) rename test/{ => amd64}/scripts/finish_test.py (100%) rename test/{ => amd64}/scripts/floating_point_test.py (100%) rename test/{ => amd64}/scripts/handle_syscall_test.py (100%) rename test/{ => amd64}/scripts/hijack_syscall_test.py (100%) rename test/{ => amd64}/scripts/jumpout_test.py (100%) rename test/{ => amd64}/scripts/jumpstart_test.py (100%) rename test/{ => amd64}/scripts/large_binary_sym_test.py (100%) rename test/{ => amd64}/scripts/memory_test.py (100%) rename test/{ => amd64}/scripts/multiple_debuggers_test.py (100%) rename test/{ => amd64}/scripts/nlinks_test.py (100%) rename test/{ => amd64}/scripts/pprint_syscalls_test.py (100%) rename test/{ => amd64}/scripts/signals_multithread_test.py (100%) rename test/{ => amd64}/scripts/speed_test.py (100%) rename test/{ => amd64}/scripts/thread_test.py (100%) rename test/{ => amd64}/scripts/vmwhere1_test.py (100%) rename test/{ => amd64}/scripts/waiting_test.py (100%) rename test/{ => amd64}/scripts/watchpoint_alias_test.py (100%) rename test/{ => amd64}/scripts/watchpoint_test.py (100%) rename test/{ => amd64}/srcs/antidebug_brute_test.c (100%) rename test/{ => amd64}/srcs/attach_test.c (100%) rename test/{ => amd64}/srcs/backtrace.c (100%) rename test/{ => amd64}/srcs/basic_test.c (100%) rename test/{ => amd64}/srcs/basic_test_pie.c (100%) rename test/{ => amd64}/srcs/benchmark.c (100%) rename test/{ => amd64}/srcs/breakpoint_test.c (100%) rename test/{ => amd64}/srcs/brute_test.c (100%) rename test/{ => amd64}/srcs/catch_signal_test.c (100%) rename test/{ => amd64}/srcs/complex_thread_test.c (100%) rename test/{ => amd64}/srcs/executable_section_test.c (100%) rename test/{ => amd64}/srcs/finish_test.c (100%) rename test/{ => amd64}/srcs/floating_point_2696_test.c (100%) rename test/{ => amd64}/srcs/floating_point_512_test.c (100%) rename test/{ => amd64}/srcs/floating_point_896_test.c (100%) rename test/{ => amd64}/srcs/handle_syscall_test.c (100%) rename test/{ => amd64}/srcs/jumpstart_test.c (100%) rename test/{ => amd64}/srcs/jumpstart_test_preload.c (100%) rename test/{ => amd64}/srcs/memory_test.c (100%) rename test/{ => amd64}/srcs/memory_test_2.c (100%) rename test/{ => amd64}/srcs/segfault_test.c (100%) rename test/{ => amd64}/srcs/signals_multithread_det_test.c (100%) rename test/{ => amd64}/srcs/signals_multithread_undet_test.c (100%) rename test/{ => amd64}/srcs/speed_test.c (100%) rename test/{ => amd64}/srcs/thread_test.c (100%) rename test/{ => amd64}/srcs/watchpoint_test.c (100%) delete mode 100755 test/binaries/jumpstart_test_preload.so diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py new file mode 100644 index 00000000..ca460a8d --- /dev/null +++ b/test/aarch64/run_suite.py @@ -0,0 +1,26 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import sys +from unittest import TestLoader, TestSuite, TextTestRunner + + +def fast_suite(): + suite = TestSuite() + return suite + + +if __name__ == "__main__": + if sys.version_info >= (3, 12): + runner = TextTestRunner(verbosity=2, durations=3) + else: + runner = TextTestRunner(verbosity=2) + + suite = fast_suite() + + runner.run(suite) + + sys.exit(0) diff --git a/test/CTF/0 b/test/amd64/CTF/0 similarity index 100% rename from test/CTF/0 rename to test/amd64/CTF/0 diff --git a/test/CTF/1 b/test/amd64/CTF/1 similarity index 100% rename from test/CTF/1 rename to test/amd64/CTF/1 diff --git a/test/CTF/2 b/test/amd64/CTF/2 similarity index 100% rename from test/CTF/2 rename to test/amd64/CTF/2 diff --git a/test/CTF/deep-dive-division b/test/amd64/CTF/deep-dive-division similarity index 100% rename from test/CTF/deep-dive-division rename to test/amd64/CTF/deep-dive-division diff --git a/test/CTF/jumpout b/test/amd64/CTF/jumpout similarity index 100% rename from test/CTF/jumpout rename to test/amd64/CTF/jumpout diff --git a/test/CTF/vmwhere1 b/test/amd64/CTF/vmwhere1 similarity index 100% rename from test/CTF/vmwhere1 rename to test/amd64/CTF/vmwhere1 diff --git a/test/CTF/vmwhere1_program b/test/amd64/CTF/vmwhere1_program similarity index 100% rename from test/CTF/vmwhere1_program rename to test/amd64/CTF/vmwhere1_program diff --git a/test/Makefile b/test/amd64/Makefile similarity index 100% rename from test/Makefile rename to test/amd64/Makefile diff --git a/test/binaries/antidebug_brute_test b/test/amd64/binaries/antidebug_brute_test similarity index 100% rename from test/binaries/antidebug_brute_test rename to test/amd64/binaries/antidebug_brute_test diff --git a/test/binaries/attach_test b/test/amd64/binaries/attach_test similarity index 100% rename from test/binaries/attach_test rename to test/amd64/binaries/attach_test diff --git a/test/binaries/backtrace_test b/test/amd64/binaries/backtrace_test similarity index 100% rename from test/binaries/backtrace_test rename to test/amd64/binaries/backtrace_test diff --git a/test/binaries/basic_test b/test/amd64/binaries/basic_test similarity index 100% rename from test/binaries/basic_test rename to test/amd64/binaries/basic_test diff --git a/test/binaries/basic_test_pie b/test/amd64/binaries/basic_test_pie similarity index 100% rename from test/binaries/basic_test_pie rename to test/amd64/binaries/basic_test_pie diff --git a/test/binaries/benchmark b/test/amd64/binaries/benchmark similarity index 100% rename from test/binaries/benchmark rename to test/amd64/binaries/benchmark diff --git a/test/binaries/breakpoint_test b/test/amd64/binaries/breakpoint_test similarity index 100% rename from test/binaries/breakpoint_test rename to test/amd64/binaries/breakpoint_test diff --git a/test/binaries/brute_test b/test/amd64/binaries/brute_test similarity index 100% rename from test/binaries/brute_test rename to test/amd64/binaries/brute_test diff --git a/test/binaries/catch_signal_test b/test/amd64/binaries/catch_signal_test similarity index 100% rename from test/binaries/catch_signal_test rename to test/amd64/binaries/catch_signal_test diff --git a/test/binaries/cc_workshop b/test/amd64/binaries/cc_workshop similarity index 100% rename from test/binaries/cc_workshop rename to test/amd64/binaries/cc_workshop diff --git a/test/binaries/complex_thread_test b/test/amd64/binaries/complex_thread_test similarity index 100% rename from test/binaries/complex_thread_test rename to test/amd64/binaries/complex_thread_test diff --git a/test/binaries/executable_section_test b/test/amd64/binaries/executable_section_test similarity index 100% rename from test/binaries/executable_section_test rename to test/amd64/binaries/executable_section_test diff --git a/test/binaries/finish_test b/test/amd64/binaries/finish_test similarity index 100% rename from test/binaries/finish_test rename to test/amd64/binaries/finish_test diff --git a/test/binaries/floating_point_2696_test b/test/amd64/binaries/floating_point_2696_test similarity index 100% rename from test/binaries/floating_point_2696_test rename to test/amd64/binaries/floating_point_2696_test diff --git a/test/binaries/floating_point_512_test b/test/amd64/binaries/floating_point_512_test similarity index 100% rename from test/binaries/floating_point_512_test rename to test/amd64/binaries/floating_point_512_test diff --git a/test/binaries/floating_point_896_test b/test/amd64/binaries/floating_point_896_test similarity index 100% rename from test/binaries/floating_point_896_test rename to test/amd64/binaries/floating_point_896_test diff --git a/test/binaries/handle_syscall_test b/test/amd64/binaries/handle_syscall_test similarity index 100% rename from test/binaries/handle_syscall_test rename to test/amd64/binaries/handle_syscall_test diff --git a/test/binaries/jumpstart_test b/test/amd64/binaries/jumpstart_test similarity index 100% rename from test/binaries/jumpstart_test rename to test/amd64/binaries/jumpstart_test diff --git a/test/binaries/memory_test b/test/amd64/binaries/memory_test similarity index 100% rename from test/binaries/memory_test rename to test/amd64/binaries/memory_test diff --git a/test/binaries/memory_test_2 b/test/amd64/binaries/memory_test_2 similarity index 100% rename from test/binaries/memory_test_2 rename to test/amd64/binaries/memory_test_2 diff --git a/test/binaries/node b/test/amd64/binaries/node similarity index 100% rename from test/binaries/node rename to test/amd64/binaries/node diff --git a/test/binaries/segfault_test b/test/amd64/binaries/segfault_test similarity index 100% rename from test/binaries/segfault_test rename to test/amd64/binaries/segfault_test diff --git a/test/binaries/signals_multithread_det_test b/test/amd64/binaries/signals_multithread_det_test similarity index 100% rename from test/binaries/signals_multithread_det_test rename to test/amd64/binaries/signals_multithread_det_test diff --git a/test/binaries/signals_multithread_undet_test b/test/amd64/binaries/signals_multithread_undet_test similarity index 100% rename from test/binaries/signals_multithread_undet_test rename to test/amd64/binaries/signals_multithread_undet_test diff --git a/test/binaries/speed_test b/test/amd64/binaries/speed_test similarity index 100% rename from test/binaries/speed_test rename to test/amd64/binaries/speed_test diff --git a/test/binaries/thread_test b/test/amd64/binaries/thread_test similarity index 100% rename from test/binaries/thread_test rename to test/amd64/binaries/thread_test diff --git a/test/binaries/watchpoint_test b/test/amd64/binaries/watchpoint_test similarity index 100% rename from test/binaries/watchpoint_test rename to test/amd64/binaries/watchpoint_test diff --git a/test/dockerfiles/archlinux.Dockerfile b/test/amd64/dockerfiles/archlinux.Dockerfile similarity index 100% rename from test/dockerfiles/archlinux.Dockerfile rename to test/amd64/dockerfiles/archlinux.Dockerfile diff --git a/test/dockerfiles/archlinux.Dockerfile.dockerignore b/test/amd64/dockerfiles/archlinux.Dockerfile.dockerignore similarity index 100% rename from test/dockerfiles/archlinux.Dockerfile.dockerignore rename to test/amd64/dockerfiles/archlinux.Dockerfile.dockerignore diff --git a/test/dockerfiles/debian.Dockerfile b/test/amd64/dockerfiles/debian.Dockerfile similarity index 100% rename from test/dockerfiles/debian.Dockerfile rename to test/amd64/dockerfiles/debian.Dockerfile diff --git a/test/dockerfiles/debian.Dockerfile.dockerignore b/test/amd64/dockerfiles/debian.Dockerfile.dockerignore similarity index 100% rename from test/dockerfiles/debian.Dockerfile.dockerignore rename to test/amd64/dockerfiles/debian.Dockerfile.dockerignore diff --git a/test/dockerfiles/fedora.Dockerfile b/test/amd64/dockerfiles/fedora.Dockerfile similarity index 100% rename from test/dockerfiles/fedora.Dockerfile rename to test/amd64/dockerfiles/fedora.Dockerfile diff --git a/test/dockerfiles/fedora.Dockerfile.dockerignore b/test/amd64/dockerfiles/fedora.Dockerfile.dockerignore similarity index 100% rename from test/dockerfiles/fedora.Dockerfile.dockerignore rename to test/amd64/dockerfiles/fedora.Dockerfile.dockerignore diff --git a/test/dockerfiles/run_tests.sh b/test/amd64/dockerfiles/run_tests.sh similarity index 100% rename from test/dockerfiles/run_tests.sh rename to test/amd64/dockerfiles/run_tests.sh diff --git a/test/dockerfiles/ubuntu.Dockerfile b/test/amd64/dockerfiles/ubuntu.Dockerfile similarity index 100% rename from test/dockerfiles/ubuntu.Dockerfile rename to test/amd64/dockerfiles/ubuntu.Dockerfile diff --git a/test/dockerfiles/ubuntu.Dockerfile.dockerignore b/test/amd64/dockerfiles/ubuntu.Dockerfile.dockerignore similarity index 100% rename from test/dockerfiles/ubuntu.Dockerfile.dockerignore rename to test/amd64/dockerfiles/ubuntu.Dockerfile.dockerignore diff --git a/test/other_tests/gdb_migration_test.py b/test/amd64/other_tests/gdb_migration_test.py similarity index 100% rename from test/other_tests/gdb_migration_test.py rename to test/amd64/other_tests/gdb_migration_test.py diff --git a/test/run_containerized_tests.sh b/test/amd64/run_containerized_tests.sh similarity index 100% rename from test/run_containerized_tests.sh rename to test/amd64/run_containerized_tests.sh diff --git a/test/run_suite.py b/test/amd64/run_suite.py similarity index 100% rename from test/run_suite.py rename to test/amd64/run_suite.py diff --git a/test/scripts/alias_test.py b/test/amd64/scripts/alias_test.py similarity index 100% rename from test/scripts/alias_test.py rename to test/amd64/scripts/alias_test.py diff --git a/test/scripts/attach_detach_test.py b/test/amd64/scripts/attach_detach_test.py similarity index 100% rename from test/scripts/attach_detach_test.py rename to test/amd64/scripts/attach_detach_test.py diff --git a/test/scripts/auto_waiting_test.py b/test/amd64/scripts/auto_waiting_test.py similarity index 100% rename from test/scripts/auto_waiting_test.py rename to test/amd64/scripts/auto_waiting_test.py diff --git a/test/scripts/backtrace_test.py b/test/amd64/scripts/backtrace_test.py similarity index 100% rename from test/scripts/backtrace_test.py rename to test/amd64/scripts/backtrace_test.py diff --git a/test/scripts/basic_test.py b/test/amd64/scripts/basic_test.py similarity index 100% rename from test/scripts/basic_test.py rename to test/amd64/scripts/basic_test.py diff --git a/test/scripts/breakpoint_test.py b/test/amd64/scripts/breakpoint_test.py similarity index 100% rename from test/scripts/breakpoint_test.py rename to test/amd64/scripts/breakpoint_test.py diff --git a/test/scripts/brute_test.py b/test/amd64/scripts/brute_test.py similarity index 100% rename from test/scripts/brute_test.py rename to test/amd64/scripts/brute_test.py diff --git a/test/scripts/builtin_handler_test.py b/test/amd64/scripts/builtin_handler_test.py similarity index 100% rename from test/scripts/builtin_handler_test.py rename to test/amd64/scripts/builtin_handler_test.py diff --git a/test/scripts/callback_test.py b/test/amd64/scripts/callback_test.py similarity index 100% rename from test/scripts/callback_test.py rename to test/amd64/scripts/callback_test.py diff --git a/test/scripts/catch_signal_test.py b/test/amd64/scripts/catch_signal_test.py similarity index 100% rename from test/scripts/catch_signal_test.py rename to test/amd64/scripts/catch_signal_test.py diff --git a/test/scripts/death_test.py b/test/amd64/scripts/death_test.py similarity index 100% rename from test/scripts/death_test.py rename to test/amd64/scripts/death_test.py diff --git a/test/scripts/deep_dive_division_test.py b/test/amd64/scripts/deep_dive_division_test.py similarity index 100% rename from test/scripts/deep_dive_division_test.py rename to test/amd64/scripts/deep_dive_division_test.py diff --git a/test/scripts/finish_test.py b/test/amd64/scripts/finish_test.py similarity index 100% rename from test/scripts/finish_test.py rename to test/amd64/scripts/finish_test.py diff --git a/test/scripts/floating_point_test.py b/test/amd64/scripts/floating_point_test.py similarity index 100% rename from test/scripts/floating_point_test.py rename to test/amd64/scripts/floating_point_test.py diff --git a/test/scripts/handle_syscall_test.py b/test/amd64/scripts/handle_syscall_test.py similarity index 100% rename from test/scripts/handle_syscall_test.py rename to test/amd64/scripts/handle_syscall_test.py diff --git a/test/scripts/hijack_syscall_test.py b/test/amd64/scripts/hijack_syscall_test.py similarity index 100% rename from test/scripts/hijack_syscall_test.py rename to test/amd64/scripts/hijack_syscall_test.py diff --git a/test/scripts/jumpout_test.py b/test/amd64/scripts/jumpout_test.py similarity index 100% rename from test/scripts/jumpout_test.py rename to test/amd64/scripts/jumpout_test.py diff --git a/test/scripts/jumpstart_test.py b/test/amd64/scripts/jumpstart_test.py similarity index 100% rename from test/scripts/jumpstart_test.py rename to test/amd64/scripts/jumpstart_test.py diff --git a/test/scripts/large_binary_sym_test.py b/test/amd64/scripts/large_binary_sym_test.py similarity index 100% rename from test/scripts/large_binary_sym_test.py rename to test/amd64/scripts/large_binary_sym_test.py diff --git a/test/scripts/memory_test.py b/test/amd64/scripts/memory_test.py similarity index 100% rename from test/scripts/memory_test.py rename to test/amd64/scripts/memory_test.py diff --git a/test/scripts/multiple_debuggers_test.py b/test/amd64/scripts/multiple_debuggers_test.py similarity index 100% rename from test/scripts/multiple_debuggers_test.py rename to test/amd64/scripts/multiple_debuggers_test.py diff --git a/test/scripts/nlinks_test.py b/test/amd64/scripts/nlinks_test.py similarity index 100% rename from test/scripts/nlinks_test.py rename to test/amd64/scripts/nlinks_test.py diff --git a/test/scripts/pprint_syscalls_test.py b/test/amd64/scripts/pprint_syscalls_test.py similarity index 100% rename from test/scripts/pprint_syscalls_test.py rename to test/amd64/scripts/pprint_syscalls_test.py diff --git a/test/scripts/signals_multithread_test.py b/test/amd64/scripts/signals_multithread_test.py similarity index 100% rename from test/scripts/signals_multithread_test.py rename to test/amd64/scripts/signals_multithread_test.py diff --git a/test/scripts/speed_test.py b/test/amd64/scripts/speed_test.py similarity index 100% rename from test/scripts/speed_test.py rename to test/amd64/scripts/speed_test.py diff --git a/test/scripts/thread_test.py b/test/amd64/scripts/thread_test.py similarity index 100% rename from test/scripts/thread_test.py rename to test/amd64/scripts/thread_test.py diff --git a/test/scripts/vmwhere1_test.py b/test/amd64/scripts/vmwhere1_test.py similarity index 100% rename from test/scripts/vmwhere1_test.py rename to test/amd64/scripts/vmwhere1_test.py diff --git a/test/scripts/waiting_test.py b/test/amd64/scripts/waiting_test.py similarity index 100% rename from test/scripts/waiting_test.py rename to test/amd64/scripts/waiting_test.py diff --git a/test/scripts/watchpoint_alias_test.py b/test/amd64/scripts/watchpoint_alias_test.py similarity index 100% rename from test/scripts/watchpoint_alias_test.py rename to test/amd64/scripts/watchpoint_alias_test.py diff --git a/test/scripts/watchpoint_test.py b/test/amd64/scripts/watchpoint_test.py similarity index 100% rename from test/scripts/watchpoint_test.py rename to test/amd64/scripts/watchpoint_test.py diff --git a/test/srcs/antidebug_brute_test.c b/test/amd64/srcs/antidebug_brute_test.c similarity index 100% rename from test/srcs/antidebug_brute_test.c rename to test/amd64/srcs/antidebug_brute_test.c diff --git a/test/srcs/attach_test.c b/test/amd64/srcs/attach_test.c similarity index 100% rename from test/srcs/attach_test.c rename to test/amd64/srcs/attach_test.c diff --git a/test/srcs/backtrace.c b/test/amd64/srcs/backtrace.c similarity index 100% rename from test/srcs/backtrace.c rename to test/amd64/srcs/backtrace.c diff --git a/test/srcs/basic_test.c b/test/amd64/srcs/basic_test.c similarity index 100% rename from test/srcs/basic_test.c rename to test/amd64/srcs/basic_test.c diff --git a/test/srcs/basic_test_pie.c b/test/amd64/srcs/basic_test_pie.c similarity index 100% rename from test/srcs/basic_test_pie.c rename to test/amd64/srcs/basic_test_pie.c diff --git a/test/srcs/benchmark.c b/test/amd64/srcs/benchmark.c similarity index 100% rename from test/srcs/benchmark.c rename to test/amd64/srcs/benchmark.c diff --git a/test/srcs/breakpoint_test.c b/test/amd64/srcs/breakpoint_test.c similarity index 100% rename from test/srcs/breakpoint_test.c rename to test/amd64/srcs/breakpoint_test.c diff --git a/test/srcs/brute_test.c b/test/amd64/srcs/brute_test.c similarity index 100% rename from test/srcs/brute_test.c rename to test/amd64/srcs/brute_test.c diff --git a/test/srcs/catch_signal_test.c b/test/amd64/srcs/catch_signal_test.c similarity index 100% rename from test/srcs/catch_signal_test.c rename to test/amd64/srcs/catch_signal_test.c diff --git a/test/srcs/complex_thread_test.c b/test/amd64/srcs/complex_thread_test.c similarity index 100% rename from test/srcs/complex_thread_test.c rename to test/amd64/srcs/complex_thread_test.c diff --git a/test/srcs/executable_section_test.c b/test/amd64/srcs/executable_section_test.c similarity index 100% rename from test/srcs/executable_section_test.c rename to test/amd64/srcs/executable_section_test.c diff --git a/test/srcs/finish_test.c b/test/amd64/srcs/finish_test.c similarity index 100% rename from test/srcs/finish_test.c rename to test/amd64/srcs/finish_test.c diff --git a/test/srcs/floating_point_2696_test.c b/test/amd64/srcs/floating_point_2696_test.c similarity index 100% rename from test/srcs/floating_point_2696_test.c rename to test/amd64/srcs/floating_point_2696_test.c diff --git a/test/srcs/floating_point_512_test.c b/test/amd64/srcs/floating_point_512_test.c similarity index 100% rename from test/srcs/floating_point_512_test.c rename to test/amd64/srcs/floating_point_512_test.c diff --git a/test/srcs/floating_point_896_test.c b/test/amd64/srcs/floating_point_896_test.c similarity index 100% rename from test/srcs/floating_point_896_test.c rename to test/amd64/srcs/floating_point_896_test.c diff --git a/test/srcs/handle_syscall_test.c b/test/amd64/srcs/handle_syscall_test.c similarity index 100% rename from test/srcs/handle_syscall_test.c rename to test/amd64/srcs/handle_syscall_test.c diff --git a/test/srcs/jumpstart_test.c b/test/amd64/srcs/jumpstart_test.c similarity index 100% rename from test/srcs/jumpstart_test.c rename to test/amd64/srcs/jumpstart_test.c diff --git a/test/srcs/jumpstart_test_preload.c b/test/amd64/srcs/jumpstart_test_preload.c similarity index 100% rename from test/srcs/jumpstart_test_preload.c rename to test/amd64/srcs/jumpstart_test_preload.c diff --git a/test/srcs/memory_test.c b/test/amd64/srcs/memory_test.c similarity index 100% rename from test/srcs/memory_test.c rename to test/amd64/srcs/memory_test.c diff --git a/test/srcs/memory_test_2.c b/test/amd64/srcs/memory_test_2.c similarity index 100% rename from test/srcs/memory_test_2.c rename to test/amd64/srcs/memory_test_2.c diff --git a/test/srcs/segfault_test.c b/test/amd64/srcs/segfault_test.c similarity index 100% rename from test/srcs/segfault_test.c rename to test/amd64/srcs/segfault_test.c diff --git a/test/srcs/signals_multithread_det_test.c b/test/amd64/srcs/signals_multithread_det_test.c similarity index 100% rename from test/srcs/signals_multithread_det_test.c rename to test/amd64/srcs/signals_multithread_det_test.c diff --git a/test/srcs/signals_multithread_undet_test.c b/test/amd64/srcs/signals_multithread_undet_test.c similarity index 100% rename from test/srcs/signals_multithread_undet_test.c rename to test/amd64/srcs/signals_multithread_undet_test.c diff --git a/test/srcs/speed_test.c b/test/amd64/srcs/speed_test.c similarity index 100% rename from test/srcs/speed_test.c rename to test/amd64/srcs/speed_test.c diff --git a/test/srcs/thread_test.c b/test/amd64/srcs/thread_test.c similarity index 100% rename from test/srcs/thread_test.c rename to test/amd64/srcs/thread_test.c diff --git a/test/srcs/watchpoint_test.c b/test/amd64/srcs/watchpoint_test.c similarity index 100% rename from test/srcs/watchpoint_test.c rename to test/amd64/srcs/watchpoint_test.c diff --git a/test/binaries/jumpstart_test_preload.so b/test/binaries/jumpstart_test_preload.so deleted file mode 100755 index ef48e265db3d98e5359109278e4ec44658b080ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15944 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 Date: Mon, 15 Jul 2024 10:53:18 +0200 Subject: [PATCH 025/144] ci: change pytest base directory for Test workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7954ff92..65b66948 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,4 +47,4 @@ jobs: - name: Test with pytest run: | - cd test && pytest --ignore=other_tests + cd test/amd64 && pytest --ignore=other_tests From df71f46439a9b6a9dae09adde26b56f3a492a444 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:05:29 +0200 Subject: [PATCH 026/144] test: move shared sources in common directories, add specific tests for aarch64 --- .../binaries/binaries/antidebug_brute_test | Bin 0 -> 70592 bytes test/aarch64/binaries/binaries/attach_test | Bin 0 -> 70624 bytes test/aarch64/binaries/binaries/backtrace | Bin 0 -> 70520 bytes test/aarch64/binaries/binaries/basic_test | Bin 0 -> 70352 bytes test/aarch64/binaries/binaries/basic_test_pie | Bin 0 -> 70352 bytes test/aarch64/binaries/binaries/benchmark | Bin 0 -> 70272 bytes .../aarch64/binaries/binaries/breakpoint_test | Bin 0 -> 70400 bytes test/aarch64/binaries/binaries/brute_test | Bin 0 -> 70496 bytes .../binaries/binaries/catch_signal_test | Bin 0 -> 70520 bytes .../binaries/binaries/complex_thread_test | Bin 0 -> 70576 bytes .../binaries/binaries/executable_section_test | Bin 0 -> 70528 bytes test/aarch64/binaries/binaries/finish_test | Bin 0 -> 70424 bytes .../binaries/binaries/floating_point_test | Bin 0 -> 70392 bytes .../binaries/binaries/handle_syscall_test | Bin 0 -> 70632 bytes test/aarch64/binaries/binaries/jumpstart_test | Bin 0 -> 70400 bytes .../binaries/binaries/jumpstart_test_preload | Bin 0 -> 69784 bytes test/aarch64/binaries/binaries/memory_test | Bin 0 -> 70552 bytes test/aarch64/binaries/binaries/memory_test_2 | Bin 0 -> 70384 bytes test/aarch64/binaries/binaries/segfault_test | Bin 0 -> 70312 bytes .../binaries/signals_multithread_det_test | Bin 0 -> 71376 bytes .../binaries/signals_multithread_undet_test | Bin 0 -> 70784 bytes test/aarch64/binaries/binaries/speed_test | Bin 0 -> 70280 bytes test/aarch64/binaries/binaries/thread_test | Bin 0 -> 70616 bytes .../aarch64/binaries/binaries/watchpoint_test | Bin 0 -> 70424 bytes test/aarch64/run_suite.py | 5 + test/aarch64/scripts/basic_test.py | 22 +++ test/aarch64/srcs/basic_test.c | 168 ++++++++++++++++++ test/aarch64/srcs/floating_point_test.c | 106 +++++++++++ test/aarch64/srcs/thread_test.c | 59 ++++++ .../srcs/antidebug_brute_test.c | 0 test/{amd64 => common}/srcs/attach_test.c | 0 test/{amd64 => common}/srcs/backtrace.c | 0 test/{amd64 => common}/srcs/basic_test_pie.c | 0 test/{amd64 => common}/srcs/benchmark.c | 0 test/{amd64 => common}/srcs/breakpoint_test.c | 0 test/{amd64 => common}/srcs/brute_test.c | 0 .../srcs/catch_signal_test.c | 0 .../srcs/complex_thread_test.c | 0 .../srcs/executable_section_test.c | 0 test/{amd64 => common}/srcs/finish_test.c | 0 .../srcs/handle_syscall_test.c | 0 test/{amd64 => common}/srcs/jumpstart_test.c | 0 .../srcs/jumpstart_test_preload.c | 0 test/{amd64 => common}/srcs/memory_test.c | 0 test/{amd64 => common}/srcs/memory_test_2.c | 0 test/{amd64 => common}/srcs/segfault_test.c | 0 .../srcs/signals_multithread_det_test.c | 0 .../srcs/signals_multithread_undet_test.c | 0 test/{amd64 => common}/srcs/speed_test.c | 0 test/{amd64 => common}/srcs/watchpoint_test.c | 0 test/run_suite.py | 35 ++++ 51 files changed, 395 insertions(+) create mode 100755 test/aarch64/binaries/binaries/antidebug_brute_test create mode 100755 test/aarch64/binaries/binaries/attach_test create mode 100755 test/aarch64/binaries/binaries/backtrace create mode 100755 test/aarch64/binaries/binaries/basic_test create mode 100755 test/aarch64/binaries/binaries/basic_test_pie create mode 100755 test/aarch64/binaries/binaries/benchmark create mode 100755 test/aarch64/binaries/binaries/breakpoint_test create mode 100755 test/aarch64/binaries/binaries/brute_test create mode 100755 test/aarch64/binaries/binaries/catch_signal_test create mode 100755 test/aarch64/binaries/binaries/complex_thread_test create mode 100755 test/aarch64/binaries/binaries/executable_section_test create mode 100755 test/aarch64/binaries/binaries/finish_test create mode 100755 test/aarch64/binaries/binaries/floating_point_test create mode 100755 test/aarch64/binaries/binaries/handle_syscall_test create mode 100755 test/aarch64/binaries/binaries/jumpstart_test create mode 100755 test/aarch64/binaries/binaries/jumpstart_test_preload create mode 100755 test/aarch64/binaries/binaries/memory_test create mode 100755 test/aarch64/binaries/binaries/memory_test_2 create mode 100755 test/aarch64/binaries/binaries/segfault_test create mode 100755 test/aarch64/binaries/binaries/signals_multithread_det_test create mode 100755 test/aarch64/binaries/binaries/signals_multithread_undet_test create mode 100755 test/aarch64/binaries/binaries/speed_test create mode 100755 test/aarch64/binaries/binaries/thread_test create mode 100755 test/aarch64/binaries/binaries/watchpoint_test create mode 100644 test/aarch64/scripts/basic_test.py create mode 100644 test/aarch64/srcs/basic_test.c create mode 100644 test/aarch64/srcs/floating_point_test.c create mode 100644 test/aarch64/srcs/thread_test.c rename test/{amd64 => common}/srcs/antidebug_brute_test.c (100%) rename test/{amd64 => common}/srcs/attach_test.c (100%) rename test/{amd64 => common}/srcs/backtrace.c (100%) rename test/{amd64 => common}/srcs/basic_test_pie.c (100%) rename test/{amd64 => common}/srcs/benchmark.c (100%) rename test/{amd64 => common}/srcs/breakpoint_test.c (100%) rename test/{amd64 => common}/srcs/brute_test.c (100%) rename test/{amd64 => common}/srcs/catch_signal_test.c (100%) rename test/{amd64 => common}/srcs/complex_thread_test.c (100%) rename test/{amd64 => common}/srcs/executable_section_test.c (100%) rename test/{amd64 => common}/srcs/finish_test.c (100%) rename test/{amd64 => common}/srcs/handle_syscall_test.c (100%) rename test/{amd64 => common}/srcs/jumpstart_test.c (100%) rename test/{amd64 => common}/srcs/jumpstart_test_preload.c (100%) rename test/{amd64 => common}/srcs/memory_test.c (100%) rename test/{amd64 => common}/srcs/memory_test_2.c (100%) rename test/{amd64 => common}/srcs/segfault_test.c (100%) rename test/{amd64 => common}/srcs/signals_multithread_det_test.c (100%) rename test/{amd64 => common}/srcs/signals_multithread_undet_test.c (100%) rename test/{amd64 => common}/srcs/speed_test.c (100%) rename test/{amd64 => common}/srcs/watchpoint_test.c (100%) create mode 100644 test/run_suite.py diff --git a/test/aarch64/binaries/binaries/antidebug_brute_test b/test/aarch64/binaries/binaries/antidebug_brute_test new file mode 100755 index 0000000000000000000000000000000000000000..93d694737c6a950cfbf4d5cb1d6fb713f6847399 GIT binary patch literal 70592 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 literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/attach_test b/test/aarch64/binaries/binaries/attach_test new file mode 100755 index 0000000000000000000000000000000000000000..2fea0a9100188b7d40f6b9851d4ab0eee270016b GIT binary patch literal 70624 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 V>bM%)x!08&RcKOkgE03mT~%lLfBZpg}JH{IPp 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 Jbrc(o{|~gSsYw6; literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/benchmark b/test/aarch64/binaries/binaries/benchmark new file mode 100755 index 0000000000000000000000000000000000000000..ca47274c9b4bd7b086861b2c283524e86520ad0b GIT binary patch literal 70272 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 literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/breakpoint_test b/test/aarch64/binaries/binaries/breakpoint_test new file mode 100755 index 0000000000000000000000000000000000000000..ac5cbb2dfb685b722bbfb574d1ff511c49a5becd GIT binary patch literal 70400 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 pV>jPAetjNRuKZ9~>pAaEr4&XXYMz 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 m&!v1f>D+_yXQuljJP=B*)Z< literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/catch_signal_test b/test/aarch64/binaries/binaries/catch_signal_test new file mode 100755 index 0000000000000000000000000000000000000000..79799718085e0d3e898ebb21787f7761e08edbce GIT binary patch literal 70520 zcmeI0Yiu0V701u+n#ABF*dc_p3CSihaOCj_DYvDz`d?{s?)l$y?&CLiuJ`8tZJpcWu^4Gd(6c1o$XuhOZNu=qnV2+frYk8; z%V;Uh;r0f8G%jjCvT14?IaW%7AIaMs7q8YXH8PK2ijFS!3aiyKZ(n6pwG^zz{3~-E zoHFf$Q>I6?$Lu_gTdR5EO5TXg8?kw6C+x)3>iT4D@^st&qIN5{Nl`1YQubW*vr>Qk z%D}C$8n0IKzRr0v&re&LZD5xjZ#FKYcK&MhJ=~JdWp2r5*W`1Bq2V>ITk7fGv@umK zrq-EtFXH~?9k=h=OZVON#>K?1fB4cp&&0p@^si3reeXBvhh*L|2kB2@q)5Eoc7ZN! zI-azD+LE9qI$8%tf);ZZ(=RyaPQn(}>G=9t^rl(#Ue=TRM9U2?wk^*3)P{}I)pfU$)6u=t$$BNP zFIV=xQuod+`C`HAb~AbDzHgvdu){hgv~HkXQk>spg7=5mnSZ6Wog;LApB(!czv2Y# zvDzbnAE&!)FZ%r_?1waJKj2~eovWU<`b0&DbB{Lud8;?_xM5xWGd4cP@v_&noD1o? z=cVnvkZzBFAmyWwK2JMgd)Vr6?q9wiEu$gbPBmyc8q&k(=&_I?s!O-Z%E6_A$`7zY*!=`Z%iHf-HE9Ljcp}5GIi*M!3}^wE`U?;!fyIR6UT%H(1&+VKxowx*Co@5Ad^%UgoI3Q|;2nL? zM3dL>nETq&x20orYcR)&_Q?^>3C3thPd?6b=lMR8q>-+DZFJiQP3g(0LoWvJRp&5a@*O`lRjGWF?Pa!kwyQs`R7Tk5*&bp0Jlj)j$JxHZ_C2p|pjf-e$etyZ?SJ+BP^3%=FY1VeIrPc+> zM;jhXv^^Z(b@rXt7KkKy!~Dox^~_qv{)7BnV(n(Onz!TZJ3-DOmY(A0LvDY?v^PH* ze=H_d6c7Lb5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH z0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI z5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X z009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH z0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI z5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X z009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009vA zella^#`X+=2Gk1?&!1DAGYnwBJF?wxKb49 zXxubhq3sQ(Vuz(uw*4yG{-m`{)-JL3T5HuLY-O-w=4ZR1Gge<@?ObcGvv%B0M0K*h zu4~-JSq!A(FSokJ{i|Zu$D;?*$JRe>*H@Wo4>BjLf6}&3THm_1_y4_l+JgUY?TCHv zv(5N<1r-TsB=e@)w`}=R^5(snp@Kh@T(=>$KDBnu`VB*YzwrxJiR4Dz1jT5xzW7jM zg2(8tt0!o@t|Hy1 zA(`jXMEbo}>+?w8XSF_`My5CVOiO|mkbdVgDslf;(=pxnp@sxC)9ZEh7FwboZdLq3 z(sLsubN|W|mpI zPvpFe@d|YRByFP1gs7IY>8FKONB9`Mmb09<_2#4fpNzc6B*({jT-EiwGx4?i#OSr} zea$D1+eF9ppyoI!+Ru^5e%Q!upEUo|k@?@j@iPCY-fDGos-5 z@ihHhO~*||r(@sCd^!?2cXixjk#T2xUmAZV690Cvt`>^wGJ$&Y{A&>ZekJ-{e;^5U z&XYO(f@1tVp0#L}GeJMK z>$=E#QjsS|_Q9fsR+=ZcYRraftO|!(` zZuRK-|FvM;`J}%`UNPO!Zq{X8(fGSqPuiHU84CIdE2jP<(c`t}E#28u^4Fz`bYqqr z$Iav%-|eFthl%AJ%RO#B?|jd5?{#_$q7_6d`DK5ow>Q;8S+C^v<;uQSa{K|OCtoah zW$q_ibo%nejGK3|ez8<`+#&9;I53#^d@q|?TR+0-%@uNvTPnE+9IxP)4p48&9q^p& z(7?a}P6>GqNBUKvJ+9x=@04?W1s>G*%05rdakh80?%d{V+qKnk$l3a>U9CGiwy@m3 zYp=6yhi%=lwTqng&fRUTozCv<+wa`g?R2-cb#8OW8_xOZ_RfyBEzbJXx?8E_=E`1J zaGZQD)8mwVx8yqmZmuxhxM3qX9o;*f89v=Rw+NkXH#NZT)V!pHkTa1@6yS zzhqkWd!=%&Sg3LwZYz0tR}yS(Fz-_;m{ZF4hPf_#liP~H`$&2Hb~pBCIafWCVYa2o z&^mYQ4&-`x)FSs~`rsWvDgG!1c(2!fum7nc_g+4VrcZCZZ>ZIKvGyym;c^2z^3#Wp ziJ<7cMD2}MSEJ{p`kS~C&ez|cYLgmhZ8U!?>;K@prjCEqR@CafU9TaLpAWQ)^W`qC z`KN3}?H;>e`I#tpa?RIY+~2Ue=Igylt={)JEGSWace5&YY0cOBmD=e1HBRlloG*8C z_4OX6cC8tfWd1U?&fm&(kSoGe^QZ4?7TCBAjPldG*ozRr^vnnd{N<$5~sz z&-DLS+5a~goMMqnG+uqR@_XfKel%b2&%>WGU-p-Nn_Qi~XOGzY30WK$-CsJc?t}B( zCLub$-p2>+&3ROkLq+q|zRPXl{27vFU>!%h&^+}oaYZy8Uw^MP2ffzvGrejy(feE# zZS9N{)HMI5h_3lztM5J>y4j9@%1&7RKeUw}oxgsUa-Y(I%?Lk@bgtuSY|AY9 P_cfWwe`=t$(foe{V}Cc* literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/complex_thread_test b/test/aarch64/binaries/binaries/complex_thread_test new file mode 100755 index 0000000000000000000000000000000000000000..237d9839c909b6a1f693453a667eb8c9bd166ad5 GIT binary patch literal 70576 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 literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/executable_section_test b/test/aarch64/binaries/binaries/executable_section_test new file mode 100755 index 0000000000000000000000000000000000000000..7f2d21dcc0ede8b21eec7e688624e3f0c6f019b5 GIT binary patch literal 70528 zcmeI0e~esJ701uaY}+ku>26zEw6&csh)C?vUxA_xo!xD_-O!d&wnh`<>)V;v?%4U| zo42srny?s*8bxn@yCM3G)f4R*i-}9Hb6-1A4?F!D9d=xd-v?V zc{58e#y?EHFL`_3{oZrVJ@@nOyKmm!w{7S4cq~SGB*inxyjjQ@8buTN| zug&$F4OactYTR8MCv|?3GurVUGmOqZ3VFBxot8n7YA!y4(2gq9%zK&Y(*@&HN9sp5!xn+{8jhROI&8`Ajy^ z)2ubizkLQ>)>||GhgnbZ89i3B(9EC8j_a0ODhw72c9|@T?{CJcc(&_V1v^)yLfI{O zPR8S|%#dvz$QA8;?yy4zr;sTRligo(Jt|17M9NCIg?e`Oc6D3pQyVr;U9P*8tlqv| zR@QNygSm?5xP802^QEHGXZPnFPJXaZDw=Vvz_qDkFt0eTnK-YJ;EWq9eWKmLR@eDc zV-NF+NRX^F(T@4;IPK?aIdmULm+y% z{Nbf}F{JC-m-shBx=AfEy&KZ!`%3WV8%B?FetC}c7!B!WYJS)8kRIMoPlWVvJtsqY zxSrD?-E3KY{xc!nY{|Yp9@6J&k!;U~^aUY(RWk9X$s@mcVlqKvT|`GGk34g7JYk(z zmkI8hAev}@wx`+_zkMtjUv*w$^z{3F&wU+1pWprix4S~^=egYWO_?Ru}L+V984r($Ky z$s@n^@6FrusJfVQ?e9w8mX6V_evK17)e(;IYidbXpWxcL-eXA`*}K1sZhL!Px;lB} zS^s`&{V(%;YTfkW#f9|r#f9mo=_|CH+hbDe~zPNSlZJumgJh*zd9@6 zw5YpfWN$x@acN30{MeBj#zss%A0t|F+4tw%oKt+i z%zclUJkoxU+Z?}cb-v`cOOEY!F7ITMUMXqk^QDaKIZ57eOYX4W8}2XIhqN#03^|!W&)-qm zFSb1KnKWB@o_{!5t9_2`6}EY{E8efwM%aFV?NPR;*q&i~n(cFJ&$4}uZ5y9*#18MJ zmZ6og&5e9BE2XKe>t>byGn zNXw&%u7~2gUw!4Jt3{HyAwH#6-Mf}>``diJ%$khNacx`Kdj((q$mkaWeeEN&AD!jB z`ih@RBpe_B0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd& z00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY z0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4ea zAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd& z00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY z0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4ea zAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd&00JNY0w4eaAOHd& z@c%$yv^A(QZEVaGeTT90jXg0txL9D?56lfNK4RMC$slsMX`6KPQPUnW{VO8<@4R0t ziF7<3bj;HJmf*q+OJ_{~Ri^*6vGa_**4S%|Rg*9m~A_otIL?&}{cdV|Sz8&d02YuBvbFzB~8e$ptB*l06BG1{az zKGd1uG5VV72^wmuw@j_mP#Q|mtf}=`ug|8|CO0lYb4c%Ry?-m|x~SLZlCHmc{VLLR zTCdL|eO~MJ`K0%=USB{Xy3j)%3A&o}ISDG6A$`8<{g-mH@kS;pb`81OIl~({w#FuG_Pm=Yx12 zjqI;M{)KcRxX?1qeB0@CWZaqVm)7}a#Q(BCuXc*+GElQRCw%|6YSHJqDgjOQllgpu zV*EV5VBY7x8~+K`TjT!L!TyWXDP%X!px-rvo?$&1ir35MeO*ScUvKxZ-WFO{>i;0? z9oh)l$5~&*>r?NS?nKX+Jl96@|HRjqHtb`+kUa6m=XZRDJkOat(fa?)dNNd>UcSkC zd%W@adWZF9oM$tZ_R#x6Ux(MQ-pv2wtP8j3e9!s$r}s};mvY=Rga3M?NB8;L{c#tN z{_fD`z_s_alLh^5?!Aq%d-1(mS-QN z>xYQt9LpIypSSLH>_gUpqG(0YT(9B{9ypN7P}XssgSm?5xRzJ2GWk-`sc@cb$vT)X z_1k$X>y_M!We;+~QlXr8JSUr4+uXxCkSpda+jZ?>%PD&9FdcC1f@5U|3x#103AHTt z^y*xlAty8F+5LIPsyG=hS1MYbQ}MV&%i6xTbJsR&+wQHFMb_5O@9x~y+s$&%?tRv_ z9j153*1cr)?7Xw9bEkFZ_U(6X>$CbgyLN7~sNfVb<>7SC&fczWYkg|ntsI)q^=GV# zXS<$Nuye(!&J7!>?6_{pol?ZTw{Mp<4XSTfw_xg%S~*pJrD8I1aaLx?77TVici0K% z7;y6Wl67y%>(?vbJZFkIN)*idmVPB^^OFZX7HygrG>juTruG>|&Njwfjr17fx^W z-pQWXT+te=I9Vzb>~dW<=jVhv%2dfDlc&O9G3syeBo(;KUKdH-+zVy5aYKFr z@pj=K`hBAIdZVk+{Z!kx@I^RYe}}3~s-v;d_^qt}gTFa-{Nv_At$x2xeWyqA?dG<8 zlWY7LbD?&RnXvp^ly7tWd7|&T&l+9h_4`S!e)qGRe?<9xkyZJo)_DESQX8#b{nQ@f zc=<-xwtkPPT^kHbQooF?^&6QE^Mx?gc>QiuJ8m4K{B-^gnD{Q^r{8yK^?fDr^1lup zU*o^Sn$)E~$MribmE|5+qnzkAjGE)pLdf1LZT z=5y-*R_6aF<;fs0>aT6J&#~LWP`rLe4}HjZSzr2W%82^Ae8j|0$j#wH*O!i~>);ai ziOW>|=68D8d_j*&aOlu@wQq1=IDYy`3I^73G=;`#`wCx(rsM1Hwl+WO^z)+rK~`-M zy~UTJt(}(qD~-P)qHBEE>e>stZZhMaF$K&2MLYS_`t`Y#?<%c596!+-jPSikavfKF S+h>UX+PuK?pXz9AH2&Y;`Za(6 literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/finish_test b/test/aarch64/binaries/binaries/finish_test new file mode 100755 index 0000000000000000000000000000000000000000..f461a5d790e001c03e38becc4d1b3b9d7eca6aab GIT binary patch literal 70424 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 literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/handle_syscall_test b/test/aarch64/binaries/binaries/handle_syscall_test new file mode 100755 index 0000000000000000000000000000000000000000..0623cfd7bb6b88e91762119a854be5b9076f4e91 GIT binary patch literal 70632 zcmeI0e~esJ701uaZ0VNmk8LRhEVR>tMv)HvB@{)O-EF(u*e!Kgv?PYdo0-?$vGZen z^OkN~A}jF^P=VdhNF!1kjHci}#Z+vJ!O}$22$E8i5Rs;>fix+9v_X{7F5@}x-P3*Z zW)?}*KTN)F^Y+~Pz2}^J@8{inXWrbuZsYoREJk`I=ocil(bhmon}#zpCwGK67Isx2%va4Ua6dZ8tl3)9O^E zoLUjgdp_rvXWYH%PCEaYFFt(5{#|eE{W*=d{p`NBCtjZOaT&LaLGp=@T;f07F9o`^ zX@5rkNG3sTG^U9{5eZtrHGN<~!+JLJLOxntG=(nXY3BbE){}fkujMQ(jfz~qjqhf8 zWc@VLkFcKPJ9^#C!rZ9H^UZlm$fpr;%3s~s?}s|=5|UWvwUqeti6UyiHqZ zjLULK+z?x7m+PS4o}eeVELz|nzv!>$Rl;oiu^AG@=Y~0#R8JfITuny7d&~ImHhL@f z8`iac%=pLHKYX6N9@4{G>tskbN0y)WOh`9Jvai1%(sj<#Fl6*N_bcasULzshOu=s& z4e8;}g9k%;c${M)UC(Gqv?rt+G=BblAw4{={ULp(?-~3%5YlIb^g|(ic1S-Q(&vQq z#mU4UCwBe%>4^l5^Vzg}V%H1%4=1eG)a4wvog+Fo_qpzB`%|AEPsSI&CNX;ZZNKEU zBSD*Af1c~7L-iwEkAK0B_1k~T^`)Wu@3`I>s=v(jOsIaG>kowLuW|jUQ2kF_KN6~+ z;QHxM{WRC(w_P0jH8gsB5#Mhm=$TWAboKPLH2yZv|0K_o-tVp^xUGfn-`8r7pW)lr z&vjSRyj}iWt6d>{bpE`d#wbnW{FP>G30ppA&mQKF;ZvMz$(in|%`xW^v1f?6`CKFZ zONd@vdg1<8J7#|KJ4-HfJoW9~-|I8$7G${b%)V8&xmkT=#UQZ%N1K zW`B(DbXP|?#vfBly82@t`|ErsX|(U2PP*mYwsdu3*Af5u>iA_o`uuBM?XT1l@v5Ke zuie!crK_i=$Jve^KN1@~PDEp|!`F{XzP-YQxy43DkDuoGu}|N_Mmv4~=)+-30>@oo%W>^b z#`nfLXYXs>IlH~%#GBz$L>zE|00@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?x zfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=9 z00@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p z2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?x zfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=9 z00@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p z2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?x zfB*=900@8p2!H?x{J#(wn-PpOZEVa8e8^a>?=CqLY{-Eht^#f*LIbIKqs;L_sCj0;19556AZ`&{hT5$5skRb$4}rHu=drGs7isIJsg~YGrEq zvX!fb{rc)p86^@MttKc&YZQE_F~NQGH`Nm~(o}D`xPC)vC_&Rm*KNIi3F-Q-*Qb*{ z_jISaMGmrE<3CYa!GSYLTUY}3R)>VQoC*Ak;{tLL;_#hL!``er9y1OT{$nnua z=WCNnPzsi9<2sRZG{!T~^^-J#G7_Rj&Ox5JbajMJpf_@sbGX@fH2=ZKb4;>-ocmQ> z&%waIah(Ku<8xp3q5ZbeK0QF$PcF^#gUEVV&21kx{$r8x-^Ttj{;1wzbc&oGL3IHi z!u8L+-}vk2Ay@tDYy3YJS`X&q!lZ3nhxhvajrxg7Gx2^QZkhje6ie zmzFnO4~^^gDCcR$GsrWC4n+6|&t(=x&t2_zEYj~(&rAIeM*L6s^O{Rho#m$Ee8=}c zUyJVR_auOKY4kji&+{1X$D`(R-go1F!MqyJw?*tfPn|+``4oEZ6uQNFG8C`HW447Qo{7U>o{b})KlRrBC zH>U9aJL^de4%th1`g22dEndrdvwl~Lf4p&@c1+>l=jWe15A}PJJ%#_*Sr`6Mo`3M? zHF*xp`j+e7Dg3`-^yufsxBY%6_tAwQ4L$1XrV=tgWL@SP&GS>%lOaDX{zme|8_#FD zBkOuAQf0a>N0w!0@|I^0&~+oka-QX^T_{-F9ebO#wIo_eG}o(m!&|qevXpaNXCPnk z9M|%SR<=+sITg;6D_aAFa>g!LIj`(iEPI#}mWx9L$8&P2<;^{;t@%>kvR&8SVL2tw z-9cMjyXaWC;bL(IhlFYtdwO-QgLWxbaIDIXia%n{sdzj*%Ua*p(YwxCw`r|qk+t@& zO&z@(x>)Ysbf>kh$F%mW?IWvudufJnmzt!K-xpAFE1CE#7o=bOc+|b!& ztxT=BnL`WtOxCJ+w(D6%J72okxN0>GIj&oFFDl}`p}*Ifgw)^LC5Za%Ou^w)nM%cE z;=x(j5nB-0h5SyZE{Bt|J)3+yCdet}cs@m^SaG~MS4&)job1Y@%x<%?gZ%L9e4*ZP zpja-2I-hhM7`ENIDQ|c4Uj2ZqT%~LU#L3Q--TE>$sJCxl~uNxXpD6wgi~cP{E^= zKcB8;@O3R$b#+dT9J1Q&79dOjus;<+lNi*Khb=HoC^^ zdz#v_+#bH7{O;yPd8gNSeVD~RyNxi_ zczr)qtG`=l{~E9J|CWjGGzs-RQmy`OBJuKDOEmrw)`W@vZlmv;YPBDB@vrFfe~8s^ zyuO#JJ!az6v}?iG-E710qh^3=+ZBxQPaCV|`&wB)+`RZtFbo~2Fymq7S!)mXI z#4A6Q_ebLOd#>7B)zR2!|IbC@_5E7yfk=F`|HIrqn{WNyOy9$MCW64Izt+{h#BOs! z@%p~L`6I^5`qaHCJ?i)WEhb(UwyrPjSJ%NQj*;i5f2yDerGO_HcZ1M$p5Hk>uL1`pS3xX7Oj+0?&V_qp{KWe*tRhS?&M; literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/jumpstart_test b/test/aarch64/binaries/binaries/jumpstart_test new file mode 100755 index 0000000000000000000000000000000000000000..78598581afeba47b62a3b2ff023827857b613126 GIT binary patch literal 70400 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 literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/jumpstart_test_preload b/test/aarch64/binaries/binaries/jumpstart_test_preload new file mode 100755 index 0000000000000000000000000000000000000000..05c57d6b97a9216adcf1e62210d5e84af808c7c1 GIT binary patch literal 69784 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 c5ASM6QWJ@$T5(vJbO6 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 aTev%vT-#OOt`*{cdZX|84|Oy)9RDu}MvapI literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/signals_multithread_det_test b/test/aarch64/binaries/binaries/signals_multithread_det_test new file mode 100755 index 0000000000000000000000000000000000000000..a7c660d5f368a6d5c8b713303ed41437d4039f29 GIT binary patch literal 71376 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 RtNaV|OysXQ(AsGJe*pqJ&}9Gs literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/signals_multithread_undet_test b/test/aarch64/binaries/binaries/signals_multithread_undet_test new file mode 100755 index 0000000000000000000000000000000000000000..22c78cd8e860028ff5440cd8a79486df4c993fc1 GIT binary patch literal 70784 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 X%dzyX5ef;4Zl9?l|G|OUhV%agI(-kR literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/speed_test b/test/aarch64/binaries/binaries/speed_test new file mode 100755 index 0000000000000000000000000000000000000000..caf5bc11f895ba06d38d89d87415b64c725fd3e4 GIT binary patch literal 70280 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 literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/thread_test b/test/aarch64/binaries/binaries/thread_test new file mode 100755 index 0000000000000000000000000000000000000000..1be46c07ebf6270c53245c68cd411c2fbc9cfbc7 GIT binary patch literal 70616 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 literal 0 HcmV?d00001 diff --git a/test/aarch64/binaries/binaries/watchpoint_test b/test/aarch64/binaries/binaries/watchpoint_test new file mode 100755 index 0000000000000000000000000000000000000000..f5e595dafbe33a7fa28522d0cb38ee02f783c726 GIT binary patch literal 70424 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 literal 0 HcmV?d00001 diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index ca460a8d..c43eda63 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -7,9 +7,14 @@ import sys from unittest import TestLoader, TestSuite, TextTestRunner +from scripts.basic_test import BasicTest + def fast_suite(): suite = TestSuite() + + suite.addTest(TestLoader().loadTestsFromTestCase(BasicTest)) + return suite diff --git a/test/aarch64/scripts/basic_test.py b/test/aarch64/scripts/basic_test.py new file mode 100644 index 00000000..21eec3dd --- /dev/null +++ b/test/aarch64/scripts/basic_test.py @@ -0,0 +1,22 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + + +class BasicTest(unittest.TestCase): + + def test_basic(self): + d = debugger("binaries/basic_test") + d.run() + bp = d.breakpoint("register_test") + d.cont() + assert bp.address == d.regs.pc + d.cont() + d.kill() + d.terminate() \ No newline at end of file diff --git a/test/aarch64/srcs/basic_test.c b/test/aarch64/srcs/basic_test.c new file mode 100644 index 00000000..66b52dc1 --- /dev/null +++ b/test/aarch64/srcs/basic_test.c @@ -0,0 +1,168 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +#include +#include + +#pragma GCC optimize ("O0") + +void register_test() +{ + asm volatile ( + "sub sp, sp, #96\n\t" + "stp x19, x20, [sp, #0]\n\t" + "stp x21, x22, [sp, #16]\n\t" + "stp x23, x24, [sp, #32]\n\t" + "stp x25, x26, [sp, #48]\n\t" + "stp x27, x28, [sp, #64]\n\t" + "stp x29, x30, [sp, #80]\n\t" + "nop\n\t" + "movk x0, #0x1111, lsl #0\n\t" + "movk x0, #0x2222, lsl #16\n\t" + "movk x0, #0x3333, lsl #32\n\t" + "movk x0, #0x4444, lsl #48\n\t" + "movk x1, #0x5555, lsl #0\n\t" + "movk x1, #0x6666, lsl #16\n\t" + "movk x1, #0x7777, lsl #32\n\t" + "movk x1, #0x8888, lsl #48\n\t" + "movk x2, #0x9999, lsl #0\n\t" + "movk x2, #0xaaaa, lsl #16\n\t" + "movk x2, #0xbbbb, lsl #32\n\t" + "movk x2, #0xcccc, lsl #48\n\t" + "movk x3, #0xdddd, lsl #0\n\t" + "movk x3, #0xeeee, lsl #16\n\t" + "movk x3, #0xffff, lsl #32\n\t" + "movk x3, #0x1111, lsl #48\n\t" + "movk x4, #0x2222, lsl #0\n\t" + "movk x4, #0x3333, lsl #16\n\t" + "movk x4, #0x4444, lsl #32\n\t" + "movk x4, #0x5555, lsl #48\n\t" + "movk x5, #0x6666, lsl #0\n\t" + "movk x5, #0x7777, lsl #16\n\t" + "movk x5, #0x8888, lsl #32\n\t" + "movk x5, #0x9999, lsl #48\n\t" + "movk x6, #0xaaaa, lsl #0\n\t" + "movk x6, #0xbbbb, lsl #16\n\t" + "movk x6, #0xcccc, lsl #32\n\t" + "movk x6, #0xdddd, lsl #48\n\t" + "movk x7, #0xeeee, lsl #0\n\t" + "movk x7, #0xffff, lsl #16\n\t" + "movk x7, #0x1111, lsl #32\n\t" + "movk x7, #0x2222, lsl #48\n\t" + "movk x8, #0x3333, lsl #0\n\t" + "movk x8, #0x4444, lsl #16\n\t" + "movk x8, #0x5555, lsl #32\n\t" + "movk x8, #0x6666, lsl #48\n\t" + "movk x9, #0x7777, lsl #0\n\t" + "movk x9, #0x8888, lsl #16\n\t" + "movk x9, #0x9999, lsl #32\n\t" + "movk x9, #0xaaaa, lsl #48\n\t" + "movk x10, #0xbbbb, lsl #0\n\t" + "movk x10, #0xcccc, lsl #16\n\t" + "movk x10, #0xdddd, lsl #32\n\t" + "movk x10, #0xeeee, lsl #48\n\t" + "movk x11, #0xffff, lsl #0\n\t" + "movk x11, #0x1111, lsl #16\n\t" + "movk x11, #0x2222, lsl #32\n\t" + "movk x11, #0x3333, lsl #48\n\t" + "movk x12, #0x4444, lsl #0\n\t" + "movk x12, #0x5555, lsl #16\n\t" + "movk x12, #0x6666, lsl #32\n\t" + "movk x12, #0x7777, lsl #48\n\t" + "movk x13, #0x8888, lsl #0\n\t" + "movk x13, #0x9999, lsl #16\n\t" + "movk x13, #0xaaaa, lsl #32\n\t" + "movk x13, #0xbbbb, lsl #48\n\t" + "movk x14, #0xcccc, lsl #0\n\t" + "movk x14, #0xdddd, lsl #16\n\t" + "movk x14, #0xeeee, lsl #32\n\t" + "movk x14, #0xffff, lsl #48\n\t" + "movk x15, #0x1111, lsl #0\n\t" + "movk x15, #0x2222, lsl #16\n\t" + "movk x15, #0x3333, lsl #32\n\t" + "movk x15, #0x4444, lsl #48\n\t" + "movk x16, #0x5555, lsl #0\n\t" + "movk x16, #0x6666, lsl #16\n\t" + "movk x16, #0x7777, lsl #32\n\t" + "movk x16, #0x8888, lsl #48\n\t" + "movk x17, #0x9999, lsl #0\n\t" + "movk x17, #0xaaaa, lsl #16\n\t" + "movk x17, #0xbbbb, lsl #32\n\t" + "movk x17, #0xcccc, lsl #48\n\t" + "movk x18, #0xdddd, lsl #0\n\t" + "movk x18, #0xeeee, lsl #16\n\t" + "movk x18, #0xffff, lsl #32\n\t" + "movk x18, #0x1111, lsl #48\n\t" + "movk x19, #0x2222, lsl #0\n\t" + "movk x19, #0x3333, lsl #16\n\t" + "movk x19, #0x4444, lsl #32\n\t" + "movk x19, #0x5555, lsl #48\n\t" + "movk x20, #0x6666, lsl #0\n\t" + "movk x20, #0x7777, lsl #16\n\t" + "movk x20, #0x8888, lsl #32\n\t" + "movk x20, #0x9999, lsl #48\n\t" + "movk x21, #0xaaaa, lsl #0\n\t" + "movk x21, #0xbbbb, lsl #16\n\t" + "movk x21, #0xcccc, lsl #32\n\t" + "movk x21, #0xdddd, lsl #48\n\t" + "movk x22, #0xeeee, lsl #0\n\t" + "movk x22, #0xffff, lsl #16\n\t" + "movk x22, #0x1111, lsl #32\n\t" + "movk x22, #0x2222, lsl #48\n\t" + "movk x23, #0x3333, lsl #0\n\t" + "movk x23, #0x4444, lsl #16\n\t" + "movk x23, #0x5555, lsl #32\n\t" + "movk x23, #0x6666, lsl #48\n\t" + "movk x24, #0x7777, lsl #0\n\t" + "movk x24, #0x8888, lsl #16\n\t" + "movk x24, #0x9999, lsl #32\n\t" + "movk x24, #0xaaaa, lsl #48\n\t" + "movk x25, #0xbbbb, lsl #0\n\t" + "movk x25, #0xcccc, lsl #16\n\t" + "movk x25, #0xdddd, lsl #32\n\t" + "movk x25, #0xeeee, lsl #48\n\t" + "movk x26, #0xffff, lsl #0\n\t" + "movk x26, #0x1111, lsl #16\n\t" + "movk x26, #0x2222, lsl #32\n\t" + "movk x26, #0x3333, lsl #48\n\t" + "movk x27, #0x4444, lsl #0\n\t" + "movk x27, #0x5555, lsl #16\n\t" + "movk x27, #0x6666, lsl #32\n\t" + "movk x27, #0x7777, lsl #48\n\t" + "movk x28, #0x8888, lsl #0\n\t" + "movk x28, #0x9999, lsl #16\n\t" + "movk x28, #0xaaaa, lsl #32\n\t" + "movk x28, #0xbbbb, lsl #48\n\t" + "movk x29, #0xcccc, lsl #0\n\t" + "movk x29, #0xdddd, lsl #16\n\t" + "movk x29, #0xeeee, lsl #32\n\t" + "movk x29, #0xffff, lsl #48\n\t" + "movk x30, #0x1111, lsl #0\n\t" + "movk x30, #0x2222, lsl #16\n\t" + "movk x30, #0x3333, lsl #32\n\t" + "movk x30, #0x4444, lsl #48\n\t" + "nop\n\t" + "ldp x19, x20, [sp, #0]\n\t" + "ldp x21, x22, [sp, #16]\n\t" + "ldp x23, x24, [sp, #32]\n\t" + "ldp x25, x26, [sp, #48]\n\t" + "ldp x27, x28, [sp, #64]\n\t" + "ldp x29, x30, [sp, #80]\n\t" + "add sp, sp, #96\n\t" + : + : + : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18" + ); +} + +int main() +{ + printf("Provola\n"); + + register_test(); + + return EXIT_SUCCESS; +} diff --git a/test/aarch64/srcs/floating_point_test.c b/test/aarch64/srcs/floating_point_test.c new file mode 100644 index 00000000..5bfdb39b --- /dev/null +++ b/test/aarch64/srcs/floating_point_test.c @@ -0,0 +1,106 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +void rotate(char value[16]) +{ + char temp = value[0]; + for (int i = 0; i < 15; i++) { + value[i] = value[i + 1]; + } + value[15] = temp; +} + +int main() +{ + char value[16]; + + for (int i = 0; i < 16; i++) { + value[i] = i; + } + + // aarch64 floating point registers + __asm__ __volatile__("ld1 {v0.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v1.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v2.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v3.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v4.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v5.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v6.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v7.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v8.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v9.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v10.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v11.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v12.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v13.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v14.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v15.16b}, [%0]" : : "r" (value)); + rotate(value); + + for (int i = 0; i < 16; i++) { + value[i] = 0x80 + i; + } + + __asm__ __volatile__("ld1 {v16.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v17.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v18.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v19.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v20.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v21.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v22.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v23.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v24.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v25.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v26.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v27.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v28.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v29.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v30.16b}, [%0]" : : "r" (value)); + rotate(value); + __asm__ __volatile__("ld1 {v31.16b}, [%0]" : : "r" (value)); + + __asm__ __volatile__("nop\n\t"); + + char result[16]; + __asm__ __volatile__("st1 {v0.16b}, [%0]" : : "r" (result)); + + unsigned long check = *(unsigned long*)result; + + if (check == 0xdeadbeefdeadbeef) { + __asm__ __volatile__("nop\n\t"); + } + + return 0; +} \ No newline at end of file diff --git a/test/aarch64/srcs/thread_test.c b/test/aarch64/srcs/thread_test.c new file mode 100644 index 00000000..54672484 --- /dev/null +++ b/test/aarch64/srcs/thread_test.c @@ -0,0 +1,59 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +#include +#include +#include + +void thread_1_function() +{ + asm volatile ( + "movk x0, #0x1111, lsl #0\n\t" + "movk x0, #0x2222, lsl #16\n\t" + "movk x0, #0x3333, lsl #32\n\t" + "movk x0, #0x4444, lsl #48\n\t" + "nop\n\t"::: "x0"); +} + +void thread_2_function() +{ + asm volatile ( + "movk x0, #0x6666, lsl #0\n\t" + "movk x0, #0x7777, lsl #16\n\t" + "movk x0, #0x8888, lsl #32\n\t" + "movk x0, #0x9999, lsl #48\n\t" + "nop\n\t"::: "x0"); +} + +void thread_3_function() +{ + asm volatile ( + "movk x0, #0xeeee, lsl #0\n\t" + "movk x0, #0xffff, lsl #16\n\t" + "movk x0, #0x1111, lsl #32\n\t" + "movk x0, #0x2222, lsl #48\n\t" + "nop\n\t"::: "x0"); +} + +void do_nothing() +{ + asm volatile ("nop\n\t"); +} + +int main() +{ + pthread_t thread_1, thread_2, thread_3; + pthread_create(&thread_1, NULL, (void *)thread_1_function, NULL); + pthread_create(&thread_2, NULL, (void *)thread_2_function, NULL); + pthread_create(&thread_3, NULL, (void *)thread_3_function, NULL); + pthread_join(thread_1, NULL); + pthread_join(thread_2, NULL); + pthread_join(thread_3, NULL); + + do_nothing(); + + return 0; +} diff --git a/test/amd64/srcs/antidebug_brute_test.c b/test/common/srcs/antidebug_brute_test.c similarity index 100% rename from test/amd64/srcs/antidebug_brute_test.c rename to test/common/srcs/antidebug_brute_test.c diff --git a/test/amd64/srcs/attach_test.c b/test/common/srcs/attach_test.c similarity index 100% rename from test/amd64/srcs/attach_test.c rename to test/common/srcs/attach_test.c diff --git a/test/amd64/srcs/backtrace.c b/test/common/srcs/backtrace.c similarity index 100% rename from test/amd64/srcs/backtrace.c rename to test/common/srcs/backtrace.c diff --git a/test/amd64/srcs/basic_test_pie.c b/test/common/srcs/basic_test_pie.c similarity index 100% rename from test/amd64/srcs/basic_test_pie.c rename to test/common/srcs/basic_test_pie.c diff --git a/test/amd64/srcs/benchmark.c b/test/common/srcs/benchmark.c similarity index 100% rename from test/amd64/srcs/benchmark.c rename to test/common/srcs/benchmark.c diff --git a/test/amd64/srcs/breakpoint_test.c b/test/common/srcs/breakpoint_test.c similarity index 100% rename from test/amd64/srcs/breakpoint_test.c rename to test/common/srcs/breakpoint_test.c diff --git a/test/amd64/srcs/brute_test.c b/test/common/srcs/brute_test.c similarity index 100% rename from test/amd64/srcs/brute_test.c rename to test/common/srcs/brute_test.c diff --git a/test/amd64/srcs/catch_signal_test.c b/test/common/srcs/catch_signal_test.c similarity index 100% rename from test/amd64/srcs/catch_signal_test.c rename to test/common/srcs/catch_signal_test.c diff --git a/test/amd64/srcs/complex_thread_test.c b/test/common/srcs/complex_thread_test.c similarity index 100% rename from test/amd64/srcs/complex_thread_test.c rename to test/common/srcs/complex_thread_test.c diff --git a/test/amd64/srcs/executable_section_test.c b/test/common/srcs/executable_section_test.c similarity index 100% rename from test/amd64/srcs/executable_section_test.c rename to test/common/srcs/executable_section_test.c diff --git a/test/amd64/srcs/finish_test.c b/test/common/srcs/finish_test.c similarity index 100% rename from test/amd64/srcs/finish_test.c rename to test/common/srcs/finish_test.c diff --git a/test/amd64/srcs/handle_syscall_test.c b/test/common/srcs/handle_syscall_test.c similarity index 100% rename from test/amd64/srcs/handle_syscall_test.c rename to test/common/srcs/handle_syscall_test.c diff --git a/test/amd64/srcs/jumpstart_test.c b/test/common/srcs/jumpstart_test.c similarity index 100% rename from test/amd64/srcs/jumpstart_test.c rename to test/common/srcs/jumpstart_test.c diff --git a/test/amd64/srcs/jumpstart_test_preload.c b/test/common/srcs/jumpstart_test_preload.c similarity index 100% rename from test/amd64/srcs/jumpstart_test_preload.c rename to test/common/srcs/jumpstart_test_preload.c diff --git a/test/amd64/srcs/memory_test.c b/test/common/srcs/memory_test.c similarity index 100% rename from test/amd64/srcs/memory_test.c rename to test/common/srcs/memory_test.c diff --git a/test/amd64/srcs/memory_test_2.c b/test/common/srcs/memory_test_2.c similarity index 100% rename from test/amd64/srcs/memory_test_2.c rename to test/common/srcs/memory_test_2.c diff --git a/test/amd64/srcs/segfault_test.c b/test/common/srcs/segfault_test.c similarity index 100% rename from test/amd64/srcs/segfault_test.c rename to test/common/srcs/segfault_test.c diff --git a/test/amd64/srcs/signals_multithread_det_test.c b/test/common/srcs/signals_multithread_det_test.c similarity index 100% rename from test/amd64/srcs/signals_multithread_det_test.c rename to test/common/srcs/signals_multithread_det_test.c diff --git a/test/amd64/srcs/signals_multithread_undet_test.c b/test/common/srcs/signals_multithread_undet_test.c similarity index 100% rename from test/amd64/srcs/signals_multithread_undet_test.c rename to test/common/srcs/signals_multithread_undet_test.c diff --git a/test/amd64/srcs/speed_test.c b/test/common/srcs/speed_test.c similarity index 100% rename from test/amd64/srcs/speed_test.c rename to test/common/srcs/speed_test.c diff --git a/test/amd64/srcs/watchpoint_test.c b/test/common/srcs/watchpoint_test.c similarity index 100% rename from test/amd64/srcs/watchpoint_test.c rename to test/common/srcs/watchpoint_test.c diff --git a/test/run_suite.py b/test/run_suite.py new file mode 100644 index 00000000..7ff469d5 --- /dev/null +++ b/test/run_suite.py @@ -0,0 +1,35 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import os +import platform +import sys + +architectures = os.listdir(".") +architectures.remove("other") + +if len(sys.argv) > 1 and sys.argv[1] not in architectures: + print("Usage: python run_test_suite.py ") + print("Available architectures:") + for arch in architectures: + print(f" {arch}") + sys.exit(1) +elif len(sys.argv) > 1: + arch = sys.argv[1] +else: + arch = platform.machine() + match arch: + case "x86_64": + arch = "amd64" + case "i686": + arch = "i386" + case "aarch64": + arch = "aarch64" + case _: + raise ValueError(f"Unsupported architecture: {arch}") + +os.chdir(arch) +os.system(" ".join([sys.executable, "run_suite.py"])) From 7c2c0582b14e261b61a3a8f6520d996e1e3f7299 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:24:02 +0200 Subject: [PATCH 027/144] feat: implement cffi hw bp support for aarch64 --- libdebug/cffi/ptrace_cffi_source.c | 127 +++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index afc1abd2..381806ff 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -231,14 +231,141 @@ int get_remaining_hw_watchpoint_count(struct global_state *state, int tid) #endif #ifdef ARCH_AARCH64 +struct user_hwdebug_state { + unsigned int dbg_info; + unsigned int pad; + struct { + unsigned long addr; + unsigned int ctrl; + unsigned int pad; + } dbg_regs[16]; +}; + void install_hardware_breakpoint(struct hardware_breakpoint *bp) { + // find a free debug register + struct user_hwdebug_state state = {0}; + + struct iovec iov; + iov.iov_base = &state; + iov.iov_len = sizeof state; + + unsigned long command = bp->type == 'x' ? NT_ARM_HW_BREAK : NT_ARM_HW_WATCH; + + ptrace(PTRACE_GETREGSET, bp->tid, command, &iov); + + int i; + for (i = 0; i < 16; i++) { + if (!state.dbg_regs[i].addr) + break; + } + if (i == 16) { + perror("No debug registers available"); + return; + } + + unsigned int length = (1 << bp->len) - 1; + unsigned int condition = bp->type == 'r' ? 3 : (bp->type == 'w' ? 2 : 0); + unsigned int control = (length << 5) | (condition << 3) | 1; + + state.dbg_regs[i].addr = bp->addr; + state.dbg_regs[i].ctrl = control; + + ptrace(PTRACE_SETREGSET, bp->tid, command, &iov); } void remove_hardware_breakpoint(struct hardware_breakpoint *bp) { + struct user_hwdebug_state state = {0}; + + struct iovec iov; + iov.iov_base = &state; + iov.iov_len = sizeof state; + + unsigned long command = bp->type == 'x' ? NT_ARM_HW_BREAK : NT_ARM_HW_WATCH; + + ptrace(PTRACE_GETREGSET, bp->tid, command, &iov); + + int i; + for (i = 0; i < 16; i++) { + if (state.dbg_regs[i].addr == bp->addr) + break; + } + + if (i == 16) { + perror("Breakpoint not found"); + return; + } + + state.dbg_regs[i].addr = 0; + state.dbg_regs[i].ctrl = 0; + + ptrace(PTRACE_SETREGSET, bp->tid, command, &iov); +} + +int is_breakpoint_hit(struct hardware_breakpoint *bp) +{ + siginfo_t si; + + if (ptrace(PTRACE_GETSIGINFO, bp->tid, NULL, &si) == -1) { + return 0; + } + + // Check that the signal is a SIGTRAP and the code is 0x4 + if (!(si.si_signo == SIGTRAP && si.si_code == 0x4)) { + return 0; + } + + unsigned long addr = si.si_addr; + + if (addr == bp->addr) { + return 1; + } + + return 0; +} + +int get_remaining_hw_breakpoint_count(struct global_state *state, int tid) +{ + struct user_hwdebug_state dbg_state = {0}; + + struct iovec iov; + iov.iov_base = &dbg_state; + iov.iov_len = sizeof dbg_state; + + unsigned long command = NT_ARM_HW_BREAK; + + ptrace(PTRACE_GETREGSET, tid, command, &iov); + + int i; + for (i = 0; i < 16; i++) { + if (!dbg_state.dbg_regs[i].addr) + break; + } + + return 16 - i; +} + +int get_remaining_hw_watchpoint_count(struct global_state *state, int tid) +{ + struct user_hwdebug_state dbg_state = {0}; + + struct iovec iov; + iov.iov_base = &dbg_state; + iov.iov_len = sizeof dbg_state; + + unsigned long command = NT_ARM_HW_WATCH; + + ptrace(PTRACE_GETREGSET, tid, command, &iov); + + int i; + for (i = 0; i < 16; i++) { + if (!dbg_state.dbg_regs[i].addr) + break; + } + return 16 - i; } #endif From 908238ae27cb6ebfcb9e1c8d92ed0c4d44f07288 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:26:59 +0200 Subject: [PATCH 028/144] ci: disable linter on test suite --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 761dfddf..f36dc557 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ issues = "https://github.com/libdebug/libdebug/issues" [tool.ruff] include = ["pyproject.toml", "libdebug/**/*.py"] -exclude = ["libdebug/cffi/*.py"] +exclude = ["libdebug/cffi/*.py", "test/"] line-length = 120 indent-width = 4 target-version = "py312" From a83c1757a4f750e8f37508ff7ef62899fde36c8e Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:28:26 +0200 Subject: [PATCH 029/144] fix: I had committed the test binaries to the wrong directory, whoops --- .../binaries/{binaries => }/antidebug_brute_test | Bin test/aarch64/binaries/{binaries => }/attach_test | Bin test/aarch64/binaries/{binaries => }/backtrace | Bin test/aarch64/binaries/{binaries => }/basic_test | Bin test/aarch64/binaries/{binaries => }/basic_test_pie | Bin test/aarch64/binaries/{binaries => }/benchmark | Bin .../aarch64/binaries/{binaries => }/breakpoint_test | Bin test/aarch64/binaries/{binaries => }/brute_test | Bin .../binaries/{binaries => }/catch_signal_test | Bin .../binaries/{binaries => }/complex_thread_test | Bin .../binaries/{binaries => }/executable_section_test | Bin test/aarch64/binaries/{binaries => }/finish_test | Bin .../binaries/{binaries => }/floating_point_test | Bin .../binaries/{binaries => }/handle_syscall_test | Bin test/aarch64/binaries/{binaries => }/jumpstart_test | Bin .../binaries/{binaries => }/jumpstart_test_preload | Bin test/aarch64/binaries/{binaries => }/memory_test | Bin test/aarch64/binaries/{binaries => }/memory_test_2 | Bin test/aarch64/binaries/{binaries => }/segfault_test | Bin .../{binaries => }/signals_multithread_det_test | Bin .../{binaries => }/signals_multithread_undet_test | Bin test/aarch64/binaries/{binaries => }/speed_test | Bin test/aarch64/binaries/{binaries => }/thread_test | Bin .../aarch64/binaries/{binaries => }/watchpoint_test | Bin 24 files changed, 0 insertions(+), 0 deletions(-) rename test/aarch64/binaries/{binaries => }/antidebug_brute_test (100%) rename test/aarch64/binaries/{binaries => }/attach_test (100%) rename test/aarch64/binaries/{binaries => }/backtrace (100%) rename test/aarch64/binaries/{binaries => }/basic_test (100%) rename test/aarch64/binaries/{binaries => }/basic_test_pie (100%) rename test/aarch64/binaries/{binaries => }/benchmark (100%) rename test/aarch64/binaries/{binaries => }/breakpoint_test (100%) rename test/aarch64/binaries/{binaries => }/brute_test (100%) rename test/aarch64/binaries/{binaries => }/catch_signal_test (100%) rename test/aarch64/binaries/{binaries => }/complex_thread_test (100%) rename test/aarch64/binaries/{binaries => }/executable_section_test (100%) rename test/aarch64/binaries/{binaries => }/finish_test (100%) rename test/aarch64/binaries/{binaries => }/floating_point_test (100%) rename test/aarch64/binaries/{binaries => }/handle_syscall_test (100%) rename test/aarch64/binaries/{binaries => }/jumpstart_test (100%) rename test/aarch64/binaries/{binaries => }/jumpstart_test_preload (100%) rename test/aarch64/binaries/{binaries => }/memory_test (100%) rename test/aarch64/binaries/{binaries => }/memory_test_2 (100%) rename test/aarch64/binaries/{binaries => }/segfault_test (100%) rename test/aarch64/binaries/{binaries => }/signals_multithread_det_test (100%) rename test/aarch64/binaries/{binaries => }/signals_multithread_undet_test (100%) rename test/aarch64/binaries/{binaries => }/speed_test (100%) rename test/aarch64/binaries/{binaries => }/thread_test (100%) rename test/aarch64/binaries/{binaries => }/watchpoint_test (100%) diff --git a/test/aarch64/binaries/binaries/antidebug_brute_test b/test/aarch64/binaries/antidebug_brute_test similarity index 100% rename from test/aarch64/binaries/binaries/antidebug_brute_test rename to test/aarch64/binaries/antidebug_brute_test diff --git a/test/aarch64/binaries/binaries/attach_test b/test/aarch64/binaries/attach_test similarity index 100% rename from test/aarch64/binaries/binaries/attach_test rename to test/aarch64/binaries/attach_test diff --git a/test/aarch64/binaries/binaries/backtrace b/test/aarch64/binaries/backtrace similarity index 100% rename from test/aarch64/binaries/binaries/backtrace rename to test/aarch64/binaries/backtrace diff --git a/test/aarch64/binaries/binaries/basic_test b/test/aarch64/binaries/basic_test similarity index 100% rename from test/aarch64/binaries/binaries/basic_test rename to test/aarch64/binaries/basic_test diff --git a/test/aarch64/binaries/binaries/basic_test_pie b/test/aarch64/binaries/basic_test_pie similarity index 100% rename from test/aarch64/binaries/binaries/basic_test_pie rename to test/aarch64/binaries/basic_test_pie diff --git a/test/aarch64/binaries/binaries/benchmark b/test/aarch64/binaries/benchmark similarity index 100% rename from test/aarch64/binaries/binaries/benchmark rename to test/aarch64/binaries/benchmark diff --git a/test/aarch64/binaries/binaries/breakpoint_test b/test/aarch64/binaries/breakpoint_test similarity index 100% rename from test/aarch64/binaries/binaries/breakpoint_test rename to test/aarch64/binaries/breakpoint_test diff --git a/test/aarch64/binaries/binaries/brute_test b/test/aarch64/binaries/brute_test similarity index 100% rename from test/aarch64/binaries/binaries/brute_test rename to test/aarch64/binaries/brute_test diff --git a/test/aarch64/binaries/binaries/catch_signal_test b/test/aarch64/binaries/catch_signal_test similarity index 100% rename from test/aarch64/binaries/binaries/catch_signal_test rename to test/aarch64/binaries/catch_signal_test diff --git a/test/aarch64/binaries/binaries/complex_thread_test b/test/aarch64/binaries/complex_thread_test similarity index 100% rename from test/aarch64/binaries/binaries/complex_thread_test rename to test/aarch64/binaries/complex_thread_test diff --git a/test/aarch64/binaries/binaries/executable_section_test b/test/aarch64/binaries/executable_section_test similarity index 100% rename from test/aarch64/binaries/binaries/executable_section_test rename to test/aarch64/binaries/executable_section_test diff --git a/test/aarch64/binaries/binaries/finish_test b/test/aarch64/binaries/finish_test similarity index 100% rename from test/aarch64/binaries/binaries/finish_test rename to test/aarch64/binaries/finish_test diff --git a/test/aarch64/binaries/binaries/floating_point_test b/test/aarch64/binaries/floating_point_test similarity index 100% rename from test/aarch64/binaries/binaries/floating_point_test rename to test/aarch64/binaries/floating_point_test diff --git a/test/aarch64/binaries/binaries/handle_syscall_test b/test/aarch64/binaries/handle_syscall_test similarity index 100% rename from test/aarch64/binaries/binaries/handle_syscall_test rename to test/aarch64/binaries/handle_syscall_test diff --git a/test/aarch64/binaries/binaries/jumpstart_test b/test/aarch64/binaries/jumpstart_test similarity index 100% rename from test/aarch64/binaries/binaries/jumpstart_test rename to test/aarch64/binaries/jumpstart_test diff --git a/test/aarch64/binaries/binaries/jumpstart_test_preload b/test/aarch64/binaries/jumpstart_test_preload similarity index 100% rename from test/aarch64/binaries/binaries/jumpstart_test_preload rename to test/aarch64/binaries/jumpstart_test_preload diff --git a/test/aarch64/binaries/binaries/memory_test b/test/aarch64/binaries/memory_test similarity index 100% rename from test/aarch64/binaries/binaries/memory_test rename to test/aarch64/binaries/memory_test diff --git a/test/aarch64/binaries/binaries/memory_test_2 b/test/aarch64/binaries/memory_test_2 similarity index 100% rename from test/aarch64/binaries/binaries/memory_test_2 rename to test/aarch64/binaries/memory_test_2 diff --git a/test/aarch64/binaries/binaries/segfault_test b/test/aarch64/binaries/segfault_test similarity index 100% rename from test/aarch64/binaries/binaries/segfault_test rename to test/aarch64/binaries/segfault_test diff --git a/test/aarch64/binaries/binaries/signals_multithread_det_test b/test/aarch64/binaries/signals_multithread_det_test similarity index 100% rename from test/aarch64/binaries/binaries/signals_multithread_det_test rename to test/aarch64/binaries/signals_multithread_det_test diff --git a/test/aarch64/binaries/binaries/signals_multithread_undet_test b/test/aarch64/binaries/signals_multithread_undet_test similarity index 100% rename from test/aarch64/binaries/binaries/signals_multithread_undet_test rename to test/aarch64/binaries/signals_multithread_undet_test diff --git a/test/aarch64/binaries/binaries/speed_test b/test/aarch64/binaries/speed_test similarity index 100% rename from test/aarch64/binaries/binaries/speed_test rename to test/aarch64/binaries/speed_test diff --git a/test/aarch64/binaries/binaries/thread_test b/test/aarch64/binaries/thread_test similarity index 100% rename from test/aarch64/binaries/binaries/thread_test rename to test/aarch64/binaries/thread_test diff --git a/test/aarch64/binaries/binaries/watchpoint_test b/test/aarch64/binaries/watchpoint_test similarity index 100% rename from test/aarch64/binaries/binaries/watchpoint_test rename to test/aarch64/binaries/watchpoint_test From ec043e2fef02c5afbf7e3f23f1bd44e25c1b66ab Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:37:05 +0200 Subject: [PATCH 030/144] fix: delete unneeded hardware bp helper file --- .../aarch64/aarch64_ptrace_hw_bp_helper.py | 136 ------------------ 1 file changed, 136 deletions(-) delete mode 100644 libdebug/architectures/aarch64/aarch64_ptrace_hw_bp_helper.py diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_hw_bp_helper.py b/libdebug/architectures/aarch64/aarch64_ptrace_hw_bp_helper.py deleted file mode 100644 index 34270b00..00000000 --- a/libdebug/architectures/aarch64/aarch64_ptrace_hw_bp_helper.py +++ /dev/null @@ -1,136 +0,0 @@ -# -# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from libdebug.architectures.ptrace_hardware_breakpoint_manager import ( - PtraceHardwareBreakpointManager, -) -from libdebug.data.breakpoint import Breakpoint -from libdebug.liblog import liblog - -if TYPE_CHECKING: - from collections.abc import Callable - - from libdebug.data.breakpoint import Breakpoint - from libdebug.state.thread_context import ThreadContext - - -AARCH64_DBREGS_OFF = {} - -AARCH64_VALID_SIZES = {1, 2, 4, 8} -AARCH64_DBGREGS_COUNT = 16 -AARCH64_COND_VAL = {"x": 0, "r": 1, "w": 2, "rw": 3} - -# Internally, we check the address & 0x1000 to determine if it's a watchpoint. -# Then we query either the DBG or WVR register. -for i in range(AARCH64_DBGREGS_COUNT): - AARCH64_DBREGS_OFF[f"DBG{i}"] = 0x8 + i * 16 - AARCH64_DBREGS_OFF[f"WVR{i}"] = 0x1000 + 0x8 + i * 16 - - -class Aarch64HardwareBreakpointManager(PtraceHardwareBreakpointManager): - """A hardware breakpoint manager for the aarch64 architecture. - - Attributes: - thread (ThreadContext): The target thread. - peek_user (callable): A function that reads a number of bytes from the target thread registers. - poke_user (callable): A function that writes a number of bytes to the target thread registers. - breakpoint_count (int): The number of hardware breakpoints set. - watchpoint_count (int): The number of hardware watchpoints set. - """ - - def __init__( - self: Aarch64HardwareBreakpointManager, - thread: ThreadContext, - peek_user: Callable[[int, int], int], - poke_user: Callable[[int, int, int], None], - ) -> None: - """Initializes the hardware breakpoint manager.""" - super().__init__(thread, peek_user, poke_user) - - self.breakpoint_registers: dict[str, Breakpoint | None] = {} - self.watchpoint_registers: dict[str, Breakpoint | None] = {} - - for i in range(AARCH64_DBGREGS_COUNT): - self.breakpoint_registers[f"DBG{i}"] = None - self.watchpoint_registers[f"WVR{i}"] = None - - self.watchpoint_count = 0 - - def install_breakpoint(self: Aarch64HardwareBreakpointManager, bp: Breakpoint) -> None: - """Installs a hardware breakpoint at the provided location.""" - if self.breakpoint_count >= AARCH64_DBGREGS_COUNT: - liblog.error(f"Cannot set more than {AARCH64_DBGREGS_COUNT} hardware breakpoints.") - return - - if bp.length not in AARCH64_VALID_SIZES: - raise ValueError(f"Invalid breakpoint length: {bp.length}.") - - if bp.address % 4 != 0: - raise ValueError("Breakpoint address must be 4-byte aligned.") - - if bp.condition == "x": - register = next(reg for reg, bp in self.breakpoint_registers.items() if bp is None) - self.breakpoint_count += 1 - else: - register = next(reg for reg, bp in self.watchpoint_registers.items() if bp is None) - self.watchpoint_count += 1 - - # https://android.googlesource.com/platform/bionic/+/master/tests/sys_ptrace_test.cpp - # https://android.googlesource.com/toolchain/gdb/+/fb3e0dcd57c379215f4c7d1c036bd497f1dccb4b/gdb-7.11/gdb/nat/aarch64-linux-hw-point.c - length = (1 << bp.length) - 1 - enable = 1 - condition = AARCH64_COND_VAL[bp.condition] - control = length << 5 | condition << 3 | enable - - self.poke_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 0, bp.address) - self.poke_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 8, control) - - self.breakpoint_registers[register] = bp - - liblog.debugger(f"Installed hardware breakpoint on register {register}.") - - def remove_breakpoint(self: Aarch64HardwareBreakpointManager, bp: Breakpoint) -> None: - """Removes a hardware breakpoint at the provided location.""" - register = next(reg for reg, b in self.breakpoint_registers.items() if b == bp) - - if register is None: - liblog.error("Breakpoint not found.") - return - - if bp.condition == "x": - self.breakpoint_count -= 1 - else: - self.watchpoint_count -= 1 - - self.poke_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 0, 0) - self.poke_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 8, 0) - - self.breakpoint_registers[register] = None - - liblog.debugger(f"Removed hardware breakpoint on register {register}.") - - def available_breakpoints(self: Aarch64HardwareBreakpointManager) -> int: - """Returns the number of available hardware breakpoints.""" - return AARCH64_DBGREGS_COUNT - self.breakpoint_count - - def is_watchpoint_hit(self: Aarch64HardwareBreakpointManager) -> Breakpoint | None: - """Checks if a watchpoint has been hit. - - Returns: - Breakpoint | None: The watchpoint that has been hit, or None if no watchpoint has been hit. - """ - for register, bp in self.watchpoint_registers.items(): - if bp is None: - continue - - if self.peek_user(self.thread.thread_id, AARCH64_DBREGS_OFF[register] + 8) & 0x1: - return bp - - return None From 06b55cec011fccdefb8d1b2c9d5a8b78a39ab675 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:37:20 +0200 Subject: [PATCH 031/144] fix: read from the correct register for 32-bit access --- .../architectures/aarch64/aarch64_ptrace_register_holder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py index 1eb6df25..4faa1fce 100644 --- a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py +++ b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py @@ -62,7 +62,7 @@ def apply_on_regs(self: Aarch64PtraceRegisterHolder, target: Aarch64Registers, t name_32 = f"w{i}" setattr(target_class, name_64, _get_property_64(name_64)) - setattr(target_class, name_32, _get_property_32(name_32)) + setattr(target_class, name_32, _get_property_32(name_64)) target_class.pc = _get_property_64("pc") From 464ef3537bcd13db919af369ad61df8d6627f960 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:45:35 +0200 Subject: [PATCH 032/144] test: remove correct folder from the list of available architectures --- test/run_suite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/run_suite.py b/test/run_suite.py index 7ff469d5..7ebf2c61 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -9,7 +9,7 @@ import sys architectures = os.listdir(".") -architectures.remove("other") +architectures.remove("common") if len(sys.argv) > 1 and sys.argv[1] not in architectures: print("Usage: python run_test_suite.py ") From a84e3f3caebef283ec3344f4f5f28939d8699d48 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:45:57 +0200 Subject: [PATCH 033/144] test: add basic_test for aarch64 --- test/aarch64/scripts/basic_test.py | 125 +++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/test/aarch64/scripts/basic_test.py b/test/aarch64/scripts/basic_test.py index 21eec3dd..f1bbe4b7 100644 --- a/test/aarch64/scripts/basic_test.py +++ b/test/aarch64/scripts/basic_test.py @@ -18,5 +18,130 @@ def test_basic(self): d.cont() assert bp.address == d.regs.pc d.cont() + d.kill() + d.terminate() + + def test_registers(self): + d = debugger("binaries/basic_test") + d.run() + + bp = d.breakpoint(0x964, file="binary") + + d.cont() + + assert d.regs.pc == bp.address + + assert d.regs.x0 == 0x4444333322221111 + assert d.regs.x1 == 0x8888777766665555 + assert d.regs.x2 == 0xccccbbbbaaaa9999 + assert d.regs.x3 == 0x1111ffffeeeedddd + assert d.regs.x4 == 0x5555444433332222 + assert d.regs.x5 == 0x9999888877776666 + assert d.regs.x6 == 0xddddccccbbbbaaaa + assert d.regs.x7 == 0x22221111ffffeeee + assert d.regs.x8 == 0x6666555544443333 + assert d.regs.x9 == 0xaaaa999988887777 + assert d.regs.x10 == 0xeeeeddddccccbbbb + assert d.regs.x11 == 0x333322221111ffff + assert d.regs.x12 == 0x7777666655554444 + assert d.regs.x13 == 0xbbbbaaaa99998888 + assert d.regs.x14 == 0xffffeeeeddddcccc + assert d.regs.x15 == 0x4444333322221111 + assert d.regs.x16 == 0x8888777766665555 + assert d.regs.x17 == 0xccccbbbbaaaa9999 + assert d.regs.x18 == 0x1111ffffeeeedddd + assert d.regs.x19 == 0x5555444433332222 + assert d.regs.x20 == 0x9999888877776666 + assert d.regs.x21 == 0xddddccccbbbbaaaa + assert d.regs.x22 == 0x22221111ffffeeee + assert d.regs.x23 == 0x6666555544443333 + assert d.regs.x24 == 0xaaaa999988887777 + assert d.regs.x25 == 0xeeeeddddccccbbbb + assert d.regs.x26 == 0x333322221111ffff + assert d.regs.x27 == 0x7777666655554444 + assert d.regs.x28 == 0xbbbbaaaa99998888 + assert d.regs.x29 == 0xffffeeeeddddcccc + assert d.regs.x30 == 0x4444333322221111 + + assert d.regs.w0 == 0x22221111 + assert d.regs.w1 == 0x66665555 + assert d.regs.w2 == 0xaaaa9999 + assert d.regs.w3 == 0xeeeedddd + assert d.regs.w4 == 0x33332222 + assert d.regs.w5 == 0x77776666 + assert d.regs.w6 == 0xbbbbaaaa + assert d.regs.w7 == 0xffffeeee + assert d.regs.w8 == 0x44443333 + assert d.regs.w9 == 0x88887777 + assert d.regs.w10 == 0xccccbbbb + assert d.regs.w11 == 0x1111ffff + assert d.regs.w12 == 0x55554444 + assert d.regs.w13 == 0x99998888 + assert d.regs.w14 == 0xddddcccc + assert d.regs.w15 == 0x22221111 + assert d.regs.w16 == 0x66665555 + assert d.regs.w17 == 0xaaaa9999 + assert d.regs.w18 == 0xeeeedddd + assert d.regs.w19 == 0x33332222 + assert d.regs.w20 == 0x77776666 + assert d.regs.w21 == 0xbbbbaaaa + assert d.regs.w22 == 0xffffeeee + assert d.regs.w23 == 0x44443333 + assert d.regs.w24 == 0x88887777 + assert d.regs.w25 == 0xccccbbbb + assert d.regs.w26 == 0x1111ffff + assert d.regs.w27 == 0x55554444 + assert d.regs.w28 == 0x99998888 + assert d.regs.w29 == 0xddddcccc + assert d.regs.w30 == 0x22221111 + + d.cont() + + d.kill() + d.terminate() + + def test_step(self): + d = debugger("binaries/basic_test") + + d.run() + bp = d.breakpoint("register_test") + d.cont() + + assert bp.address == d.regs.pc + assert bp.hit_count == 1 + + d.step() + + assert (bp.address + 4) == d.regs.pc + assert bp.hit_count == 1 + + d.step() + + assert (bp.address + 8) == d.regs.pc + assert bp.hit_count == 1 + + d.kill() + d.terminate() + + def test_step_hardware(self): + d = debugger("binaries/basic_test") + + d.run() + bp = d.breakpoint("register_test", hardware=True) + d.cont() + + assert bp.address == d.regs.pc + assert bp.hit_count == 1 + + d.step() + + assert (bp.address + 4) == d.regs.pc + assert bp.hit_count == 1 + + d.step() + + assert (bp.address + 8) == d.regs.pc + assert bp.hit_count == 1 + d.kill() d.terminate() \ No newline at end of file From 2fe166809f445afca79488e876139962a31bbb77 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 14:50:12 +0200 Subject: [PATCH 034/144] fix: make singlestep work over hardware breakpoints for aarch64 --- libdebug/cffi/ptrace_cffi_source.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 381806ff..d8472e58 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -756,7 +756,27 @@ long singlestep(struct global_state *state, int tid) t = t->next; } +#ifdef ARCH_AMD64 return ptrace(PTRACE_SINGLESTEP, tid, NULL, signal_to_forward); +#endif + +#ifdef ARCH_AARCH64 + // Cannot singlestep if we are stopped on a hardware breakpoint + // So we have to check for this, remove it, singlestep and then re-add it + struct hardware_breakpoint *bp = state->hw_b_HEAD; + + while (bp != NULL) { + if (bp->tid == tid && bp->enabled && is_breakpoint_hit(bp)) { + remove_hardware_breakpoint(bp); + long ret = ptrace(PTRACE_SINGLESTEP, tid, NULL, signal_to_forward); + install_hardware_breakpoint(bp); + return ret; + } + bp = bp->next; + } + + return ptrace(PTRACE_SINGLESTEP, tid, NULL, signal_to_forward); +#endif } int step_until(struct global_state *state, int tid, uint64_t addr, int max_steps) From d7c4b3d852c4521cc585588891e5a64031b20785 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 15:04:56 +0200 Subject: [PATCH 035/144] test: add more tests for aarch64 --- test/aarch64/run_suite.py | 9 +- test/aarch64/scripts/attach_detach_test.py | 92 +++++++++++++++ test/aarch64/scripts/auto_waiting_test.py | 63 +++++++++++ test/aarch64/scripts/basic_test.py | 23 ---- test/aarch64/scripts/basic_test_hw.py | 123 +++++++++++++++++++++ test/aarch64/scripts/basic_test_pie.py | 24 ++++ 6 files changed, 310 insertions(+), 24 deletions(-) create mode 100644 test/aarch64/scripts/attach_detach_test.py create mode 100644 test/aarch64/scripts/auto_waiting_test.py create mode 100644 test/aarch64/scripts/basic_test_hw.py create mode 100644 test/aarch64/scripts/basic_test_pie.py diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index c43eda63..ca8b7067 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -7,13 +7,20 @@ import sys from unittest import TestLoader, TestSuite, TextTestRunner +from scripts.attach_detach_test import AttachDetachTest +from scripts.auto_waiting_test import AutoWaitingTest from scripts.basic_test import BasicTest - +from scripts.basic_test_pie import BasicTestPie +from scripts.basic_test_hw import BasicTestHw def fast_suite(): suite = TestSuite() + suite.addTest(TestLoader().loadTestsFromTestCase(AttachDetachTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(AutoWaitingTest)) suite.addTest(TestLoader().loadTestsFromTestCase(BasicTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestPie)) + suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestHw)) return suite diff --git a/test/aarch64/scripts/attach_detach_test.py b/test/aarch64/scripts/attach_detach_test.py new file mode 100644 index 00000000..403129c9 --- /dev/null +++ b/test/aarch64/scripts/attach_detach_test.py @@ -0,0 +1,92 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import logging +import unittest + +from pwn import process + +from libdebug import debugger + +logging.getLogger("pwnlib").setLevel(logging.ERROR) + + +class AttachDetachTest(unittest.TestCase): + def setUp(self): + pass + + def test_attach(self): + r = process("binaries/attach_test") + + d = debugger() + d.attach(r.pid) + bp = d.breakpoint("printName", hardware=True) + d.cont() + + r.recvuntil(b"name:") + r.sendline(b"Io_no") + + self.assertTrue(d.regs.pc == bp.address) + + d.cont() + + d.kill() + + def test_attach_and_detach_1(self): + r = process("binaries/attach_test") + + d = debugger() + + # Attach to the process + d.attach(r.pid) + d.detach() + + # Validate that, after detaching, the process is still running + r.recvuntil(b"name:", timeout=1) + r.sendline(b"Io_no") + + r.kill() + + def test_attach_and_detach_2(self): + d = debugger("binaries/attach_test") + + # Run the process + r = d.run() + d.detach() + + # Validate that, after detaching, the process is still running + r.recvuntil(b"name:", timeout=1) + r.sendline(b"Io_no") + + d.kill() + + def test_attach_and_detach_3(self): + d = debugger("binaries/attach_test") + + r = d.run() + + # We must ensure that any breakpoint is unset before detaching + d.breakpoint(0xa04) + d.breakpoint(0xa08, hardware=True) + + d.detach() + + # Validate that, after detaching, the process is still running + r.recvuntil(b"name:", timeout=1) + r.sendline(b"Io_no") + + d.kill() + + def test_attach_and_detach_4(self): + r = process("binaries/attach_test") + + d = debugger() + d.attach(r.pid) + d.detach() + d.kill() + + # Validate that, after detaching and killing, the process is effectively terminated + self.assertRaises(EOFError, r.sendline, b"provola") \ No newline at end of file diff --git a/test/aarch64/scripts/auto_waiting_test.py b/test/aarch64/scripts/auto_waiting_test.py new file mode 100644 index 00000000..5e052366 --- /dev/null +++ b/test/aarch64/scripts/auto_waiting_test.py @@ -0,0 +1,63 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import io +import logging +import unittest + +from libdebug import debugger + + +class AutoWaitingTest(unittest.TestCase): + def setUp(self): + # Redirect logging to a string buffer + self.log_capture_string = io.StringIO() + self.log_handler = logging.StreamHandler(self.log_capture_string) + self.log_handler.setLevel(logging.WARNING) + + self.logger = logging.getLogger("libdebug") + self.original_handlers = self.logger.handlers + self.logger.handlers = [] + self.logger.addHandler(self.log_handler) + self.logger.setLevel(logging.WARNING) + + def test_bps_auto_waiting(self): + d = debugger("binaries/breakpoint_test", auto_interrupt_on_command=False) + + d.run() + + bp1 = d.breakpoint("random_function") + bp2 = d.breakpoint(0x7fc, file="binary") + bp3 = d.breakpoint(0x820, file="binary") + + counter = 1 + + d.cont() + + while True: + if d.regs.pc == bp1.address: + self.assertTrue(bp1.hit_count == 1) + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + elif d.regs.pc == bp2.address: + self.assertTrue(bp2.hit_count == counter) + self.assertTrue(bp2.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + counter += 1 + elif d.regs.pc == bp3.address: + self.assertTrue(bp3.hit_count == 1) + self.assertTrue(d.regs.x0 == 45) + self.assertTrue(d.regs.w0 == 45) + self.assertTrue(bp3.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + break + + d.cont() + + d.kill() \ No newline at end of file diff --git a/test/aarch64/scripts/basic_test.py b/test/aarch64/scripts/basic_test.py index f1bbe4b7..cc4ae841 100644 --- a/test/aarch64/scripts/basic_test.py +++ b/test/aarch64/scripts/basic_test.py @@ -120,28 +120,5 @@ def test_step(self): assert (bp.address + 8) == d.regs.pc assert bp.hit_count == 1 - d.kill() - d.terminate() - - def test_step_hardware(self): - d = debugger("binaries/basic_test") - - d.run() - bp = d.breakpoint("register_test", hardware=True) - d.cont() - - assert bp.address == d.regs.pc - assert bp.hit_count == 1 - - d.step() - - assert (bp.address + 4) == d.regs.pc - assert bp.hit_count == 1 - - d.step() - - assert (bp.address + 8) == d.regs.pc - assert bp.hit_count == 1 - d.kill() d.terminate() \ No newline at end of file diff --git a/test/aarch64/scripts/basic_test_hw.py b/test/aarch64/scripts/basic_test_hw.py new file mode 100644 index 00000000..943b19ea --- /dev/null +++ b/test/aarch64/scripts/basic_test_hw.py @@ -0,0 +1,123 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + + +class BasicTestHw(unittest.TestCase): + def test_basic(self): + d = debugger("binaries/basic_test") + d.run() + bp = d.breakpoint("register_test", hardware=True) + d.cont() + assert bp.address == d.regs.pc + d.cont() + d.kill() + d.terminate() + + def test_registers(self): + d = debugger("binaries/basic_test") + d.run() + + bp = d.breakpoint(0x964, file="binary", hardware=True) + + d.cont() + + assert d.regs.pc == bp.address + + assert d.regs.x0 == 0x4444333322221111 + assert d.regs.x1 == 0x8888777766665555 + assert d.regs.x2 == 0xccccbbbbaaaa9999 + assert d.regs.x3 == 0x1111ffffeeeedddd + assert d.regs.x4 == 0x5555444433332222 + assert d.regs.x5 == 0x9999888877776666 + assert d.regs.x6 == 0xddddccccbbbbaaaa + assert d.regs.x7 == 0x22221111ffffeeee + assert d.regs.x8 == 0x6666555544443333 + assert d.regs.x9 == 0xaaaa999988887777 + assert d.regs.x10 == 0xeeeeddddccccbbbb + assert d.regs.x11 == 0x333322221111ffff + assert d.regs.x12 == 0x7777666655554444 + assert d.regs.x13 == 0xbbbbaaaa99998888 + assert d.regs.x14 == 0xffffeeeeddddcccc + assert d.regs.x15 == 0x4444333322221111 + assert d.regs.x16 == 0x8888777766665555 + assert d.regs.x17 == 0xccccbbbbaaaa9999 + assert d.regs.x18 == 0x1111ffffeeeedddd + assert d.regs.x19 == 0x5555444433332222 + assert d.regs.x20 == 0x9999888877776666 + assert d.regs.x21 == 0xddddccccbbbbaaaa + assert d.regs.x22 == 0x22221111ffffeeee + assert d.regs.x23 == 0x6666555544443333 + assert d.regs.x24 == 0xaaaa999988887777 + assert d.regs.x25 == 0xeeeeddddccccbbbb + assert d.regs.x26 == 0x333322221111ffff + assert d.regs.x27 == 0x7777666655554444 + assert d.regs.x28 == 0xbbbbaaaa99998888 + assert d.regs.x29 == 0xffffeeeeddddcccc + assert d.regs.x30 == 0x4444333322221111 + + assert d.regs.w0 == 0x22221111 + assert d.regs.w1 == 0x66665555 + assert d.regs.w2 == 0xaaaa9999 + assert d.regs.w3 == 0xeeeedddd + assert d.regs.w4 == 0x33332222 + assert d.regs.w5 == 0x77776666 + assert d.regs.w6 == 0xbbbbaaaa + assert d.regs.w7 == 0xffffeeee + assert d.regs.w8 == 0x44443333 + assert d.regs.w9 == 0x88887777 + assert d.regs.w10 == 0xccccbbbb + assert d.regs.w11 == 0x1111ffff + assert d.regs.w12 == 0x55554444 + assert d.regs.w13 == 0x99998888 + assert d.regs.w14 == 0xddddcccc + assert d.regs.w15 == 0x22221111 + assert d.regs.w16 == 0x66665555 + assert d.regs.w17 == 0xaaaa9999 + assert d.regs.w18 == 0xeeeedddd + assert d.regs.w19 == 0x33332222 + assert d.regs.w20 == 0x77776666 + assert d.regs.w21 == 0xbbbbaaaa + assert d.regs.w22 == 0xffffeeee + assert d.regs.w23 == 0x44443333 + assert d.regs.w24 == 0x88887777 + assert d.regs.w25 == 0xccccbbbb + assert d.regs.w26 == 0x1111ffff + assert d.regs.w27 == 0x55554444 + assert d.regs.w28 == 0x99998888 + assert d.regs.w29 == 0xddddcccc + assert d.regs.w30 == 0x22221111 + + d.cont() + + d.kill() + d.terminate() + + def test_step(self): + d = debugger("binaries/basic_test") + + d.run() + bp = d.breakpoint("register_test", hardware=True) + d.cont() + + assert bp.address == d.regs.pc + assert bp.hit_count == 1 + + d.step() + + assert (bp.address + 4) == d.regs.pc + assert bp.hit_count == 1 + + d.step() + + assert (bp.address + 8) == d.regs.pc + assert bp.hit_count == 1 + + d.kill() + d.terminate() \ No newline at end of file diff --git a/test/aarch64/scripts/basic_test_pie.py b/test/aarch64/scripts/basic_test_pie.py new file mode 100644 index 00000000..a7aa4122 --- /dev/null +++ b/test/aarch64/scripts/basic_test_pie.py @@ -0,0 +1,24 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + +class BasicTestPie(unittest.TestCase): + def test_basic(self): + d = debugger("binaries/basic_test_pie") + + d.run() + bp = d.breakpoint("register_test") + d.cont() + + assert bp.address == d.regs.pc + assert d.regs.x0 == 0x4444333322221111 + + d.cont() + d.kill() + d.terminate() \ No newline at end of file From 849269230d929304132a77b4dd195dae88a58bad Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 15:13:07 +0200 Subject: [PATCH 036/144] test: rebuild basic_test to be non-pie, correct a few offsets --- test/aarch64/binaries/basic_test | Bin 70352 -> 70552 bytes test/aarch64/scripts/attach_detach_test.py | 4 ++-- test/aarch64/scripts/auto_waiting_test.py | 4 ++-- test/aarch64/scripts/basic_test.py | 2 +- test/aarch64/scripts/basic_test_hw.py | 2 +- test/aarch64/scripts/basic_test_pie.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/aarch64/binaries/basic_test b/test/aarch64/binaries/basic_test index 3338f4255f13b5a2020d86be3d6df5abce6a89a1..149c055b7e2119212f76da985225b2ce76bcc419 100755 GIT binary patch literal 70552 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 literal 70352 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 Date: Mon, 15 Jul 2024 18:00:12 +0200 Subject: [PATCH 037/144] fix: remove and reinsert hardware bps on CONT for aarch64 --- libdebug/cffi/ptrace_cffi_source.c | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index d8472e58..150b9b5b 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -874,6 +874,45 @@ int prepare_for_run(struct global_state *state, int pid) t = t->next; } +#ifdef ARCH_AARCH64 + // iterate over all the threads and check if any of them has hit a hardware + // breakpoint + t = state->t_HEAD; + struct hardware_breakpoint *bp; + int bp_hit; + + while (t != NULL) { + bp_hit = 0; + + bp = state->hw_b_HEAD; + while (bp != NULL && !bp_hit) { + if (bp->tid == t->tid && bp->enabled && is_breakpoint_hit(bp)) { + // we hit a hardware breakpoint on this thread + bp_hit = 1; + break; + } + + bp = bp->next; + } + + if (bp_hit) { + // remove the breakpoint + remove_hardware_breakpoint(bp); + + // step over the breakpoint + if (ptrace(PTRACE_SINGLESTEP, t->tid, NULL, NULL)) return -1; + + // wait for the child + waitpid(t->tid, &status, 0); + + // re-add the breakpoint + install_hardware_breakpoint(bp); + } + + t = t->next; + } +#endif + // Reset any software breakpoint b = state->sw_b_HEAD; while (b != NULL) { From 0ff05b74ea04fc59d7359598f519ea83306ebf49 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 18:01:34 +0200 Subject: [PATCH 038/144] test: add breakpoint_test for aarch64 --- test/aarch64/run_suite.py | 2 + test/aarch64/scripts/basic_test_pie.py | 4 +- test/aarch64/scripts/breakpoint_test.py | 379 ++++++++++++++++++++++++ 3 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 test/aarch64/scripts/breakpoint_test.py diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index ca8b7067..4a7084ff 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -12,6 +12,7 @@ from scripts.basic_test import BasicTest from scripts.basic_test_pie import BasicTestPie from scripts.basic_test_hw import BasicTestHw +from scripts.breakpoint_test import BreakpointTest def fast_suite(): suite = TestSuite() @@ -21,6 +22,7 @@ def fast_suite(): suite.addTest(TestLoader().loadTestsFromTestCase(BasicTest)) suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestPie)) suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestHw)) + suite.addTest(TestLoader().loadTestsFromTestCase(BreakpointTest)) return suite diff --git a/test/aarch64/scripts/basic_test_pie.py b/test/aarch64/scripts/basic_test_pie.py index 2a5c5629..1ff08c44 100644 --- a/test/aarch64/scripts/basic_test_pie.py +++ b/test/aarch64/scripts/basic_test_pie.py @@ -13,11 +13,11 @@ def test_basic(self): d = debugger("binaries/basic_test_pie") d.run() - bp = d.breakpoint(0x964, file="binary") + bp = d.breakpoint(0x780, file="binary") d.cont() assert bp.address == d.regs.pc - assert d.regs.x0 == 0x4444333322221111 + assert d.regs.x0 == 0xaabbccdd11223344 d.cont() d.kill() diff --git a/test/aarch64/scripts/breakpoint_test.py b/test/aarch64/scripts/breakpoint_test.py new file mode 100644 index 00000000..dfd985eb --- /dev/null +++ b/test/aarch64/scripts/breakpoint_test.py @@ -0,0 +1,379 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import io +import logging +import unittest + +from libdebug import debugger + + +class BreakpointTest(unittest.TestCase): + def setUp(self): + # Redirect logging to a string buffer + self.log_capture_string = io.StringIO() + self.log_handler = logging.StreamHandler(self.log_capture_string) + self.log_handler.setLevel(logging.WARNING) + + self.logger = logging.getLogger("libdebug") + self.original_handlers = self.logger.handlers + self.logger.handlers = [] + self.logger.addHandler(self.log_handler) + self.logger.setLevel(logging.WARNING) + + def test_bps(self): + d = debugger("binaries/breakpoint_test") + + d.run() + + bp1 = d.breakpoint("random_function") + bp2 = d.breakpoint(0x7fc, file="binary") + bp3 = d.breakpoint(0x820, file="binary") + + counter = 1 + + d.cont() + + while True: + if d.regs.pc == bp1.address: + self.assertTrue(bp1.hit_count == 1) + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + elif d.regs.pc == bp2.address: + self.assertTrue(bp2.hit_count == counter) + self.assertTrue(bp2.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + counter += 1 + elif d.regs.pc == bp3.address: + self.assertTrue(bp3.hit_count == 1) + self.assertTrue(d.regs.x1 == 45) + self.assertTrue(d.regs.w1 == 45) + self.assertTrue(bp3.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + break + + d.cont() + + assert bp2.hit_count == 10 + + d.kill() + d.terminate() + + def test_bp_disable(self): + d = debugger("binaries/breakpoint_test") + + d.run() + + bp1 = d.breakpoint("random_function") + bp2 = d.breakpoint(0x7fc, file="binary") + bp3 = d.breakpoint(0x820, file="binary") + + counter = 1 + + d.cont() + + while True: + if d.regs.pc == bp1.address: + self.assertTrue(bp1.hit_count == 1) + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + elif d.regs.pc == bp2.address: + self.assertTrue(bp2.hit_count == counter) + self.assertTrue(bp2.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + bp2.disable() + elif d.regs.pc == bp3.address: + self.assertTrue(bp3.hit_count == 1) + self.assertTrue(d.regs.w1 == 45) + self.assertTrue(d.regs.x1 == 45) + self.assertTrue(bp3.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + break + + d.cont() + + assert bp2.hit_count == 1 + + d.kill() + d.terminate() + + def test_bp_disable_hardware(self): + d = debugger("binaries/breakpoint_test") + + d.run() + + bp1 = d.breakpoint("random_function") + bp2 = d.breakpoint(0x7fc, file="binary", hardware=True) + bp3 = d.breakpoint(0x820, file="binary") + + counter = 1 + + d.cont() + + while True: + if d.regs.pc == bp1.address: + self.assertTrue(bp1.hit_count == 1) + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + elif d.regs.pc == bp2.address: + self.assertTrue(bp2.hit_count == counter) + self.assertTrue(bp2.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + bp2.disable() + elif d.regs.pc == bp3.address: + self.assertTrue(bp3.hit_count == 1) + self.assertTrue(d.regs.w1 == 45) + self.assertTrue(d.regs.x1 == 45) + self.assertTrue(bp3.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + break + + d.cont() + + assert bp2.hit_count == 1 + + d.kill() + d.terminate() + + def test_bp_disable_reenable(self): + d = debugger("binaries/breakpoint_test") + + d.run() + + bp1 = d.breakpoint("random_function") + bp2 = d.breakpoint(0x7fc, file="binary") + bp4 = d.breakpoint(0x814, file="binary") + bp3 = d.breakpoint(0x820, file="binary") + + counter = 1 + + d.cont() + + while True: + if d.regs.pc == bp1.address: + self.assertTrue(bp1.hit_count == 1) + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + elif d.regs.pc == bp2.address: + self.assertTrue(bp2.hit_count == counter) + self.assertTrue(bp2.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + if bp4.enabled: + bp4.disable() + else: + bp4.enable() + counter += 1 + elif d.regs.pc == bp3.address: + self.assertTrue(bp3.hit_count == 1) + self.assertTrue(d.regs.w1 == 45) + self.assertTrue(d.regs.x1 == 45) + self.assertTrue(bp3.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + break + elif bp4.hit_on(d): + pass + + d.cont() + + assert bp3.hit_count == 1 + assert bp4.hit_count == (bp2.hit_count // 2 + 1) + + d.kill() + d.terminate() + + def test_bp_disable_reenable_hardware(self): + d = debugger("binaries/breakpoint_test") + + d.run() + + bp1 = d.breakpoint("random_function", hardware=True) + bp2 = d.breakpoint(0x7fc, file="binary", hardware=True) + bp4 = d.breakpoint(0x810, file="binary", hardware=True) + bp3 = d.breakpoint(0x820, file="binary", hardware=True) + + counter = 1 + + d.cont() + + for _ in range(20): + if d.regs.pc == bp1.address: + print('A') + self.assertTrue(bp1.hit_count == 1) + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + elif d.regs.pc == bp2.address: + print('B') + self.assertTrue(bp2.hit_count == counter) + self.assertTrue(bp2.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + if bp4.enabled: + bp4.disable() + else: + bp4.enable() + counter += 1 + elif d.regs.pc == bp3.address: + print('C') + self.assertTrue(bp3.hit_count == 1) + self.assertTrue(d.regs.w1 == 45) + self.assertTrue(d.regs.x1 == 45) + self.assertTrue(bp3.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + break + elif bp4.hit_on(d): + print('D') + pass + + d.cont() + + assert bp4.hit_count == (bp2.hit_count // 2 + 1) + + d.kill() + d.terminate() + + def test_bps_running(self): + d = debugger("binaries/breakpoint_test") + + d.run() + + bp1 = d.breakpoint("random_function") + bp2 = d.breakpoint(0x7fc, file="binary") + bp3 = d.breakpoint(0x820, file="binary") + + counter = 1 + + d.cont() + + while True: + if d.running: + pass + if d.regs.pc == bp1.address: + self.assertFalse(d.running) + self.assertTrue(bp1.hit_count == 1) + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + elif d.regs.pc == bp2.address: + self.assertFalse(d.running) + self.assertTrue(bp2.hit_count == counter) + self.assertTrue(bp2.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp3.hit_on(d)) + counter += 1 + elif d.regs.pc == bp3.address: + self.assertFalse(d.running) + self.assertTrue(bp3.hit_count == 1) + self.assertTrue(d.regs.w1 == 45) + self.assertTrue(d.regs.x1 == 45) + self.assertTrue(bp3.hit_on(d)) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + break + + d.cont() + + assert bp2.hit_count == 10 + + d.kill() + d.terminate() + + def test_bp_backing_file(self): + d = debugger("binaries/executable_section_test") + + d.run() + + bp1 = d.breakpoint(0x968, file="binary") + + d.cont() + + d.wait() + + if bp1.hit_on(d): + for vmap in d.maps(): + if "x" in vmap.permissions and "anon" in vmap.backing_file: + section = vmap.backing_file + bp2 = d.breakpoint(0x10, file=section) + d.cont() + + d.wait() + + if bp2.hit_on(d): + self.assertEqual(d.memory[d.regs.pc, 4], bytes.fromhex("ff430091")) + self.assertEqual(d.regs.w0, 9) + + d.kill() + + self.assertEqual(bp1.hit_count, 1) + self.assertEqual(bp2.hit_count, 1) + + d.run() + + bp1 = d.breakpoint(0x968, file="executable_section_test") + + d.cont() + + d.wait() + + if bp1.hit_on(d): + for vmap in d.maps(): + if "x" in vmap.permissions and "anon" in vmap.backing_file: + section = vmap.backing_file + bp2 = d.breakpoint(0x10, file=section) + d.cont() + + d.wait() + + if bp2.hit_on(d): + self.assertEqual(d.memory[d.regs.pc, 4], bytes.fromhex("ff430091")) + self.assertEqual(d.regs.w0, 9) + + d.run() + + bp1 = d.breakpoint(0x968, file="hybrid") + + d.cont() + + d.wait() + + if bp1.hit_on(d): + for vmap in d.maps(): + if "x" in vmap.permissions and "anon" in vmap.backing_file: + section = vmap.backing_file + bp2 = d.breakpoint(0x10, file=section) + d.cont() + + d.wait() + + if bp2.hit_on(d): + self.assertEqual(d.memory[d.regs.pc, 4], bytes.fromhex("ff430091")) + self.assertEqual(d.regs.w0, 9) + + d.kill() + + self.assertEqual(bp1.hit_count, 1) + self.assertEqual(bp2.hit_count, 1) + + d.run() + + with self.assertRaises(ValueError): + d.breakpoint(0x968, file="absolute") + + d.kill() + d.terminate() From 4cba5012b7dc369d85ae52853dfa0dc661dcfe9b Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 18:05:37 +0200 Subject: [PATCH 039/144] test: add brute_test for aarch64 --- test/aarch64/run_suite.py | 2 ++ test/aarch64/scripts/brute_test.py | 48 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 test/aarch64/scripts/brute_test.py diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index 4a7084ff..1da28031 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -13,6 +13,7 @@ from scripts.basic_test_pie import BasicTestPie from scripts.basic_test_hw import BasicTestHw from scripts.breakpoint_test import BreakpointTest +from scripts.brute_test import BruteTest def fast_suite(): suite = TestSuite() @@ -23,6 +24,7 @@ def fast_suite(): suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestPie)) suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestHw)) suite.addTest(TestLoader().loadTestsFromTestCase(BreakpointTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(BruteTest)) return suite diff --git a/test/aarch64/scripts/brute_test.py b/test/aarch64/scripts/brute_test.py new file mode 100644 index 00000000..0b4e0c82 --- /dev/null +++ b/test/aarch64/scripts/brute_test.py @@ -0,0 +1,48 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio, Francesco Panebianco. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import string +import unittest + +from libdebug import debugger + + +class BruteTest(unittest.TestCase): + def setUp(self): + pass + + def test_bruteforce(self): + flag = "" + counter = 1 + + d = debugger("binaries/brute_test") + + while not flag or flag != "BRUTINOBRUTONE": + for c in string.printable: + r = d.run() + bp = d.breakpoint(0x974, hardware=True, file="binary") + d.cont() + + r.sendlineafter(b"chars\n", (flag + c).encode()) + + while bp.address == d.regs.pc: + d.cont() + + if bp.hit_count > counter: + flag += c + counter = bp.hit_count + d.kill() + break + + message = r.recvline() + + d.kill() + + if message == b"Giusto!": + flag += c + break + + self.assertEqual(flag, "BRUTINOBRUTONE") From 17b8a64001103d4df6d65b23f8095d2509827d31 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 18:09:54 +0200 Subject: [PATCH 040/144] fix: add aarch64 syscall remote defition list in syscall_utils.py --- libdebug/utils/syscall_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libdebug/utils/syscall_utils.py b/libdebug/utils/syscall_utils.py index 2a38ec69..d23f01dd 100644 --- a/libdebug/utils/syscall_utils.py +++ b/libdebug/utils/syscall_utils.py @@ -19,6 +19,8 @@ def get_remote_definition_url(arch: str) -> str: match arch: case "amd64": return f"{SYSCALLS_REMOTE}/x86/64/x64/latest/table.json" + case "aarch64": + return f"{SYSCALLS_REMOTE}/arm/64/aarch64/latest/table.json" case _: raise ValueError(f"Architecture {arch} not supported") From 359d4565c51ddaf442b9bc3c36f001f6bbb28318 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 18:11:09 +0200 Subject: [PATCH 041/144] test: add builtin_handler_test for aarch64 --- test/aarch64/run_suite.py | 2 + test/aarch64/scripts/builtin_handler_test.py | 62 ++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 test/aarch64/scripts/builtin_handler_test.py diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index 1da28031..d974ada2 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -14,6 +14,7 @@ from scripts.basic_test_hw import BasicTestHw from scripts.breakpoint_test import BreakpointTest from scripts.brute_test import BruteTest +from scripts.builtin_handler_test import BuiltinHandlerTest def fast_suite(): suite = TestSuite() @@ -25,6 +26,7 @@ def fast_suite(): suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestHw)) suite.addTest(TestLoader().loadTestsFromTestCase(BreakpointTest)) suite.addTest(TestLoader().loadTestsFromTestCase(BruteTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(BuiltinHandlerTest)) return suite diff --git a/test/aarch64/scripts/builtin_handler_test.py b/test/aarch64/scripts/builtin_handler_test.py new file mode 100644 index 00000000..96d0fe88 --- /dev/null +++ b/test/aarch64/scripts/builtin_handler_test.py @@ -0,0 +1,62 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest +import string + +from libdebug import debugger + + +class BuiltinHandlerTest(unittest.TestCase): + def test_antidebug_escaping(self): + d = debugger("binaries/antidebug_brute_test") + + # validate that without the handler the binary cannot be debugged + r = d.run() + d.cont() + msg = r.recvline() + self.assertEqual(msg, b"Debugger detected") + d.kill() + + # validate that with the handler the binary can be debugged + d = debugger("binaries/antidebug_brute_test", escape_antidebug=True) + r = d.run() + d.cont() + msg = r.recvline() + self.assertEqual(msg, b"Write up to 64 chars") + d.interrupt() + d.kill() + + # validate that the binary still works + flag = "" + counter = 1 + + while not flag or flag != "BRUTE": + for c in string.printable: + r = d.run() + bp = d.breakpoint(0xa10, hardware=True, file="binary") + d.cont() + + r.sendlineafter(b"chars\n", (flag + c).encode()) + + while bp.address == d.regs.pc: + d.cont() + + if bp.hit_count > counter: + flag += c + counter = bp.hit_count + d.kill() + break + + message = r.recvline() + + d.kill() + + if message == b"Giusto!": + flag += c + break + + self.assertEqual(flag, "BRUTE") From 21755f68198052b09b2bf32d47973cfc67806ca2 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 18:37:45 +0200 Subject: [PATCH 042/144] test: remove unneeded print calls from breakpoint_test.py --- test/aarch64/scripts/breakpoint_test.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/aarch64/scripts/breakpoint_test.py b/test/aarch64/scripts/breakpoint_test.py index dfd985eb..b95b1c50 100644 --- a/test/aarch64/scripts/breakpoint_test.py +++ b/test/aarch64/scripts/breakpoint_test.py @@ -212,13 +212,11 @@ def test_bp_disable_reenable_hardware(self): for _ in range(20): if d.regs.pc == bp1.address: - print('A') self.assertTrue(bp1.hit_count == 1) self.assertTrue(bp1.hit_on(d)) self.assertFalse(bp2.hit_on(d)) self.assertFalse(bp3.hit_on(d)) elif d.regs.pc == bp2.address: - print('B') self.assertTrue(bp2.hit_count == counter) self.assertTrue(bp2.hit_on(d)) self.assertFalse(bp1.hit_on(d)) @@ -229,7 +227,6 @@ def test_bp_disable_reenable_hardware(self): bp4.enable() counter += 1 elif d.regs.pc == bp3.address: - print('C') self.assertTrue(bp3.hit_count == 1) self.assertTrue(d.regs.w1 == 45) self.assertTrue(d.regs.x1 == 45) @@ -238,7 +235,6 @@ def test_bp_disable_reenable_hardware(self): self.assertFalse(bp2.hit_on(d)) break elif bp4.hit_on(d): - print('D') pass d.cont() From c9b97c6de859fbba0db5ae20c6cb0b2effdc5d2c Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 18:38:38 +0200 Subject: [PATCH 043/144] fix: whoops I typed the wrong URL --- libdebug/utils/syscall_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdebug/utils/syscall_utils.py b/libdebug/utils/syscall_utils.py index d23f01dd..f96cb93e 100644 --- a/libdebug/utils/syscall_utils.py +++ b/libdebug/utils/syscall_utils.py @@ -20,7 +20,7 @@ def get_remote_definition_url(arch: str) -> str: case "amd64": return f"{SYSCALLS_REMOTE}/x86/64/x64/latest/table.json" case "aarch64": - return f"{SYSCALLS_REMOTE}/arm/64/aarch64/latest/table.json" + return f"{SYSCALLS_REMOTE}/arm64/64/aarch64/latest/table.json" case _: raise ValueError(f"Architecture {arch} not supported") From 0d371bfd422e421d7ea9a9a311b8aa2a4b3c7e14 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 19:46:07 +0200 Subject: [PATCH 044/144] feat: introduce stack unwinding for aarch64 --- .../aarch64/aarch64_stack_unwinder.py | 68 +++++++++++++++++++ .../architectures/stack_unwinding_provider.py | 6 ++ 2 files changed, 74 insertions(+) create mode 100644 libdebug/architectures/aarch64/aarch64_stack_unwinder.py diff --git a/libdebug/architectures/aarch64/aarch64_stack_unwinder.py b/libdebug/architectures/aarch64/aarch64_stack_unwinder.py new file mode 100644 index 00000000..12a0edaa --- /dev/null +++ b/libdebug/architectures/aarch64/aarch64_stack_unwinder.py @@ -0,0 +1,68 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2023-2024 Roberto Alessandro Bertolini, Francesco Panebianco, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from libdebug.architectures.stack_unwinding_manager import StackUnwindingManager + +if TYPE_CHECKING: + from libdebug.state.thread_context import ThreadContext + + +class Aarch64StackUnwinder(StackUnwindingManager): + """Class that provides stack unwinding for the AArch64 architecture.""" + + def unwind(self: Aarch64StackUnwinder, target: ThreadContext) -> list: + """Unwind the stack of a process. + + Args: + target (ThreadContext): The target ThreadContext. + + Returns: + list: A list of return addresses. + """ + assert hasattr(target.regs, "pc") + + frame_pointer = target.regs.x29 + initial_link_register = target.regs.x30 + stack_trace = [target.regs.pc, initial_link_register] + + vmaps = target._internal_debugger.debugging_interface.maps() + + # Follow the frame chain + while frame_pointer: + try: + link_register = int.from_bytes(target.memory[frame_pointer + 8, 8], byteorder="little") + frame_pointer = int.from_bytes(target.memory[frame_pointer, 8], byteorder="little") + + if not any(vmap.start <= link_register < vmap.end for vmap in vmaps): + break + + # Leaf functions don't set the previous stack frame pointer + # But they set the link register to the return address + # Non-leaf functions set both + if initial_link_register and link_register == initial_link_register: + initial_link_register = None + continue + + stack_trace.append(link_register) + except (OSError, ValueError): + break + + return stack_trace + + def get_return_address(self: Aarch64StackUnwinder, target: ThreadContext) -> int: + """Get the return address of the current function. + + Args: + target (ThreadContext): The target ThreadContext. + + Returns: + int: The return address. + """ + return target.regs.x30 diff --git a/libdebug/architectures/stack_unwinding_provider.py b/libdebug/architectures/stack_unwinding_provider.py index f0a84840..51778dec 100644 --- a/libdebug/architectures/stack_unwinding_provider.py +++ b/libdebug/architectures/stack_unwinding_provider.py @@ -4,11 +4,15 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # +from libdebug.architectures.aarch64.aarch64_stack_unwinder import ( + Aarch64StackUnwinder, +) from libdebug.architectures.amd64.amd64_stack_unwinder import ( Amd64StackUnwinder, ) from libdebug.architectures.stack_unwinding_manager import StackUnwindingManager +_aarch64_stack_unwinder = Aarch64StackUnwinder() _amd64_stack_unwinder = Amd64StackUnwinder() @@ -17,5 +21,7 @@ def stack_unwinding_provider(architecture: str) -> StackUnwindingManager: match architecture: case "amd64": return _amd64_stack_unwinder + case "aarch64": + return _aarch64_stack_unwinder case _: raise NotImplementedError(f"Architecture {architecture} not available.") From d4ac616dbbd13ca1f20e7ab8b77cefcb278c9223 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 19:46:26 +0200 Subject: [PATCH 045/144] test: add backtrace test for aarch64 --- .../binaries/{backtrace => backtrace_test} | Bin test/aarch64/run_suite.py | 2 + test/aarch64/scripts/backtrace_test.py | 108 ++++++++++++++++++ 3 files changed, 110 insertions(+) rename test/aarch64/binaries/{backtrace => backtrace_test} (100%) create mode 100644 test/aarch64/scripts/backtrace_test.py diff --git a/test/aarch64/binaries/backtrace b/test/aarch64/binaries/backtrace_test similarity index 100% rename from test/aarch64/binaries/backtrace rename to test/aarch64/binaries/backtrace_test diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index d974ada2..c0c393c4 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -9,6 +9,7 @@ from scripts.attach_detach_test import AttachDetachTest from scripts.auto_waiting_test import AutoWaitingTest +from scripts.backtrace_test import BacktraceTest from scripts.basic_test import BasicTest from scripts.basic_test_pie import BasicTestPie from scripts.basic_test_hw import BasicTestHw @@ -21,6 +22,7 @@ def fast_suite(): suite.addTest(TestLoader().loadTestsFromTestCase(AttachDetachTest)) suite.addTest(TestLoader().loadTestsFromTestCase(AutoWaitingTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(BacktraceTest)) suite.addTest(TestLoader().loadTestsFromTestCase(BasicTest)) suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestPie)) suite.addTest(TestLoader().loadTestsFromTestCase(BasicTestHw)) diff --git a/test/aarch64/scripts/backtrace_test.py b/test/aarch64/scripts/backtrace_test.py new file mode 100644 index 00000000..97ea5a3a --- /dev/null +++ b/test/aarch64/scripts/backtrace_test.py @@ -0,0 +1,108 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2023-2024 Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + + +class BacktraceTest(unittest.TestCase): + def setUp(self): + self.d = debugger("binaries/backtrace_test") + + def test_backtrace(self): + d = self.d + + d.run() + + bp0 = d.breakpoint("main+8") + bp1 = d.breakpoint("function1+8") + bp2 = d.breakpoint("function2+8") + bp3 = d.breakpoint("function3+8") + bp4 = d.breakpoint("function4+8") + bp5 = d.breakpoint("function5+8") + bp6 = d.breakpoint("function6+8") + + d.cont() + + self.assertTrue(d.regs.pc == bp0.address) + backtrace = d.backtrace() + self.assertIn("_start", backtrace.pop()) + self.assertEqual(backtrace[:1], ["main+8"]) + + d.cont() + + self.assertTrue(d.regs.pc == bp1.address) + backtrace = d.backtrace() + self.assertIn("_start", backtrace.pop()) + self.assertEqual(backtrace[:2], ["function1+8", "main+c"]) + + d.cont() + + self.assertTrue(d.regs.pc == bp2.address) + backtrace = d.backtrace() + self.assertIn("_start", backtrace.pop()) + self.assertEqual(backtrace[:3], ["function2+8", "function1+10", "main+c"]) + + d.cont() + + self.assertTrue(d.regs.pc == bp3.address) + backtrace = d.backtrace() + self.assertIn("_start", backtrace.pop()) + self.assertEqual( + backtrace[:4], ["function3+8", "function2+18", "function1+10", "main+c"] + ) + + d.cont() + + self.assertTrue(d.regs.pc == bp4.address) + backtrace = d.backtrace() + self.assertIn("_start", backtrace.pop()) + self.assertEqual( + backtrace[:5], + ["function4+8", "function3+18", "function2+18", "function1+10", "main+c"], + ) + + d.cont() + + self.assertTrue(d.regs.pc == bp5.address) + backtrace = d.backtrace() + self.assertIn("_start", backtrace.pop()) + self.assertEqual( + backtrace[:6], + [ + "function5+8", + "function4+18", + "function3+18", + "function2+18", + "function1+10", + "main+c", + ], + ) + + d.cont() + + self.assertTrue(d.regs.pc == bp6.address) + backtrace = d.backtrace() + self.assertIn("_start", backtrace.pop()) + self.assertEqual( + backtrace[:7], + [ + "function6+8", + "function5+18", + "function4+18", + "function3+18", + "function2+18", + "function1+10", + "main+c", + ], + ) + + d.kill() + + +if __name__ == "__main__": + unittest.main() From c1d61a4311abed4a520c00e487f504ea85b405e7 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 15 Jul 2024 20:33:02 +0200 Subject: [PATCH 046/144] test: add callback_test, catch_signal_test, death_test, jumpstart_test, memory_test and thread tests for aarch64 --- test/aarch64/binaries/jumpstart_test_preload | Bin 69784 -> 0 bytes ...omplex_thread_test => thread_test_complex} | Bin test/aarch64/run_suite.py | 14 + test/aarch64/scripts/callback_test.py | 264 ++++ test/aarch64/scripts/catch_signal_test.py | 1266 +++++++++++++++++ test/aarch64/scripts/death_test.py | 154 ++ test/aarch64/scripts/jumpstart_test.py | 24 + test/aarch64/scripts/memory_test.py | 288 ++++ test/aarch64/scripts/thread_test.py | 83 ++ test/aarch64/scripts/thread_test_complex.py | 67 + 10 files changed, 2160 insertions(+) delete mode 100755 test/aarch64/binaries/jumpstart_test_preload rename test/aarch64/binaries/{complex_thread_test => thread_test_complex} (100%) create mode 100644 test/aarch64/scripts/callback_test.py create mode 100644 test/aarch64/scripts/catch_signal_test.py create mode 100644 test/aarch64/scripts/death_test.py create mode 100644 test/aarch64/scripts/jumpstart_test.py create mode 100644 test/aarch64/scripts/memory_test.py create mode 100644 test/aarch64/scripts/thread_test.py create mode 100644 test/aarch64/scripts/thread_test_complex.py diff --git a/test/aarch64/binaries/jumpstart_test_preload b/test/aarch64/binaries/jumpstart_test_preload deleted file mode 100755 index 05c57d6b97a9216adcf1e62210d5e84af808c7c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69784 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 c5ASM6QW counter: + flag += c + counter = new_counter + d.kill() + break + d.kill() + if message == b"Giusto!": + flag += c + end = True + break + if end: + break + + self.assertEqual(flag, "BRUTINOBRUTONE") + + if self.exceptions: + raise self.exceptions[0] + + def test_callback_exception(self): + self.exceptions.clear() + + d = debugger("binaries/basic_test") + + d.run() + + def callback(thread, bp): + # This operation should not raise any exception + _ = d.regs.x0 + + d.breakpoint("register_test", callback=callback, hardware=True) + + d.cont() + + d.kill() + + def test_callback_step(self): + self.exceptions.clear() + + d = debugger("binaries/basic_test") + + d.run() + + def callback(t, bp): + self.assertEqual(t.regs.pc, bp.address) + d.step() + self.assertEqual(t.regs.pc, bp.address + 4) + + d.breakpoint("register_test", callback=callback) + + d.cont() + + d.kill() + + def test_callback_pid_accessible(self): + self.exceptions.clear() + + d = debugger("binaries/basic_test") + + d.run() + + hit = False + + def callback(t, bp): + nonlocal hit + self.assertEqual(t.process_id, d.process_id) + hit = True + + d.breakpoint("register_test", callback=callback) + + d.cont() + d.kill() + + self.assertTrue(hit) + + def test_callback_pid_accessible_alias(self): + self.exceptions.clear() + + d = debugger("binaries/basic_test") + + d.run() + + hit = False + + def callback(t, bp): + nonlocal hit + self.assertEqual(t.pid, d.pid) + self.assertEqual(t.pid, t.process_id) + hit = True + + d.breakpoint("register_test", callback=callback) + + d.cont() + d.kill() + + self.assertTrue(hit) + + def test_callback_tid_accessible_alias(self): + self.exceptions.clear() + + d = debugger("binaries/basic_test") + + d.run() + + hit = False + + def callback(t, bp): + nonlocal hit + self.assertEqual(t.tid, t.thread_id) + hit = True + + d.breakpoint("register_test", callback=callback) + + d.cont() + d.kill() + + self.assertTrue(hit) diff --git a/test/aarch64/scripts/catch_signal_test.py b/test/aarch64/scripts/catch_signal_test.py new file mode 100644 index 00000000..82e16d08 --- /dev/null +++ b/test/aarch64/scripts/catch_signal_test.py @@ -0,0 +1,1266 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import io +import logging +import unittest + +from libdebug import debugger + + +class CatchSignalTest(unittest.TestCase): + def setUp(self): + # Redirect logging to a string buffer + self.log_capture_string = io.StringIO() + self.log_handler = logging.StreamHandler(self.log_capture_string) + self.log_handler.setLevel(logging.WARNING) + + self.logger = logging.getLogger("libdebug") + self.original_handlers = self.logger.handlers + self.logger.handlers = [] + self.logger.addHandler(self.log_handler) + self.logger.setLevel(logging.WARNING) + + def tearDown(self): + self.logger.removeHandler(self.log_handler) + self.logger.handlers = self.original_handlers + self.log_handler.close() + + def test_signal_catch_signal_block(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + + SIGQUIT_count += 1 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + SIGPIPE_count += 1 + + d = debugger("binaries/catch_signal_test") + + d.signals_to_block = ["SIGUSR1", 15, "SIGINT", 3, 13] + + d.run() + + catcher1 = d.catch_signal(10, callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal(2, callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + d.cont() + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + def test_signal_pass_to_process(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + + SIGQUIT_count += 1 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + SIGPIPE_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 10" * 2) + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + def test_signal_disable_catch_signal(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + + SIGQUIT_count += 1 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + SIGPIPE_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + bp = d.breakpoint(0x964) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + # Uncatchering signals + if bp.hit_on(d): + catcher4.disable() + catcher5.disable() + d.cont() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 2) # 1 times less because of the disable catch + self.assertEqual(SIGPIPE_count, 2) # 1 times less because of the disable catch + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 10" * 2) + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + def test_signal_unblock(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + + SIGQUIT_count += 1 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + SIGPIPE_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + d.signals_to_block = [10, 15, 2, 3, 13] + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + bp = d.breakpoint(0x964) + + d.cont() + + # No block the signals anymore + if bp.hit_on(d): + d.signals_to_block = [] + + d.cont() + + signal_received = [] + while True: + try: + signal_received.append(r.recvline()) + except RuntimeError: + break + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + self.assertEqual(signal_received[0], b"Received signal 3") + self.assertEqual(signal_received[1], b"Received signal 13") + self.assertEqual(signal_received[2], b"Exiting normally.") + + self.assertEqual(len(signal_received), 3) + + def test_signal_disable_catch_signal_unblock(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + + SIGQUIT_count += 1 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + SIGPIPE_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + d.signals_to_block = [10, 15, 2, 3, 13] + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + bp = d.breakpoint(0x964) + + d.cont() + + # No block the signals anymore + if bp.hit_on(d): + d.signals_to_block = [] + catcher4.disable() + catcher5.disable() + + d.cont() + + signal_received = [] + while True: + try: + signal_received.append(r.recvline()) + except RuntimeError: + break + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 2) # 1 times less because of the disable catch + self.assertEqual(SIGPIPE_count, 2) # 1 times less because of the disable catch + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + self.assertEqual(signal_received[0], b"Received signal 3") + self.assertEqual(signal_received[1], b"Received signal 13") + self.assertEqual(signal_received[2], b"Exiting normally.") + + self.assertEqual(len(signal_received), 3) + + def test_hijack_signal_with_catch_signal(self): + def catcher_SIGUSR1(t, sc): + # Hijack to SIGTERM + t.signal = 15 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(catcher1.hit_count, 2) + + self.assertEqual(SIGUSR1, b"Received signal 15" * 2) # hijacked signal + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + def test_hijack_signal_with_api(self): + d = debugger("binaries/catch_signal_test") + + r = d.run() + + # Hijack to SIGTERM + catcher1 = d.hijack_signal("SIGUSR1", 15) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(catcher1.hit_count, 2) + + self.assertEqual(SIGUSR1, b"Received signal 15" * 2) # hijacked signal + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + def test_recursive_true_with_catch_signal(self): + SIGUSR1_count = 0 + SIGTERM_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + # Hijack to SIGTERM + t.signal = 15 + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal(10, callback=catcher_SIGUSR1, recursive=True) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 4) # 2 times more because of the hijack + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 15" * 2) # hijacked signal + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + def test_recursive_true_with_api(self): + SIGTERM_count = 0 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.hijack_signal(10, 15, recursive=True) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(SIGTERM_count, 4) # 2 times more because of the hijack + self.assertEqual(catcher1.hit_count, 2) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 15" * 2) # hijacked signal + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + def test_recursive_false_with_catch_signal(self): + SIGUSR1_count = 0 + SIGTERM_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + # Hijack to SIGTERM + t.signal = 15 + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal(10, callback=catcher_SIGUSR1, recursive=False) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) # 2 times in total because of the recursive=False + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 15" * 2) # hijacked signal + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + def test_recursive_false_with_api(self): + SIGTERM_count = 0 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.hijack_signal(10, 15, recursive=False) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(catcher1.hit_count, 2) + self.assertEqual(SIGTERM_count, 2) # 2 times in total because of the recursive=False + self.assertEqual(SIGTERM_count, catcher2.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 15" * 2) # hijacked signal + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + def test_hijack_signal_with_catch_signal_loop(self): + # Let create a loop of hijacking signals + + def catcher_SIGUSR1(t, sc): + # Hijack to SIGTERM + t.signal = 15 + + def catcher_SIGTERM(t, sc): + # Hijack to SIGINT + t.signal = 10 + + d = debugger("binaries/catch_signal_test") + + d.run() + + d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1, recursive=True) + d.catch_signal("SIGTERM", callback=catcher_SIGTERM, recursive=True) + + with self.assertRaises(RuntimeError): + d.cont() + d.wait() + + d.kill() + + # Now we set recursive=False to avoid the loop + d.run() + + d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1, recursive=False) + d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + + d.cont() + d.kill() + + d.run() + + d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + d.catch_signal("SIGTERM", callback=catcher_SIGTERM, recursive=False) + + d.cont() + d.kill() + + d.run() + + d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1, recursive=False) + d.catch_signal("SIGTERM", callback=catcher_SIGTERM, recursive=False) + + d.cont() + d.kill() + + def test_hijack_signal_with_api_loop(self): + # Let create a loop of hijacking signals + + d = debugger("binaries/catch_signal_test") + + d.run() + + d.hijack_signal("SIGUSR1", "SIGTERM", recursive=True) + d.hijack_signal(15, 10, recursive=True) + + with self.assertRaises(RuntimeError): + d.cont() + d.wait() + + d.kill() + + # Now we set recursive=False to avoid the loop + d.run() + + d.hijack_signal("SIGUSR1", "SIGTERM", recursive=False) + d.hijack_signal(15, 10) + + d.cont() + d.kill() + + d.run() + + d.hijack_signal("SIGUSR1", "SIGTERM") + d.hijack_signal(15, 10, recursive=False) + + d.cont() + d.kill() + + d.run() + + d.hijack_signal("SIGUSR1", "SIGTERM", recursive=False) + d.hijack_signal(15, 10, recursive=False) + + d.cont() + d.kill() + + def test_signal_unhijacking(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGTERM_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.hijack_signal("SIGQUIT", "SIGTERM", recursive=True) + catcher5 = d.hijack_signal("SIGPIPE", "SIGTERM", recursive=True) + + bp = d.breakpoint(0x964) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + # Disable catching of signals + if bp.hit_on(d): + catcher4.disable() + catcher5.disable() + d.cont() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2 + 2 + 2) # 2 times more because of the hijacking * 2 (SIGQUIT and SIGPIPE) + self.assertEqual(SIGINT_count, 2) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 10" * 2) + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 15" * 2 + b"Received signal 3") + self.assertEqual(SIGPIPE, b"Received signal 15" * 2 + b"Received signal 13") + + def test_override_catch_signal(self): + SIGPIPE_count_first = 0 + SIGPIPE_count_second = 0 + + def catcher_SIGPIPE_first(t, sc): + nonlocal SIGPIPE_count_first + + SIGPIPE_count_first += 1 + + def catcher_SIGPIPE_second(t, sc): + nonlocal SIGPIPE_count_second + + SIGPIPE_count_second += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE_first) + + bp = d.breakpoint(0x964) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + # Overriding the catcher + if bp.hit_on(d): + self.assertEqual(catcher1.hit_count, 2) + catcher2 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE_second) + d.cont() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(SIGPIPE_count_first, 2) + self.assertEqual(SIGPIPE_count_second, 1) + + self.assertEqual(SIGPIPE_count_first, catcher1.hit_count) + self.assertEqual(SIGPIPE_count_second, catcher2.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 10" * 2) + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 13" * 3) + + self.assertEqual( + self.log_capture_string.getvalue().count("has already been caught. Overriding it."), + 1, + ) + + def test_override_hijack(self): + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.hijack_signal("SIGPIPE", 15) + + bp = d.breakpoint(0x964) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + # Overriding the catcher + if bp.hit_on(d): + self.assertEqual(catcher1.hit_count, 2) + catcher2 = d.hijack_signal("SIGPIPE", "SIGINT") + d.cont() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(catcher1.hit_count, 2) + self.assertEqual(catcher2.hit_count, 1) + + self.assertEqual(SIGUSR1, b"Received signal 10" * 2) + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 15" * 2 + b"Received signal 2") + + self.assertEqual( + self.log_capture_string.getvalue().count("has already been caught. Overriding it."), + 1, + ) + + def test_override_hybrid(self): + SIGPIPE_count = 0 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + SIGPIPE_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.hijack_signal("SIGPIPE", 15) + + bp = d.breakpoint(0x964) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + # Overriding the catcher + if bp.hit_on(d): + self.assertEqual(catcher1.hit_count, 2) + catcher2 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + d.cont() + + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(catcher1.hit_count, 2) + self.assertEqual(catcher2.hit_count, 1) + self.assertEqual(SIGPIPE_count, 1) + + self.assertEqual(SIGUSR1, b"Received signal 10" * 2) + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 3" * 3) + self.assertEqual(SIGPIPE, b"Received signal 15" * 2 + b"Received signal 13") + + self.assertEqual( + self.log_capture_string.getvalue().count("has already been caught. Overriding it."), + 1, + ) + + def test_signal_get_signal(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + self.assertEqual(t.signal, "SIGUSR1") + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + self.assertEqual(t.signal, "SIGTERM") + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + self.assertEqual(t.signal, "SIGINT") + + SIGINT_count += 1 + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + + self.assertEqual(t.signal, "SIGQUIT") + + SIGQUIT_count += 1 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + self.assertEqual(t.signal, "SIGPIPE") + + SIGPIPE_count += 1 + + d = debugger("binaries/catch_signal_test") + + d.signals_to_block = ["SIGUSR1", 15, "SIGINT", 3, 13] + + d.run() + + catcher1 = d.catch_signal(10, callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal(2, callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + d.cont() + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + def test_signal_send_signal(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGTERM_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.hijack_signal("SIGQUIT", "SIGTERM", recursive=True) + catcher5 = d.hijack_signal("SIGPIPE", "SIGTERM", recursive=True) + + bp = d.breakpoint(0x964) + + d.cont() + + SIGUSR1 = r.recvline() + SIGTERM = r.recvline() + SIGINT = r.recvline() + SIGQUIT = r.recvline() + SIGPIPE = r.recvline() + + SIGUSR1 += r.recvline() + SIGTERM += r.recvline() + SIGINT += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + # Uncatchering and send signals + if bp.hit_on(d): + catcher4.disable() + catcher5.disable() + d.signal = 10 + d.cont() + + SIGUSR1 += r.recvline() + SIGQUIT += r.recvline() + SIGPIPE += r.recvline() + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2 + 2 + 2) # 2 times more because of the hijacking * 2 (SIGQUIT and SIGPIPE) + self.assertEqual(SIGINT_count, 2) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + + self.assertEqual(SIGUSR1, b"Received signal 10" * 3) + self.assertEqual(SIGTERM, b"Received signal 15" * 2) + self.assertEqual(SIGINT, b"Received signal 2" * 2) + self.assertEqual(SIGQUIT, b"Received signal 15" * 2 + b"Received signal 3") + self.assertEqual(SIGPIPE, b"Received signal 15" * 2 + b"Received signal 13") + + def test_signal_catch_sync_block(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + d = debugger("binaries/catch_signal_test") + + d.signals_to_block = ["SIGUSR1", 15, "SIGINT", 3, 13] + + d.run() + + catcher1 = d.catch_signal(10) + catcher2 = d.catch_signal("SIGTERM") + catcher3 = d.catch_signal(2) + catcher4 = d.catch_signal("SIGQUIT") + catcher5 = d.catch_signal("SIGPIPE") + + while not d.dead: + d.cont() + d.wait() + if catcher1.hit_on(d): + SIGUSR1_count += 1 + elif catcher2.hit_on(d): + SIGTERM_count += 1 + elif catcher3.hit_on(d): + SIGINT_count += 1 + elif catcher4.hit_on(d): + SIGQUIT_count += 1 + elif catcher5.hit_on(d): + SIGPIPE_count += 1 + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + def test_signal_catch_sync_pass(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + signals = b"" + + d = debugger("binaries/catch_signal_test") + + r = d.run() + + catcher1 = d.catch_signal(10) + catcher2 = d.catch_signal("SIGTERM") + catcher3 = d.catch_signal(2) + catcher4 = d.catch_signal("SIGQUIT") + catcher5 = d.catch_signal("SIGPIPE") + + signals = b"" + while not d.dead: + d.cont() + try: + signals += r.recvline() + except: + pass + d.wait() + if catcher1.hit_on(d): + SIGUSR1_count += 1 + elif catcher2.hit_on(d): + SIGTERM_count += 1 + elif catcher3.hit_on(d): + SIGINT_count += 1 + elif catcher4.hit_on(d): + SIGQUIT_count += 1 + elif catcher5.hit_on(d): + SIGPIPE_count += 1 + + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + self.assertEqual(signals.count(b"Received signal 10"), 2) + self.assertEqual(signals.count(b"Received signal 15"), 2) + self.assertEqual(signals.count(b"Received signal 2"), 2) + self.assertEqual(signals.count(b"Received signal 3"), 3) + self.assertEqual(signals.count(b"Received signal 13"), 3) diff --git a/test/aarch64/scripts/death_test.py b/test/aarch64/scripts/death_test.py new file mode 100644 index 00000000..0a228b54 --- /dev/null +++ b/test/aarch64/scripts/death_test.py @@ -0,0 +1,154 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import io +import logging +import unittest + +from libdebug import debugger + + +class DeathTest(unittest.TestCase): + def setUp(self): + # Redirect logging to a string buffer + self.log_capture_string = io.StringIO() + self.log_handler = logging.StreamHandler(self.log_capture_string) + self.log_handler.setLevel(logging.WARNING) + + self.logger = logging.getLogger("libdebug") + self.original_handlers = self.logger.handlers + self.logger.handlers = [] + self.logger.addHandler(self.log_handler) + self.logger.setLevel(logging.WARNING) + + def tearDown(self): + self.logger.removeHandler(self.log_handler) + self.logger.handlers = self.original_handlers + self.log_handler.close() + + def test_io_death(self): + d = debugger("binaries/segfault_test") + + r = d.run() + + d.cont() + + self.assertEqual(r.recvline(), b"Hello, World!") + self.assertEqual(r.recvline(), b"Death is coming!") + + with self.assertRaises(RuntimeError): + r.recvline() + + d.kill() + + def test_cont_death(self): + d = debugger("binaries/segfault_test") + + r = d.run() + + d.cont() + + self.assertEqual(r.recvline(), b"Hello, World!") + self.assertEqual(r.recvline(), b"Death is coming!") + + d.wait() + + with self.assertRaises(RuntimeError): + d.cont() + + self.assertEqual(d.dead, True) + self.assertEqual(d.threads[0].dead, True) + + d.kill() + + def test_instr_death(self): + d = debugger("binaries/segfault_test") + + r = d.run() + + d.cont() + + self.assertEqual(r.recvline(), b"Hello, World!") + self.assertEqual(r.recvline(), b"Death is coming!") + + d.wait() + + self.assertEqual(d.regs.pc, 0xaaaaaaaa0784) + + d.kill() + + def test_exit_signal_death(self): + d = debugger("binaries/segfault_test") + + r = d.run() + + d.cont() + + self.assertEqual(r.recvline(), b"Hello, World!") + self.assertEqual(r.recvline(), b"Death is coming!") + + d.wait() + + self.assertEqual(d.exit_signal, "SIGSEGV") + self.assertEqual(d.exit_signal, d.threads[0].exit_signal) + + d.kill() + + def test_exit_code_death(self): + d = debugger("binaries/segfault_test") + + r = d.run() + + d.cont() + + self.assertEqual(r.recvline(), b"Hello, World!") + self.assertEqual(r.recvline(), b"Death is coming!") + + d.wait() + + d.exit_code + + self.assertEqual( + self.log_capture_string.getvalue().count("No exit code available."), + 1, + ) + + d.kill() + + def test_exit_code_normal(self): + d = debugger("binaries/basic_test") + + d.run() + + d.cont() + + d.wait() + + self.assertEqual(d.exit_code, 0) + + d.exit_signal + + self.assertEqual( + self.log_capture_string.getvalue().count("No exit signal available."), + 1, + ) + + d.kill() + + def test_post_mortem_after_kill(self): + d = debugger("binaries/basic_test") + + d.run() + + d.cont() + + d.interrupt() + d.kill() + + # We should be able to access the registers also after the process has been killed + d.regs.x0 + d.regs.x1 + d.regs.x2 diff --git a/test/aarch64/scripts/jumpstart_test.py b/test/aarch64/scripts/jumpstart_test.py new file mode 100644 index 00000000..d5e1d7a3 --- /dev/null +++ b/test/aarch64/scripts/jumpstart_test.py @@ -0,0 +1,24 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + +class JumpstartTest(unittest.TestCase): + + def test_cursed_ldpreload(self): + d = debugger("binaries/jumpstart_test", env={"LD_PRELOAD": "binaries/jumpstart_test_preload.so"}) + + r = d.run() + + d.cont() + + self.assertEqual(r.recvline(), b"Preload library loaded") + self.assertEqual(r.recvline(), b"Jumpstart test") + self.assertEqual(r.recvline(), b"execve(/bin/ls, (nil), (nil))") + + d.kill() diff --git a/test/aarch64/scripts/memory_test.py b/test/aarch64/scripts/memory_test.py new file mode 100644 index 00000000..91daa85c --- /dev/null +++ b/test/aarch64/scripts/memory_test.py @@ -0,0 +1,288 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import io +import logging +import sys +import unittest + +from libdebug import debugger, libcontext + + +class MemoryTest(unittest.TestCase): + def setUp(self) -> None: + self.d = debugger("binaries/memory_test") + + # Redirect logging to a string buffer + self.log_capture_string = io.StringIO() + self.log_handler = logging.StreamHandler(self.log_capture_string) + self.log_handler.setLevel(logging.WARNING) + + self.logger = logging.getLogger("libdebug") + self.original_handlers = self.logger.handlers + self.logger.handlers = [] + self.logger.addHandler(self.log_handler) + self.logger.setLevel(logging.WARNING) + + def test_memory(self): + d = self.d + + d.run() + + bp = d.breakpoint("change_memory") + + d.cont() + + assert d.regs.pc == bp.address + + address = d.regs.x0 + prev = bytes(range(256)) + + self.assertTrue(d.memory[address, 256] == prev) + + d.memory[address + 128 :] = b"abcd123456" + prev = prev[:128] + b"abcd123456" + prev[138:] + + self.assertTrue(d.memory[address : address + 256] == prev) + + d.kill() + + def test_mem_access_libs(self): + d = self.d + + d.run() + + bp = d.breakpoint("leak_address") + + d.cont() + + assert d.regs.pc == bp.address + + address = d.regs.x0 + with libcontext.tmp(sym_lvl=5): + arena = d.memory["main_arena", 256, "libc"] + + def p64(x): + return x.to_bytes(8, sys.byteorder) + + self.assertTrue(p64(address - 0x10) in arena) + + d.kill() + + def test_memory_exceptions(self): + d = self.d + + d.run() + + bp = d.breakpoint("change_memory") + + d.cont() + + # This should not raise an exception + file = d.memory[0x0, 256] + + # File should start with ELF magic number + self.assertTrue(file.startswith(b"\x7fELF")) + + assert d.regs.pc == bp.address + + address = d.regs.x0 + prev = bytes(range(256)) + + self.assertTrue(d.memory[address, 256] == prev) + + d.memory[address + 128 :] = b"abcd123456" + prev = prev[:128] + b"abcd123456" + prev[138:] + + self.assertTrue(d.memory[address : address + 256] == prev) + + d.kill() + + def test_memory_multiple_runs(self): + d = self.d + + for _ in range(10): + d.run() + + bp = d.breakpoint("change_memory") + + d.cont() + + assert d.regs.pc == bp.address + + address = d.regs.x0 + prev = bytes(range(256)) + + self.assertTrue(d.memory[address, 256] == prev) + + d.memory[address + 128 :] = b"abcd123456" + prev = prev[:128] + b"abcd123456" + prev[138:] + + self.assertTrue(d.memory[address : address + 256] == prev) + + d.kill() + + def test_memory_access_while_running(self): + d = debugger("binaries/memory_test_2") + + d.run() + + bp = d.breakpoint("do_nothing") + + d.cont() + + # Verify that memory access is only possible when the process is stopped + value = int.from_bytes(d.memory["state", 8], sys.byteorder) + self.assertEqual(value, 0xDEADBEEF) + self.assertEqual(d.regs.pc, bp.address) + + d.kill() + + def test_memory_access_methods(self): + d = debugger("binaries/memory_test_2") + + d.run() + + base = d.regs.pc & 0xFFFFFFFFFFFFF000 + + # Test different ways to access memory at the start of the file + file_0 = d.memory[base, 256] + file_1 = d.memory[0x0, 256] + file_2 = d.memory[0x0:0x100] + + self.assertEqual(file_0, file_1) + self.assertEqual(file_0, file_2) + + # Validate that the length of the read bytes is correct + file_0 = d.memory[0x0] + file_1 = d.memory[base] + + self.assertEqual(file_0, file_1) + self.assertEqual(len(file_0), 1) + + # Validate that slices work correctly + file_0 = d.memory[0x0:"do_nothing"] + file_1 = d.memory[base:"do_nothing"] + + self.assertEqual(file_0, file_1) + + self.assertRaises(ValueError, lambda: d.memory[0x1000:0x0]) + # _fini is after main + self.assertRaises(ValueError, lambda: d.memory["_fini":"main"]) + + # Test different ways to write memory + d.memory[0x0, 8] = b"abcd1234" + self.assertEqual(d.memory[0x0, 8], b"abcd1234") + + d.memory[0x0, 8] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[base:] = b"abcd1234" + self.assertEqual(d.memory[base, 8], b"abcd1234") + + d.memory[base:] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[base] = b"abcd1234" + self.assertEqual(d.memory[base, 8], b"abcd1234") + + d.memory[base] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[0x0:0x8] = b"abcd1234" + self.assertEqual(d.memory[0x0, 8], b"abcd1234") + + d.memory[0x0, 8] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":] = b"abcd1234" + self.assertEqual(d.memory["main", 8], b"abcd1234") + + d.memory["main":] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main"] = b"abcd1234" + self.assertEqual(d.memory["main", 8], b"abcd1234") + + d.memory["main"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":"main+8"] = b"abcd1234" + self.assertEqual(d.memory["main", 8], b"abcd1234") + + d.kill() + + def test_memory_access_methods_backing_file(self): + d = debugger("binaries/memory_test_2") + + d.run() + + base = d.regs.pc & 0xFFFFFFFFFFFFF000 + + # Validate that slices work correctly + file_0 = d.memory[0x0:"do_nothing", "binary"] + file_1 = d.memory[0x0:"do_nothing", "memory_test_2"] + file_2 = d.memory[base:"do_nothing", "binary"] + file_3 = d.memory[base:"do_nothing", "memory_test_2"] + + self.assertEqual(file_0, file_1) + self.assertEqual(file_1, file_2) + self.assertEqual(file_2, file_3) + + # Test different ways to write memory + d.memory[0x0, 8, "binary"] = b"abcd1234" + self.assertEqual(d.memory[0x0, 8, "binary"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[0x0, 8, "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory[0x0, 8, "memory_test_2"], b"abcd1234") + + d.memory[0x0, 8, "memory_test_2"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[0x0:0x8, "binary"] = b"abcd1234" + self.assertEqual(d.memory[0x0:8, "binary"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[0x0:0x8, "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory[0x0:8, "memory_test_2"], b"abcd1234") + + d.memory[0x0, 8, "memory_test_2"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":, "binary"] = b"abcd1234" + self.assertEqual(d.memory["main", 8, "binary"], b"abcd1234") + + d.memory["main":, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":, "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory["main", 8, "binary"], b"abcd1234") + + d.memory["main":, "memory_test_2"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main", "binary"] = b"abcd1234" + self.assertEqual(d.memory["main", 8, "binary"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main", "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory["main", 8, "memory_test_2"], b"abcd1234") + + d.memory[0x0, 8, "memory_test_2"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":"main+8", "binary"] = b"abcd1234" + self.assertEqual(d.memory["main":"main+8", "binary"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":"main+8", "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory["main":"main+8", "memory_test_2"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":"main+8", "hybrid"] = b"abcd1234" + self.assertEqual(d.memory["main":"main+8", "hybrid"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + with self.assertRaises(ValueError): + d.memory["main":"main+8", "absolute"] = b"abcd1234" + + d.kill() diff --git a/test/aarch64/scripts/thread_test.py b/test/aarch64/scripts/thread_test.py new file mode 100644 index 00000000..0a31bc68 --- /dev/null +++ b/test/aarch64/scripts/thread_test.py @@ -0,0 +1,83 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + + +class ThreadTest(unittest.TestCase): + def setUp(self): + pass + + def test_thread(self): + d = debugger("binaries/thread_test") + + d.run() + + bp_t0 = d.breakpoint("do_nothing") + bp_t1 = d.breakpoint("thread_1_function") + bp_t2 = d.breakpoint("thread_2_function") + bp_t3 = d.breakpoint("thread_3_function") + + t1_done, t2_done, t3_done = False, False, False + + d.cont() + + for _ in range(150): + if bp_t0.address == d.regs.pc: + self.assertTrue(t1_done) + self.assertTrue(t2_done) + self.assertTrue(t3_done) + break + + if len(d.threads) > 1 and bp_t1.address == d.threads[1].regs.pc: + t1_done = True + if len(d.threads) > 2 and bp_t2.address == d.threads[2].regs.pc: + t2_done = True + if len(d.threads) > 3 and bp_t3.address == d.threads[3].regs.pc: + t3_done = True + + d.cont() + + d.kill() + d.terminate() + + def test_thread_hardware(self): + d = debugger("binaries/thread_test") + + d.run() + + bp_t0 = d.breakpoint("do_nothing", hardware=True) + bp_t1 = d.breakpoint("thread_1_function", hardware=True) + bp_t2 = d.breakpoint("thread_2_function", hardware=True) + bp_t3 = d.breakpoint("thread_3_function", hardware=True) + + t1_done, t2_done, t3_done = False, False, False + + d.cont() + + for _ in range(15): + if bp_t0.address == d.regs.pc: + self.assertTrue(t1_done) + self.assertTrue(t2_done) + self.assertTrue(t3_done) + break + + if len(d.threads) > 1 and bp_t1.address == d.threads[1].regs.pc: + t1_done = True + if len(d.threads) > 2 and bp_t2.address == d.threads[2].regs.pc: + t2_done = True + if len(d.threads) > 3 and bp_t3.address == d.threads[3].regs.pc: + t3_done = True + + d.cont() + + d.kill() + d.terminate() + +if __name__ == "__main__": + unittest.main() diff --git a/test/aarch64/scripts/thread_test_complex.py b/test/aarch64/scripts/thread_test_complex.py new file mode 100644 index 00000000..786f61d7 --- /dev/null +++ b/test/aarch64/scripts/thread_test_complex.py @@ -0,0 +1,67 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + + +class ThreadTestComplex(unittest.TestCase): + def setUp(self): + pass + + def test_thread(self): + def factorial(n): + if n == 0: + return 1 + else: + return (n * factorial(n - 1)) & (2**32 - 1) + + d = debugger("binaries/thread_test_complex") + + d.run() + + bp1_t0 = d.breakpoint("do_nothing") + bp2_t1 = d.breakpoint("thread_1_function+18") + bp3_t2 = d.breakpoint("thread_2_function+24") + + bp1_hit, bp2_hit, bp3_hit = False, False, False + t1, t2 = None, None + + d.cont() + + while True: + if len(d.threads) == 2: + t1 = d.threads[1] + + if len(d.threads) == 3: + t2 = d.threads[2] + + if t1 and bp2_t1.address == t1.regs.pc: + bp2_hit = True + self.assertTrue(bp2_t1.hit_count == (t1.regs.w0 + 1)) + + if bp1_t0.address == d.regs.pc: + bp1_hit = True + self.assertTrue(bp2_hit) + self.assertEqual(bp2_t1.hit_count, 50) + self.assertFalse(bp3_hit) + self.assertEqual(bp1_t0.hit_count, 1) + + if t2 and bp3_t2.address == t2.regs.pc: + bp3_hit = True + self.assertTrue(factorial(bp3_t2.hit_count) == t2.regs.x0) + self.assertTrue(bp2_hit) + self.assertTrue(bp1_hit) + + d.cont() + + if bp3_t2.hit_count == 49: + break + + d.kill() + d.terminate() + From d5efd97b4656218c5af7c4d72edcd0dd1cda5930 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Tue, 16 Jul 2024 17:07:30 +0200 Subject: [PATCH 047/144] feat: add floating point register support for aarch64 --- .../aarch64/aarch64_ptrace_register_holder.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py index 4faa1fce..281cb33e 100644 --- a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py +++ b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py @@ -6,6 +6,7 @@ from __future__ import annotations +import sys from dataclasses import dataclass from typing import TYPE_CHECKING @@ -35,6 +36,8 @@ def getter(self: Aarch64Registers) -> int: self._internal_debugger._ensure_process_stopped() return getattr(self.register_file, name) & 0xFFFFFFFF + # https://developer.arm.com/documentation/102374/0101/Registers-in-AArch64---general-purpose-registers + # When a W register is written the top 32 bits of the 64-bit register are zeroed. def setter(self: Aarch64Registers, value: int) -> None: self._internal_debugger._ensure_process_stopped() return setattr(self.register_file, name, value & 0xFFFFFFFF) @@ -42,6 +45,76 @@ def setter(self: Aarch64Registers, value: int) -> None: return property(getter, setter, None, name) +def _get_property_fp_8(name: str, index: int) -> property: + def getter(self: Aarch64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) & 0xFF + + def setter(self: Aarch64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + data = value.to_bytes(1, sys.byteorder) + self._internal_debugger._fp_register_file.vregs[index] = data + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_16(name: str, index: int) -> property: + def getter(self: Aarch64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) & 0xFFFF + + def setter(self: Aarch64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + data = value.to_bytes(2, sys.byteorder) + self._internal_debugger._fp_register_file.vregs[index] = data + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_32(name: str, index: int) -> property: + def getter(self: Aarch64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) & 0xFFFFFFFF + + def setter(self: Aarch64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + data = value.to_bytes(4, sys.byteorder) + self._internal_debugger._fp_register_file.vregs[index] = data + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_64(name: str, index: int) -> property: + def getter(self: Aarch64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) + + def setter(self: Aarch64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + data = value.to_bytes(8, sys.byteorder) + self._internal_debugger._fp_register_file.vregs[index] = data + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + +def _get_property_fp_128(name: str, index: int) -> property: + def getter(self: Aarch64Registers) -> int: + self._internal_debugger._fetch_fp_registers(self._thread_id) + return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) + + def setter(self: Aarch64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + data = value.to_bytes(16, sys.byteorder) + self._internal_debugger._fp_register_file.vregs[index] = data + self._internal_debugger._flush_fp_registers(self._thread_id) + + return property(getter, setter, None, name) + + @dataclass class Aarch64PtraceRegisterHolder(PtraceRegisterHolder): """A class that provides views and setters for the register of an aarch64 process.""" @@ -66,6 +139,21 @@ def apply_on_regs(self: Aarch64PtraceRegisterHolder, target: Aarch64Registers, t target_class.pc = _get_property_64("pc") + # setup the floating point registers + for i in range(32): + name_v = f"v{i}" + name_128 = f"q{i}" + name_64 = f"d{i}" + name_32 = f"s{i}" + name_16 = f"h{i}" + name_8 = f"b{i}" + setattr(target_class, name_v, _get_property_fp_128(name_v, i)) + setattr(target_class, name_128, _get_property_fp_128(name_128, i)) + setattr(target_class, name_64, _get_property_fp_64(name_64, i)) + setattr(target_class, name_32, _get_property_fp_32(name_32, i)) + setattr(target_class, name_16, _get_property_fp_16(name_16, i)) + setattr(target_class, name_8, _get_property_fp_8(name_8, i)) + def apply_on_thread(self: Aarch64PtraceRegisterHolder, target: ThreadContext, target_class: type) -> None: """Apply the register accessors to the thread class.""" target.register_file = self.register_file From d4a972348917bc418b0b38b1a3b50e5c3c498f57 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Tue, 16 Jul 2024 17:07:53 +0200 Subject: [PATCH 048/144] test: add remaining tests for aarch64 (not really working yet) --- test/aarch64/run_suite.py | 16 +- test/aarch64/scripts/floating_point_test.py | 72 +++ test/aarch64/scripts/handle_syscall_test.py | 551 ++++++++++++++++++ test/aarch64/scripts/hijack_syscall_test.py | 333 +++++++++++ .../scripts/signals_multithread_test.py | 423 ++++++++++++++ test/aarch64/scripts/speed_test.py | 57 ++ test/aarch64/scripts/watchpoint_test.py | 122 ++++ 7 files changed, 1572 insertions(+), 2 deletions(-) create mode 100644 test/aarch64/scripts/floating_point_test.py create mode 100644 test/aarch64/scripts/handle_syscall_test.py create mode 100644 test/aarch64/scripts/hijack_syscall_test.py create mode 100644 test/aarch64/scripts/signals_multithread_test.py create mode 100644 test/aarch64/scripts/speed_test.py create mode 100644 test/aarch64/scripts/watchpoint_test.py diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index 907b583a..7fd4bb00 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -19,10 +19,16 @@ from scripts.callback_test import CallbackTest from scripts.catch_signal_test import CatchSignalTest from scripts.death_test import DeathTest +from scripts.floating_point_test import FloatingPointTest +from scripts.handle_syscall_test import HandleSyscallTest +from scripts.hijack_syscall_test import HijackSyscallTest from scripts.jumpstart_test import JumpstartTest from scripts.memory_test import MemoryTest -from scripts.thread_test import ThreadTest +from scripts.signals_multithread_test import SignalMultithreadTest +from scripts.speed_test import SpeedTest from scripts.thread_test_complex import ThreadTestComplex +from scripts.thread_test import ThreadTest +from scripts.watchpoint_test import WatchpointTest def fast_suite(): suite = TestSuite() @@ -39,10 +45,16 @@ def fast_suite(): suite.addTest(TestLoader().loadTestsFromTestCase(CallbackTest)) suite.addTest(TestLoader().loadTestsFromTestCase(CatchSignalTest)) suite.addTest(TestLoader().loadTestsFromTestCase(DeathTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(FloatingPointTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(HandleSyscallTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(HijackSyscallTest)) suite.addTest(TestLoader().loadTestsFromTestCase(JumpstartTest)) suite.addTest(TestLoader().loadTestsFromTestCase(MemoryTest)) - suite.addTest(TestLoader().loadTestsFromTestCase(ThreadTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(SignalMultithreadTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(SpeedTest)) suite.addTest(TestLoader().loadTestsFromTestCase(ThreadTestComplex)) + suite.addTest(TestLoader().loadTestsFromTestCase(ThreadTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(WatchpointTest)) return suite diff --git a/test/aarch64/scripts/floating_point_test.py b/test/aarch64/scripts/floating_point_test.py new file mode 100644 index 00000000..2d31adba --- /dev/null +++ b/test/aarch64/scripts/floating_point_test.py @@ -0,0 +1,72 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import sys +import unittest +from random import randint + +from libdebug import debugger + + +class FloatingPointTest(unittest.TestCase): + def test_floating_point_reg_access(self): + d = debugger("binaries/floating_point_test") + + d.run() + + bp1 = d.bp(0x810, file="binary") + bp2 = d.bp(0x844, file="binary") + + d.cont() + + assert bp1.hit_on(d) + + baseval = int.from_bytes(bytes(list(range(16))), sys.byteorder) + + for i in range(32): + assert hasattr(d.regs, f"q{i}") + assert getattr(d.regs, f"q{i}") == baseval + assert getattr(d.regs, f"v{i}") == baseval + assert getattr(d.regs, f"d{i}") == baseval & ((1 << 64) - 1) + assert getattr(d.regs, f"s{i}") == baseval & ((1 << 32) - 1) + assert getattr(d.regs, f"h{i}") == baseval & ((1 << 16) - 1) + assert getattr(d.regs, f"b{i}") == baseval & ((1 << 8) - 1) + baseval = (baseval >> 8) + ((baseval & 255) << 120) + + for i in range(32): + val = randint(0, (1 << 128) - 1) + setattr(d.regs, f"q{i}", val) + assert getattr(d.regs, f"q{i}") == val + assert getattr(d.regs, f"v{i}") == val + + for i in range(32): + val = randint(0, (1 << 64) - 1) + setattr(d.regs, f"d{i}", val) + assert getattr(d.regs, f"d{i}") == val + + for i in range(32): + val = randint(0, (1 << 32) - 1) + setattr(d.regs, f"s{i}", val) + assert getattr(d.regs, f"s{i}") == val + + for i in range(32): + val = randint(0, (1 << 16) - 1) + setattr(d.regs, f"h{i}", val) + assert getattr(d.regs, f"h{i}") == val + + for i in range(32): + val = randint(0, (1 << 8) - 1) + setattr(d.regs, f"b{i}", val) + assert getattr(d.regs, f"b{i}") == val + + d.regs.q0 = 0xdeadbeefdeadbeef + + d.cont() + + assert bp2.hit_on(d) + + d.kill() + d.terminate() \ No newline at end of file diff --git a/test/aarch64/scripts/handle_syscall_test.py b/test/aarch64/scripts/handle_syscall_test.py new file mode 100644 index 00000000..e599c8b6 --- /dev/null +++ b/test/aarch64/scripts/handle_syscall_test.py @@ -0,0 +1,551 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import io +import logging +import os +import sys +import unittest + +from libdebug import debugger + + +class HandleSyscallTest(unittest.TestCase): + def setUp(self): + # Redirect stdout + self.capturedOutput = io.StringIO() + sys.stdout = self.capturedOutput + sys.stderr = self.capturedOutput + + self.log_capture_string = io.StringIO() + self.log_handler = logging.StreamHandler(self.log_capture_string) + self.log_handler.setLevel(logging.WARNING) + + self.logger = logging.getLogger("libdebug") + self.original_handlers = self.logger.handlers + self.logger.handlers = [] + self.logger.addHandler(self.log_handler) + self.logger.setLevel(logging.WARNING) + + def tearDown(self): + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + + self.logger.removeHandler(self.log_handler) + self.logger.handlers = self.original_handlers + self.log_handler.close() + + def test_handles(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + + ptr = 0 + write_count = 0 + + def on_enter_write(d, sh): + nonlocal write_count + + if write_count == 0: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + else: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + + def on_exit_mmap(d, sh): + self.assertTrue(sh.syscall_number == 9) + + nonlocal ptr + + ptr = d.regs.w0 + + def on_enter_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.syscall_arg0, ptr) + + def on_exit_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + + handler1 = d.handle_syscall("write", on_enter_write, None) + handler2 = d.handle_syscall("mmap", None, on_exit_mmap) + handler3 = d.handle_syscall("getcwd", on_enter_getcwd, on_exit_getcwd) + + r.sendline(b"provola") + + d.cont() + + d.kill() + d.terminate() + + self.assertEqual(write_count, 2) + self.assertEqual(handler1.hit_count, 2) + self.assertEqual(handler2.hit_count, 1) + self.assertEqual(handler3.hit_count, 1) + + def test_handles_with_pprint(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + + d.pprint_syscalls = True + + ptr = 0 + write_count = 0 + + def on_enter_write(d, sh): + nonlocal write_count + + if write_count == 0: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + else: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + + def on_exit_mmap(d, sh): + self.assertTrue(sh.syscall_number == 9) + + nonlocal ptr + + ptr = d.regs.w0 + + def on_enter_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.syscall_arg0, ptr) + + def on_exit_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + + handler1 = d.handle_syscall("write", on_enter_write, None) + handler2 = d.handle_syscall("mmap", None, on_exit_mmap) + handler3 = d.handle_syscall("getcwd", on_enter_getcwd, on_exit_getcwd) + + r.sendline(b"provola") + + d.cont() + d.wait() + + d.kill() + d.terminate() + + self.assertEqual(write_count, 2) + self.assertEqual(handler1.hit_count, 2) + self.assertEqual(handler2.hit_count, 1) + self.assertEqual(handler3.hit_count, 1) + + def test_handle_disabling(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + + ptr = 0 + write_count = 0 + + def on_enter_write(d, sh): + nonlocal write_count + + if write_count == 0: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + else: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + + def on_exit_mmap(d, sh): + self.assertTrue(sh.syscall_number == 9) + + nonlocal ptr + + ptr = d.regs.rax + + def on_enter_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.syscall_arg0, ptr) + + def on_exit_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + + handler1 = d.handle_syscall(1, on_enter_write, None) + handler2 = d.handle_syscall(9, None, on_exit_mmap) + handler3 = d.handle_syscall(0x4F, on_enter_getcwd, on_exit_getcwd) + + r.sendline(b"provola") + + d.breakpoint(0x401196) + + d.cont() + + d.wait() + + self.assertEqual(d.regs.rip, 0x401196) + handler1.disable() + + d.cont() + + d.kill() + d.terminate() + + self.assertEqual(write_count, 1) + self.assertEqual(handler1.hit_count, 1) + self.assertEqual(handler2.hit_count, 1) + self.assertEqual(handler3.hit_count, 1) + + def test_handle_disabling_with_pprint(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + + d.pprint_syscalls = True + + ptr = 0 + write_count = 0 + + def on_enter_write(d, sh): + nonlocal write_count + + if write_count == 0: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + else: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + + def on_exit_mmap(d, sh): + self.assertTrue(sh.syscall_number == 9) + + nonlocal ptr + + ptr = d.regs.rax + + def on_enter_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.syscall_arg0, ptr) + + def on_exit_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + + handler1 = d.handle_syscall(1, on_enter_write, None) + handler2 = d.handle_syscall(9, None, on_exit_mmap) + handler3 = d.handle_syscall(0x4F, on_enter_getcwd, on_exit_getcwd) + + r.sendline(b"provola") + + d.breakpoint(0x401196) + + d.cont() + + d.wait() + + self.assertEqual(d.regs.rip, 0x401196) + handler1.disable() + + d.cont() + + d.kill() + d.terminate() + + self.assertEqual(write_count, 1) + self.assertEqual(handler1.hit_count, 1) + self.assertEqual(handler2.hit_count, 1) + self.assertEqual(handler3.hit_count, 1) + + def test_handle_overwrite(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + + ptr = 0 + write_count_first = 0 + write_count_second = 0 + + def on_enter_write_first(d, sh): + nonlocal write_count_first + + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") + self.assertEqual(d.syscall_arg0, 1) + write_count_first += 1 + + def on_enter_write_second(d, sh): + nonlocal write_count_second + + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") + self.assertEqual(d.syscall_arg0, 1) + write_count_second += 1 + + def on_exit_mmap(d, sh): + self.assertTrue(sh.syscall_number == 9) + + nonlocal ptr + + ptr = d.regs.rax + + def on_enter_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.syscall_arg0, ptr) + + def on_exit_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + + handler1_1 = d.handle_syscall(1, on_enter_write_first, None) + handler2 = d.handle_syscall(9, None, on_exit_mmap) + handler3 = d.handle_syscall(0x4F, on_enter_getcwd, on_exit_getcwd) + + r.sendline(b"provola") + + d.breakpoint(0x401196) + + d.cont() + + d.wait() + + self.assertEqual(d.regs.rip, 0x401196) + handler1_2 = d.handle_syscall(1, on_enter_write_second, None) + + d.cont() + + d.kill() + d.terminate() + + self.assertEqual(write_count_first, 1) + self.assertEqual(write_count_second, 1) + self.assertEqual(handler1_1.hit_count, 2) + self.assertEqual(handler1_2.hit_count, 2) + self.assertEqual(handler2.hit_count, 1) + self.assertEqual(handler3.hit_count, 1) + + self.assertIn("WARNING", self.log_capture_string.getvalue()) + self.assertIn( + "Syscall write is already handled by a user-defined handler. Overriding it.", + self.log_capture_string.getvalue(), + ) + + def test_handle_overwrite_with_pprint(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + + d.pprint_syscalls = True + + ptr = 0 + write_count_first = 0 + write_count_second = 0 + + def on_enter_write_first(d, sh): + nonlocal write_count_first + + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") + self.assertEqual(d.syscall_arg0, 1) + write_count_first += 1 + + def on_enter_write_second(d, sh): + nonlocal write_count_second + + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") + self.assertEqual(d.syscall_arg0, 1) + write_count_second += 1 + + def on_exit_mmap(d, sh): + self.assertTrue(sh.syscall_number == 9) + + nonlocal ptr + + ptr = d.regs.rax + + def on_enter_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.syscall_arg0, ptr) + + def on_exit_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + + handler1_1 = d.handle_syscall(1, on_enter_write_first, None) + handler2 = d.handle_syscall(9, None, on_exit_mmap) + handler3 = d.handle_syscall(0x4F, on_enter_getcwd, on_exit_getcwd) + + r.sendline(b"provola") + + d.breakpoint(0x401196) + + d.cont() + + d.wait() + + self.assertEqual(d.regs.rip, 0x401196) + handler1_2 = d.handle_syscall(1, on_enter_write_second, None) + + d.cont() + + d.kill() + d.terminate() + + self.assertEqual(write_count_first, 1) + self.assertEqual(write_count_second, 1) + self.assertEqual(handler1_1.hit_count, 2) + self.assertEqual(handler1_2.hit_count, 2) + self.assertEqual(handler2.hit_count, 1) + self.assertEqual(handler3.hit_count, 1) + + self.assertIn("WARNING", self.log_capture_string.getvalue()) + self.assertIn( + "Syscall write is already handled by a user-defined handler. Overriding it.", + self.log_capture_string.getvalue(), + ) + + + def test_handles_sync(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + + ptr = 0 + write_count = 0 + + def on_enter_write(d, sh): + nonlocal write_count + + if write_count == 0: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + else: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + + def on_exit_mmap(d, sh): + self.assertTrue(sh.syscall_number == 9) + + nonlocal ptr + + ptr = d.regs.rax + + def on_enter_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.syscall_arg0, ptr) + + def on_exit_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + + handler1 = d.handle_syscall("write") + handler2 = d.handle_syscall("mmap") + handler3 = d.handle_syscall("getcwd") + + r.sendline(b"provola") + + while not d.dead: + d.cont() + d.wait() + if handler1.hit_on_enter(d): + on_enter_write(d, handler1) + elif handler2.hit_on_exit(d): + on_exit_mmap(d, handler2) + elif handler3.hit_on_enter(d): + on_enter_getcwd(d, handler3) + elif handler3.hit_on_exit(d): + on_exit_getcwd(d, handler3) + + d.kill() + d.terminate() + + self.assertEqual(write_count, 2) + self.assertEqual(handler1.hit_count, 2) + self.assertEqual(handler2.hit_count, 1) + self.assertEqual(handler3.hit_count, 1) + + def test_handles_sync_with_pprint(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + + ptr = 0 + write_count = 0 + + def on_enter_write(d, sh): + nonlocal write_count + + if write_count == 0: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + else: + self.assertTrue(sh.syscall_number == 1) + self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") + self.assertEqual(d.syscall_arg0, 1) + write_count += 1 + + def on_exit_mmap(d, sh): + self.assertTrue(sh.syscall_number == 9) + + nonlocal ptr + + ptr = d.regs.rax + + def on_enter_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.syscall_arg0, ptr) + + def on_exit_getcwd(d, sh): + self.assertTrue(sh.syscall_number == 0x4F) + self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + + handler1 = d.handle_syscall("write") + handler2 = d.handle_syscall("mmap") + handler3 = d.handle_syscall("getcwd") + + d.pprint_syscalls = True + + r.sendline(b"provola") + + while not d.dead: + d.cont() + d.wait() + if handler1.hit_on_enter(d): + on_enter_write(d, handler1) + elif handler2.hit_on_exit(d): + on_exit_mmap(d, handler2) + elif handler3.hit_on_enter(d): + on_enter_getcwd(d, handler3) + elif handler3.hit_on_exit(d): + on_exit_getcwd(d, handler3) + + d.kill() + d.terminate() + + self.assertEqual(write_count, 2) + self.assertEqual(handler1.hit_count, 2) + self.assertEqual(handler2.hit_count, 1) + self.assertEqual(handler3.hit_count, 1) diff --git a/test/aarch64/scripts/hijack_syscall_test.py b/test/aarch64/scripts/hijack_syscall_test.py new file mode 100644 index 00000000..559169a2 --- /dev/null +++ b/test/aarch64/scripts/hijack_syscall_test.py @@ -0,0 +1,333 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import io +import sys +import unittest + +from libdebug import debugger + + +class HijackSyscallTest(unittest.TestCase): + def setUp(self): + # Redirect stdout + self.capturedOutput = io.StringIO() + sys.stdout = self.capturedOutput + + def tearDown(self): + sys.stdout = sys.__stdout__ + + def test_hijack_syscall(self): + def on_enter_write(d, sh): + nonlocal write_count + + write_count += 1 + + d = debugger("binaries/handle_syscall_test") + + write_count = 0 + r = d.run() + + d.hijack_syscall("getcwd", "write", recursive=True) + + # recursive is on, we expect the write handler to be called three times + handler = d.handle_syscall("write", on_enter=on_enter_write, recursive=True) + + r.sendline(b"provola") + + d.cont() + + d.kill() + + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 3) + + write_count = 0 + r = d.run() + + d.hijack_syscall("getcwd", "write", recursive=False) + + # recursive is off, we expect the write handler to be called only twice + handler = d.handle_syscall("write", on_enter=on_enter_write) + + r.sendline(b"provola") + + d.cont() + + d.kill() + + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 2) + + def test_hijack_syscall_with_pprint(self): + def on_enter_write(d, sh): + nonlocal write_count + + write_count += 1 + + d = debugger("binaries/handle_syscall_test") + + write_count = 0 + r = d.run() + + d.pprint_syscalls = True + d.hijack_syscall("getcwd", "write", recursive=True) + + # recursive is on, we expect the write handler to be called three times + handler = d.handle_syscall("write", on_enter=on_enter_write, recursive=True) + + r.sendline(b"provola") + + d.cont() + + d.kill() + + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 3) + + write_count = 0 + r = d.run() + + d.pprint_syscalls = True + d.hijack_syscall("getcwd", "write", recursive=False) + + # recursive is off, we expect the write handler to be called only twice + handler = d.handle_syscall("write", on_enter=on_enter_write, recursive=False) + + r.sendline(b"provola") + + d.cont() + + d.kill() + + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 2) + + def test_hijack_handle_syscall(self): + def on_enter_write(d, sh): + nonlocal write_count + + write_count += 1 + + def on_enter_getcwd(d, sh): + d.syscall_number = 0x1 + + d = debugger("binaries/handle_syscall_test") + + write_count = 0 + r = d.run() + + d.handle_syscall("getcwd", on_enter=on_enter_getcwd, recursive=True) + + # recursive is on, we expect the write handler to be called three times + handler = d.handle_syscall("write", on_enter=on_enter_write) + + r.sendline(b"provola") + + d.cont() + + d.kill() + + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 3) + + write_count = 0 + r = d.run() + + d.handle_syscall("getcwd", on_enter=on_enter_getcwd, recursive=False) + + # recursive is off, we expect the write handler to be called only twice + handler = d.handle_syscall("write", on_enter=on_enter_write) + + r.sendline(b"provola") + + d.cont() + + d.kill() + + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 2) + + def test_hijack_handle_syscall_with_pprint(self): + def on_enter_write(d, sh): + nonlocal write_count + + write_count += 1 + + def on_enter_getcwd(d, sh): + d.syscall_number = 0x1 + + d = debugger("binaries/handle_syscall_test") + + write_count = 0 + r = d.run() + + d.pprint_syscalls = True + d.handle_syscall("getcwd", on_enter=on_enter_getcwd, recursive=True) + + # recursive hijack is on, we expect the write handler to be called three times + handler = d.handle_syscall("write", on_enter=on_enter_write, recursive=True) + + r.sendline(b"provola") + + d.cont() + + d.kill() + + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 3) + + write_count = 0 + r = d.run() + + d.pprint_syscalls = True + d.handle_syscall("getcwd", on_enter=on_enter_getcwd, recursive=False) + + # recursive is off, we expect the write handler to be called only twice + handler = d.handle_syscall("write", on_enter=on_enter_write) + + r.sendline(b"provola") + + d.cont() + + d.kill() + + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 2) + + def test_hijack_syscall_args(self): + write_buffer = None + + def on_enter_write(d, sh): + nonlocal write_buffer + nonlocal write_count + + write_buffer = d.syscall_arg1 + + write_count += 1 + + d = debugger("binaries/handle_syscall_test") + + write_count = 0 + r = d.run() + + # recursive hijack is on, we expect the write handler to be called three times + handler = d.handle_syscall("write", on_enter=on_enter_write, recursive=True) + d.breakpoint(0x4011B0) + + d.cont() + print(r.recvline()) + # Install the hijack. We expect to receive the "Hello, World!" string + + d.wait() + + d.hijack_syscall( + "read", + "write", + syscall_arg0=0x1, + syscall_arg1=write_buffer, + syscall_arg2=14, + recursive=True, + ) + + d.cont() + + print(r.recvline()) + + d.kill() + + self.assertEqual(self.capturedOutput.getvalue().count("Hello, World!"), 2) + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 3) + + def test_hijack_syscall_args_with_pprint(self): + write_buffer = None + + def on_enter_write(d, sh): + nonlocal write_buffer + nonlocal write_count + + write_buffer = d.syscall_arg1 + + write_count += 1 + + d = debugger("binaries/handle_syscall_test") + + write_count = 0 + r = d.run() + + d.pprint_syscalls = True + + # recursive hijack is on, we expect the write handler to be called three times + handler = d.handle_syscall("write", on_enter=on_enter_write, recursive=True) + d.breakpoint(0x4011B0) + + d.cont() + print(r.recvline()) + # Install the hijack. We expect to receive the "Hello, World!" string + + d.wait() + + d.hijack_syscall( + "read", + "write", + syscall_arg0=0x1, + syscall_arg1=write_buffer, + syscall_arg2=14, + recursive=True, + ) + + d.cont() + + print(r.recvline()) + + d.kill() + + self.assertEqual(self.capturedOutput.getvalue().count("Hello, World!"), 2) + self.assertEqual(self.capturedOutput.getvalue().count("write"), 3) + self.assertEqual(self.capturedOutput.getvalue().count("0x402010"), 3) + self.assertEqual(write_count, handler.hit_count) + self.assertEqual(handler.hit_count, 3) + + def test_hijack_syscall_wrong_args(self): + d = debugger("binaries/handle_syscall_test") + + d.run() + + with self.assertRaises(ValueError): + d.hijack_syscall("read", "write", syscall_arg26=0x1) + + d.kill() + + def loop_detection_test(self): + d = debugger("binaries/handle_syscall_test") + + r = d.run() + d.hijack_syscall("getcwd", "write", recursive=True) + d.hijack_syscall("write", "getcwd", recursive=True) + r.sendline(b"provola") + + # We expect an exception to be raised + with self.assertRaises(RuntimeError): + d.cont() + d.wait() + d.kill() + + r = d.run() + d.hijack_syscall("getcwd", "write", recursive=False) + d.hijack_syscall("write", "getcwd", recursive=True) + r.sendline(b"provola") + + # We expect no exception to be raised + d.cont() + + r = d.run() + d.hijack_syscall("getcwd", "write", recursive=True) + d.hijack_syscall("write", "getcwd", recursive=False) + r.sendline(b"provola") + + # We expect no exception to be raised + d.cont() diff --git a/test/aarch64/scripts/signals_multithread_test.py b/test/aarch64/scripts/signals_multithread_test.py new file mode 100644 index 00000000..e42a481c --- /dev/null +++ b/test/aarch64/scripts/signals_multithread_test.py @@ -0,0 +1,423 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + + +class SignalMultithreadTest(unittest.TestCase): + def test_signal_multithread_undet_catch_signal_block(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + + SIGQUIT_count += 1 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + SIGPIPE_count += 1 + + d = debugger("binaries/signals_multithread_undet_test") + + r = d.run() + + catcher1 = d.catch_signal(10, callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal(2, callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + d.signals_to_block = ["SIGUSR1", 15, "SIGINT", 3, 13] + + d.cont() + + r.sendline(b"sync") + r.sendline(b"sync") + + # Receive the exit message + r.recvline(2) + + d.kill() + + self.assertEqual(SIGUSR1_count, 4) + self.assertEqual(SIGTERM_count, 4) + self.assertEqual(SIGINT_count, 4) + self.assertEqual(SIGQUIT_count, 6) + self.assertEqual(SIGPIPE_count, 6) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + def test_signal_multithread_undet_pass(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + + SIGUSR1_count += 1 + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + + SIGTERM_count += 1 + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + + SIGINT_count += 1 + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + + SIGQUIT_count += 1 + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + + SIGPIPE_count += 1 + + d = debugger("binaries/signals_multithread_undet_test") + + r = d.run() + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + d.cont() + + received = [] + for _ in range(24): + received.append(r.recvline()) + + r.sendline(b"sync") + r.sendline(b"sync") + + received.append(r.recvline()) + received.append(r.recvline()) + + d.kill() + + self.assertEqual(SIGUSR1_count, 4) + self.assertEqual(SIGTERM_count, 4) + self.assertEqual(SIGINT_count, 4) + self.assertEqual(SIGQUIT_count, 6) + self.assertEqual(SIGPIPE_count, 6) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + # Count the number of times each signal was received + self.assertEqual(received.count(b"Received signal 10"), 4) + self.assertEqual(received.count(b"Received signal 15"), 4) + self.assertEqual(received.count(b"Received signal 2"), 4) + self.assertEqual(received.count(b"Received signal 3"), 6) + self.assertEqual(received.count(b"Received signal 13"), 6) + # Note: sometimes the signals are passed to ptrace once and received twice + # Maybe another ptrace/kernel/whatever problem in multithreaded programs (?) + # Using raise(sig) instead of kill(pid, sig) to send signals in the original + # program seems to mitigate the problem for whatever reason + # I will investigate this further in the future, but for now this is fine + + def test_signal_multithread_det_catch_signal_block(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + tids = [] + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + nonlocal tids + + SIGUSR1_count += 1 + tids.append(t.thread_id) + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + nonlocal tids + + SIGTERM_count += 1 + tids.append(t.thread_id) + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + nonlocal tids + + SIGINT_count += 1 + tids.append(t.thread_id) + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + nonlocal tids + + SIGQUIT_count += 1 + tids.append(t.thread_id) + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + nonlocal tids + + SIGPIPE_count += 1 + tids.append(t.thread_id) + + d = debugger("binaries/signals_multithread_det_test") + + r = d.run() + + catcher1 = d.catch_signal(10, callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal(2, callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + d.signals_to_block = ["SIGUSR1", 15, "SIGINT", 3, 13] + + d.cont() + + # Receive the exit message + r.recvline(timeout=15) + r.sendline(b"sync") + r.recvline() + + receiver = d.threads[1].thread_id + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + set_tids = set(tids) + self.assertEqual(len(set_tids), 1) + self.assertEqual(set_tids.pop(), receiver) + + def test_signal_multithread_det_pass(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + tids = [] + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + nonlocal tids + + SIGUSR1_count += 1 + tids.append(t.thread_id) + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + nonlocal tids + + SIGTERM_count += 1 + tids.append(t.thread_id) + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + nonlocal tids + + SIGINT_count += 1 + tids.append(t.thread_id) + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + nonlocal tids + + SIGQUIT_count += 1 + tids.append(t.thread_id) + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + nonlocal tids + + SIGPIPE_count += 1 + tids.append(t.thread_id) + + d = debugger("binaries/signals_multithread_det_test") + + r = d.run() + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + d.cont() + + received = [] + for _ in range(13): + received.append(r.recvline(timeout=5)) + + r.sendline(b"sync") + received.append(r.recvline(timeout=5)) + + receiver = d.threads[1].thread_id + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + # Count the number of times each signal was received + self.assertEqual(received.count(b"Received signal on receiver 10"), 2) + self.assertEqual(received.count(b"Received signal on receiver 15"), 2) + self.assertEqual(received.count(b"Received signal on receiver 2"), 2) + self.assertEqual(received.count(b"Received signal on receiver 3"), 3) + self.assertEqual(received.count(b"Received signal on receiver 13"), 3) + + set_tids = set(tids) + self.assertEqual(len(set_tids), 1) + self.assertEqual(set_tids.pop(), receiver) + + def test_signal_multithread_send_signal(self): + SIGUSR1_count = 0 + SIGINT_count = 0 + SIGQUIT_count = 0 + SIGTERM_count = 0 + SIGPIPE_count = 0 + tids = [] + + def catcher_SIGUSR1(t, sc): + nonlocal SIGUSR1_count + nonlocal tids + + SIGUSR1_count += 1 + tids.append(t.thread_id) + + def catcher_SIGTERM(t, sc): + nonlocal SIGTERM_count + nonlocal tids + + SIGTERM_count += 1 + tids.append(t.thread_id) + + def catcher_SIGINT(t, sc): + nonlocal SIGINT_count + nonlocal tids + + SIGINT_count += 1 + tids.append(t.thread_id) + + def catcher_SIGQUIT(t, sc): + nonlocal SIGQUIT_count + nonlocal tids + + SIGQUIT_count += 1 + tids.append(t.thread_id) + + def catcher_SIGPIPE(t, sc): + nonlocal SIGPIPE_count + nonlocal tids + + SIGPIPE_count += 1 + tids.append(t.thread_id) + + d = debugger("binaries/signals_multithread_det_test") + + # Set a breakpoint to stop the program before the end of the receiver thread + r = d.run() + + bp = d.breakpoint(0x14d8, hardware=True, file="binary") + + catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) + catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) + catcher3 = d.catch_signal("SIGINT", callback=catcher_SIGINT) + catcher4 = d.catch_signal("SIGQUIT", callback=catcher_SIGQUIT) + catcher5 = d.catch_signal("SIGPIPE", callback=catcher_SIGPIPE) + + d.cont() + + received = [] + for _ in range(13): + received.append(r.recvline(timeout=5)) + + r.sendline(b"sync") + + d.wait() + if bp.hit_on(d.threads[1]): + d.threads[1].signal = "SIGUSR1" + d.cont() + received.append(r.recvline(timeout=5)) + received.append(r.recvline(timeout=5)) + + receiver = d.threads[1].thread_id + d.kill() + + self.assertEqual(SIGUSR1_count, 2) + self.assertEqual(SIGTERM_count, 2) + self.assertEqual(SIGINT_count, 2) + self.assertEqual(SIGQUIT_count, 3) + self.assertEqual(SIGPIPE_count, 3) + + self.assertEqual(SIGUSR1_count, catcher1.hit_count) + self.assertEqual(SIGTERM_count, catcher2.hit_count) + self.assertEqual(SIGINT_count, catcher3.hit_count) + self.assertEqual(SIGQUIT_count, catcher4.hit_count) + self.assertEqual(SIGPIPE_count, catcher5.hit_count) + + # Count the number of times each signal was received + self.assertEqual(received.count(b"Received signal on receiver 10"), 3) + self.assertEqual(received.count(b"Received signal on receiver 15"), 2) + self.assertEqual(received.count(b"Received signal on receiver 2"), 2) + self.assertEqual(received.count(b"Received signal on receiver 3"), 3) + self.assertEqual(received.count(b"Received signal on receiver 13"), 3) + + set_tids = set(tids) + self.assertEqual(len(set_tids), 1) + self.assertEqual(set_tids.pop(), receiver) diff --git a/test/aarch64/scripts/speed_test.py b/test/aarch64/scripts/speed_test.py new file mode 100644 index 00000000..c86bf708 --- /dev/null +++ b/test/aarch64/scripts/speed_test.py @@ -0,0 +1,57 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest +from time import perf_counter_ns + +from libdebug import debugger + + +class SpeedTest(unittest.TestCase): + def setUp(self): + self.d = debugger("binaries/speed_test") + + def test_speed(self): + d = self.d + + start_time = perf_counter_ns() + + d.run() + + bp = d.breakpoint("do_nothing") + + d.cont() + + for _ in range(65536): + self.assertTrue(bp.address == d.regs.pc) + d.cont() + + d.kill() + + end_time = perf_counter_ns() + + self.assertTrue((end_time - start_time) < 15 * 1e9) # 15 seconds + + def test_speed_hardware(self): + d = self.d + + start_time = perf_counter_ns() + + d.run() + + bp = d.breakpoint("do_nothing", hardware=True) + + d.cont() + + for _ in range(65536): + self.assertTrue(bp.address == d.regs.pc) + d.cont() + + d.kill() + + end_time = perf_counter_ns() + + self.assertTrue((end_time - start_time) < 15 * 1e9) # 15 seconds diff --git a/test/aarch64/scripts/watchpoint_test.py b/test/aarch64/scripts/watchpoint_test.py new file mode 100644 index 00000000..ceece1bf --- /dev/null +++ b/test/aarch64/scripts/watchpoint_test.py @@ -0,0 +1,122 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2023-2024 Francesco Panebianco, Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + + +class WatchpointTest(unittest.TestCase): + def test_watchpoint(self): + d = debugger("binaries/watchpoint_test", auto_interrupt_on_command=False) + + d.run() + + d.breakpoint("global_char", hardware=True, condition="rw", length=1) + d.breakpoint("global_int", hardware=True, condition="w", length=4) + d.breakpoint("global_long", hardware=True, condition="rw", length=8) + + d.cont() + + self.assertEqual(d.regs.rip, 0x401111) # mov byte ptr [global_char], 0x1 + + d.cont() + + self.assertEqual(d.regs.rip, 0x401124) # mov dword ptr [global_int], 0x4050607 + + d.cont() + + self.assertEqual( + d.regs.rip, 0x401135 + ) # mov qword ptr [global_long], 0x8090a0b0c0d0e0f + + d.cont() + + self.assertEqual(d.regs.rip, 0x401155) # movzx eax, byte ptr [global_char] + + d.cont() + + self.assertEqual(d.regs.rip, 0x401173) # mov rax, qword ptr [global_long] + + d.cont() + + d.kill() + + def test_watchpoint_callback(self): + global_char_ip = [] + global_int_ip = [] + global_long_ip = [] + + def watchpoint_global_char(t, b): + nonlocal global_char_ip + + global_char_ip.append(t.regs.rip) + + def watchpoint_global_int(t, b): + nonlocal global_int_ip + + global_int_ip.append(t.regs.rip) + + def watchpoint_global_long(t, b): + nonlocal global_long_ip + + global_long_ip.append(t.regs.rip) + + d = debugger("binaries/watchpoint_test", auto_interrupt_on_command=False) + + d.run() + + wp1 = d.breakpoint( + "global_char", + hardware=True, + condition="rw", + length=1, + callback=watchpoint_global_char, + ) + wp2 = d.breakpoint( + "global_int", + hardware=True, + condition="w", + length=4, + callback=watchpoint_global_int, + ) + wp3 = d.breakpoint( + "global_long", + hardware=True, + condition="rw", + length=8, + callback=watchpoint_global_long, + ) + + d.cont() + + d.kill() + + self.assertEqual(global_char_ip[0], 0x401111) # mov byte ptr [global_char], 0x1 + self.assertEqual( + global_int_ip[0], 0x401124 + ) # mov dword ptr [global_int], 0x4050607 + self.assertEqual( + global_long_ip[0], 0x401135 + ) # mov qword ptr [global_long], 0x8090a0b0c0d0e0f + self.assertEqual( + global_char_ip[1], 0x401155 + ) # movzx eax, byte ptr [global_char] + self.assertEqual( + global_long_ip[1], 0x401173 + ) # mov rax, qword ptr [global_long] + + self.assertEqual(len(global_char_ip), 2) + self.assertEqual(len(global_int_ip), 1) + + # There is one extra hit performed by the exit routine of libc + self.assertEqual(len(global_long_ip), 3) + + self.assertEqual(wp1.hit_count, 2) + self.assertEqual(wp2.hit_count, 1) + + # There is one extra hit performed by the exit routine of libc + self.assertEqual(wp3.hit_count, 3) From f2eb4578eff9ea3dea2c5e395de3d99ff50d1ae1 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 16 Jul 2024 17:20:14 +0200 Subject: [PATCH 049/144] fix: add missing __init__.py --- libdebug/architectures/aarch64/__init__,py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 libdebug/architectures/aarch64/__init__,py diff --git a/libdebug/architectures/aarch64/__init__,py b/libdebug/architectures/aarch64/__init__,py new file mode 100644 index 00000000..e69de29b From acdfb341def3e357270a361081ffbc1f583d8089 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 16 Jul 2024 17:28:19 +0200 Subject: [PATCH 050/144] fix: reintroduce jumpstart test preloads They never got re-committed due to the .gitignore --- test/aarch64/binaries/jumpstart_test_preload.so | Bin 0 -> 69784 bytes test/amd64/binaries/jumpstart_test_preload.so | Bin 0 -> 15944 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100755 test/aarch64/binaries/jumpstart_test_preload.so create mode 100755 test/amd64/binaries/jumpstart_test_preload.so diff --git a/test/aarch64/binaries/jumpstart_test_preload.so b/test/aarch64/binaries/jumpstart_test_preload.so new file mode 100755 index 0000000000000000000000000000000000000000..05c57d6b97a9216adcf1e62210d5e84af808c7c1 GIT binary patch literal 69784 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 c5ASM6QW@*}cQiZ~F>>b++-d$^U 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 literal 0 HcmV?d00001 From 3c8ab048998e9d389b055436f0bd14de504d4ec7 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 16 Jul 2024 18:15:35 +0200 Subject: [PATCH 051/144] fix: make fp regs access actually work for aarch64 --- .../aarch64/aarch64_ptrace_register_holder.py | 21 +++++++------- libdebug/cffi/ptrace_cffi_build.py | 2 +- test/aarch64/scripts/floating_point_test.py | 28 +++++++++++++------ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py index 281cb33e..bfd2dc5a 100644 --- a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py +++ b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py @@ -48,12 +48,12 @@ def setter(self: Aarch64Registers, value: int) -> None: def _get_property_fp_8(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: self._internal_debugger._fetch_fp_registers(self._thread_id) - return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) & 0xFF + return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) & 0xFF def setter(self: Aarch64Registers, value: int) -> None: self._internal_debugger._ensure_process_stopped() data = value.to_bytes(1, sys.byteorder) - self._internal_debugger._fp_register_file.vregs[index] = data + self._fp_register_file.vregs[index].data = data self._internal_debugger._flush_fp_registers(self._thread_id) return property(getter, setter, None, name) @@ -62,12 +62,12 @@ def setter(self: Aarch64Registers, value: int) -> None: def _get_property_fp_16(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: self._internal_debugger._fetch_fp_registers(self._thread_id) - return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) & 0xFFFF + return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) & 0xFFFF def setter(self: Aarch64Registers, value: int) -> None: self._internal_debugger._ensure_process_stopped() data = value.to_bytes(2, sys.byteorder) - self._internal_debugger._fp_register_file.vregs[index] = data + self._fp_register_file.vregs[index].data = data self._internal_debugger._flush_fp_registers(self._thread_id) return property(getter, setter, None, name) @@ -76,12 +76,12 @@ def setter(self: Aarch64Registers, value: int) -> None: def _get_property_fp_32(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: self._internal_debugger._fetch_fp_registers(self._thread_id) - return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) & 0xFFFFFFFF + return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) & 0xFFFFFFFF def setter(self: Aarch64Registers, value: int) -> None: self._internal_debugger._ensure_process_stopped() data = value.to_bytes(4, sys.byteorder) - self._internal_debugger._fp_register_file.vregs[index] = data + self._fp_register_file.vregs[index].data = data self._internal_debugger._flush_fp_registers(self._thread_id) return property(getter, setter, None, name) @@ -90,12 +90,12 @@ def setter(self: Aarch64Registers, value: int) -> None: def _get_property_fp_64(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: self._internal_debugger._fetch_fp_registers(self._thread_id) - return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) + return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) & 0xFFFFFFFFFFFFFFFF def setter(self: Aarch64Registers, value: int) -> None: self._internal_debugger._ensure_process_stopped() data = value.to_bytes(8, sys.byteorder) - self._internal_debugger._fp_register_file.vregs[index] = data + self._fp_register_file.vregs[index].data = data self._internal_debugger._flush_fp_registers(self._thread_id) return property(getter, setter, None, name) @@ -104,12 +104,12 @@ def setter(self: Aarch64Registers, value: int) -> None: def _get_property_fp_128(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: self._internal_debugger._fetch_fp_registers(self._thread_id) - return int.from_bytes(self._internal_debugger._fp_register_file.vregs[index], sys.byteorder) + return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) def setter(self: Aarch64Registers, value: int) -> None: self._internal_debugger._ensure_process_stopped() data = value.to_bytes(16, sys.byteorder) - self._internal_debugger._fp_register_file.vregs[index] = data + self._fp_register_file.vregs[index].data = data self._internal_debugger._flush_fp_registers(self._thread_id) return property(getter, setter, None, name) @@ -126,6 +126,7 @@ def provide_regs_class(self: Aarch64PtraceRegisterHolder) -> type: def apply_on_regs(self: Aarch64PtraceRegisterHolder, target: Aarch64Registers, target_class: type) -> None: """Apply the register accessors to the Aarch64Registers class.""" target.register_file = self.register_file + target._fp_register_file = self.fp_register_file if hasattr(target_class, "w0"): return diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 244caa2b..b5cf298f 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -198,7 +198,7 @@ fp_regs_struct = """ struct reg_128 { - unsigned long s_0, s_1; + unsigned char data[16]; }; // /usr/include/aarch64-linux-gnu/asm/ptrace.h diff --git a/test/aarch64/scripts/floating_point_test.py b/test/aarch64/scripts/floating_point_test.py index 2d31adba..770c67b6 100644 --- a/test/aarch64/scripts/floating_point_test.py +++ b/test/aarch64/scripts/floating_point_test.py @@ -17,8 +17,8 @@ def test_floating_point_reg_access(self): d.run() - bp1 = d.bp(0x810, file="binary") - bp2 = d.bp(0x844, file="binary") + bp1 = d.bp(0xb10, file="binary") + bp2 = d.bp(0xb44, file="binary") d.cont() @@ -26,14 +26,26 @@ def test_floating_point_reg_access(self): baseval = int.from_bytes(bytes(list(range(16))), sys.byteorder) - for i in range(32): + for i in range(16): + assert hasattr(d.regs, f"q{i}") + assert getattr(d.regs, f"q{i}") == baseval + assert getattr(d.regs, f"v{i}") == baseval + assert getattr(d.regs, f"d{i}") == baseval & 0xFFFFFFFFFFFFFFFF + assert getattr(d.regs, f"s{i}") == baseval & 0xFFFFFFFF + assert getattr(d.regs, f"h{i}") == baseval & 0xFFFF + assert getattr(d.regs, f"b{i}") == baseval & 0xFF + baseval = (baseval >> 8) + ((baseval & 255) << 120) + + baseval = int.from_bytes(bytes(list(range(128, 128 + 16, 1))), sys.byteorder) + + for i in range(16, 32, 1): assert hasattr(d.regs, f"q{i}") assert getattr(d.regs, f"q{i}") == baseval assert getattr(d.regs, f"v{i}") == baseval - assert getattr(d.regs, f"d{i}") == baseval & ((1 << 64) - 1) - assert getattr(d.regs, f"s{i}") == baseval & ((1 << 32) - 1) - assert getattr(d.regs, f"h{i}") == baseval & ((1 << 16) - 1) - assert getattr(d.regs, f"b{i}") == baseval & ((1 << 8) - 1) + assert getattr(d.regs, f"d{i}") == baseval & 0xFFFFFFFFFFFFFFFF + assert getattr(d.regs, f"s{i}") == baseval & 0xFFFFFFFF + assert getattr(d.regs, f"h{i}") == baseval & 0xFFFF + assert getattr(d.regs, f"b{i}") == baseval & 0xFF baseval = (baseval >> 8) + ((baseval & 255) << 120) for i in range(32): @@ -69,4 +81,4 @@ def test_floating_point_reg_access(self): assert bp2.hit_on(d) d.kill() - d.terminate() \ No newline at end of file + d.terminate() From aa25cac7b3982cd4c855f462d23f9839539bb3f7 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 16 Jul 2024 18:51:32 +0200 Subject: [PATCH 052/144] feat: make SyscallHijacker universal --- ...yscall_hijacker.py => syscall_hijacker.py} | 10 +++--- .../syscall_hijacking_manager.py | 31 ------------------- .../syscall_hijacking_provider.py | 21 ------------- libdebug/debugger/internal_debugger.py | 6 ++-- 4 files changed, 7 insertions(+), 61 deletions(-) rename libdebug/architectures/{amd64/amd64_syscall_hijacker.py => syscall_hijacker.py} (89%) delete mode 100644 libdebug/architectures/syscall_hijacking_manager.py delete mode 100644 libdebug/architectures/syscall_hijacking_provider.py diff --git a/libdebug/architectures/amd64/amd64_syscall_hijacker.py b/libdebug/architectures/syscall_hijacker.py similarity index 89% rename from libdebug/architectures/amd64/amd64_syscall_hijacker.py rename to libdebug/architectures/syscall_hijacker.py index bea4ef4f..99a4f4b6 100644 --- a/libdebug/architectures/amd64/amd64_syscall_hijacker.py +++ b/libdebug/architectures/syscall_hijacker.py @@ -1,6 +1,6 @@ # # This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2024 Gabriele Digregorio. All rights reserved. +# Copyright (c) 2024 Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for details. # @@ -8,15 +8,13 @@ from typing import TYPE_CHECKING -from libdebug.architectures.syscall_hijacking_manager import SyscallHijackingManager - if TYPE_CHECKING: from collections.abc import Callable from libdebug.state.thread_context import ThreadContext -class Amd64SyscallHijacker(SyscallHijackingManager): +class SyscallHijacker: """Class that provides syscall hijacking for the x86_64 architecture.""" # Allowed arguments for the hijacker @@ -33,7 +31,7 @@ class Amd64SyscallHijacker(SyscallHijackingManager): ) def create_hijacker( - self: Amd64SyscallHijacker, + self: SyscallHijacker, new_syscall: int, **kwargs: int, ) -> Callable[[ThreadContext, int], None]: @@ -51,7 +49,7 @@ def hijack_on_enter_wrapper(d: ThreadContext, _: int) -> None: return hijack_on_enter_wrapper def _hijack_on_enter( - self: Amd64SyscallHijacker, + self: SyscallHijacker, d: ThreadContext, new_syscall: int, **kwargs: int, diff --git a/libdebug/architectures/syscall_hijacking_manager.py b/libdebug/architectures/syscall_hijacking_manager.py deleted file mode 100644 index 697a3f5f..00000000 --- a/libdebug/architectures/syscall_hijacking_manager.py +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2024 Gabriele Digregorio. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Callable - - from libdebug.state.thread_context import ThreadContext - - -class SyscallHijackingManager(ABC): - """An architecture-independent interface for syscall hijacking.""" - - @abstractmethod - def create_hijacker( - self: SyscallHijackingManager, - new_syscall: int, - **kwargs: int, - ) -> Callable[[ThreadContext, int], None]: - """Create a new hijacker for the given syscall.""" - - @abstractmethod - def _hijack_on_enter(self: SyscallHijackingManager, d: ThreadContext, new_syscall: int, **kwargs: int) -> None: - """Hijack the syscall on enter.""" diff --git a/libdebug/architectures/syscall_hijacking_provider.py b/libdebug/architectures/syscall_hijacking_provider.py deleted file mode 100644 index 3dfafabc..00000000 --- a/libdebug/architectures/syscall_hijacking_provider.py +++ /dev/null @@ -1,21 +0,0 @@ -# -# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2024 Gabriele Digregorio. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -from libdebug.architectures.amd64.amd64_syscall_hijacker import ( - Amd64SyscallHijacker, -) -from libdebug.architectures.syscall_hijacking_manager import SyscallHijackingManager - -_amd64_syscall_hijacker = Amd64SyscallHijacker() - - -def syscall_hijacking_provider(architecture: str) -> SyscallHijackingManager: - """Returns an instance of the syscall hijacking provider to be used by the `_InternalDebugger` class.""" - match architecture: - case "amd64": - return _amd64_syscall_hijacker - case _: - raise NotImplementedError(f"Architecture {architecture} not available.") diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index f60dff9e..194b4213 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -20,7 +20,7 @@ import psutil -from libdebug.architectures.syscall_hijacking_provider import syscall_hijacking_provider +from libdebug.architectures.syscall_hijacker import SyscallHijacker from libdebug.builtin.antidebug_syscall_handler import on_enter_ptrace, on_exit_ptrace from libdebug.builtin.pretty_print_syscall_handler import pprint_on_enter, pprint_on_exit from libdebug.data.breakpoint import Breakpoint @@ -637,7 +637,7 @@ def hijack_syscall( Returns: HandledSyscall: The HandledSyscall object. """ - if set(kwargs) - syscall_hijacking_provider(self.arch).allowed_args: + if set(kwargs) - SyscallHijacker.allowed_args: raise ValueError("Invalid keyword arguments in syscall hijack") if isinstance(original_syscall, str): @@ -654,7 +654,7 @@ def hijack_syscall( "The original syscall and the new syscall must be different during hijacking.", ) - on_enter = syscall_hijacking_provider(self.arch).create_hijacker( + on_enter = SyscallHijacker().create_hijacker( new_syscall_number, **kwargs, ) From 7a0028780fae4d9a2051cbdfe20a4f844cf4f2ac Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 16 Jul 2024 18:51:52 +0200 Subject: [PATCH 053/144] test: make handle_syscall_test work for aarch64 --- test/aarch64/scripts/handle_syscall_test.py | 157 ++++++++++---------- 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/test/aarch64/scripts/handle_syscall_test.py b/test/aarch64/scripts/handle_syscall_test.py index e599c8b6..9ca32a1d 100644 --- a/test/aarch64/scripts/handle_syscall_test.py +++ b/test/aarch64/scripts/handle_syscall_test.py @@ -50,30 +50,30 @@ def on_enter_write(d, sh): nonlocal write_count if write_count == 0: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") self.assertEqual(d.syscall_arg0, 1) write_count += 1 else: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") self.assertEqual(d.syscall_arg0, 1) write_count += 1 def on_exit_mmap(d, sh): - self.assertTrue(sh.syscall_number == 9) + self.assertTrue(sh.syscall_number == 222) nonlocal ptr - ptr = d.regs.w0 + ptr = d.regs.x0 def on_enter_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) + self.assertTrue(sh.syscall_number == 17) self.assertEqual(d.syscall_arg0, ptr) def on_exit_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) - self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + self.assertTrue(sh.syscall_number == 17) + self.assertEqual(d.memory[ptr, 8], os.getcwd()[:8].encode()) handler1 = d.handle_syscall("write", on_enter_write, None) handler2 = d.handle_syscall("mmap", None, on_exit_mmap) @@ -82,6 +82,7 @@ def on_exit_getcwd(d, sh): r.sendline(b"provola") d.cont() + d.wait() d.kill() d.terminate() @@ -105,30 +106,30 @@ def on_enter_write(d, sh): nonlocal write_count if write_count == 0: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") self.assertEqual(d.syscall_arg0, 1) write_count += 1 else: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") self.assertEqual(d.syscall_arg0, 1) write_count += 1 def on_exit_mmap(d, sh): - self.assertTrue(sh.syscall_number == 9) + self.assertTrue(sh.syscall_number == 222) nonlocal ptr - ptr = d.regs.w0 + ptr = d.regs.x0 def on_enter_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) + self.assertTrue(sh.syscall_number == 17) self.assertEqual(d.syscall_arg0, ptr) def on_exit_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) - self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + self.assertTrue(sh.syscall_number == 17) + self.assertEqual(d.memory[ptr, 8], os.getcwd()[:8].encode()) handler1 = d.handle_syscall("write", on_enter_write, None) handler2 = d.handle_syscall("mmap", None, on_exit_mmap) @@ -159,44 +160,44 @@ def on_enter_write(d, sh): nonlocal write_count if write_count == 0: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") self.assertEqual(d.syscall_arg0, 1) write_count += 1 else: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") self.assertEqual(d.syscall_arg0, 1) write_count += 1 def on_exit_mmap(d, sh): - self.assertTrue(sh.syscall_number == 9) + self.assertTrue(sh.syscall_number == 222) nonlocal ptr - ptr = d.regs.rax + ptr = d.regs.x0 def on_enter_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) + self.assertTrue(sh.syscall_number == 17) self.assertEqual(d.syscall_arg0, ptr) def on_exit_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) - self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + self.assertTrue(sh.syscall_number == 17) + self.assertEqual(d.memory[ptr, 8], os.getcwd()[:8].encode()) - handler1 = d.handle_syscall(1, on_enter_write, None) - handler2 = d.handle_syscall(9, None, on_exit_mmap) - handler3 = d.handle_syscall(0x4F, on_enter_getcwd, on_exit_getcwd) + handler1 = d.handle_syscall(0x40, on_enter_write, None) + handler2 = d.handle_syscall(222, None, on_exit_mmap) + handler3 = d.handle_syscall(17, on_enter_getcwd, on_exit_getcwd) r.sendline(b"provola") - d.breakpoint(0x401196) + bp = d.breakpoint(0x9d4, file="binary") d.cont() d.wait() - self.assertEqual(d.regs.rip, 0x401196) + self.assertEqual(d.regs.pc, bp.address) handler1.disable() d.cont() @@ -223,44 +224,44 @@ def on_enter_write(d, sh): nonlocal write_count if write_count == 0: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") self.assertEqual(d.syscall_arg0, 1) write_count += 1 else: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") self.assertEqual(d.syscall_arg0, 1) write_count += 1 def on_exit_mmap(d, sh): - self.assertTrue(sh.syscall_number == 9) + self.assertTrue(sh.syscall_number == 222) nonlocal ptr - ptr = d.regs.rax + ptr = d.regs.x0 def on_enter_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) + self.assertTrue(sh.syscall_number == 17) self.assertEqual(d.syscall_arg0, ptr) def on_exit_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) - self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + self.assertTrue(sh.syscall_number == 17) + self.assertEqual(d.memory[ptr, 8], os.getcwd()[:8].encode()) - handler1 = d.handle_syscall(1, on_enter_write, None) - handler2 = d.handle_syscall(9, None, on_exit_mmap) - handler3 = d.handle_syscall(0x4F, on_enter_getcwd, on_exit_getcwd) + handler1 = d.handle_syscall(0x40, on_enter_write, None) + handler2 = d.handle_syscall(222, None, on_exit_mmap) + handler3 = d.handle_syscall(17, on_enter_getcwd, on_exit_getcwd) r.sendline(b"provola") - d.breakpoint(0x401196) + bp = d.breakpoint(0x9d4, file="binary") d.cont() d.wait() - self.assertEqual(d.regs.rip, 0x401196) + self.assertEqual(d.regs.pc, bp.address) handler1.disable() d.cont() @@ -285,7 +286,7 @@ def test_handle_overwrite(self): def on_enter_write_first(d, sh): nonlocal write_count_first - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") self.assertEqual(d.syscall_arg0, 1) write_count_first += 1 @@ -293,40 +294,40 @@ def on_enter_write_first(d, sh): def on_enter_write_second(d, sh): nonlocal write_count_second - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") self.assertEqual(d.syscall_arg0, 1) write_count_second += 1 def on_exit_mmap(d, sh): - self.assertTrue(sh.syscall_number == 9) + self.assertTrue(sh.syscall_number == 222) nonlocal ptr - ptr = d.regs.rax + ptr = d.regs.x0 def on_enter_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) + self.assertTrue(sh.syscall_number == 17) self.assertEqual(d.syscall_arg0, ptr) def on_exit_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) - self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + self.assertTrue(sh.syscall_number == 17) + self.assertEqual(d.memory[ptr, 8], os.getcwd()[:8].encode()) - handler1_1 = d.handle_syscall(1, on_enter_write_first, None) - handler2 = d.handle_syscall(9, None, on_exit_mmap) - handler3 = d.handle_syscall(0x4F, on_enter_getcwd, on_exit_getcwd) + handler1_1 = d.handle_syscall(0x40, on_enter_write_first, None) + handler2 = d.handle_syscall(222, None, on_exit_mmap) + handler3 = d.handle_syscall(17, on_enter_getcwd, on_exit_getcwd) r.sendline(b"provola") - d.breakpoint(0x401196) + bp = d.breakpoint(0x9d4, file="binary") d.cont() d.wait() - self.assertEqual(d.regs.rip, 0x401196) - handler1_2 = d.handle_syscall(1, on_enter_write_second, None) + self.assertEqual(d.regs.pc, bp.address) + handler1_2 = d.handle_syscall(0x40, on_enter_write_second, None) d.cont() @@ -360,7 +361,7 @@ def test_handle_overwrite_with_pprint(self): def on_enter_write_first(d, sh): nonlocal write_count_first - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") self.assertEqual(d.syscall_arg0, 1) write_count_first += 1 @@ -368,40 +369,40 @@ def on_enter_write_first(d, sh): def on_enter_write_second(d, sh): nonlocal write_count_second - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") self.assertEqual(d.syscall_arg0, 1) write_count_second += 1 def on_exit_mmap(d, sh): - self.assertTrue(sh.syscall_number == 9) + self.assertTrue(sh.syscall_number == 222) nonlocal ptr - ptr = d.regs.rax + ptr = d.regs.x0 def on_enter_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) + self.assertTrue(sh.syscall_number == 17) self.assertEqual(d.syscall_arg0, ptr) def on_exit_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) - self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + self.assertTrue(sh.syscall_number == 17) + self.assertEqual(d.memory[ptr, 8], os.getcwd()[:8].encode()) - handler1_1 = d.handle_syscall(1, on_enter_write_first, None) - handler2 = d.handle_syscall(9, None, on_exit_mmap) - handler3 = d.handle_syscall(0x4F, on_enter_getcwd, on_exit_getcwd) + handler1_1 = d.handle_syscall(0x40, on_enter_write_first, None) + handler2 = d.handle_syscall(222, None, on_exit_mmap) + handler3 = d.handle_syscall(17, on_enter_getcwd, on_exit_getcwd) r.sendline(b"provola") - d.breakpoint(0x401196) + bp = d.breakpoint(0x9d4, file="binary") d.cont() d.wait() - self.assertEqual(d.regs.rip, 0x401196) - handler1_2 = d.handle_syscall(1, on_enter_write_second, None) + self.assertEqual(d.regs.pc, bp.address) + handler1_2 = d.handle_syscall(0x40, on_enter_write_second, None) d.cont() @@ -434,30 +435,30 @@ def on_enter_write(d, sh): nonlocal write_count if write_count == 0: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") self.assertEqual(d.syscall_arg0, 1) write_count += 1 else: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") self.assertEqual(d.syscall_arg0, 1) write_count += 1 def on_exit_mmap(d, sh): - self.assertTrue(sh.syscall_number == 9) + self.assertTrue(sh.syscall_number == 222) nonlocal ptr - ptr = d.regs.rax + ptr = d.regs.x0 def on_enter_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) + self.assertTrue(sh.syscall_number == 17) self.assertEqual(d.syscall_arg0, ptr) def on_exit_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) - self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + self.assertTrue(sh.syscall_number == 17) + self.assertEqual(d.memory[ptr, 8], os.getcwd()[:8].encode()) handler1 = d.handle_syscall("write") handler2 = d.handle_syscall("mmap") @@ -497,30 +498,30 @@ def on_enter_write(d, sh): nonlocal write_count if write_count == 0: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 13], b"Hello, World!") self.assertEqual(d.syscall_arg0, 1) write_count += 1 else: - self.assertTrue(sh.syscall_number == 1) + self.assertTrue(sh.syscall_number == 0x40) self.assertEqual(d.memory[d.syscall_arg1, 7], b"provola") self.assertEqual(d.syscall_arg0, 1) write_count += 1 def on_exit_mmap(d, sh): - self.assertTrue(sh.syscall_number == 9) + self.assertTrue(sh.syscall_number == 222) nonlocal ptr - ptr = d.regs.rax + ptr = d.regs.x0 def on_enter_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) + self.assertTrue(sh.syscall_number == 17) self.assertEqual(d.syscall_arg0, ptr) def on_exit_getcwd(d, sh): - self.assertTrue(sh.syscall_number == 0x4F) - self.assertEqual(d.memory[d.syscall_arg0, 8], os.getcwd()[:8].encode()) + self.assertTrue(sh.syscall_number == 17) + self.assertEqual(d.memory[ptr, 8], os.getcwd()[:8].encode()) handler1 = d.handle_syscall("write") handler2 = d.handle_syscall("mmap") From 60ec74c9b0292c8784b9e22e254c1beaf95b574e Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Tue, 16 Jul 2024 19:01:40 +0200 Subject: [PATCH 054/144] feature: draft implementation of next() control flow command --- libdebug/cffi/ptrace_cffi_build.py | 47 +++++++++++++++++-- libdebug/cffi/ptrace_cffi_source.c | 30 +++++++++++++ libdebug/debugger/internal_debugger.py | 22 +++++++++ libdebug/interfaces/debugging_interface.py | 6 ++- libdebug/ptrace/ptrace_interface.py | 52 ++++++++++++++++++++++ libdebug/state/thread_context.py | 12 ++++- 6 files changed, 164 insertions(+), 5 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 95f8d0a3..2fcda90c 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -49,10 +49,10 @@ #define IS_SW_BREAKPOINT(instruction) (instruction == 0xCC) """ - finish_define = """ + control_flow_define = """ + // X86_64 Architecture specific #define IS_RET_INSTRUCTION(instruction) (instruction == 0xC3 || instruction == 0xCB || instruction == 0xC2 || instruction == 0xCA) - // X86_64 Architecture specific int IS_CALL_INSTRUCTION(uint8_t* instr) { // Check for direct CALL (E8 xx xx xx xx) @@ -73,7 +73,46 @@ return 0; // Not a CALL } + + int CALL_INSTRUCTION_SIZE(uint8_t* instr) + { + // Check for direct CALL (E8 xx xx xx xx) + if (instr[0] == 0xE8) { + return 5; // Direct CALL + } + + // Check for indirect CALL using ModR/M (FF /2) + if (instr[0] == 0xFF) { + // Extract ModR/M byte + uint8_t modRM = instr[1]; + uint8_t mod = modRM >> 6; // First two bits + uint8_t reg = (modRM >> 3) & 0x07; // Next three bits + + // Check if reg field is 010 (indirect CALL) + if (reg == 2) { + switch (mod) { + case 0: + if ((modRM & 0x07) == 4) { + return 3 + ((instr[2] == 0x25) ? 4 : 0); // SIB byte + optional disp32 + } else if ((modRM & 0x07) == 5) { + return 6; // disp32 + } + return 2; // No displacement + case 1: + return 3; // disp8 + case 2: + return 6; // disp32 + case 3: + return 2; // Register direct + } + } + } + + return 0; // Not a CALL + } """ + + else: raise NotImplementedError(f"Architecture {platform.machine()} not available.") @@ -140,6 +179,8 @@ int cont_all_and_set_bps(struct global_state *state, int pid); int stepping_finish(struct global_state *state, int tid); + int is_call_instruction(struct global_state *state, int tid); + int compute_call_skip(struct global_state *state, int tid); struct thread_status *wait_all_and_update_regs(struct global_state *state, int pid); void free_thread_status_list(struct thread_status *head); @@ -159,7 +200,7 @@ with open("libdebug/cffi/ptrace_cffi_source.c") as f: ffibuilder.set_source( "libdebug.cffi._ptrace_cffi", - breakpoint_define + finish_define + f.read(), + breakpoint_define + control_flow_define + f.read(), libraries=[], ) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index f843b250..c9ff5307 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -661,3 +661,33 @@ int stepping_finish(struct global_state *state, int tid) return 0; } + +int is_call_instruction(struct global_state *state, int tid) +{ + struct thread *t = state->t_HEAD; + while (t != NULL) { + if (t->tid == tid) { + uint64_t opcode_window = ptrace(PTRACE_PEEKDATA, tid, (void *)INSTRUCTION_POINTER(t->regs), NULL); + return IS_CALL_INSTRUCTION((uint8_t*) &opcode_window); + } + t = t->next; + } +} + +int compute_call_skip(struct global_state *state, int tid) +{ + struct thread *t = state->t_HEAD; + while (t != NULL) { + if (t->tid == tid) { + uint64_t opcode_window = ptrace(PTRACE_PEEKDATA, tid, (void *)INSTRUCTION_POINTER(t->regs), NULL); + + // Get program counter + uint64_t pc = INSTRUCTION_POINTER(t->regs); + + return pc + CALL_INSTRUCTION_SIZE((uint8_t*) &opcode_window); + } + t = t->next; + } + + return 0; +} \ No newline at end of file diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 25a7e386..07163e8c 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -888,6 +888,23 @@ def finish(self: InternalDebugger, thread: ThreadContext, heuristic: str = "back self._join_and_check_status() + def _background_next( + self: InternalDebugger, + thread: ThreadContext, + ) -> None: + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """ + self.__threaded_next(thread) + + @background_alias(_background_next) + @change_state_function_thread + def next(self: InternalDebugger, thread: ThreadContext) -> None: + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """ + self._ensure_process_stopped() + self.__polling_thread_command_queue.put((self.__threaded_next, (thread,))) + self._join_and_check_status() + def enable_pretty_print( self: InternalDebugger, ) -> SyscallHandler: @@ -1278,6 +1295,11 @@ def __threaded_finish(self: InternalDebugger, thread: ThreadContext, heuristic: self.set_stopped() + def __threaded_next(self: InternalDebugger, thread: ThreadContext) -> None: + liblog.debugger("Next on thread %s.", thread.thread_id) + self.debugging_interface.next(thread) + self.set_stopped() + def __threaded_gdb(self: InternalDebugger) -> None: self.debugging_interface.migrate_to_gdb() diff --git a/libdebug/interfaces/debugging_interface.py b/libdebug/interfaces/debugging_interface.py index f1463eae..210edd5a 100644 --- a/libdebug/interfaces/debugging_interface.py +++ b/libdebug/interfaces/debugging_interface.py @@ -1,6 +1,6 @@ # # This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2023-2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Copyright (c) 2023-2024 Roberto Alessandro Bertolini, Gabriele Digregorio, Francesco Panebianco. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for details. # @@ -94,6 +94,10 @@ def finish(self: DebuggingInterface, thread: ThreadContext, heuristic: str) -> N heuristic (str, optional): The heuristic to use. Defaults to "backtrace". """ + def next(self: DebuggingInterface, thread: ThreadContext) -> None: + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """ + @abstractmethod def maps(self: DebuggingInterface) -> list[MemoryMap]: """Returns the memory maps of the process.""" diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index 073f06e9..4bfb5e26 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -350,6 +350,58 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None else: raise ValueError(f"Unimplemented heuristic {heuristic}") + def next(self: PtraceInterface, thread: ThreadContext) -> None: + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """ + + # Check if the current instruction is a call and its skip + is_call = self.lib_trace.is_call_instruction( + self._global_state, + thread.thread_id + ) + + print(f"Is call: {is_call}") + + if is_call: + skip_address = self.lib_trace.compute_call_skip( + self._global_state, + thread.thread_id + ) + + print(f"Skip address: {hex(skip_address)}") + + # Step forward + self.step(thread) + + if is_call: + + # If a breakpoint already exists at the return address, we don't need to set a new one + found = False + ip_breakpoint = None + + for bp in self._internal_debugger.breakpoints.values(): + if bp.address == skip_address: + found = True + ip_breakpoint = bp + break + + if not found: + # Check if we have enough hardware breakpoints available + # Otherwise we use a software breakpoint + install_hw_bp = self.hardware_bp_helpers[thread.thread_id].available_breakpoints() > 0 + + ip_breakpoint = Breakpoint(skip_address, hardware=install_hw_bp) + self.set_breakpoint(ip_breakpoint) + elif not ip_breakpoint.enabled: + self._enable_breakpoint(ip_breakpoint) + + self.cont() + self.wait() + + # Remove the breakpoint if it was set by us + if not found: + self.unset_breakpoint(ip_breakpoint) + def _setup_pipe(self: PtraceInterface) -> None: """Sets up the pipe manager for the child process. diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index f0b5d193..905da5bf 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -1,6 +1,6 @@ # # This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved. +# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio, Francesco Panebianco. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for details. # from __future__ import annotations @@ -221,6 +221,11 @@ def finish(self: ThreadContext, heuristic: str = "backtrace") -> None: """ self._internal_debugger.finish(self, heuristic=heuristic) + def next(self: ThreadContext) -> None: + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """ + self._internal_debugger.next(self) + def si(self: ThreadContext) -> None: """Alias for the `step` method. @@ -254,3 +259,8 @@ def fin(self: ThreadContext, heuristic: str = "backtrace") -> None: heuristic (str, optional): The heuristic to use. Defaults to "backtrace". """ self._internal_debugger.finish(self, heuristic) + + def ni(self: ThreadContext) -> None: + """Alias for the `next` method. Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """ + self._internal_debugger.next(self) From 37e612bf480d9a0241f47c690391efc3c8abac83 Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Tue, 16 Jul 2024 19:02:04 +0200 Subject: [PATCH 055/144] test: test for the next() command --- test/run_suite.py | 4 ++ test/scripts/next_test.py | 128 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 test/scripts/next_test.py diff --git a/test/run_suite.py b/test/run_suite.py index 8f48b72a..16aee32d 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -27,6 +27,7 @@ from scripts.large_binary_sym_test import LargeBinarySymTest from scripts.memory_test import MemoryTest from scripts.multiple_debuggers_test import MultipleDebuggersTest +from scripts.next_test import NextTest from scripts.nlinks_test import Nlinks from scripts.pprint_syscalls_test import PPrintSyscallsTest from scripts.signals_multithread_test import SignalMultithreadTest @@ -108,6 +109,9 @@ def fast_suite(): suite.addTest(WaitingNlinks("test_nlinks")) suite.addTest(AutoWaitingTest("test_bps_auto_waiting")) suite.addTest(AutoWaitingTest("test_jumpout_auto_waiting")) + suite.addTest(NextTest("test_next")) + suite.addTest(NextTest("test_next_breakpoint")) + suite.addTest(NextTest("test_next_breakpoint_hw")) suite.addTest(AutoWaitingNlinks("test_nlinks")) suite.addTest(WatchpointTest("test_watchpoint")) suite.addTest(WatchpointTest("test_watchpoint_callback")) diff --git a/test/scripts/next_test.py b/test/scripts/next_test.py new file mode 100644 index 00000000..24bdbaf9 --- /dev/null +++ b/test/scripts/next_test.py @@ -0,0 +1,128 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Francesco Panebianco. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# +import unittest + +from libdebug import debugger + +TEST_ENTRYPOINT = 0x4011f8 + +# Addresses of the dummy functions +CALL_C_ADDRESS = 0x4011fd +TEST_BREAKPOINT_ADDRESS = 0x4011f1 + +# Addresses of noteworthy instructions +RETURN_POINT_FROM_C = 0x401202 + +class NextTest(unittest.TestCase): + def setUp(self): + pass + + def test_next(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + d.run() + + # Get to test entrypoint + entrypoint_bp = d.breakpoint(TEST_ENTRYPOINT) + d.cont() + + self.assertEqual(d.regs.rip, TEST_ENTRYPOINT) + + # -------- Block 1 ------- # + # Simple Step # + # ------------------------ # + + # Reach call of function c + + print(f"RIP IS AT {hex(d.regs.rip)}") + d.next() + print('Simple step') + print(f"RIP IS AT {hex(d.regs.rip)}") + print(f'Should be {hex(CALL_C_ADDRESS)}') + self.assertEqual(d.regs.rip, CALL_C_ADDRESS) + + # -------- Block 2 ------- # + # Skip a call # + # ------------------------ # + + print(f"RIP IS AT {hex(d.regs.rip)}") + d.next() + print('Skip a call') + print(f"RIP IS AT {hex(d.regs.rip)}") + print(f'Should be {hex(RETURN_POINT_FROM_C)}') + self.assertEqual(d.regs.rip, RETURN_POINT_FROM_C) + + d.kill() + + def test_next_breakpoint(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + d.run() + + # Get to test entrypoint + entrypoint_bp = d.breakpoint(TEST_ENTRYPOINT) + d.cont() + + self.assertEqual(d.regs.rip, TEST_ENTRYPOINT) + + print(f"RIP IS AT {hex(d.regs.rip)}") + + # Reach call of function c + d.next() + + print('Simple step') + print(f"RIP IS AT {hex(d.regs.rip)}") + print(f'Should be {hex(CALL_C_ADDRESS)}') + self.assertEqual(d.regs.rip, CALL_C_ADDRESS) + + # -------- Block 1 ------- # + # Call with breakpoint # + # ------------------------ # + + # Set breakpoint + test_breakpoint = d.breakpoint(TEST_BREAKPOINT_ADDRESS) + + + print(f"RIP IS AT {hex(d.regs.rip)}") + d.next() + + print('Breakpoint hit') + print(f"RIP IS AT {hex(d.regs.rip)}") + print(f'Should be {hex(TEST_BREAKPOINT_ADDRESS)}') + + # Check we hit the breakpoint + self.assertEqual(d.regs.rip, TEST_BREAKPOINT_ADDRESS) + self.assertEqual(test_breakpoint.hit_count, 1) + + d.kill() + + def test_next_breakpoint_hw(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + d.run() + + # Get to test entrypoint + entrypoint_bp = d.breakpoint(TEST_ENTRYPOINT) + d.cont() + + self.assertEqual(d.regs.rip, TEST_ENTRYPOINT) + + # Reach call of function c + d.next() + + self.assertEqual(d.regs.rip, CALL_C_ADDRESS) + + # -------- Block 1 ------- # + # Call with breakpoint # + # ------------------------ # + + # Set breakpoint + test_breakpoint = d.breakpoint(TEST_BREAKPOINT_ADDRESS, hardware=True) + + d.next() + + # Check we hit the breakpoint + self.assertEqual(d.regs.rip, TEST_BREAKPOINT_ADDRESS) + self.assertEqual(test_breakpoint.hit_count, 1) + + d.kill() \ No newline at end of file From 8bb888cb77b7e6858162041141ae97dc1f4d1767 Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Tue, 16 Jul 2024 19:02:29 +0200 Subject: [PATCH 056/144] docs: documentation for the next() command --- docs/source/basic_features.rst | 9 +++++++++ docs/source/multithreading.rst | 1 + 2 files changed, 10 insertions(+) diff --git a/docs/source/basic_features.rst b/docs/source/basic_features.rst index b2435edf..7deea1cc 100644 --- a/docs/source/basic_features.rst +++ b/docs/source/basic_features.rst @@ -219,6 +219,15 @@ The available heuristics are: The default heuristic when none is specified is "backtrace". +Next +^^^^ + +The `next` command is similar to the `step` command, but when a ``call`` instruction is found, it will continue until the end of the function being called or until the process stops for other reasons. The syntax is as follows: + +.. code-block:: python + + d.next() + Detach and GDB Migration ==================================== diff --git a/docs/source/multithreading.rst b/docs/source/multithreading.rst index 483963dc..e9730a32 100644 --- a/docs/source/multithreading.rst +++ b/docs/source/multithreading.rst @@ -33,6 +33,7 @@ The following is a list of behaviors to keep in mind when using control flow fun - `cont` will continue all threads. - `step` and `step_until` will step the selected thread. +- `next` will step on the selected thread or, if a call function is found, continue on all threads until the end of the function or another stopping event. - `finish` will have different behavior depending on the selected heuristic. - `backtrace` will continue on all threads but will stop at any breakpoint that any of the threads hit. - `step-mode` will step exclusively on the thread that has been specified. From 0b0f67a7e7faf0755371b8befa889543da16e4a0 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 16 Jul 2024 20:37:54 +0200 Subject: [PATCH 057/144] test: fix signals_multithread_test.py --- test/aarch64/scripts/signals_multithread_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/aarch64/scripts/signals_multithread_test.py b/test/aarch64/scripts/signals_multithread_test.py index e42a481c..392e20d4 100644 --- a/test/aarch64/scripts/signals_multithread_test.py +++ b/test/aarch64/scripts/signals_multithread_test.py @@ -373,7 +373,7 @@ def catcher_SIGPIPE(t, sc): # Set a breakpoint to stop the program before the end of the receiver thread r = d.run() - bp = d.breakpoint(0x14d8, hardware=True, file="binary") + bp = d.breakpoint(0xf1c, hardware=True, file="binary") catcher1 = d.catch_signal("SIGUSR1", callback=catcher_SIGUSR1) catcher2 = d.catch_signal("SIGTERM", callback=catcher_SIGTERM) From e9b1aa9f4cb74b38088f501d7f2add4ea3f8b950 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 16 Jul 2024 20:38:25 +0200 Subject: [PATCH 058/144] feat: introduce support for syscall number overwrite This works differently on aarch64 than on amd64 --- .../aarch64/aarch64_ptrace_register_holder.py | 18 +++++++++++++++++- libdebug/cffi/ptrace_cffi_build.py | 1 + libdebug/cffi/ptrace_cffi_source.c | 12 +++++++++++- test/aarch64/scripts/hijack_syscall_test.py | 10 +++++----- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py index bfd2dc5a..027b8417 100644 --- a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py +++ b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py @@ -115,6 +115,19 @@ def setter(self: Aarch64Registers, value: int) -> None: return property(getter, setter, None, name) +def _get_property_syscall_num() -> property: + def getter(self: Aarch64Registers) -> int: + self._internal_debugger._ensure_process_stopped() + return self.register_file.x8 + + def setter(self: Aarch64Registers, value: int) -> None: + self._internal_debugger._ensure_process_stopped() + self.register_file.x8 = value + self.register_file.override_syscall_number = True + + return property(getter, setter, None, "syscall_number") + + @dataclass class Aarch64PtraceRegisterHolder(PtraceRegisterHolder): """A class that provides views and setters for the register of an aarch64 process.""" @@ -167,7 +180,6 @@ def apply_on_thread(self: Aarch64PtraceRegisterHolder, target: ThreadContext, ta target_class.instruction_pointer = _get_property_64("pc") # setup generic syscall properties - target_class.syscall_number = _get_property_64("x8") target_class.syscall_return = _get_property_64("x0") target_class.syscall_arg0 = _get_property_64("x0") target_class.syscall_arg1 = _get_property_64("x1") @@ -175,3 +187,7 @@ def apply_on_thread(self: Aarch64PtraceRegisterHolder, target: ThreadContext, ta target_class.syscall_arg3 = _get_property_64("x3") target_class.syscall_arg4 = _get_property_64("x4") target_class.syscall_arg5 = _get_property_64("x5") + + # syscall number handling is special on aarch64, as the original number is stored in x8 + # but writing to x8 isn't enough to change the actual called syscall + target_class.syscall_number = _get_property_syscall_num() diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index b5cf298f..57b40141 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -250,6 +250,7 @@ unsigned long sp; unsigned long pc; unsigned long pstate; + _Bool override_syscall_number; }; """ diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 150b9b5b..3cdb3697 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -98,6 +98,8 @@ int setregs(int tid, struct ptrace_regs_struct *regs) #ifdef ARCH_AARCH64 int getregs(int tid, struct ptrace_regs_struct *regs) { + regs->override_syscall_number = 0; + struct iovec iov; iov.iov_base = regs; iov.iov_len = sizeof(struct ptrace_regs_struct); @@ -107,6 +109,14 @@ int getregs(int tid, struct ptrace_regs_struct *regs) int setregs(int tid, struct ptrace_regs_struct *regs) { struct iovec iov; + + if (regs->override_syscall_number) { + iov.iov_base = ®s->x8; + iov.iov_len = sizeof(regs->x8); + ptrace(PTRACE_SETREGSET, tid, NT_ARM_SYSTEM_CALL, &iov); + regs->override_syscall_number = 0; + } + iov.iov_base = regs; iov.iov_len = sizeof(struct ptrace_regs_struct); return ptrace(PTRACE_SETREGSET, tid, NT_PRSTATUS, &iov); @@ -1311,4 +1321,4 @@ unsigned long get_hit_hw_breakpoint(struct global_state *state, int tid) } return 0; -} \ No newline at end of file +} diff --git a/test/aarch64/scripts/hijack_syscall_test.py b/test/aarch64/scripts/hijack_syscall_test.py index 559169a2..4e39195b 100644 --- a/test/aarch64/scripts/hijack_syscall_test.py +++ b/test/aarch64/scripts/hijack_syscall_test.py @@ -113,7 +113,7 @@ def on_enter_write(d, sh): write_count += 1 def on_enter_getcwd(d, sh): - d.syscall_number = 0x1 + d.syscall_number = 0x40 d = debugger("binaries/handle_syscall_test") @@ -158,7 +158,7 @@ def on_enter_write(d, sh): write_count += 1 def on_enter_getcwd(d, sh): - d.syscall_number = 0x1 + d.syscall_number = 0x40 d = debugger("binaries/handle_syscall_test") @@ -216,7 +216,7 @@ def on_enter_write(d, sh): # recursive hijack is on, we expect the write handler to be called three times handler = d.handle_syscall("write", on_enter=on_enter_write, recursive=True) - d.breakpoint(0x4011B0) + d.breakpoint(0x9f0, file="binary") d.cont() print(r.recvline()) @@ -263,7 +263,7 @@ def on_enter_write(d, sh): # recursive hijack is on, we expect the write handler to be called three times handler = d.handle_syscall("write", on_enter=on_enter_write, recursive=True) - d.breakpoint(0x4011B0) + d.breakpoint(0x9f0, file="binary") d.cont() print(r.recvline()) @@ -288,7 +288,7 @@ def on_enter_write(d, sh): self.assertEqual(self.capturedOutput.getvalue().count("Hello, World!"), 2) self.assertEqual(self.capturedOutput.getvalue().count("write"), 3) - self.assertEqual(self.capturedOutput.getvalue().count("0x402010"), 3) + self.assertEqual(self.capturedOutput.getvalue().count("0xaaaaaaaa0ab0"), 3) self.assertEqual(write_count, handler.hit_count) self.assertEqual(handler.hit_count, 3) From 9d920c2a5ba9377b0d27d3347fac6c30e6474138 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Wed, 17 Jul 2024 22:13:40 +0200 Subject: [PATCH 059/144] fix: delete unused file --- .../ptrace_hardware_breakpoint_manager.py | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 libdebug/architectures/ptrace_hardware_breakpoint_manager.py diff --git a/libdebug/architectures/ptrace_hardware_breakpoint_manager.py b/libdebug/architectures/ptrace_hardware_breakpoint_manager.py deleted file mode 100644 index 1328c090..00000000 --- a/libdebug/architectures/ptrace_hardware_breakpoint_manager.py +++ /dev/null @@ -1,59 +0,0 @@ -# -# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2023-2024 Roberto Alessandro Bertolini. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Callable - - from libdebug.data.breakpoint import Breakpoint - from libdebug.state.thread_context import ThreadContext - - -class PtraceHardwareBreakpointManager(ABC): - """An architecture-independent interface for managing hardware breakpoints. - - Attributes: - thread (ThreadContext): The target thread. - peek_user (callable): A function that reads a number of bytes from the target thread registers. - poke_user (callable): A function that writes a number of bytes to the target thread registers. - breakpoint_count (int): The number of hardware breakpoints set. - """ - - def __init__( - self: PtraceHardwareBreakpointManager, - thread: ThreadContext, - peek_user: Callable[[int, int], int], - poke_user: Callable[[int, int, int], None], - ) -> None: - """Initializes the hardware breakpoint manager.""" - self.thread = thread - self.peek_user = peek_user - self.poke_user = poke_user - self.breakpoint_count = 0 - - @abstractmethod - def install_breakpoint(self: PtraceHardwareBreakpointManager, bp: Breakpoint) -> None: - """Installs a hardware breakpoint at the provided location.""" - - @abstractmethod - def remove_breakpoint(self: PtraceHardwareBreakpointManager, bp: Breakpoint) -> None: - """Removes a hardware breakpoint at the provided location.""" - - @abstractmethod - def available_breakpoints(self: PtraceHardwareBreakpointManager) -> int: - """Returns the number of available hardware breakpoint registers.""" - - @abstractmethod - def is_watchpoint_hit(self: PtraceHardwareBreakpointManager) -> Breakpoint | None: - """Checks if a watchpoint has been hit. - - Returns: - Breakpoint | None: The watchpoint that has been hit, or None if no watchpoint has been hit. - """ From a47b4c49b7bdc49dba5c4dd93d08c0d1781fab5e Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Wed, 17 Jul 2024 22:21:55 +0200 Subject: [PATCH 060/144] feat: move arch-dependent hardware bp validation to helper class, correctly implement watchpoints for aarch64 --- .../aarch64/aarch64_breakpoint_validator.py | 27 +++++++ .../amd64/amd64_breakpoint_validator.py | 21 ++++++ .../architectures/breakpoint_validator.py | 24 ++++++ libdebug/cffi/ptrace_cffi_build.py | 4 +- libdebug/cffi/ptrace_cffi_source.c | 73 ++++++++++++------- libdebug/debugger/debugger.py | 4 +- libdebug/debugger/internal_debugger.py | 26 ++----- libdebug/ptrace/ptrace_interface.py | 6 +- 8 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 libdebug/architectures/aarch64/aarch64_breakpoint_validator.py create mode 100644 libdebug/architectures/amd64/amd64_breakpoint_validator.py create mode 100644 libdebug/architectures/breakpoint_validator.py diff --git a/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py b/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py new file mode 100644 index 00000000..c94503f0 --- /dev/null +++ b/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py @@ -0,0 +1,27 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from libdebug.data.breakpoint import Breakpoint + + +def validate_breakpoint_aarch64(bp: Breakpoint) -> None: + """Validate a hardware breakpoint for the AARCH64 architecture.""" + if bp.condition.lower() not in ["r", "w", "rw", "x"]: + raise ValueError("Invalid condition for watchpoints. Supported conditions are 'r', 'w', 'rw', 'x'.") + + if not (1 <= bp.length <= 8): + raise ValueError("Invalid length for watchpoints. Supported lengths are between 1 and 8.") + + if bp.condition == "x" and bp.length != 4: + raise ValueError("Invalid length for execution watchpoints. Supported length is 4.") + + if bp.address & 0x8: + raise ValueError("Watchpoint address must be aligned to 8 bytes on aarch64. This is a kernel limitation.") diff --git a/libdebug/architectures/amd64/amd64_breakpoint_validator.py b/libdebug/architectures/amd64/amd64_breakpoint_validator.py new file mode 100644 index 00000000..08aee677 --- /dev/null +++ b/libdebug/architectures/amd64/amd64_breakpoint_validator.py @@ -0,0 +1,21 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from libdebug.data.breakpoint import Breakpoint + + +def validate_breakpoint_amd64(bp: Breakpoint) -> None: + """Validate a hardware breakpoint for the AMD64 architecture.""" + if bp.condition.lower() not in ["w", "rw", "x"]: + raise ValueError("Invalid condition for watchpoints. Supported conditions are 'w', 'rw', 'x'.") + + if bp.length not in [1, 2, 4, 8]: + raise ValueError("Invalid length for watchpoints. Supported lengths are 1, 2, 4, 8.") diff --git a/libdebug/architectures/breakpoint_validator.py b/libdebug/architectures/breakpoint_validator.py new file mode 100644 index 00000000..6804f497 --- /dev/null +++ b/libdebug/architectures/breakpoint_validator.py @@ -0,0 +1,24 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from libdebug.architectures.aarch64.aarch64_breakpoint_validator import validate_breakpoint_aarch64 +from libdebug.architectures.amd64.amd64_breakpoint_validator import validate_breakpoint_amd64 + +if TYPE_CHECKING: + from libdebug.data.breakpoint import Breakpoint + +def validate_hardware_breakpoint(arch: str, bp: Breakpoint) -> None: + """Validate a hardware breakpoint for the specified architecture.""" + if arch == "aarch64": + validate_breakpoint_aarch64(bp) + elif arch == "amd64": + validate_breakpoint_amd64(bp) + else: + raise ValueError(f"Architecture {arch} not supported") diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 57b40141..fb4855cf 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -312,7 +312,7 @@ uint64_t addr; int tid; char enabled; - char type; + char type[2]; char len; struct hardware_breakpoint *next; }; @@ -379,7 +379,7 @@ void enable_breakpoint(struct global_state *state, uint64_t address); void disable_breakpoint(struct global_state *state, uint64_t address); - void register_hw_breakpoint(struct global_state *state, int tid, uint64_t address, char type, char len); + void register_hw_breakpoint(struct global_state *state, int tid, uint64_t address, char type[2], char len); void unregister_hw_breakpoint(struct global_state *state, int tid, uint64_t address); void enable_hw_breakpoint(struct global_state *state, int tid, uint64_t address); void disable_hw_breakpoint(struct global_state *state, int tid, uint64_t address); diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 3cdb3697..7b0112a5 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -56,7 +56,7 @@ struct hardware_breakpoint { uint64_t addr; int tid; char enabled; - char type; + char type[2]; char len; struct hardware_breakpoint *next; }; @@ -149,7 +149,7 @@ void install_hardware_breakpoint(struct hardware_breakpoint *bp) return; } - unsigned long ctrl = CTRL_LOCAL(i) | CTRL_COND_VAL(bp->type) << CTRL_COND(i) | CTRL_LEN_VAL(bp->len) << CTRL_LEN(i); + unsigned long ctrl = CTRL_LOCAL(i) | CTRL_COND_VAL(bp->type[0]) << CTRL_COND(i) | CTRL_LEN_VAL(bp->len) << CTRL_LEN(i); // read the state from DR7 unsigned long state = ptrace(PTRACE_PEEKUSER, bp->tid, DR_BASE + 7 * DR_SIZE); @@ -251,6 +251,23 @@ struct user_hwdebug_state { } dbg_regs[16]; }; +int get_breakpoint_type(char type[2]) +{ + if (type[0] == 'r') { + if (type[1] == 'w') { + return 3; + } else { + return 1; + } + } else if (type[0] == 'w') { + return 2; + } else if (type[0] == 'x') { + return 0; + } else { + return -1; + } +} + void install_hardware_breakpoint(struct hardware_breakpoint *bp) { // find a free debug register @@ -260,9 +277,10 @@ void install_hardware_breakpoint(struct hardware_breakpoint *bp) iov.iov_base = &state; iov.iov_len = sizeof state; - unsigned long command = bp->type == 'x' ? NT_ARM_HW_BREAK : NT_ARM_HW_WATCH; + unsigned long command = get_breakpoint_type(bp->type) == 0 ? NT_ARM_HW_BREAK : NT_ARM_HW_WATCH; - ptrace(PTRACE_GETREGSET, bp->tid, command, &iov); + if (ptrace(PTRACE_GETREGSET, bp->tid, command, &iov)) + perror("install_hardware_breakpoint_get"); int i; for (i = 0; i < 16; i++) { @@ -275,14 +293,20 @@ void install_hardware_breakpoint(struct hardware_breakpoint *bp) return; } + if (bp->type[0] == 'x') { + // Hardware breakpoint can only be of length 4 + bp->len = 4; + } + unsigned int length = (1 << bp->len) - 1; - unsigned int condition = bp->type == 'r' ? 3 : (bp->type == 'w' ? 2 : 0); - unsigned int control = (length << 5) | (condition << 3) | 1; + unsigned int condition = get_breakpoint_type(bp->type); + unsigned int control = (length << 5) | (condition << 3) | (2 << 1) | 1; state.dbg_regs[i].addr = bp->addr; state.dbg_regs[i].ctrl = control; - ptrace(PTRACE_SETREGSET, bp->tid, command, &iov); + if (ptrace(PTRACE_SETREGSET, bp->tid, command, &iov)) + perror("install_hardware_breakpoint_set"); } void remove_hardware_breakpoint(struct hardware_breakpoint *bp) @@ -293,9 +317,10 @@ void remove_hardware_breakpoint(struct hardware_breakpoint *bp) iov.iov_base = &state; iov.iov_len = sizeof state; - unsigned long command = bp->type == 'x' ? NT_ARM_HW_BREAK : NT_ARM_HW_WATCH; + unsigned long command = get_breakpoint_type(bp->type) == 0 ? NT_ARM_HW_BREAK : NT_ARM_HW_WATCH; - ptrace(PTRACE_GETREGSET, bp->tid, command, &iov); + if (ptrace(PTRACE_GETREGSET, bp->tid, command, &iov)) + perror("remove_hardware_breakpoint_get"); int i; for (i = 0; i < 16; i++) { @@ -311,7 +336,8 @@ void remove_hardware_breakpoint(struct hardware_breakpoint *bp) state.dbg_regs[i].addr = 0; state.dbg_regs[i].ctrl = 0; - ptrace(PTRACE_SETREGSET, bp->tid, command, &iov); + if (ptrace(PTRACE_SETREGSET, bp->tid, command, &iov)) + perror("remove_hardware_breakpoint_set"); } int is_breakpoint_hit(struct hardware_breakpoint *bp) @@ -346,15 +372,10 @@ int get_remaining_hw_breakpoint_count(struct global_state *state, int tid) unsigned long command = NT_ARM_HW_BREAK; - ptrace(PTRACE_GETREGSET, tid, command, &iov); + if (ptrace(PTRACE_GETREGSET, tid, command, &iov)) + perror("get_remaining_hw_breakpoint_count"); - int i; - for (i = 0; i < 16; i++) { - if (!dbg_state.dbg_regs[i].addr) - break; - } - - return 16 - i; + return dbg_state.dbg_info & 0xff; } int get_remaining_hw_watchpoint_count(struct global_state *state, int tid) @@ -367,15 +388,10 @@ int get_remaining_hw_watchpoint_count(struct global_state *state, int tid) unsigned long command = NT_ARM_HW_WATCH; - ptrace(PTRACE_GETREGSET, tid, command, &iov); - - int i; - for (i = 0; i < 16; i++) { - if (!dbg_state.dbg_regs[i].addr) - break; - } + if (ptrace(PTRACE_GETREGSET, tid, command, &iov)) + perror("get_remaining_hw_watchpoint_count"); - return 16 - i; + return dbg_state.dbg_info & 0xff; } #endif @@ -1227,7 +1243,7 @@ int stepping_finish(struct global_state *state, int tid) return 0; } -void register_hw_breakpoint(struct global_state *state, int tid, uint64_t address, char type, char len) +void register_hw_breakpoint(struct global_state *state, int tid, uint64_t address, char type[2], char len) { struct hardware_breakpoint *b = state->hw_b_HEAD; @@ -1243,7 +1259,8 @@ void register_hw_breakpoint(struct global_state *state, int tid, uint64_t addres b->addr = address; b->tid = tid; b->enabled = 1; - b->type = type; + b->type[0] = type[0]; + b->type[1] = type[1]; b->len = len; b->next = state->hw_b_HEAD; diff --git a/libdebug/debugger/debugger.py b/libdebug/debugger/debugger.py index 749516ee..b4b5bcea 100644 --- a/libdebug/debugger/debugger.py +++ b/libdebug/debugger/debugger.py @@ -94,7 +94,7 @@ def breakpoint( self: Debugger, position: int | str, hardware: bool = False, - condition: str | None = None, + condition: str = "x", length: int = 1, callback: None | Callable[[ThreadContext, Breakpoint], None] = None, file: str = "hybrid", @@ -264,7 +264,7 @@ def bp( self: Debugger, position: int | str, hardware: bool = False, - condition: str | None = None, + condition: str = "x", length: int = 1, callback: None | Callable[[ThreadContext, Breakpoint], None] = None, file: str = "hybrid", diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 194b4213..dff6b7c9 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -20,6 +20,7 @@ import psutil +from libdebug.architectures.breakpoint_validator import validate_hardware_breakpoint from libdebug.architectures.syscall_hijacker import SyscallHijacker from libdebug.builtin.antidebug_syscall_handler import on_enter_ptrace, on_exit_ptrace from libdebug.builtin.pretty_print_syscall_handler import pprint_on_enter, pprint_on_exit @@ -403,7 +404,7 @@ def breakpoint( self: InternalDebugger, position: int | str, hardware: bool = False, - condition: str | None = None, + condition: str = "x", length: int = 1, callback: None | Callable[[ThreadContext, Breakpoint], None] = None, file: str = "hybrid", @@ -428,26 +429,13 @@ def breakpoint( address = self.resolve_address(position, file) position = hex(address) - if condition: - if not hardware: - raise ValueError( - "Breakpoint condition is supported only for hardware watchpoints.", - ) - - if condition.lower() not in ["w", "rw", "x"]: - raise ValueError( - "Invalid condition for watchpoints. Supported conditions are 'w', 'rw', 'x'.", - ) - - if length not in [1, 2, 4, 8]: - raise ValueError( - "Invalid length for watchpoints. Supported lengths are 1, 2, 4, 8.", - ) + if condition != "x" and not hardware: + raise ValueError("Breakpoint condition is supported only for hardware watchpoints.") - if hardware and not condition: - condition = "x" + bp = Breakpoint(address, position, 0, hardware, callback, condition.lower(), length) - bp = Breakpoint(address, position, 0, hardware, callback, condition, length) + if hardware: + validate_hardware_breakpoint(self.arch, bp) link_to_internal_debugger(bp, self) diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index 26b65b76..6b091c73 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -457,7 +457,7 @@ def migrate_from_gdb(self: PtraceInterface) -> None: self._global_state, thread.thread_id, bp.address, - bp.condition[:1].encode(), + bp.condition.encode().ljust(2, b"\x00"), chr(bp.length).encode(), ) @@ -489,7 +489,7 @@ def register_new_thread(self: PtraceInterface, new_thread_id: int) -> None: self._global_state, new_thread_id, bp.address, - bp.condition[:1].encode(), + bp.condition.encode().ljust(2, b"\x00"), chr(bp.length).encode(), ) @@ -567,7 +567,7 @@ def set_breakpoint(self: PtraceInterface, bp: Breakpoint, insert: bool = True) - self._global_state, thread.thread_id, bp.address, - bp.condition[:1].encode(), + bp.condition.encode().ljust(2, b"\x00"), chr(bp.length).encode(), ) elif insert: From 3b29677126b53c4769b5b4330a2732478f5d9170 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Wed, 17 Jul 2024 23:02:49 +0200 Subject: [PATCH 061/144] fix: remove debug prints from aarch64-specific code --- libdebug/cffi/ptrace_cffi_source.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 7b0112a5..3d6c616d 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -279,8 +279,7 @@ void install_hardware_breakpoint(struct hardware_breakpoint *bp) unsigned long command = get_breakpoint_type(bp->type) == 0 ? NT_ARM_HW_BREAK : NT_ARM_HW_WATCH; - if (ptrace(PTRACE_GETREGSET, bp->tid, command, &iov)) - perror("install_hardware_breakpoint_get"); + ptrace(PTRACE_GETREGSET, bp->tid, command, &iov); int i; for (i = 0; i < 16; i++) { @@ -305,8 +304,7 @@ void install_hardware_breakpoint(struct hardware_breakpoint *bp) state.dbg_regs[i].addr = bp->addr; state.dbg_regs[i].ctrl = control; - if (ptrace(PTRACE_SETREGSET, bp->tid, command, &iov)) - perror("install_hardware_breakpoint_set"); + ptrace(PTRACE_SETREGSET, bp->tid, command, &iov); } void remove_hardware_breakpoint(struct hardware_breakpoint *bp) @@ -319,8 +317,7 @@ void remove_hardware_breakpoint(struct hardware_breakpoint *bp) unsigned long command = get_breakpoint_type(bp->type) == 0 ? NT_ARM_HW_BREAK : NT_ARM_HW_WATCH; - if (ptrace(PTRACE_GETREGSET, bp->tid, command, &iov)) - perror("remove_hardware_breakpoint_get"); + ptrace(PTRACE_GETREGSET, bp->tid, command, &iov); int i; for (i = 0; i < 16; i++) { @@ -336,8 +333,7 @@ void remove_hardware_breakpoint(struct hardware_breakpoint *bp) state.dbg_regs[i].addr = 0; state.dbg_regs[i].ctrl = 0; - if (ptrace(PTRACE_SETREGSET, bp->tid, command, &iov)) - perror("remove_hardware_breakpoint_set"); + ptrace(PTRACE_SETREGSET, bp->tid, command, &iov); } int is_breakpoint_hit(struct hardware_breakpoint *bp) @@ -372,8 +368,7 @@ int get_remaining_hw_breakpoint_count(struct global_state *state, int tid) unsigned long command = NT_ARM_HW_BREAK; - if (ptrace(PTRACE_GETREGSET, tid, command, &iov)) - perror("get_remaining_hw_breakpoint_count"); + ptrace(PTRACE_GETREGSET, tid, command, &iov); return dbg_state.dbg_info & 0xff; } @@ -388,8 +383,7 @@ int get_remaining_hw_watchpoint_count(struct global_state *state, int tid) unsigned long command = NT_ARM_HW_WATCH; - if (ptrace(PTRACE_GETREGSET, tid, command, &iov)) - perror("get_remaining_hw_watchpoint_count"); + ptrace(PTRACE_GETREGSET, tid, command, &iov); return dbg_state.dbg_info & 0xff; } From bf7ab41b0355d3ec8cb22f7d317a6bf1ec1cfa4b Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Wed, 17 Jul 2024 23:03:10 +0200 Subject: [PATCH 062/144] fix: make watchpoints work on aarch64 --- .../aarch64/aarch64_breakpoint_validator.py | 5 +- test/aarch64/binaries/watchpoint_test | Bin 70424 -> 70424 bytes test/aarch64/scripts/watchpoint_test.py | 86 ++++++++++++------ test/aarch64/srcs/watchpoint_test.c | 34 +++++++ 4 files changed, 92 insertions(+), 33 deletions(-) create mode 100644 test/aarch64/srcs/watchpoint_test.c diff --git a/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py b/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py index c94503f0..ebc4684e 100644 --- a/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py +++ b/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py @@ -20,8 +20,5 @@ def validate_breakpoint_aarch64(bp: Breakpoint) -> None: if not (1 <= bp.length <= 8): raise ValueError("Invalid length for watchpoints. Supported lengths are between 1 and 8.") - if bp.condition == "x" and bp.length != 4: - raise ValueError("Invalid length for execution watchpoints. Supported length is 4.") - - if bp.address & 0x8: + if bp.condition != "x" and bp.address & 0x7: raise ValueError("Watchpoint address must be aligned to 8 bytes on aarch64. This is a kernel limitation.") diff --git a/test/aarch64/binaries/watchpoint_test b/test/aarch64/binaries/watchpoint_test index f5e595dafbe33a7fa28522d0cb38ee02f783c726..f30ad9123681b774b41e588fcbc38d09a92d1007 100755 GIT binary patch delta 198 zcmbQSjAh0$mJI@oj0-jkG6pk>e4H)%d&lALtAFj_NZB#pQtiOKe-k%XFl}W~ZD5#~ z=+F?v$iPs^z{oIxVFAO$hX)#h7B(y>0*dVfiyfF8#BQw!RLQ{LQ2Brzp%Td73FJdn zZC=N&^q)~<`b9QIaYl>jFWDH~fs`RTqdud;^h9>Xa!CUQCI%4T0Fw+1j6kjcgE)i5 Y^pETy#oH}88258BYHa^0$SBVU0ACw92mk;8 delta 198 zcmbQSjAh0$mJI@oj1x8sG6pk>C~y06$kNmHq1pP=wfFr!SZVY*_$ zUdOKVpHXJ|MK(rpMvdt&*%;k{lp#B#KBLt1M0Un global_char + self.assertEqual(d.regs.pc, base + 0x724) + + d.cont() + + # str w1, [x0] => global_int + self.assertEqual(d.regs.pc, base + 0x748) d.cont() - self.assertEqual(d.regs.rip, 0x401124) # mov dword ptr [global_int], 0x4050607 + # str x1, [x0] => global_long + self.assertEqual(d.regs.pc, base + 0x764) d.cont() - self.assertEqual( - d.regs.rip, 0x401135 - ) # mov qword ptr [global_long], 0x8090a0b0c0d0e0f + # ldrb w0, [x0] => global_char + self.assertEqual(d.regs.pc, base + 0x780) d.cont() - self.assertEqual(d.regs.rip, 0x401155) # movzx eax, byte ptr [global_char] + # ldr w0, [x0] => global_short + self.assertEqual(d.regs.pc, base + 0x790) d.cont() - self.assertEqual(d.regs.rip, 0x401173) # mov rax, qword ptr [global_long] + # ldr x0, [x0] => global_long + self.assertEqual(d.regs.pc, base + 0x7b0) d.cont() @@ -48,27 +59,35 @@ def test_watchpoint(self): def test_watchpoint_callback(self): global_char_ip = [] global_int_ip = [] + global_short_ip = [] global_long_ip = [] def watchpoint_global_char(t, b): nonlocal global_char_ip - global_char_ip.append(t.regs.rip) + global_char_ip.append(t.regs.pc) def watchpoint_global_int(t, b): nonlocal global_int_ip - global_int_ip.append(t.regs.rip) + global_int_ip.append(t.regs.pc) + + def watchpoint_global_short(t, b): + nonlocal global_short_ip + + global_short_ip.append(t.regs.pc) def watchpoint_global_long(t, b): nonlocal global_long_ip - global_long_ip.append(t.regs.rip) + global_long_ip.append(t.regs.pc) d = debugger("binaries/watchpoint_test", auto_interrupt_on_command=False) d.run() + base = d.regs.pc & ~0xfff + wp1 = d.breakpoint( "global_char", hardware=True, @@ -90,33 +109,42 @@ def watchpoint_global_long(t, b): length=8, callback=watchpoint_global_long, ) + wp4 = d.breakpoint( + "global_short", + hardware=True, + condition="r", + length=2, + callback=watchpoint_global_short, + ) d.cont() d.kill() - self.assertEqual(global_char_ip[0], 0x401111) # mov byte ptr [global_char], 0x1 - self.assertEqual( - global_int_ip[0], 0x401124 - ) # mov dword ptr [global_int], 0x4050607 - self.assertEqual( - global_long_ip[0], 0x401135 - ) # mov qword ptr [global_long], 0x8090a0b0c0d0e0f - self.assertEqual( - global_char_ip[1], 0x401155 - ) # movzx eax, byte ptr [global_char] - self.assertEqual( - global_long_ip[1], 0x401173 - ) # mov rax, qword ptr [global_long] + # strb w1, [x0] => global_char + self.assertEqual(global_char_ip[0], base + 0x724) - self.assertEqual(len(global_char_ip), 2) - self.assertEqual(len(global_int_ip), 1) + # str w1, [x0] => global_int + self.assertEqual(global_int_ip[0], base + 0x748) + + # str x1, [x0] => global_long + self.assertEqual(global_long_ip[0], base + 0x764) - # There is one extra hit performed by the exit routine of libc - self.assertEqual(len(global_long_ip), 3) + # ldrb w0, [x0] => global_char + self.assertEqual(global_char_ip[1], base + 0x780) + # ldr w0, [x0] => global_short + self.assertEqual(global_short_ip[0], base + 0x790) + + # ldr x0, [x0] => global_long + self.assertEqual(global_long_ip[1], base + 0x7b0) + + self.assertEqual(len(global_char_ip), 2) + self.assertEqual(len(global_int_ip), 1) + self.assertEqual(len(global_short_ip), 1) + self.assertEqual(len(global_long_ip), 2) self.assertEqual(wp1.hit_count, 2) self.assertEqual(wp2.hit_count, 1) + self.assertEqual(wp3.hit_count, 2) + self.assertEqual(wp4.hit_count, 1) - # There is one extra hit performed by the exit routine of libc - self.assertEqual(wp3.hit_count, 3) diff --git a/test/aarch64/srcs/watchpoint_test.c b/test/aarch64/srcs/watchpoint_test.c new file mode 100644 index 00000000..4ca44540 --- /dev/null +++ b/test/aarch64/srcs/watchpoint_test.c @@ -0,0 +1,34 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +#include +#include +#include + +uint8_t __attribute__((aligned(8))) global_char = 0; +uint16_t __attribute__((aligned(8))) global_short = 0; +uint32_t __attribute__((aligned(8))) global_int = 0; +uint64_t __attribute__((aligned(8))) global_long = 0; + +int main() +{ + global_char = 0x01; + global_short = 0x0203; + global_int = 0x04050607; + global_long = 0x08090a0b0c0d0e0f; + + uint8_t local_char = 0; + uint16_t local_short = 0; + uint32_t local_int = 0; + uint64_t local_long = 0; + + local_char = global_char; + local_short = global_short; + local_int = global_int; + local_long = global_long; + + return 0; +} From 1d60158817cbf4dde6ecb0b9e50c068119c4a262 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Wed, 17 Jul 2024 23:07:56 +0200 Subject: [PATCH 063/144] fix: rename __init__ file Well, whoops --- libdebug/architectures/aarch64/{__init__,py => __init__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename libdebug/architectures/aarch64/{__init__,py => __init__.py} (100%) diff --git a/libdebug/architectures/aarch64/__init__,py b/libdebug/architectures/aarch64/__init__.py similarity index 100% rename from libdebug/architectures/aarch64/__init__,py rename to libdebug/architectures/aarch64/__init__.py From d12ea48f05b9df60361bfe469c0de535e1a3c889 Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Thu, 18 Jul 2024 20:30:19 +0200 Subject: [PATCH 064/144] fix: next() command now working thanks to libdebug universal fix (d.wait) --- libdebug/ptrace/ptrace_interface.py | 5 +---- test/scripts/next_test.py | 19 ------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index 4bfb5e26..9527e255 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -360,18 +360,15 @@ def next(self: PtraceInterface, thread: ThreadContext) -> None: thread.thread_id ) - print(f"Is call: {is_call}") - if is_call: skip_address = self.lib_trace.compute_call_skip( self._global_state, thread.thread_id ) - - print(f"Skip address: {hex(skip_address)}") # Step forward self.step(thread) + self.wait() if is_call: diff --git a/test/scripts/next_test.py b/test/scripts/next_test.py index 24bdbaf9..1b1f8b2f 100644 --- a/test/scripts/next_test.py +++ b/test/scripts/next_test.py @@ -36,22 +36,14 @@ def test_next(self): # Reach call of function c - print(f"RIP IS AT {hex(d.regs.rip)}") d.next() - print('Simple step') - print(f"RIP IS AT {hex(d.regs.rip)}") - print(f'Should be {hex(CALL_C_ADDRESS)}') self.assertEqual(d.regs.rip, CALL_C_ADDRESS) # -------- Block 2 ------- # # Skip a call # # ------------------------ # - print(f"RIP IS AT {hex(d.regs.rip)}") d.next() - print('Skip a call') - print(f"RIP IS AT {hex(d.regs.rip)}") - print(f'Should be {hex(RETURN_POINT_FROM_C)}') self.assertEqual(d.regs.rip, RETURN_POINT_FROM_C) d.kill() @@ -66,14 +58,9 @@ def test_next_breakpoint(self): self.assertEqual(d.regs.rip, TEST_ENTRYPOINT) - print(f"RIP IS AT {hex(d.regs.rip)}") - # Reach call of function c d.next() - print('Simple step') - print(f"RIP IS AT {hex(d.regs.rip)}") - print(f'Should be {hex(CALL_C_ADDRESS)}') self.assertEqual(d.regs.rip, CALL_C_ADDRESS) # -------- Block 1 ------- # @@ -83,14 +70,8 @@ def test_next_breakpoint(self): # Set breakpoint test_breakpoint = d.breakpoint(TEST_BREAKPOINT_ADDRESS) - - print(f"RIP IS AT {hex(d.regs.rip)}") d.next() - print('Breakpoint hit') - print(f"RIP IS AT {hex(d.regs.rip)}") - print(f'Should be {hex(TEST_BREAKPOINT_ADDRESS)}') - # Check we hit the breakpoint self.assertEqual(d.regs.rip, TEST_BREAKPOINT_ADDRESS) self.assertEqual(test_breakpoint.hit_count, 1) From 970765d9fb761638831e422cc9cb8a850ce8d525 Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Sun, 21 Jul 2024 18:57:18 +0200 Subject: [PATCH 065/144] fix: moved call utilities to python + fixed logic redundancies --- .../amd64/amd64_call_utilities.py | 66 +++++++++++++++++++ .../architectures/call_utilities_manager.py | 29 ++++++++ .../architectures/call_utilities_provider.py | 23 +++++++ libdebug/cffi/ptrace_cffi_build.py | 39 ----------- libdebug/cffi/ptrace_cffi_source.c | 30 --------- libdebug/ptrace/ptrace_interface.py | 39 +++++++---- 6 files changed, 143 insertions(+), 83 deletions(-) create mode 100644 libdebug/architectures/amd64/amd64_call_utilities.py create mode 100644 libdebug/architectures/call_utilities_manager.py create mode 100644 libdebug/architectures/call_utilities_provider.py diff --git a/libdebug/architectures/amd64/amd64_call_utilities.py b/libdebug/architectures/amd64/amd64_call_utilities.py new file mode 100644 index 00000000..04fe3a04 --- /dev/null +++ b/libdebug/architectures/amd64/amd64_call_utilities.py @@ -0,0 +1,66 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2023-2024 Francesco Panebianco. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from libdebug.architectures.call_utilities_manager import CallUtilitiesManager + +class Amd64CallUtilities(CallUtilitiesManager): + """Class that provides call utilities for the x86_64 architecture.""" + + def is_call(self, opcode_window: bytes) -> bool: + """Check if the current instruction is a call instruction.""" + # Check for direct CALL (E8 xx xx xx xx) + if opcode_window[0] == 0xE8: + return True + + # Check for indirect CALL using ModR/M (FF /2) + if opcode_window[0] == 0xFF: + # Extract ModR/M byte + modRM = opcode_window[1] + reg = (modRM >> 3) & 0x07 # Middle three bits + + if reg == 2: + return True + + return False + + def compute_call_skip(self, opcode_window: bytes) -> int: + """Compute the instruction size of the current call instruction.""" + # Check for direct CALL (E8 xx xx xx xx) + if opcode_window[0] == 0xE8: + return 5 # Direct CALL + + # Check for indirect CALL using ModR/M (FF /2) + if opcode_window[0] == 0xFF: + # Extract ModR/M byte + modRM = opcode_window[1] + mod = (modRM >> 6) & 0x03 # First two bits + reg = (modRM >> 3) & 0x07 # Next three bits + + # Check if reg field is 010 (indirect CALL) + if reg == 2: + if mod == 0: + if (modRM & 0x07) == 4: + return 3 + (4 if opcode_window[2] == 0x25 else 0) # SIB byte + optional disp32 + elif (modRM & 0x07) == 5: + return 6 # disp32 + return 2 # No displacement + elif mod == 1: + return 3 # disp8 + elif mod == 2: + return 6 # disp32 + elif mod == 3: + return 2 # Register direct + + return 0 # Not a CALL + + def get_call_and_skip_amount(self, opcode_window: bytes) -> tuple[bool, int]: + """Check if the current instruction is a call instruction and compute the instruction size.""" + skip = self.compute_call_skip(opcode_window) + return skip != 0, skip diff --git a/libdebug/architectures/call_utilities_manager.py b/libdebug/architectures/call_utilities_manager.py new file mode 100644 index 00000000..35ee5250 --- /dev/null +++ b/libdebug/architectures/call_utilities_manager.py @@ -0,0 +1,29 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2023-2024 Francesco Panebianco. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from libdebug.state.thread_context import ThreadContext + + +class CallUtilitiesManager(ABC): + """An architecture-independent interface for call instruction utilities.""" + + @abstractmethod + def is_call(self: CallUtilitiesManager, opcode_window: bytes) -> bool: + """Check if the current instruction is a call instruction.""" + + @abstractmethod + def compute_call_skip(self: CallUtilitiesManager, opcode_window: bytes) -> int: + """Compute the address where to skip after the current call instruction.""" + + @abstractmethod + def get_call_and_skip_amount(self, opcode_window: bytes) -> tuple[bool, int]: + """Check if the current instruction is a call instruction and compute the instruction size.""" \ No newline at end of file diff --git a/libdebug/architectures/call_utilities_provider.py b/libdebug/architectures/call_utilities_provider.py new file mode 100644 index 00000000..0098efe4 --- /dev/null +++ b/libdebug/architectures/call_utilities_provider.py @@ -0,0 +1,23 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2023-2024 Francesco Panebianco. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from libdebug.architectures.amd64.amd64_call_utilities import ( + Amd64CallUtilities, +) +from libdebug.architectures.call_utilities_manager import CallUtilitiesManager +from libdebug.utils.libcontext import libcontext + +_amd64_call_utilities = Amd64CallUtilities() + +def call_utilities_provider() -> CallUtilitiesManager: + """Returns an instance of the call utilities provider to be used by the `_InternalDebugger` class.""" + architecture = libcontext.arch + + match architecture: + case "amd64": + return _amd64_call_utilities + case _: + raise NotImplementedError(f"Architecture {architecture} not available.") diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 2fcda90c..85faa626 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -73,43 +73,6 @@ return 0; // Not a CALL } - - int CALL_INSTRUCTION_SIZE(uint8_t* instr) - { - // Check for direct CALL (E8 xx xx xx xx) - if (instr[0] == 0xE8) { - return 5; // Direct CALL - } - - // Check for indirect CALL using ModR/M (FF /2) - if (instr[0] == 0xFF) { - // Extract ModR/M byte - uint8_t modRM = instr[1]; - uint8_t mod = modRM >> 6; // First two bits - uint8_t reg = (modRM >> 3) & 0x07; // Next three bits - - // Check if reg field is 010 (indirect CALL) - if (reg == 2) { - switch (mod) { - case 0: - if ((modRM & 0x07) == 4) { - return 3 + ((instr[2] == 0x25) ? 4 : 0); // SIB byte + optional disp32 - } else if ((modRM & 0x07) == 5) { - return 6; // disp32 - } - return 2; // No displacement - case 1: - return 3; // disp8 - case 2: - return 6; // disp32 - case 3: - return 2; // Register direct - } - } - } - - return 0; // Not a CALL - } """ @@ -179,8 +142,6 @@ int cont_all_and_set_bps(struct global_state *state, int pid); int stepping_finish(struct global_state *state, int tid); - int is_call_instruction(struct global_state *state, int tid); - int compute_call_skip(struct global_state *state, int tid); struct thread_status *wait_all_and_update_regs(struct global_state *state, int pid); void free_thread_status_list(struct thread_status *head); diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index c9ff5307..f843b250 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -661,33 +661,3 @@ int stepping_finish(struct global_state *state, int tid) return 0; } - -int is_call_instruction(struct global_state *state, int tid) -{ - struct thread *t = state->t_HEAD; - while (t != NULL) { - if (t->tid == tid) { - uint64_t opcode_window = ptrace(PTRACE_PEEKDATA, tid, (void *)INSTRUCTION_POINTER(t->regs), NULL); - return IS_CALL_INSTRUCTION((uint8_t*) &opcode_window); - } - t = t->next; - } -} - -int compute_call_skip(struct global_state *state, int tid) -{ - struct thread *t = state->t_HEAD; - while (t != NULL) { - if (t->tid == tid) { - uint64_t opcode_window = ptrace(PTRACE_PEEKDATA, tid, (void *)INSTRUCTION_POINTER(t->regs), NULL); - - // Get program counter - uint64_t pc = INSTRUCTION_POINTER(t->regs); - - return pc + CALL_INSTRUCTION_SIZE((uint8_t*) &opcode_window); - } - t = t->next; - } - - return 0; -} \ No newline at end of file diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index 9527e255..cd9276bd 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -17,6 +17,7 @@ ptrace_hardware_breakpoint_manager_provider, ) from libdebug.architectures.register_helper import register_holder_provider +from libdebug.architectures.call_utilities_provider import call_utilities_provider from libdebug.cffi import _ptrace_cffi from libdebug.data.breakpoint import Breakpoint from libdebug.debugger.internal_debugger_instance_manager import ( @@ -331,6 +332,10 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None ip_breakpoint = bp break + # If we find an existing breakpoint that is disabled, we enable it + # but we need to disable it back after the command + should_disable = False + if not found: # Check if we have enough hardware breakpoints available # Otherwise we use a software breakpoint @@ -340,6 +345,7 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None self.set_breakpoint(ip_breakpoint) elif not ip_breakpoint.enabled: self._enable_breakpoint(ip_breakpoint) + should_disable = True self.cont() self.wait() @@ -347,6 +353,9 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None # Remove the breakpoint if it was set by us if not found: self.unset_breakpoint(ip_breakpoint) + # Disable the breakpoint if it was just enabled by us + elif should_disable: + self._disable_breakpoint(ip_breakpoint) else: raise ValueError(f"Unimplemented heuristic {heuristic}") @@ -354,23 +363,13 @@ def next(self: PtraceInterface, thread: ThreadContext) -> None: """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. """ - # Check if the current instruction is a call and its skip - is_call = self.lib_trace.is_call_instruction( - self._global_state, - thread.thread_id - ) + opcode_window = thread.memory.read(thread.regs.rip, 8) - if is_call: - skip_address = self.lib_trace.compute_call_skip( - self._global_state, - thread.thread_id - ) - - # Step forward - self.step(thread) - self.wait() + # Check if the current instruction is a call and its skip amount + is_call, skip = call_utilities_provider().get_call_and_skip_amount(opcode_window) if is_call: + skip_address = thread.regs.rip + skip # If a breakpoint already exists at the return address, we don't need to set a new one found = False @@ -382,6 +381,10 @@ def next(self: PtraceInterface, thread: ThreadContext) -> None: ip_breakpoint = bp break + # If we find an existing breakpoint that is disabled, we enable it + # but we need to disable it back after the command + should_disable = False + if not found: # Check if we have enough hardware breakpoints available # Otherwise we use a software breakpoint @@ -391,6 +394,7 @@ def next(self: PtraceInterface, thread: ThreadContext) -> None: self.set_breakpoint(ip_breakpoint) elif not ip_breakpoint.enabled: self._enable_breakpoint(ip_breakpoint) + should_disable = True self.cont() self.wait() @@ -398,6 +402,13 @@ def next(self: PtraceInterface, thread: ThreadContext) -> None: # Remove the breakpoint if it was set by us if not found: self.unset_breakpoint(ip_breakpoint) + # Disable the breakpoint if it was just enabled by us + elif should_disable: + self._disable_breakpoint(ip_breakpoint) + else: + # Step forward + self.step(thread) + self.wait() def _setup_pipe(self: PtraceInterface) -> None: """Sets up the pipe manager for the child process. From da5ccd8f9497f8141e37139338307e2b5819357c Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Sun, 21 Jul 2024 18:57:40 +0200 Subject: [PATCH 066/144] test: added d.terminate() for repeated testing --- test/scripts/next_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/scripts/next_test.py b/test/scripts/next_test.py index 1b1f8b2f..1829854e 100644 --- a/test/scripts/next_test.py +++ b/test/scripts/next_test.py @@ -47,6 +47,7 @@ def test_next(self): self.assertEqual(d.regs.rip, RETURN_POINT_FROM_C) d.kill() + d.terminate() def test_next_breakpoint(self): d = debugger("binaries/finish_test", auto_interrupt_on_command=False) @@ -77,6 +78,7 @@ def test_next_breakpoint(self): self.assertEqual(test_breakpoint.hit_count, 1) d.kill() + d.terminate() def test_next_breakpoint_hw(self): d = debugger("binaries/finish_test", auto_interrupt_on_command=False) @@ -106,4 +108,5 @@ def test_next_breakpoint_hw(self): self.assertEqual(d.regs.rip, TEST_BREAKPOINT_ADDRESS) self.assertEqual(test_breakpoint.hit_count, 1) - d.kill() \ No newline at end of file + d.kill() + d.terminate() \ No newline at end of file From 9e855f6e3ddf478fadbb735e8701f220cea3f867 Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Sun, 21 Jul 2024 19:00:00 +0200 Subject: [PATCH 067/144] refactor: fixed Lint failures --- libdebug/architectures/amd64/amd64_call_utilities.py | 2 -- libdebug/architectures/call_utilities_manager.py | 5 ----- 2 files changed, 7 deletions(-) diff --git a/libdebug/architectures/amd64/amd64_call_utilities.py b/libdebug/architectures/amd64/amd64_call_utilities.py index 04fe3a04..1956ca15 100644 --- a/libdebug/architectures/amd64/amd64_call_utilities.py +++ b/libdebug/architectures/amd64/amd64_call_utilities.py @@ -6,8 +6,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING - from libdebug.architectures.call_utilities_manager import CallUtilitiesManager class Amd64CallUtilities(CallUtilitiesManager): diff --git a/libdebug/architectures/call_utilities_manager.py b/libdebug/architectures/call_utilities_manager.py index 35ee5250..2a53a38a 100644 --- a/libdebug/architectures/call_utilities_manager.py +++ b/libdebug/architectures/call_utilities_manager.py @@ -7,11 +7,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from libdebug.state.thread_context import ThreadContext - class CallUtilitiesManager(ABC): """An architecture-independent interface for call instruction utilities.""" From cc56da667361b8f659d2615359543604e20650ea Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 22 Jul 2024 10:49:30 +0200 Subject: [PATCH 068/144] style: fix minor lint warnings --- .../amd64/amd64_ptrace_register_holder.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libdebug/architectures/amd64/amd64_ptrace_register_holder.py b/libdebug/architectures/amd64/amd64_ptrace_register_holder.py index 3e0a246f..98ba2f47 100644 --- a/libdebug/architectures/amd64/amd64_ptrace_register_holder.py +++ b/libdebug/architectures/amd64/amd64_ptrace_register_holder.py @@ -282,7 +282,7 @@ def apply_on_regs(self: Amd64PtraceRegisterHolder, target: Amd64Registers, targe self._handle_fp_2696(target_class) case _: raise NotImplementedError( - f"Floating-point register file type {self.fp_register_file.type} not available." + f"Floating-point register file type {self.fp_register_file.type} not available.", ) def apply_on_thread(self: Amd64PtraceRegisterHolder, target: ThreadContext, target_class: type) -> None: @@ -306,13 +306,13 @@ def apply_on_thread(self: Amd64PtraceRegisterHolder, target: ThreadContext, targ target_class.syscall_arg4 = _get_property_64("r8") target_class.syscall_arg5 = _get_property_64("r9") - def _handle_fp_512(self, target_class: type) -> None: + def _handle_fp_512(self: Amd64PtraceRegisterHolder, target_class: type) -> None: """Handle the case where the xsave area is 512 bytes long, which means we just have the xmm registers.""" for index in range(16): name = f"xmm{index}" setattr(target_class, name, _get_property_fp_xmm0(name, index)) - def _handle_fp_896(self, target_class: type) -> None: + def _handle_fp_896(self: Amd64PtraceRegisterHolder, target_class: type) -> None: """Handle the case where the xsave area is 896 bytes long, which means we have the xmm and ymm registers.""" for index in range(16): name = f"xmm{index}" @@ -322,8 +322,8 @@ def _handle_fp_896(self, target_class: type) -> None: name = f"ymm{index}" setattr(target_class, name, _get_property_fp_ymm0(name, index)) - def _handle_fp_2696(self, target_class: type) -> None: - """Handle the case where the xsave area is 2696 bytes long, which means we have the xmm, ymm and zmm registers.""" + def _handle_fp_2696(self: Amd64PtraceRegisterHolder, target_class: type) -> None: + """Handle the case where the xsave area is 2696 bytes long, which means we have 32 zmm registers.""" for index in range(16): name = f"xmm{index}" setattr(target_class, name, _get_property_fp_xmm0(name, index)) From cd65140a09ac3456547822c981eed7dce3310dc2 Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Mon, 22 Jul 2024 11:02:10 +0200 Subject: [PATCH 069/144] refactor: changed copyright date --- libdebug/architectures/amd64/amd64_call_utilities.py | 2 +- libdebug/architectures/call_utilities_manager.py | 2 +- libdebug/architectures/call_utilities_provider.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libdebug/architectures/amd64/amd64_call_utilities.py b/libdebug/architectures/amd64/amd64_call_utilities.py index 1956ca15..8f96dcb7 100644 --- a/libdebug/architectures/amd64/amd64_call_utilities.py +++ b/libdebug/architectures/amd64/amd64_call_utilities.py @@ -1,6 +1,6 @@ # # This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2023-2024 Francesco Panebianco. All rights reserved. +# Copyright (c) 2024 Francesco Panebianco. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for details. # diff --git a/libdebug/architectures/call_utilities_manager.py b/libdebug/architectures/call_utilities_manager.py index 2a53a38a..09d26b9c 100644 --- a/libdebug/architectures/call_utilities_manager.py +++ b/libdebug/architectures/call_utilities_manager.py @@ -1,6 +1,6 @@ # # This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2023-2024 Francesco Panebianco. All rights reserved. +# Copyright (c) 2024 Francesco Panebianco. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for details. # diff --git a/libdebug/architectures/call_utilities_provider.py b/libdebug/architectures/call_utilities_provider.py index 0098efe4..037f5f0c 100644 --- a/libdebug/architectures/call_utilities_provider.py +++ b/libdebug/architectures/call_utilities_provider.py @@ -1,6 +1,6 @@ # # This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2023-2024 Francesco Panebianco. All rights reserved. +# Copyright (c) 2024 Francesco Panebianco. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for details. # From ef778ed3d9d449740c5326db45930a1a3bd75ca2 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 22 Jul 2024 11:08:08 +0200 Subject: [PATCH 070/144] fix: pass around Registers reference directly for floating-point register management --- .../amd64/amd64_ptrace_register_holder.py | 24 +++++++++---------- libdebug/cffi/ptrace_cffi_build.py | 4 ++-- libdebug/cffi/ptrace_cffi_source.c | 22 ++++------------- libdebug/debugger/internal_debugger.py | 17 ++++++------- libdebug/interfaces/debugging_interface.py | 9 +++---- libdebug/ptrace/ptrace_interface.py | 13 +++++----- 6 files changed, 39 insertions(+), 50 deletions(-) diff --git a/libdebug/architectures/amd64/amd64_ptrace_register_holder.py b/libdebug/architectures/amd64/amd64_ptrace_register_holder.py index 98ba2f47..3fca7f15 100644 --- a/libdebug/architectures/amd64/amd64_ptrace_register_holder.py +++ b/libdebug/architectures/amd64/amd64_ptrace_register_holder.py @@ -117,21 +117,21 @@ def setter(self: Amd64Registers, value: int) -> None: def _get_property_fp_xmm0(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.xmm0[index].data, "little") def setter(self: Amd64Registers, value: int) -> None: self._internal_debugger._ensure_process_stopped() data = value.to_bytes(16, "little") self._fp_register_file.xmm0[index].data = data - self._internal_debugger._flush_fp_registers(self._thread_id) + self._internal_debugger._flush_fp_registers(self) return property(getter, setter, None, name) def _get_property_fp_ymm0(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + self._internal_debugger._fetch_fp_registers(self) xmm0 = int.from_bytes(self._fp_register_file.xmm0[index].data, "little") ymm0 = int.from_bytes(self._fp_register_file.ymm0[index].data, "little") return (ymm0 << 128) | xmm0 @@ -142,14 +142,14 @@ def setter(self: Amd64Registers, value: int) -> None: new_ymm0 = value >> 128 self._fp_register_file.xmm0[index].data = new_xmm0.to_bytes(16, "little") self._fp_register_file.ymm0[index].data = new_ymm0.to_bytes(16, "little") - self._internal_debugger._flush_fp_registers(self._thread_id) + self._internal_debugger._flush_fp_registers(self) return property(getter, setter, None, name) def _get_property_fp_zmm0(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + self._internal_debugger._fetch_fp_registers(self) zmm0 = int.from_bytes(self._fp_register_file.zmm0[index].data, "little") ymm0 = int.from_bytes(self._fp_register_file.ymm0[index].data, "little") xmm0 = int.from_bytes(self._fp_register_file.xmm0[index].data, "little") @@ -163,14 +163,14 @@ def setter(self: Amd64Registers, value: int) -> None: self._fp_register_file.xmm0[index].data = new_xmm0.to_bytes(16, "little") self._fp_register_file.ymm0[index].data = new_ymm0.to_bytes(16, "little") self._fp_register_file.zmm0[index].data = new_zmm0.to_bytes(32, "little") - self._internal_debugger._flush_fp_registers(self._thread_id) + self._internal_debugger._flush_fp_registers(self) return property(getter, setter, None, name) def _get_property_fp_xmm1(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + self._internal_debugger._fetch_fp_registers(self) zmm1 = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") return zmm1 & ((1 << 128) - 1) @@ -180,14 +180,14 @@ def setter(self: Amd64Registers, value: int) -> None: previous_value = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") new_value = (previous_value & ~((1 << 128) - 1)) | (value & ((1 << 128) - 1)) self._fp_register_file.zmm1[index].data = new_value.to_bytes(64, "little") - self._internal_debugger._flush_fp_registers(self._thread_id) + self._internal_debugger._flush_fp_registers(self) return property(getter, setter, None, name) def _get_property_fp_ymm1(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + self._internal_debugger._fetch_fp_registers(self) zmm1 = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") return zmm1 & ((1 << 256) - 1) @@ -197,20 +197,20 @@ def setter(self: Amd64Registers, value: int) -> None: previous_value = self._fp_register_file.zmm1[index] new_value = (previous_value & ~((1 << 256) - 1)) | (value & ((1 << 256) - 1)) self._fp_register_file.zmm1[index].data = new_value.to_bytes(64, "little") - self._internal_debugger._flush_fp_registers(self._thread_id) + self._internal_debugger._flush_fp_registers(self) return property(getter, setter, None, name) def _get_property_fp_zmm1(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.zmm1[index].data, "little") def setter(self: Amd64Registers, value: int) -> None: self._internal_debugger._ensure_process_stopped() self._fp_register_file.zmm1[index].data = value.to_bytes(64, "little") - self._internal_debugger._flush_fp_registers(self._thread_id) + self._internal_debugger._flush_fp_registers(self) return property(getter, setter, None, name) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index bfb81b8c..e8cb3da3 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -248,8 +248,8 @@ uint64_t ptrace_pokeuser(int pid, uint64_t addr, uint64_t data); struct fp_regs_struct *get_thread_fp_regs(struct global_state *state, int tid); - void get_fp_regs(struct global_state *state, int tid); - void set_fp_regs(struct global_state *state, int tid); + void get_fp_regs(int tid, struct fp_regs_struct *fpregs); + void set_fp_regs(int tid, struct fp_regs_struct *fpregs); uint64_t ptrace_geteventmsg(int pid); diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index d2d0bc07..e4ad6e3e 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -93,21 +93,14 @@ struct fp_regs_struct *get_thread_fp_regs(struct global_state *state, int tid) return NULL; } -void get_fp_regs(struct global_state *state, int tid) +void get_fp_regs(int tid, struct fp_regs_struct *fpregs) { - struct thread *t = get_thread(state, tid); - - if (t == NULL) { - perror("Thread not found"); - return; - } - #if (XSAVE == 0) #else struct iovec iov; - iov.iov_base = (unsigned char *)(&t->fpregs) + offsetof(struct fp_regs_struct, padding0); + iov.iov_base = (unsigned char *)(fpregs) + offsetof(struct fp_regs_struct, padding0); iov.iov_len = sizeof(struct fp_regs_struct) - sizeof(unsigned long); if (ptrace(PTRACE_GETREGSET, tid, NT_X86_XSTATE, &iov) == -1) { @@ -116,21 +109,14 @@ void get_fp_regs(struct global_state *state, int tid) #endif } -void set_fp_regs(struct global_state *state, int tid) +void set_fp_regs(int tid, struct fp_regs_struct *fpregs) { - struct thread *t = get_thread(state, tid); - - if (t == NULL) { - perror("Thread not found"); - return; - } - #if (XSAVE == 0) #else struct iovec iov; - iov.iov_base = (unsigned char *)(&t->fpregs) + offsetof(struct fp_regs_struct, padding0); + iov.iov_base = (unsigned char *)(fpregs) + offsetof(struct fp_regs_struct, padding0); iov.iov_len = sizeof(struct fp_regs_struct) - sizeof(unsigned long); if (ptrace(PTRACE_SETREGSET, tid, NT_X86_XSTATE, &iov) == -1) { diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 4a3d0155..fe4864d5 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -59,6 +59,7 @@ from collections.abc import Callable from libdebug.data.memory_map import MemoryMap + from libdebug.data.registers import Registers from libdebug.interfaces.debugging_interface import DebuggingInterface from libdebug.state.thread_context import ThreadContext from libdebug.utils.pipe_manager import PipeManager @@ -1296,11 +1297,11 @@ def __threaded_poke_memory(self: InternalDebugger, address: int, data: bytes) -> int_data = int.from_bytes(data, "little") self.debugging_interface.poke_memory(address, int_data) - def __threaded_fetch_fp_registers(self: InternalDebugger, thread_id: int) -> None: - self.debugging_interface.fetch_fp_registers(thread_id) + def __threaded_fetch_fp_registers(self: InternalDebugger, registers: Registers) -> None: + self.debugging_interface.fetch_fp_registers(registers) - def __threaded_flush_fp_registers(self: InternalDebugger, thread_id: int) -> None: - self.debugging_interface.flush_fp_registers(thread_id) + def __threaded_flush_fp_registers(self: InternalDebugger, registers: Registers) -> None: + self.debugging_interface.flush_fp_registers(registers) @background_alias(__threaded_peek_memory) def _peek_memory(self: InternalDebugger, address: int) -> bytes: @@ -1354,7 +1355,7 @@ def _poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: self._join_and_check_status() @background_alias(__threaded_fetch_fp_registers) - def _fetch_fp_registers(self: InternalDebugger, thread_id: int) -> None: + def _fetch_fp_registers(self: InternalDebugger, registers: Registers) -> None: """Fetches the floating point registers of a thread.""" if not self.instanced: raise RuntimeError("Process not running, cannot step.") @@ -1362,13 +1363,13 @@ def _fetch_fp_registers(self: InternalDebugger, thread_id: int) -> None: self._ensure_process_stopped() self.__polling_thread_command_queue.put( - (self.__threaded_fetch_fp_registers, (thread_id,)), + (self.__threaded_fetch_fp_registers, (registers,)), ) self._join_and_check_status() @background_alias(__threaded_flush_fp_registers) - def _flush_fp_registers(self: InternalDebugger, thread_id: int) -> None: + def _flush_fp_registers(self: InternalDebugger, registers: Registers) -> None: """Flushes the floating point registers of a thread.""" if not self.instanced: raise RuntimeError("Process not running, cannot step.") @@ -1376,7 +1377,7 @@ def _flush_fp_registers(self: InternalDebugger, thread_id: int) -> None: self._ensure_process_stopped() self.__polling_thread_command_queue.put( - (self.__threaded_flush_fp_registers, (thread_id,)), + (self.__threaded_flush_fp_registers, (registers,)), ) self._join_and_check_status() diff --git a/libdebug/interfaces/debugging_interface.py b/libdebug/interfaces/debugging_interface.py index 87ed2390..defbcca4 100644 --- a/libdebug/interfaces/debugging_interface.py +++ b/libdebug/interfaces/debugging_interface.py @@ -12,6 +12,7 @@ if TYPE_CHECKING: from libdebug.data.breakpoint import Breakpoint from libdebug.data.memory_map import MemoryMap + from libdebug.data.registers import Registers from libdebug.data.signal_catcher import SignalCatcher from libdebug.data.syscall_handler import SyscallHandler from libdebug.state.thread_context import ThreadContext @@ -168,17 +169,17 @@ def poke_memory(self: DebuggingInterface, address: int, data: int) -> None: """ @abstractmethod - def fetch_fp_registers(self: DebuggingInterface, thread_id: int) -> None: + def fetch_fp_registers(self: DebuggingInterface, registers: Registers) -> None: """Fetches the floating-point registers of the specified thread. Args: - thread_id (int): The thread to fetch. + registers (Registers): The registers instance to update. """ @abstractmethod - def flush_fp_registers(self: DebuggingInterface, thread_id: int) -> None: + def flush_fp_registers(self: DebuggingInterface, registers: Registers) -> None: """Flushes the floating-point registers of the specified thread. Args: - thread_id (int): The thread to store. + registers (Registers): The registers instance to flush. """ diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index b80178ba..ebd082d1 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -54,6 +54,7 @@ PtraceHardwareBreakpointManager, ) from libdebug.data.memory_map import MemoryMap + from libdebug.data.registers import Registers from libdebug.data.signal_catcher import SignalCatcher from libdebug.data.syscall_handler import SyscallHandler from libdebug.debugger.internal_debugger import InternalDebugger @@ -640,21 +641,21 @@ def poke_memory(self: PtraceInterface, address: int, value: int) -> None: error = self.ffi.errno raise OSError(error, errno.errorcode[error]) - def fetch_fp_registers(self: PtraceInterface, thread_id: int) -> None: + def fetch_fp_registers(self: PtraceInterface, registers: Registers) -> None: """Fetches the floating-point registers of the specified thread. Args: - thread_id (int): The thread to fetch. + registers (Registers): The registers instance to update. """ - self.lib_trace.get_fp_regs(self._global_state, thread_id) + self.lib_trace.get_fp_regs(registers._thread_id, registers._fp_register_file) - def flush_fp_registers(self: PtraceInterface, thread_id: int) -> None: + def flush_fp_registers(self: PtraceInterface, registers: Registers) -> None: """Flushes the floating-point registers of the specified thread. Args: - thread_id (int): The thread to flush. + registers (Registers): The registers instance to update. """ - self.lib_trace.set_fp_regs(self._global_state, thread_id) + self.lib_trace.set_fp_regs(registers._thread_id, registers._fp_register_file) def _peek_user(self: PtraceInterface, thread_id: int, address: int) -> int: """Reads the memory at the specified address.""" From 13ccc00d8413223c4848f8ba9df05e088750c08e Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 22 Jul 2024 11:27:41 +0200 Subject: [PATCH 071/144] docs: add reference about floating-point register struct --- libdebug/cffi/ptrace_cffi_build.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index e8cb3da3..6fbfb9d5 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -36,6 +36,9 @@ unsigned char data[64]; }; + // For details about the layout of the xsave structure, see Intel's Architecture Instruction Set Extensions Programming Reference + // Chapter 3.2.4 "The Layout of XSAVE Save Area" + // https://www.intel.com/content/dam/develop/external/us/en/documents/319433-024-697869.pdf struct fp_regs_struct { unsigned long type; @@ -71,6 +74,9 @@ unsigned char data[32]; }; + // For details about the layout of the xsave structure, see Intel's Architecture Instruction Set Extensions Programming Reference + // Chapter 3.2.4 "The Layout of XSAVE Save Area" + // https://www.intel.com/content/dam/develop/external/us/en/documents/319433-024-697869.pdf struct fp_regs_struct { unsigned long type; @@ -96,6 +102,9 @@ unsigned char data[16]; }; + // For details about the layout of the xsave structure, see Intel's Architecture Instruction Set Extensions Programming Reference + // Chapter 3.2.4 "The Layout of XSAVE Save Area" + // https://www.intel.com/content/dam/develop/external/us/en/documents/319433-024-697869.pdf struct fp_regs_struct { unsigned long type; From dbedf3ff1ad9664b0db91b08eeada72a97af3e22 Mon Sep 17 00:00:00 2001 From: io-no Date: Thu, 1 Aug 2024 17:20:37 +0200 Subject: [PATCH 072/144] docs: added DOI --- README.md | 4 +++- docs/source/index.rst | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 452d85e4..66f29519 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ![logo](https://github.com/libdebug/libdebug/blob/dev/media/libdebug_header.png?raw=true) -# libdebug +# libdebug [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.13151549.svg)](https://doi.org/10.5281/zenodo.13151549) + libdebug is an open source Python library to automate the debugging of a binary executable. With libdebug you have full control of the flow of your debugged executable. With it you can: @@ -128,5 +129,6 @@ If you intend to use libdebug in your work, please cite this repository using th publisher = {libdebug.org}, author = {Digregorio, Gabriele and Bertolini, Roberto Alessandro and Panebianco, Francesco and Polino, Mario}, year = {2024}, + doi = {10.5281/zenodo.13151549}, } ``` diff --git a/docs/source/index.rst b/docs/source/index.rst index 38b221b8..b3ecf6c9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,6 +9,9 @@ ---- +.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.13151549.svg + :target: https://doi.org/10.5281/zenodo.13151549 + Quick Start ==================================== Welcome to libdebug! This powerful Python library can be used to debug your binary executables programmatically, providing a robust, user-friendly interface. @@ -120,6 +123,22 @@ Examples of some known issues include: - Attaching libdebug to a process that was started with pwntools with ``shell=True`` will cause the process to attach to the shell process instead. This behavior is described in https://github.com/libdebug/libdebug/issues/57. +Cite Us +------- +Need to cite libdebug in your research? Use the following BibTeX entry: + +.. code-block:: bibtex + + @software{libdebug_2024, + title = {libdebug: {Build} {Your} {Own} {Debugger}}, + copyright = {MIT Licence}, + url = {https://libdebug.org}, + publisher = {libdebug.org}, + author = {Digregorio, Gabriele and Bertolini, Roberto Alessandro and Panebianco, Francesco and Polino, Mario}, + year = {2024}, + doi = {10.5281/zenodo.13151549}, + } + .. toctree:: :maxdepth: 2 :caption: Contents: From 92f20c00165c2073b310892a609ca117a5f5e7f0 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sat, 3 Aug 2024 15:37:49 +0200 Subject: [PATCH 073/144] feat: make floating point register accessors flush and fetch only when required --- .../amd64/amd64_ptrace_register_holder.py | 50 ++++++++++++------- libdebug/cffi/ptrace_cffi_build.py | 27 ++++++---- libdebug/cffi/ptrace_cffi_source.c | 33 ++++++++++-- libdebug/ptrace/ptrace_interface.py | 5 +- 4 files changed, 81 insertions(+), 34 deletions(-) diff --git a/libdebug/architectures/amd64/amd64_ptrace_register_holder.py b/libdebug/architectures/amd64/amd64_ptrace_register_holder.py index 3fca7f15..44597dba 100644 --- a/libdebug/architectures/amd64/amd64_ptrace_register_holder.py +++ b/libdebug/architectures/amd64/amd64_ptrace_register_holder.py @@ -117,100 +117,114 @@ def setter(self: Amd64Registers, value: int) -> None: def _get_property_fp_xmm0(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.xmm0[index].data, "little") def setter(self: Amd64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) data = value.to_bytes(16, "little") self._fp_register_file.xmm0[index].data = data - self._internal_debugger._flush_fp_registers(self) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_ymm0(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) xmm0 = int.from_bytes(self._fp_register_file.xmm0[index].data, "little") ymm0 = int.from_bytes(self._fp_register_file.ymm0[index].data, "little") return (ymm0 << 128) | xmm0 def setter(self: Amd64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) new_xmm0 = value & ((1 << 128) - 1) new_ymm0 = value >> 128 self._fp_register_file.xmm0[index].data = new_xmm0.to_bytes(16, "little") self._fp_register_file.ymm0[index].data = new_ymm0.to_bytes(16, "little") - self._internal_debugger._flush_fp_registers(self) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_zmm0(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) zmm0 = int.from_bytes(self._fp_register_file.zmm0[index].data, "little") ymm0 = int.from_bytes(self._fp_register_file.ymm0[index].data, "little") xmm0 = int.from_bytes(self._fp_register_file.xmm0[index].data, "little") return (zmm0 << 256) | (ymm0 << 128) | xmm0 def setter(self: Amd64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) new_xmm0 = value & ((1 << 128) - 1) new_ymm0 = (value >> 128) & ((1 << 128) - 1) new_zmm0 = value >> 256 self._fp_register_file.xmm0[index].data = new_xmm0.to_bytes(16, "little") self._fp_register_file.ymm0[index].data = new_ymm0.to_bytes(16, "little") self._fp_register_file.zmm0[index].data = new_zmm0.to_bytes(32, "little") - self._internal_debugger._flush_fp_registers(self) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_xmm1(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) zmm1 = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") return zmm1 & ((1 << 128) - 1) def setter(self: Amd64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() # We do not clear the upper 384 bits of the register + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) previous_value = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") + new_value = (previous_value & ~((1 << 128) - 1)) | (value & ((1 << 128) - 1)) self._fp_register_file.zmm1[index].data = new_value.to_bytes(64, "little") - self._internal_debugger._flush_fp_registers(self) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_ymm1(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) zmm1 = int.from_bytes(self._fp_register_file.zmm1[index].data, "little") return zmm1 & ((1 << 256) - 1) def setter(self: Amd64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() # We do not clear the upper 256 bits of the register + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) previous_value = self._fp_register_file.zmm1[index] + new_value = (previous_value & ~((1 << 256) - 1)) | (value & ((1 << 256) - 1)) self._fp_register_file.zmm1[index].data = new_value.to_bytes(64, "little") - self._internal_debugger._flush_fp_registers(self) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_zmm1(name: str, index: int) -> property: def getter(self: Amd64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.zmm1[index].data, "little") def setter(self: Amd64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) self._fp_register_file.zmm1[index].data = value.to_bytes(64, "little") - self._internal_debugger._flush_fp_registers(self) + self._fp_register_file.dirty = True return property(getter, setter, None, name) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 6fbfb9d5..02eb229c 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -39,9 +39,13 @@ // For details about the layout of the xsave structure, see Intel's Architecture Instruction Set Extensions Programming Reference // Chapter 3.2.4 "The Layout of XSAVE Save Area" // https://www.intel.com/content/dam/develop/external/us/en/documents/319433-024-697869.pdf + #pragma pack(push, 1) struct fp_regs_struct { unsigned long type; + _Bool dirty; // true if the debugging script has modified the state of the registers + _Bool fresh; // true if the registers have already been fetched for this state + unsigned char bool_padding[6]; unsigned char padding0[32]; struct reg_128 st[8]; struct reg_128 xmm0[16]; @@ -57,6 +61,7 @@ struct reg_512 zmm1[16]; unsigned char padding4[8]; }; + #pragma pack(pop) """ fpregs_define = """ @@ -69,17 +74,16 @@ unsigned char data[16]; }; - struct reg_256 - { - unsigned char data[32]; - }; - // For details about the layout of the xsave structure, see Intel's Architecture Instruction Set Extensions Programming Reference // Chapter 3.2.4 "The Layout of XSAVE Save Area" // https://www.intel.com/content/dam/develop/external/us/en/documents/319433-024-697869.pdf + #pragma pack(push, 1) struct fp_regs_struct { unsigned long type; + _Bool dirty; // true if the debugging script has modified the state of the registers + _Bool fresh; // true if the registers have already been fetched for this state + unsigned char bool_padding[6]; unsigned char padding0[32]; struct reg_128 st[8]; struct reg_128 xmm0[16]; @@ -90,6 +94,7 @@ struct reg_128 ymm0[16]; unsigned char padding3[64]; }; + #pragma pack(pop) """ fpregs_define = """ @@ -105,14 +110,19 @@ // For details about the layout of the xsave structure, see Intel's Architecture Instruction Set Extensions Programming Reference // Chapter 3.2.4 "The Layout of XSAVE Save Area" // https://www.intel.com/content/dam/develop/external/us/en/documents/319433-024-697869.pdf + #pragma pack(push, 1) struct fp_regs_struct { unsigned long type; + _Bool dirty; // true if the debugging script has modified the state of the registers + _Bool fresh; // true if the registers have already been fetched for this state + unsigned char bool_padding[6]; unsigned char padding0[32]; struct reg_128 st[8]; struct reg_128 xmm0[16]; unsigned char padding1[96]; }; + #pragma pack(pop) """ fpregs_define = """ @@ -201,10 +211,9 @@ ffibuilder = FFI() -ffibuilder.cdef( - user_regs_struct - + fp_regs_struct - + """ +ffibuilder.cdef(user_regs_struct) +ffibuilder.cdef(fp_regs_struct, packed=True) +ffibuilder.cdef(""" struct ptrace_hit_bp { int pid; uint64_t addr; diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 66920cd7..9675001b 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -26,11 +26,11 @@ #endif #if (FPREGS_AVX == 0) - _Static_assert(sizeof(struct fp_regs_struct) == 520, "user_fpregs_struct size is not 512 bytes"); + _Static_assert((sizeof(struct fp_regs_struct) - offsetof(struct fp_regs_struct, padding0)) == 512, "user_fpregs_struct size is not 512 bytes"); #elif (FPREGS_AVX == 1) - _Static_assert(sizeof(struct fp_regs_struct) == 904, "user_fpregs_struct size is not 896 bytes"); + _Static_assert((sizeof(struct fp_regs_struct) - offsetof(struct fp_regs_struct, padding0)) == 896, "user_fpregs_struct size is not 896 bytes"); #elif (FPREGS_AVX == 2) - _Static_assert(sizeof(struct fp_regs_struct) == 2704, "user_fpregs_struct size is not 2696 bytes"); + _Static_assert((sizeof(struct fp_regs_struct) - offsetof(struct fp_regs_struct, padding0)) == 2696, "user_fpregs_struct size is not 2696 bytes"); #else #error "FPREGS_AVX must be 0, 1 or 2" #endif @@ -101,12 +101,14 @@ void get_fp_regs(int tid, struct fp_regs_struct *fpregs) struct iovec iov; iov.iov_base = (unsigned char *)(fpregs) + offsetof(struct fp_regs_struct, padding0); - iov.iov_len = sizeof(struct fp_regs_struct) - sizeof(unsigned long); + iov.iov_len = sizeof(struct fp_regs_struct) - offsetof(struct fp_regs_struct, padding0); if (ptrace(PTRACE_GETREGSET, tid, NT_X86_XSTATE, &iov) == -1) { perror("ptrace_getregset_xstate"); } #endif + + fpregs->fresh = 1; } void set_fp_regs(int tid, struct fp_regs_struct *fpregs) @@ -117,12 +119,22 @@ void set_fp_regs(int tid, struct fp_regs_struct *fpregs) struct iovec iov; iov.iov_base = (unsigned char *)(fpregs) + offsetof(struct fp_regs_struct, padding0); - iov.iov_len = sizeof(struct fp_regs_struct) - sizeof(unsigned long); + iov.iov_len = sizeof(struct fp_regs_struct) - offsetof(struct fp_regs_struct, padding0); if (ptrace(PTRACE_SETREGSET, tid, NT_X86_XSTATE, &iov) == -1) { perror("ptrace_setregset_xstate"); } #endif + + fpregs->dirty = 0; + fpregs->fresh = 0; +} + +void check_and_set_fp_regs(struct thread *t) +{ + if (t->fpregs.dirty) { + set_fp_regs(t->tid, &t->fpregs); + } } struct user_regs_struct *register_thread(struct global_state *state, int tid) @@ -139,6 +151,8 @@ struct user_regs_struct *register_thread(struct global_state *state, int tid) t->signal_to_forward = 0; t->fpregs.type = FPREGS_AVX; + t->fpregs.dirty = 0; + t->fpregs.fresh = 0; ptrace(PTRACE_GETREGS, tid, NULL, &t->regs); @@ -250,6 +264,7 @@ void ptrace_detach_for_migration(struct global_state *state, int pid) // set the registers again, as the first time it failed ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs); + check_and_set_fp_regs(t); } // Be sure that the thread will not run during gdb reattachment @@ -342,6 +357,9 @@ long singlestep(struct global_state *state, int tid) while (t != NULL) { if (ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs)) perror("ptrace_setregs"); + + check_and_set_fp_regs(t); + if (t->tid == tid) { signal_to_forward = t->signal_to_forward; t->signal_to_forward = 0; @@ -360,6 +378,8 @@ int step_until(struct global_state *state, int tid, uint64_t addr, int max_steps if (ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs)) perror("ptrace_setregs"); + check_and_set_fp_regs(t); + if (t->tid == tid) stepping_thread = t; @@ -407,6 +427,9 @@ int prepare_for_run(struct global_state *state, int pid) if (ptrace(PTRACE_SETREGS, t->tid, NULL, &t->regs)) fprintf(stderr, "ptrace_setregs failed for thread %d: %s\\n", t->tid, strerror(errno)); + + check_and_set_fp_regs(t); + t = t->next; } diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index ebd082d1..645e5f3c 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -647,15 +647,16 @@ def fetch_fp_registers(self: PtraceInterface, registers: Registers) -> None: Args: registers (Registers): The registers instance to update. """ + liblog.debugger("Fetching floating-point registers for thread %d", registers._thread_id) self.lib_trace.get_fp_regs(registers._thread_id, registers._fp_register_file) - def flush_fp_registers(self: PtraceInterface, registers: Registers) -> None: + def flush_fp_registers(self: PtraceInterface, _: Registers) -> None: """Flushes the floating-point registers of the specified thread. Args: registers (Registers): The registers instance to update. """ - self.lib_trace.set_fp_regs(registers._thread_id, registers._fp_register_file) + raise NotImplementedError("Flushing floating-point registers is automatically handled by the native code.") def _peek_user(self: PtraceInterface, thread_id: int, address: int) -> int: """Reads the memory at the specified address.""" From d18710a6e35d9e2ffdce6f8f231fd18066c4fc70 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sat, 3 Aug 2024 15:38:06 +0200 Subject: [PATCH 074/144] test: add accessor check for floating point registers --- test/scripts/floating_point_test.py | 39 ++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/test/scripts/floating_point_test.py b/test/scripts/floating_point_test.py index d213ff38..733c725e 100644 --- a/test/scripts/floating_point_test.py +++ b/test/scripts/floating_point_test.py @@ -37,7 +37,7 @@ def avx512(self): d.run() - bp1 = d.bp(0x40143e) + bp1 = d.bp(0x40143E) bp2 = d.bp(0x401467) d.cont() @@ -66,7 +66,7 @@ def avx512(self): self.assertTrue(bp2.hit_on(d)) for i in range(32): - val = randint(0, 2 ** 512 - 1) + val = randint(0, 2**512 - 1) setattr(d.regs, f"zmm{i}", val) self.assertEqual(getattr(d.regs, f"zmm{i}"), val) @@ -146,11 +146,42 @@ def avx(self): self.assertTrue(bp2.hit_on(d)) for i in range(16): - val = randint(0, 2 ** 256 - 1) + val = randint(0, 2**256 - 1) setattr(d.regs, f"ymm{i}", val) self.assertEqual(getattr(d.regs, f"xmm{i}"), val & ((1 << 128) - 1)) self.assertEqual(getattr(d.regs, f"ymm{i}"), val) + # validate that register states are correctly flushed and then restored + values = [] + + for i in range(16): + val = randint(0, 2**256 - 1) + setattr(d.regs, f"ymm{i}", val) + values.append(val) + + d.step() + + for i in range(16): + self.assertEqual(getattr(d.regs, f"ymm{i}"), values[i]) + + d.regs.ymm7 = 0xDEADBEEFDEADBEEF + + for i in range(16): + if i == 7: + continue + + self.assertEqual(getattr(d.regs, f"ymm{i}"), values[i]) + + d.step() + + for i in range(16): + if i == 7: + continue + + self.assertEqual(getattr(d.regs, f"ymm{i}"), values[i]) + + self.assertEqual(d.regs.ymm7, 0xDEADBEEFDEADBEEF) + d.kill() def callback(t, _): @@ -228,7 +259,7 @@ def mmx(self): self.assertTrue(bp2.hit_on(d)) for i in range(16): - val = randint(0, 2 ** 128 - 1) + val = randint(0, 2**128 - 1) setattr(d.regs, f"xmm{i}", val) self.assertEqual(getattr(d.regs, f"xmm{i}"), val) From a70e92ecaea6fb247a00b2a2745d0a6f61f6f57d Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 15:00:44 +0200 Subject: [PATCH 075/144] fix: make cffi build on aarch64 after merge --- libdebug/cffi/ptrace_cffi_build.py | 2 ++ libdebug/cffi/ptrace_cffi_source.c | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 84f2755d..15b8151d 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -223,6 +223,8 @@ // /usr/include/aarch64-linux-gnu/asm/ptrace.h struct fp_regs_struct { + _Bool dirty; // true if the debugging script has modified the state of the registers + _Bool fresh; // true if the registers have already been fetched for this state struct reg_128 vregs[32]; unsigned int fpsr; unsigned int fpcr; diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index acf6423b..14b25b47 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -462,20 +462,20 @@ void get_fp_regs(int tid, struct fp_regs_struct *fpregs) { struct iovec iov; - iov.iov_base = (unsigned char *)(fpregs); - iov.iov_len = sizeof(struct fp_regs_struct); + iov.iov_base = (unsigned char *)(fpregs) + offsetof(struct fp_regs_struct, vregs); + iov.iov_len = sizeof(struct fp_regs_struct) - offsetof(struct fp_regs_struct, vregs); if (ptrace(PTRACE_GETREGSET, tid, NT_FPREGSET, &iov) == -1) { perror("ptrace_getregset_xstate"); } } -void get_fp_regs(int tid, struct fp_regs_struct *fpregs) +void set_fp_regs(int tid, struct fp_regs_struct *fpregs) { struct iovec iov; - iov.iov_base = (unsigned char *)(fpregs); - iov.iov_len = sizeof(struct fp_regs_struct); + iov.iov_base = (unsigned char *)(fpregs) + offsetof(struct fp_regs_struct, vregs); + iov.iov_len = sizeof(struct fp_regs_struct) - offsetof(struct fp_regs_struct, vregs); if (ptrace(PTRACE_SETREGSET, tid, NT_FPREGSET, &iov) == -1) { perror("ptrace_setregset_xstate"); From e673cb2d76fdc813112218842258aa46ce9e84be Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 15:02:02 +0200 Subject: [PATCH 076/144] feat: move fp regs dirty check out of amd64-specific section --- libdebug/cffi/ptrace_cffi_source.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 14b25b47..b7288fc5 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -448,13 +448,6 @@ void set_fp_regs(int tid, struct fp_regs_struct *fpregs) fpregs->dirty = 0; fpregs->fresh = 0; } - -void check_and_set_fp_regs(struct thread *t) -{ - if (t->fpregs.dirty) { - set_fp_regs(t->tid, &t->fpregs); - } -} #endif #ifdef ARCH_AARCH64 @@ -483,6 +476,13 @@ void set_fp_regs(int tid, struct fp_regs_struct *fpregs) } #endif +void check_and_set_fp_regs(struct thread *t) +{ + if (t->fpregs.dirty) { + set_fp_regs(t->tid, &t->fpregs); + } +} + struct ptrace_regs_struct *register_thread(struct global_state *state, int tid) { // Verify if the thread is already registered From 1b31d7cfabd9f0b4f118d1f65b44ac6f9dbdb889 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 15:05:54 +0200 Subject: [PATCH 077/144] fix: add struct packing to aarch64 fp-regs struct --- libdebug/cffi/ptrace_cffi_build.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 15b8151d..9c7293e5 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -221,15 +221,18 @@ }; // /usr/include/aarch64-linux-gnu/asm/ptrace.h + #pragma pack(push, 1) struct fp_regs_struct { _Bool dirty; // true if the debugging script has modified the state of the registers _Bool fresh; // true if the registers have already been fetched for this state + unsigned char padding[2]; struct reg_128 vregs[32]; unsigned int fpsr; unsigned int fpcr; unsigned long padding; }; + #pragma pack(pop) """ fpregs_define = "" From 09e663ffefb00541d583767d541f537a7824aa81 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 15:07:12 +0200 Subject: [PATCH 078/144] fix: rename padding after utility bools in fp-regs struct I hate blind edits --- libdebug/cffi/ptrace_cffi_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 9c7293e5..8cf5fa00 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -226,7 +226,7 @@ { _Bool dirty; // true if the debugging script has modified the state of the registers _Bool fresh; // true if the registers have already been fetched for this state - unsigned char padding[2]; + unsigned char bool_padding[2]; struct reg_128 vregs[32]; unsigned int fpsr; unsigned int fpcr; From 8acece08c4f7f3ff1fd7024592f0e39037ded5c6 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 15:11:56 +0200 Subject: [PATCH 079/144] fix: correct fp-regs access for aarch64 --- .../aarch64/aarch64_ptrace_register_holder.py | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py index 027b8417..317d55a6 100644 --- a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py +++ b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py @@ -47,70 +47,80 @@ def setter(self: Aarch64Registers, value: int) -> None: def _get_property_fp_8(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) & 0xFF def setter(self: Aarch64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) data = value.to_bytes(1, sys.byteorder) self._fp_register_file.vregs[index].data = data - self._internal_debugger._flush_fp_registers(self._thread_id) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_16(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) & 0xFFFF def setter(self: Aarch64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) data = value.to_bytes(2, sys.byteorder) self._fp_register_file.vregs[index].data = data - self._internal_debugger._flush_fp_registers(self._thread_id) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_32(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) & 0xFFFFFFFF def setter(self: Aarch64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) data = value.to_bytes(4, sys.byteorder) self._fp_register_file.vregs[index].data = data - self._internal_debugger._flush_fp_registers(self._thread_id) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_64(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) & 0xFFFFFFFFFFFFFFFF def setter(self: Aarch64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) data = value.to_bytes(8, sys.byteorder) self._fp_register_file.vregs[index].data = data - self._internal_debugger._flush_fp_registers(self._thread_id) + self._fp_register_file.dirty = True return property(getter, setter, None, name) def _get_property_fp_128(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: - self._internal_debugger._fetch_fp_registers(self._thread_id) + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) return int.from_bytes(self._fp_register_file.vregs[index].data, sys.byteorder) def setter(self: Aarch64Registers, value: int) -> None: - self._internal_debugger._ensure_process_stopped() + if not self._fp_register_file.fresh: + self._internal_debugger._fetch_fp_registers(self) data = value.to_bytes(16, sys.byteorder) self._fp_register_file.vregs[index].data = data - self._internal_debugger._flush_fp_registers(self._thread_id) + self._fp_register_file.dirty = True return property(getter, setter, None, name) From d29073efe3ed985f8fed14ad9cc75f6fbdacd5e1 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 15:14:29 +0200 Subject: [PATCH 080/144] test: update backtrace_test for aarch64 --- test/aarch64/scripts/backtrace_test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/aarch64/scripts/backtrace_test.py b/test/aarch64/scripts/backtrace_test.py index 97ea5a3a..7c497e13 100644 --- a/test/aarch64/scripts/backtrace_test.py +++ b/test/aarch64/scripts/backtrace_test.py @@ -29,28 +29,28 @@ def test_backtrace(self): d.cont() self.assertTrue(d.regs.pc == bp0.address) - backtrace = d.backtrace() + backtrace = d.backtrace(as_symbols=True) self.assertIn("_start", backtrace.pop()) self.assertEqual(backtrace[:1], ["main+8"]) d.cont() self.assertTrue(d.regs.pc == bp1.address) - backtrace = d.backtrace() + backtrace = d.backtrace(as_symbols=True) self.assertIn("_start", backtrace.pop()) self.assertEqual(backtrace[:2], ["function1+8", "main+c"]) d.cont() self.assertTrue(d.regs.pc == bp2.address) - backtrace = d.backtrace() + backtrace = d.backtrace(as_symbols=True) self.assertIn("_start", backtrace.pop()) self.assertEqual(backtrace[:3], ["function2+8", "function1+10", "main+c"]) d.cont() self.assertTrue(d.regs.pc == bp3.address) - backtrace = d.backtrace() + backtrace = d.backtrace(as_symbols=True) self.assertIn("_start", backtrace.pop()) self.assertEqual( backtrace[:4], ["function3+8", "function2+18", "function1+10", "main+c"] @@ -59,7 +59,7 @@ def test_backtrace(self): d.cont() self.assertTrue(d.regs.pc == bp4.address) - backtrace = d.backtrace() + backtrace = d.backtrace(as_symbols=True) self.assertIn("_start", backtrace.pop()) self.assertEqual( backtrace[:5], @@ -69,7 +69,7 @@ def test_backtrace(self): d.cont() self.assertTrue(d.regs.pc == bp5.address) - backtrace = d.backtrace() + backtrace = d.backtrace(as_symbols=True) self.assertIn("_start", backtrace.pop()) self.assertEqual( backtrace[:6], @@ -86,7 +86,7 @@ def test_backtrace(self): d.cont() self.assertTrue(d.regs.pc == bp6.address) - backtrace = d.backtrace() + backtrace = d.backtrace(as_symbols=True) self.assertIn("_start", backtrace.pop()) self.assertEqual( backtrace[:7], From f652648ed9bee46241899fa4d5c3529824df1e9a Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 15:17:30 +0200 Subject: [PATCH 081/144] fix: correctly set dirty and fresh flags for fp regs for aarch64 --- libdebug/cffi/ptrace_cffi_source.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index b7288fc5..efeffcf9 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -461,6 +461,8 @@ void get_fp_regs(int tid, struct fp_regs_struct *fpregs) if (ptrace(PTRACE_GETREGSET, tid, NT_FPREGSET, &iov) == -1) { perror("ptrace_getregset_xstate"); } + + fpregs->fresh = 1; } void set_fp_regs(int tid, struct fp_regs_struct *fpregs) @@ -473,6 +475,9 @@ void set_fp_regs(int tid, struct fp_regs_struct *fpregs) if (ptrace(PTRACE_SETREGSET, tid, NT_FPREGSET, &iov) == -1) { perror("ptrace_setregset_xstate"); } + + fpregs->dirty = 0; + fpregs->fresh = 0; } #endif From 9c22299c321cdcd46de11a1d780d5723fc643913 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 20:38:13 +0200 Subject: [PATCH 082/144] test: add aarch64 control flow test --- test/aarch64/run_suite.py | 2 + test/aarch64/scripts/control_flow_test.py | 183 ++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 test/aarch64/scripts/control_flow_test.py diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index 7fd4bb00..aae68554 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -18,6 +18,7 @@ from scripts.builtin_handler_test import BuiltinHandlerTest from scripts.callback_test import CallbackTest from scripts.catch_signal_test import CatchSignalTest +from scripts.control_flow_test import ControlFlowTest from scripts.death_test import DeathTest from scripts.floating_point_test import FloatingPointTest from scripts.handle_syscall_test import HandleSyscallTest @@ -44,6 +45,7 @@ def fast_suite(): suite.addTest(TestLoader().loadTestsFromTestCase(BuiltinHandlerTest)) suite.addTest(TestLoader().loadTestsFromTestCase(CallbackTest)) suite.addTest(TestLoader().loadTestsFromTestCase(CatchSignalTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(ControlFlowTest)) suite.addTest(TestLoader().loadTestsFromTestCase(DeathTest)) suite.addTest(TestLoader().loadTestsFromTestCase(FloatingPointTest)) suite.addTest(TestLoader().loadTestsFromTestCase(HandleSyscallTest)) diff --git a/test/aarch64/scripts/control_flow_test.py b/test/aarch64/scripts/control_flow_test.py new file mode 100644 index 00000000..7aa3e1fb --- /dev/null +++ b/test/aarch64/scripts/control_flow_test.py @@ -0,0 +1,183 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger + + +class ControlFlowTest(unittest.TestCase): + def test_step_until_1(self): + d = debugger("binaries/breakpoint_test") + d.run() + + bp = d.breakpoint("main") + d.cont() + + self.assertTrue(bp.hit_on(d)) + + d.step_until(0x0000aaaaaaaa0854) + + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa0854) + self.assertTrue(bp.hit_count == 1) + self.assertFalse(bp.hit_on(d)) + + d.kill() + d.terminate() + + def test_step_until_2(self): + d = debugger("binaries/breakpoint_test") + d.run() + + bp = d.breakpoint(0x7fc, hardware=True) + d.cont() + + self.assertTrue(bp.hit_on(d)) + + d.step_until(0x0000aaaaaaaa0854, max_steps=7) + + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa0818) + self.assertTrue(bp.hit_count == 1) + self.assertFalse(bp.hit_on(d)) + + d.kill() + d.terminate() + + def test_step_until_3(self): + d = debugger("binaries/breakpoint_test") + d.run() + + bp = d.breakpoint(0x7fc) + + # Let's put some breakpoints in-between + d.breakpoint(0x804) + d.breakpoint(0x80c) + d.breakpoint(0x808, hardware=True) + + d.cont() + + self.assertTrue(bp.hit_on(d)) + + # trace is [0x7fc, 0x800, 0x804, 0x808, 0x80c, 0x810, 0x814, 0x818] + d.step_until(0x0000aaaaaaaa0854, max_steps=7) + + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa0818) + self.assertTrue(bp.hit_count == 1) + self.assertFalse(bp.hit_on(d)) + + d.kill() + d.terminate() + + def test_step_and_cont(self): + d = debugger("binaries/breakpoint_test") + d.run() + + bp1 = d.breakpoint("main") + bp2 = d.breakpoint("random_function") + d.cont() + + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.step() + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa083c) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.step() + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa0840) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.cont() + + self.assertTrue(bp2.hit_on(d)) + + d.cont() + + d.kill() + d.terminate() + + def test_step_and_cont_hardware(self): + d = debugger("binaries/breakpoint_test") + d.run() + + bp1 = d.breakpoint("main", hardware=True) + bp2 = d.breakpoint("random_function", hardware=True) + d.cont() + + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.step() + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa083c) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.step() + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa0840) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.cont() + + self.assertTrue(bp2.hit_on(d)) + + d.cont() + + d.kill() + d.terminate() + + def test_step_until_and_cont(self): + d = debugger("binaries/breakpoint_test") + d.run() + + bp1 = d.breakpoint("main") + bp2 = d.breakpoint("random_function") + d.cont() + + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.step_until(0x0000aaaaaaaa083c) + + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa083c) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.cont() + + self.assertTrue(bp2.hit_on(d)) + + d.cont() + + d.kill() + d.terminate() + + def test_step_until_and_cont_hardware(self): + d = debugger("binaries/breakpoint_test") + d.run() + + bp1 = d.breakpoint("main", hardware=True) + bp2 = d.breakpoint("random_function", hardware=True) + d.cont() + + self.assertTrue(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.step_until(0x0000aaaaaaaa083c) + self.assertTrue(d.regs.pc == 0x0000aaaaaaaa083c) + self.assertFalse(bp1.hit_on(d)) + self.assertFalse(bp2.hit_on(d)) + + d.cont() + + self.assertTrue(bp2.hit_on(d)) + + d.cont() + + d.kill() + d.terminate() \ No newline at end of file From 018c43bcdf5ee82d266dbd86906b795e4c76a175 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 20:38:37 +0200 Subject: [PATCH 083/144] fix: make step_until work for aarch64 You can't step over hardware breakpoints --- libdebug/cffi/ptrace_cffi_source.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index efeffcf9..fac835df 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -818,6 +818,16 @@ int step_until(struct global_state *state, int tid, uint64_t addr, int max_steps return -1; } + // remove any hardware breakpoint that might be set on the stepping thread + struct hardware_breakpoint *bp = state->hw_b_HEAD; + + while (bp != NULL) { + if (bp->tid == tid && bp->enabled) { + remove_hardware_breakpoint(bp); + } + bp = bp->next; + } + while (max_steps == -1 || count < max_steps) { if (ptrace(PTRACE_SINGLESTEP, tid, NULL, NULL)) return -1; @@ -838,6 +848,16 @@ int step_until(struct global_state *state, int tid, uint64_t addr, int max_steps count++; } + // re-add the hardware breakpoints + bp = state->hw_b_HEAD; + + while (bp != NULL) { + if (bp->tid == tid && bp->enabled) { + install_hardware_breakpoint(bp); + } + bp = bp->next; + } + return 0; } From 1ff3c2ca9bf2ecbf46c381435f11408bb9ce2357 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Sun, 4 Aug 2024 20:54:03 +0200 Subject: [PATCH 084/144] fix: specify arch for stack unwinding during print_backtrace() --- libdebug/state/thread_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index 2ca01595..c814d57b 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -191,7 +191,7 @@ def backtrace(self: ThreadContext, as_symbols: bool = False) -> list: def print_backtrace(self: ThreadContext) -> None: """Prints the current backtrace of the thread.""" self._internal_debugger._ensure_process_stopped() - stack_unwinder = stack_unwinding_provider() + stack_unwinder = stack_unwinding_provider(self._internal_debugger.arch) backtrace = stack_unwinder.unwind(self) maps = self._internal_debugger.debugging_interface.maps() for return_address in backtrace: From 4199f07db7eab12cf2d7bb077e9388f46183550d Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 00:10:38 +0200 Subject: [PATCH 085/144] fix: correct opcode identification for aarch64 --- libdebug/cffi/ptrace_cffi_build.py | 4 ++-- libdebug/cffi/ptrace_cffi_source.c | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index 8cf5fa00..979feeb4 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -296,12 +296,12 @@ int IS_CALL_INSTRUCTION(uint8_t* instr) { // Check for direct CALL (BL) - if ((instr[0] & 0xFC) == 0x94) { + if ((instr[3] & 0xFC) == 0x94) { return 1; // It's a CALL } // Check for indirect CALL (BLR) - if ((instr[0] & 0xFC) == 0x90) { + if ((instr[3] == 0xD6 && (instr[2] & 0x3F) == 0x3F)) { return 1; // It's a CALL } diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index fac835df..c75e4174 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -1211,7 +1211,7 @@ int stepping_finish(struct global_state *state, int tid) } uint64_t previous_ip, current_ip; - uint64_t opcode_window, first_opcode_byte; + uint64_t opcode_window, opcode; // We need to keep track of the nested calls int nested_call_counter = 1; @@ -1231,18 +1231,26 @@ int stepping_finish(struct global_state *state, int tid) // Get value at current instruction pointer opcode_window = ptrace(PTRACE_PEEKDATA, tid, (void *)current_ip, NULL); - first_opcode_byte = opcode_window & 0xFF; + +#ifdef ARCH_AMD64 + // on amd64 we care only about the first byte + opcode = opcode_window & 0xFF; +#endif + +#ifdef ARCH_AARCH64 + opcode = opcode_window & 0xFFFFFFFF; +#endif // if the instruction pointer didn't change, we return // because we hit a hardware breakpoint // we do the same if we hit a software breakpoint - if (current_ip == previous_ip || IS_SW_BREAKPOINT(first_opcode_byte)) + if (current_ip == previous_ip || IS_SW_BREAKPOINT(opcode)) goto cleanup; // If we hit a call instruction, we increment the counter if (IS_CALL_INSTRUCTION((uint8_t*) &opcode_window)) nested_call_counter++; - else if (IS_RET_INSTRUCTION(first_opcode_byte)) + else if (IS_RET_INSTRUCTION(opcode)) nested_call_counter--; } while (nested_call_counter > 0); From 95b5363e30cbb53ded33854c1dbc93b5e43ac178 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 00:11:04 +0200 Subject: [PATCH 086/144] test: add finish_test.py for aarch64 --- test/aarch64/run_suite.py | 2 + test/aarch64/scripts/finish_test.py | 325 ++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 test/aarch64/scripts/finish_test.py diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index aae68554..619514d6 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -20,6 +20,7 @@ from scripts.catch_signal_test import CatchSignalTest from scripts.control_flow_test import ControlFlowTest from scripts.death_test import DeathTest +from scripts.finish_test import FinishTest from scripts.floating_point_test import FloatingPointTest from scripts.handle_syscall_test import HandleSyscallTest from scripts.hijack_syscall_test import HijackSyscallTest @@ -47,6 +48,7 @@ def fast_suite(): suite.addTest(TestLoader().loadTestsFromTestCase(CatchSignalTest)) suite.addTest(TestLoader().loadTestsFromTestCase(ControlFlowTest)) suite.addTest(TestLoader().loadTestsFromTestCase(DeathTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(FinishTest)) suite.addTest(TestLoader().loadTestsFromTestCase(FloatingPointTest)) suite.addTest(TestLoader().loadTestsFromTestCase(HandleSyscallTest)) suite.addTest(TestLoader().loadTestsFromTestCase(HijackSyscallTest)) diff --git a/test/aarch64/scripts/finish_test.py b/test/aarch64/scripts/finish_test.py new file mode 100644 index 00000000..7354b204 --- /dev/null +++ b/test/aarch64/scripts/finish_test.py @@ -0,0 +1,325 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Francesco Panebianco, Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# +import unittest + +from libdebug import debugger +from libdebug.architectures.stack_unwinding_provider import stack_unwinding_provider + +# Addresses of the dummy functions +C_ADDRESS = 0xaaaaaaaa0914 +B_ADDRESS = 0xaaaaaaaa08fc +A_ADDRESS = 0xaaaaaaaa0814 + +# Addresses of noteworthy instructions +RETURN_POINT_FROM_C = 0xaaaaaaaa0938 +RETURN_POINT_FROM_A = 0xaaaaaaaa0908 + +class FinishTest(unittest.TestCase): + def setUp(self): + pass + + def test_finish_exact_no_auto_interrupt_no_breakpoint(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + + # ------------------ Block 1 ------------------ # + # Return from the first function call # + # --------------------------------------------- # + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + # Finish function c + d.finish(heuristic="step-mode") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_C) + + d.kill() + + # ------------------ Block 2 ------------------ # + # Return from the nested function call # + # --------------------------------------------- # + + # Reach function a + d.run() + d.breakpoint(A_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, A_ADDRESS) + + # Finish function a + d.finish(heuristic="step-mode") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_A) + + d.kill() + + def test_finish_heuristic_no_auto_interrupt_no_breakpoint(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + + # ------------------ Block 1 ------------------ # + # Return from the first function call # + # --------------------------------------------- # + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + # Finish function c + d.finish(heuristic="backtrace") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_C) + + d.kill() + + # ------------------ Block 2 ------------------ # + # Return from the nested function call # + # --------------------------------------------- # + + # Reach function a + d.run() + d.breakpoint(A_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, A_ADDRESS) + + # Finish function a + d.finish(heuristic="backtrace") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_A) + + d.kill() + + def test_finish_exact_auto_interrupt_no_breakpoint(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=True) + + # ------------------ Block 1 ------------------ # + # Return from the first function call # + # --------------------------------------------- # + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + d.wait() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + # Finish function c + d.finish(heuristic="step-mode") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_C) + + d.kill() + + # ------------------ Block 2 ------------------ # + # Return from the nested function call # + # --------------------------------------------- # + + # Reach function a + d.run() + d.breakpoint(A_ADDRESS) + d.cont() + d.wait() + + self.assertEqual(d.regs.pc, A_ADDRESS) + + # Finish function a + d.finish(heuristic="step-mode") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_A) + + d.kill() + + def test_finish_heuristic_auto_interrupt_no_breakpoint(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=True) + + # ------------------ Block 1 ------------------ # + # Return from the first function call # + # --------------------------------------------- # + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + d.wait() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + # Finish function c + d.finish(heuristic="backtrace") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_C) + + d.kill() + + # ------------------ Block 2 ------------------ # + # Return from the nested function call # + # --------------------------------------------- # + + # Reach function a + d.run() + d.breakpoint(A_ADDRESS) + d.cont() + d.wait() + + self.assertEqual(d.regs.pc, A_ADDRESS) + + # Finish function a + d.finish(heuristic="backtrace") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_A) + + d.kill() + + def test_finish_exact_no_auto_interrupt_breakpoint(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + d.breakpoint(A_ADDRESS) + + # Finish function c + d.finish(heuristic="step-mode") + + self.assertEqual(d.regs.pc, A_ADDRESS, f"Expected {hex(A_ADDRESS)} but got {hex(d.regs.pc)}") + + d.kill() + + def test_finish_heuristic_no_auto_interrupt_breakpoint(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + d.breakpoint(A_ADDRESS) + + # Finish function c + d.finish(heuristic="backtrace") + + self.assertEqual(d.regs.pc, A_ADDRESS) + + d.kill() + + def test_heuristic_return_address(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + stack_unwinder = stack_unwinding_provider(d._internal_debugger.arch) + + # We need to repeat the check for the three stages of the function preamble + + # Get current return address + curr_srip = stack_unwinder.get_return_address(d) + self.assertEqual(curr_srip, RETURN_POINT_FROM_C) + + d.step() + + # Get current return address + curr_srip = stack_unwinder.get_return_address(d) + self.assertEqual(curr_srip, RETURN_POINT_FROM_C) + + d.step() + + # Get current return address + curr_srip = stack_unwinder.get_return_address(d) + self.assertEqual(curr_srip, RETURN_POINT_FROM_C) + + d.kill() + + def test_exact_breakpoint_return(self): + BREAKPOINT_LOCATION = 0xaaaaaaaa0920 + + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + + # Place a breakpoint at a location inbetween + d.breakpoint(BREAKPOINT_LOCATION) + + # Finish function c + d.finish(heuristic="step-mode") + + self.assertEqual(d.regs.pc, BREAKPOINT_LOCATION) + + d.kill() + + def test_heuristic_breakpoint_return(self): + BREAKPOINT_LOCATION = 0xaaaaaaaa0920 + + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + + # Place a breakpoint a location in between + d.breakpoint(BREAKPOINT_LOCATION) + + # Finish function c + d.finish(heuristic="backtrace") + + self.assertEqual(d.regs.pc, BREAKPOINT_LOCATION) + + d.kill() + + def test_breakpoint_collision(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + + # Reach function c + d.run() + d.breakpoint(C_ADDRESS) + d.cont() + + self.assertEqual(d.regs.pc, C_ADDRESS) + + # Place a breakpoint at the same location as the return address + d.breakpoint(RETURN_POINT_FROM_C) + + # Finish function c + d.finish(heuristic="backtrace") + + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_C) + self.assertFalse(d.running) + + d.step() + + # Check that the execution is still running and nothing has broken + self.assertFalse(d.running) + self.assertFalse(d.dead) + + d.kill() From 2a0bf2bb8faae901a5642b82f1c8560bb82b8bfd Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 03:17:46 +0200 Subject: [PATCH 087/144] feat: make memory_view.py an abstract class with multiple possible implementations --- ...memory_view.py => abstract_memory_view.py} | 112 ++++------------- libdebug/data/chunked_memory_view.py | 114 ++++++++++++++++++ libdebug/data/direct_memory_view.py | 74 ++++++++++++ libdebug/debugger/internal_debugger.py | 19 ++- libdebug/ptrace/ptrace_interface.py | 1 - libdebug/ptrace/ptrace_status_handler.py | 3 +- libdebug/state/thread_context.py | 16 +-- libdebug/utils/debugger_wrappers.py | 5 +- 8 files changed, 234 insertions(+), 110 deletions(-) rename libdebug/data/{memory_view.py => abstract_memory_view.py} (72%) create mode 100644 libdebug/data/chunked_memory_view.py create mode 100644 libdebug/data/direct_memory_view.py diff --git a/libdebug/data/memory_view.py b/libdebug/data/abstract_memory_view.py similarity index 72% rename from libdebug/data/memory_view.py rename to libdebug/data/abstract_memory_view.py index 44ef6793..1e87a8f3 100644 --- a/libdebug/data/memory_view.py +++ b/libdebug/data/abstract_memory_view.py @@ -6,7 +6,8 @@ from __future__ import annotations -from collections.abc import Callable, MutableSequence +from abc import ABC, abstractmethod +from collections.abc import MutableSequence from typing import TYPE_CHECKING from libdebug.debugger.internal_debugger_instance_manager import provide_internal_debugger @@ -16,38 +17,22 @@ from libdebug.debugger.internal_debugger import InternalDebugger -class MemoryView(MutableSequence): - """A memory interface for the target process. +class AbstractMemoryView(MutableSequence, ABC): + """An abstract memory interface for the target process. - This class must be used to read and write memory of the target process. - - Attributes: - getter (Callable[[int], bytes]): A function that reads memory from the target process. - setter (Callable[[int, bytes], None]): A function that writes memory to the target process. - maps_provider (Callable[[], list[MemoryMap]]): A function that returns the memory maps of the target process. - unit_size (int, optional): The data size used by the getter and setter functions. Defaults to 8. - align_to (int, optional): The address alignment that must be used when reading and writing memory. Defaults to 1. + An implementation of class must be used to read and write memory of the target process. """ context: InternalDebugger """The debugging context of the target process.""" - def __init__( - self: MemoryView, - getter: Callable[[int], bytes], - setter: Callable[[int, bytes], None], - unit_size: int = 8, - align_to: int = 1, - ) -> None: + def __init__(self: AbstractMemoryView) -> None: """Initializes the MemoryView.""" - self.getter = getter - self.setter = setter - self.unit_size = unit_size - self.align_to = align_to self._internal_debugger = provide_internal_debugger(self) self.maps_provider = self._internal_debugger.debugging_interface.maps - def read(self: MemoryView, address: int, size: int) -> bytes: + @abstractmethod + def read(self: AbstractMemoryView, address: int, size: int) -> bytes: """Reads memory from the target process. Args: @@ -57,72 +42,17 @@ def read(self: MemoryView, address: int, size: int) -> bytes: Returns: bytes: The read bytes. """ - if self.align_to == 1: - data = b"" - - remainder = size % self.unit_size - - for i in range(address, address + size - remainder, self.unit_size): - data += self.getter(i) - - if remainder: - data += self.getter(address + size - remainder)[:remainder] - - return data - else: - prefix = address % self.align_to - prefix_size = self.unit_size - prefix - - data = self.getter(address - prefix)[prefix:] - - remainder = (size - prefix_size) % self.unit_size - - for i in range( - address + prefix_size, - address + size - remainder, - self.unit_size, - ): - data += self.getter(i) - - if remainder: - data += self.getter(address + size - remainder)[:remainder] - return data - - def write(self: MemoryView, address: int, data: bytes) -> None: + @abstractmethod + def write(self: AbstractMemoryView, address: int, data: bytes) -> None: """Writes memory to the target process. Args: address (int): The address to write to. data (bytes): The data to write. """ - size = len(data) - - if self.align_to == 1: - remainder = size % self.unit_size - base = address - else: - prefix = address % self.align_to - prefix_size = self.unit_size - prefix - - prev_data = self.getter(address - prefix) - - self.setter(address - prefix, prev_data[:prefix_size] + data[:prefix]) - remainder = (size - prefix_size) % self.unit_size - base = address + prefix_size - - for i in range(base, address + size - remainder, self.unit_size): - self.setter(i, data[i - address : i - address + self.unit_size]) - - if remainder: - prev_data = self.getter(address + size - remainder) - self.setter( - address + size - remainder, - data[size - remainder :] + prev_data[remainder:], - ) - - def __getitem__(self: MemoryView, key: int | slice | str | tuple) -> bytes: + def __getitem__(self: AbstractMemoryView, key: int | slice | str | tuple) -> bytes: """Read from memory, either a single byte or a byte string. Args: @@ -130,7 +60,7 @@ def __getitem__(self: MemoryView, key: int | slice | str | tuple) -> bytes: """ return self._manage_memory_read_type(key) - def __setitem__(self: MemoryView, key: int | slice | str | tuple, value: bytes) -> None: + def __setitem__(self: AbstractMemoryView, key: int | slice | str | tuple, value: bytes) -> None: """Write to memory, either a single byte or a byte string. Args: @@ -141,7 +71,11 @@ def __setitem__(self: MemoryView, key: int | slice | str | tuple, value: bytes) raise TypeError("Invalid type for the value to write to memory. Expected bytes.") self._manage_memory_write_type(key, value) - def _manage_memory_read_type(self: MemoryView, key: int | slice | str | tuple, file: str = "hybrid") -> bytes: + def _manage_memory_read_type( + self: AbstractMemoryView, + key: int | slice | str | tuple, + file: str = "hybrid", + ) -> bytes: """Manage the read from memory, according to the typing. Args: @@ -183,7 +117,7 @@ def _manage_memory_read_type(self: MemoryView, key: int | slice | str | tuple, f else: raise TypeError("Invalid key type.") - def _manage_memory_read_tuple(self: MemoryView, key: tuple) -> bytes: + def _manage_memory_read_tuple(self: AbstractMemoryView, key: tuple) -> bytes: """Manage the read from memory, when the access is through a tuple. Args: @@ -223,7 +157,7 @@ def _manage_memory_read_tuple(self: MemoryView, key: tuple) -> bytes: raise ValueError("Invalid address.") from e def _manage_memory_write_type( - self: MemoryView, + self: AbstractMemoryView, key: int | slice | str | tuple, value: bytes, file: str = "hybrid", @@ -279,7 +213,7 @@ def _manage_memory_write_type( else: raise TypeError("Invalid key type.") - def _manage_memory_write_tuple(self: MemoryView, key: tuple, value: bytes) -> None: + def _manage_memory_write_tuple(self: AbstractMemoryView, key: tuple, value: bytes) -> None: """Manage the write to memory, when the access is through a tuple. Args: @@ -323,14 +257,14 @@ def _manage_memory_write_tuple(self: MemoryView, key: tuple, value: bytes) -> No except OSError as e: raise ValueError("Invalid address.") from e - def __delitem__(self: MemoryView, key: int | slice | str | tuple) -> None: + def __delitem__(self: AbstractMemoryView, key: int | slice | str | tuple) -> None: """MemoryView doesn't support deletion.""" raise NotImplementedError("MemoryView doesn't support deletion") - def __len__(self: MemoryView) -> None: + def __len__(self: AbstractMemoryView) -> None: """MemoryView doesn't support length.""" raise NotImplementedError("MemoryView doesn't support length") - def insert(self: MemoryView, index: int, value: int) -> None: + def insert(self: AbstractMemoryView, index: int, value: int) -> None: """MemoryView doesn't support insertion.""" raise NotImplementedError("MemoryView doesn't support insertion") diff --git a/libdebug/data/chunked_memory_view.py b/libdebug/data/chunked_memory_view.py new file mode 100644 index 00000000..c37cea06 --- /dev/null +++ b/libdebug/data/chunked_memory_view.py @@ -0,0 +1,114 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from libdebug.data.abstract_memory_view import AbstractMemoryView + +if TYPE_CHECKING: + from collections.abc import Callable + + +class ChunkedMemoryView(AbstractMemoryView): + """A memory interface for the target process, intended for chunk-based memory access. + + Attributes: + getter (Callable[[int], bytes]): A function that reads a chunk of memory from the target process. + setter (Callable[[int, bytes], None]): A function that writes a chunk of memory to the target process. + unit_size (int, optional): The chunk size used by the getter and setter functions. Defaults to 8. + align_to (int, optional): The address alignment that must be used when reading and writing memory. Defaults to 1. + """ + + def __init__( + self: ChunkedMemoryView, + getter: Callable[[int], bytes], + setter: Callable[[int, bytes], None], + unit_size: int = 8, + align_to: int = 1, + ) -> None: + """Initializes the MemoryView.""" + super().__init__() + self.getter = getter + self.setter = setter + self.unit_size = unit_size + self.align_to = align_to + + def read(self: ChunkedMemoryView, address: int, size: int) -> bytes: + """Reads memory from the target process. + + Args: + address (int): The address to read from. + size (int): The number of bytes to read. + + Returns: + bytes: The read bytes. + """ + if self.align_to == 1: + data = b"" + + remainder = size % self.unit_size + + for i in range(address, address + size - remainder, self.unit_size): + data += self.getter(i) + + if remainder: + data += self.getter(address + size - remainder)[:remainder] + + return data + else: + prefix = address % self.align_to + prefix_size = self.unit_size - prefix + + data = self.getter(address - prefix)[prefix:] + + remainder = (size - prefix_size) % self.unit_size + + for i in range( + address + prefix_size, + address + size - remainder, + self.unit_size, + ): + data += self.getter(i) + + if remainder: + data += self.getter(address + size - remainder)[:remainder] + + return data + + def write(self: ChunkedMemoryView, address: int, data: bytes) -> None: + """Writes memory to the target process. + + Args: + address (int): The address to write to. + data (bytes): The data to write. + """ + size = len(data) + + if self.align_to == 1: + remainder = size % self.unit_size + base = address + else: + prefix = address % self.align_to + prefix_size = self.unit_size - prefix + + prev_data = self.getter(address - prefix) + + self.setter(address - prefix, prev_data[:prefix_size] + data[:prefix]) + + remainder = (size - prefix_size) % self.unit_size + base = address + prefix_size + + for i in range(base, address + size - remainder, self.unit_size): + self.setter(i, data[i - address : i - address + self.unit_size]) + + if remainder: + prev_data = self.getter(address + size - remainder) + self.setter( + address + size - remainder, + data[size - remainder :] + prev_data[remainder:], + ) diff --git a/libdebug/data/direct_memory_view.py b/libdebug/data/direct_memory_view.py new file mode 100644 index 00000000..5978af72 --- /dev/null +++ b/libdebug/data/direct_memory_view.py @@ -0,0 +1,74 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from libdebug.data.abstract_memory_view import AbstractMemoryView + +if TYPE_CHECKING: + from collections.abc import Callable + + +class DirectMemoryView(AbstractMemoryView): + """A memory interface for the target process, intended for direct memory access. + + Attributes: + getter (Callable[[int, int], bytes]): A function that reads a variable amount of data from the target's memory. + setter (Callable[[int, bytes], None]): A function that writes memory to the target process. + align_to (int, optional): The address alignment that must be used when reading and writing memory. Defaults to 1. + """ + + def __init__( + self: DirectMemoryView, + getter: Callable[[int], bytes], + setter: Callable[[int, bytes], None], + align_to: int = 1, + ) -> None: + """Initializes the MemoryView.""" + super().__init__() + self.getter = getter + self.setter = setter + self.align_to = align_to + + def read(self: DirectMemoryView, address: int, size: int) -> bytes: + """Reads memory from the target process. + + Args: + address (int): The address to read from. + size (int): The number of bytes to read. + + Returns: + bytes: The read bytes. + """ + if self.align_to == 1: + return self.getter(address, size) + else: + prefix = address % self.align_to + base_address = address - prefix + new_size = size + prefix + data = self.getter(base_address, new_size) + return data[prefix : prefix + size] + + def write(self: DirectMemoryView, address: int, data: bytes) -> None: + """Writes memory to the target process. + + Args: + address (int): The address to write to. + data (bytes): The data to write. + """ + size = len(data) + + if self.align_to == 1: + self.setter(address, data) + else: + prefix = address % self.align_to + base_address = address - prefix + new_size = size + prefix + prefix_data = self.getter(base_address, new_size) + new_data = prefix_data[:prefix] + data + prefix_data[prefix + size :] + self.setter(base_address, new_data) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 107d1cc9..19e31c8f 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -23,7 +23,7 @@ from libdebug.builtin.antidebug_syscall_handler import on_enter_ptrace, on_exit_ptrace from libdebug.builtin.pretty_print_syscall_handler import pprint_on_enter, pprint_on_exit from libdebug.data.breakpoint import Breakpoint -from libdebug.data.memory_view import MemoryView +from libdebug.data.chunked_memory_view import ChunkedMemoryView from libdebug.data.signal_catcher import SignalCatcher from libdebug.data.syscall_handler import SyscallHandler from libdebug.debugger.internal_debugger_instance_manager import ( @@ -58,6 +58,7 @@ if TYPE_CHECKING: from collections.abc import Callable + from libdebug.data.abstract_memory_view import AbstractMemoryView from libdebug.data.memory_map import MemoryMap from libdebug.interfaces.debugging_interface import DebuggingInterface from libdebug.state.thread_context import ThreadContext @@ -118,7 +119,7 @@ class InternalDebugger: pipe_manager: PipeManager """The pipe manager used to communicate with the debugged process.""" - memory: MemoryView + memory: AbstractMemoryView """The memory view of the debugged process.""" debugging_interface: DebuggingInterface @@ -188,14 +189,13 @@ def clear(self: InternalDebugger) -> None: def start_up(self: InternalDebugger) -> None: """Starts up the context.""" - # The context is linked to itself link_to_internal_debugger(self, self) self.start_processing_thread() with extend_internal_debugger(self): self.debugging_interface = provide_debugging_interface() - self.memory = MemoryView(self._peek_memory, self._poke_memory) + self.memory = ChunkedMemoryView(self._peek_memory, self._poke_memory) def start_processing_thread(self: InternalDebugger) -> None: """Starts the thread that will poll the traced process for state change.""" @@ -470,15 +470,15 @@ def catch_signal( match signal_number: case SIGKILL.value: raise ValueError( - f"Cannot catch SIGKILL ({signal_number}) as it cannot be caught or ignored. This is a kernel restriction." + f"Cannot catch SIGKILL ({signal_number}) as it cannot be caught or ignored. This is a kernel restriction.", ) case SIGSTOP.value: raise ValueError( - f"Cannot catch SIGSTOP ({signal_number}) as it is used by the debugger or ptrace for their internal operations." + f"Cannot catch SIGSTOP ({signal_number}) as it is used by the debugger or ptrace for their internal operations.", ) case SIGTRAP.value: raise ValueError( - f"Cannot catch SIGTRAP ({signal_number}) as it is used by the debugger or ptrace for their internal operations." + f"Cannot catch SIGTRAP ({signal_number}) as it is used by the debugger or ptrace for their internal operations.", ) if signal_number in self.caught_signals: @@ -671,7 +671,6 @@ def hijack_syscall( @change_state_function_process def gdb(self: InternalDebugger, open_in_new_process: bool = True) -> None: """Migrates the current debugging session to GDB.""" - # TODO: not needed? self.interrupt() @@ -1056,7 +1055,7 @@ def resolve_address( if not filtered_maps: raise ValueError( - f"The specified string {backing_file} does not correspond to any backing file. The available backing files are: {', '.join(set(vmap.backing_file for vmap in maps))}." + f"The specified string {backing_file} does not correspond to any backing file. The available backing files are: {', '.join(set(vmap.backing_file for vmap in maps))}.", ) return normalize_and_validate_address(address, filtered_maps) @@ -1101,7 +1100,7 @@ def resolve_symbol(self: InternalDebugger, symbol: str, backing_file: str) -> in if not filtered_maps: raise ValueError( - f"The specified string {backing_file} does not correspond to any backing file. The available backing files are: {', '.join(set(vmap.backing_file for vmap in maps))}." + f"The specified string {backing_file} does not correspond to any backing file. The available backing files are: {', '.join(set(vmap.backing_file for vmap in maps))}.", ) return resolve_symbol_in_maps(symbol, filtered_maps) diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index 073f06e9..182b1a65 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -221,7 +221,6 @@ def kill(self: PtraceInterface) -> None: def cont(self: PtraceInterface) -> None: """Continues the execution of the process.""" - # Forward signals to the threads if self._internal_debugger.resume_context.threads_with_signals_to_forward: self.forward_signal() diff --git a/libdebug/ptrace/ptrace_status_handler.py b/libdebug/ptrace/ptrace_status_handler.py index 2717ac13..74300953 100644 --- a/libdebug/ptrace/ptrace_status_handler.py +++ b/libdebug/ptrace/ptrace_status_handler.py @@ -380,13 +380,12 @@ def _internal_signal_handler( case StopEvents.FORK_EVENT: # The process has been forked liblog.warning( - f"Process {pid} forked. Continuing execution of the parent process. The child process will be stopped until the user decides to attach to it." + f"Process {pid} forked. Continuing execution of the parent process. The child process will be stopped until the user decides to attach to it.", ) self.forward_signal = False def _handle_change(self: PtraceStatusHandler, pid: int, status: int, results: list) -> None: """Handle a change in the status of a traced process.""" - # Initialize the forward_signal flag self.forward_signal = True diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index c65d0e0b..f25d2d6e 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -17,7 +17,7 @@ from libdebug.utils.signal_utils import resolve_signal_name, resolve_signal_number if TYPE_CHECKING: - from libdebug.data.memory_view import MemoryView + from libdebug.data.abstract_memory_view import AbstractMemoryView from libdebug.data.register_holder import RegisterHolder from libdebug.data.registers import Registers from libdebug.debugger.internal_debugger import InternalDebugger @@ -93,12 +93,12 @@ def dead(self: ThreadContext) -> bool: return self._dead @property - def memory(self: ThreadContext) -> MemoryView: + def memory(self: ThreadContext) -> AbstractMemoryView: """The memory view of the debugged process.""" return self._internal_debugger.memory @property - def mem(self: ThreadContext) -> MemoryView: + def mem(self: ThreadContext) -> AbstractMemoryView: """Alias for the `memory` property. Get the memory view of the process. @@ -167,7 +167,7 @@ def signal(self: ThreadContext, signal: str | int) -> None: self._internal_debugger._ensure_process_stopped() if self._signal_number != 0: liblog.debugger( - f"Overwriting signal {resolve_signal_name(self._signal_number)} with {resolve_signal_name(signal) if isinstance(signal, int) else signal}." + f"Overwriting signal {resolve_signal_name(self._signal_number)} with {resolve_signal_name(signal) if isinstance(signal, int) else signal}.", ) if isinstance(signal, str): signal = resolve_signal_number(signal) @@ -205,12 +205,14 @@ def current_return_address(self: ThreadContext) -> int: """Returns the return address of the current function.""" self._internal_debugger._ensure_process_stopped() stack_unwinder = stack_unwinding_provider() - + try: return_address = stack_unwinder.get_return_address(self) except (OSError, ValueError) as e: - raise ValueError("Failed to get the return address. Check stack frame registers (e.g., base pointer).") from e - + raise ValueError( + "Failed to get the return address. Check stack frame registers (e.g., base pointer).", + ) from e + return return_address def step(self: ThreadContext) -> None: diff --git a/libdebug/utils/debugger_wrappers.py b/libdebug/utils/debugger_wrappers.py index 9c57fff8..f56b7cb4 100644 --- a/libdebug/utils/debugger_wrappers.py +++ b/libdebug/utils/debugger_wrappers.py @@ -40,7 +40,10 @@ def change_state_function_thread(method: callable) -> callable: @wraps(method) def wrapper( - self: InternalDebugger, thread: ThreadContext, *args: ..., **kwargs: ... + self: InternalDebugger, + thread: ThreadContext, + *args: ..., + **kwargs: ..., ) -> ...: if not self.instanced: raise RuntimeError( From 34b056f2840ba038a042675acbb35dbe6dfebcf8 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 03:18:16 +0200 Subject: [PATCH 088/144] test: add memory benchmark --- test/binaries/memory_test_3 | Bin 0 -> 16760 bytes test/run_suite.py | 1 + test/scripts/memory_test.py | 20 ++++++++++++++++++++ test/srcs/memory_test_3.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100755 test/binaries/memory_test_3 create mode 100644 test/srcs/memory_test_3.c diff --git a/test/binaries/memory_test_3 b/test/binaries/memory_test_3 new file mode 100755 index 0000000000000000000000000000000000000000..81032021da7396025f38550d821ddb4b5fd6504f GIT binary patch literal 16760 zcmeHOZ)_W98Gr4xtIO8J>Az)dx1LyAwV+(=Bz1$UuIDCBE+cJ2(zQckyItaIV#z)m z`#h4NcHK%rp-c*27(xZXm$47<34~gZx~ze99U!%!P8DB>5R+1Mj8qgTsH%CM_nybS zT-~Yy2?=qJboc!IJ@0$Zd%nBZckj#Tf!=T^B$&eDDS_HxRFfumFT}*Bq)N6fu|jA< z5gWxrU|9m2COybqiTRbV>tG(CxCY>eYe7MBZ4uufH6fUD50NA84rwI1Twm&{%th4I z5hq=w3pzgLm#G2fUYu-Caq|d8a|Plz$jsb}!`j?zbpvkh(xkwA1Jy+#&Nx&N7g1%n zh&-i9f%!%1Cv&bR_7S%(s%KY&i(~F;I6f}8c zWEv4wG$}Cebj^Y}xAz&qVO<`Sc$kN%y?c#AgzC?n$}8G(*^xwNTQ1X@%i5KR)`_k} zYob#v7u0sy1?*Q0Haw^L_B|uQ$HWZU7#|X$MS7TY2$6)bt6{^kBmVi=Z>HD2Ka)Om z`SnlEj{WjGPhbCb5o7Q;FekgeN58Xj_?7Dl{HLxqN`3c*umq+9) zcHS%s!+=7L8fC{UIY!>h+9Ho;G-5FmqHmyoPq)#owyWD~az`iN#_|Q5a0a#-hGP%O z<3}w7`bOY9&Xsl9IX8UM;hl1(k>Z-2B~fBR;G9(qT=blYjJWmrZT{CI5t*skT!DN$MVj6U_j z{7Y~Y?=1a0%)dj=&EC}>#$Wp?2I^BsZcaZpE5+j@ehTuUEHmn0cop)=!@GbZh5eG_SQ|Oa8cuwn@6o&zTHmk}-@W2{&FDh4Isse*J_x)BJOg~o^=kFIz~jJYfFt3%Or+3D zLn1U04XuA5L6(j!2eMNV(>Cn5^??}62GZLdcw_nFbA+`)xMxXP0QG%5R{N&yoLQr~vJE znUHfokXuM!L!23Z%Q;Wqck(P(&MhH`=d1XAC(@%X6D>>WtP?&I!DL5jitWTzNTCu1Tz47){L$l6&4+LNu2EK`!aC>B13cCw?b zB`a4Lb^91od^VZKXOsDSHu-ys;-JBa)|c88>M)_7G?p(C$8*;obAo)#>&5VIf9-wp z@b%y#T^H)*nI?SVJ~6)_AM<*7*vGtH4EJ^YefrFOy-aOl`R+yd`&}ZgSN(o$I|QCn zbbV4NU?uQ+@sEQ&EE)yZg2LrD>}a2%{<6KD;)Sn|I@m+vF|k_HWGG)>4oEv()#!RF zov`~NY?!}xy(h<4fQQ5?!Pjpxi#I^1*Z-f~!;as!gkS~0f}(-6nCj*EHH3u3n%cS+ zM!YYD=GuA?wqKR;j|#qyvmFNnYwf2#D2M`gg(%gB{Tsj@1v}S`?N5SzWw=T3dCc~0 zXlKR4lhWQO_*~}uP@3}l+Z_VC;=!|Mg1tE$6%k+EY_#LAmF!Cha0Kj87=ODp^=tmCcYTwWq%oPYM;o$IbG%P&1P@G~sfm5)YqI@s}O;>GiNL0(6=$r}tZU&q+o@5A$b^b|d~D;!ANk3R(V zzriy#_kV_te}?dU{0|Y{-+vSAc+-80)177%&(2pP%PeKOlYu zwNK%-)QtaJ&Eg8&KJ&F8VB3CtkqrAekn*s~d@~3Z$MMDn#OIHzxnspLa{QUs!*_MLQ9FJ8 H_o?Dvm56zd literal 0 HcmV?d00001 diff --git a/test/run_suite.py b/test/run_suite.py index 27486054..549d444c 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -63,6 +63,7 @@ def fast_suite(): suite.addTest(MemoryTest("test_memory_multiple_runs")) suite.addTest(MemoryTest("test_memory_access_while_running")) suite.addTest(MemoryTest("test_memory_access_methods")) + suite.addTest(MemoryTest("test_memory_large_read")) suite.addTest(HwBasicTest("test_basic")) suite.addTest(HwBasicTest("test_registers")) suite.addTest(BacktraceTest("test_backtrace_as_symbols")) diff --git a/test/scripts/memory_test.py b/test/scripts/memory_test.py index edfc3525..995f72ce 100644 --- a/test/scripts/memory_test.py +++ b/test/scripts/memory_test.py @@ -286,6 +286,26 @@ def test_memory_access_methods_backing_file(self): d.kill() + def test_memory_large_read(self): + d = debugger("binaries/memory_test_3") + + d.run() + + bp = d.bp("do_nothing") + + d.cont() + + assert bp.hit_on(d) + + leak = d.regs.rdi + + # Read 64K of memory + data = d.memory[leak, 256 * 1024] + + assert data == b"".join(x.to_bytes(4, "little") for x in range(64 * 1024)) + + d.kill() + d.terminate() if __name__ == "__main__": unittest.main() diff --git a/test/srcs/memory_test_3.c b/test/srcs/memory_test_3.c new file mode 100644 index 00000000..e1992d4c --- /dev/null +++ b/test/srcs/memory_test_3.c @@ -0,0 +1,28 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +#include +#include + +void do_nothing(int *leak) +{ + +} + +int main() +{ + int *buffer = mmap(NULL, sizeof(int) * 1024 * 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + for (int i = 0; i < 1024 * 1024; i++) { + buffer[i] = i; + } + + do_nothing(buffer); + + munmap(buffer, sizeof(int) * 1024 * 1024); + + return 0; +} \ No newline at end of file From ef6fb2553b91f5c3ff8768db054d6faa2c62280c Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 03:29:28 +0200 Subject: [PATCH 089/144] fix: remove functools.cache from InternalDebugger Caches are intended for functions, not class methods, as they leak any referee indefinitely, which for classes is the class itself as the first argument --- libdebug/debugger/internal_debugger.py | 30 ++++++++++++-------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 107d1cc9..203633dd 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -1030,15 +1030,14 @@ def resolve_address( else: # If the address was not found and the backing file is not "absolute", # we have to assume it is in the main map - backing_file = self._get_process_full_path() + backing_file = self._process_full_path liblog.warning( f"No backing file specified and no corresponding absolute address found for {hex(address)}. Assuming {backing_file}.", ) - elif ( - backing_file == (full_backing_path := self._get_process_full_path()) - or backing_file == "binary" - or backing_file == self._get_process_name() - ): + elif backing_file == (full_backing_path := self._process_full_path) or backing_file in [ + "binary", + self._process_name, + ]: backing_file = full_backing_path filtered_maps = [] @@ -1077,13 +1076,12 @@ def resolve_symbol(self: InternalDebugger, symbol: str, backing_file: str) -> in if backing_file == "hybrid": # If no explicit backing file is specified, we have to assume it is in the main map - backing_file = self._get_process_full_path() + backing_file = self._process_full_path liblog.debugger(f"No backing file specified for the symbol {symbol}. Assuming {backing_file}.") - elif ( - backing_file == (full_backing_path := self._get_process_full_path()) - or backing_file == "binary" - or backing_file == self._get_process_name() - ): + elif backing_file == (full_backing_path := self._process_full_path) or backing_file in [ + "binary", + self._process_name, + ]: backing_file = full_backing_path filtered_maps = [] @@ -1162,8 +1160,8 @@ def _join_and_check_status(self: InternalDebugger) -> None: if response is not None: raise response - @functools.cache - def _get_process_full_path(self: InternalDebugger) -> str: + @functools.cached_property + def _process_full_path(self: InternalDebugger) -> str: """Get the full path of the process. Returns: @@ -1171,8 +1169,8 @@ def _get_process_full_path(self: InternalDebugger) -> str: """ return str(Path(f"/proc/{self.process_id}/exe").readlink()) - @functools.cache - def _get_process_name(self: InternalDebugger) -> str: + @functools.cached_property + def _process_name(self: InternalDebugger) -> str: """Get the name of the process. Returns: From 6c24bff5145a1402b531dd2f3b1e0914f9cbd95e Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 12:59:13 +0200 Subject: [PATCH 090/144] style: correct typing for DirectMemoryView --- libdebug/data/direct_memory_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdebug/data/direct_memory_view.py b/libdebug/data/direct_memory_view.py index 5978af72..24d66935 100644 --- a/libdebug/data/direct_memory_view.py +++ b/libdebug/data/direct_memory_view.py @@ -25,7 +25,7 @@ class DirectMemoryView(AbstractMemoryView): def __init__( self: DirectMemoryView, - getter: Callable[[int], bytes], + getter: Callable[[int, int], bytes], setter: Callable[[int, bytes], None], align_to: int = 1, ) -> None: From be2bbb1c283012f55b4409c4a77a4d0cf297324a Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 13:39:43 +0200 Subject: [PATCH 091/144] feat: introduce ProcessMemoryManager for faster memory access --- libdebug/debugger/internal_debugger.py | 59 ++++++++++++++++++- libdebug/libdebug.py | 3 + .../{data => memory}/abstract_memory_view.py | 0 .../{data => memory}/chunked_memory_view.py | 2 +- .../{data => memory}/direct_memory_view.py | 2 +- libdebug/memory/process_memory_manager.py | 43 ++++++++++++++ 6 files changed, 104 insertions(+), 5 deletions(-) rename libdebug/{data => memory}/abstract_memory_view.py (100%) rename libdebug/{data => memory}/chunked_memory_view.py (98%) rename libdebug/{data => memory}/direct_memory_view.py (97%) create mode 100644 libdebug/memory/process_memory_manager.py diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 19e31c8f..6920af5d 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -23,7 +23,6 @@ from libdebug.builtin.antidebug_syscall_handler import on_enter_ptrace, on_exit_ptrace from libdebug.builtin.pretty_print_syscall_handler import pprint_on_enter, pprint_on_exit from libdebug.data.breakpoint import Breakpoint -from libdebug.data.chunked_memory_view import ChunkedMemoryView from libdebug.data.signal_catcher import SignalCatcher from libdebug.data.syscall_handler import SyscallHandler from libdebug.debugger.internal_debugger_instance_manager import ( @@ -32,6 +31,9 @@ ) from libdebug.interfaces.interface_helper import provide_debugging_interface from libdebug.liblog import liblog +from libdebug.memory.chunked_memory_view import ChunkedMemoryView +from libdebug.memory.direct_memory_view import DirectMemoryView +from libdebug.memory.process_memory_manager import ProcessMemoryManager from libdebug.state.resume_context import ResumeContext from libdebug.utils.debugger_wrappers import ( background_alias, @@ -58,9 +60,9 @@ if TYPE_CHECKING: from collections.abc import Callable - from libdebug.data.abstract_memory_view import AbstractMemoryView from libdebug.data.memory_map import MemoryMap from libdebug.interfaces.debugging_interface import DebuggingInterface + from libdebug.memory.abstract_memory_view import AbstractMemoryView from libdebug.state.thread_context import ThreadContext from libdebug.utils.pipe_manager import PipeManager @@ -83,6 +85,9 @@ class InternalDebugger: escape_antidebug: bool """A flag that indicates if the debugger should escape anti-debugging techniques.""" + fast_memory: bool + """A flag that indicates if the debugger should use a faster memory reading method.""" + autoreach_entrypoint: bool """A flag that indicates if the debugger should automatically reach the entry point of the debugged process.""" @@ -167,6 +172,7 @@ def __init__(self: InternalDebugger) -> None: self.instanced = False self._is_running = False self.resume_context = ResumeContext() + self._process_memory_manager = ProcessMemoryManager() self.__polling_thread_command_queue = Queue() self.__polling_thread_response_queue = Queue() @@ -195,7 +201,10 @@ def start_up(self: InternalDebugger) -> None: self.start_processing_thread() with extend_internal_debugger(self): self.debugging_interface = provide_debugging_interface() - self.memory = ChunkedMemoryView(self._peek_memory, self._poke_memory) + if self.fast_memory: + self.memory = DirectMemoryView(self._fast_read_memory, self._fast_write_memory) + else: + self.memory = ChunkedMemoryView(self._peek_memory, self._poke_memory) def start_processing_thread(self: InternalDebugger) -> None: """Starts the thread that will poll the traced process for state change.""" @@ -247,6 +256,9 @@ def run(self: InternalDebugger) -> None: if not self.pipe_manager: raise RuntimeError("Something went wrong during pipe initialization.") + if self.fast_memory: + self._process_memory_manager.open(self.process_id) + return self.pipe_manager def attach(self: InternalDebugger, pid: int) -> None: @@ -265,6 +277,9 @@ def attach(self: InternalDebugger, pid: int) -> None: self.__polling_thread_command_queue.put((self.__threaded_attach, (pid,))) + if self.fast_memory: + self._process_memory_manager.open(self.process_id) + self._join_and_check_status() def detach(self: InternalDebugger) -> None: @@ -278,6 +293,9 @@ def detach(self: InternalDebugger) -> None: self._join_and_check_status() + if self.fast_memory: + self._process_memory_manager.close() + @background_alias(_background_invalid_call) def kill(self: InternalDebugger) -> None: """Kills the process.""" @@ -287,6 +305,9 @@ def kill(self: InternalDebugger) -> None: # This exception might occur if the process has already died liblog.debugger("OSError raised during kill") + if self.fast_memory: + self._process_memory_manager.close() + self.__polling_thread_command_queue.put((self.__threaded_kill, ())) self.instanced = False @@ -1331,6 +1352,22 @@ def _peek_memory(self: InternalDebugger, address: int) -> bytes: return value + def _fast_read_memory(self: InternalDebugger, address: int, size: int) -> bytes: + """Reads memory from the process.""" + if not self.instanced: + raise RuntimeError("Process not running, cannot step.") + + if self.running: + # Reading memory while the process is running could lead to concurrency issues + # and corrupted values + liblog.debugger( + "Process is running. Waiting for it to stop before reading memory.", + ) + + self._ensure_process_stopped() + + return self._process_memory_manager.read(address, size) + @background_alias(__threaded_poke_memory) def _poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: """Writes memory to the process.""" @@ -1352,6 +1389,22 @@ def _poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: self._join_and_check_status() + def _fast_write_memory(self: InternalDebugger, address: int, data: bytes) -> None: + """Writes memory to the process.""" + if not self.instanced: + raise RuntimeError("Process not running, cannot step.") + + if self.running: + # Reading memory while the process is running could lead to concurrency issues + # and corrupted values + liblog.debugger( + "Process is running. Waiting for it to stop before writing to memory.", + ) + + self._ensure_process_stopped() + + self._process_memory_manager.write(address, data) + def _enable_antidebug_escaping(self: InternalDebugger) -> None: """Enables the anti-debugging escape mechanism.""" handler = SyscallHandler( diff --git a/libdebug/libdebug.py b/libdebug/libdebug.py index 55819051..13f89d1e 100644 --- a/libdebug/libdebug.py +++ b/libdebug/libdebug.py @@ -16,6 +16,7 @@ def debugger( escape_antidebug: bool = False, continue_to_binary_entrypoint: bool = True, auto_interrupt_on_command: bool = False, + fast_memory: bool = False, ) -> Debugger: """This function is used to create a new `Debugger` object. It returns a `Debugger` object. @@ -26,6 +27,7 @@ def debugger( escape_antidebug (bool): Whether to automatically attempt to patch antidebugger detectors based on the ptrace syscall. continue_to_binary_entrypoint (bool, optional): Whether to automatically continue to the binary entrypoint. Defaults to True. auto_interrupt_on_command (bool, optional): Whether to automatically interrupt the process when a command is issued. Defaults to False. + fast_memory (bool, optional): Whether to use a faster memory reading method. Defaults to False. Returns: Debugger: The `Debugger` object. @@ -40,6 +42,7 @@ def debugger( internal_debugger.autoreach_entrypoint = continue_to_binary_entrypoint internal_debugger.auto_interrupt_on_command = auto_interrupt_on_command internal_debugger.escape_antidebug = escape_antidebug + internal_debugger.fast_memory = fast_memory debugger = Debugger() debugger.post_init_(internal_debugger) diff --git a/libdebug/data/abstract_memory_view.py b/libdebug/memory/abstract_memory_view.py similarity index 100% rename from libdebug/data/abstract_memory_view.py rename to libdebug/memory/abstract_memory_view.py diff --git a/libdebug/data/chunked_memory_view.py b/libdebug/memory/chunked_memory_view.py similarity index 98% rename from libdebug/data/chunked_memory_view.py rename to libdebug/memory/chunked_memory_view.py index c37cea06..cec0fb88 100644 --- a/libdebug/data/chunked_memory_view.py +++ b/libdebug/memory/chunked_memory_view.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING -from libdebug.data.abstract_memory_view import AbstractMemoryView +from libdebug.memory.abstract_memory_view import AbstractMemoryView if TYPE_CHECKING: from collections.abc import Callable diff --git a/libdebug/data/direct_memory_view.py b/libdebug/memory/direct_memory_view.py similarity index 97% rename from libdebug/data/direct_memory_view.py rename to libdebug/memory/direct_memory_view.py index 24d66935..5823b61e 100644 --- a/libdebug/data/direct_memory_view.py +++ b/libdebug/memory/direct_memory_view.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING -from libdebug.data.abstract_memory_view import AbstractMemoryView +from libdebug.memory.abstract_memory_view import AbstractMemoryView if TYPE_CHECKING: from collections.abc import Callable diff --git a/libdebug/memory/process_memory_manager.py b/libdebug/memory/process_memory_manager.py new file mode 100644 index 00000000..d2cafa79 --- /dev/null +++ b/libdebug/memory/process_memory_manager.py @@ -0,0 +1,43 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + + +class ProcessMemoryManager: + """A class that provides accessors to the memory of a process, through /proc/pid/mem.""" + + def open(self: ProcessMemoryManager, process_id: int) -> None: + """Initializes the ProcessMemoryManager.""" + self.process_id = process_id + self._mem_file = open(f"/proc/{process_id}/mem", "r+b", buffering=0) + + def read(self: ProcessMemoryManager, address: int, size: int) -> bytes: + """Reads memory from the target process. + + Args: + address (int): The address to read from. + size (int): The number of bytes to read. + + Returns: + bytes: The read bytes. + """ + self._mem_file.seek(address) + return self._mem_file.read(size) + + def write(self: ProcessMemoryManager, address: int, data: bytes) -> None: + """Writes memory to the target process. + + Args: + address (int): The address to write to. + data (bytes): The data to write. + """ + self._mem_file.seek(address) + self._mem_file.write(data) + + def close(self: ProcessMemoryManager) -> None: + """Closes the memory file.""" + self._mem_file.close() From ce6f688541b4cbb362bfacb29ae07a88f72904b4 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 13:44:36 +0200 Subject: [PATCH 092/144] test: add tests for fast memory access --- test/run_suite.py | 10 ++ test/scripts/callback_test.py | 43 ++++- test/scripts/memory_fast_test.py | 300 +++++++++++++++++++++++++++++++ test/scripts/memory_test.py | 2 +- 4 files changed, 352 insertions(+), 3 deletions(-) create mode 100644 test/scripts/memory_fast_test.py diff --git a/test/run_suite.py b/test/run_suite.py index 549d444c..fc57b818 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -25,6 +25,7 @@ from scripts.jumpout_test import Jumpout from scripts.jumpstart_test import JumpstartTest from scripts.large_binary_sym_test import LargeBinarySymTest +from scripts.memory_fast_test import MemoryFastTest from scripts.memory_test import MemoryTest from scripts.multiple_debuggers_test import MultipleDebuggersTest from scripts.nlinks_test import Nlinks @@ -64,6 +65,14 @@ def fast_suite(): suite.addTest(MemoryTest("test_memory_access_while_running")) suite.addTest(MemoryTest("test_memory_access_methods")) suite.addTest(MemoryTest("test_memory_large_read")) + suite.addTest(MemoryFastTest("test_memory")) + suite.addTest(MemoryFastTest("test_mem_access_libs")) + suite.addTest(MemoryFastTest("test_memory_access_methods_backing_file")) + suite.addTest(MemoryFastTest("test_memory_exceptions")) + suite.addTest(MemoryFastTest("test_memory_multiple_runs")) + suite.addTest(MemoryFastTest("test_memory_access_while_running")) + suite.addTest(MemoryFastTest("test_memory_access_methods")) + suite.addTest(MemoryFastTest("test_memory_large_read")) suite.addTest(HwBasicTest("test_basic")) suite.addTest(HwBasicTest("test_registers")) suite.addTest(BacktraceTest("test_backtrace_as_symbols")) @@ -79,6 +88,7 @@ def fast_suite(): suite.addTest(CallbackTest("test_callback_simple")) suite.addTest(CallbackTest("test_callback_simple_hardware")) suite.addTest(CallbackTest("test_callback_memory")) + suite.addTest(CallbackTest("test_callback_fast_memory")) suite.addTest(CallbackTest("test_callback_jumpout")) suite.addTest(CallbackTest("test_callback_intermixing")) suite.addTest(CallbackTest("test_callback_exception")) diff --git a/test/scripts/callback_test.py b/test/scripts/callback_test.py index 9b95f3c6..0e716e2d 100644 --- a/test/scripts/callback_test.py +++ b/test/scripts/callback_test.py @@ -117,6 +117,45 @@ def callback(thread, bp): if self.exceptions: raise self.exceptions[0] + def test_callback_fast_memory(self): + self.exceptions.clear() + + global hit + hit = False + + d = debugger("binaries/memory_test", fast_memory=True) + + d.run() + + def callback(thread, bp): + global hit + + prev = bytes(range(256)) + try: + self.assertEqual(bp.address, thread.regs.rip) + self.assertEqual(bp.hit_count, 1) + self.assertEqual(thread.memory[thread.regs.rdi, 256], prev) + + thread.memory[thread.regs.rdi + 128 :] = b"abcd123456" + prev = prev[:128] + b"abcd123456" + prev[138:] + + self.assertEqual(thread.memory[thread.regs.rdi, 256], prev) + except Exception as e: + self.exceptions.append(e) + + hit = True + + d.breakpoint("change_memory", callback=callback) + + d.cont() + + d.kill() + + self.assertTrue(hit) + + if self.exceptions: + raise self.exceptions[0] + def test_callback_bruteforce(self): global flag global counter @@ -303,7 +342,7 @@ def callback(t, bp): d.kill() self.assertTrue(hit) - + def test_callback_pid_accessible_alias(self): self.exceptions.clear() @@ -325,7 +364,7 @@ def callback(t, bp): d.kill() self.assertTrue(hit) - + def test_callback_tid_accessible_alias(self): self.exceptions.clear() diff --git a/test/scripts/memory_fast_test.py b/test/scripts/memory_fast_test.py new file mode 100644 index 00000000..254f3501 --- /dev/null +++ b/test/scripts/memory_fast_test.py @@ -0,0 +1,300 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import unittest + +from libdebug import debugger, libcontext + + +class MemoryFastTest(unittest.TestCase): + def test_memory(self): + d = debugger("binaries/memory_test", fast_memory=True) + + d.run() + + bp = d.breakpoint("change_memory") + + d.cont() + + assert d.regs.rip == bp.address + + address = d.regs.rdi + prev = bytes(range(256)) + + self.assertTrue(d.memory[address, 256] == prev) + + d.memory[address + 128 :] = b"abcd123456" + prev = prev[:128] + b"abcd123456" + prev[138:] + + self.assertTrue(d.memory[address : address + 256] == prev) + + d.kill() + d.terminate() + + def test_mem_access_libs(self): + d = debugger("binaries/memory_test", fast_memory=True) + + d.run() + + bp = d.breakpoint("leak_address") + + d.cont() + + assert d.regs.rip == bp.address + + address = d.regs.rdi + with libcontext.tmp(sym_lvl=5): + arena = d.memory["main_arena", 256, "libc"] + + def p64(x): + return x.to_bytes(8, "little") + + self.assertTrue(p64(address - 0x10) in arena) + + d.kill() + d.terminate() + + def test_memory_exceptions(self): + d = debugger("binaries/memory_test", fast_memory=True) + + d.run() + + bp = d.breakpoint("change_memory") + + d.cont() + + # This should not raise an exception + file = d.memory[0x0, 256] + + # File should start with ELF magic number + self.assertTrue(file.startswith(b"\x7fELF")) + + assert d.regs.rip == bp.address + + address = d.regs.rdi + prev = bytes(range(256)) + + self.assertTrue(d.memory[address, 256] == prev) + + d.memory[address + 128 :] = b"abcd123456" + prev = prev[:128] + b"abcd123456" + prev[138:] + + self.assertTrue(d.memory[address : address + 256] == prev) + + d.kill() + d.terminate() + + def test_memory_multiple_runs(self): + d = debugger("binaries/memory_test", fast_memory=True) + + for _ in range(10): + d.run() + + bp = d.breakpoint("change_memory") + + d.cont() + + assert d.regs.rip == bp.address + + address = d.regs.rdi + prev = bytes(range(256)) + + self.assertTrue(d.memory[address, 256] == prev) + + d.memory[address + 128 :] = b"abcd123456" + prev = prev[:128] + b"abcd123456" + prev[138:] + + self.assertTrue(d.memory[address : address + 256] == prev) + + d.kill() + + d.terminate() + + def test_memory_access_while_running(self): + d = debugger("binaries/memory_test_2", fast_memory=True) + + d.run() + + bp = d.breakpoint("do_nothing") + + d.cont() + + # Verify that memory access is only possible when the process is stopped + value = int.from_bytes(d.memory["state", 8], "little") + self.assertEqual(value, 0xDEADBEEF) + self.assertEqual(d.regs.rip, bp.address) + + d.kill() + d.terminate() + + def test_memory_access_methods(self): + d = debugger("binaries/memory_test_2", fast_memory=True) + + d.run() + + base = d.regs.rip & 0xFFFFFFFFFFFFF000 - 0x1000 + + # Test different ways to access memory at the start of the file + file_0 = d.memory[base, 256] + file_1 = d.memory[0x0, 256] + file_2 = d.memory[0x0:0x100] + + self.assertEqual(file_0, file_1) + self.assertEqual(file_0, file_2) + + # Validate that the length of the read bytes is correct + file_0 = d.memory[0x0] + file_1 = d.memory[base] + + self.assertEqual(file_0, file_1) + self.assertEqual(len(file_0), 1) + + # Validate that slices work correctly + file_0 = d.memory[0x0:"do_nothing"] + file_1 = d.memory[base:"do_nothing"] + + self.assertEqual(file_0, file_1) + + self.assertRaises(ValueError, lambda: d.memory[0x1000:0x0]) + # _fini is after main + self.assertRaises(ValueError, lambda: d.memory["_fini":"main"]) + + # Test different ways to write memory + d.memory[0x0, 8] = b"abcd1234" + self.assertEqual(d.memory[0x0, 8], b"abcd1234") + + d.memory[0x0, 8] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[base:] = b"abcd1234" + self.assertEqual(d.memory[base, 8], b"abcd1234") + + d.memory[base:] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[base] = b"abcd1234" + self.assertEqual(d.memory[base, 8], b"abcd1234") + + d.memory[base] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[0x0:0x8] = b"abcd1234" + self.assertEqual(d.memory[0x0, 8], b"abcd1234") + + d.memory[0x0, 8] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":] = b"abcd1234" + self.assertEqual(d.memory["main", 8], b"abcd1234") + + d.memory["main":] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main"] = b"abcd1234" + self.assertEqual(d.memory["main", 8], b"abcd1234") + + d.memory["main"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":"main+8"] = b"abcd1234" + self.assertEqual(d.memory["main", 8], b"abcd1234") + + d.kill() + d.terminate() + + def test_memory_access_methods_backing_file(self): + d = debugger("binaries/memory_test_2", fast_memory=True) + + d.run() + + base = d.regs.rip & 0xFFFFFFFFFFFFF000 - 0x1000 + + # Validate that slices work correctly + file_0 = d.memory[0x0:"do_nothing", "binary"] + file_1 = d.memory[0x0:"do_nothing", "memory_test_2"] + file_2 = d.memory[base:"do_nothing", "binary"] + file_3 = d.memory[base:"do_nothing", "memory_test_2"] + + self.assertEqual(file_0, file_1) + self.assertEqual(file_1, file_2) + self.assertEqual(file_2, file_3) + + # Test different ways to write memory + d.memory[0x0, 8, "binary"] = b"abcd1234" + self.assertEqual(d.memory[0x0, 8, "binary"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[0x0, 8, "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory[0x0, 8, "memory_test_2"], b"abcd1234") + + d.memory[0x0, 8, "memory_test_2"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[0x0:0x8, "binary"] = b"abcd1234" + self.assertEqual(d.memory[0x0:8, "binary"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory[0x0:0x8, "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory[0x0:8, "memory_test_2"], b"abcd1234") + + d.memory[0x0, 8, "memory_test_2"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":, "binary"] = b"abcd1234" + self.assertEqual(d.memory["main", 8, "binary"], b"abcd1234") + + d.memory["main":, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":, "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory["main", 8, "binary"], b"abcd1234") + + d.memory["main":, "memory_test_2"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main", "binary"] = b"abcd1234" + self.assertEqual(d.memory["main", 8, "binary"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main", "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory["main", 8, "memory_test_2"], b"abcd1234") + + d.memory[0x0, 8, "memory_test_2"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":"main+8", "binary"] = b"abcd1234" + self.assertEqual(d.memory["main":"main+8", "binary"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":"main+8", "memory_test_2"] = b"abcd1234" + self.assertEqual(d.memory["main":"main+8", "memory_test_2"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + d.memory["main":"main+8", "hybrid"] = b"abcd1234" + self.assertEqual(d.memory["main":"main+8", "hybrid"], b"abcd1234") + + d.memory[0x0, 8, "binary"] = b"\x00\x00\x00\x00\x00\x00\x00\x00" + + with self.assertRaises(ValueError): + d.memory["main":"main+8", "absolute"] = b"abcd1234" + + d.kill() + d.terminate() + + def test_memory_large_read(self): + d = debugger("binaries/memory_test_3", fast_memory=True) + + d.run() + + bp = d.bp("do_nothing") + + d.cont() + + assert bp.hit_on(d) + + leak = d.regs.rdi + + # Read 4MB of memory + data = d.memory[leak, 4 * 1024 * 1024] + + assert data == b"".join(x.to_bytes(4, "little") for x in range(1024 * 1024)) + + d.kill() + d.terminate() diff --git a/test/scripts/memory_test.py b/test/scripts/memory_test.py index 995f72ce..3732ece7 100644 --- a/test/scripts/memory_test.py +++ b/test/scripts/memory_test.py @@ -299,7 +299,7 @@ def test_memory_large_read(self): leak = d.regs.rdi - # Read 64K of memory + # Read 256K of memory data = d.memory[leak, 256 * 1024] assert data == b"".join(x.to_bytes(4, "little") for x in range(64 * 1024)) From e71988e85502cbf2fa9728162d51f6c8a6d317fc Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 13:54:49 +0200 Subject: [PATCH 093/144] fix: add missing __init__.py in the memory module --- libdebug/memory/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 libdebug/memory/__init__.py diff --git a/libdebug/memory/__init__.py b/libdebug/memory/__init__.py new file mode 100644 index 00000000..e69de29b From 75e51ebf43730ba323ec9360655a5f206c47d260 Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Mon, 5 Aug 2024 20:20:54 +0200 Subject: [PATCH 094/144] fix: implemented optimization on breakpoint search --- libdebug/ptrace/ptrace_interface.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index cd9276bd..78a7b088 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -360,7 +360,7 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None raise ValueError(f"Unimplemented heuristic {heuristic}") def next(self: PtraceInterface, thread: ThreadContext) -> None: - """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns. """ opcode_window = thread.memory.read(thread.regs.rip, 8) @@ -373,14 +373,11 @@ def next(self: PtraceInterface, thread: ThreadContext) -> None: # If a breakpoint already exists at the return address, we don't need to set a new one found = False - ip_breakpoint = None - - for bp in self._internal_debugger.breakpoints.values(): - if bp.address == skip_address: - found = True - ip_breakpoint = bp - break + ip_breakpoint = self._internal_debugger.breakpoints.get(skip_address) + if ip_breakpoint is not None: + found = True + # If we find an existing breakpoint that is disabled, we enable it # but we need to disable it back after the command should_disable = False From fb11af7cf439a12391ae1831aee03fcd2b39b45f Mon Sep 17 00:00:00 2001 From: Frank01001 Date: Mon, 5 Aug 2024 20:21:26 +0200 Subject: [PATCH 095/144] docs: made next() documentation and Pydoc clearer --- docs/source/multithreading.rst | 2 +- libdebug/debugger/internal_debugger.py | 4 ++-- libdebug/interfaces/debugging_interface.py | 2 +- libdebug/state/thread_context.py | 4 ++-- test/scripts/next_test.py | 1 - 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/source/multithreading.rst b/docs/source/multithreading.rst index e9730a32..b1287335 100644 --- a/docs/source/multithreading.rst +++ b/docs/source/multithreading.rst @@ -33,7 +33,7 @@ The following is a list of behaviors to keep in mind when using control flow fun - `cont` will continue all threads. - `step` and `step_until` will step the selected thread. -- `next` will step on the selected thread or, if a call function is found, continue on all threads until the end of the function or another stopping event. +- `next` will step on the selected thread or, if a call function is found, continue on all threads until the end of the called function or another stopping event. - `finish` will have different behavior depending on the selected heuristic. - `backtrace` will continue on all threads but will stop at any breakpoint that any of the threads hit. - `step-mode` will step exclusively on the thread that has been specified. diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 0cc512d4..82ad0af9 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -892,14 +892,14 @@ def _background_next( self: InternalDebugger, thread: ThreadContext, ) -> None: - """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns. """ self.__threaded_next(thread) @background_alias(_background_next) @change_state_function_thread def next(self: InternalDebugger, thread: ThreadContext) -> None: - """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns. """ self._ensure_process_stopped() self.__polling_thread_command_queue.put((self.__threaded_next, (thread,))) diff --git a/libdebug/interfaces/debugging_interface.py b/libdebug/interfaces/debugging_interface.py index 210edd5a..684fef41 100644 --- a/libdebug/interfaces/debugging_interface.py +++ b/libdebug/interfaces/debugging_interface.py @@ -95,7 +95,7 @@ def finish(self: DebuggingInterface, thread: ThreadContext, heuristic: str) -> N """ def next(self: DebuggingInterface, thread: ThreadContext) -> None: - """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns. """ @abstractmethod diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index 70b2914c..f637f989 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -247,7 +247,7 @@ def finish(self: ThreadContext, heuristic: str = "backtrace") -> None: self._internal_debugger.finish(self, heuristic=heuristic) def next(self: ThreadContext) -> None: - """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns. """ self._internal_debugger.next(self) @@ -286,6 +286,6 @@ def fin(self: ThreadContext, heuristic: str = "backtrace") -> None: self._internal_debugger.finish(self, heuristic) def ni(self: ThreadContext) -> None: - """Alias for the `next` method. Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the function returns. + """Alias for the `next` method. Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns. """ self._internal_debugger.next(self) diff --git a/test/scripts/next_test.py b/test/scripts/next_test.py index 1829854e..af9096d0 100644 --- a/test/scripts/next_test.py +++ b/test/scripts/next_test.py @@ -35,7 +35,6 @@ def test_next(self): # ------------------------ # # Reach call of function c - d.next() self.assertEqual(d.regs.rip, CALL_C_ADDRESS) From 757bf84f893371e7c3703107909cb098f4992e58 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Mon, 5 Aug 2024 23:49:32 +0200 Subject: [PATCH 096/144] test: add small test about absolute address memory access at invalid locations --- test/run_suite.py | 2 ++ test/scripts/memory_fast_test.py | 19 +++++++++++++++++++ test/scripts/memory_test.py | 20 ++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/test/run_suite.py b/test/run_suite.py index fc57b818..2af88338 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -65,6 +65,7 @@ def fast_suite(): suite.addTest(MemoryTest("test_memory_access_while_running")) suite.addTest(MemoryTest("test_memory_access_methods")) suite.addTest(MemoryTest("test_memory_large_read")) + suite.addTest(MemoryTest("test_invalid_memory_location")) suite.addTest(MemoryFastTest("test_memory")) suite.addTest(MemoryFastTest("test_mem_access_libs")) suite.addTest(MemoryFastTest("test_memory_access_methods_backing_file")) @@ -73,6 +74,7 @@ def fast_suite(): suite.addTest(MemoryFastTest("test_memory_access_while_running")) suite.addTest(MemoryFastTest("test_memory_access_methods")) suite.addTest(MemoryFastTest("test_memory_large_read")) + suite.addTest(MemoryFastTest("test_invalid_memory_location")) suite.addTest(HwBasicTest("test_basic")) suite.addTest(HwBasicTest("test_registers")) suite.addTest(BacktraceTest("test_backtrace_as_symbols")) diff --git a/test/scripts/memory_fast_test.py b/test/scripts/memory_fast_test.py index 254f3501..11082845 100644 --- a/test/scripts/memory_fast_test.py +++ b/test/scripts/memory_fast_test.py @@ -298,3 +298,22 @@ def test_memory_large_read(self): d.kill() d.terminate() + + def test_invalid_memory_location(self): + d = debugger("binaries/memory_test", fast_memory=True) + + d.run() + + bp = d.bp("change_memory") + + d.cont() + + assert d.regs.rip == bp.address + + address = 0xDEADBEEFD00D + + with self.assertRaises(ValueError): + d.memory[address, 256, "absolute"] + + d.kill() + d.terminate() diff --git a/test/scripts/memory_test.py b/test/scripts/memory_test.py index 3732ece7..ff04cabc 100644 --- a/test/scripts/memory_test.py +++ b/test/scripts/memory_test.py @@ -307,5 +307,25 @@ def test_memory_large_read(self): d.kill() d.terminate() + def test_invalid_memory_location(self): + d = debugger("binaries/memory_test") + + d.run() + + bp = d.bp("change_memory") + + d.cont() + + assert d.regs.rip == bp.address + + address = 0xDEADBEEFD00D + + with self.assertRaises(ValueError): + d.memory[address, 256, "absolute"] + + d.kill() + d.terminate() + + if __name__ == "__main__": unittest.main() From 94b7cf6ef7a25fc5b11c419e8078dfdbb8734118 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Tue, 6 Aug 2024 23:54:53 +0200 Subject: [PATCH 097/144] test: add memory test for multithreaded processes The junior developer should take an operating systems course next semester to refresh his knowledge --- test/binaries/memory_test_4 | Bin 0 -> 16936 bytes test/run_suite.py | 2 ++ test/scripts/memory_fast_test.py | 32 +++++++++++++++++++ test/scripts/memory_test.py | 32 +++++++++++++++++++ test/srcs/memory_test_4.c | 53 +++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100755 test/binaries/memory_test_4 create mode 100644 test/srcs/memory_test_4.c diff --git a/test/binaries/memory_test_4 b/test/binaries/memory_test_4 new file mode 100755 index 0000000000000000000000000000000000000000..a2b9f4c9ebf568955951886cf2cad02f4d482445 GIT binary patch literal 16936 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 ZXtgUix7)Vj+#)~z9lG(`T>zIT{sqM42&Vu5 literal 0 HcmV?d00001 diff --git a/test/run_suite.py b/test/run_suite.py index 2af88338..0a897929 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -66,6 +66,7 @@ def fast_suite(): suite.addTest(MemoryTest("test_memory_access_methods")) suite.addTest(MemoryTest("test_memory_large_read")) suite.addTest(MemoryTest("test_invalid_memory_location")) + suite.addTest(MemoryTest("test_memory_multiple_threads")) suite.addTest(MemoryFastTest("test_memory")) suite.addTest(MemoryFastTest("test_mem_access_libs")) suite.addTest(MemoryFastTest("test_memory_access_methods_backing_file")) @@ -75,6 +76,7 @@ def fast_suite(): suite.addTest(MemoryFastTest("test_memory_access_methods")) suite.addTest(MemoryFastTest("test_memory_large_read")) suite.addTest(MemoryFastTest("test_invalid_memory_location")) + suite.addTest(MemoryFastTest("test_memory_multiple_threads")) suite.addTest(HwBasicTest("test_basic")) suite.addTest(HwBasicTest("test_registers")) suite.addTest(BacktraceTest("test_backtrace_as_symbols")) diff --git a/test/scripts/memory_fast_test.py b/test/scripts/memory_fast_test.py index 11082845..3c8f3264 100644 --- a/test/scripts/memory_fast_test.py +++ b/test/scripts/memory_fast_test.py @@ -317,3 +317,35 @@ def test_invalid_memory_location(self): d.kill() d.terminate() + + def test_memory_multiple_threads(self): + d = debugger("binaries/memory_test_4", fast_memory=True) + + d.run() + + leaks = [] + leak_addresses = [] + + def leak(t, _): + leaks.append(t.memory[t.regs.rdi, 16]) + leak_addresses.append(t.regs.rdi) + + d.bp("leak", callback=leak, hardware=True) + exit = d.bp("before_exit", hardware=True) + + d.cont() + d.wait() + + assert exit.hit_on(d) + + for i in range(8): + assert (chr(i).encode("latin-1") * 16) in leaks + + leaks = [d.memory[x, 16] for x in leak_addresses] + + # heap allocated leaks should still be valid + for i in range(4, 8, 1): + assert (chr(i).encode("latin-1") * 16) in leaks + + d.kill() + d.terminate() diff --git a/test/scripts/memory_test.py b/test/scripts/memory_test.py index ff04cabc..73f645e9 100644 --- a/test/scripts/memory_test.py +++ b/test/scripts/memory_test.py @@ -326,6 +326,38 @@ def test_invalid_memory_location(self): d.kill() d.terminate() + def test_memory_multiple_threads(self): + d = debugger("binaries/memory_test_4") + + d.run() + + leaks = [] + leak_addresses = [] + + def leak(t, _): + leaks.append(t.memory[t.regs.rdi, 16]) + leak_addresses.append(t.regs.rdi) + + d.bp("leak", callback=leak, hardware=True) + exit = d.bp("before_exit", hardware=True) + + d.cont() + d.wait() + + assert exit.hit_on(d) + + for i in range(8): + assert (chr(i).encode("latin-1") * 16) in leaks + + leaks = [d.memory[x, 16] for x in leak_addresses] + + # heap allocated leaks should still be valid + for i in range(4, 8, 1): + assert (chr(i).encode("latin-1") * 16) in leaks + + d.kill() + d.terminate() + if __name__ == "__main__": unittest.main() diff --git a/test/srcs/memory_test_4.c b/test/srcs/memory_test_4.c new file mode 100644 index 00000000..4a63ca91 --- /dev/null +++ b/test/srcs/memory_test_4.c @@ -0,0 +1,53 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +#include +#include +#include +#include + +void leak(char *ptr) +{ + +} + +void before_exit() +{ + +} + +void* thread_fun(void *arg) +{ + // cast arg to int + int thread_index = (int) ((unsigned long) arg); + + char test[16]; + + memset(test, thread_index, 16); + + char *test_ptr = malloc(16); + + memset(test_ptr, thread_index + 4, 16); + + leak(test); + leak(test_ptr); +} + +int main() +{ + // allocate four threads + pthread_t threads[4]; + + for (int i = 0; i < 4; i++) + pthread_create(&threads[i], NULL, thread_fun, (void *) ((unsigned long) i)); + + for (int i = 0; i < 4; i++) + pthread_join(threads[i], NULL); + + before_exit(); + + return 0; +} \ No newline at end of file From 794c9398ad58345ce78b470c6126714292d8983a Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Wed, 7 Aug 2024 00:08:16 +0200 Subject: [PATCH 098/144] test: add some synchronization in memory_test_4 The thread stacks get deallocated on the CI, now we ensure they aren't deallocated too early --- test/binaries/memory_test_4 | Bin 16936 -> 17168 bytes test/srcs/memory_test_4.c | 22 ++++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/test/binaries/memory_test_4 b/test/binaries/memory_test_4 index a2b9f4c9ebf568955951886cf2cad02f4d482445..9ec0cc406b80a832013512392068fbd94fc7c5e5 100755 GIT binary patch delta 2144 zcmZWqeQZ-z6ugW%?;fst{|GY z55c)HmUJ8>(?Bv!ROAl>sflDUY|SFe#DqpeOb7{!39$GEn)I<*12+s$f`UR%N_b}fHmjs!v zWvib~q^(z(ujR#;GVg zcL{3tSBWtonlaBJ<Tor$XP13IXj(@Y+5w>sXPhe0g z>!?Rx?GI0?z2oUq$W!~m8P@K`_Q)g-n0^`=ec@SkP)$yXs<@BM4GBdE$0x?v0P3-O zo?$l4D@n99{XU{(0*Y&Jj44h)P`*5!5IwB7~1dgeM?{C)0pP8(!TKY;#F)rz>}si zY1+_!dpw-UQ8P&G+wo&+ZtPCZ%pfy^Oy2#(7_t+)Y_|$dIV&pTnQV3+0;6bSXpcO| zW-p@cLYqR{gO+u_>@RrUS-E^I(pIBOb}X@4(Rf%B`ki*#312#&q*q|V>7d`hl+#ho zx<1Lcp+al7uzjgX__$yRp3gFbe#UPWn&8cRPll-0h}Oc+u@J)Qbuv#lw(x2c#o2KzDj+OqzWGR>@e!f!+SB|bK~AV^!-uNh@0Rf#Uy}-d7+fS z=Ns)_4@RvR4K9lM5w{6NBsc$DW(~`tbxc38U(a3K^C85wir5Kl@!-TJQNYcM&W zG*)o&;9%~;y7;Q|F&dWPhfS82|t8rAF}C+O9dRBJp&upU`xV@v+BX5 zlWRh%7%mhOMaOlRY(<=Bmb}bbxCB-QnDA?WHj+qI`zU`&*y90d|2kQJIbzB)&+Xi{ zeQ%Y^w*ln9Cd=Cmk+<6R?myVt0M7=}_*7X#gOF5e=^)UM-%KJ352qm%sm2fqU4KQbtA=8|hWmgm)k%p@Yvdl%P8fVBlY43+~_JZ~=MTMw2^)ey1yL~p#F;4x% zcHdvKcVY5k3Fe50*_v=(WR&>JR}z-X%%CoJCuh6wl5HM^T;lxUUrWF(7Sa-^77Ltz z`Y>$J`B?n;k1H>zs(RJ*6ZDEL8L$1Ls+Uu(ge$J z1h(Ce zxcd`5hq^kSI`|~6i7sVti=%GG^q8^uZch zu?0E2qgk_IS6H^BYfYODz2TrGq@fT@hl5)U-J`uqZ1eCDoV9^kdF#{O&ju0Y2MX`j z`nGjq>SnE3pdnaOWQ%^;5|l=QqTVoGHoquF7^wC@GLnbi^yx?)9fBK?prxck;oc=E gjn-N70vwPl1~?S0vRtp)ggG!14GO=6;AXV=Ul3n(bpQYW diff --git a/test/srcs/memory_test_4.c b/test/srcs/memory_test_4.c index 4a63ca91..5b59f92f 100644 --- a/test/srcs/memory_test_4.c +++ b/test/srcs/memory_test_4.c @@ -8,6 +8,10 @@ #include #include #include +#include + +sem_t semaphores[4]; +sem_t leaks_done; void leak(char *ptr) { @@ -34,6 +38,10 @@ void* thread_fun(void *arg) leak(test); leak(test_ptr); + + sem_post(&leaks_done); + + sem_wait(&semaphores[thread_index]); } int main() @@ -41,13 +49,23 @@ int main() // allocate four threads pthread_t threads[4]; - for (int i = 0; i < 4; i++) + sem_init(&leaks_done, 0, 0); + + for (int i = 0; i < 4; i++) { + sem_init(&semaphores[i], 0, 0); pthread_create(&threads[i], NULL, thread_fun, (void *) ((unsigned long) i)); + } for (int i = 0; i < 4; i++) - pthread_join(threads[i], NULL); + sem_wait(&leaks_done); before_exit(); + for (int i = 0; i < 4; i++) + sem_post(&semaphores[i]); + + for (int i = 0; i < 4; i++) + pthread_join(threads[i], NULL); + return 0; } \ No newline at end of file From a43ba10e50586bc922d6ce460614ccbef3a81f7e Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Wed, 7 Aug 2024 00:16:41 +0200 Subject: [PATCH 099/144] fix: change error descriptions in internal_debugger A nice chain of wrong copy-pastes --- libdebug/debugger/internal_debugger.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 7452a3f3..e4e038de 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -1351,7 +1351,7 @@ def __threaded_flush_fp_registers(self: InternalDebugger, registers: Registers) def _peek_memory(self: InternalDebugger, address: int) -> bytes: """Reads memory from the process.""" if not self.instanced: - raise RuntimeError("Process not running, cannot step.") + raise RuntimeError("Process not running, cannot access memory.") if self.running: # Reading memory while the process is running could lead to concurrency issues @@ -1380,7 +1380,7 @@ def _peek_memory(self: InternalDebugger, address: int) -> bytes: def _fast_read_memory(self: InternalDebugger, address: int, size: int) -> bytes: """Reads memory from the process.""" if not self.instanced: - raise RuntimeError("Process not running, cannot step.") + raise RuntimeError("Process not running, cannot access memory.") if self.running: # Reading memory while the process is running could lead to concurrency issues @@ -1397,7 +1397,7 @@ def _fast_read_memory(self: InternalDebugger, address: int, size: int) -> bytes: def _poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: """Writes memory to the process.""" if not self.instanced: - raise RuntimeError("Process not running, cannot step.") + raise RuntimeError("Process not running, cannot access memory.") if self.running: # Reading memory while the process is running could lead to concurrency issues @@ -1417,7 +1417,7 @@ def _poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: def _fast_write_memory(self: InternalDebugger, address: int, data: bytes) -> None: """Writes memory to the process.""" if not self.instanced: - raise RuntimeError("Process not running, cannot step.") + raise RuntimeError("Process not running, cannot access memory.") if self.running: # Reading memory while the process is running could lead to concurrency issues @@ -1434,7 +1434,7 @@ def _fast_write_memory(self: InternalDebugger, address: int, data: bytes) -> Non def _fetch_fp_registers(self: InternalDebugger, registers: Registers) -> None: """Fetches the floating point registers of a thread.""" if not self.instanced: - raise RuntimeError("Process not running, cannot step.") + raise RuntimeError("Process not running, cannot read floating-point registers.") self._ensure_process_stopped() @@ -1448,7 +1448,7 @@ def _fetch_fp_registers(self: InternalDebugger, registers: Registers) -> None: def _flush_fp_registers(self: InternalDebugger, registers: Registers) -> None: """Flushes the floating point registers of a thread.""" if not self.instanced: - raise RuntimeError("Process not running, cannot step.") + raise RuntimeError("Process not running, cannot write floating-point registers.") self._ensure_process_stopped() From 858e89a3fe1e18130950373aa3e9f1f3834d52a9 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Wed, 7 Aug 2024 02:54:25 +0200 Subject: [PATCH 100/144] feat: make memory access methods interchangeable, direct access lazily initialized --- libdebug/debugger/internal_debugger.py | 21 +++++++++++---------- libdebug/memory/process_memory_manager.py | 15 +++++++++++++-- libdebug/state/thread_context.py | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index e4e038de..ad97a10e 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -174,6 +174,7 @@ def __init__(self: InternalDebugger) -> None: self._is_running = False self.resume_context = ResumeContext() self._process_memory_manager = ProcessMemoryManager() + self.fast_memory = False self.__polling_thread_command_queue = Queue() self.__polling_thread_response_queue = Queue() @@ -202,10 +203,8 @@ def start_up(self: InternalDebugger) -> None: self.start_processing_thread() with extend_internal_debugger(self): self.debugging_interface = provide_debugging_interface() - if self.fast_memory: - self.memory = DirectMemoryView(self._fast_read_memory, self._fast_write_memory) - else: - self.memory = ChunkedMemoryView(self._peek_memory, self._poke_memory) + self._fast_memory = DirectMemoryView(self._fast_read_memory, self._fast_write_memory) + self._slow_memory = ChunkedMemoryView(self._peek_memory, self._poke_memory) def start_processing_thread(self: InternalDebugger) -> None: """Starts the thread that will poll the traced process for state change.""" @@ -257,8 +256,7 @@ def run(self: InternalDebugger) -> None: if not self.pipe_manager: raise RuntimeError("Something went wrong during pipe initialization.") - if self.fast_memory: - self._process_memory_manager.open(self.process_id) + self._process_memory_manager.open(self.process_id) return self.pipe_manager @@ -278,8 +276,7 @@ def attach(self: InternalDebugger, pid: int) -> None: self.__polling_thread_command_queue.put((self.__threaded_attach, (pid,))) - if self.fast_memory: - self._process_memory_manager.open(self.process_id) + self._process_memory_manager.open(self.process_id) self._join_and_check_status() @@ -306,8 +303,7 @@ def kill(self: InternalDebugger) -> None: # This exception might occur if the process has already died liblog.debugger("OSError raised during kill") - if self.fast_memory: - self._process_memory_manager.close() + self._process_memory_manager.close() self.__polling_thread_command_queue.put((self.__threaded_kill, ())) @@ -384,6 +380,11 @@ def maps(self: InternalDebugger) -> list[MemoryMap]: self._ensure_process_stopped() return self.debugging_interface.maps() + @property + def memory(self: InternalDebugger) -> AbstractMemoryView: + """The memory view of the debugged process.""" + return self._fast_memory if self.fast_memory else self._slow_memory + def print_maps(self: InternalDebugger) -> None: """Prints the memory maps of the process.""" self._ensure_process_stopped() diff --git a/libdebug/memory/process_memory_manager.py b/libdebug/memory/process_memory_manager.py index d2cafa79..05f5cfa7 100644 --- a/libdebug/memory/process_memory_manager.py +++ b/libdebug/memory/process_memory_manager.py @@ -13,7 +13,10 @@ class ProcessMemoryManager: def open(self: ProcessMemoryManager, process_id: int) -> None: """Initializes the ProcessMemoryManager.""" self.process_id = process_id - self._mem_file = open(f"/proc/{process_id}/mem", "r+b", buffering=0) + self._mem_file = None + + def _open(self: ProcessMemoryManager) -> None: + self._mem_file = open(f"/proc/{self.process_id}/mem", "r+b", buffering=0) def read(self: ProcessMemoryManager, address: int, size: int) -> bytes: """Reads memory from the target process. @@ -25,6 +28,9 @@ def read(self: ProcessMemoryManager, address: int, size: int) -> bytes: Returns: bytes: The read bytes. """ + if not self._mem_file: + self._open() + self._mem_file.seek(address) return self._mem_file.read(size) @@ -35,9 +41,14 @@ def write(self: ProcessMemoryManager, address: int, data: bytes) -> None: address (int): The address to write to. data (bytes): The data to write. """ + if not self._mem_file: + self._open() + self._mem_file.seek(address) self._mem_file.write(data) def close(self: ProcessMemoryManager) -> None: """Closes the memory file.""" - self._mem_file.close() + if self._mem_file: + self._mem_file.close() + self._mem_file = None diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index 97fd9510..c1dfc8d4 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -17,10 +17,10 @@ from libdebug.utils.signal_utils import resolve_signal_name, resolve_signal_number if TYPE_CHECKING: - from libdebug.data.abstract_memory_view import AbstractMemoryView from libdebug.data.register_holder import RegisterHolder from libdebug.data.registers import Registers from libdebug.debugger.internal_debugger import InternalDebugger + from libdebug.memory.abstract_memory_view import AbstractMemoryView class ThreadContext: From d373132201918e51368ad739703e299589345c36 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Wed, 7 Aug 2024 03:03:09 +0200 Subject: [PATCH 101/144] test: add test for mixed memory access, slightly change multithreaded memory test --- test/run_suite.py | 1 + test/scripts/memory_fast_test.py | 44 ++++++++++++++++++++++++++++++-- test/scripts/memory_test.py | 4 +-- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/test/run_suite.py b/test/run_suite.py index 7087acea..30ca2fd9 100644 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -79,6 +79,7 @@ def fast_suite(): suite.addTest(MemoryFastTest("test_memory_large_read")) suite.addTest(MemoryFastTest("test_invalid_memory_location")) suite.addTest(MemoryFastTest("test_memory_multiple_threads")) + suite.addTest(MemoryFastTest("test_memory_mixed_access")) suite.addTest(HwBasicTest("test_basic")) suite.addTest(HwBasicTest("test_registers")) suite.addTest(BacktraceTest("test_backtrace_as_symbols")) diff --git a/test/scripts/memory_fast_test.py b/test/scripts/memory_fast_test.py index 3c8f3264..80bcd7a9 100644 --- a/test/scripts/memory_fast_test.py +++ b/test/scripts/memory_fast_test.py @@ -343,9 +343,49 @@ def leak(t, _): leaks = [d.memory[x, 16] for x in leak_addresses] - # heap allocated leaks should still be valid - for i in range(4, 8, 1): + # threads are stopped, check we correctly read the memory + for i in range(8): assert (chr(i).encode("latin-1") * 16) in leaks d.kill() d.terminate() + + def test_memory_mixed_access(self): + d = debugger("binaries/memory_test_2", fast_memory=True) + + d.run() + + base = d.regs.rip & 0xFFFFFFFFFFFFF000 - 0x1000 + + # Test different ways to access memory at the start of the file + file_0 = d.memory[base, 256] + d.fast_memory = False + file_1 = d.memory[0x0, 256] + d.fast_memory = True + file_2 = d.memory[0x0:0x100] + d.fast_memory = False + file_3 = d.memory[0x0:0x100] + + self.assertEqual(file_0, file_1) + self.assertEqual(file_0, file_2) + self.assertEqual(file_0, file_3) + + for _ in range(3): + d.step() + + d.fast_memory = False + d.memory[base] = b"abcd1234" + self.assertEqual(d.memory[base, 8], b"abcd1234") + + d.fast_memory = True + self.assertEqual(d.memory[base, 8], b"abcd1234") + d.memory[base] = b"\x01\x02\x03\x04\x05\x06\x07\x08" + self.assertEqual(d.memory[base, 8], b"\x01\x02\x03\x04\x05\x06\x07\x08") + + d.fast_memory = False + self.assertEqual(d.memory[base, 8], b"\x01\x02\x03\x04\x05\x06\x07\x08") + d.memory[base] = b"abcd1234" + self.assertEqual(d.memory[base, 8], b"abcd1234") + + d.kill() + d.terminate() diff --git a/test/scripts/memory_test.py b/test/scripts/memory_test.py index 73f645e9..febbf77e 100644 --- a/test/scripts/memory_test.py +++ b/test/scripts/memory_test.py @@ -351,8 +351,8 @@ def leak(t, _): leaks = [d.memory[x, 16] for x in leak_addresses] - # heap allocated leaks should still be valid - for i in range(4, 8, 1): + # threads are stopped, check we correctly read the memory + for i in range(8): assert (chr(i).encode("latin-1") * 16) in leaks d.kill() From edfa28c816fcb043182bef8a7900dc827d815e3b Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Wed, 7 Aug 2024 13:50:07 +0200 Subject: [PATCH 102/144] feat: add aarch64 call utilities manager --- .../aarch64/aarch64_call_utilities.py | 38 +++++++++++++++++++ .../architectures/call_utilities_provider.py | 9 ++++- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 libdebug/architectures/aarch64/aarch64_call_utilities.py diff --git a/libdebug/architectures/aarch64/aarch64_call_utilities.py b/libdebug/architectures/aarch64/aarch64_call_utilities.py new file mode 100644 index 00000000..4b7b6952 --- /dev/null +++ b/libdebug/architectures/aarch64/aarch64_call_utilities.py @@ -0,0 +1,38 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +from __future__ import annotations + +from libdebug.architectures.call_utilities_manager import CallUtilitiesManager + + +class Aarch64CallUtilities(CallUtilitiesManager): + """Class that provides call utilities for the AArch64 architecture.""" + + def is_call(self: Aarch64CallUtilities, opcode_window: bytes) -> bool: + """Check if the current instruction is a call instruction.""" + # Check for BL instruction + if (opcode_window[3] & 0xFC) == 0x94: + return True + + # Check for BLR instruction + if opcode_window[3] == 0xD6 and (opcode_window[2] & 0x3F) == 0x3F: + return True + + return False + + def compute_call_skip(self: Aarch64CallUtilities, opcode_window: bytes) -> int: + """Compute the instruction size of the current call instruction.""" + # Check for BL instruction + if self.is_call(opcode_window): + return 4 + + return 0 + + def get_call_and_skip_amount(self: Aarch64CallUtilities, opcode_window: bytes) -> tuple[bool, int]: + """Get the call instruction and the amount of bytes to skip.""" + skip = self.compute_call_skip(opcode_window) + return skip != 0, skip diff --git a/libdebug/architectures/call_utilities_provider.py b/libdebug/architectures/call_utilities_provider.py index 8b2acf0e..4bee02f8 100644 --- a/libdebug/architectures/call_utilities_provider.py +++ b/libdebug/architectures/call_utilities_provider.py @@ -1,20 +1,27 @@ # # This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2024 Francesco Panebianco. All rights reserved. +# Copyright (c) 2024 Francesco Panebianco, Roberto Alessandro Bertolini. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for details. # +from libdebug.architectures.aarch64.aarch64_call_utilities import ( + Aarch64CallUtilities, +) from libdebug.architectures.amd64.amd64_call_utilities import ( Amd64CallUtilities, ) from libdebug.architectures.call_utilities_manager import CallUtilitiesManager +_aarch64_call_utilities = Aarch64CallUtilities() _amd64_call_utilities = Amd64CallUtilities() + def call_utilities_provider(architecture: str) -> CallUtilitiesManager: """Returns an instance of the call utilities provider to be used by the `_InternalDebugger` class.""" match architecture: case "amd64": return _amd64_call_utilities + case "aarch64": + return _aarch64_call_utilities case _: raise NotImplementedError(f"Architecture {architecture} not available.") From f3d1e4e845ceab370801397ed5a9350ac18070c0 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Wed, 7 Aug 2024 13:51:13 +0200 Subject: [PATCH 103/144] test: add next test for aarch64 --- test/aarch64/run_suite.py | 2 + test/aarch64/scripts/next_test.py | 111 ++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 test/aarch64/scripts/next_test.py diff --git a/test/aarch64/run_suite.py b/test/aarch64/run_suite.py index 619514d6..ebf8be5d 100644 --- a/test/aarch64/run_suite.py +++ b/test/aarch64/run_suite.py @@ -26,6 +26,7 @@ from scripts.hijack_syscall_test import HijackSyscallTest from scripts.jumpstart_test import JumpstartTest from scripts.memory_test import MemoryTest +from scripts.next_test import NextTest from scripts.signals_multithread_test import SignalMultithreadTest from scripts.speed_test import SpeedTest from scripts.thread_test_complex import ThreadTestComplex @@ -54,6 +55,7 @@ def fast_suite(): suite.addTest(TestLoader().loadTestsFromTestCase(HijackSyscallTest)) suite.addTest(TestLoader().loadTestsFromTestCase(JumpstartTest)) suite.addTest(TestLoader().loadTestsFromTestCase(MemoryTest)) + suite.addTest(TestLoader().loadTestsFromTestCase(NextTest)) suite.addTest(TestLoader().loadTestsFromTestCase(SignalMultithreadTest)) suite.addTest(TestLoader().loadTestsFromTestCase(SpeedTest)) suite.addTest(TestLoader().loadTestsFromTestCase(ThreadTestComplex)) diff --git a/test/aarch64/scripts/next_test.py b/test/aarch64/scripts/next_test.py new file mode 100644 index 00000000..8ebadcb4 --- /dev/null +++ b/test/aarch64/scripts/next_test.py @@ -0,0 +1,111 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Francesco Panebianco, Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# +import unittest + +from libdebug import debugger + +TEST_ENTRYPOINT = 0xaaaaaaaa0930 + +# Addresses of the dummy functions +CALL_C_ADDRESS = 0xaaaaaaaa0934 +TEST_BREAKPOINT_ADDRESS = 0xaaaaaaaa0920 + +# Addresses of noteworthy instructions +RETURN_POINT_FROM_C = 0xaaaaaaaa0938 + +class NextTest(unittest.TestCase): + def setUp(self): + pass + + def test_next(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + d.run() + + # Get to test entrypoint + entrypoint_bp = d.breakpoint(TEST_ENTRYPOINT) + d.cont() + + self.assertEqual(d.regs.pc, TEST_ENTRYPOINT) + + # -------- Block 1 ------- # + # Simple Step # + # ------------------------ # + + # Reach call of function c + d.next() + self.assertEqual(d.regs.pc, CALL_C_ADDRESS) + + # -------- Block 2 ------- # + # Skip a call # + # ------------------------ # + + d.next() + self.assertEqual(d.regs.pc, RETURN_POINT_FROM_C) + + d.kill() + d.terminate() + + def test_next_breakpoint(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + d.run() + + # Get to test entrypoint + entrypoint_bp = d.breakpoint(TEST_ENTRYPOINT) + d.cont() + + self.assertEqual(d.regs.pc, TEST_ENTRYPOINT) + + # Reach call of function c + d.next() + + self.assertEqual(d.regs.pc, CALL_C_ADDRESS) + + # -------- Block 1 ------- # + # Call with breakpoint # + # ------------------------ # + + # Set breakpoint + test_breakpoint = d.breakpoint(TEST_BREAKPOINT_ADDRESS) + + d.next() + + # Check we hit the breakpoint + self.assertEqual(d.regs.pc, TEST_BREAKPOINT_ADDRESS) + self.assertEqual(test_breakpoint.hit_count, 1) + + d.kill() + d.terminate() + + def test_next_breakpoint_hw(self): + d = debugger("binaries/finish_test", auto_interrupt_on_command=False) + d.run() + + # Get to test entrypoint + entrypoint_bp = d.breakpoint(TEST_ENTRYPOINT) + d.cont() + + self.assertEqual(d.regs.pc, TEST_ENTRYPOINT) + + # Reach call of function c + d.next() + + self.assertEqual(d.regs.pc, CALL_C_ADDRESS) + + # -------- Block 1 ------- # + # Call with breakpoint # + # ------------------------ # + + # Set breakpoint + test_breakpoint = d.breakpoint(TEST_BREAKPOINT_ADDRESS, hardware=True) + + d.next() + + # Check we hit the breakpoint + self.assertEqual(d.regs.pc, TEST_BREAKPOINT_ADDRESS) + self.assertEqual(test_breakpoint.hit_count, 1) + + d.kill() + d.terminate() \ No newline at end of file From 2eabc7d767a8bfc383bfa1fc4e8467b91588db9c Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Wed, 7 Aug 2024 14:02:38 +0200 Subject: [PATCH 104/144] fix: correct architecture-dependent instruction pointer accesses --- libdebug/ptrace/ptrace_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index fb2a7b9f..d0919b4a 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -352,13 +352,13 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None def next(self: PtraceInterface, thread: ThreadContext) -> None: """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns.""" - opcode_window = thread.memory.read(thread.regs.rip, 8) + opcode_window = thread.memory.read(thread.instruction_pointer, 8) # Check if the current instruction is a call and its skip amount is_call, skip = call_utilities_provider(self._internal_debugger.arch).get_call_and_skip_amount(opcode_window) if is_call: - skip_address = thread.regs.rip + skip + skip_address = thread.instruction_pointer + skip # If a breakpoint already exists at the return address, we don't need to set a new one found = False From 849b455cdb44ed9dc25c14f500ff6298b730d597 Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Fri, 16 Aug 2024 17:28:37 +0200 Subject: [PATCH 105/144] feat: add atexit handler to kill all processes --- libdebug/debugger/internal_debugger_holder.py | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/libdebug/debugger/internal_debugger_holder.py b/libdebug/debugger/internal_debugger_holder.py index 186a307b..2adda00a 100644 --- a/libdebug/debugger/internal_debugger_holder.py +++ b/libdebug/debugger/internal_debugger_holder.py @@ -1,20 +1,44 @@ # # This file is part of libdebug Python library (https://github.com/libdebug/libdebug). -# Copyright (c) 2024 Gabriele Digregorio. All rights reserved. +# Copyright (c) 2024 Gabriele Digregorio, Roberto Alessandro Bertolini. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for details. # +import atexit from dataclasses import dataclass, field from threading import Lock from weakref import WeakKeyDictionary +from libdebug.liblog import liblog + @dataclass class InternalDebuggerHolder: """A holder for internal debuggers.""" + internal_debuggers: WeakKeyDictionary = field(default_factory=WeakKeyDictionary) global_internal_debugger = None internal_debugger_lock = Lock() internal_debugger_holder = InternalDebuggerHolder() + + +def _cleanup_internal_debugger() -> None: + """Cleanup the internal debugger.""" + for debugger in set(internal_debugger_holder.internal_debuggers.values()): + if debugger.instanced: + try: + debugger.interrupt() + except Exception as e: + liblog.debugger(f"Error while interrupting debuggee: {e}") + + try: + debugger.kill() + except Exception as e: + liblog.debugger(f"Error while killing debuggee: {e}") + + debugger.terminate() + + +atexit.register(_cleanup_internal_debugger) From 690ee165382763b4fafe69d7c2608f058c02ec56 Mon Sep 17 00:00:00 2001 From: io-no Date: Fri, 30 Aug 2024 00:26:18 +0200 Subject: [PATCH 106/144] fix: exposed fast_memory attribute correctly --- libdebug/debugger/debugger.py | 24 ++++++++++++++++++++++++ libdebug/debugger/internal_debugger.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/libdebug/debugger/debugger.py b/libdebug/debugger/debugger.py index d7437be1..e1db515e 100644 --- a/libdebug/debugger/debugger.py +++ b/libdebug/debugger/debugger.py @@ -475,6 +475,30 @@ def signals_to_block(self: Debugger, signals: list[int | str]) -> None: self._internal_debugger.signals_to_block = signals + @property + def fast_memory(self: Debugger) -> bool: + """Get the state of the fast_memory flag. + + It is used to determine if the debugger should use a faster memory access method. + + Returns: + bool: True if the debugger should use a faster memory access method, False otherwise. + """ + return self._internal_debugger.fast_memory + + @fast_memory.setter + def fast_memory(self: Debugger, value: bool) -> None: + """Set the state of the fast_memory flag. + + It is used to determine if the debugger should use a faster memory access method. + + Args: + value (bool): the value to set. + """ + if not isinstance(value, bool): + raise TypeError("fast_memory must be a boolean") + self._internal_debugger.fast_memory = value + def __getattr__(self: Debugger, name: str) -> object: """This function is called when an attribute is not found in the `Debugger` object. diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index ad97a10e..34da2159 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -87,7 +87,7 @@ class InternalDebugger: """A flag that indicates if the debugger should escape anti-debugging techniques.""" fast_memory: bool - """A flag that indicates if the debugger should use a faster memory reading method.""" + """A flag that indicates if the debugger should use a faster memory access method.""" autoreach_entrypoint: bool """A flag that indicates if the debugger should automatically reach the entry point of the debugged process.""" From bc9db4634e70d9a692ded656cadc8fa8ed675d20 Mon Sep 17 00:00:00 2001 From: io-no Date: Fri, 30 Aug 2024 00:28:45 +0200 Subject: [PATCH 107/144] fix: now the process memory manager correctly closes the memory file when detach is called --- libdebug/debugger/internal_debugger.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 34da2159..92871515 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -291,8 +291,7 @@ def detach(self: InternalDebugger) -> None: self._join_and_check_status() - if self.fast_memory: - self._process_memory_manager.close() + self._process_memory_manager.close() @background_alias(_background_invalid_call) def kill(self: InternalDebugger) -> None: From 4465e886eda3e6966281c26e29aa458dcb610403 Mon Sep 17 00:00:00 2001 From: io-no Date: Fri, 30 Aug 2024 00:34:08 +0200 Subject: [PATCH 108/144] refactor: deleted unused legacy attribute --- libdebug/memory/abstract_memory_view.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/libdebug/memory/abstract_memory_view.py b/libdebug/memory/abstract_memory_view.py index 1e87a8f3..865970ec 100644 --- a/libdebug/memory/abstract_memory_view.py +++ b/libdebug/memory/abstract_memory_view.py @@ -23,9 +23,6 @@ class AbstractMemoryView(MutableSequence, ABC): An implementation of class must be used to read and write memory of the target process. """ - context: InternalDebugger - """The debugging context of the target process.""" - def __init__(self: AbstractMemoryView) -> None: """Initializes the MemoryView.""" self._internal_debugger = provide_internal_debugger(self) From 0c069175cd956be8875be5711df0d1246a6d17b2 Mon Sep 17 00:00:00 2001 From: io-no Date: Fri, 30 Aug 2024 00:35:55 +0200 Subject: [PATCH 109/144] refactor: fixed attribute definitions --- libdebug/debugger/internal_debugger.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 92871515..fa7f1b13 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -152,6 +152,12 @@ class InternalDebugger: _is_running: bool """The overall state of the debugged process. True if the process is running, False otherwise.""" + _fast_memory: DirectMemoryView + """The memory view of the debugged process using the fast memory access method.""" + + _slow_memory: ChunkedMemoryView + """The memory view of the debugged process using the slow memory access method.""" + def __init__(self: InternalDebugger) -> None: """Initialize the context.""" # These must be reinitialized on every call to "debugger" From 932402f4e9c2cd20b830f88cc19da6083e0f16fd Mon Sep 17 00:00:00 2001 From: io-no Date: Fri, 30 Aug 2024 00:40:15 +0200 Subject: [PATCH 110/144] style: removed unused imports --- libdebug/memory/abstract_memory_view.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libdebug/memory/abstract_memory_view.py b/libdebug/memory/abstract_memory_view.py index 865970ec..e9d66b21 100644 --- a/libdebug/memory/abstract_memory_view.py +++ b/libdebug/memory/abstract_memory_view.py @@ -8,14 +8,10 @@ from abc import ABC, abstractmethod from collections.abc import MutableSequence -from typing import TYPE_CHECKING from libdebug.debugger.internal_debugger_instance_manager import provide_internal_debugger from libdebug.liblog import liblog -if TYPE_CHECKING: - from libdebug.debugger.internal_debugger import InternalDebugger - class AbstractMemoryView(MutableSequence, ABC): """An abstract memory interface for the target process. From 6a47427d39a5963661b508ae63ece43b89203752 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 30 Aug 2024 16:48:07 +0200 Subject: [PATCH 111/144] fix: correct improper arch usage in place of platform Additionally, this restores our unofficial partial support for i386 on amd64 --- libdebug/architectures/ptrace_software_breakpoint_patcher.py | 2 +- libdebug/architectures/register_helper.py | 2 +- libdebug/debugger/internal_debugger.py | 4 ++-- libdebug/utils/libcontext.py | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libdebug/architectures/ptrace_software_breakpoint_patcher.py b/libdebug/architectures/ptrace_software_breakpoint_patcher.py index 0a5d21be..54f2d267 100644 --- a/libdebug/architectures/ptrace_software_breakpoint_patcher.py +++ b/libdebug/architectures/ptrace_software_breakpoint_patcher.py @@ -8,7 +8,7 @@ def software_breakpoint_byte_size(architecture: str) -> int: """Return the size of a software breakpoint instruction.""" match architecture: - case "amd64": + case "amd64" | "i386": return 1 case "aarch64": return 4 diff --git a/libdebug/architectures/register_helper.py b/libdebug/architectures/register_helper.py index 8341d6aa..95e20048 100644 --- a/libdebug/architectures/register_helper.py +++ b/libdebug/architectures/register_helper.py @@ -20,7 +20,7 @@ def register_holder_provider( ) -> RegisterHolder: """Returns an instance of the register holder to be used by the `_InternalDebugger` class.""" match architecture: - case "amd64": + case "amd64" | "i386": return Amd64PtraceRegisterHolder(register_file, fp_register_file) case "aarch64": return Aarch64PtraceRegisterHolder(register_file, fp_register_file) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index d9edf4da..cdd89c12 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -207,7 +207,7 @@ def start_up(self: InternalDebugger) -> None: self.memory = MemoryView( self._peek_memory, self._poke_memory, - unit_size=get_platform_register_size(self.arch), + unit_size=get_platform_register_size(libcontext.platform), ) def start_processing_thread(self: InternalDebugger) -> None: @@ -1327,7 +1327,7 @@ def __threaded_migrate_from_gdb(self: InternalDebugger) -> None: def __threaded_peek_memory(self: InternalDebugger, address: int) -> bytes | BaseException: value = self.debugging_interface.peek_memory(address) - return value.to_bytes(get_platform_register_size(self.arch), sys.byteorder) + return value.to_bytes(get_platform_register_size(libcontext.platform), sys.byteorder) def __threaded_poke_memory(self: InternalDebugger, address: int, data: bytes) -> None: int_data = int.from_bytes(data, sys.byteorder) diff --git a/libdebug/utils/libcontext.py b/libdebug/utils/libcontext.py index 3ad679f2..e1167bf7 100644 --- a/libdebug/utils/libcontext.py +++ b/libdebug/utils/libcontext.py @@ -12,6 +12,7 @@ from copy import deepcopy from libdebug.liblog import liblog +from libdebug.utils.arch_mappings import map_arch class LibContext: @@ -153,7 +154,7 @@ def general_logger(self: LibContext, value: str) -> None: @property def platform(self: LibContext) -> str: """Return the current platform.""" - return platform.machine() + return map_arch(platform.machine()) @property def terminal(self: LibContext) -> list[str]: From 5e7153d88f121d3aef8d1b2bd2cdc3ffde9d989c Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 30 Aug 2024 16:50:04 +0200 Subject: [PATCH 112/144] fix: remove unneeded lower() calls The junior developer had a near-fatal seizure at the sight of this monstrosity: an unneeded call to .lower()! --- libdebug/architectures/aarch64/aarch64_breakpoint_validator.py | 2 +- libdebug/architectures/amd64/amd64_breakpoint_validator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py b/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py index ebc4684e..b7c5f4b8 100644 --- a/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py +++ b/libdebug/architectures/aarch64/aarch64_breakpoint_validator.py @@ -14,7 +14,7 @@ def validate_breakpoint_aarch64(bp: Breakpoint) -> None: """Validate a hardware breakpoint for the AARCH64 architecture.""" - if bp.condition.lower() not in ["r", "w", "rw", "x"]: + if bp.condition not in ["r", "w", "rw", "x"]: raise ValueError("Invalid condition for watchpoints. Supported conditions are 'r', 'w', 'rw', 'x'.") if not (1 <= bp.length <= 8): diff --git a/libdebug/architectures/amd64/amd64_breakpoint_validator.py b/libdebug/architectures/amd64/amd64_breakpoint_validator.py index 08aee677..cf54a53d 100644 --- a/libdebug/architectures/amd64/amd64_breakpoint_validator.py +++ b/libdebug/architectures/amd64/amd64_breakpoint_validator.py @@ -14,7 +14,7 @@ def validate_breakpoint_amd64(bp: Breakpoint) -> None: """Validate a hardware breakpoint for the AMD64 architecture.""" - if bp.condition.lower() not in ["w", "rw", "x"]: + if bp.condition not in ["w", "rw", "x"]: raise ValueError("Invalid condition for watchpoints. Supported conditions are 'w', 'rw', 'x'.") if bp.length not in [1, 2, 4, 8]: From 1f5115df71993564ce9824c2872d95fe4cd26eb4 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 30 Aug 2024 16:59:28 +0200 Subject: [PATCH 113/144] fix: move arch getters and setters to the public debugger --- libdebug/debugger/debugger.py | 11 +++++++++++ libdebug/debugger/internal_debugger.py | 12 +----------- libdebug/libdebug.py | 8 ++++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/libdebug/debugger/debugger.py b/libdebug/debugger/debugger.py index b4b5bcea..bac8603e 100644 --- a/libdebug/debugger/debugger.py +++ b/libdebug/debugger/debugger.py @@ -8,6 +8,7 @@ from contextlib import contextmanager from typing import TYPE_CHECKING +from libdebug.utils.arch_mappings import map_arch from libdebug.utils.signal_utils import ( get_all_signal_numbers, resolve_signal_name, @@ -317,6 +318,16 @@ def wp( file=file, ) + @property + def arch(self: Debugger) -> str: + """Get the architecture of the process.""" + return self._internal_debugger.arch + + @arch.setter + def arch(self: Debugger, value: str) -> None: + """Set the architecture of the process.""" + self._internal_debugger.arch = map_arch(value) + @property def threads(self: Debugger) -> list[ThreadContext]: """Get the list of threads in the process.""" diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index cdd89c12..d987423b 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -174,7 +174,7 @@ def __init__(self: InternalDebugger) -> None: self.instanced = False self._is_running = False self.resume_context = ResumeContext() - self._arch = map_arch(libcontext.platform) + self.arch = map_arch(libcontext.platform) self.__polling_thread_command_queue = Queue() self.__polling_thread_response_queue = Queue() @@ -291,16 +291,6 @@ def detach(self: InternalDebugger) -> None: self._join_and_check_status() - @property - def arch(self: InternalDebugger) -> str: - """The architecture of the debugged process.""" - return self._arch - - @arch.setter - def arch(self: InternalDebugger, value: str) -> None: - """The architecture of the debugged process.""" - self._arch = map_arch(value) - @background_alias(_background_invalid_call) def kill(self: InternalDebugger) -> None: """Kills the process.""" diff --git a/libdebug/libdebug.py b/libdebug/libdebug.py index 67a473fe..bf7e4dd0 100644 --- a/libdebug/libdebug.py +++ b/libdebug/libdebug.py @@ -42,11 +42,11 @@ def debugger( internal_debugger.auto_interrupt_on_command = auto_interrupt_on_command internal_debugger.escape_antidebug = escape_antidebug - # If we are attaching, we assume the architecture is the same as the current platform - if argv: - internal_debugger.arch = elf_architecture(argv[0]) - debugger = Debugger() debugger.post_init_(internal_debugger) + # If we are attaching, we assume the architecture is the same as the current platform + if argv: + debugger.arch = elf_architecture(argv[0]) + return debugger From 594b70dccdc974e19418e07a420ec47e1f649170 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 30 Aug 2024 17:01:46 +0200 Subject: [PATCH 114/144] docs: restore wrongfully-deleted comment in ptrace_interface --- libdebug/ptrace/ptrace_interface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index d0919b4a..dea1414e 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -327,6 +327,8 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None should_disable = False if not found: + # Check if we have enough hardware breakpoints available + # Otherwise we use a software breakpoint install_hw_bp = ( self.lib_trace.get_remaining_hw_breakpoint_count(self._global_state, thread.thread_id) > 0 ) From 67e537649a8e1940500d747cff76d90104419cb6 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 30 Aug 2024 17:13:02 +0200 Subject: [PATCH 115/144] feat: parse all the things we need from the ELF at the same time Many thanks to the junior developer for proposing this change, which net us a tremendous 0% increase in performance --- libdebug/utils/elf_utils.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/libdebug/utils/elf_utils.py b/libdebug/utils/elf_utils.py index f738dbe2..7a5212bb 100644 --- a/libdebug/utils/elf_utils.py +++ b/libdebug/utils/elf_utils.py @@ -211,22 +211,37 @@ def resolve_address(path: str, address: int) -> str: @functools.cache -def is_pie(path: str) -> bool: - """Returns True if the specified ELF file is position independent, False otherwise. +def parse_elf_characteristics(path: str) -> tuple[bool, int, str]: + """Returns a tuple containing the PIE flag, the entry point and the architecture of the specified ELF file. Args: path (str): The path to the ELF file. Returns: - bool: True if the specified ELF file is position independent, False otherwise. + tuple: A tuple containing the PIE flag, the entry point and the architecture of the specified ELF file. """ with Path(path).open("rb") as elf_file: elf = ELFFile(elf_file) - return elf.header.e_type == "ET_DYN" + pie = elf.header.e_type == "ET_DYN" + entry_point = elf.header.e_entry + arch = elf.get_machine_arch() + + return pie, entry_point, arch + + +def is_pie(path: str) -> bool: + """Returns True if the specified ELF file is position independent, False otherwise. + + Args: + path (str): The path to the ELF file. + + Returns: + bool: True if the specified ELF file is position independent, False otherwise. + """ + return parse_elf_characteristics(path)[0] -@functools.cache def get_entry_point(path: str) -> int: """Returns the entry point of the specified ELF file. @@ -236,13 +251,9 @@ def get_entry_point(path: str) -> int: Returns: int: The entry point of the specified ELF file. """ - with Path(path).open("rb") as elf_file: - elf = ELFFile(elf_file) + return parse_elf_characteristics(path)[1] - return elf.header.e_entry - -@functools.cache def elf_architecture(path: str) -> str: """Returns the architecture of the specified ELF file. @@ -252,7 +263,4 @@ def elf_architecture(path: str) -> str: Returns: str: The architecture of the specified ELF file. """ - with Path(path).open("rb") as elf_file: - elf = ELFFile(elf_file) - - return elf.get_machine_arch() + return parse_elf_characteristics(path)[2] From 0717f00f5c0dd5a405b22cf154f6728be667d48f Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 30 Aug 2024 17:38:09 +0200 Subject: [PATCH 116/144] fix: correct wrong attribute declarations in internal_debugger --- libdebug/debugger/internal_debugger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index d987423b..4f81eee2 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -141,13 +141,13 @@ class InternalDebugger: resume_context: ResumeContext """Context that indicates if the debugger should resume the debugged process.""" - _polling_thread: Thread | None + __polling_thread: Thread | None """The background thread used to poll the process for state change.""" - _polling_thread_command_queue: Queue | None + __polling_thread_command_queue: Queue | None """The queue used to send commands to the background thread.""" - _polling_thread_response_queue: Queue | None + __polling_thread_response_queue: Queue | None """The queue used to receive responses from the background thread.""" _is_running: bool From a9f9ece9ac9786aea1d59d194dbc4150334e2de0 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 30 Aug 2024 17:41:37 +0200 Subject: [PATCH 117/144] fix: join mostly-duplicated functions in ptrace_cffi_source --- libdebug/cffi/ptrace_cffi_source.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index c75e4174..43eda2c5 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -358,7 +358,7 @@ int is_breakpoint_hit(struct hardware_breakpoint *bp) return 0; } -int get_remaining_hw_breakpoint_count(struct global_state *state, int tid) +int _get_remaining_count(struct global_state *state, int tid, int command) { struct user_hwdebug_state dbg_state = {0}; @@ -366,26 +366,19 @@ int get_remaining_hw_breakpoint_count(struct global_state *state, int tid) iov.iov_base = &dbg_state; iov.iov_len = sizeof dbg_state; - unsigned long command = NT_ARM_HW_BREAK; - ptrace(PTRACE_GETREGSET, tid, command, &iov); return dbg_state.dbg_info & 0xff; } -int get_remaining_hw_watchpoint_count(struct global_state *state, int tid) +int get_remaining_hw_breakpoint_count(struct global_state *state, int tid) { - struct user_hwdebug_state dbg_state = {0}; - - struct iovec iov; - iov.iov_base = &dbg_state; - iov.iov_len = sizeof dbg_state; - - unsigned long command = NT_ARM_HW_WATCH; - - ptrace(PTRACE_GETREGSET, tid, command, &iov); + return _get_remaining_count(state, tid, NT_ARM_HW_BREAK); +} - return dbg_state.dbg_info & 0xff; +int get_remaining_hw_watchpoint_count(struct global_state *state, int tid) +{ + return _get_remaining_count(state, tid, NT_ARM_HW_WATCH); } #endif From 9f1187e6e09b0f479b6a430f8d915c11585ad741 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Fri, 30 Aug 2024 17:45:16 +0200 Subject: [PATCH 118/144] chore: remove dead code from CFFI and ptrace_interface I'm starting to think that the junior developer might be low on hard drive space, and that's why he is so pedant about unused methods in the codebase --- libdebug/cffi/ptrace_cffi_build.py | 3 -- libdebug/cffi/ptrace_cffi_source.c | 65 ----------------------------- libdebug/ptrace/ptrace_interface.py | 28 ------------- 3 files changed, 96 deletions(-) diff --git a/libdebug/cffi/ptrace_cffi_build.py b/libdebug/cffi/ptrace_cffi_build.py index ae115061..101044ae 100644 --- a/libdebug/cffi/ptrace_cffi_build.py +++ b/libdebug/cffi/ptrace_cffi_build.py @@ -374,9 +374,6 @@ uint64_t ptrace_peekdata(int pid, uint64_t addr); uint64_t ptrace_pokedata(int pid, uint64_t addr, uint64_t data); - uint64_t ptrace_peekuser(int pid, uint64_t addr); - uint64_t ptrace_pokeuser(int pid, uint64_t addr, uint64_t data); - struct fp_regs_struct *get_thread_fp_regs(struct global_state *state, int tid); void get_fp_regs(int tid, struct fp_regs_struct *fpregs); void set_fp_regs(int tid, struct fp_regs_struct *fpregs); diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 43eda2c5..50f170bb 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -672,71 +672,6 @@ uint64_t ptrace_pokedata(int pid, uint64_t addr, uint64_t data) return ptrace(PTRACE_POKEDATA, pid, (void *)addr, data); } -#ifdef ARCH_AMD64 -uint64_t ptrace_peekuser(int pid, uint64_t addr) -{ - // Since the value returned by a successful PTRACE_PEEK* - // request may be -1, the caller must clear errno before the call, - errno = 0; - - return ptrace(PTRACE_PEEKUSER, pid, addr, NULL); -} - -uint64_t ptrace_pokeuser(int pid, uint64_t addr, uint64_t data) -{ - return ptrace(PTRACE_POKEUSER, pid, addr, data); -} -#endif - -#ifdef ARCH_AARCH64 -// peekuser doesn't exist on ARM64, we have to use getregset -#define SIZEOF_STRUCT_HWDEBUG_STATE 8 + (16 * 16) - -uint64_t ptrace_peekuser(int pid, uint64_t addr) -{ - unsigned char *data = malloc(SIZEOF_STRUCT_HWDEBUG_STATE); - memset(data, 0, SIZEOF_STRUCT_HWDEBUG_STATE); - - struct iovec iov; - iov.iov_base = data; - iov.iov_len = SIZEOF_STRUCT_HWDEBUG_STATE; - - unsigned long command = (addr & 0x1000) ? NT_ARM_HW_WATCH : NT_ARM_HW_BREAK; - addr &= ~0x1000; - - ptrace(PTRACE_GETREGSET, pid, command, &iov); - - unsigned long result = *(unsigned long *) (data + addr); - - free(data); - - return result; -} - -uint64_t ptrace_pokeuser(int pid, uint64_t addr, uint64_t data) -{ - unsigned char *data_buffer = malloc(SIZEOF_STRUCT_HWDEBUG_STATE); - memset(data_buffer, 0, SIZEOF_STRUCT_HWDEBUG_STATE); - - struct iovec iov; - iov.iov_base = data_buffer; - iov.iov_len = SIZEOF_STRUCT_HWDEBUG_STATE; - - unsigned long command = (addr & 0x1000) ? NT_ARM_HW_WATCH : NT_ARM_HW_BREAK; - addr &= ~0x1000; - - ptrace(PTRACE_GETREGSET, pid, command, &iov); - - *(unsigned long *) (data_buffer + addr) = data; - - ptrace(PTRACE_SETREGSET, pid, command, &iov); - - free(data_buffer); - - return 0; -} -#endif - uint64_t ptrace_geteventmsg(int pid) { uint64_t data = 0; diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index dea1414e..860e067b 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -737,34 +737,6 @@ def flush_fp_registers(self: PtraceInterface, _: Registers) -> None: """ raise NotImplementedError("Flushing floating-point registers is automatically handled by the native code.") - def _peek_user(self: PtraceInterface, thread_id: int, address: int) -> int: - """Reads the memory at the specified address.""" - result = self.lib_trace.ptrace_peekuser(thread_id, address) - liblog.debugger( - "PEEKUSER at address %d returned with result %x", - address, - result, - ) - - error = self.ffi.errno - if error: - raise OSError(error, errno.errorcode[error]) - - return result - - def _poke_user(self: PtraceInterface, thread_id: int, address: int, value: int) -> None: - """Writes the memory at the specified address.""" - result = self.lib_trace.ptrace_pokeuser(thread_id, address, value) - liblog.debugger( - "POKEUSER at address %d returned with result %d", - address, - result, - ) - - if result == -1: - error = self.ffi.errno - raise OSError(error, errno.errorcode[error]) - def _get_event_msg(self: PtraceInterface, thread_id: int) -> int: """Returns the event message.""" return self.lib_trace.ptrace_geteventmsg(thread_id) From 8342682e7093e96cb020dd3d2e827efe4de4b1d7 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Sun, 1 Sep 2024 11:53:09 +0200 Subject: [PATCH 119/144] docs: correct pydoc for aarch64_call_utilities.py --- libdebug/architectures/aarch64/aarch64_call_utilities.py | 2 +- libdebug/architectures/amd64/amd64_call_utilities.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_call_utilities.py b/libdebug/architectures/aarch64/aarch64_call_utilities.py index 4b7b6952..cb4f9959 100644 --- a/libdebug/architectures/aarch64/aarch64_call_utilities.py +++ b/libdebug/architectures/aarch64/aarch64_call_utilities.py @@ -33,6 +33,6 @@ def compute_call_skip(self: Aarch64CallUtilities, opcode_window: bytes) -> int: return 0 def get_call_and_skip_amount(self: Aarch64CallUtilities, opcode_window: bytes) -> tuple[bool, int]: - """Get the call instruction and the amount of bytes to skip.""" + """Check if the current instruction is a call instruction and compute the instruction size.""" skip = self.compute_call_skip(opcode_window) return skip != 0, skip diff --git a/libdebug/architectures/amd64/amd64_call_utilities.py b/libdebug/architectures/amd64/amd64_call_utilities.py index 8f96dcb7..b89d1940 100644 --- a/libdebug/architectures/amd64/amd64_call_utilities.py +++ b/libdebug/architectures/amd64/amd64_call_utilities.py @@ -8,6 +8,7 @@ from libdebug.architectures.call_utilities_manager import CallUtilitiesManager + class Amd64CallUtilities(CallUtilitiesManager): """Class that provides call utilities for the x86_64 architecture.""" @@ -57,7 +58,7 @@ def compute_call_skip(self, opcode_window: bytes) -> int: return 2 # Register direct return 0 # Not a CALL - + def get_call_and_skip_amount(self, opcode_window: bytes) -> tuple[bool, int]: """Check if the current instruction is a call instruction and compute the instruction size.""" skip = self.compute_call_skip(opcode_window) From 2af0309075b9ce2aefecd074cbacdb548ffbf4bd Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Sun, 1 Sep 2024 12:09:43 +0200 Subject: [PATCH 120/144] fix: expose additional registers as available in arm assembly --- .../aarch64/aarch64_ptrace_register_holder.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py index 317d55a6..6a52ecbf 100644 --- a/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py +++ b/libdebug/architectures/aarch64/aarch64_ptrace_register_holder.py @@ -45,6 +45,16 @@ def setter(self: Aarch64Registers, value: int) -> None: return property(getter, setter, None, name) +def _get_property_zr(name: str) -> property: + def getter(_: Aarch64Registers) -> int: + return 0 + + def setter(_: Aarch64Registers, __: int) -> None: + pass + + return property(getter, setter, None, name) + + def _get_property_fp_8(name: str, index: int) -> property: def getter(self: Aarch64Registers) -> int: if not self._fp_register_file.fresh: @@ -161,8 +171,6 @@ def apply_on_regs(self: Aarch64PtraceRegisterHolder, target: Aarch64Registers, t setattr(target_class, name_64, _get_property_64(name_64)) setattr(target_class, name_32, _get_property_32(name_64)) - target_class.pc = _get_property_64("pc") - # setup the floating point registers for i in range(32): name_v = f"v{i}" @@ -178,6 +186,14 @@ def apply_on_regs(self: Aarch64PtraceRegisterHolder, target: Aarch64Registers, t setattr(target_class, name_16, _get_property_fp_16(name_16, i)) setattr(target_class, name_8, _get_property_fp_8(name_8, i)) + # setup special aarch64 registers + target_class.pc = _get_property_64("pc") + target_class.sp = _get_property_64("sp") + target_class.lr = _get_property_64("x30") + target_class.fp = _get_property_64("x29") + target_class.xzr = _get_property_zr("xzr") + target_class.wzr = _get_property_zr("wzr") + def apply_on_thread(self: Aarch64PtraceRegisterHolder, target: ThreadContext, target_class: type) -> None: """Apply the register accessors to the thread class.""" target.register_file = self.register_file From df3aad0e8df1052dd0544076bedf82ad82e7c642 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Sun, 1 Sep 2024 12:11:59 +0200 Subject: [PATCH 121/144] test: add check for other aarch64 registers in basic_test --- test/aarch64/scripts/basic_test.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/aarch64/scripts/basic_test.py b/test/aarch64/scripts/basic_test.py index 599c20fb..758ffb94 100644 --- a/test/aarch64/scripts/basic_test.py +++ b/test/aarch64/scripts/basic_test.py @@ -63,6 +63,17 @@ def test_registers(self): assert d.regs.x29 == 0xffffeeeeddddcccc assert d.regs.x30 == 0x4444333322221111 + assert d.regs.lr == 0x4444333322221111 + assert d.regs.fp == 0xffffeeeeddddcccc + assert d.regs.xzr == 0 + assert d.regs.wzr == 0 + + d.regs.xzr = 0x123456789abcdef0 + d.regs.wzr = 0x12345678 + + assert d.regs.xzr == 0 + assert d.regs.wzr == 0 + assert d.regs.w0 == 0x22221111 assert d.regs.w1 == 0x66665555 assert d.regs.w2 == 0xaaaa9999 @@ -121,4 +132,4 @@ def test_step(self): assert bp.hit_count == 1 d.kill() - d.terminate() \ No newline at end of file + d.terminate() From 54d49b0e930c748da430359a2bbc133f8aa0bd90 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Sun, 1 Sep 2024 12:23:27 +0200 Subject: [PATCH 122/144] docs: add support for aarch64 in documentation --- docs/source/basic_features.rst | 4 ++-- docs/source/breakpoints.rst | 11 +++++++++-- docs/source/index.rst | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/source/basic_features.rst b/docs/source/basic_features.rst index 1c945e88..01bf4f36 100644 --- a/docs/source/basic_features.rst +++ b/docs/source/basic_features.rst @@ -86,7 +86,7 @@ Register Access libdebug offers a simple register access interface for supported architectures. The registers are accessed through the `regs`` attribute of the debugger object. The field includes both general purpose and special registers, as well as the flags register. Effectively, any register that can be accessed by an assembly instruction, can also be accessed through the regs attribute. The debugger specifically exposes properties of the main thread, including the registers. See :doc:`multithreading` to learn how to access registers and other properties from different threads. Floating point and vector registers are available as well. The syntax is identical to the one used for integer registers. -The list of available AVX registers is determined during installation by checking the CPU capabilities, thus special registers, such as `zmm0` to `zmm31`, are available only on CPUs that support the specific ISA extension. +For amd64, the list of available AVX registers is determined during installation by checking the CPU capabilities, thus special registers, such as `zmm0` to `zmm31`, are available only on CPUs that support the specific ISA extension. If you believe that your target CPU supports AVX registers, but they are not available during debugging, please file an issue on the GitHub repository and include your precise hardware details, so that we can investigate and resolve the issue. Memory Access @@ -303,4 +303,4 @@ You can also access registers after the process has died. This is useful for *po Supported Architectures ======================= -libdebug currently only supports Linux under the x86_64 (AMD64) architecture. Support for other architectures is planned for future releases. Stay tuned. +libdebug currently only supports Linux under the x86_64 (AMD64) and AArch64 (ARM64) architectures. Support for other architectures is planned for future releases. Stay tuned. diff --git a/docs/source/breakpoints.rst b/docs/source/breakpoints.rst index 926dde11..468acb57 100644 --- a/docs/source/breakpoints.rst +++ b/docs/source/breakpoints.rst @@ -102,20 +102,27 @@ Features of watchpoints are shared with breakpoints, so you can set callbacks, c callback=...) -> Breakpoint: Again, the position can be specified both as a relative address or as a symbol. -The condition parameter specifies the type of access that triggers the watchpoint. The following values are supported: +The condition parameter specifies the type of access that triggers the watchpoint. The following values are supported in all architectures: - ``"w"``: write access - ``"rw"``: read/write access - ``"x"``: execute access +AArch64 additionally supports: + +- ``"r"``: read access + By default, the watchpoint is triggered only on write access. -The length parameter specifies the size of the word being watched. The following values are supported: +The length parameter specifies the size of the word being watched. +In x86_64 (amd64) the following values are supported: - ``1``: byte - ``2``: word - ``4``: dword - ``8``: qword +AArch64 supports any length from 1 to 8 bytes. + By default, the watchpoint is set to watch a byte. diff --git a/docs/source/index.rst b/docs/source/index.rst index b3ecf6c9..60aca123 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -28,7 +28,7 @@ e.g, for version 0.5.0, go to https://docs.libdebug.org/archive/0.5.0 Supported Architectures ----------------------- -libdebug currently supports Linux under the x86_64 architecture. +libdebug currently supports Linux under the x86_64 and AArch64 architectures. Other operating systems and architectures are not supported at this time. @@ -109,7 +109,7 @@ Now that you have libdebug installed, you can start using it in your scripts. He The above script will run the binary `test` in the working directory and stop at the function corresponding to the symbol "function". It will then print the value of the RAX register and kill the process. -Conflicts with other python packages +Conflicts with other Python packages ------------------------------------ The current version of libdebug is incompatible with https://github.com/Gallopsled/pwntools. From 1a90120882d94000a6a57839faf56bde2a873cd1 Mon Sep 17 00:00:00 2001 From: io-no Date: Mon, 2 Sep 2024 14:10:08 +0200 Subject: [PATCH 123/144] build: new libdebug version (0.5.4 -> 0.6.0) --- docs/source/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index adc39e57..85d0b263 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,7 @@ project = 'libdebug' copyright = '2024, Gabriele Digregorio, Roberto Alessandro Bertolini, Francesco Panebianco' author = 'JinBlack, Io_no, MrIndeciso, Frank01001' -release = '0.5.4' +release = '0.6.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/setup.py b/setup.py index 2eff8455..c45c07a6 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ def get_outputs(self): setup( name="libdebug", - version="0.5.4", + version="0.6.0", author="JinBlack, Io_no, MrIndeciso, Frank01001", description="A library to debug binary programs", packages=find_packages(include=["libdebug", "libdebug.*"]), From 3c4cb4817c3c52c86ef21f83a36951cb7f6a6ffd Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Mon, 2 Sep 2024 14:55:34 +0200 Subject: [PATCH 124/144] docs: add paragraph about fast_memory --- docs/source/basic_features.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/source/basic_features.rst b/docs/source/basic_features.rst index 01bf4f36..f27bebb1 100644 --- a/docs/source/basic_features.rst +++ b/docs/source/basic_features.rst @@ -161,6 +161,23 @@ If you specify a full or a substring of a file name, libdebug will search for th You can also use the wildcard string "binary" to use the base address of the binary as the base address for the relative addressing. The same behavior is applied if you pass a string corresponding to the binary name. +Faster Memory Access +------------------- + +By default, libdebug uses the kernel's ptrace interface to access memory. This is guaranteed to work, but it might be slow during large memory transfers. +To speed up memory access, we provide a secondary system that relies on /proc/$pid/mem for read and write operations. You can enable this feature by setting `fast_memory` to True when instancing the debugger. +The final behavior is identical, but the speed is significantly improved. + +Additionally, you can mix the two memory access methods by changing the `fast_memory` attribute of the debugger at runtime: + +.. code-block:: python + + d.fast_memory = True + + # ... + + d.fast_memory = False + Control Flow Commands ==================================== From 44ba08f80ec463faf239df73e8df368099eb4f91 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Mon, 2 Sep 2024 17:52:48 +0200 Subject: [PATCH 125/144] fix: improve compatibility with various compiler flags from different distros --- libdebug/cffi/debug_sym_cffi_source.c | 2 ++ libdebug/cffi/debug_sym_cffi_source_legacy.c | 2 ++ libdebug/cffi/ptrace_cffi_source.c | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/libdebug/cffi/debug_sym_cffi_source.c b/libdebug/cffi/debug_sym_cffi_source.c index 1e660b46..75fc93b5 100644 --- a/libdebug/cffi/debug_sym_cffi_source.c +++ b/libdebug/cffi/debug_sym_cffi_source.c @@ -23,6 +23,8 @@ typedef struct SymbolInfo struct SymbolInfo *next; } SymbolInfo; +void process_symbol_tables(Elf *elf); + // Function to add new symbol info to the linked list SymbolInfo *add_symbol_info(SymbolInfo **head, const char *name, Dwarf_Addr low_pc, Dwarf_Addr high_pc) { diff --git a/libdebug/cffi/debug_sym_cffi_source_legacy.c b/libdebug/cffi/debug_sym_cffi_source_legacy.c index 39b18bce..8d1a7567 100644 --- a/libdebug/cffi/debug_sym_cffi_source_legacy.c +++ b/libdebug/cffi/debug_sym_cffi_source_legacy.c @@ -23,6 +23,8 @@ typedef struct SymbolInfo struct SymbolInfo *next; } SymbolInfo; +void process_symbol_tables(Elf *elf); + // Function to add new symbol info to the linked list SymbolInfo *add_symbol_info(SymbolInfo **head, const char *name, Dwarf_Addr low_pc, Dwarf_Addr high_pc) { diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 50f170bb..8614b123 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -349,7 +349,7 @@ int is_breakpoint_hit(struct hardware_breakpoint *bp) return 0; } - unsigned long addr = si.si_addr; + unsigned long addr = (unsigned long) si.si_addr; if (addr == bp->addr) { return 1; From 047ee9115cf38cebd1ef152667b0c0c7a581d80d Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Mon, 2 Sep 2024 23:39:56 +0200 Subject: [PATCH 126/144] feat: add kill_on_exit argument to the debugger --- libdebug/debugger/debugger.py | 12 ++++++++++++ libdebug/debugger/internal_debugger.py | 4 ++++ libdebug/debugger/internal_debugger_holder.py | 12 ++++++++++-- libdebug/libdebug.py | 3 +++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/libdebug/debugger/debugger.py b/libdebug/debugger/debugger.py index 9a106a6c..8673b711 100644 --- a/libdebug/debugger/debugger.py +++ b/libdebug/debugger/debugger.py @@ -328,6 +328,18 @@ def arch(self: Debugger, value: str) -> None: """Set the architecture of the process.""" self._internal_debugger.arch = map_arch(value) + @property + def kill_on_exit(self: Debugger) -> bool: + """Get whether the process will be killed when the debugger exits.""" + return self._internal_debugger.kill_on_exit + + @kill_on_exit.setter + def kill_on_exit(self: Debugger, value: bool) -> None: + if not isinstance(value, bool): + raise TypeError("kill_on_exit must be a boolean") + + self._internal_debugger.kill_on_exit = value + @property def threads(self: Debugger) -> list[ThreadContext]: """Get the list of threads in the process.""" diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 61b81c36..6036506e 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -123,6 +123,9 @@ class InternalDebugger: syscalls_to_not_pprint: list[int] | None """The syscalls to not pretty print.""" + kill_on_exit: bool + """A flag that indicates if the debugger should kill the debugged process when it exits.""" + threads: list[ThreadContext] """A list of all the threads of the debugged process.""" @@ -187,6 +190,7 @@ def __init__(self: InternalDebugger) -> None: self._is_running = False self.resume_context = ResumeContext() self.arch = map_arch(libcontext.platform) + self.kill_on_exit = True self._process_memory_manager = ProcessMemoryManager() self.fast_memory = False self.__polling_thread_command_queue = Queue() diff --git a/libdebug/debugger/internal_debugger_holder.py b/libdebug/debugger/internal_debugger_holder.py index 2adda00a..41e0d7f8 100644 --- a/libdebug/debugger/internal_debugger_holder.py +++ b/libdebug/debugger/internal_debugger_holder.py @@ -4,13 +4,19 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # +from __future__ import annotations + import atexit from dataclasses import dataclass, field from threading import Lock +from typing import TYPE_CHECKING from weakref import WeakKeyDictionary from libdebug.liblog import liblog +if TYPE_CHECKING: + from libdebug.debugger.internal_debugger import InternalDebugger + @dataclass class InternalDebuggerHolder: @@ -27,7 +33,9 @@ class InternalDebuggerHolder: def _cleanup_internal_debugger() -> None: """Cleanup the internal debugger.""" for debugger in set(internal_debugger_holder.internal_debuggers.values()): - if debugger.instanced: + debugger: InternalDebugger + + if debugger.instanced and debugger.kill_on_exit: try: debugger.interrupt() except Exception as e: @@ -38,7 +46,7 @@ def _cleanup_internal_debugger() -> None: except Exception as e: liblog.debugger(f"Error while killing debuggee: {e}") - debugger.terminate() + debugger.terminate() atexit.register(_cleanup_internal_debugger) diff --git a/libdebug/libdebug.py b/libdebug/libdebug.py index aa09b19c..5d5bcf88 100644 --- a/libdebug/libdebug.py +++ b/libdebug/libdebug.py @@ -18,6 +18,7 @@ def debugger( continue_to_binary_entrypoint: bool = True, auto_interrupt_on_command: bool = False, fast_memory: bool = False, + kill_on_exit: bool = True, ) -> Debugger: """This function is used to create a new `Debugger` object. It returns a `Debugger` object. @@ -29,6 +30,7 @@ def debugger( continue_to_binary_entrypoint (bool, optional): Whether to automatically continue to the binary entrypoint. Defaults to True. auto_interrupt_on_command (bool, optional): Whether to automatically interrupt the process when a command is issued. Defaults to False. fast_memory (bool, optional): Whether to use a faster memory reading method. Defaults to False. + kill_on_exit (bool, optional): Whether to kill the debugged process when the debugger exits. Defaults to True. Returns: Debugger: The `Debugger` object. @@ -44,6 +46,7 @@ def debugger( internal_debugger.auto_interrupt_on_command = auto_interrupt_on_command internal_debugger.escape_antidebug = escape_antidebug internal_debugger.fast_memory = fast_memory + internal_debugger.kill_on_exit = kill_on_exit debugger = Debugger() debugger.post_init_(internal_debugger) From 0a750df8704cd7b95882468b21bbd525d0c57eef Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 3 Sep 2024 19:53:33 +0200 Subject: [PATCH 127/144] fix: make terminate() automatically call kill() when needed --- libdebug/debugger/internal_debugger.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 6036506e..4e386bc1 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -341,6 +341,12 @@ def terminate(self: InternalDebugger) -> None: The debugger object cannot be used after this method is called. This method should only be called to free up resources when the debugger object is no longer needed. """ + if self.instanced and self.running: + self.interrupt() + self.kill() + + self.instanced = False + if self.__polling_thread is not None: self.__polling_thread_command_queue.put((THREAD_TERMINATE, ())) self.__polling_thread.join() From fb95c23f181a80e28b3f3db63c5b4981fd291f5e Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 3 Sep 2024 19:53:58 +0200 Subject: [PATCH 128/144] test: add test for the atexit handler --- test/amd64/binaries/infinite_loop_test | Bin 0 -> 16688 bytes test/amd64/run_suite.py | 11 + test/amd64/scripts/atexit_handler_test.py | 238 ++++++++++++++++++++++ test/common/srcs/infinite_loop_test.c | 18 ++ 4 files changed, 267 insertions(+) create mode 100755 test/amd64/binaries/infinite_loop_test create mode 100644 test/amd64/scripts/atexit_handler_test.py create mode 100644 test/common/srcs/infinite_loop_test.c diff --git a/test/amd64/binaries/infinite_loop_test b/test/amd64/binaries/infinite_loop_test new file mode 100755 index 0000000000000000000000000000000000000000..d893a6248b5319e1f91adbe6de22675933a6a0d9 GIT binary patch literal 16688 zcmeHOU2Ggz6~4Q6nr-XYYdcNc2AXL}LW1J)uI<>)Mrp?X>&i)t?IuNPn2vYH_K4kC zvpX(!kxEebsf#L%R6!*2f`s6y5)wQFRU)nuB`M+|ftN@~U{yp43Z$th$e<|8IrpBk z8BaG+R3wnl9BJm<^WF2^pEI*}?%kRDwZYM$Xe1(7qT)G$mJv53q|QPl_Dhqn9t6Pg)Hg=F z&6+GmY^FG}-m752n(O;K@Gve9D}0&9slNMNhZx15HHBBS<(#Rm&bC~(HRrhV3#|)1 zU9DZ6da0nd%O+sE5^&%>HGJd+5j`nZ5MzEsMAq3+vcW|P`fi2;!;bvrslLAV|1jJB z+JPTF_RKq1-}v#Cy;sl&`+;Sm4+{G8ErT}Z@qzZk3UZpNIQIgkRl%D9tL86E6C#x{ zOP*Eq%)I5efHX8(S(l(F1tf!P?hE-^eh(${ac>)n-9M<;mCA5EWgJM!9y#kQ(n1>>JCf5BJAS7>0D&2xq$WnG)(S z0$~Kg2!s&`BM?R)j6fKHFarO#5%^of)Bj4J`b#YRoefvpgh+pL#fz4&q)%OlotJ$s z@B1~t<(?@}Z77i_|#=MX5bPA{LIzUPef|4RCQ{gzZq zx6|+5I+T8QttK70n7(?~Ypj&8kxB^ivHp_>8=+BRzWIgp()Am!LN6|ED8qU?a^n2Q z>O=pVZ=fSYDSm12j%?zagLl$P7th5tL&zF$T)$f`-@FTX=r7}0i69a}2_q0jAdEm5 zfiMDL1i}b}5eOp?Mj(tp7=bVX|Jw-Qdp5u9%)&Z&Tg`#p0OI?{y4&S)4)k8o1E605 z{UYe556k7ZK;t`1l*p^&BC-&VY^&QCTZR{K_>K@!?!~Oj!Z8v;ryhlrR>PbDRwI|v;6WxiV zwtL*pYH7=(jMnZw0q=J9?g@B9Mr(49=slL|oJ+Rv>lWk3QpI`Kb=(Ik} zEY~eeId0YL;p3^qLU*<+v9GIL)AhE}Vkv9SmD=F@>^5u4X>|*pt!Ko;=^(<8`~FwoHH~o zY2?rZx|;u|0gsA$!LcBB*g2!{pHX`mZzr6`WyaHxh=?aeonc6b$CXKm!)Qk1tjvVd zSK+{Vd7Nkb+mMKeX2Ih&<3E8!wf4VrAN+zWr%7DU3`nh)YUTVETp}<^2gjk`OKM|f z9|)IpOZsmWJbp7?3mwN;2PFZx2BUF|;=}pR03HXN$v88$<9I{E}XJC zQ$8y!=!hEs%{d2r&8bp}%B-^CrqY;h@SHr{&)vj;2%(o2^PV*Y+AAtO%PGf&3pXcp z`3RkH=k>W_Va_gki#}*--hnMqPL`nFzL8eE2Xu6wwMw%>&n~)9h0t-8ue%3z_}^&d4C`8`!gWVFZjIj3+|Wl^Ld{i@BhR1F@E83i?I<%`1$-C zJVO(1jU7n``NskO2h4@J{VUY}72yyg?KG)Cf;r{=C_s0KZbEWh4nL^_r3Cd*uCsXI#~aE0luLb($mnS60a*&IS1?i9pu!* zK|hzuJdpRt2L0J){YT(7`1qB1C+}B=JijoH^-ajcdxz`i`5^Ci=6%h0|D!OEuj5C6 zVf>iS`#!I=p*O8}R8pRq&-$N$`uRM+yg_`;$HgS`SVw{E=kq)l7Bd0fK(T{w1%q0R}~2z)M3f53z}d3F8= O-Krd%=ldmKihlz#3Mmr+ literal 0 HcmV?d00001 diff --git a/test/amd64/run_suite.py b/test/amd64/run_suite.py index 562e49e7..9a70dea6 100644 --- a/test/amd64/run_suite.py +++ b/test/amd64/run_suite.py @@ -8,6 +8,7 @@ import unittest from scripts.alias_test import AliasTest +from scripts.atexit_handler_test import AtexitHandlerTest from scripts.attach_detach_test import AttachDetachTest from scripts.auto_waiting_test import AutoWaitingNlinks, AutoWaitingTest from scripts.backtrace_test import BacktraceTest @@ -205,6 +206,16 @@ def fast_suite(): suite.addTest(AliasTest("test_finish_alias")) suite.addTest(AliasTest("test_waiting_alias")) suite.addTest(AliasTest("test_interrupt_alias")) + suite.addTest(AtexitHandlerTest("test_attach_detach_1")) + suite.addTest(AtexitHandlerTest("test_attach_detach_2")) + suite.addTest(AtexitHandlerTest("test_attach_detach_3")) + suite.addTest(AtexitHandlerTest("test_attach_detach_4")) + suite.addTest(AtexitHandlerTest("test_attach_1")) + suite.addTest(AtexitHandlerTest("test_attach_2")) + suite.addTest(AtexitHandlerTest("test_run_1")) + suite.addTest(AtexitHandlerTest("test_run_2")) + suite.addTest(AtexitHandlerTest("test_run_3")) + suite.addTest(AtexitHandlerTest("test_run_4")) return suite diff --git a/test/amd64/scripts/atexit_handler_test.py b/test/amd64/scripts/atexit_handler_test.py new file mode 100644 index 00000000..332849cf --- /dev/null +++ b/test/amd64/scripts/atexit_handler_test.py @@ -0,0 +1,238 @@ +# +# This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +import os +import psutil +import signal +import unittest +from pwn import process + +from libdebug import debugger +from libdebug.debugger.internal_debugger_holder import _cleanup_internal_debugger + + +class AtexitHandlerTest(unittest.TestCase): + def test_run_1(self): + d = debugger("binaries/infinite_loop_test") + + r = d.run() + + pid = d.pid + + d.cont() + + r.sendline(b"3") + + _cleanup_internal_debugger() + + # The process should have been killed + self.assertNotIn(pid, psutil.pids()) + + def test_run_2(self): + d = debugger("binaries/infinite_loop_test", kill_on_exit=False) + + r = d.run() + + pid = d.pid + + d.cont() + + r.sendline(b"3") + + _cleanup_internal_debugger() + + # The process should not have been killed + self.assertIn(pid, psutil.pids()) + + os.kill(pid, signal.SIGKILL) + + # The process should now be dead + self.assertNotIn(pid, psutil.pids()) + + def test_run_3(self): + d = debugger("binaries/infinite_loop_test", kill_on_exit=False) + + r = d.run() + + pid = d.pid + + d.cont() + + r.sendline(b"3") + + d.kill_on_exit = True + + _cleanup_internal_debugger() + + # The process should have been killed + self.assertNotIn(pid, psutil.pids()) + + def test_run_4(self): + d = debugger("binaries/infinite_loop_test") + + r = d.run() + + pid = d.pid + + d.cont() + + d.kill_on_exit = False + + r.sendline(b"3") + + _cleanup_internal_debugger() + + # The process should not have been killed + self.assertIn(pid, psutil.pids()) + + os.kill(pid, signal.SIGKILL) + + # The process should now be dead + self.assertNotIn(pid, psutil.pids()) + + def test_attach_detach_1(self): + p = process("binaries/infinite_loop_test") + + d = debugger() + + d.attach(p.pid) + + p.sendline(b"3") + + d.step() + d.step() + + d.detach() + + # If the process is still running, poll() should return None + self.assertIsNone(p.poll(block=False)) + + _cleanup_internal_debugger() + + # The process should now be dead + self.assertIsNotNone(p.poll(block=False)) + + def test_attach_detach_2(self): + p = process("binaries/infinite_loop_test") + + d = debugger(kill_on_exit=False) + + d.attach(p.pid) + + p.sendline(b"3") + + d.step() + d.step() + + d.detach() + + # If the process is still running, poll() should return None + self.assertIsNone(p.poll(block=False)) + + _cleanup_internal_debugger() + + # We set kill_on_exit to False, so the process should still be alive + # The process should still be alive + self.assertIsNone(p.poll(block=False)) + + p.kill() + + # The process should now be dead + self.assertIsNotNone(p.poll(block=False)) + + def test_attach_detach_3(self): + p = process("binaries/infinite_loop_test") + + d = debugger(kill_on_exit=False) + + d.attach(p.pid) + + p.sendline(b"3") + + d.step() + d.step() + + d.detach() + + # If the process is still running, poll() should return None + self.assertIsNone(p.poll(block=False)) + + d.kill_on_exit = True + + _cleanup_internal_debugger() + + # The process should now be dead + self.assertIsNotNone(p.poll(block=False)) + + def test_attach_detach_4(self): + p = process("binaries/infinite_loop_test") + + d = debugger() + + d.attach(p.pid) + + p.sendline(b"3") + + d.step() + d.step() + + d.detach() + + # If the process is still running, poll() should return None + self.assertIsNone(p.poll(block=False)) + + d.kill_on_exit = False + + _cleanup_internal_debugger() + + # We set kill_on_exit to False, so the process should still be alive + # The process should still be alive + self.assertIsNone(p.poll(block=False)) + + p.kill() + + # The process should now be dead + self.assertIsNotNone(p.poll(block=False)) + + def test_attach_1(self): + p = process("binaries/infinite_loop_test") + + d = debugger() + + d.attach(p.pid) + + p.sendline(b"3") + + d.step() + d.step() + + # If the process is still running, poll() should return None + self.assertIsNone(p.poll(block=False)) + + _cleanup_internal_debugger() + + # The process should now be dead + self.assertIsNotNone(p.poll(block=False)) + + def test_attach_2(self): + p = process("binaries/infinite_loop_test") + + d = debugger() + + d.attach(p.pid) + + p.sendline(b"3") + + d.step() + d.step() + + p.kill() + + # The process should now be dead + self.assertIsNotNone(p.poll(block=False)) + + # Even if we kill the process, the next call should not raise an exception + _cleanup_internal_debugger() diff --git a/test/common/srcs/infinite_loop_test.c b/test/common/srcs/infinite_loop_test.c new file mode 100644 index 00000000..8fdc3f1c --- /dev/null +++ b/test/common/srcs/infinite_loop_test.c @@ -0,0 +1,18 @@ +// +// This file is part of libdebug Python library (https://github.com/libdebug/libdebug). +// Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. +// + +#include + +int main() +{ + int number = 0; + + scanf("%d", &number); + + while (1); + + return 0; +} From c8f4446ed01b1ef0c752f2c267d2d98af62a266d Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 3 Sep 2024 19:55:53 +0200 Subject: [PATCH 129/144] fix: move atexit handler call to terminate() inside try-catch block --- libdebug/debugger/internal_debugger_holder.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libdebug/debugger/internal_debugger_holder.py b/libdebug/debugger/internal_debugger_holder.py index 41e0d7f8..7e151078 100644 --- a/libdebug/debugger/internal_debugger_holder.py +++ b/libdebug/debugger/internal_debugger_holder.py @@ -46,7 +46,10 @@ def _cleanup_internal_debugger() -> None: except Exception as e: liblog.debugger(f"Error while killing debuggee: {e}") - debugger.terminate() + try: + debugger.terminate() + except Exception as e: + liblog.debugger(f"Error while terminating the debugger: {e}") atexit.register(_cleanup_internal_debugger) From 3f72adbcb6b3c203dcfd7ed82c3045e9bf1defb6 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 3 Sep 2024 20:06:07 +0200 Subject: [PATCH 130/144] test: fix atexit handler tests --- test/amd64/scripts/atexit_handler_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/amd64/scripts/atexit_handler_test.py b/test/amd64/scripts/atexit_handler_test.py index 332849cf..3bd8523f 100644 --- a/test/amd64/scripts/atexit_handler_test.py +++ b/test/amd64/scripts/atexit_handler_test.py @@ -47,7 +47,7 @@ def test_run_2(self): # The process should not have been killed self.assertIn(pid, psutil.pids()) - os.kill(pid, signal.SIGKILL) + psutil.Process(pid).kill() # The process should now be dead self.assertNotIn(pid, psutil.pids()) @@ -88,7 +88,7 @@ def test_run_4(self): # The process should not have been killed self.assertIn(pid, psutil.pids()) - os.kill(pid, signal.SIGKILL) + psutil.Process(pid).kill() # The process should now be dead self.assertNotIn(pid, psutil.pids()) From e7794eebc21f0770a36b61930ef558624b667214 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 3 Sep 2024 20:13:50 +0200 Subject: [PATCH 131/144] test: fix atexit handler tests --- test/amd64/scripts/atexit_handler_test.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/amd64/scripts/atexit_handler_test.py b/test/amd64/scripts/atexit_handler_test.py index 3bd8523f..1f1a6d2e 100644 --- a/test/amd64/scripts/atexit_handler_test.py +++ b/test/amd64/scripts/atexit_handler_test.py @@ -47,7 +47,9 @@ def test_run_2(self): # The process should not have been killed self.assertIn(pid, psutil.pids()) - psutil.Process(pid).kill() + process = psutil.Process(pid) + process.kill() + process.wait() # The process should now be dead self.assertNotIn(pid, psutil.pids()) @@ -88,7 +90,9 @@ def test_run_4(self): # The process should not have been killed self.assertIn(pid, psutil.pids()) - psutil.Process(pid).kill() + process = psutil.Process(pid) + process.kill() + process.wait() # The process should now be dead self.assertNotIn(pid, psutil.pids()) From a3ae0bf208235923fd87eee6cf3d4f433db73d08 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Tue, 3 Sep 2024 20:19:57 +0200 Subject: [PATCH 132/144] fix atexit handler tests --- test/amd64/scripts/atexit_handler_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/amd64/scripts/atexit_handler_test.py b/test/amd64/scripts/atexit_handler_test.py index 1f1a6d2e..790fcd09 100644 --- a/test/amd64/scripts/atexit_handler_test.py +++ b/test/amd64/scripts/atexit_handler_test.py @@ -47,9 +47,9 @@ def test_run_2(self): # The process should not have been killed self.assertIn(pid, psutil.pids()) - process = psutil.Process(pid) - process.kill() - process.wait() + # We can actually still use the debugger + d.interrupt() + d.kill() # The process should now be dead self.assertNotIn(pid, psutil.pids()) @@ -90,9 +90,9 @@ def test_run_4(self): # The process should not have been killed self.assertIn(pid, psutil.pids()) - process = psutil.Process(pid) - process.kill() - process.wait() + # We can actually still use the debugger + d.interrupt() + d.kill() # The process should now be dead self.assertNotIn(pid, psutil.pids()) From f10b3647c79af674a5d0662e4ac73b7ae00c0194 Mon Sep 17 00:00:00 2001 From: Joshua Tian Yang Liu Date: Sat, 7 Sep 2024 01:09:20 -0400 Subject: [PATCH 133/144] Update breakpoints.rst Fixed library import --- docs/source/breakpoints.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/breakpoints.rst b/docs/source/breakpoints.rst index 468acb57..88833437 100644 --- a/docs/source/breakpoints.rst +++ b/docs/source/breakpoints.rst @@ -14,7 +14,7 @@ libdebug provides a simple API to set breakpoints in your debugged program. The .. code-block:: python - from libdebug import Debugger + from libdebug import debugger d = debugger("./test_program") From bed16199f2f3ade221011acf3af1b2d6f6f4938e Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Sun, 8 Sep 2024 21:05:11 +0200 Subject: [PATCH 134/144] fix: set fp regs as not fresh before any control flow operation --- libdebug/cffi/ptrace_cffi_source.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libdebug/cffi/ptrace_cffi_source.c b/libdebug/cffi/ptrace_cffi_source.c index 8614b123..bfd42780 100644 --- a/libdebug/cffi/ptrace_cffi_source.c +++ b/libdebug/cffi/ptrace_cffi_source.c @@ -479,6 +479,8 @@ void check_and_set_fp_regs(struct thread *t) if (t->fpregs.dirty) { set_fp_regs(t->tid, &t->fpregs); } + + t->fpregs.fresh = 0; } struct ptrace_regs_struct *register_thread(struct global_state *state, int tid) From 1c333cb2de69174b8295b984f78e8ffc5c5c773e Mon Sep 17 00:00:00 2001 From: io-no Date: Mon, 9 Sep 2024 15:45:14 +0200 Subject: [PATCH 135/144] fix: the addresses were not absolute --- .../architectures/aarch64/aarch64_stack_unwinder.py | 4 ++-- libdebug/architectures/amd64/amd64_stack_unwinder.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_stack_unwinder.py b/libdebug/architectures/aarch64/aarch64_stack_unwinder.py index 12a0edaa..80e0190f 100644 --- a/libdebug/architectures/aarch64/aarch64_stack_unwinder.py +++ b/libdebug/architectures/aarch64/aarch64_stack_unwinder.py @@ -37,8 +37,8 @@ def unwind(self: Aarch64StackUnwinder, target: ThreadContext) -> list: # Follow the frame chain while frame_pointer: try: - link_register = int.from_bytes(target.memory[frame_pointer + 8, 8], byteorder="little") - frame_pointer = int.from_bytes(target.memory[frame_pointer, 8], byteorder="little") + link_register = int.from_bytes(target.memory[frame_pointer + 8, 8, "absolute"], byteorder="little") + frame_pointer = int.from_bytes(target.memory[frame_pointer, 8, "absolute"], byteorder="little") if not any(vmap.start <= link_register < vmap.end for vmap in vmaps): break diff --git a/libdebug/architectures/amd64/amd64_stack_unwinder.py b/libdebug/architectures/amd64/amd64_stack_unwinder.py index 0a82e8b4..ae7eba5b 100644 --- a/libdebug/architectures/amd64/amd64_stack_unwinder.py +++ b/libdebug/architectures/amd64/amd64_stack_unwinder.py @@ -39,13 +39,13 @@ def unwind(self: Amd64StackUnwinder, target: ThreadContext) -> list: while current_rbp: try: # Read the return address - return_address = int.from_bytes(target.memory[current_rbp + 8, 8], byteorder="little") + return_address = int.from_bytes(target.memory[current_rbp + 8, 8, "absolute"], byteorder="little") if not any(vmap.start <= return_address < vmap.end for vmap in vmaps): break # Read the previous rbp and set it as the current one - current_rbp = int.from_bytes(target.memory[current_rbp, 8], byteorder="little") + current_rbp = int.from_bytes(target.memory[current_rbp, 8, "absolute"], byteorder="little") stack_trace.append(return_address) except (OSError, ValueError): @@ -74,17 +74,17 @@ def get_return_address(self: Amd64StackUnwinder, target: ThreadContext) -> int: Returns: int: The return address. """ - instruction_window = target.memory[target.regs.rip, 4] + instruction_window = target.memory[target.regs.rip, 4, "absolute"] # Check if the instruction window is a function preamble and handle each case return_address = None if self._preamble_state(instruction_window) == 0: - return_address = target.memory[target.regs.rbp + 8, 8] + return_address = target.memory[target.regs.rbp + 8, 8, "absolute"] elif self._preamble_state(instruction_window) == 1: - return_address = target.memory[target.regs.rsp, 8] + return_address = target.memory[target.regs.rsp, 8, "absolute"] else: - return_address = target.memory[target.regs.rsp + 8, 8] + return_address = target.memory[target.regs.rsp + 8, 8, "absolute"] return int.from_bytes(return_address, byteorder="little") From 78a524bd7178191908ed38084be6d009b7236bcc Mon Sep 17 00:00:00 2001 From: io-no Date: Mon, 9 Sep 2024 15:55:28 +0200 Subject: [PATCH 136/144] fix: wrong path for the goback gdb command file. Solve https://github.com/libdebug/libdebug/issues/104 --- libdebug/debugger/internal_debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 61b81c36..ebbb1533 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -72,7 +72,7 @@ from libdebug.utils.pipe_manager import PipeManager THREAD_TERMINATE = -1 -GDB_GOBACK_LOCATION = str((Path(__file__).parent / "utils" / "gdb.py").resolve()) +GDB_GOBACK_LOCATION = str((Path(__file__).parent.parent / "utils" / "gdb.py").resolve()) class InternalDebugger: From 905ec397a253d89f381fdb2f65eaf8b29708fdbe Mon Sep 17 00:00:00 2001 From: io-no Date: Tue, 10 Sep 2024 19:36:07 +0200 Subject: [PATCH 137/144] fix: now exceptions raised during run are correctly handled --- libdebug/debugger/internal_debugger.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index ebbb1533..e6f34fad 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -265,12 +265,12 @@ def run(self: InternalDebugger) -> None: self.__polling_thread_command_queue.put((self.__threaded_run, ())) + self._join_and_check_status() + if self.escape_antidebug: liblog.debugger("Enabling anti-debugging escape mechanism.") self._enable_antidebug_escaping() - self._join_and_check_status() - if not self.pipe_manager: raise RuntimeError("Something went wrong during pipe initialization.") @@ -1478,6 +1478,8 @@ def _enable_antidebug_escaping(self: InternalDebugger) -> None: self.__polling_thread_command_queue.put((self.__threaded_handle_syscall, (handler,))) + self._join_and_check_status() + # Seutp hidden state for the handler handler._traceme_called = False handler._command = None From 04dec13cfeac95d4674a42949b2cf938d7f44fac Mon Sep 17 00:00:00 2001 From: io-no Date: Wed, 11 Sep 2024 01:02:26 +0200 Subject: [PATCH 138/144] fix: solved out of range issue --- .../amd64/amd64_stack_unwinder.py | 25 +++++++++++++------ libdebug/state/thread_context.py | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/libdebug/architectures/amd64/amd64_stack_unwinder.py b/libdebug/architectures/amd64/amd64_stack_unwinder.py index ae7eba5b..2f6f9146 100644 --- a/libdebug/architectures/amd64/amd64_stack_unwinder.py +++ b/libdebug/architectures/amd64/amd64_stack_unwinder.py @@ -9,13 +9,13 @@ from typing import TYPE_CHECKING from libdebug.architectures.stack_unwinding_manager import StackUnwindingManager -from libdebug.liblog import logging +from libdebug.liblog import liblog if TYPE_CHECKING: + from libdebug.data.memory_map import MemoryMap from libdebug.state.thread_context import ThreadContext - class Amd64StackUnwinder(StackUnwindingManager): """Class that provides stack unwinding for the x86_64 architecture.""" @@ -54,22 +54,26 @@ def unwind(self: Amd64StackUnwinder, target: ThreadContext) -> list: # If we are in the prolouge of a function, we need to get the return address from the stack # using a slightly more complex method try: - first_return_address = self.get_return_address(target) + first_return_address = self.get_return_address(target, vmaps) - if first_return_address != stack_trace[1]: - stack_trace.insert(1, first_return_address) + if len(stack_trace) > 1: + if first_return_address != stack_trace[1]: + stack_trace.insert(1, first_return_address) + else: + stack_trace.append(first_return_address) except (OSError, ValueError): - logging.WARNING( + liblog.warning( "Failed to get the return address from the stack. Check stack frame registers (e.g., base pointer). The stack trace may be incomplete.", ) return stack_trace - def get_return_address(self: Amd64StackUnwinder, target: ThreadContext) -> int: + def get_return_address(self: Amd64StackUnwinder, target: ThreadContext, vmaps: list[MemoryMap]) -> int: """Get the return address of the current function. Args: target (ThreadContext): The target ThreadContext. + vmaps (list[MemoryMap]): The memory maps of the process. Returns: int: The return address. @@ -86,7 +90,12 @@ def get_return_address(self: Amd64StackUnwinder, target: ThreadContext) -> int: else: return_address = target.memory[target.regs.rsp + 8, 8, "absolute"] - return int.from_bytes(return_address, byteorder="little") + return_address = int.from_bytes(return_address, byteorder="little") + + if not any(vmap.start <= return_address < vmap.end for vmap in vmaps): + raise ValueError("Return address not in any valid memory map") + + return return_address def _preamble_state(self: Amd64StackUnwinder, instruction_window: bytes) -> int: """Check if the instruction window is a function preamble and if so at what stage. diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index c9dabdae..145e9cb9 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -207,7 +207,7 @@ def current_return_address(self: ThreadContext) -> int: stack_unwinder = stack_unwinding_provider(self._internal_debugger.arch) try: - return_address = stack_unwinder.get_return_address(self) + return_address = stack_unwinder.get_return_address(self, self._internal_debugger.debugging_interface.maps()) except (OSError, ValueError) as e: raise ValueError( "Failed to get the return address. Check stack frame registers (e.g., base pointer).", From e090cb562621f583fbbbfe7c82219cebee773cef Mon Sep 17 00:00:00 2001 From: io-no Date: Wed, 11 Sep 2024 01:03:30 +0200 Subject: [PATCH 139/144] test: update tests to the most recent API --- test/aarch64/scripts/finish_test.py | 6 +++--- test/amd64/scripts/finish_test.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/aarch64/scripts/finish_test.py b/test/aarch64/scripts/finish_test.py index 7354b204..fb7cf8e9 100644 --- a/test/aarch64/scripts/finish_test.py +++ b/test/aarch64/scripts/finish_test.py @@ -234,19 +234,19 @@ def test_heuristic_return_address(self): # We need to repeat the check for the three stages of the function preamble # Get current return address - curr_srip = stack_unwinder.get_return_address(d) + curr_srip = d.current_return_address() self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.step() # Get current return address - curr_srip = stack_unwinder.get_return_address(d) + curr_srip = d.current_return_address() self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.step() # Get current return address - curr_srip = stack_unwinder.get_return_address(d) + curr_srip = d.current_return_address() self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.kill() diff --git a/test/amd64/scripts/finish_test.py b/test/amd64/scripts/finish_test.py index ce968c82..ebece0ad 100644 --- a/test/amd64/scripts/finish_test.py +++ b/test/amd64/scripts/finish_test.py @@ -234,19 +234,19 @@ def test_heuristic_return_address(self): # We need to repeat the check for the three stages of the function preamble # Get current return address - curr_srip = stack_unwinder.get_return_address(d) + curr_srip = d.current_return_address() self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.step() # Get current return address - curr_srip = stack_unwinder.get_return_address(d) + curr_srip = d.current_return_address() self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.step() # Get current return address - curr_srip = stack_unwinder.get_return_address(d) + curr_srip = d.current_return_address() self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.kill() From 06fd644d7b32078fede31d9354da89d09af4848f Mon Sep 17 00:00:00 2001 From: MrIndeciso Date: Wed, 11 Sep 2024 17:41:06 +0200 Subject: [PATCH 140/144] fix: terminate now kills stopped binaries too --- libdebug/debugger/internal_debugger.py | 2 ++ libdebug/debugger/internal_debugger_holder.py | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index 4e386bc1..e657b1a9 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -343,6 +343,8 @@ def terminate(self: InternalDebugger) -> None: """ if self.instanced and self.running: self.interrupt() + + if self.instanced: self.kill() self.instanced = False diff --git a/libdebug/debugger/internal_debugger_holder.py b/libdebug/debugger/internal_debugger_holder.py index 7e151078..8a2d6da7 100644 --- a/libdebug/debugger/internal_debugger_holder.py +++ b/libdebug/debugger/internal_debugger_holder.py @@ -41,11 +41,6 @@ def _cleanup_internal_debugger() -> None: except Exception as e: liblog.debugger(f"Error while interrupting debuggee: {e}") - try: - debugger.kill() - except Exception as e: - liblog.debugger(f"Error while killing debuggee: {e}") - try: debugger.terminate() except Exception as e: From 22cb0d1333b04543373eff313b48d3c7d8e9028e Mon Sep 17 00:00:00 2001 From: io-no Date: Wed, 11 Sep 2024 23:49:25 +0200 Subject: [PATCH 141/144] refactor!: chenged the API to access to the last return address --- libdebug/ptrace/ptrace_interface.py | 2 +- libdebug/state/thread_context.py | 11 +++++------ test/aarch64/scripts/finish_test.py | 6 +++--- test/amd64/scripts/finish_test.py | 6 +++--- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/libdebug/ptrace/ptrace_interface.py b/libdebug/ptrace/ptrace_interface.py index 860e067b..f4246f82 100644 --- a/libdebug/ptrace/ptrace_interface.py +++ b/libdebug/ptrace/ptrace_interface.py @@ -310,7 +310,7 @@ def finish(self: PtraceInterface, thread: ThreadContext, heuristic: str) -> None invalidate_process_cache() elif heuristic == "backtrace": # Breakpoint to return address - last_saved_instruction_pointer = thread.current_return_address() + last_saved_instruction_pointer = thread.saved_ip # If a breakpoint already exists at the return address, we don't need to set a new one found = False diff --git a/libdebug/state/thread_context.py b/libdebug/state/thread_context.py index 145e9cb9..6b165a34 100644 --- a/libdebug/state/thread_context.py +++ b/libdebug/state/thread_context.py @@ -201,8 +201,9 @@ def print_backtrace(self: ThreadContext) -> None: else: print(f"{PrintStyle.RED}{return_address:#x} <{return_address_symbol}> {PrintStyle.RESET}") - def current_return_address(self: ThreadContext) -> int: - """Returns the return address of the current function.""" + @property + def saved_ip(self: ThreadContext) -> int: + """The return address of the current function.""" self._internal_debugger._ensure_process_stopped() stack_unwinder = stack_unwinding_provider(self._internal_debugger.arch) @@ -249,8 +250,7 @@ def finish(self: ThreadContext, heuristic: str = "backtrace") -> None: self._internal_debugger.finish(self, heuristic=heuristic) def next(self: ThreadContext) -> None: - """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns. - """ + """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns.""" self._internal_debugger.next(self) def si(self: ThreadContext) -> None: @@ -288,6 +288,5 @@ def fin(self: ThreadContext, heuristic: str = "backtrace") -> None: self._internal_debugger.finish(self, heuristic) def ni(self: ThreadContext) -> None: - """Alias for the `next` method. Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns. - """ + """Alias for the `next` method. Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns.""" self._internal_debugger.next(self) diff --git a/test/aarch64/scripts/finish_test.py b/test/aarch64/scripts/finish_test.py index fb7cf8e9..e2d7e025 100644 --- a/test/aarch64/scripts/finish_test.py +++ b/test/aarch64/scripts/finish_test.py @@ -234,19 +234,19 @@ def test_heuristic_return_address(self): # We need to repeat the check for the three stages of the function preamble # Get current return address - curr_srip = d.current_return_address() + curr_srip = d.saved_ip self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.step() # Get current return address - curr_srip = d.current_return_address() + curr_srip = d.saved_ip self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.step() # Get current return address - curr_srip = d.current_return_address() + curr_srip = d.saved_ip self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.kill() diff --git a/test/amd64/scripts/finish_test.py b/test/amd64/scripts/finish_test.py index ebece0ad..1c6337d4 100644 --- a/test/amd64/scripts/finish_test.py +++ b/test/amd64/scripts/finish_test.py @@ -234,19 +234,19 @@ def test_heuristic_return_address(self): # We need to repeat the check for the three stages of the function preamble # Get current return address - curr_srip = d.current_return_address() + curr_srip = d.saved_ip self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.step() # Get current return address - curr_srip = d.current_return_address() + curr_srip = d.saved_ip self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.step() # Get current return address - curr_srip = d.current_return_address() + curr_srip = d.saved_ip self.assertEqual(curr_srip, RETURN_POINT_FROM_C) d.kill() From 0d9c9e3a327ba56f3b9db6ffaae621db0ae933d3 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Thu, 12 Sep 2024 13:18:51 +0200 Subject: [PATCH 142/144] docs: change pydoc for d.terminate() --- libdebug/debugger/debugger.py | 4 ++-- libdebug/debugger/internal_debugger.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libdebug/debugger/debugger.py b/libdebug/debugger/debugger.py index 8673b711..33f36ecb 100644 --- a/libdebug/debugger/debugger.py +++ b/libdebug/debugger/debugger.py @@ -64,9 +64,9 @@ def kill(self: Debugger) -> None: self._internal_debugger.kill() def terminate(self: Debugger) -> None: - """Terminates the background thread. + """Interrupts the process, kills it and then terminates the background thread. - The debugger object cannot be used after this method is called. + The debugger object will not be usable after this method is called. This method should only be called to free up resources when the debugger object is no longer needed. """ self._internal_debugger.terminate() diff --git a/libdebug/debugger/internal_debugger.py b/libdebug/debugger/internal_debugger.py index e657b1a9..292c128a 100644 --- a/libdebug/debugger/internal_debugger.py +++ b/libdebug/debugger/internal_debugger.py @@ -336,9 +336,9 @@ def kill(self: InternalDebugger) -> None: self._join_and_check_status() def terminate(self: InternalDebugger) -> None: - """Terminates the background thread. + """Interrupts the process, kills it and then terminates the background thread. - The debugger object cannot be used after this method is called. + The debugger object will not be usable after this method is called. This method should only be called to free up resources when the debugger object is no longer needed. """ if self.instanced and self.running: From a0a1788daf8b3157db900e687cc9078f81e6cb89 Mon Sep 17 00:00:00 2001 From: Roberto Bertolini Date: Thu, 12 Sep 2024 13:22:57 +0200 Subject: [PATCH 143/144] docs: add an explanation about the kill_on_exit parameter --- docs/source/basic_features.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/basic_features.rst b/docs/source/basic_features.rst index f27bebb1..7a9ab062 100644 --- a/docs/source/basic_features.rst +++ b/docs/source/basic_features.rst @@ -36,6 +36,8 @@ After creating the debugger object, you can start the execution of the program u The `run()` command returns a `PipeManager` object, which you can use to interact with the program's standard input, output, and error. To read more about the PipeManager interface, please refer to the PipeManager documentation :class:`libdebug.utils.pipe_manager.PipeManager`. Please note that breakpoints are not kept between different runs of the program. If you want to set a breakpoint again, you should do so after the program has restarted. +Any process will be automatically killed when the debugging script exits. If you want to prevent this behavior, you can set the `kill_on_exit` parameter to False when creating the debugger object, or set the companion attribute `kill_on_exit` to False at runtime. + The command queue ----------------- Control flow commands, register access and memory access are all done through the command queue. This is a FIFO queue of commands that are executed in order. @@ -275,6 +277,9 @@ An alternative to running the program from the beginning and to resume libdebug d.attach(pid) +Do note that libdebug automatically kills any running process when the debugging script exits, even if the debugger has detached from it. +If you want to prevent this behavior, you can set the `kill_on_exit` parameter to False when creating the debugger object, or set the companion attribute `kill_on_exit` to False at runtime. + Graceful Termination ==================== From de031c57dd16fb0fe49b12ceb57fe167aafca500 Mon Sep 17 00:00:00 2001 From: io-no Date: Thu, 12 Sep 2024 18:32:32 +0200 Subject: [PATCH 144/144] fix: added validation of the return address on AARCH64 --- .../aarch64/aarch64_stack_unwinder.py | 24 +++++++++++++++---- .../amd64/amd64_stack_unwinder.py | 2 +- .../architectures/stack_unwinding_manager.py | 3 ++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/libdebug/architectures/aarch64/aarch64_stack_unwinder.py b/libdebug/architectures/aarch64/aarch64_stack_unwinder.py index 80e0190f..7a9b5065 100644 --- a/libdebug/architectures/aarch64/aarch64_stack_unwinder.py +++ b/libdebug/architectures/aarch64/aarch64_stack_unwinder.py @@ -9,8 +9,10 @@ from typing import TYPE_CHECKING from libdebug.architectures.stack_unwinding_manager import StackUnwindingManager +from libdebug.liblog import liblog if TYPE_CHECKING: + from libdebug.data.memory_map import MemoryMap from libdebug.state.thread_context import ThreadContext @@ -29,10 +31,18 @@ def unwind(self: Aarch64StackUnwinder, target: ThreadContext) -> list: assert hasattr(target.regs, "pc") frame_pointer = target.regs.x29 - initial_link_register = target.regs.x30 - stack_trace = [target.regs.pc, initial_link_register] vmaps = target._internal_debugger.debugging_interface.maps() + initial_link_register = None + + try: + initial_link_register = self.get_return_address(target, vmaps) + except ValueError: + liblog.warning( + "Failed to get the return address. Check stack frame registers (e.g., base pointer). The stack trace may be incomplete.", + ) + + stack_trace = [target.regs.pc, initial_link_register] if initial_link_register else [target.regs.pc] # Follow the frame chain while frame_pointer: @@ -56,13 +66,19 @@ def unwind(self: Aarch64StackUnwinder, target: ThreadContext) -> list: return stack_trace - def get_return_address(self: Aarch64StackUnwinder, target: ThreadContext) -> int: + def get_return_address(self: Aarch64StackUnwinder, target: ThreadContext, vmaps: list[MemoryMap]) -> int: """Get the return address of the current function. Args: target (ThreadContext): The target ThreadContext. + vmaps (list[MemoryMap]): The memory maps of the process. Returns: int: The return address. """ - return target.regs.x30 + return_address = target.regs.x30 + + if not any(vmap.start <= return_address < vmap.end for vmap in vmaps): + raise ValueError("Return address not in any valid memory map") + + return return_address diff --git a/libdebug/architectures/amd64/amd64_stack_unwinder.py b/libdebug/architectures/amd64/amd64_stack_unwinder.py index 2f6f9146..6cec1798 100644 --- a/libdebug/architectures/amd64/amd64_stack_unwinder.py +++ b/libdebug/architectures/amd64/amd64_stack_unwinder.py @@ -63,7 +63,7 @@ def unwind(self: Amd64StackUnwinder, target: ThreadContext) -> list: stack_trace.append(first_return_address) except (OSError, ValueError): liblog.warning( - "Failed to get the return address from the stack. Check stack frame registers (e.g., base pointer). The stack trace may be incomplete.", + "Failed to get the return address. Check stack frame registers (e.g., base pointer). The stack trace may be incomplete.", ) return stack_trace diff --git a/libdebug/architectures/stack_unwinding_manager.py b/libdebug/architectures/stack_unwinding_manager.py index 110dc9b1..fb0f8f02 100644 --- a/libdebug/architectures/stack_unwinding_manager.py +++ b/libdebug/architectures/stack_unwinding_manager.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from libdebug.data.memory_map import MemoryMap from libdebug.state.thread_context import ThreadContext @@ -21,5 +22,5 @@ def unwind(self: StackUnwindingManager, target: ThreadContext) -> list: """Unwind the stack of the target process.""" @abstractmethod - def get_return_address(self: StackUnwindingManager, target: ThreadContext) -> int: + def get_return_address(self: StackUnwindingManager, target: ThreadContext, vmaps: list[MemoryMap]) -> int: """Get the return address of the current function."""