diff --git a/hyperdbg/include/platform/general/header/nt-list.h b/hyperdbg/include/platform/general/header/nt-list.h new file mode 100644 index 00000000..f53f174f --- /dev/null +++ b/hyperdbg/include/platform/general/header/nt-list.h @@ -0,0 +1,145 @@ +/** + * @file nt-list.h + * @author Max Raulea (max.raulea@gmail.com) + * @brief Cross-platform NT-style intrusive doubly-linked list helpers + CONTAINING_RECORD + * @details The shared debugger code uses the NT LIST_ENTRY API (InitializeListHead, + * InsertHeadList, RemoveEntryList, CONTAINING_RECORD, ...). On Windows these + * come from (or, under USE_NATIVE_SDK_HEADERS, from the in-tree + * platform/user/header/Windows.h). Platforms whose system headers don't ship + * them (Linux) get them here, so the shared code compiles unchanged. + * + * These operate on the exact LIST_ENTRY {Flink, Blink} layout defined in + * SDK/headers/DataTypes.h, which is part of the kernel<->user IOCTL ABI -- + * that's why we mirror the NT list rather than reaching for sys/queue.h + * (different layout + a colliding LIST_ENTRY name). + * + * The body is compiled only where the OS headers don't already provide these + * (i.e. not on Windows), so including this unconditionally is safe. + * + * @version 0.1 + * @date 2026-06-16 + * + * @copyright This project is released under the GNU Public License v3. + * + */ +#pragma once + +#ifndef _WIN32 + +# include // offsetof + +// +// Given the address of a LIST_ENTRY field embedded in a struct, recover the +// address of the containing struct. +// +# ifndef CONTAINING_RECORD +# define CONTAINING_RECORD(address, type, field) \ + ((type *)((char *)(address) - offsetof(type, field))) +# endif + +// +// Initialize a list head to the empty (points-to-self) state. +// +static inline void +InitializeListHead(PLIST_ENTRY ListHead) +{ + ListHead->Flink = ListHead->Blink = ListHead; +} + +// +// TRUE if the list has no entries. +// +static inline BOOLEAN +IsListEmpty(const LIST_ENTRY * ListHead) +{ + return (BOOLEAN)(ListHead->Flink == ListHead); +} + +// +// Unlink an entry. Returns TRUE if the list is now empty. +// +static inline BOOLEAN +RemoveEntryList(PLIST_ENTRY Entry) +{ + PLIST_ENTRY Flink = Entry->Flink; + PLIST_ENTRY Blink = Entry->Blink; + + Blink->Flink = Flink; + Flink->Blink = Blink; + + return (BOOLEAN)(Flink == Blink); +} + +// +// Unlink and return the first entry. +// +static inline PLIST_ENTRY +RemoveHeadList(PLIST_ENTRY ListHead) +{ + PLIST_ENTRY Entry = ListHead->Flink; + PLIST_ENTRY Flink = Entry->Flink; + + ListHead->Flink = Flink; + Flink->Blink = ListHead; + + return Entry; +} + +// +// Unlink and return the last entry. +// +static inline PLIST_ENTRY +RemoveTailList(PLIST_ENTRY ListHead) +{ + PLIST_ENTRY Entry = ListHead->Blink; + PLIST_ENTRY Blink = Entry->Blink; + + ListHead->Blink = Blink; + Blink->Flink = ListHead; + + return Entry; +} + +// +// Insert an entry at the tail of the list. +// +static inline void +InsertTailList(PLIST_ENTRY ListHead, PLIST_ENTRY Entry) +{ + PLIST_ENTRY Blink = ListHead->Blink; + + Entry->Flink = ListHead; + Entry->Blink = Blink; + Blink->Flink = Entry; + ListHead->Blink = Entry; +} + +// +// Insert an entry at the head of the list. +// +static inline void +InsertHeadList(PLIST_ENTRY ListHead, PLIST_ENTRY Entry) +{ + PLIST_ENTRY Flink = ListHead->Flink; + + Entry->Flink = Flink; + Entry->Blink = ListHead; + Flink->Blink = Entry; + ListHead->Flink = Entry; +} + +// +// Splice the entries of ListToAppend onto the tail of ListHead. +// +static inline void +AppendTailList(PLIST_ENTRY ListHead, PLIST_ENTRY ListToAppend) +{ + PLIST_ENTRY ListEnd = ListHead->Blink; + + ListHead->Blink->Flink = ListToAppend; + ListHead->Blink = ListToAppend->Blink; + ListToAppend->Blink->Flink = ListHead; + ListToAppend->Blink = ListEnd; +} + +#endif // !_WIN32 diff --git a/hyperdbg/include/platform/user/code/platform-signal.c b/hyperdbg/include/platform/user/code/platform-signal.c new file mode 100644 index 00000000..4125f38f --- /dev/null +++ b/hyperdbg/include/platform/user/code/platform-signal.c @@ -0,0 +1,137 @@ +/** + * @file platform-signal.c + * @author Max Raulea (max.raulea@gmail.com) + * @brief User mode cross-platform implementation of the console-control handler + * @details See platform-signal.h. The Windows branch forwards to SetConsoleCtrlHandler. + * The Linux branch blocks the handled signals and dispatches them from a + * dedicated sigwait() thread. sigwait() (rather than a sigaction() handler) + * is used deliberately: BreakController calls ShowMessages (printf), socket + * I/O and Sleep, none of which are async-signal-safe. Running the handler on + * a normal thread woken by sigwait() sidesteps that entirely and mirrors the + * Windows model where the console-control handler also runs on its own thread. + * + * @version 0.1 + * @date 2026-06-16 + * + * @copyright This project is released under the GNU Public License v3. + * + */ +#include "pch.h" + +#if defined(__linux__) +# include "../header/platform-signal.h" +#endif // defined(__linux__) + +#if defined(_WIN32) + +BOOLEAN +PlatformInstallCtrlHandler(PLATFORM_CTRL_HANDLER Handler) +{ + return (BOOLEAN)SetConsoleCtrlHandler((PHANDLER_ROUTINE)Handler, TRUE); +} + +#elif defined(__linux__) + +# include +# include + +// +// The installed handler, read by the dispatch thread. +// +static PLATFORM_CTRL_HANDLER g_PlatformCtrlHandler = NULL; + +// +// Map a POSIX signal onto the Win32 console-control event the shared handler +// understands. Only the two interactive break signals are mapped: +// SIGINT (CTRL+C) -> CTRL_C_EVENT +// SIGQUIT (CTRL+\) -> CTRL_BREAK_EVENT +// SIGTERM/SIGHUP are intentionally left at their default disposition so the +// process stays killable/terminable the usual way; the CLOSE/LOGOFF/SHUTDOWN +// console events have no clean Linux analog. +// +static DWORD +PlatformMapSignalToCtrlType(int Signal) +{ + switch (Signal) + { + case SIGINT: + return CTRL_C_EVENT; + case SIGQUIT: + return CTRL_BREAK_EVENT; + default: + return (DWORD)-1; + } +} + +// +// Dedicated thread: waits for the handled signals and dispatches them to the +// installed handler in ordinary (async-signal-safe) thread context. +// +static void * +PlatformSignalThread(void * Arg) +{ + sigset_t WaitSet; + int Signal; + + (void)Arg; + + sigemptyset(&WaitSet); + sigaddset(&WaitSet, SIGINT); + sigaddset(&WaitSet, SIGQUIT); + + while (1) + { + if (sigwait(&WaitSet, &Signal) != 0) + { + continue; + } + + DWORD CtrlType = PlatformMapSignalToCtrlType(Signal); + + if (g_PlatformCtrlHandler != NULL && CtrlType != (DWORD)-1) + { + g_PlatformCtrlHandler(CtrlType); + } + } + + return NULL; +} + +BOOLEAN +PlatformInstallCtrlHandler(PLATFORM_CTRL_HANDLER Handler) +{ + sigset_t BlockSet; + pthread_t Thread; + + g_PlatformCtrlHandler = Handler; + + // + // Block the handled signals in this (main) thread. Done before any other + // thread is created, the mask is inherited by every thread, guaranteeing + // the signals are delivered to our sigwait() thread and nowhere else. + // + sigemptyset(&BlockSet); + sigaddset(&BlockSet, SIGINT); + sigaddset(&BlockSet, SIGQUIT); + + if (pthread_sigmask(SIG_BLOCK, &BlockSet, NULL) != 0) + { + return FALSE; + } + + if (pthread_create(&Thread, NULL, PlatformSignalThread, NULL) != 0) + { + return FALSE; + } + + // + // Fire-and-forget: the thread lives for the life of the process. + // + pthread_detach(Thread); + + return TRUE; +} + +#else +# error "Unsupported platform" +#endif diff --git a/hyperdbg/include/platform/user/header/platform-signal.h b/hyperdbg/include/platform/user/header/platform-signal.h new file mode 100644 index 00000000..963ba771 --- /dev/null +++ b/hyperdbg/include/platform/user/header/platform-signal.h @@ -0,0 +1,43 @@ +/** + * @file platform-signal.h + * @author Max Raulea (max.raulea@gmail.com) + * @brief User mode cross-platform interface for the console-control (CTRL+C / CTRL+BREAK) handler + * @details HyperDbg installs a single handler (BreakController) that pauses the + * debuggee when the user hits CTRL+C / CTRL+BREAK. The handler body is + * platform independent; only the OS mechanism that delivers the event + * and the thread context it runs in are OS specific. This interface + * isolates that registration. Windows maps onto SetConsoleCtrlHandler; + * Linux maps onto POSIX signals serviced by a dedicated sigwait() thread + * so the handler runs in ordinary thread context (matching Windows, which + * also dispatches the handler on its own thread). + * + * @version 0.1 + * @date 2026-06-16 + * + * @copyright This project is released under the GNU Public License v3. + * + */ +#pragma once + +#if defined(__linux__) +# include "../../../../include/SDK/HyperDbgSdk.h" +#endif // defined(__linux__) + +// +// Console-control handler signature. Matches the Win32 PHANDLER_ROUTINE shape +// (and BreakController) so the same function pointer is usable on both platforms. +// CtrlType is one of the CTRL_*_EVENT codes. +// +typedef BOOL (*PLATFORM_CTRL_HANDLER)(DWORD CtrlType); + +// +// INSTALL the process console-control handler. +// Windows: registers Handler via SetConsoleCtrlHandler. +// Linux: blocks the relevant signals process-wide and starts a dedicated +// sigwait() thread that translates them into CTRL_*_EVENT codes and +// invokes Handler. Must be called once, before other threads spawn, +// so the blocked-signal mask is inherited by every thread. +// Returns TRUE on success, FALSE on failure. +// +BOOLEAN +PlatformInstallCtrlHandler(PLATFORM_CTRL_HANDLER Handler); diff --git a/hyperdbg/libhyperdbg/code/debugger/core/debugger.cpp b/hyperdbg/libhyperdbg/code/debugger/core/debugger.cpp index 65cf542f..de827e8c 100644 --- a/hyperdbg/libhyperdbg/code/debugger/core/debugger.cpp +++ b/hyperdbg/libhyperdbg/code/debugger/core/debugger.cpp @@ -656,6 +656,7 @@ ShowErrorMessage(UINT32 Error) UINT64 DebuggerGetNtoskrnlBase() { +#ifdef _WIN32 UINT64 NtoskrnlBase = NULL; PRTL_PROCESS_MODULES Modules = NULL; @@ -678,6 +679,14 @@ DebuggerGetNtoskrnlBase() free(Modules); return NtoskrnlBase; +#else + // + // TODO(Linux): NT-style system module enumeration (PRTL_PROCESS_MODULES via + // NtQuerySystemInformation) has no Linux analog. The kernel-module base lookup + // will need a Linux-specific mechanism once the kernel side exists. + // + return NULL64_ZERO; +#endif } /** @@ -719,7 +728,7 @@ DebuggerPauseDebuggee() // // Send a pause IOCTL // - StatusIoctl = DeviceIoControl(g_DeviceHandle, // Handle to device + StatusIoctl = PlatformDeviceIoControl(g_DeviceHandle, // Handle to device IOCTL_PAUSE_PACKET_RECEIVED, // IO Control Code (IOCTL) &PauseRequest, // Input Buffer to driver. SIZEOF_DEBUGGER_PAUSE_PACKET_RECEIVED, // Input buffer @@ -733,7 +742,7 @@ DebuggerPauseDebuggee() if (!StatusIoctl) { - ShowMessages("ioctl failed with code 0x%x\n", GetLastError()); + ShowMessages("ioctl failed with code 0x%x\n", PlatformGetLastError()); return FALSE; } @@ -1391,7 +1400,7 @@ SendEventToKernel(PDEBUGGER_GENERAL_EVENT_DETAIL Event, // Send IOCTL // - Status = DeviceIoControl(g_DeviceHandle, // Handle to device + Status = PlatformDeviceIoControl(g_DeviceHandle, // Handle to device IOCTL_DEBUGGER_REGISTER_EVENT, // IO Control Code (IOCTL) Event, // Input Buffer to driver. EventBufferLength, // Input buffer length @@ -1408,7 +1417,7 @@ SendEventToKernel(PDEBUGGER_GENERAL_EVENT_DETAIL Event, if (!Status) { - ShowMessages("ioctl failed with code 0x%x\n", GetLastError()); + ShowMessages("ioctl failed with code 0x%x\n", PlatformGetLastError()); return FALSE; } } @@ -1560,7 +1569,7 @@ RegisterActionToEvent(PDEBUGGER_GENERAL_EVENT_DETAIL Event, // if (ActionBreakToDebugger != NULL) { - Status = DeviceIoControl( + Status = PlatformDeviceIoControl( g_DeviceHandle, // Handle to device IOCTL_DEBUGGER_ADD_ACTION_TO_EVENT, // IO Control Code (IOCTL) ActionBreakToDebugger, // Input Buffer to driver. @@ -1578,7 +1587,7 @@ RegisterActionToEvent(PDEBUGGER_GENERAL_EVENT_DETAIL Event, if (!Status) { - ShowMessages("ioctl failed with code 0x%x\n", GetLastError()); + ShowMessages("ioctl failed with code 0x%x\n", PlatformGetLastError()); return FALSE; } } @@ -1588,7 +1597,7 @@ RegisterActionToEvent(PDEBUGGER_GENERAL_EVENT_DETAIL Event, // if (ActionCustomCode != NULL) { - Status = DeviceIoControl( + Status = PlatformDeviceIoControl( g_DeviceHandle, // Handle to device IOCTL_DEBUGGER_ADD_ACTION_TO_EVENT, // IO Control Code (IOCTL) ActionCustomCode, // Input Buffer to driver. @@ -1606,7 +1615,7 @@ RegisterActionToEvent(PDEBUGGER_GENERAL_EVENT_DETAIL Event, if (!Status) { - ShowMessages("ioctl failed with code 0x%x\n", GetLastError()); + ShowMessages("ioctl failed with code 0x%x\n", PlatformGetLastError()); return FALSE; } } @@ -1616,7 +1625,7 @@ RegisterActionToEvent(PDEBUGGER_GENERAL_EVENT_DETAIL Event, // if (ActionScript != NULL) { - Status = DeviceIoControl( + Status = PlatformDeviceIoControl( g_DeviceHandle, // Handle to device IOCTL_DEBUGGER_ADD_ACTION_TO_EVENT, // IO Control Code (IOCTL) ActionScript, // Input Buffer to driver. @@ -1634,7 +1643,7 @@ RegisterActionToEvent(PDEBUGGER_GENERAL_EVENT_DETAIL Event, if (!Status) { - ShowMessages("ioctl failed with code 0x%x\n", GetLastError()); + ShowMessages("ioctl failed with code 0x%x\n", PlatformGetLastError()); return FALSE; } } @@ -1800,7 +1809,7 @@ InterpretGeneralEventAndActionsFields( // PVOID BufferOfCommandString = malloc(BufferOfCommandStringLength); - RtlZeroMemory(BufferOfCommandString, BufferOfCommandStringLength); + PlatformZeroMemory(BufferOfCommandString, BufferOfCommandStringLength); // // Copy the string to the buffer @@ -2105,7 +2114,7 @@ InterpretGeneralEventAndActionsFields( LengthOfEventBuffer = sizeof(DEBUGGER_GENERAL_EVENT_DETAIL) + ConditionBufferLength; TempEvent = (PDEBUGGER_GENERAL_EVENT_DETAIL)malloc(LengthOfEventBuffer); - RtlZeroMemory(TempEvent, LengthOfEventBuffer); + PlatformZeroMemory(TempEvent, LengthOfEventBuffer); // // Check if buffer is available @@ -2187,7 +2196,7 @@ InterpretGeneralEventAndActionsFields( TempActionCustomCode = (PDEBUGGER_GENERAL_ACTION)malloc(LengthOfCustomCodeActionBuffer); - RtlZeroMemory(TempActionCustomCode, LengthOfCustomCodeActionBuffer); + PlatformZeroMemory(TempActionCustomCode, LengthOfCustomCodeActionBuffer); memcpy( (PVOID)((UINT64)TempActionCustomCode + sizeof(DEBUGGER_GENERAL_ACTION)), @@ -2226,7 +2235,7 @@ InterpretGeneralEventAndActionsFields( LengthOfScriptActionBuffer = sizeof(DEBUGGER_GENERAL_ACTION) + ScriptBufferLength; TempActionScript = (PDEBUGGER_GENERAL_ACTION)malloc(LengthOfScriptActionBuffer); - RtlZeroMemory(TempActionScript, LengthOfScriptActionBuffer); + PlatformZeroMemory(TempActionScript, LengthOfScriptActionBuffer); memcpy((PVOID)((UINT64)TempActionScript + sizeof(DEBUGGER_GENERAL_ACTION)), (PVOID)ScriptBufferAddress, @@ -2272,7 +2281,7 @@ InterpretGeneralEventAndActionsFields( TempActionBreak = (PDEBUGGER_GENERAL_ACTION)malloc(LengthOfBreakActionBuffer); - RtlZeroMemory(TempActionBreak, LengthOfBreakActionBuffer); + PlatformZeroMemory(TempActionBreak, LengthOfBreakActionBuffer); // // Set the action Tag diff --git a/hyperdbg/libhyperdbg/libhyperdbg.vcxproj b/hyperdbg/libhyperdbg/libhyperdbg.vcxproj index e1a15be8..30b4dbf2 100644 --- a/hyperdbg/libhyperdbg/libhyperdbg.vcxproj +++ b/hyperdbg/libhyperdbg/libhyperdbg.vcxproj @@ -137,6 +137,7 @@ msbuild "$(SolutionDir)dependencies\zydis\msvc\Zydis.sln" /m /p:Configuration="R + diff --git a/hyperdbg/libhyperdbg/libhyperdbg.vcxproj.filters b/hyperdbg/libhyperdbg/libhyperdbg.vcxproj.filters index 4e3ef59f..7c38052a 100644 --- a/hyperdbg/libhyperdbg/libhyperdbg.vcxproj.filters +++ b/hyperdbg/libhyperdbg/libhyperdbg.vcxproj.filters @@ -206,6 +206,9 @@ header\platform + + header\platform + header diff --git a/hyperdbg/libhyperdbg/pch.h b/hyperdbg/libhyperdbg/pch.h index b78b022d..f06ccdf7 100644 --- a/hyperdbg/libhyperdbg/pch.h +++ b/hyperdbg/libhyperdbg/pch.h @@ -171,6 +171,12 @@ typedef const wchar_t *LPCWCHAR, *PCWCHAR; // #include "platform/user/header/platform-signal.h" +// +// NT-style intrusive linked-list helpers + CONTAINING_RECORD (self-guards to +// non-Windows; Windows gets these from / the native-SDK shim) +// +#include "platform/general/header/nt-list.h" + // // Platform-specific intrinsics //