From 18d3d006275c5f71a67ced7be2e8f6ed2e3b0653 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 17 Dec 2024 11:06:11 +0000 Subject: [PATCH 001/148] Sleep skipping: ensure time_skipped variable is only used when sleep_skip_active is true (hook_sleep.c) --- hook_sleep.c | 95 ++++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/hook_sleep.c b/hook_sleep.c index a09f4e20..c002b79d 100644 --- a/hook_sleep.c +++ b/hook_sleep.c @@ -80,7 +80,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtWaitForSingleObject, newint.QuadPart = Timeout->QuadPart; - if (newint.QuadPart > 0LL) { + if (sleep_skip_active && newint.QuadPart > 0LL) { /* convert absolute time to relative time */ GetSystemTimeAsFileTime(&ft); @@ -99,13 +99,13 @@ HOOKDEF(NTSTATUS, WINAPI, NtWaitForSingleObject, li.LowPart = ft.dwLowDateTime; /* clamp sleeps between 30 seconds and 1 hour down to 10 seconds as long as we didn't force off sleep skipping */ - if (milli >= 30000 && milli <= 3600000 && g_config.force_sleepskip != 0) { + if (sleep_skip_active && milli >= 30000 && milli <= 3600000 && g_config.force_sleepskip != 0) { newint.QuadPart = -(10000 * 10000); time_skipped.QuadPart += interval - (10000 * 10000); LOQ_ntstatus("system", "pis", "Handle", Handle, "Milliseconds", milli, "Status", "Skipped"); goto docall; } - else if (g_config.force_sleepskip > 0) { + else if (sleep_skip_active && g_config.force_sleepskip > 0) { time_skipped.QuadPart += interval; LOQ_ntstatus("system", "pis", "Handle", Handle, "Milliseconds", milli, "Status", "Skipped"); newint.QuadPart = 0; @@ -114,7 +114,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtWaitForSingleObject, else { disable_sleep_skip(); } - if (milli <= 10) { + if (sleep_skip_active && milli <= 10) { if (num_wait_small < 20) { LOQ_ntstatus("system", "pi", "Handle", Handle, "Milliseconds", milli); num_wait_small++; @@ -165,7 +165,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtDelayExecution, goto docall; } - if (newint.QuadPart > 0LL) { + if (sleep_skip_active && newint.QuadPart > 0LL) { /* convert absolute time to relative time */ if (Old_GetSystemTimeAsFileTime) Old_GetSystemTimeAsFileTime(&ft); @@ -190,7 +190,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtDelayExecution, li.LowPart = ft.dwLowDateTime; // check if we're still within the hardcoded limit - if(sleep_skip_active && (li.QuadPart < time_start.QuadPart + MAX_SLEEP_SKIP_DIFF * 10000)) { + if (sleep_skip_active && (li.QuadPart < time_start.QuadPart + MAX_SLEEP_SKIP_DIFF * 10000)) { time_skipped.QuadPart += interval; if (num_skipped < 20) { @@ -205,13 +205,13 @@ HOOKDEF(NTSTATUS, WINAPI, NtDelayExecution, goto skipcall; } /* clamp sleeps between 30 seconds and 1 hour down to 10 seconds as long as we didn't force off sleep skipping */ - else if (milli >= 30000 && milli <= 3600000 && g_config.force_sleepskip != 0) { + else if (sleep_skip_active && milli >= 30000 && milli <= 3600000 && g_config.force_sleepskip != 0) { newint.QuadPart = -(10000 * 10000); time_skipped.QuadPart += interval - (10000 * 10000); LOQ_ntstatus("system", "is", "Milliseconds", milli, "Status", "Skipped"); goto docall; } - else if (g_config.force_sleepskip > 0) { + else if (sleep_skip_active && g_config.force_sleepskip > 0) { time_skipped.QuadPart += interval; LOQ_ntstatus("system", "is", "Milliseconds", milli, "Status", "Skipped"); newint.QuadPart = 0; @@ -220,7 +220,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtDelayExecution, else { disable_sleep_skip(); } - if (milli <= 10) { + if (sleep_skip_active && milli <= 10) { if (num_small < 20) { LOQ_ntstatus("system", "i", "Milliseconds", milli); num_small++; @@ -259,13 +259,13 @@ HOOKDEF(DWORD, WINAPI, MsgWaitForMultipleObjectsEx, goto docall; /* clamp sleeps between 30 seconds and 1 hour down to 10 seconds as long as we didn't force off sleep skipping */ - else if (dwMilliseconds >= 30000 && dwMilliseconds <= 3600000 && g_config.force_sleepskip != 0) { + else if (sleep_skip_active && dwMilliseconds >= 30000 && dwMilliseconds <= 3600000 && g_config.force_sleepskip != 0) { time_skipped.QuadPart += (dwMilliseconds - 10000) * 10000; LOQ_msgwait("system", "is", "Milliseconds", dwMilliseconds, "Status", "Skipped"); dwMilliseconds = 10000; goto docall; } - else if (g_config.force_sleepskip > 0) { + else if (sleep_skip_active && g_config.force_sleepskip > 0) { LOQ_msgwait("system", "is", "Milliseconds", dwMilliseconds, "Status", "Skipped"); dwMilliseconds = 0; goto docall; @@ -274,7 +274,7 @@ HOOKDEF(DWORD, WINAPI, MsgWaitForMultipleObjectsEx, disable_sleep_skip(); } - if (dwMilliseconds <= 10) { + if (sleep_skip_active && dwMilliseconds <= 10) { if (num_msg_small < 20) { LOQ_msgwait("system", "i", "Milliseconds", dwMilliseconds); num_msg_small++; @@ -326,7 +326,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetTimer, goto docall; } - if (newint.QuadPart > 0LL) { + if (sleep_skip_active && newint.QuadPart > 0LL) { /* convert absolute time to relative time */ GetSystemTimeAsFileTime(&ft); @@ -341,13 +341,13 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetTimer, milli = (unsigned long)(interval / 10000); /* clamp sleeps between 30 seconds and 1 hour down to 10 seconds as long as we didn't force off sleep skipping */ - if (milli >= 30000 && milli <= 3600000 && g_config.force_sleepskip != 0) { + if (sleep_skip_active && milli >= 30000 && milli <= 3600000 && g_config.force_sleepskip != 0) { newint.QuadPart = -(10000 * 10000); time_skipped.QuadPart += interval - (10000 * 10000); LOQ_ntstatus("system", "is", "Milliseconds", milli, "Status", "Skipped"); goto docall; } - else if (g_config.force_sleepskip > 0) { + else if (sleep_skip_active && g_config.force_sleepskip > 0) { time_skipped.QuadPart += interval; LOQ_ntstatus("system", "is", "Milliseconds", milli, "Status", "Skipped"); newint.QuadPart = 0; @@ -393,7 +393,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetTimerEx, goto docall; } - if (newint.QuadPart > 0LL) { + if (sleep_skip_active && newint.QuadPart > 0LL) { /* convert absolute time to relative time */ GetSystemTimeAsFileTime(&ft); @@ -410,14 +410,14 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetTimerEx, milli = (unsigned long)(interval / 10000); /* clamp sleeps between 30 seconds and 1 hour down to 10 seconds as long as we didn't force off sleep skipping */ - if (milli >= 30000 && milli <= 3600000 && g_config.force_sleepskip != 0) { + if (sleep_skip_active && milli >= 30000 && milli <= 3600000 && g_config.force_sleepskip != 0) { timerinfo->DueTime.QuadPart = -(10000 * 10000); time_skipped.QuadPart += interval - (10000 * 10000); LOQ_ntstatus("system", "is", "Milliseconds", milli, "Status", "Skipped"); modified_delay = TRUE; goto docall; } - else if (g_config.force_sleepskip > 0) { + else if (sleep_skip_active && g_config.force_sleepskip > 0) { time_skipped.QuadPart += interval; LOQ_ntstatus("system", "is", "Milliseconds", milli, "Status", "Skipped"); timerinfo->DueTime.QuadPart = 0; @@ -447,7 +447,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtQueryPerformanceCounter, ret = Old_NtQueryPerformanceCounter(PerformanceCounter, PerformanceFrequency); - if (NT_SUCCESS(ret)) { + if (NT_SUCCESS(ret) && sleep_skip_active) { if (!perf_multiplier.QuadPart) perf_multiplier.QuadPart = PerformanceFrequency->QuadPart / 1000; PerformanceCounter->QuadPart += (time_skipped.QuadPart / 10000) * perf_multiplier.QuadPart; @@ -468,13 +468,15 @@ HOOKDEF(void, WINAPI, GetLocalTime, get_lasterrors(&lasterror); - SystemTimeToFileTime(lpSystemTime, &ft); - li.HighPart = ft.dwHighDateTime; - li.LowPart = ft.dwLowDateTime; - li.QuadPart += time_skipped.QuadPart; - ft.dwHighDateTime = li.HighPart; - ft.dwLowDateTime = li.LowPart; - FileTimeToSystemTime(&ft, lpSystemTime); + if (sleep_skip_active) { + SystemTimeToFileTime(lpSystemTime, &ft); + li.HighPart = ft.dwHighDateTime; + li.LowPart = ft.dwLowDateTime; + li.QuadPart += time_skipped.QuadPart; + ft.dwHighDateTime = li.HighPart; + ft.dwLowDateTime = li.LowPart; + FileTimeToSystemTime(&ft, lpSystemTime); + } LOQ_void("system", ""); @@ -492,13 +494,15 @@ HOOKDEF(void, WINAPI, GetSystemTime, get_lasterrors(&lasterror); - SystemTimeToFileTime(lpSystemTime, &ft); - li.HighPart = ft.dwHighDateTime; - li.LowPart = ft.dwLowDateTime; - li.QuadPart += time_skipped.QuadPart; - ft.dwHighDateTime = li.HighPart; - ft.dwLowDateTime = li.LowPart; - FileTimeToSystemTime(&ft, lpSystemTime); + if (sleep_skip_active) { + SystemTimeToFileTime(lpSystemTime, &ft); + li.HighPart = ft.dwHighDateTime; + li.LowPart = ft.dwLowDateTime; + li.QuadPart += time_skipped.QuadPart; + ft.dwHighDateTime = li.HighPart; + ft.dwLowDateTime = li.LowPart; + FileTimeToSystemTime(&ft, lpSystemTime); + } LOQ_void("system", ""); @@ -534,7 +538,8 @@ HOOKDEF(DWORD, WINAPI, GetTickCount, ret = raw_gettickcount(); // add the time we've skipped - ret += (DWORD)(time_skipped.QuadPart / 10000); + if (sleep_skip_active) + ret += (DWORD)(time_skipped.QuadPart / 10000); return ret; } @@ -549,7 +554,8 @@ HOOKDEF(ULONGLONG, WINAPI, GetTickCount64, ret = raw_gettickcount64(); // add the time we've skipped - ret += (time_skipped.QuadPart / 10000); + if (sleep_skip_active) + ret += (time_skipped.QuadPart / 10000); return ret; } @@ -560,7 +566,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtQuerySystemTime, ) { NTSTATUS ret = Old_NtQuerySystemTime(SystemTime); LOQ_ntstatus("system", ""); - if(NT_SUCCESS(ret)) { + if (NT_SUCCESS(ret) && sleep_skip_active) { SystemTime->QuadPart += time_skipped.QuadPart; } return 0; @@ -576,7 +582,8 @@ HOOKDEF(DWORD, WINAPI, timeGetTime, ret = Old_timeGetTime(); // add the time we've skipped - ret += (DWORD)(time_skipped.QuadPart / 10000); + if (sleep_skip_active) + ret += (DWORD)(time_skipped.QuadPart / 10000); return ret; } @@ -590,11 +597,13 @@ HOOKDEF(void, WINAPI, GetSystemTimeAsFileTime, Old_GetSystemTimeAsFileTime(&ft); - li.HighPart = ft.dwHighDateTime; - li.LowPart = ft.dwLowDateTime; - li.QuadPart += time_skipped.QuadPart; - ft.dwHighDateTime = li.HighPart; - ft.dwLowDateTime = li.LowPart; + if (sleep_skip_active) { + li.HighPart = ft.dwHighDateTime; + li.LowPart = ft.dwLowDateTime; + li.QuadPart += time_skipped.QuadPart; + ft.dwHighDateTime = li.HighPart; + ft.dwLowDateTime = li.LowPart; + } memcpy(lpSystemTimeAsFileTime, &ft, sizeof(ft)); @@ -655,7 +664,7 @@ void init_sleep_skip(int first_process) time_start.LowPart = ft.dwLowDateTime; // we don't want to skip sleep calls in child processes - if(first_process == 0) { + if (first_process == 0) { disable_sleep_skip(); } } From 322c30b707b64f195c1b6bf51f8d7cf13953d8ac Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 17 Dec 2024 11:07:33 +0000 Subject: [PATCH 002/148] CoGetClassObject hook: add inspect_clsid for improved injection (e.g. 38a9847cb5ce4918bdfee2d54d5d3b79e1399cce15c7b68d86e8f0a5f48e3131) --- hook_special.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hook_special.c b/hook_special.c index 94a353d3..5b90b72d 100644 --- a/hook_special.c +++ b/hook_special.c @@ -459,6 +459,12 @@ HOOKDEF(HRESULT, WINAPI, CoGetClassObject, sprintf(idbuf2, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", id2.Data1, id2.Data2, id2.Data3, id2.Data4[0], id2.Data4[1], id2.Data4[2], id2.Data4[3], id2.Data4[4], id2.Data4[5], id2.Data4[6], id2.Data4[7]); + if (!called_by_hook()) { + inspect_clsid(&id1); + } + + disable_sleep_skip(); + set_lasterrors(&lasterror); memcpy(&saved_hookinfo, hook_info(), sizeof(saved_hookinfo)); From e67164a013d63a37624581ed776582329537a315 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 17 Dec 2024 11:18:21 +0000 Subject: [PATCH 003/148] SetFileInformationByHandle hook --- hook_file.c | 34 ++++++++++++++++++++++++++++++++-- hooks.c | 1 + hooks.h | 7 +++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/hook_file.c b/hook_file.c index 032e1027..409acc3d 100644 --- a/hook_file.c +++ b/hook_file.c @@ -728,8 +728,10 @@ HOOKDEF(NTSTATUS, WINAPI, NtQueryAttributesFile, __out PFILE_BASIC_INFORMATION FileInformation ) { NTSTATUS ret = Old_NtQueryAttributesFile(ObjectAttributes, FileInformation); - - LOQ_ntstatus("filesystem", "O", "FileName", ObjectAttributes); + if (ObjectAttributes) + LOQ_ntstatus("filesystem", "O", "FileName", ObjectAttributes); + else + LOQ_ntstatus("filesystem", ""); return ret; } @@ -1494,6 +1496,34 @@ HOOKDEF(BOOL, WINAPI, GetVolumeInformationByHandleW, return ret; } +HOOKDEF(BOOL, WINAPI, SetFileInformationByHandle, + _In_ HANDLE hFile, + _In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + _In_ LPVOID lpFileInformation, + _In_ DWORD dwBufferSize +) { + if (FileInformationClass == FileDispositionInfo && dropped_count < g_config.dropped_limit) { + wchar_t *fname = calloc(32768, sizeof(wchar_t)); + wchar_t *path = calloc(32768, sizeof(wchar_t)); + + path_from_handle(hFile, fname, 32768); + ensure_absolute_unicode_path(path, fname); +#ifdef DEBUG_COMMENTS + DebugOutput("SetFileInformationByHandle: FILE_DEL %ws\n", path); +#endif + unsigned int len = lstrlenW(path); + pipe("FILE_DEL:%S", len, path); + dropped_count++; + + free(fname); + free(path); + } + + BOOL ret = Old_SetFileInformationByHandle(hFile, FileInformationClass, lpFileInformation, dwBufferSize); + + return ret; +} + HOOKDEF(HRESULT, WINAPI, SHGetFolderPathW, _In_ HWND hwndOwner, _In_ int nFolder, diff --git a/hooks.c b/hooks.c index f215184b..9f8a2c34 100644 --- a/hooks.c +++ b/hooks.c @@ -193,6 +193,7 @@ hook_t full_hooks[] = { HOOK(kernel32, GetDiskFreeSpaceW), HOOK(kernel32, GetVolumeNameForVolumeMountPointW), HOOK(kernel32, GetVolumeInformationByHandleW), + HOOK(kernel32, SetFileInformationByHandle), HOOK(shell32, SHGetFolderPathW), HOOK(shell32, SHGetKnownFolderPath), HOOK(shell32, SHGetFileInfoW), diff --git a/hooks.h b/hooks.h index 6887c986..0e46e895 100644 --- a/hooks.h +++ b/hooks.h @@ -412,6 +412,13 @@ HOOKDEF(BOOL, WINAPI, GetVolumeInformationByHandleW, _In_ DWORD nFileSystemNameSize ); +HOOKDEF(BOOL, WINAPI, SetFileInformationByHandle, + _In_ HANDLE hFile, + _In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + _In_ LPVOID lpFileInformation, + _In_ DWORD dwBufferSize +); + HOOKDEF(DWORD, WINAPI, RmStartSession, __out DWORD* pSessionHandle, DWORD dwSessionFlags, From 0f8eb924b146bde7ccd0a5edf36e53c10ebc5f53 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 17 Dec 2024 11:21:47 +0000 Subject: [PATCH 004/148] GetComputerNameExW hook: add fake results for all NameTypes (e.g. 8056b8ff55c452cc87e35d69928cccbcfc5af848db1abb4fe0364510986e068b) --- hook_misc.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/hook_misc.c b/hook_misc.c index 01ab3b28..32925961 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -625,7 +625,25 @@ HOOKDEF(BOOL, WINAPI, GetComputerNameExW, __out LPWSTR lpBuffer, __out LPDWORD nSize ) { + const wchar_t* ComputerNames[ComputerNameMax] = { + L"NETBIOS", + L"hostname", + L"domain", + L"fully.qualified.name", + L"PHYSICAL-NETBIOS", + L"physical-hostname", + L"physical-domain", + L"physical.fqdn" + }; + DWORD bufsize = 0; + if (nSize && *nSize) + bufsize = *nSize; BOOL ret = Old_GetComputerNameExW(NameType, lpBuffer, nSize); + if (ret && nSize && !*nSize && NameType < ComputerNameMax && wcslen(ComputerNames[NameType]) < bufsize) { + bufsize = (DWORD)wcslen(ComputerNames[NameType]); + wcsncpy(lpBuffer, ComputerNames[NameType], bufsize + 1); + *nSize = bufsize; + } LOQ_bool("misc", "u", "ComputerName", lpBuffer); return ret; } From f9183ca74c4dbffbfc05d80980a27d511e9b1f21 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 17 Dec 2024 11:29:34 +0000 Subject: [PATCH 005/148] ntapi.h: expanded SYSTEM_PROCESS_INFORMATION struct definition --- ntapi.h | 53 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/ntapi.h b/ntapi.h index e1049448..69eed2ae 100644 --- a/ntapi.h +++ b/ntapi.h @@ -233,23 +233,42 @@ typedef struct _SYSTEM_THREAD { } SYSTEM_THREAD, *PSYSTEM_THREAD; typedef struct _SYSTEM_PROCESS_INFORMATION { - ULONG NextEntryOffset; - ULONG NumberOfThreads; - LARGE_INTEGER Reserved[3]; - LARGE_INTEGER CreateTime; - LARGE_INTEGER UserTime; - LARGE_INTEGER KernelTime; - UNICODE_STRING ImageName; - KPRIORITY BasePriority; - HANDLE UniqueProcessId; - HANDLE InheritedFromProcessId; - ULONG HandleCount; - BYTE Reserved4[4]; - PVOID Reserved5[11]; - SIZE_T PeakPagefileUsage; - SIZE_T PrivatePageCount; - LARGE_INTEGER Reserved6[6]; - SYSTEM_THREAD Threads[0]; + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER WorkingSetPrivateSize; + ULONG HardFaultCount; + ULONG NumberOfThreadsHighWatermark; + ULONGLONG CycleTime; + LARGE_INTEGER CreateTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER KernelTime; + UNICODE_STRING ImageName; + KPRIORITY BasePriority; + HANDLE UniqueProcessId; + HANDLE InheritedFromUniqueProcessId; + ULONG HandleCount; + ULONG SessionId; + ULONG_PTR UniqueProcessKey; + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER ReadOperationCount; + LARGE_INTEGER WriteOperationCount; + LARGE_INTEGER OtherOperationCount; + LARGE_INTEGER ReadTransferCount; + LARGE_INTEGER WriteTransferCount; + LARGE_INTEGER OtherTransferCount; + LARGE_INTEGER Reserved6[6]; + SYSTEM_THREAD Threads[0]; } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION { From 6013d265f1f1c47a0c3b24b74979711c1e4d3cf9 Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Mon, 17 Feb 2025 10:06:14 -0500 Subject: [PATCH 006/148] Initial commit for WMI hooks --- capemon.vcxproj | 1 + capemon.vcxproj.filters | 3 + hook_wmi.c | 110 +++++++++++++++ hooking_32.c | 24 ++++ hooking_64.c | 12 ++ hooks.c | 16 +++ hooks.h | 68 +++++++++ log.c | 130 +++++++++++++++++ log.h | 1 + misc.c | 302 ++++++++++++++++++++++++++++++++++++++++ misc.h | 7 + 11 files changed, 674 insertions(+) create mode 100644 hook_wmi.c diff --git a/capemon.vcxproj b/capemon.vcxproj index b3a3b5cf..1770c601 100644 --- a/capemon.vcxproj +++ b/capemon.vcxproj @@ -266,6 +266,7 @@ + diff --git a/capemon.vcxproj.filters b/capemon.vcxproj.filters index efeb5826..e54f9002 100644 --- a/capemon.vcxproj.filters +++ b/capemon.vcxproj.filters @@ -303,6 +303,9 @@ Source Files + + Source Files + diff --git a/hook_wmi.c b/hook_wmi.c new file mode 100644 index 00000000..68d39a52 --- /dev/null +++ b/hook_wmi.c @@ -0,0 +1,110 @@ +#include "log.h" +#include "misc.h" + + +HOOKDEF(HRESULT, WINAPI, WMI_Get, + PVOID _this, + LPCWSTR wszName, + LONG lFlags, + VARIANT* pVal, + LONG* pType, + LONG* plFlavor +) { + HRESULT ret; + lasterror_t lasterror; + + ret = Old_WMI_Get(_this, wszName, lFlags, pVal, pType, plFlavor); + get_lasterrors(&lasterror); + __try { + if (wcscmp(wszName, L"__GENUS") && wcscmp(wszName, L"__PATH") && wcscmp(wszName, L"__RELPATH") && wcscmp(wszName, L"__SUPERCLASS") && wcscmp(wszName, L"SECURITY_DESCRIPTOR") && wcscmp(wszName, L"__NAMESPACE") && wcscmp(wszName, L"__CLASS")) { + // Don't log spammy property names + LOQ_hresult("system", "un", "Name", wszName, "Value", pVal); + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + ; + } + + set_lasterrors(&lasterror); + return ret; +} + +HOOKDEF_NOTAIL(WINAPI, WMI_ExecQuery, + PVOID _this, + const BSTR strQueryLanguage, + const BSTR strQuery, + LONG lFlags, + PVOID pCtx, + PVOID* ppEnum +) { + HRESULT ret = 0; + LOQ_hresult("system", "u", "Query", strQuery); + return 0; +} + +HOOKDEF_NOTAIL(WINAPI, WMI_ExecQueryAsync, + PVOID _this, + const BSTR strQueryLanguage, + const BSTR strQuery, + long lFlags, + PVOID pCtx, + PVOID pResponseHandler +) { + HRESULT ret = 0; + LOQ_hresult("system", "u", "Query", strQuery); + return 0; +} + +HOOKDEF_NOTAIL(WINAPI, WMI_ExecMethod, + PVOID _this, + const BSTR strObjectPath, + const BSTR strMethodName, + long lFlags, + PVOID pCtx, + PVOID pInParams, + PVOID* ppOutParams, + PVOID* ppCallResult +) { + HRESULT ret = 0; + LOQ_hresult("system", "uu", "ObjectPath", strObjectPath, "MethodName", strMethodName); + return 0; +} + +HOOKDEF_NOTAIL(WINAPI, WMI_ExecMethodAsync, + PVOID _this, + const BSTR strObjectPath, + const BSTR strMethodName, + long lFlags, + PVOID pCtx, + PVOID pInParams, + PVOID pResponseHandler +) { + HRESULT ret = 0; + LOQ_hresult("system", "uu", "ObjectPath", strObjectPath, "MethodName", strMethodName); + return 0; +} + +HOOKDEF_NOTAIL(WINAPI, WMI_GetObject, + PVOID _this, + const BSTR strObjectPath, + long lFlags, + PVOID pCtx, + PVOID* ppObject, + PVOID* ppCallResult +) { + HRESULT ret = 0; + LOQ_hresult("system", "u", "ObjectPath", strObjectPath); + return 0; +} + +HOOKDEF_NOTAIL(WINAPI, WMI_GetObjectAsync, + PVOID _this, + const BSTR strObjectPath, + long lFlags, + PVOID pCtx, + PVOID pResultHandler +) { + HRESULT ret = 0; + LOQ_hresult("system", "u", "ObjectPath", strObjectPath); + return 0; +} \ No newline at end of file diff --git a/hooking_32.c b/hooking_32.c index d4c24c21..8b810a31 100644 --- a/hooking_32.c +++ b/hooking_32.c @@ -691,6 +691,30 @@ int hook_api(hook_t *h, int type) type = HOOK_JMP_DIRECT; addr = (unsigned char *)get_cdocument_write_addr(hmod); } + else if (!strcmp(h->funcname, "WMI_ExecQuery")) { + type = HOOK_JMP_DIRECT; + addr = (unsigned char*)get_wmi_execquery_addr(hmod); + } + else if (!strcmp(h->funcname, "WMI_ExecMethod")) { + type = HOOK_JMP_DIRECT; + addr = (unsigned char*)get_wmi_execmethod_addr(hmod); + } + else if (!strcmp(h->funcname, "WMI_ExecQueryAsync")) { + type = HOOK_JMP_DIRECT; + addr = (unsigned char*)get_wmi_execqueryasync_addr(hmod); + } + else if (!strcmp(h->funcname, "WMI_ExecMethodAsync")) { + type = HOOK_JMP_DIRECT; + addr = (unsigned char*)get_wmi_execmethodasync_addr(hmod); + } + else if (!strcmp(h->funcname, "WMI_GetObject")) { + type = HOOK_JMP_DIRECT; + addr = (unsigned char*)get_wmi_getobject_addr(hmod); + } + else if (!strcmp(h->funcname, "WMI_GetObjectAsync")) { + type = HOOK_JMP_DIRECT; + addr = (unsigned char*)get_wmi_getobjectasync_addr(hmod); + } else if (!wcscmp(h->library, L"combase")) { PVOID getprocaddr = (PVOID)GetProcAddress(hmod, h->funcname); addr = (unsigned char *)GetFunctionAddress(hmod, (PCHAR)h->funcname); diff --git a/hooking_64.c b/hooking_64.c index be189860..0a3aef2b 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -963,6 +963,18 @@ int hook_api(hook_t *h, int type) addr = (unsigned char *)get_olescript_parsescripttext_addr(hmod); else if (!strcmp(h->funcname, "CDocument_write")) addr = (unsigned char *)get_cdocument_write_addr(hmod); + else if (!strcmp(h->funcname, "WMI_ExecQuery")) + addr = (unsigned char*)get_wmi_execquery_addr(hmod); + else if (!strcmp(h->funcname, "WMI_ExecMethod")) + addr = (unsigned char*)get_wmi_execmethod_addr(hmod); + else if (!strcmp(h->funcname, "WMI_ExecQueryAsync")) + addr = (unsigned char*)get_wmi_execqueryasync_addr(hmod); + else if (!strcmp(h->funcname, "WMI_ExecMethodAsync")) + addr = (unsigned char*)get_wmi_execmethodasync_addr(hmod); + else if (!strcmp(h->funcname, "WMI_GetObject")) + addr = (unsigned char*)get_wmi_getobject_addr(hmod); + else if (!strcmp(h->funcname, "WMI_GetObjectAsync")) + addr = (unsigned char*)get_wmi_getobjectasync_addr(hmod); else if (!wcscmp(h->library, L"combase")) { PVOID getprocaddr = (PVOID)GetProcAddress(hmod, h->funcname); addr = (unsigned char *)GetFunctionAddress(hmod, (PCHAR)h->funcname); diff --git a/hooks.c b/hooks.c index 41324b14..8986f767 100644 --- a/hooks.c +++ b/hooks.c @@ -38,6 +38,9 @@ void disable_tail_call_optimization(void) #define HOOK(library, funcname) {L###library, #funcname, NULL, NULL, \ &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE} +#define HOOK_WITHNAME(library, funcname, mangled) {L###library, mangled, NULL, NULL, \ + &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE, 0, FALSE, FALSE, 0, 0} + #define HOOK_SPECIAL(library, funcname) {L###library, #funcname, NULL, NULL, \ &New_##funcname, (void **) &Old_##funcname, NULL, TRUE, FALSE, 0, FALSE} @@ -155,6 +158,19 @@ hook_t full_hooks[] = { HOOK_SPECIAL(combase, CoGetClassObject), HOOK_SPECIAL(combase, CoGetObject), + // WMI Hooks +#ifdef _WIN64 + HOOK_WITHNAME(fastprox, WMI_Get, "?Get@CWbemObject@@UEAAJPEBGJPEAUtagVARIANT@@PEAJ2@Z"), +#else + HOOK_WITHNAME(fastprox, WMI_Get, "?Get@CWbemObject@@UAGJPBGJPAUtagVARIANT@@PAJ2@Z"), +#endif + HOOK_NOTAIL(fastprox, WMI_ExecQuery, 6), + HOOK_NOTAIL(fastprox, WMI_ExecMethod, 8), + HOOK_NOTAIL(fastprox, WMI_ExecQueryAsync, 6), + HOOK_NOTAIL(fastprox, WMI_ExecMethodAsync, 7), + HOOK_NOTAIL(fastprox, WMI_GetObject, 6), + HOOK_NOTAIL(fastprox, WMI_GetObjectAsync, 5), + // File Hooks HOOK(ntdll, NtQueryAttributesFile), HOOK(ntdll, NtQueryFullAttributesFile), diff --git a/hooks.h b/hooks.h index 0e46e895..87fb5c09 100644 --- a/hooks.h +++ b/hooks.h @@ -1261,6 +1261,74 @@ HOOKDEF(HRESULT, WINAPI, CoGetObject, _Out_ LPVOID *ppv ); +// WMI Hooks +HOOKDEF(HRESULT, WINAPI, WMI_Get, + PVOID _this, + LPCWSTR wszName, + LONG lFlags, + VARIANT* pVal, + LONG* pType, + LONG* plFlavor +); + +HOOKDEF_NOTAIL(WINAPI, WMI_ExecQuery, + PVOID _this, + const BSTR strQueryLanguage, + const BSTR strQuery, + LONG lFlags, + PVOID pCtx, + PVOID* ppEnum +); + +HOOKDEF_NOTAIL(WINAPI, WMI_ExecQueryAsync, + PVOID _this, + const BSTR strQueryLanguage, + const BSTR strQuery, + LONG lFlags, + PVOID pCtx, + PVOID pResponseHandler +); + +HOOKDEF_NOTAIL(WINAPI, WMI_ExecMethod, + PVOID _this, + const BSTR strObjectPath, + const BSTR strMethodName, + long lFlags, + PVOID pCtx, + PVOID pInParams, + PVOID* ppOutParams, + PVOID* ppCallResult +); + +HOOKDEF_NOTAIL(WINAPI, WMI_ExecMethodAsync, + PVOID _this, + const BSTR strObjectPath, + const BSTR strMethodName, + long lFlags, + PVOID pCtx, + PVOID pInParams, + PVOID pResponseHandler +); + +HOOKDEF_NOTAIL(WINAPI, WMI_GetObject, + PVOID _this, + const BSTR strObjectPath, + LONG lFlags, + PVOID pCtx, + PVOID* ppObject, + PVOID* ppCallResult +); + +HOOKDEF_NOTAIL(WINAPI, WMI_GetObjectAsync, + PVOID _this, + const BSTR strObjectPath, + LONG lFlags, + PVOID pCtx, + PVOID pResultHandler +); + +// End of WMI Hooks + HOOKDEF(NTSTATUS, WINAPI, NtMapViewOfSection, __in HANDLE SectionHandle, __in HANDLE ProcessHandle, diff --git a/log.c b/log.c index 3f328cc4..d039fb94 100644 --- a/log.c +++ b/log.c @@ -290,6 +290,129 @@ static void log_wstring(const wchar_t *str, int length) free(utf8s); } +static void log_variant(VARIANT* var) { + char log_msg[32]; + __try { + switch (var->vt) { + case 74: + // Undocumented, likely internal Variant Type in vbscript engine + // Observed with: + // Return value (arg1) with VbsStrReverse + // Function argument (arg3) with VbsExecute + log_variant((VARIANT*)var->pvRecord); + break; + case 130: + log_wstring(var->bstrVal, -1); + break; + case VT_BSTR: + log_wstring(var->bstrVal, -1); + break; + case VT_BSTR | VT_BYREF: + log_wstring(var->pbstrVal ? *var->pbstrVal : NULL, -1); + break; + case VT_BOOL: + if (var->boolVal) + log_string("TRUE", 4); + else + log_string("FALSE", 5); + break; + case VT_BOOL | VT_BYREF: + if (*var->pboolVal) + log_string("TRUE", 4); + else + log_string("FALSE", 5); + break; + case VT_INT: + log_int32(var->intVal); + break; + case VT_INT | VT_BYREF: + log_int32(*var->pintVal); + break; + case VT_UINT: + log_int32(var->uintVal); + break; + case VT_UINT | VT_BYREF: + log_int32(*var->puintVal); + break; + case VT_I8: + log_int64(var->llVal); + break; + case VT_I8 | VT_BYREF: + log_int64(*var->pllVal); + break; + case VT_UI8: + log_int64(var->ullVal); + break; + case VT_UI8 | VT_BYREF: + log_int64(*var->pullVal); + break; + case VT_I4: + log_int32(var->lVal); + break; + case VT_I4 | VT_BYREF: + log_int32(*var->plVal); + break; + case VT_UI4: + log_int32(var->ulVal); + break; + case VT_UI4 | VT_BYREF: + log_int32(*var->pulVal); + break; + case VT_I2: + log_int32(var->iVal); + break; + case VT_I2 | VT_BYREF: + log_int32(*var->piVal); + break; + case VT_UI2: + log_int32(var->uiVal); + break; + case VT_UI2 | VT_BYREF: + log_int32(*var->puiVal); + break; + case VT_I1: + log_int32(var->cVal); + break; + case VT_I1 | VT_BYREF: + log_int32(*var->pcVal); + break; + case VT_UI1: + log_int32(var->bVal); + break; + case VT_UI1 | VT_BYREF: + log_int32(*var->pbVal); + break; + case VT_VARIANT: + log_variant(var->pvarVal); + break; + case VT_VARIANT | VT_BYREF: + log_variant(var->pvarVal); + break; + case VT_DATE: + // Note: Maybe convert to a datestamp? + log_int64((int64_t)var->date); + break; + case VT_DATE | VT_BYREF: + // Note: Maybe convert to a datestamp string? + log_int64((int64_t)*var->pdate); + break; + case VT_R8: + log_int64((int64_t)var->dblVal); + break; + case VT_R8 | VT_BYREF: + log_int64((int64_t)*var->pdblVal); + break; + default: + snprintf(log_msg, 32, "Unhandled VARIANT Type: %hu", var->vt); + log_string((const char*)var->vt, -1); + break; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + log_string("", 0); + } +} + static void log_argv(int argc, const char ** argv) { int i; @@ -482,6 +605,9 @@ void loq(int index, const char *category, const char *name, else if (key == 'l' || key == 'L') { (void)va_arg(args, ULONG_PTR); } + else if (key == 'n') { + (void)va_arg(args, VARIANT *); + } else if (key == 'p' || key == 'P') { (void)va_arg(args, void *); } @@ -664,6 +790,10 @@ void loq(int index, const char *category, const char *name, } log_ptr(theptr); } + else if (key == 'n') { + VARIANT* s = va_arg(args, VARIANT*); + log_variant(s); + } else if (key == 'x') { LARGE_INTEGER value = va_arg(args, LARGE_INTEGER); log_int64(value.QuadPart); diff --git a/log.h b/log.h index 4f4cc936..98c5bf63 100644 --- a/log.h +++ b/log.h @@ -33,6 +33,7 @@ along with this program. If not, see . // i -> (int) -> integer // l -> (long) -> long integer // L -> (long *) -> pointer to a long integer +// n -> (VARIANT *) -> logs the value appropiately based on the variant type // p -> (void *) -> pointer (alias for l) // P -> (void **) -> pointer to a handle (alias for L) // o -> (UNICODE_STRING *) -> unicode string diff --git a/misc.c b/misc.c index e62a84e0..0a12ff14 100644 --- a/misc.c +++ b/misc.c @@ -2167,6 +2167,308 @@ ULONG_PTR get_vbscript_addr(HMODULE mod, const char * function) return 0; } +ULONG_PTR get_wmi_execquery_addr(HMODULE mod) +{ + if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) + { +#ifndef _WIN64 + return ((ULONG_PTR)(mod)+0xC7E0); +#else + return ((ULONG_PTR)(mod)+0xD7A0); +#endif + } + else + { + PUCHAR start, end; + PUCHAR addr, addr2; + + if (!get_section_bounds(mod, ".text", &start, &end)) + return 0; + +#ifndef _WIN64 + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x8b\x40\x10\x57\x50\x8b\x30\x8b\x4e\x50", 10); + } + else { + addr = find_string_in_bounds(start, end, "\xff\x75\x1c\x8b\x46\x04\xff\x75\x18\x8b\x40\x10\xff\x75\x14\x8b\x08\x57\xff\x75\x08\x50\xff\x51\x50\x8b\xd8", 27); + } +#else + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x98\xa0\x00\x00\x00", 18); + } + else { + addr = find_string_in_bounds(start, end, "\x45\x8b\xcd\x4c\x8b\xc7\x48\x8b\xd6\x41\xff\x92\xa0\x00\x00\x00", 16); + } +#endif + if (addr == NULL) + return 0; + + addr2 = addr; + + while (addr > addr2 - 256) { + if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) + return (ULONG_PTR)addr + 4; + addr--; + } + } + + return 0; +} + +ULONG_PTR get_wmi_execmethod_addr(HMODULE mod) +{ + if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) + { +#ifndef _WIN64 + return ((ULONG_PTR)(mod)+0x88B10); +#else + return ((ULONG_PTR)(mod)+0xA30C0); +#endif + } + else + { + PUCHAR start, end; + PUCHAR addr, addr2; + + if (!get_section_bounds(mod, ".text", &start, &end)) + return 0; + +#ifndef _WIN64 + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x8b\x40\x10\x53\x57\x50\x8b\x30\x8b\x4e\x60", 11); + } + else { + addr = find_string_in_bounds(start, end, "\xff\x75\x18\xff\x75\x14\x57\xff\x75\x08\x50\xff\x51\x60\x8b\xd8", 16); + } +#else + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x98\xc0\x00\x00\x00", 18); + } + else { + addr = find_string_in_bounds(start, end, "\x48\x89\x44\x24\x20\x45\x8b\xcc\x4c\x8b\xc7\x48\x8b\xd6\x41\xff\x92\xc0\x00\x00\x00\x8b\xd8", 23); + } +#endif + if (addr == NULL) + return 0; + + addr2 = addr; + + while (addr > addr2 - 256) { + if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) + return (ULONG_PTR)addr + 4; + addr--; + } + } + + return 0; +} + +ULONG_PTR get_wmi_execqueryasync_addr(HMODULE mod) +{ + if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) + { +#ifndef _WIN64 + return ((ULONG_PTR)(mod)+0x9FB0); +#else + return ((ULONG_PTR)(mod)+0xD920); +#endif + } + else + { + PUCHAR start, end; + PUCHAR addr, addr2; + + if (!get_section_bounds(mod, ".text", &start, &end)) + return 0; + +#ifndef _WIN64 + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x8b\x40\x10\x57\x50\x8b\x30\x8b\x4e\x54", 10); + } + else { + addr = find_string_in_bounds(start, end, "\xff\x75\x14\x8b\x08\x53\xff\x75\x08\x50\xff\x51\x54\x8b\xf8", 15); + } +#else + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x98\xa8\x00\x00\x00", 18); + } + else { + addr = find_string_in_bounds(start, end, "\x45\x8b\xcd\x4c\x8b\xc7\x48\x8b\xd6\x41\xff\x92\xa8\x00\x00\x00\x8b\xd8", 18); + } +#endif + if (addr == NULL) + return 0; + + addr2 = addr; + + while (addr > addr2 - 256) { + if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) + return (ULONG_PTR)addr + 4; + addr--; + } + } + + return 0; +} + +ULONG_PTR get_wmi_execmethodasync_addr(HMODULE mod) +{ + if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) + { +#ifndef _WIN64 + return ((ULONG_PTR)(mod)+0x88C50); +#else + return ((ULONG_PTR)(mod)+0xA32C0); +#endif + } + else + { + PUCHAR start, end; + PUCHAR addr, addr2; + + if (!get_section_bounds(mod, ".text", &start, &end)) + return 0; + +#ifndef _WIN64 + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x53\x8b\x40\x10\x57\x50\x8b\x30\x8b\x4e\x64", 11); + } + else { + addr = find_string_in_bounds(start, end, "\xff\x75\x18\x8b\x08\xff\x75\x14\x53\xff\x75\x08\x50\xff\x51\x64\x8b\xf8", 18); + } +#else + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x98\xc8\x00\x00\x00", 18); + } + else { + addr = find_string_in_bounds(start, end, "\x45\x8b\xcd\x4c\x8b\xc7\x48\x8b\xd6\x41\xff\x92\xc8\x00\x00\x00\x8b\xd8", 18); + } +#endif + if (addr == NULL) + return 0; + + addr2 = addr; + + while (addr > addr2 - 256) { + if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) + return (ULONG_PTR)addr + 4; + addr--; + } + } + + return 0; +} + +ULONG_PTR get_wmi_getobject_addr(HMODULE mod) +{ + if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) + { +#ifndef _WIN64 + return ((ULONG_PTR)(mod)+0xC940); +#else + return ((ULONG_PTR)(mod)+0x2ED50); +#endif + } + else + { + PUCHAR start, end; + PUCHAR addr, addr2; + + if (!get_section_bounds(mod, ".text", &start, &end)) + return 0; + +#ifndef _WIN64 + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x53\x8b\x30\x50\x8b\x4e\x18", 7); + } + else { + addr = find_string_in_bounds(start, end, "\x51\xff\x75\x14\xff\x75\x10\x53\x50\xff\x52\x18\x8b\xf8", 14); + if (addr == NULL) // windows xp + addr = find_string_in_bounds(start, end, "\x51\xff\x75\x14\xff\x75\x10\x57\x50\xff\x52\x18\x89\x45\x0c", 15); + } +#else + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x58\x30", 15); + } + else { + addr = find_string_in_bounds(start, end, "\x4c\x8b\xcd\x45\x8b\xc4\x48\x8b\xd7\x41\xff\x52\x30\x8b\xd8", 15); + } +#endif + if (addr == NULL) + return 0; + + addr2 = addr; + + while (addr > addr2 - 256) { + if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) + return (ULONG_PTR)addr + 4; + addr--; + } + } + + return 0; +} + +ULONG_PTR get_wmi_getobjectasync_addr(HMODULE mod) +{ + if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) + { +#ifndef _WIN64 + return ((ULONG_PTR)(mod)+0x88F00); +#else + return ((ULONG_PTR)(mod)+0xA36D0); +#endif + } + else + { + PUCHAR start, end; + PUCHAR addr, addr2; + + if (!get_section_bounds(mod, ".text", &start, &end)) + return 0; + +#ifndef _WIN64 + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x57\x8b\x30\x50\x8b\x4e\x1c", 7); + } + else { + addr = find_string_in_bounds(start, end, "\x8b\x08\x53\x50\xff\x51\x1c\x8b\xf8", 9); + } +#else + if (g_osverinfo.dwMajorVersion == 10) { + // on windows 10 the code is different due to CFG checking + addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x58\x38", 15); + } + else { + addr = find_string_in_bounds(start, end, "\x4d\x8b\xcc\x45\x8b\xc5\x48\x8b\xd7\xff\x50\x38\x8b\xd8", 14); + } +#endif + if (addr == NULL) + return 0; + + addr2 = addr; + + while (addr > addr2 - 256) { + if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) + return (ULONG_PTR)addr + 4; + addr--; + } + } + + return 0; +} + typedef struct _DLL_NOTIFICATION_STRUCT { struct _DLL_NOTIFICATION_STRUCT *Next; DWORD Unused; diff --git a/misc.h b/misc.h index 3b342df9..52e6265a 100644 --- a/misc.h +++ b/misc.h @@ -237,6 +237,13 @@ ULONG_PTR get_olescript_compile_addr(HMODULE mod); ULONG_PTR get_olescript_parsescripttext_addr(HMODULE mod); ULONG_PTR get_vbscript_addr(HMODULE mod, const char * function); +ULONG_PTR get_wmi_execquery_addr(HMODULE mod); +ULONG_PTR get_wmi_execmethod_addr(HMODULE mod); +ULONG_PTR get_wmi_execqueryasync_addr(HMODULE mod); +ULONG_PTR get_wmi_execmethodasync_addr(HMODULE mod); +ULONG_PTR get_wmi_getobject_addr(HMODULE mod); +ULONG_PTR get_wmi_getobjectasync_addr(HMODULE mod); + BOOL is_bytes_in_buf(PCHAR buf, ULONG len, PCHAR memstr, ULONG memlen, ULONG maxsearchbytes); void replace_string_in_buf(PCHAR buf, ULONG len, PCHAR findstr, PCHAR repstr); void replace_wstring_in_buf(PWCHAR buf, ULONG len, PWCHAR findstr, PWCHAR repstr); From 5e5f9a00dc9f6e4164c1f726545e90b8b1121353 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 20 Feb 2025 15:14:25 +0000 Subject: [PATCH 007/148] Perform WMI function lookups via yara --- CAPE/CAPE.c | 6 + CAPE/YaraHarness.c | 20 ++- hook_wmi.c | 34 ++--- hooking_32.c | 24 ---- hooking_64.c | 12 -- hooks.c | 2 +- misc.c | 302 --------------------------------------------- misc.h | 7 -- 8 files changed, 38 insertions(+), 369 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 78457f26..4f383b05 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -698,6 +698,12 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) const char *YaraFunctions[] = { "LdrpCallInitRoutine", + "WMI_ExecQuery", + "WMI_ExecMethod", + "WMI_ExecQueryAsync", + "WMI_ExecMethodAsync", + "WMI_GetObject", + "WMI_GetObjectAsync", }; for (int i = 0; i < sizeof(YaraFunctions) / sizeof(YaraFunctions[0]); i++) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index f89442df..e18411f2 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -51,6 +51,24 @@ char InternalYara[] = "rule LdrpCallInitRoutine" "{strings:$function = {55 8B EC 56 57 53 8B F4 [0-2] FF 75 14 FF 75 10 FF 75 0C FF 55 08 8B E6 5B 5F 5E 5D C2 10 00}" "condition:uint16(0) == 0x5a4d and any of them}" + "rule WMI_ExecQuery" + "{strings:$function = {4C 8B DC 56 57 41 54 41 56 41 57 48 83 EC 60 49 C7 43 B8 FE FF FF FF 49 89 5B 10 49 89 6B 18 45 8B E1 4D 8B F0 4C 8B F9 48 8B 41 08 48 83 78 20 00 0F 84}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule WMI_ExecMethod" + "{strings:$function = {48 8B C4 56 57 41 54 41 56 41 57 48 83 EC 70 48 C7 40 B8 FE FF FF FF 48 89 58 10 48 89 68 18 45 8B E1 4D 8B F0 48 8B EA 4C 8B F9 48 8B 41 08 48 83 78 20 00 75 0A B8 08 01 01 80 E9}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule WMI_ExecQueryAsync" + "{strings:$function = {4C 8B DC 56 57 41 54 41 56 41 57 48 83 EC 60 49 C7 43 B8 FE FF FF FF 49 89 5B 10 49 89 6B 18 45 8B E1 4D 8B F0 48 8B E9 48 8B 41 08 48 83 78 20 00 0F 84 [4] 49 83 63 08 00 4D 8D 43 08 E8}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule WMI_ExecMethodAsync" + "{strings:$function = {48 8B C4 57 41 54 41 55 41 56 41 57 48 83 EC 60 48 C7 40 B8 FE FF FF FF 48 89 58 10 48 89 68 18 48 89 70 20 45 8B E9 4D 8B F8 4C 8B F2 48 8B E9 48 8B 41 08 48 83 78 20 00 75 0A B8 08 01 01 80 E9}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule WMI_GetObject" + "{strings:$function = {4C 8B DC 56 57 41 54 41 56 41 57 48 83 EC 50 49 C7 43 ?? FE FF FF FF 49 89 5B 10 49 89 6B 18 4D 8B F9 45 8B E0 48 8B EA 4C 8B F1 48 8B 41 08 48 83 78 20 00 0F 84 12 AB 02 00 49 83 63 08 00 4D 8D 43 08 E8}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule WMI_GetObjectAsync" + "{strings:$function = {48 8B C4 56 57 41 54 41 56 41 57 48 83 EC 40 48 C7 40 C8 FE FF FF FF 48 89 58 10 48 89 68 18 4D 8B F9 45 8B E0 48 8B EA 48 8B F1 48 8B 41 08 48 83 78 20 00 75 0A B8 08 01 01 80 E9}" + "condition:uint16(0) == 0x5a4d and any of them}" "rule capemon" "{strings:$hash = {d3 b9 46 1d 9a 14 bc 44 a1 61 c3 47 6a 0e 35 90 00 2c 28 81 dc a0 36 dc 2c 92 0c 7c b6 84 39 59}" "condition:all of them}"; @@ -361,7 +379,7 @@ PVOID GetAddressByYara(HMODULE ModuleBase, PCHAR FunctionName) return NULL; #ifdef DEBUG_COMMENTS - DebugOutput("GetAddressByYara: %s found at 0x%x", FunctionName, (ULONG_PTR)ModuleBase + (ULONG_PTR)AddressInfo.Address); + DebugOutput("GetAddressByYara: %s found at 0x%p", FunctionName, (ULONG_PTR)ModuleBase + (ULONG_PTR)AddressInfo.Address); #endif return (PVOID)((ULONG_PTR)ModuleBase + (ULONG_PTR)AddressInfo.Address); diff --git a/hook_wmi.c b/hook_wmi.c index 68d39a52..44b9b5fc 100644 --- a/hook_wmi.c +++ b/hook_wmi.c @@ -1,7 +1,6 @@ #include "log.h" #include "misc.h" - HOOKDEF(HRESULT, WINAPI, WMI_Get, PVOID _this, LPCWSTR wszName, @@ -11,21 +10,9 @@ HOOKDEF(HRESULT, WINAPI, WMI_Get, LONG* plFlavor ) { HRESULT ret; - lasterror_t lasterror; - ret = Old_WMI_Get(_this, wszName, lFlags, pVal, pType, plFlavor); - get_lasterrors(&lasterror); - __try { - if (wcscmp(wszName, L"__GENUS") && wcscmp(wszName, L"__PATH") && wcscmp(wszName, L"__RELPATH") && wcscmp(wszName, L"__SUPERCLASS") && wcscmp(wszName, L"SECURITY_DESCRIPTOR") && wcscmp(wszName, L"__NAMESPACE") && wcscmp(wszName, L"__CLASS")) { - // Don't log spammy property names - LOQ_hresult("system", "un", "Name", wszName, "Value", pVal); - } - } - __except (EXCEPTION_EXECUTE_HANDLER) { - ; - } - - set_lasterrors(&lasterror); + LOQ_hresult("system", "u", "Name", wszName); + //LOQ_hresult("system", "un", "Name", wszName, "Value", pVal); return ret; } @@ -85,15 +72,18 @@ HOOKDEF_NOTAIL(WINAPI, WMI_ExecMethodAsync, } HOOKDEF_NOTAIL(WINAPI, WMI_GetObject, - PVOID _this, - const BSTR strObjectPath, - long lFlags, - PVOID pCtx, - PVOID* ppObject, - PVOID* ppCallResult + PVOID _this, + const BSTR strObjectPath, + long lFlags, + PVOID pCtx, + PVOID* ppObject, + PVOID* ppCallResult ) { HRESULT ret = 0; - LOQ_hresult("system", "u", "ObjectPath", strObjectPath); + if (strObjectPath && SysStringLen(strObjectPath) > 0) + LOQ_hresult("system", "u", "ObjectPath", strObjectPath); + else + LOQ_hresult("system", "u", "ObjectPath", L"[NULL or Empty]"); return 0; } diff --git a/hooking_32.c b/hooking_32.c index 8b810a31..d4c24c21 100644 --- a/hooking_32.c +++ b/hooking_32.c @@ -691,30 +691,6 @@ int hook_api(hook_t *h, int type) type = HOOK_JMP_DIRECT; addr = (unsigned char *)get_cdocument_write_addr(hmod); } - else if (!strcmp(h->funcname, "WMI_ExecQuery")) { - type = HOOK_JMP_DIRECT; - addr = (unsigned char*)get_wmi_execquery_addr(hmod); - } - else if (!strcmp(h->funcname, "WMI_ExecMethod")) { - type = HOOK_JMP_DIRECT; - addr = (unsigned char*)get_wmi_execmethod_addr(hmod); - } - else if (!strcmp(h->funcname, "WMI_ExecQueryAsync")) { - type = HOOK_JMP_DIRECT; - addr = (unsigned char*)get_wmi_execqueryasync_addr(hmod); - } - else if (!strcmp(h->funcname, "WMI_ExecMethodAsync")) { - type = HOOK_JMP_DIRECT; - addr = (unsigned char*)get_wmi_execmethodasync_addr(hmod); - } - else if (!strcmp(h->funcname, "WMI_GetObject")) { - type = HOOK_JMP_DIRECT; - addr = (unsigned char*)get_wmi_getobject_addr(hmod); - } - else if (!strcmp(h->funcname, "WMI_GetObjectAsync")) { - type = HOOK_JMP_DIRECT; - addr = (unsigned char*)get_wmi_getobjectasync_addr(hmod); - } else if (!wcscmp(h->library, L"combase")) { PVOID getprocaddr = (PVOID)GetProcAddress(hmod, h->funcname); addr = (unsigned char *)GetFunctionAddress(hmod, (PCHAR)h->funcname); diff --git a/hooking_64.c b/hooking_64.c index 0a3aef2b..be189860 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -963,18 +963,6 @@ int hook_api(hook_t *h, int type) addr = (unsigned char *)get_olescript_parsescripttext_addr(hmod); else if (!strcmp(h->funcname, "CDocument_write")) addr = (unsigned char *)get_cdocument_write_addr(hmod); - else if (!strcmp(h->funcname, "WMI_ExecQuery")) - addr = (unsigned char*)get_wmi_execquery_addr(hmod); - else if (!strcmp(h->funcname, "WMI_ExecMethod")) - addr = (unsigned char*)get_wmi_execmethod_addr(hmod); - else if (!strcmp(h->funcname, "WMI_ExecQueryAsync")) - addr = (unsigned char*)get_wmi_execqueryasync_addr(hmod); - else if (!strcmp(h->funcname, "WMI_ExecMethodAsync")) - addr = (unsigned char*)get_wmi_execmethodasync_addr(hmod); - else if (!strcmp(h->funcname, "WMI_GetObject")) - addr = (unsigned char*)get_wmi_getobject_addr(hmod); - else if (!strcmp(h->funcname, "WMI_GetObjectAsync")) - addr = (unsigned char*)get_wmi_getobjectasync_addr(hmod); else if (!wcscmp(h->library, L"combase")) { PVOID getprocaddr = (PVOID)GetProcAddress(hmod, h->funcname); addr = (unsigned char *)GetFunctionAddress(hmod, (PCHAR)h->funcname); diff --git a/hooks.c b/hooks.c index 8986f767..ebb5a0c0 100644 --- a/hooks.c +++ b/hooks.c @@ -39,7 +39,7 @@ void disable_tail_call_optimization(void) &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE} #define HOOK_WITHNAME(library, funcname, mangled) {L###library, mangled, NULL, NULL, \ - &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE, 0, FALSE, FALSE, 0, 0} + &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE} #define HOOK_SPECIAL(library, funcname) {L###library, #funcname, NULL, NULL, \ &New_##funcname, (void **) &Old_##funcname, NULL, TRUE, FALSE, 0, FALSE} diff --git a/misc.c b/misc.c index 0a12ff14..e62a84e0 100644 --- a/misc.c +++ b/misc.c @@ -2167,308 +2167,6 @@ ULONG_PTR get_vbscript_addr(HMODULE mod, const char * function) return 0; } -ULONG_PTR get_wmi_execquery_addr(HMODULE mod) -{ - if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) - { -#ifndef _WIN64 - return ((ULONG_PTR)(mod)+0xC7E0); -#else - return ((ULONG_PTR)(mod)+0xD7A0); -#endif - } - else - { - PUCHAR start, end; - PUCHAR addr, addr2; - - if (!get_section_bounds(mod, ".text", &start, &end)) - return 0; - -#ifndef _WIN64 - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x8b\x40\x10\x57\x50\x8b\x30\x8b\x4e\x50", 10); - } - else { - addr = find_string_in_bounds(start, end, "\xff\x75\x1c\x8b\x46\x04\xff\x75\x18\x8b\x40\x10\xff\x75\x14\x8b\x08\x57\xff\x75\x08\x50\xff\x51\x50\x8b\xd8", 27); - } -#else - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x98\xa0\x00\x00\x00", 18); - } - else { - addr = find_string_in_bounds(start, end, "\x45\x8b\xcd\x4c\x8b\xc7\x48\x8b\xd6\x41\xff\x92\xa0\x00\x00\x00", 16); - } -#endif - if (addr == NULL) - return 0; - - addr2 = addr; - - while (addr > addr2 - 256) { - if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) - return (ULONG_PTR)addr + 4; - addr--; - } - } - - return 0; -} - -ULONG_PTR get_wmi_execmethod_addr(HMODULE mod) -{ - if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) - { -#ifndef _WIN64 - return ((ULONG_PTR)(mod)+0x88B10); -#else - return ((ULONG_PTR)(mod)+0xA30C0); -#endif - } - else - { - PUCHAR start, end; - PUCHAR addr, addr2; - - if (!get_section_bounds(mod, ".text", &start, &end)) - return 0; - -#ifndef _WIN64 - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x8b\x40\x10\x53\x57\x50\x8b\x30\x8b\x4e\x60", 11); - } - else { - addr = find_string_in_bounds(start, end, "\xff\x75\x18\xff\x75\x14\x57\xff\x75\x08\x50\xff\x51\x60\x8b\xd8", 16); - } -#else - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x98\xc0\x00\x00\x00", 18); - } - else { - addr = find_string_in_bounds(start, end, "\x48\x89\x44\x24\x20\x45\x8b\xcc\x4c\x8b\xc7\x48\x8b\xd6\x41\xff\x92\xc0\x00\x00\x00\x8b\xd8", 23); - } -#endif - if (addr == NULL) - return 0; - - addr2 = addr; - - while (addr > addr2 - 256) { - if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) - return (ULONG_PTR)addr + 4; - addr--; - } - } - - return 0; -} - -ULONG_PTR get_wmi_execqueryasync_addr(HMODULE mod) -{ - if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) - { -#ifndef _WIN64 - return ((ULONG_PTR)(mod)+0x9FB0); -#else - return ((ULONG_PTR)(mod)+0xD920); -#endif - } - else - { - PUCHAR start, end; - PUCHAR addr, addr2; - - if (!get_section_bounds(mod, ".text", &start, &end)) - return 0; - -#ifndef _WIN64 - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x8b\x40\x10\x57\x50\x8b\x30\x8b\x4e\x54", 10); - } - else { - addr = find_string_in_bounds(start, end, "\xff\x75\x14\x8b\x08\x53\xff\x75\x08\x50\xff\x51\x54\x8b\xf8", 15); - } -#else - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x98\xa8\x00\x00\x00", 18); - } - else { - addr = find_string_in_bounds(start, end, "\x45\x8b\xcd\x4c\x8b\xc7\x48\x8b\xd6\x41\xff\x92\xa8\x00\x00\x00\x8b\xd8", 18); - } -#endif - if (addr == NULL) - return 0; - - addr2 = addr; - - while (addr > addr2 - 256) { - if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) - return (ULONG_PTR)addr + 4; - addr--; - } - } - - return 0; -} - -ULONG_PTR get_wmi_execmethodasync_addr(HMODULE mod) -{ - if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) - { -#ifndef _WIN64 - return ((ULONG_PTR)(mod)+0x88C50); -#else - return ((ULONG_PTR)(mod)+0xA32C0); -#endif - } - else - { - PUCHAR start, end; - PUCHAR addr, addr2; - - if (!get_section_bounds(mod, ".text", &start, &end)) - return 0; - -#ifndef _WIN64 - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x53\x8b\x40\x10\x57\x50\x8b\x30\x8b\x4e\x64", 11); - } - else { - addr = find_string_in_bounds(start, end, "\xff\x75\x18\x8b\x08\xff\x75\x14\x53\xff\x75\x08\x50\xff\x51\x64\x8b\xf8", 18); - } -#else - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x98\xc8\x00\x00\x00", 18); - } - else { - addr = find_string_in_bounds(start, end, "\x45\x8b\xcd\x4c\x8b\xc7\x48\x8b\xd6\x41\xff\x92\xc8\x00\x00\x00\x8b\xd8", 18); - } -#endif - if (addr == NULL) - return 0; - - addr2 = addr; - - while (addr > addr2 - 256) { - if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) - return (ULONG_PTR)addr + 4; - addr--; - } - } - - return 0; -} - -ULONG_PTR get_wmi_getobject_addr(HMODULE mod) -{ - if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) - { -#ifndef _WIN64 - return ((ULONG_PTR)(mod)+0xC940); -#else - return ((ULONG_PTR)(mod)+0x2ED50); -#endif - } - else - { - PUCHAR start, end; - PUCHAR addr, addr2; - - if (!get_section_bounds(mod, ".text", &start, &end)) - return 0; - -#ifndef _WIN64 - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x53\x8b\x30\x50\x8b\x4e\x18", 7); - } - else { - addr = find_string_in_bounds(start, end, "\x51\xff\x75\x14\xff\x75\x10\x53\x50\xff\x52\x18\x8b\xf8", 14); - if (addr == NULL) // windows xp - addr = find_string_in_bounds(start, end, "\x51\xff\x75\x14\xff\x75\x10\x57\x50\xff\x52\x18\x89\x45\x0c", 15); - } -#else - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x58\x30", 15); - } - else { - addr = find_string_in_bounds(start, end, "\x4c\x8b\xcd\x45\x8b\xc4\x48\x8b\xd7\x41\xff\x52\x30\x8b\xd8", 15); - } -#endif - if (addr == NULL) - return 0; - - addr2 = addr; - - while (addr > addr2 - 256) { - if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) - return (ULONG_PTR)addr + 4; - addr--; - } - } - - return 0; -} - -ULONG_PTR get_wmi_getobjectasync_addr(HMODULE mod) -{ - if (g_osverinfo.dwMajorVersion == 10 && g_osverinfo.dwBuildNumber == 19044) - { -#ifndef _WIN64 - return ((ULONG_PTR)(mod)+0x88F00); -#else - return ((ULONG_PTR)(mod)+0xA36D0); -#endif - } - else - { - PUCHAR start, end; - PUCHAR addr, addr2; - - if (!get_section_bounds(mod, ".text", &start, &end)) - return 0; - -#ifndef _WIN64 - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x57\x8b\x30\x50\x8b\x4e\x1c", 7); - } - else { - addr = find_string_in_bounds(start, end, "\x8b\x08\x53\x50\xff\x51\x1c\x8b\xf8", 9); - } -#else - if (g_osverinfo.dwMajorVersion == 10) { - // on windows 10 the code is different due to CFG checking - addr = find_string_in_bounds(start, end, "\x48\x8b\x47\x08\x48\x8b\x78\x20\x48\x8b\x07\x48\x8b\x58\x38", 15); - } - else { - addr = find_string_in_bounds(start, end, "\x4d\x8b\xcc\x45\x8b\xc5\x48\x8b\xd7\xff\x50\x38\x8b\xd8", 14); - } -#endif - if (addr == NULL) - return 0; - - addr2 = addr; - - while (addr > addr2 - 256) { - if (!memcmp(addr, "\x90\x90\x90\x90", 4) || !memcmp(addr, "\xcc\xcc\xcc\xcc", 4)) - return (ULONG_PTR)addr + 4; - addr--; - } - } - - return 0; -} - typedef struct _DLL_NOTIFICATION_STRUCT { struct _DLL_NOTIFICATION_STRUCT *Next; DWORD Unused; diff --git a/misc.h b/misc.h index 52e6265a..3b342df9 100644 --- a/misc.h +++ b/misc.h @@ -237,13 +237,6 @@ ULONG_PTR get_olescript_compile_addr(HMODULE mod); ULONG_PTR get_olescript_parsescripttext_addr(HMODULE mod); ULONG_PTR get_vbscript_addr(HMODULE mod, const char * function); -ULONG_PTR get_wmi_execquery_addr(HMODULE mod); -ULONG_PTR get_wmi_execmethod_addr(HMODULE mod); -ULONG_PTR get_wmi_execqueryasync_addr(HMODULE mod); -ULONG_PTR get_wmi_execmethodasync_addr(HMODULE mod); -ULONG_PTR get_wmi_getobject_addr(HMODULE mod); -ULONG_PTR get_wmi_getobjectasync_addr(HMODULE mod); - BOOL is_bytes_in_buf(PCHAR buf, ULONG len, PCHAR memstr, ULONG memlen, ULONG maxsearchbytes); void replace_string_in_buf(PCHAR buf, ULONG len, PCHAR findstr, PCHAR repstr); void replace_wstring_in_buf(PWCHAR buf, ULONG len, PWCHAR findstr, PWCHAR repstr); From a8ec18f7dc6d45dbeeb3447ebb1f4c7af3f89227 Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Wed, 9 Apr 2025 12:01:35 -0400 Subject: [PATCH 008/148] Hook CryptDuplicateKey Allows ability to properly track CryptKey handles (.NET often times operates on dupe key instead of original) --- hook_crypto.c | 23 ++++++++++++++++++----- hooks.h | 7 +++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/hook_crypto.c b/hook_crypto.c index 57e287c0..21e95411 100644 --- a/hook_crypto.c +++ b/hook_crypto.c @@ -278,12 +278,12 @@ HOOKDEF(BOOL, WINAPI, CryptDeriveKey, } HOOKDEF(BOOL, WINAPI, CryptExportKey, - _In_ HCRYPTKEY hKey, - _In_ HCRYPTKEY hExpKey, - _In_ DWORD dwBlobType, - _In_ DWORD dwFlags, + _In_ HCRYPTKEY hKey, + _In_ HCRYPTKEY hExpKey, + _In_ DWORD dwBlobType, + _In_ DWORD dwFlags, _Out_ BYTE *pbData, - _Inout_ DWORD *pdwDataLen + _Inout_ DWORD *pdwDataLen ) { BOOL ret = Old_CryptExportKey(hKey, hExpKey, dwBlobType, dwFlags, pbData, pdwDataLen); if (pbData && pdwDataLen) @@ -291,6 +291,19 @@ HOOKDEF(BOOL, WINAPI, CryptExportKey, return ret; } +HOOKDEF(BOOL, WINAPI, CryptDuplicateKey, + _In_ HCRYPTKEY hKey, + _In_ DWORD* pdwReserved, + _In_ DWORD dwFlags, + _Out_ HCRYPTKEY* phKey +) { + BOOL ret = Old_CryptDuplicateKey(hKey, pdwReserved, dwFlags, phKey); + if (ret) { + LOQ_bool("crypto", "pph", "OldCryptKey", hKey, "NewCryptKey", *phKey, "Flags", dwFlags); + } + return ret; +} + HOOKDEF(BOOL, WINAPI, CryptDestroyKey, _In_ HCRYPTKEY hKey ) { diff --git a/hooks.h b/hooks.h index 5a57d0da..d6ab4df9 100644 --- a/hooks.h +++ b/hooks.h @@ -2974,6 +2974,13 @@ HOOKDEF(BOOL, WINAPI, CryptExportKey, _Inout_ DWORD *pdwDataLen ); +HOOKDEF(BOOL, WINAPI, CryptDuplicateKey, + _In_ HCRYPTKEY hKey, + _In_ DWORD* pdwReserved, + _In_ DWORD dwFlags, + _Out_ HCRYPTKEY* phKey +); + HOOKDEF(BOOL, WINAPI, CryptDestroyKey, _In_ HCRYPTKEY hKey ); From 5e74ae4c4aa8ba2782ae7e5e94b73a6c0050ff6e Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 11 Apr 2025 13:58:32 +0100 Subject: [PATCH 009/148] Loader: add basic loader snaps capture ('snaps' parameter) --- loader/loader/Loader.c | 76 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/loader/loader/Loader.c b/loader/loader/Loader.c index 18f28f95..7782387b 100644 --- a/loader/loader/Loader.c +++ b/loader/loader/Loader.c @@ -1322,7 +1322,7 @@ int CreateMonitorPipe(char* Name, char* Dll) } if (ProcessId && ThreadId && ProcessId != LastPid) { - DebugOutput("About to call InjectDll on process %d, thread 5%d.\n", ProcessId, ThreadId); + DebugOutput("About to call InjectDll on process %d, thread %d.\n", ProcessId, ThreadId); if (InjectDll(ProcessId, ThreadId, Dll)) LastPid = ProcessId; } @@ -1334,6 +1334,28 @@ int CreateMonitorPipe(char* Name, char* Dll) } +void HandleDebugOutputString(const DEBUG_EVENT dbgEvent, HANDLE hProcess) +{ + char buffer[4096]; + SIZE_T read, size = min(sizeof(buffer) - 2, (SIZE_T)dbgEvent.u.DebugString.nDebugStringLength); + + ReadProcessMemory(hProcess, dbgEvent.u.DebugString.lpDebugStringData, buffer, size, &read); + + size = (int)(min(read, size)); + buffer[size] = 0; + buffer[size + 1] = 0; + + if (dbgEvent.u.DebugString.fUnicode) + DebugOutput("%ws", buffer); + else + { + wchar_t wbuffer[4096]; + int ret = MultiByteToWideChar(CP_ACP, 0, (char*)buffer, (int)size, wbuffer, ARRAYSIZE(wbuffer)); + if (ret != 0) + DebugOutput("%ws", wbuffer); + } +} + int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DebugOutput("CAPE loader.\n"); @@ -1425,7 +1447,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine return ret; } - else if (!strcmp(__argv[1], "load")) + else if (!strcmp(__argv[1], "load") || !strcmp(__argv[1], "snaps")) { // usage: loader.exe load DWORD ExplorerPid = 0, ProcessId = 0; @@ -1490,7 +1512,12 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine sie.lpAttributeList = pAttributeList; - if (!CreateProcess(__argv[3], szCommand, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &sie.StartupInfo, &pi)) + DWORD CreationFlags = CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED | EXTENDED_STARTUPINFO_PRESENT; + + if (!strcmp(__argv[1], "snaps")) + CreationFlags = CreationFlags | DEBUG_ONLY_THIS_PROCESS; + + if (!CreateProcess(__argv[3], szCommand, NULL, NULL, FALSE, CreationFlags, NULL, NULL, &sie.StartupInfo, &pi)) { ErrorOutput("Loader: CreateProcess error"); return 0; @@ -1525,6 +1552,49 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine { ResumeThread(ThreadHandle); CloseHandle(ThreadHandle); + if (!strcmp(__argv[1], "snaps")) + { + while (TRUE) + { + DEBUG_EVENT dbgEvent; + WaitForDebugEventEx(&dbgEvent, INFINITE); + switch (dbgEvent.dwDebugEventCode) + { + case EXCEPTION_DEBUG_EVENT: + DebugOutput("Exception event"); + break; + case CREATE_THREAD_DEBUG_EVENT: + DebugOutput("Thread created"); + break; + case CREATE_PROCESS_DEBUG_EVENT: + DebugOutput("Process created"); + break; + case EXIT_THREAD_DEBUG_EVENT: + DebugOutput("Thread exited"); + break; + case EXIT_PROCESS_DEBUG_EVENT: + DebugOutput("Process exited"); + break; + case LOAD_DLL_DEBUG_EVENT: + DebugOutput("Dll loaded"); + break; + case UNLOAD_DLL_DEBUG_EVENT: + DebugOutput("Dll unloaded"); + break; + case OUTPUT_DEBUG_STRING_EVENT: + HandleDebugOutputString(dbgEvent, pi.hProcess); + break; + case RIP_EVENT: + DebugOutput("RIP event"); + break; + } + + if (dbgEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) + break; + + ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); + } + } } else DebugOutput("There was a problem resuming the new process %s.\n", __argv[3]); From ee56e863cdec607ab043d33c7cd73ffbebeb1926 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 11 Apr 2025 17:18:01 +0100 Subject: [PATCH 010/148] Loader: enable loader snaps in remote process via NtGlobalFlags --- loader/loader/Loader.c | 49 ++++++++++++++++++++++++++++++++++++++---- loader/loader/Loader.h | 2 ++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/loader/loader/Loader.c b/loader/loader/Loader.c index 7782387b..76a610fd 100644 --- a/loader/loader/Loader.c +++ b/loader/loader/Loader.c @@ -255,7 +255,14 @@ int ReadConfig(DWORD ProcessId, char *DllName) return 1; } -BOOL GetProcessPeb(HANDLE ProcessHandle, PPEB Peb) +DWORD GetNtGlobalFlagsOffset() +{ + _RtlGetNtGlobalFlags pRtlGetNtGlobalFlags = (_RtlGetNtGlobalFlags)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlGetNtGlobalFlags"); + + return *(DWORD*)((PBYTE)pRtlGetNtGlobalFlags + 11); +} + +PVOID GetProcessPeb(HANDLE ProcessHandle, PPEB Peb) { _NtQueryInformationProcess pNtQueryInformationProcess; PROCESS_BASIC_INFORMATION ProcessBasicInformation; @@ -267,10 +274,10 @@ BOOL GetProcessPeb(HANDLE ProcessHandle, PPEB Peb) memset(&ProcessBasicInformation, 0, sizeof(ProcessBasicInformation)); if (pNtQueryInformationProcess(ProcessHandle, 0, &ProcessBasicInformation, sizeof(ProcessBasicInformation), &ulSize) >= 0 && ulSize == sizeof(ProcessBasicInformation)) - if (ReadProcessMemory(ProcessHandle, ProcessBasicInformation.PebBaseAddress, Peb, sizeof(PEB), &dwBytesRead)) - return TRUE; + if (!Peb || (Peb && ReadProcessMemory(ProcessHandle, ProcessBasicInformation.PebBaseAddress, Peb, sizeof(PEB), &dwBytesRead))) + return ProcessBasicInformation.PebBaseAddress; - return FALSE; + return NULL; } DWORD GetProcessInitialThreadId(HANDLE ProcessHandle) @@ -1334,6 +1341,32 @@ int CreateMonitorPipe(char* Name, char* Dll) } +BOOL EnableLoaderSnaps(HANDLE ProcessHandle, PPEB Peb) +{ + SIZE_T dwBytesRead, dwBytesWritten; + ULONG gflags = 0; + + PVOID pNtGlobalFlag = (PVOID)((PBYTE)Peb + GetNtGlobalFlagsOffset()); + + if (!ReadProcessMemory(ProcessHandle, pNtGlobalFlag, &gflags, sizeof(gflags), &dwBytesRead)) + { + ErrorOutput("Loader: ReadProcessMemory failed (ProcessParameters)"); + return FALSE; + } + + gflags |= 0x2; + + if (!WriteProcessMemory(ProcessHandle, pNtGlobalFlag, &gflags, sizeof(gflags), &dwBytesWritten)) + { + ErrorOutput("Loader: WriteProcessMemory failed (gflags)"); + return FALSE; + } + + DebugOutput("Loader: snaps enabled.\n"); + + return TRUE; +} + void HandleDebugOutputString(const DEBUG_EVENT dbgEvent, HANDLE hProcess) { char buffer[4096]; @@ -1539,6 +1572,14 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine else DebugOutput("Loader: Loaded config for process %d.\n", pi.dwProcessId); #endif + + if (!strcmp(__argv[1], "snaps")) + { + PPEB Peb = GetProcessPeb(pi.hProcess, NULL); + if (Peb) + EnableLoaderSnaps(pi.hProcess, Peb); + } + ret = InjectDll(pi.dwProcessId, pi.dwThreadId, __argv[2]); if (ret) diff --git a/loader/loader/Loader.h b/loader/loader/Loader.h index bd99167a..f52b301f 100644 --- a/loader/loader/Loader.h +++ b/loader/loader/Loader.h @@ -160,6 +160,8 @@ typedef ULONG(WINAPI * _RtlNtStatusToDosError) __in NTSTATUS Status ); +typedef ULONG (WINAPI *_RtlGetNtGlobalFlags)(void); + typedef HRESULT (WINAPI *PDLLREGRSRV)(void); typedef void (cdecl *PSHELLCODE)(void); From 706e973eae33bcb52a6f33135422106fa0154a26 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 11 Apr 2025 17:22:28 +0100 Subject: [PATCH 011/148] Loader: small correction in EnableLoaderSnaps debug output --- loader/loader/Loader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader/loader/Loader.c b/loader/loader/Loader.c index 76a610fd..795b36a5 100644 --- a/loader/loader/Loader.c +++ b/loader/loader/Loader.c @@ -1350,7 +1350,7 @@ BOOL EnableLoaderSnaps(HANDLE ProcessHandle, PPEB Peb) if (!ReadProcessMemory(ProcessHandle, pNtGlobalFlag, &gflags, sizeof(gflags), &dwBytesRead)) { - ErrorOutput("Loader: ReadProcessMemory failed (ProcessParameters)"); + ErrorOutput("Loader: ReadProcessMemory failed (NtGlobalFlag)"); return FALSE; } From 98afb82f9ac823c8f4f8aa10917d6f23bef660be Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 14 Apr 2025 15:45:16 +0100 Subject: [PATCH 012/148] Loader: make GetNtGlobalFlagsOffset() work on 32-bit as well as 64-bit --- loader/loader/Loader.c | 58 ++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/loader/loader/Loader.c b/loader/loader/Loader.c index 795b36a5..d4ae0378 100644 --- a/loader/loader/Loader.c +++ b/loader/loader/Loader.c @@ -259,7 +259,11 @@ DWORD GetNtGlobalFlagsOffset() { _RtlGetNtGlobalFlags pRtlGetNtGlobalFlags = (_RtlGetNtGlobalFlags)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlGetNtGlobalFlags"); - return *(DWORD*)((PBYTE)pRtlGetNtGlobalFlags + 11); +#ifdef _WIN64 + return (DWORD)*((PBYTE)pRtlGetNtGlobalFlags + 11); +#else + return (DWORD)*((PBYTE)pRtlGetNtGlobalFlags + 8); +#endif } PVOID GetProcessPeb(HANDLE ProcessHandle, PPEB Peb) @@ -333,6 +337,32 @@ DWORD GetProcessInitialThreadId(HANDLE ProcessHandle) return 0; } +static BOOL EnableLoaderSnaps(HANDLE ProcessHandle, PPEB Peb) +{ + SIZE_T dwBytesRead, dwBytesWritten; + ULONG gflags = 0; + + PVOID pNtGlobalFlag = (PVOID)((PBYTE)Peb + GetNtGlobalFlagsOffset()); + + if (!ReadProcessMemory(ProcessHandle, pNtGlobalFlag, &gflags, sizeof(gflags), &dwBytesRead)) + { + ErrorOutput("Loader: ReadProcessMemory failed (NtGlobalFlag)"); + return FALSE; + } + + gflags |= 0x2; + + if (!WriteProcessMemory(ProcessHandle, pNtGlobalFlag, &gflags, sizeof(gflags), &dwBytesWritten)) + { + ErrorOutput("Loader: WriteProcessMemory failed (NtGlobalFlag)"); + return FALSE; + } + + DebugOutput("Loader: snaps enabled.\n"); + + return TRUE; +} + static int GrantDebugPrivileges(void) { HANDLE Token = NULL; @@ -1341,32 +1371,6 @@ int CreateMonitorPipe(char* Name, char* Dll) } -BOOL EnableLoaderSnaps(HANDLE ProcessHandle, PPEB Peb) -{ - SIZE_T dwBytesRead, dwBytesWritten; - ULONG gflags = 0; - - PVOID pNtGlobalFlag = (PVOID)((PBYTE)Peb + GetNtGlobalFlagsOffset()); - - if (!ReadProcessMemory(ProcessHandle, pNtGlobalFlag, &gflags, sizeof(gflags), &dwBytesRead)) - { - ErrorOutput("Loader: ReadProcessMemory failed (NtGlobalFlag)"); - return FALSE; - } - - gflags |= 0x2; - - if (!WriteProcessMemory(ProcessHandle, pNtGlobalFlag, &gflags, sizeof(gflags), &dwBytesWritten)) - { - ErrorOutput("Loader: WriteProcessMemory failed (gflags)"); - return FALSE; - } - - DebugOutput("Loader: snaps enabled.\n"); - - return TRUE; -} - void HandleDebugOutputString(const DEBUG_EVENT dbgEvent, HANDLE hProcess) { char buffer[4096]; From 92b1fa7eecd1cf8eaac4137a24aadc51df2453f3 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 16 Apr 2025 07:30:49 +0100 Subject: [PATCH 013/148] Native hookset (ntdll only) option: native=1 --- config.c | 5 ++ config.h | 3 ++ hooks.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) diff --git a/config.c b/config.c index f9eb15ed..c31b1e89 100644 --- a/config.c +++ b/config.c @@ -1275,6 +1275,11 @@ void parse_config_line(char* line) if (g_config.zerohook) DebugOutput("All* hooks disabled (*except essential)\n"); } + else if (!stricmp(key, "native")) { + g_config.native = value[0] == '1'; + if (g_config.native) + DebugOutput("Native hooks only (ntdll)\n"); + } else if (!stricmp(key, "tlsdump")) { g_config.tlsdump = value[0] == '1'; if (g_config.tlsdump) { diff --git a/config.h b/config.h index ba57aeb4..1887c03b 100644 --- a/config.h +++ b/config.h @@ -222,6 +222,9 @@ struct _g_config { // Zero hook set int zerohook; + // Native hook set + int native; + // Microsoft Office hook set int office; diff --git a/hooks.c b/hooks.c index 811454b6..a440bcb9 100644 --- a/hooks.c +++ b/hooks.c @@ -745,6 +745,147 @@ hook_t full_hooks[] = { HOOK_SPECIAL(vbscript, VbsPrint), }; +hook_t native_hooks[] = { + + HOOK_NOTAIL_ALT(ntdll, RtlDispatchException, 2), + HOOK_NOTAIL(ntdll, NtRaiseException, 3), + HOOK_NOTAIL_ALT(ntdll, LdrLoadDll, 4), + HOOK_NOTAIL(ntdll, LdrUnloadDll, 1), + HOOK_SPECIAL(ntdll, NtCreateUserProcess), + + // File Hooks + HOOK(ntdll, NtQueryAttributesFile), + HOOK(ntdll, NtQueryFullAttributesFile), + HOOK(ntdll, NtCreateFile), + HOOK(ntdll, NtOpenFile), + HOOK(ntdll, NtReadFile), + HOOK(ntdll, NtWriteFile), + HOOK(ntdll, NtDeleteFile), + HOOK(ntdll, NtDeviceIoControlFile), + HOOK(ntdll, NtQueryDirectoryFile), + HOOK(ntdll, NtQueryInformationFile), + HOOK(ntdll, NtSetInformationFile), + HOOK(ntdll, NtOpenDirectoryObject), + HOOK(ntdll, NtCreateDirectoryObject), + HOOK(ntdll, NtQueryDirectoryObject), + + // Native Registry Hooks + HOOK(ntdll, NtCreateKey), + HOOK(ntdll, NtOpenKey), + HOOK(ntdll, NtOpenKeyEx), + HOOK(ntdll, NtRenameKey), + HOOK(ntdll, NtReplaceKey), + HOOK(ntdll, NtEnumerateKey), + HOOK(ntdll, NtEnumerateValueKey), + HOOK(ntdll, NtSetValueKey), + HOOK(ntdll, NtQueryValueKey), + HOOK(ntdll, NtQueryMultipleValueKey), + HOOK(ntdll, NtDeleteKey), + HOOK(ntdll, NtDeleteValueKey), + HOOK(ntdll, NtLoadKey), + HOOK(ntdll, NtLoadKey2), + HOOK(ntdll, NtLoadKeyEx), + HOOK(ntdll, NtQueryKey), + HOOK(ntdll, NtSaveKey), + HOOK(ntdll, NtSaveKeyEx), + + // Sync Hooks + HOOK(ntdll, NtCreateMutant), + HOOK(ntdll, NtOpenMutant), + HOOK(ntdll, NtReleaseMutant), + HOOK(ntdll, NtCreateEvent), + HOOK(ntdll, NtOpenEvent), + HOOK(ntdll, NtCreateNamedPipeFile), + HOOK(ntdll, NtAddAtom), + HOOK(ntdll, NtAddAtomEx), + HOOK(ntdll, NtFindAtom), + HOOK(ntdll, NtDeleteAtom), + HOOK(ntdll, NtQueryInformationAtom), + + // Process Hooks + HOOK(ntdll, NtAllocateVirtualMemory), + HOOK(ntdll, NtAllocateVirtualMemoryEx), + HOOK(ntdll, NtReadVirtualMemory), + HOOK(ntdll, NtWriteVirtualMemory), + HOOK(ntdll, NtWow64WriteVirtualMemory64), + HOOK(ntdll, NtWow64ReadVirtualMemory64), + HOOK(ntdll, NtProtectVirtualMemory), + HOOK(ntdll, NtFreeVirtualMemory), + HOOK(ntdll, NtCreateProcess), + HOOK(ntdll, NtCreateProcessEx), + HOOK(ntdll, RtlCreateUserProcess), + HOOK(ntdll, NtOpenProcess), + HOOK(ntdll, NtTerminateProcess), + HOOK(ntdll, RtlReportSilentProcessExit), + HOOK(ntdll, NtResumeProcess), + HOOK(ntdll, NtCreateSection), + HOOK(ntdll, NtDuplicateObject), + HOOK(ntdll, NtMakeTemporaryObject), + HOOK(ntdll, NtMakePermanentObject), + HOOK(ntdll, NtOpenSection), + HOOK(ntdll, NtMapViewOfSection), + HOOK(ntdll, NtMapViewOfSectionEx), + HOOK(ntdll, NtUnmapViewOfSection), + HOOK(ntdll, NtUnmapViewOfSectionEx), + HOOK(ntdll, NtOpenProcessToken), + HOOK(ntdll, NtQueryInformationToken), + HOOK(ntdll, DbgUiWaitStateChange), + + // Thread Hooks + HOOK(ntdll, NtCreateThread), + HOOK(ntdll, NtCreateThreadEx), + HOOK(ntdll, NtTerminateThread), + HOOK(ntdll, NtQueueApcThread), + HOOK(ntdll, NtQueueApcThreadEx), + HOOK(ntdll, NtOpenThread), + HOOK(ntdll, NtGetContextThread), + HOOK(ntdll, RtlWow64GetThreadContext), + HOOK(ntdll, NtSetContextThread), + HOOK(ntdll, NtSuspendThread), + HOOK(ntdll, NtResumeThread), + HOOK(ntdll, RtlCreateUserThread), + HOOK(ntdll, NtSetInformationThread), + HOOK(ntdll, NtQueryInformationThread), + HOOK(ntdll, NtYieldExecution), + HOOK(ntdll, NtContinue), + + // Misc Hooks + //HOOK(ntdll, RtlMoveMemory), + HOOK(ntdll, RtlAddVectoredExceptionHandler), + HOOK(ntdll, LdrGetDllHandle), + HOOK(ntdll, LdrGetProcedureAddress), + HOOK(ntdll, LdrGetProcedureAddressForCaller), + HOOK_NOTAIL(ntdll, NtShutdownSystem, 1), + HOOK_NOTAIL(ntdll, NtSetSystemPowerState, 3), + HOOK_NOTAIL(ntdll, NtRaiseHardError, 6), + HOOK(ntdll, NtClose), + HOOK(ntdll, NtLoadDriver), + HOOK(ntdll, NtSetInformationProcess), + //HOOK(ntdll, NtQueryInformationProcess), + HOOK(ntdll, RtlDecompressBuffer), + HOOK(ntdll, RtlCompressBuffer), + HOOK(ntdll, NtQuerySystemInformation), +#ifndef _WIN64 + HOOK(ntdll, RtlDosPathNameToNtPathName_U), + HOOK(ntdll, NtQueryLicenseValue), +#endif + + // transaction functions (for process doppelganging) + HOOK(ntdll, NtCreateTransaction), + HOOK(ntdll, NtOpenTransaction), + HOOK(ntdll, NtRollbackTransaction), + HOOK(ntdll, NtCommitTransaction), + HOOK(ntdll, RtlSetCurrentTransaction), + + // Sleep Hooks + HOOK(ntdll, NtQueryPerformanceCounter), + HOOK(ntdll, NtDelayExecution), + HOOK(ntdll, NtWaitForSingleObject), + HOOK_SPECIAL(ntdll, NtQuerySystemTime), + HOOK(ntdll, NtSetTimer), + HOOK(ntdll, NtSetTimerEx), +}; + // This hook set is intended to include only hooks which are necessary // to follow the execution chain with base functionality @@ -1595,6 +1736,11 @@ void set_hooks() hooks_size = sizeof(browser_hooks); hooks_arraysize = ARRAYSIZE(browser_hooks); } + else if (g_config.native) { + hooks = native_hooks; + hooks_size = sizeof(native_hooks); + hooks_arraysize = ARRAYSIZE(native_hooks); + } else { hooks = full_hooks; hooks_size = sizeof(full_hooks); From 1b6396dcdac133300c15b1be18f8591b8aef5dce Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 16 Apr 2025 11:35:13 +0100 Subject: [PATCH 014/148] Hooking: 64-bit push-ret hook-type (hook-type=push-ret), low (<2GB) trampoline allocation (hook-low=1) --- config.c | 30 +++++++++++--- config.h | 3 ++ hooking.h | 4 +- hooking_64.c | 110 ++++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 126 insertions(+), 21 deletions(-) diff --git a/config.c b/config.c index c31b1e89..7ed76b66 100644 --- a/config.c +++ b/config.c @@ -132,16 +132,35 @@ void parse_config_line(char* line) g_config.hook_range = atoi(value); DebugOutput("Config: hook range limit set to %d", g_config.hook_range); } - else if (!strcmp(key, "hook-type")) { //Valid for 32-bit analyses only. Specifies the hook type to use: direct, indirect, or safe. Safe attempts a Detours-style hook. + else if (!strcmp(key, "hook-type")) { + if (!strcmp(value, "indirect")) { + g_config.hook_type = HOOK_JMP_INDIRECT; + DebugOutput("Config: Indirect hooking selected.\n"); + } + else if (!strcmp(value, "pushret")) { + g_config.hook_type = HOOK_PUSH_RETN; + DebugOutput("Config: Push-ret hooking selected.\n"); + } #ifndef _WIN64 - if (!strcmp(value, "direct")) + else if (!strcmp(value, "direct")) { g_config.hook_type = HOOK_JMP_DIRECT; - else if (!strcmp(value, "indirect")) - g_config.hook_type = HOOK_JMP_INDIRECT; - else if (!strcmp(value, "safe")) + DebugOutput("Config: Direct hooking selected.\n"); + } + else if (!strcmp(value, "safe")) { g_config.hook_type = HOOK_SAFEST; + DebugOutput("Config: Safest hooking selected.\n"); + } #endif } +#ifdef _WIN64 + else if (!stricmp(key, "hook-low")) { + g_config.hook_low = value[0]; + if (g_config.hook_low) { + DebugOutput("Config: Hook 'low' enabled (trampoline address < 2GB)\n"); + g_config.hook_type = HOOK_PUSH_RETN; + } + } +#endif else if (!strcmp(key, "disable_hook_content")) { //Set to 1 to remove functionality of all hooks except those critical for monitoring other processes. Set to 2 to apply to all hooks. g_config.disable_hook_content = atoi(value); } @@ -1362,7 +1381,6 @@ int read_config(void) g_config.api_rate_cap = 1; g_config.yarascan = 1; g_config.loaderlock_scans = 1; - g_config.amsidump = 1; g_config.syscall = 1; StepLimit = SINGLE_STEP_LIMIT; diff --git a/config.h b/config.h index 1887c03b..82cacecd 100644 --- a/config.h +++ b/config.h @@ -104,6 +104,9 @@ struct _g_config { // Default hook type (may be overridden for specific functions) int hook_type; + // Hook trampoline allocated in low (<2GB) memory (64-bit) + int hook_low; + // Disable hook content int disable_hook_content; diff --git a/hooking.h b/hooking.h index 57c40e92..c04d7410 100644 --- a/hooking.h +++ b/hooking.h @@ -193,7 +193,9 @@ enum { #else enum { HOOK_NATIVE_JMP_INDIRECT, - HOOK_JMP_INDIRECT + HOOK_NATIVE_PUSH_RETN, + HOOK_JMP_INDIRECT, + HOOK_PUSH_RETN }; #endif diff --git a/hooking_64.c b/hooking_64.c index be189860..d41b9090 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -170,6 +170,48 @@ static int addr_is_in_range(ULONG_PTR addr, const unsigned char *buf, DWORD size return 0; } +static void retarget_relative_displacement(unsigned char **tramp, unsigned char **addr, _DInst *insn) +{ + unsigned short length = insn->size; + unsigned char *newtramp = *tramp; + unsigned char *newaddr = *addr; + + unsigned char offset = (unsigned char)(length - insn->imm_encoded_size - sizeof(int)); + ULONG_PTR target = (ULONG_PTR)(newaddr + length + *(int *)(newaddr + offset)); + int64_t rel = (int64_t)(target - (ULONG_PTR)(newtramp + length)); + + if (rel >= INT_MIN && rel <= INT_MAX) { + while (length-- != 0) + *newtramp++ = *newaddr++; + *(int64_t *)(newtramp - insn->imm_encoded_size - sizeof(int)) = rel; + } + else { + // mov r11, far target + *((WORD*)newtramp)++ = 0xBB49; + *((ULONG_PTR *)newtramp)++ = (ULONG_PTR)target; + if (*newaddr == 0xE8) { + // replace call near target with call far r11 + *((WORD*)newtramp)++ = 0xFF41; + *newtramp++ = 0xD3; + } + else if (*newaddr == 0xE9) { + // replace jmp near target with jmp far r11 + *((WORD*)newtramp)++ = 0xFF41; + *newtramp++ = 0xE3; + } + else if (insn->flags & FLAG_RIP_RELATIVE) { + if ((*newaddr & 0xF0) == 0x40) + *newtramp++ = *newaddr++ | 0x41; + *newtramp++ = *newaddr++; + *newtramp++ = (*newaddr++ & 0xF8) | 3; + } + newaddr += 4; + } + + *tramp = newtramp; + *addr = newaddr; +} + static void retarget_rip_relative_displacement(unsigned char **tramp, unsigned char **addr, _DInst *insn) { unsigned short length = insn->size; @@ -234,9 +276,8 @@ static int hook_create_trampoline(unsigned char *addr, int len, // addresses, otherwise we can simply copy the instruction to our // trampoline - if (addr[0] == 0xe8 || addr[0] == 0xe9 || (addr[0] == 0x0f && addr[1] >= 0x80 && addr[1] < 0x90) || - (insn->flags & FLAG_RIP_RELATIVE)) { - retarget_rip_relative_displacement(&tramp, &addr, insn); + if (addr[0] == 0xe8 || addr[0] == 0xe9 || (addr[0] == 0x0f && addr[1] >= 0x80 && addr[1] < 0x90) || (insn->flags & FLAG_RIP_RELATIVE)) { + retarget_relative_displacement(&tramp, &addr, insn); if (addr[0] == 0xe9 && len > 0) goto error; } @@ -272,9 +313,9 @@ static int hook_create_trampoline(unsigned char *addr, int len, } // append a jump from the trampoline to the original function - *tramp++ = 0xe9; - emit_rel(tramp, tramp, addr); - tramp += 4; + *((WORD*)tramp)++ = 0x25FF; + *((DWORD*)tramp)++ = 0; + *((uintptr_t*)tramp)++ = (uintptr_t)addr; // return the length of this trampoline return (int)(tramp - base); @@ -833,6 +874,25 @@ static int hook_api_native_jmp_indirect(hook_t *h, unsigned char *from, return hook_api_jmp_indirect(h, from, to); } +static int hook_api_push_retn(hook_t *h, unsigned char *from, unsigned char *to) +{ + // push addr + *from++ = 0x68; + *(DWORD *) from = (DWORD)(DWORD_PTR)to; + + // retn + from[4] = 0xc3; + + memcpy(h->hookdata->hook_data, &to, sizeof(to)); + return 0; +} + +static int hook_api_native_push_retn(hook_t *h, unsigned char *from, unsigned char *to) +{ + from += 8; + return hook_api_push_retn(h, from, to); +} + hook_data_t *alloc_hookdata_near(void *addr) { PVOID BaseAddress; @@ -853,6 +913,23 @@ hook_data_t *alloc_hookdata_near(void *addr) return NULL; } +hook_data_t *alloc_hookdata_low() +{ + PCHAR BaseAddress = (PCHAR)0x10000; + int offset = 0x10000; + SIZE_T RegionSize = sizeof(hook_data_t); + LONG status; + + do { + status = pNtAllocateVirtualMemory(GetCurrentProcess(), (PVOID)&BaseAddress, 0, &RegionSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (status >= 0) + return (hook_data_t *)BaseAddress; + BaseAddress += offset; + } while (status < 0); + + return NULL; +} + unsigned char *handle_stub(hook_t *h, unsigned char *addr) { unsigned char stack_offset = 0; @@ -915,7 +992,9 @@ int hook_api(hook_t *h, int type) int len; } hook_types[] = { /* HOOK_NATIVE_JMP_INDIRECT */{ &hook_api_native_jmp_indirect, 14 }, + /* HOOK_NATIVE_PUSH_RETN */{ &hook_api_native_push_retn, 14 }, /* HOOK_JMP_INDIRECT */{ &hook_api_jmp_indirect, 6 }, + /* HOOK_PUSH_RETN */{ &hook_api_push_retn, 6 }, }; // is this address already hooked? @@ -1046,7 +1125,10 @@ int hook_api(hook_t *h, int type) // as some malware depends on this for direct syscalls // missing a few syscalls is better than crashing and getting no information // at all - type = HOOK_NATIVE_JMP_INDIRECT; + if (type == HOOK_PUSH_RETN) + type = HOOK_NATIVE_PUSH_RETN; + else + type = HOOK_NATIVE_JMP_INDIRECT; } // check if this is a valid hook type @@ -1061,10 +1143,12 @@ int hook_api(hook_t *h, int type) return 0; // make the address writable - if (VirtualProtect(addr, hook_types[type].len, PAGE_EXECUTE_READWRITE, - &old_protect)) { + if (VirtualProtect(addr, hook_types[type].len, PAGE_EXECUTE_READWRITE, &old_protect)) { - h->hookdata = alloc_hookdata_near(addr); + if (g_config.hook_low) + h->hookdata = alloc_hookdata_low(); + else + h->hookdata = alloc_hookdata_near(addr); if (h->hookdata && hook_create_trampoline(addr, hook_types[type].len, h->hookdata->tramp)) { //hook_store_exception_info(h); @@ -1095,17 +1179,15 @@ int hook_api(hook_t *h, int type) add_dll_range((ULONG_PTR)hmod, (ULONG_PTR)hmod + GetAllocationSize(hmod)); } } - else { + else pipe("WARNING:Unable to place hook on %z", h->funcname); - } // restore the old protection VirtualProtect(addr, hook_types[type].len, old_protect, &old_protect); } - else { + else pipe("WARNING:Unable to change protection for hook on %z", h->funcname); - } return ret; } From 67e800a91117daf949c39299eb25f86112b8198e Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 16 Apr 2025 11:55:45 +0100 Subject: [PATCH 015/148] Disable AMSI dumps by default (also see previous commit) --- config.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/config.c b/config.c index 7ed76b66..9d3a373a 100644 --- a/config.c +++ b/config.c @@ -1281,8 +1281,6 @@ void parse_config_line(char* line) g_config.amsidump = value[0] == '1'; if (g_config.amsidump) DebugOutput("AMSI dumping enabled.\n"); - else - DebugOutput("AMSI dumping disabled.\n"); } else if (!stricmp(key, "minhook")) { g_config.minhook = value[0] == '1'; From 4892e5213a64c7dcade29133965d826863c1c38c Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Wed, 16 Apr 2025 11:52:17 -0400 Subject: [PATCH 016/148] Improve log args for a few hooks: - Log Process ID in NtReadVirtualMemory/NtWriteVirtualMemory/ReadProcessMemory/WriteProcessMemory if the process handle used does not represent the current running process - Remove a dupe arg from NtOpenThread - Better logging for NtGetContextThread; logs have appropriate arguments based on the context flags - Cloned this logic to NtSetContextThread as well - Added PID/TID to NtGet/SetContextThread Changes inspired by trying to detect https://research.checkpoint.com/2025/waiting-thread-hijacking/ I've found it's quite difficult to keep track of all all of the handles being mapped to various identifiers (PID/TID) from the various APIs in a signature without these improvements. --- hook_process.c | 95 ++++++++++++++++++++++++---- hook_thread.c | 164 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 234 insertions(+), 25 deletions(-) diff --git a/hook_process.c b/hook_process.c index 6b342b5d..5cc5927a 100644 --- a/hook_process.c +++ b/hook_process.c @@ -898,8 +898,27 @@ HOOKDEF(NTSTATUS, WINAPI, NtReadVirtualMemory, ENSURE_SIZET(NumberOfBytesRead); ret = Old_NtReadVirtualMemory(ProcessHandle, BaseAddress, Buffer, NumberOfBytesToRead, NumberOfBytesRead); + DWORD pid = pid_from_process_handle(ProcessHandle); - LOQ_ntstatus("process", "pphB", "ProcessHandle", ProcessHandle, "BaseAddress", BaseAddress, "Size", NumberOfBytesToRead, "Buffer", NumberOfBytesRead, Buffer); + if (pid != GetCurrentProcessId()) { + LOQ_ntstatus( + "process", "piphB", + "ProcessHandle", ProcessHandle, + "ProcessId", pid, + "BaseAddress", BaseAddress, + "Size", NumberOfBytesToRead, + "Buffer", NumberOfBytesRead, Buffer + ); + } + else { + LOQ_ntstatus( + "process", "piphB", + "ProcessHandle", ProcessHandle, + "BaseAddress", BaseAddress, + "Size", NumberOfBytesToRead, + "Buffer", NumberOfBytesRead, Buffer + ); + } return ret; } @@ -915,8 +934,28 @@ HOOKDEF(BOOL, WINAPI, ReadProcessMemory, ENSURE_SIZET(lpNumberOfBytesRead); ret = Old_ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead); - - LOQ_bool("process", "pphB", "ProcessHandle", hProcess, "BaseAddress", lpBaseAddress, "Size", nSize, "Buffer", lpNumberOfBytesRead, lpBuffer); + DWORD pid = pid_from_process_handle(hProcess); + + if (pid != GetCurrentProcessId()) { + LOQ_bool( + "process", "pphB", + "ProcessHandle", hProcess, + "BaseAddress", lpBaseAddress, + "Size", nSize, + "Buffer", lpNumberOfBytesRead, lpBuffer, + "ProcessId", pid + ); + } + else { + LOQ_bool( + "process", "pphB", + "ProcessHandle", hProcess, + "BaseAddress", lpBaseAddress, + "Size", nSize, + "Buffer", lpNumberOfBytesRead, lpBuffer, + "ProcessId", pid + ); + } return ret; } @@ -937,12 +976,27 @@ HOOKDEF(NTSTATUS, WINAPI, NtWriteVirtualMemory, pid = pid_from_process_handle(ProcessHandle); - LOQ_ntstatus("process", "ppBhs", - "ProcessHandle", ProcessHandle, - "BaseAddress", BaseAddress, - "Buffer", NumberOfBytesWritten, Buffer, - "BufferLength", is_valid_address_range((ULONG_PTR)NumberOfBytesWritten, 4) ? *NumberOfBytesWritten : 0, - "StackPivoted", is_stack_pivoted() ? "yes" : "no"); + if (pid != GetCurrentProcess()) { + LOQ_ntstatus( + "process", "pipBhs", + "ProcessHandle", ProcessHandle, + "ProcessId", pid, + "BaseAddress", BaseAddress, + "Buffer", NumberOfBytesWritten, Buffer, + "BufferLength", is_valid_address_range((ULONG_PTR)NumberOfBytesWritten, 4) ? *NumberOfBytesWritten : 0, + "StackPivoted", is_stack_pivoted() ? "yes" : "no" + ); + } + else { + LOQ_ntstatus( + "process", "pipBhs", + "ProcessHandle", ProcessHandle, + "BaseAddress", BaseAddress, + "Buffer", NumberOfBytesWritten, Buffer, + "BufferLength", is_valid_address_range((ULONG_PTR)NumberOfBytesWritten, 4) ? *NumberOfBytesWritten : 0, + "StackPivoted", is_stack_pivoted() ? "yes" : "no" + ); + } if (pid != GetCurrentProcessId() && NT_SUCCESS(ret)) { if (g_config.injection) @@ -970,8 +1024,27 @@ HOOKDEF(BOOL, WINAPI, WriteProcessMemory, pid = pid_from_process_handle(hProcess); - LOQ_bool("process", "ppBhs", "ProcessHandle", hProcess, "BaseAddress", lpBaseAddress, - "Buffer", lpNumberOfBytesWritten, lpBuffer, "BufferLength", *lpNumberOfBytesWritten, "StackPivoted", is_stack_pivoted() ? "yes" : "no"); + if (pid != GetCurrentProcess()) { + LOQ_bool( + "process", "pipBhs", + "ProcessHandle", hProcess, + "ProcessId", pid, + "BaseAddress", lpBaseAddress, + "Buffer", lpNumberOfBytesWritten, lpBuffer, + "BufferLength", *lpNumberOfBytesWritten, + "StackPivoted", is_stack_pivoted() ? "yes" : "no" + ); + } + else { + LOQ_bool( + "process", "ppBhs", + "ProcessHandle", hProcess, + "BaseAddress", lpBaseAddress, + "Buffer", lpNumberOfBytesWritten, lpBuffer, + "BufferLength", *lpNumberOfBytesWritten, + "StackPivoted", is_stack_pivoted() ? "yes" : "no" + ); + } if (pid != GetCurrentProcessId() && ret) { if (g_config.injection) diff --git a/hook_thread.c b/hook_thread.c index 8cc7c604..aec6c419 100644 --- a/hook_thread.c +++ b/hook_thread.c @@ -286,8 +286,8 @@ HOOKDEF(NTSTATUS, WINAPI, NtOpenThread, } if (ClientId) { - LOQ_ntstatus("threading", "Phiii", "ThreadHandle", ThreadHandle, "DesiredAccess", DesiredAccess, - "ProcessId", pid, "ThreadId", tid, "ProcessId", pid); + LOQ_ntstatus("threading", "Phii", "ThreadHandle", ThreadHandle, "DesiredAccess", DesiredAccess, + "ProcessId", pid, "ThreadId", tid); } else { LOQ_ntstatus("threading", "PhOi", "ThreadHandle", ThreadHandle, "DesiredAccess", DesiredAccess, "ObjectAttributes", ObjectAttributes, "ProcessId", pid); @@ -306,16 +306,83 @@ HOOKDEF(NTSTATUS, WINAPI, NtGetContextThread, NTSTATUS ret = Old_NtGetContextThread(ThreadHandle, Context); DWORD pid = pid_from_thread_handle(ThreadHandle); - if (Context && Context->ContextFlags & CONTEXT_CONTROL) + if (Context && (Context->ContextFlags & (CONTEXT_CONTROL | CONTEXT_INTEGER)) == (CONTEXT_CONTROL | CONTEXT_INTEGER)) #ifdef _WIN64 - LOQ_ntstatus("threading", "pppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", - Context->Rcx, "CurrentInstructionPointer", Context->Rip, "ProcessId", pid); + LOQ_ntstatus( + "threading", "pppppppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Rip, + "Rax", Context->Rax, + "Rbx", Context->Rbx, + "Rcx", Context->Rcx, + "Rdx", Context->Rdx, + "Rsp", Context->Rsp, + "ProcessId", pid, + "ThreadId", tid + ); #else - LOQ_ntstatus("threading", "pppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", - Context->Eax, "CurrentInstructionPointer", Context->Eip, "ProcessId", pid); + LOQ_ntstatus( + "threading", "pppppppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Eip, + "Eax", Context->Eax, + "Ebx", Context->Ebx, + "Ecx", Context->Ecx, + "Edx", Context->Edx, + "Esp", Context->Esp, + "ProcessId", pid, + "ThreadId", tid + ); #endif - else - LOQ_ntstatus("threading", "pi", "ThreadHandle", ThreadHandle, "ProcessId", pid); + else if (Context && (Context->ContextFlags & CONTEXT_INTEGER)) { +#ifdef _WIN64 + LOQ_ntstatus( + "threading", "pppppii", + "ThreadHandle", ThreadHandle, + "Rax", Context->Rax, + "Rbx", Context->Rbx, + "Rcx", Context->Rcx, + "Rdx", Context->Rdx, + "ProcessId", pid, + "ThreadId", tid + ); +#else + LOQ_ntstatus( + "threading", "pppppii", + "ThreadHandle", ThreadHandle, + "Eax", Context->Eax, + "Ebx", Context->Ebx, + "Ecx", Context->Ecx, + "Edx", Context->Edx, + "ProcessId", pid, + "ThreadId", tid + ); +#endif + } + else if (Context && (Context->ContextFlags & CONTEXT_CONTROL)) { +#ifdef _WIN64 + LOQ_ntstatus( + "threading", "pppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Rip, + "Rsp", Context->Rsp, + "ProcessId", pid, + "ThreadId", tid + ); +#else + LOQ_ntstatus( + "threading", "pppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Eip, + "Esp", Context->Esp, + "ProcessId", pid, + "ThreadId", tid + ); +#endif + } + else { + LOQ_ntstatus("threading", "pii", "ThreadHandle", ThreadHandle, "ProcessId", pid, "ThreadId", tid); + } GetThreadContextHandler(pid, Context); @@ -370,14 +437,83 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetContextThread, ret = Old_NtSetContextThread(ThreadHandle, Context); - if (Context && Context->ContextFlags & CONTEXT_CONTROL) + if (Context && (Context->ContextFlags & (CONTEXT_CONTROL | CONTEXT_INTEGER)) == (CONTEXT_CONTROL | CONTEXT_INTEGER)) #ifdef _WIN64 - LOQ_ntstatus("threading", "pppp", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Rcx, "CurrentInstructionPointer", Context->Rip, "Flags", Context->ContextFlags); + LOQ_ntstatus( + "threading", "pppppppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Rip, + "Rax", Context->Rax, + "Rbx", Context->Rbx, + "Rcx", Context->Rcx, + "Rdx", Context->Rdx, + "Rsp", Context->Rsp, + "ProcessId", pid, + "ThreadId", tid + ); #else - LOQ_ntstatus("threading", "pppp", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Eax, "CurrentInstructionPointer", Context->Eip, "Flags", Context->ContextFlags); + LOQ_ntstatus( + "threading", "pppppppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Eip, + "Eax", Context->Eax, + "Ebx", Context->Ebx, + "Ecx", Context->Ecx, + "Edx", Context->Edx, + "Esp", Context->Esp, + "ProcessId", pid, + "ThreadId", tid + ); #endif - else - LOQ_ntstatus("threading", "p", "ThreadHandle", ThreadHandle); + else if (Context && (Context->ContextFlags & CONTEXT_INTEGER)) { +#ifdef _WIN64 + LOQ_ntstatus( + "threading", "pppppii", + "ThreadHandle", ThreadHandle, + "Rax", Context->Rax, + "Rbx", Context->Rbx, + "Rcx", Context->Rcx, + "Rdx", Context->Rdx, + "ProcessId", pid, + "ThreadId", tid + ); +#else + LOQ_ntstatus( + "threading", "pppppii", + "ThreadHandle", ThreadHandle, + "Eax", Context->Eax, + "Ebx", Context->Ebx, + "Ecx", Context->Ecx, + "Edx", Context->Edx, + "ProcessId", pid, + "ThreadId", tid + ); +#endif + } + else if (Context && (Context->ContextFlags & CONTEXT_CONTROL)) { +#ifdef _WIN64 + LOQ_ntstatus( + "threading", "pppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Rip, + "Rsp", Context->Rsp, + "ProcessId", pid, + "ThreadId", tid + ); +#else + LOQ_ntstatus( + "threading", "pppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Eip, + "Esp", Context->Esp, + "ProcessId", pid, + "ThreadId", tid + ); +#endif + } + else { + LOQ_ntstatus("threading", "pii", "ThreadHandle", ThreadHandle, "ProcessId", pid, "ThreadId", tid); + } SetThreadContextHandler(pid, Context); if (pid != GetCurrentProcessId()) From 64de4600851edac00544b6b753fc993888c8cf3f Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Wed, 16 Apr 2025 11:56:42 -0400 Subject: [PATCH 017/148] Fix autocomplete typos, tks Gemini --- hook_process.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hook_process.c b/hook_process.c index 5cc5927a..d464057b 100644 --- a/hook_process.c +++ b/hook_process.c @@ -976,7 +976,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtWriteVirtualMemory, pid = pid_from_process_handle(ProcessHandle); - if (pid != GetCurrentProcess()) { + if (pid != GetCurrentProcessId()) { LOQ_ntstatus( "process", "pipBhs", "ProcessHandle", ProcessHandle, @@ -1024,7 +1024,7 @@ HOOKDEF(BOOL, WINAPI, WriteProcessMemory, pid = pid_from_process_handle(hProcess); - if (pid != GetCurrentProcess()) { + if (pid != GetCurrentProcessId()) { LOQ_bool( "process", "pipBhs", "ProcessHandle", hProcess, From ae6014885d83819c78b2acea8808b10812a1289c Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 24 Apr 2025 21:59:08 +0100 Subject: [PATCH 018/148] Windows Loader Snaps: vDbgPrintExWithPrefixInternal hook & option 'snaps=1' for loader snaps output in analysis log --- CAPE/CAPE.c | 34 +++++++++++++++++++++++++++++----- CAPE/YaraHarness.c | 7 +++++++ config.c | 5 +++++ config.h | 3 +++ hook_misc.c | 25 ++++++++++++++++++++++++- hooks.c | 1 + hooks.h | 9 +++++++++ loader/loader/Loader.c | 16 +++++++++++----- 8 files changed, 89 insertions(+), 11 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 78457f26..41973af4 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -698,6 +698,7 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) const char *YaraFunctions[] = { "LdrpCallInitRoutine", + "vDbgPrintExWithPrefixInternal", }; for (int i = 0; i < sizeof(YaraFunctions) / sizeof(YaraFunctions[0]); i++) @@ -3565,6 +3566,32 @@ void RestoreHeaders() DebugOutput("RestoreHeaders: Restored original import table.\n"); } +static void EnableLoaderSnaps() +{ +#ifdef _WIN64 + PBYTE _fltused = (PBYTE)GetProcAddress(GetModuleHandle("ntdll"), "_fltused"); + if (_fltused == NULL) + return; + DWORD* LdrpDebugFlags = (DWORD*)(_fltused - 0x10); + if (!*LdrpDebugFlags) + *LdrpDebugFlags = 1; +#else + PBYTE LdrGetDllHandleEx = (PBYTE)GetProcAddress(GetModuleHandle("ntdll"), "LdrGetDllHandleEx"); + if (LdrGetDllHandleEx == NULL) + return; + DWORD* ShowSnaps = NULL; + for (PBYTE p = LdrGetDllHandleEx; p < LdrGetDllHandleEx + 50; p++) + { + if (p[0] == 0xf6 && p[1] == 0x05 && p[6] == 0x09) + ShowSnaps = *(DWORD**)(p+2); + } + if (!ShowSnaps) + return; + if (!*ShowSnaps) + *ShowSnaps = 1; +#endif +} + void CAPE_post_init() { if (g_config.syscall && ((OSVersion.dwMajorVersion == 6 && OSVersion.dwMinorVersion > 1) || OSVersion.dwMajorVersion > 6)) @@ -3592,9 +3619,6 @@ void CAPE_post_init() void CAPE_init() { - // Initialise CAPE global variables - // - //if (!g_config.standalone) CapeMetaData = (PCAPEMETADATA)calloc(sizeof(CAPEMETADATA), sizeof(BYTE)); CapeMetaData->Pid = GetCurrentProcessId(); CapeMetaData->PPid = parent_process_id(); @@ -3612,8 +3636,8 @@ void CAPE_init() ProcessDumped = FALSE; DumpCount = 0; - // Cuckoo debug output level for development (0=none, 2=max) - // g_config.debug = 2; + if (g_config.snaps) + EnableLoaderSnaps(); YaraInit(); diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index f89442df..91e07df2 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -50,6 +50,13 @@ char InternalYara[] = "condition:uint16(0) == 0x5a4d and any of them}" "rule LdrpCallInitRoutine" "{strings:$function = {55 8B EC 56 57 53 8B F4 [0-2] FF 75 14 FF 75 10 FF 75 0C FF 55 08 8B E6 5B 5F 5E 5D C2 10 00}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule vDbgPrintExWithPrefixInternal" +#ifdef _WIN64 + "{strings:$function = {40 55 53 56 41 54 41 55 41 56 41 57 48 81 EC 20 01 00 00 48 8D 6C 24 20 48 8B 05 [4] 48 33 C5 48 89 85 ?? 00 00 00 4C 89 4D ?? 44 89 45 ?? 44 8B E2 89 55 ?? 48 8B D1 48 89 4D ?? 48 8B 85}" +#else + "{strings:$function = {68 90 00 00 00 68 [4] E8 [4] 89 95 [4] 89 8D [4] 8B 45 ?? 89 85 [4] 8B 45 ?? 89 85 [4] 64 A1 18 00 00 00 89 45 ?? 83 FA FF 0F 84}" +#endif "condition:uint16(0) == 0x5a4d and any of them}" "rule capemon" "{strings:$hash = {d3 b9 46 1d 9a 14 bc 44 a1 61 c3 47 6a 0e 35 90 00 2c 28 81 dc a0 36 dc 2c 92 0c 7c b6 84 39 59}" diff --git a/config.c b/config.c index 9d3a373a..ce875f07 100644 --- a/config.c +++ b/config.c @@ -1345,6 +1345,11 @@ void parse_config_line(char* line) if (g_config.interactive == 1) DebugOutput("Interactive desktop enabled.\n"); } + else if (!stricmp(key, "snaps")) { + g_config.snaps = value[0] == '1'; + if (g_config.snaps) + DebugOutput("Loader snaps enabled.\n"); + } else if (stricmp(key, "no-iat")) DebugOutput("Monitor config - unrecognised key %s.\n", key); diff --git a/config.h b/config.h index 82cacecd..adc4bad4 100644 --- a/config.h +++ b/config.h @@ -252,6 +252,9 @@ struct _g_config { // Specify custom trace stepping behavior int stepmode; + // Enable Windows Loader snaps output + int snaps; + char *break_on_apiname; char *break_on_modname; char break_on_return[MAX_PATH]; diff --git a/hook_misc.c b/hook_misc.c index c26a93f0..13e9db07 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1937,4 +1937,27 @@ HOOKDEF(UINT, WINAPI, MsiInstallProductW, UINT ret = Old_MsiInstallProductW(szPackagePath, szCommandLine); LOQ_zero("misc", "uu", "PackagePath", szPackagePath, "CommandLine", szCommandLine); return ret; -} \ No newline at end of file +} + +HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, + __in PCH Prefix, + __in ULONG ComponentId, + __in ULONG Level, + __in PCHAR Format, + __in va_list arglist, + __in BOOLEAN HandleBreakpoint +) { + UCHAR Buffer[512]; + size_t cb = strlen(Prefix); + strcpy(Buffer, Prefix); + cb = _vsnprintf(Buffer + cb, sizeof(Buffer) - cb, Format, arglist) + cb; + + if (cb == -1) { + cb = sizeof(Buffer); + Buffer[sizeof(Buffer) - 1] = '\n'; + } + + DebugOutput("%s", Buffer); + + return Old_vDbgPrintExWithPrefixInternal(Prefix, ComponentId, Level, Format, arglist, HandleBreakpoint); +} diff --git a/hooks.c b/hooks.c index a440bcb9..eb6533f1 100644 --- a/hooks.c +++ b/hooks.c @@ -109,6 +109,7 @@ hook_t full_hooks[] = { // all variants of ShellExecute end up in ShellExecuteExW HOOK(shell32, ShellExecuteExW), HOOK(msvcrt, system), + HOOK(ntdll, vDbgPrintExWithPrefixInternal), // Thread Hooks HOOK_SPECIAL(ntdll, NtCreateThread), diff --git a/hooks.h b/hooks.h index d6ab4df9..87375e55 100644 --- a/hooks.h +++ b/hooks.h @@ -3660,4 +3660,13 @@ HOOKDEF(UINT, WINAPI, MsiInstallProductW, _In_ LPCWSTR szCommandLine ); +HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, + __in PCH Prefix, + __in ULONG ComponentId, + __in ULONG Level, + __in PCHAR Format, + __in va_list arglist, + __in BOOLEAN HandleBreakpoint +); + #include "hook_vbscript.h" diff --git a/loader/loader/Loader.c b/loader/loader/Loader.c index d4ae0378..301a0878 100644 --- a/loader/loader/Loader.c +++ b/loader/loader/Loader.c @@ -30,7 +30,7 @@ along with this program.If not, see . SYSTEM_INFO SystemInfo; char PipeOutput[MAX_PATH], LogPipe[MAX_PATH]; -BOOL DisableIATPatching, FirstProcess; +BOOL DisableIATPatching, FirstProcess, LoaderSnaps; void pipe(char* Buffer, SIZE_T Length); @@ -236,7 +236,7 @@ int ReadConfig(DWORD ProcessId, char *DllName) DebugOutput("Loader: Successfully loaded pipe name %s.\n", LogPipe); #endif } - if (!strcmp(key, "no-iat")) + else if (!strcmp(key, "no-iat")) { DisableIATPatching = Value[0] == '1'; if (DisableIATPatching) @@ -244,6 +244,8 @@ int ReadConfig(DWORD ProcessId, char *DllName) else DebugOutput("Loader: IAT patching enabled.\n"); } + else if (!strcmp(key, "snaps")) + LoaderSnaps = Value[0] == '1'; else if (!strcmp(key, "first-process")) FirstProcess = Value[0] == '1'; } @@ -339,8 +341,8 @@ DWORD GetProcessInitialThreadId(HANDLE ProcessHandle) static BOOL EnableLoaderSnaps(HANDLE ProcessHandle, PPEB Peb) { - SIZE_T dwBytesRead, dwBytesWritten; ULONG gflags = 0; + SIZE_T dwBytesRead, dwBytesWritten; PVOID pNtGlobalFlag = (PVOID)((PBYTE)Peb + GetNtGlobalFlagsOffset()); @@ -358,7 +360,7 @@ static BOOL EnableLoaderSnaps(HANDLE ProcessHandle, PPEB Peb) return FALSE; } - DebugOutput("Loader: snaps enabled.\n"); + DebugOutput("Loader: Snaps enabled.\n"); return TRUE; } @@ -1229,9 +1231,13 @@ static int InjectDll(int ProcessId, int ThreadId, const char *DllPath) goto out; } - if (!GetProcessPeb(ProcessHandle, &Peb)) + PPEB pPeb = GetProcessPeb(ProcessHandle, &Peb); + if (!pPeb) DebugOutput("InjectDll: GetProcessPeb failure.\n"); + if (LoaderSnaps) + EnableLoaderSnaps(ProcessHandle, pPeb); + // If no thread id supplied, we fetch the initial thread id from the initial TEB if (!ThreadId && Peb.ImageBaseAddress && !Peb.Ldr) { From 86f0619d0802970e281169f14b82c8e35bfde523 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 25 Apr 2025 12:57:25 +0100 Subject: [PATCH 019/148] .NET JIT cache dumps: off by default, configurable limit with option jit-dumps=X --- CAPE/CAPE.c | 12 +++++++++--- CAPE/CAPE.h | 2 +- config.c | 5 +++++ config.h | 3 +++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 41973af4..0e35f26f 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -3281,8 +3281,6 @@ void DumpInterestingRegions(MEMORY_BASIC_INFORMATION MemInfo) if (lookup_get(&g_dotnet_jit, (ULONG_PTR)MemInfo.BaseAddress, 0)) { - DebugOutput("DumpInterestingRegions: Dumping .NET JIT native cache at 0x%p.\n", MemInfo.BaseAddress); - CapeMetaData->ModulePath = NULL; CapeMetaData->DumpType = 0; #ifdef _WIN64 @@ -3292,7 +3290,15 @@ void DumpInterestingRegions(MEMORY_BASIC_INFORMATION MemInfo) #endif CapeMetaData->Address = MemInfo.BaseAddress; - DumpMemory(MemInfo.BaseAddress, GetAccessibleSize(MemInfo.BaseAddress)); + if (DotNetCacheDumpCount < g_config.jit_dumps && DumpMemory(MemInfo.BaseAddress, GetAccessibleSize(MemInfo.BaseAddress))) + { + DebugOutput("DumpInterestingRegions: Dumped .NET JIT native cache at 0x%p.\n", MemInfo.BaseAddress); + DotNetCacheDumpCount++; + } + else if (g_config.jit_dumps && DotNetCacheDumpCount >= g_config.jit_dumps) + DebugOutput("DumpInterestingRegions: .NET JIT native cache dump limit hit: %d", g_config.jit_dumps); + else if (!g_config.jit_dumps) + DebugOutput("DumpInterestingRegions: Skipping .NET JIT native cache at 0x%p (jit-dumps=0)\n", MemInfo.BaseAddress); } } diff --git a/CAPE/CAPE.h b/CAPE/CAPE.h index c61880fe..61f1d821 100644 --- a/CAPE/CAPE.h +++ b/CAPE/CAPE.h @@ -79,7 +79,7 @@ BOOL DumpRange(PVOID Address, SIZE_T Size); BOOL DumpStackRegion(void); BOOL ProcessDumped; -unsigned int DumpCount; +unsigned int DumpCount, DotNetCacheDumpCount; SYSTEM_INFO SystemInfo; PVOID CallingModule; diff --git a/config.c b/config.c index ce875f07..ce9821d9 100644 --- a/config.c +++ b/config.c @@ -1282,6 +1282,11 @@ void parse_config_line(char* line) if (g_config.amsidump) DebugOutput("AMSI dumping enabled.\n"); } + else if (!stricmp(key, "jit-dumps")) { + g_config.jit_dumps = (unsigned int)strtoul(value, NULL, 10); + if (g_config.jit_dumps) + DebugOutput(".NET JIT cache dumps enabled, limit %d\n", g_config.jit_dumps); + } else if (!stricmp(key, "minhook")) { g_config.minhook = value[0] == '1'; if (g_config.minhook) diff --git a/config.h b/config.h index adc4bad4..ab76a103 100644 --- a/config.h +++ b/config.h @@ -219,6 +219,9 @@ struct _g_config { // AMSI dumps (Win10+) int amsidump; + // .NET JIT cache dumps + unsigned int jit_dumps; + // Minimal hook set int minhook; From 3208642c4c36cfa2c4417dfea159f7b6209cdd85 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 28 Apr 2025 10:34:58 +0100 Subject: [PATCH 020/148] Remove obsolete function retarget_rip_relative_displacement() (hooking_64.c) --- hooking_64.c | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/hooking_64.c b/hooking_64.c index d41b9090..8e908935 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -212,27 +212,6 @@ static void retarget_relative_displacement(unsigned char **tramp, unsigned char *addr = newaddr; } -static void retarget_rip_relative_displacement(unsigned char **tramp, unsigned char **addr, _DInst *insn) -{ - unsigned short length = insn->size; - unsigned char offset = (unsigned char)(length - insn->imm_encoded_size - sizeof(int)); - unsigned char *newtramp = *tramp; - unsigned char *newaddr = *addr; - ULONG_PTR target; - int rel = *(int *)(newaddr + offset); - target = (ULONG_PTR)(newaddr + length + rel); - // copy the instruction directly to the trampoline - while (length-- != 0) { - *newtramp++ = *newaddr++; - } - // now replace the displacement - rel = (int)(target - (ULONG_PTR)newtramp); - *(int *)(newtramp - insn->imm_encoded_size - sizeof(int)) = rel; - - *tramp = newtramp; - *addr = newaddr; -} - // create a trampoline at the given address, that is, we are going to replace // the original instructions at this particular address. So, in order to // call the original function from our hook, we have to execute the original From ddc1b4e97e40598209758e182ba7bac2a8cb5be2 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 28 Apr 2025 10:36:44 +0100 Subject: [PATCH 021/148] Remove obsolete function is_suspended() (misc.c) --- misc.c | 51 --------------------------------------------------- misc.h | 1 - 2 files changed, 52 deletions(-) diff --git a/misc.c b/misc.c index df4d9963..e224e72a 100644 --- a/misc.c +++ b/misc.c @@ -1768,57 +1768,6 @@ DWORD get_pid_by_tid(DWORD tid) return ret; } -BOOLEAN is_suspended(DWORD pid, DWORD tid) -{ - ULONG length; - PSYSTEM_PROCESS_INFORMATION pspi = NULL, proc; - ULONG requestedlen = 16384; - lasterror_t lasterror; - BOOLEAN ret = FALSE; - - get_lasterrors(&lasterror); - - pspi = malloc(requestedlen); - if (pspi == NULL) - goto out; - - while (pNtQuerySystemInformation(SystemProcessInformation, pspi, requestedlen, &length) == STATUS_INFO_LENGTH_MISMATCH) { - free(pspi); - requestedlen <<= 1; - pspi = malloc(requestedlen); - if (pspi == NULL) - goto out; - } - // now we have a valid list of process information - proc = pspi; - while (1) { - ULONG i; - - if ((DWORD)(ULONG_PTR)proc->UniqueProcessId != pid) - goto next; - for (i = 0; i < proc->NumberOfThreads; i++) { - PSYSTEM_THREAD thread = &proc->Threads[i]; - if (tid && (DWORD)(ULONG_PTR)thread->ClientId.UniqueThread != tid) - continue; - if (thread->WaitReason != Suspended) - goto out; - } - break; -next: - if (!proc->NextEntryOffset) - break; - proc = (PSYSTEM_PROCESS_INFORMATION)((PCHAR)proc + proc->NextEntryOffset); - } - ret = TRUE; -out: - if (pspi) - free(pspi); - - set_lasterrors(&lasterror); - - return ret; -} - static PUCHAR get_rel_target(PUCHAR buf) { return buf + 5 + *(int *)&buf[1]; diff --git a/misc.h b/misc.h index c8c3c313..19c0af7c 100644 --- a/misc.h +++ b/misc.h @@ -147,7 +147,6 @@ DWORD randint(DWORD min, DWORD max); BOOL is_directory_objattr(const OBJECT_ATTRIBUTES *obj); BOOL file_exists(const OBJECT_ATTRIBUTES *obj); void hide_module_from_peb(HMODULE module_handle); -BOOLEAN is_suspended(DWORD pid, DWORD tid); int path_is_system(const wchar_t *path_w); int path_is_program_files(const wchar_t *path_w); BOOLEAN parent_has_path(char* path); From df6dba6c8e7b231764f9328ae8e6fb63e0bbfbd5 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 28 Apr 2025 12:05:38 +0100 Subject: [PATCH 022/148] Loader: fix Win7 compatibility, use WaitForDebugEvent instead of unavailable WaitForDebugEventEx --- loader/loader/Loader.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/loader/loader/Loader.c b/loader/loader/Loader.c index 301a0878..4359624a 100644 --- a/loader/loader/Loader.c +++ b/loader/loader/Loader.c @@ -1608,7 +1608,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine while (TRUE) { DEBUG_EVENT dbgEvent; - WaitForDebugEventEx(&dbgEvent, INFINITE); + WaitForDebugEvent(&dbgEvent, INFINITE); switch (dbgEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: @@ -1640,9 +1640,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine break; } - if (dbgEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) - break; - ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); } } From c98ddb8008196a074123fdad2d863e027daeea61 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 8 May 2025 14:13:18 +0100 Subject: [PATCH 023/148] Allow monitor to load without config ini file - defaults to standalone mode --- capemon.c | 22 +++++++--------------- config.c | 11 ++++------- config.h | 2 +- hook_special.c | 16 +++++++--------- 4 files changed, 19 insertions(+), 32 deletions(-) diff --git a/capemon.c b/capemon.c index 5ab1ad16..e0f408bb 100644 --- a/capemon.c +++ b/capemon.c @@ -618,26 +618,18 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved) get_our_commandline(); + InitializeCriticalSection(&g_mutex); + InitializeCriticalSection(&g_writing_log_buffer_mutex); + + // read the config settings + read_config(); + if (g_config.standalone) { - // initialise CAPE CAPE_init(); DebugOutput("Standalone mode initialised.\n"); return TRUE; - } - - InitializeCriticalSection(&g_mutex); - InitializeCriticalSection(&g_writing_log_buffer_mutex); - // read the config settings - if (!read_config()) -#if CUCKOODBG - ; - else - DebugOutput("Config loaded.\n"); -#else - // if we're not debugging, then failure to read the capemon config should be a critical error - goto abort; -#endif + } // don't inject into our own binaries run out of the analyzer directory unless they're the first process (intended) if (wcslen(g_config.w_analyzer) && !wcsnicmp(our_process_path_w, g_config.w_analyzer, wcslen(g_config.w_analyzer)) && !g_config.first_process) diff --git a/config.c b/config.c index ce9821d9..55eae563 100644 --- a/config.c +++ b/config.c @@ -1363,7 +1363,7 @@ void parse_config_line(char* line) } } -int read_config(void) +void read_config(void) { char buf[32768], config_fname[MAX_PATH]; FILE *fp; @@ -1416,17 +1416,14 @@ int read_config(void) fp = fopen(config_fname, "r"); } - // for debugging purposes if (fp == NULL) { memset(config_fname, 0, sizeof(config_fname)); sprintf(config_fname, "%s\\config.ini", g_config.analyzer); fp = fopen(config_fname, "r"); - if (fp == NULL) - return 0; } - memset(buf, 0, sizeof(buf)); if (fp) { + memset(buf, 0, sizeof(buf)); while (fgets(buf, sizeof(buf), fp) != NULL) { // cut off the newline char *p = strchr(buf, '\r'); @@ -1490,7 +1487,7 @@ int read_config(void) ImageBaseRemapped = TRUE; if (!our_process_name) - return 1; + return; if (!_stricmp(our_process_name, "explorer.exe") && g_config.interactive == 1) { @@ -1656,5 +1653,5 @@ int read_config(void) } } - return 1; + return; } diff --git a/config.h b/config.h index ab76a103..6a8e1f01 100644 --- a/config.h +++ b/config.h @@ -299,6 +299,6 @@ struct _g_config { extern struct _g_config g_config; -int read_config(void); +void read_config(void); #endif diff --git a/hook_special.c b/hook_special.c index b1fc35a1..687e5376 100644 --- a/hook_special.c +++ b/hook_special.c @@ -69,21 +69,19 @@ HOOKDEF_NOTAIL(WINAPI, LdrLoadDll, if (g_config.tlsdump) { // lsass injected a second time - switch to 'normal' mode g_config.tlsdump = 0; - if (read_config()) { - log_init(g_config.debug || g_config.standalone); - set_hooks(); - notify_successful_load(); - } + read_config(); + log_init(g_config.debug || g_config.standalone); + set_hooks(); + notify_successful_load(); ret = 0; } if (g_config.interactive) { // explorer injected by malware - switch to 'normal' mode g_config.interactive = 2; g_config.minhook = 0; - if (read_config()) { - set_hooks(); - notify_successful_load(); - } + read_config(); + set_hooks(); + notify_successful_load(); ret = 0; } } From 190d22d6694fd7c78aa143927777e9f0d7c5e2a0 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 14 May 2025 11:01:00 +0100 Subject: [PATCH 024/148] Harden ScanForExport() function (used by GetExportNameByAddress()) --- CAPE/CAPE.c | 53 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 0e35f26f..417eb4a1 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -2049,41 +2049,62 @@ PCHAR ScanForExport(PVOID Address, SIZE_T ScanMax) __try { PVOID Base = GetAllocationBase(Address); - if (!Base) + if (!Base || !IsAddressAccessible(Base)) return NULL; - PIMAGE_NT_HEADERS pNtHeader = pNtHeader = (PIMAGE_NT_HEADERS)((PUCHAR)Base + (ULONG)((PIMAGE_DOS_HEADER)Base)->e_lfanew); - if (!pNtHeader) + PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)Base; + if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL; - PIMAGE_EXPORT_DIRECTORY ExportDirectory = ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)Base + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); - if (!ExportDirectory) + PIMAGE_NT_HEADERS NtHeader = (PIMAGE_NT_HEADERS)((PUCHAR)Base + DosHeader->e_lfanew); + if (NtHeader->Signature != IMAGE_NT_SIGNATURE) return NULL; - PDWORD AddressOfNames = (PDWORD)((PUCHAR)Base + ExportDirectory->AddressOfNames); - if (!AddressOfNames) + IMAGE_DATA_DIRECTORY ExportDirEntry = NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (ExportDirEntry.VirtualAddress == 0 || ExportDirEntry.Size == 0) return NULL; - PDWORD AddressOfFunctions = (PDWORD)((PUCHAR)Base + ExportDirectory->AddressOfFunctions); - if (!AddressOfFunctions) + PIMAGE_EXPORT_DIRECTORY ExportDir = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)Base + ExportDirEntry.VirtualAddress); + if (!IsAddressAccessible(ExportDir)) return NULL; - PWORD AddressOfNameOrdinals = (PWORD)((PUCHAR)Base + ExportDirectory->AddressOfNameOrdinals); - if (!AddressOfNameOrdinals) + if (ExportDir->NumberOfFunctions > 0x100000) return NULL; - for (unsigned int j = 0; j < ExportDirectory->NumberOfFunctions; j++) + PDWORD AddressOfFunctions = (PDWORD)((PUCHAR)Base + ExportDir->AddressOfFunctions); + if (!IsAddressAccessible(AddressOfFunctions)) + return NULL; + + PDWORD AddressOfNames = (PDWORD)((PUCHAR)Base + ExportDir->AddressOfNames); + PWORD AddressOfNameOrdinals = (PWORD)((PUCHAR)Base + ExportDir->AddressOfNameOrdinals); + + for (DWORD i = 0; i < ExportDir->NumberOfNames; i++) { - if ((PUCHAR)Address - (PUCHAR)Base > (int)AddressOfFunctions[AddressOfNameOrdinals[j]] - && (PUCHAR)Address - (PUCHAR)Base - AddressOfFunctions[AddressOfNameOrdinals[j]] <= (int)ScanMax) - return (PCHAR)Base + AddressOfNames[j]; + if (!IsAddressAccessible(&AddressOfNameOrdinals[i]) || !IsAddressAccessible(&AddressOfNames[i])) + continue; + + WORD Ordinal = AddressOfNameOrdinals[i]; + if (Ordinal >= ExportDir->NumberOfFunctions) + continue; + + DWORD FunctionRva = AddressOfFunctions[Ordinal]; + if (FunctionRva == 0) + continue; + + if ((ULONG_PTR)Address - (ULONG_PTR)Base >= FunctionRva && (ULONG_PTR)Address - (ULONG_PTR)Base - FunctionRva <= ScanMax) + { + PCHAR Name = (PCHAR)((PUCHAR)Base + AddressOfNames[i]); + if (IsAddressAccessible(Name)) + return Name; + } } } __except(EXCEPTION_EXECUTE_HANDLER) { return NULL; } - return NULL; + + return NULL; } //************************************************************************************** From fc9b8e56616ad32f207b4d50d9ee39818a1cd774 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 14 May 2025 11:02:51 +0100 Subject: [PATCH 025/148] Trace: switch from using ScyllaGetExportNameByAddress() to GetExportNameByAddress() --- CAPE/Trace.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 566359cf..9e56e056 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -207,7 +207,7 @@ SIZE_T StrTestW(PWCHAR StrCandidate, PWCHAR OutputBuffer, SIZE_T BufferSize) void StringCheck(PVOID PossibleString) { - PCHAR ExportName = ScyllaGetExportNameByAddress(PossibleString, NULL); + PCHAR ExportName = GetExportNameByAddress(PossibleString); if (ExportName) { DebuggerOutput(" %s ", ExportName); @@ -1773,7 +1773,7 @@ void InstructionHandler(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst } else if (CallTarget && !ExportName) { - ExportName = ScyllaGetExportNameByAddress(CallTarget, NULL); + ExportName = GetExportNameByAddress(CallTarget); if (!ExportName && (!FilterTrace || g_config.trace_all)) TraceOutputFuncAddress(CIP, DecodedInstruction, CallTarget); @@ -1837,7 +1837,7 @@ void InstructionHandler(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst } else { - ExportName = ScyllaGetExportNameByAddress(JumpTarget, NULL); + ExportName = GetExportNameByAddress(JumpTarget); if (ExportName) { @@ -2104,7 +2104,7 @@ BOOL Trace(struct _EXCEPTION_POINTERS* ExceptionInfo) PCHAR FunctionName = NULL; __try { - FunctionName = ScyllaGetExportNameByAddress(CIP, NULL); + FunctionName = GetExportNameByAddress(CIP); } __except(EXCEPTION_EXECUTE_HANDLER) { @@ -2338,7 +2338,7 @@ BOOL BreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINT __try { - FunctionName = ScyllaGetExportNameByAddress(CIP, NULL); + FunctionName = GetExportNameByAddress(CIP); } __except(EXCEPTION_EXECUTE_HANDLER) { @@ -2596,7 +2596,7 @@ BOOL BreakOnReturnCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_PO PCHAR FunctionName; __try { - FunctionName = ScyllaGetExportNameByAddress(CIP, NULL); + FunctionName = GetExportNameByAddress(CIP); } __except(EXCEPTION_EXECUTE_HANDLER) { From 30b5c864efe3ffac6c4af4f3a67ba1799c94ff36 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 19 May 2025 17:46:29 +0100 Subject: [PATCH 026/148] Replace remaining uses of pipe("INFO:...) with DebugOutput() --- hook_sleep.c | 2 +- hook_thread.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hook_sleep.c b/hook_sleep.c index a09f4e20..cc7f2337 100644 --- a/hook_sleep.c +++ b/hook_sleep.c @@ -49,7 +49,7 @@ static int num_wait_small = 0; void disable_sleep_skip() { if (sleep_skip_active && g_config.force_sleepskip < 1) { - pipe("INFO:Disabling sleep skipping."); + DebugOutput("Disabling sleep skipping."); sleep_skip_active = 0; } } diff --git a/hook_thread.c b/hook_thread.c index 8cc7c604..e8cf802a 100644 --- a/hook_thread.c +++ b/hook_thread.c @@ -72,7 +72,7 @@ void add_ignored_thread(DWORD tid) lasterror_t lasterror; get_lasterrors(&lasterror); - pipe("INFO:Adding ignored thread %d", tid); + DebugOutput("Adding ignored thread %d", tid); lookup_add(&g_ignored_threads, tid, 0); set_lasterrors(&lasterror); } From ac31615f84ec1541530b0086f4448fcb8a764117 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 22 May 2025 17:14:14 +0100 Subject: [PATCH 027/148] Define values for anti-analysis spoofing in hooks for consistency --- config.h | 15 ++++++++++++++- hook_file.c | 18 +++++------------- hook_misc.c | 8 ++++---- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/config.h b/config.h index db2003ad..ab9a52f3 100644 --- a/config.h +++ b/config.h @@ -23,6 +23,19 @@ along with this program. If not, see . #define BREAKPOINT_MAX 0x100 #define SYSBP_MAX 0x400 +// Anti-analysis related items so we can be consistent across various APIs +#define SPOOFED_DISK_SIZE 0x10000000000ull // 1TB +#define RECOVERY_PARTITION_SIZE 0x1f2af000 // Taken from random Win10 install + +#define SPOOFED_GPU_RAM 0x100000000l // 4GB +#define SPOOFED_GPU_NAME L"NVIDIA GTX 1650" + +#define SPOOFED_RAM 0x100000000l // 4GB +#define SPOOFED_RAM_RESERVED 0x410000 + +#define SPOOFED_CPU_CORE_NUM 4 + + struct _g_config { // name of the pipe to communicate with cuckoo wchar_t pipe_name[MAX_PATH]; @@ -283,4 +296,4 @@ extern struct _g_config g_config; int read_config(void); -#endif +#endif \ No newline at end of file diff --git a/hook_file.c b/hook_file.c index 409acc3d..4e8693d8 100644 --- a/hook_file.c +++ b/hook_file.c @@ -1351,10 +1351,8 @@ HOOKDEF(BOOL, WINAPI, GetDiskFreeSpaceExA, ) { BOOL ret = Old_GetDiskFreeSpaceExA(lpDirectoryName, lpFreeBytesAvailable, lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes); LOQ_bool("filesystem", "s", "DirectoryName", lpDirectoryName); - - /* Fake harddrive size to 256GB */ if (!g_config.no_stealth && ret && lpTotalNumberOfBytes) { - lpTotalNumberOfBytes->QuadPart = 256060514304L; + lpTotalNumberOfBytes->QuadPart = SPOOFED_DISK_SIZE - RECOVERY_PARTITION_SIZE; } return ret; @@ -1368,10 +1366,8 @@ HOOKDEF(BOOL, WINAPI, GetDiskFreeSpaceExW, ) { BOOL ret = Old_GetDiskFreeSpaceExW(lpDirectoryName, lpFreeBytesAvailable, lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes); LOQ_bool("filesystem", "u", "DirectoryName", lpDirectoryName); - - /* Fake harddrive size to 256GB */ if (!g_config.no_stealth && ret && lpTotalNumberOfBytes) { - lpTotalNumberOfBytes->QuadPart = 256060514304L; + lpTotalNumberOfBytes->QuadPart = SPOOFED_DISK_SIZE - RECOVERY_PARTITION_SIZE; } return ret; @@ -1386,12 +1382,10 @@ HOOKDEF(BOOL, WINAPI, GetDiskFreeSpaceA, ) { BOOL ret = Old_GetDiskFreeSpaceA(lpRootPathName, lpSectorsPerCluster, lpBytesPerSector, lpNumberOfFreeClusters, lpTotalNumberOfClusters); LOQ_bool("filesystem", "s", "RootPathName", lpRootPathName); - - /* Fake harddrive size to 256GB */ if (!g_config.no_stealth) { __try { if (lpTotalNumberOfClusters && lpSectorsPerCluster && lpBytesPerSector && *lpSectorsPerCluster && *lpBytesPerSector) { - *lpTotalNumberOfClusters = (DWORD)(256060514304L / (*lpSectorsPerCluster * *lpBytesPerSector)); + *lpTotalNumberOfClusters = (DWORD)((SPOOFED_DISK_SIZE - RECOVERY_PARTITION_SIZE) / (*lpSectorsPerCluster * *lpBytesPerSector)); } } __except (EXCEPTION_EXECUTE_HANDLER) { @@ -1411,12 +1405,10 @@ HOOKDEF(BOOL, WINAPI, GetDiskFreeSpaceW, ) { BOOL ret = Old_GetDiskFreeSpaceW(lpRootPathName, lpSectorsPerCluster, lpBytesPerSector, lpNumberOfFreeClusters, lpTotalNumberOfClusters); LOQ_bool("filesystem", "u", "RootPathName", lpRootPathName); - - /* Fake harddrive size to 256GB */ if (!g_config.no_stealth) { __try { if (lpTotalNumberOfClusters && lpSectorsPerCluster && lpBytesPerSector && *lpSectorsPerCluster && *lpBytesPerSector) { - *lpTotalNumberOfClusters = (DWORD)(256060514304L / (*lpSectorsPerCluster * *lpBytesPerSector)); + *lpTotalNumberOfClusters = (DWORD)((SPOOFED_DISK_SIZE - RECOVERY_PARTITION_SIZE) / (*lpSectorsPerCluster * *lpBytesPerSector)); } } __except (EXCEPTION_EXECUTE_HANDLER) { @@ -1639,4 +1631,4 @@ HOOKDEF(DWORD, WINAPI, RmStartSession, LOQ_ntstatus("filesystem", ""); return ret; -} +} \ No newline at end of file diff --git a/hook_misc.c b/hook_misc.c index 7aea0922..1fac691e 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -763,8 +763,8 @@ HOOKDEF(void, WINAPI, GetSystemInfo, Old_GetSystemInfo(lpSystemInfo); - if (!g_config.no_stealth && lpSystemInfo->dwNumberOfProcessors < 4) - lpSystemInfo->dwNumberOfProcessors = 4; + if (!g_config.no_stealth && lpSystemInfo->dwNumberOfProcessors < SPOOFED_CPU_CORE_NUM) + lpSystemInfo->dwNumberOfProcessors = SPOOFED_CPU_CORE_NUM; LOQ_void("misc", ""); @@ -817,7 +817,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtQuerySystemInformation, if (!g_config.no_stealth && SystemInformationClass == SystemBasicInformation && SystemInformationLength >= sizeof(SYSTEM_BASIC_INFORMATION) && NT_SUCCESS(ret)) { PSYSTEM_BASIC_INFORMATION p = (PSYSTEM_BASIC_INFORMATION)SystemInformation; - p->NumberOfProcessors = 4; + p->NumberOfProcessors = SPOOFED_CPU_CORE_NUM; } /* This is nearly arbitrary and simply designed to test whether the Upatre author(s) or others @@ -1912,4 +1912,4 @@ HOOKDEF(BOOL, WINAPI, EnumDisplayDevicesW, } LOQ_bool("misc", "u", "DeviceString", lpDisplayDevice->DeviceString); return ret; -} +} \ No newline at end of file From 36f03fc89000d05b9062a25176a9b3ca870c3fc9 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 23 May 2025 16:50:45 +0100 Subject: [PATCH 028/148] Debugger: fix br1 (break on return) config option parsing (config.c) --- config.c | 79 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/config.c b/config.c index 55eae563..e0715656 100644 --- a/config.c +++ b/config.c @@ -823,43 +823,64 @@ void parse_config_line(char* line) } } else if (!stricmp(key, "br1")) { - int delta=0; - p = strchr(value, '+'); - if (p) { - delta = strtoul(p+1, NULL, 0); - DebugOutput("Config: Delta 0x%x.\n", delta); + p = strchr(value, ':'); + if (p && *(p+1) == ':') { + g_config.br1 = 0; *p = '\0'; + *(p+1) = '\0'; + HANDLE Module = NULL; + if (!stricmp(value, "capemon")) + Module = (HANDLE)g_our_dll_base; + else + Module = GetModuleHandle(value); + g_config.break_on_apiname = strdup(p+2); + g_config.break_on_modname = strdup(value); + if (Module) + g_config.br1 = GetProcAddress(Module, p+2); + else + DebugOutput("Config: Failed to get base for module (%s).\n", g_config.break_on_modname); + if (g_config.br1) { + g_config.debugger = 1; + g_config.bpva1 = 1; + DebugOutput("Config: br1 set to 0x%p (%s::%s).\n", g_config.br1, g_config.break_on_modname, g_config.break_on_apiname); + } + else if (Module) { + unsigned int delta = strtoul(p+2, NULL, 0); + if (delta) { + g_config.br1 = (PBYTE)Module + delta; + g_config.debugger = 1; + g_config.bpva1 = 1; + DebugOutput("Config: br1 set to 0x%p (%s::%s).\n", g_config.br1, g_config.break_on_modname, g_config.break_on_apiname); + } + else + DebugOutput("Config: Failed to get address for function %s::%s\n", g_config.break_on_modname, p+2); + } } else { - p = strchr(value, '-'); + int delta=0; + p = strchr(value, '+'); if (p) { - delta = - (int)strtoul(p+1, NULL, 0); + delta = strtoul(p+1, NULL, 0); DebugOutput("Config: Delta 0x%x.\n", delta); *p = '\0'; } - } - g_config.br1 = (PVOID)(DWORD_PTR)strtoul(value, NULL, 0); - if (g_config.br1) { - g_config.debugger = 1; - if (delta) { - DebugOutput("Config: br1 was 0x%x (delta 0x%x).\n", g_config.br1, delta); - g_config.br1 = (PVOID)(DWORD_PTR)((PUCHAR)g_config.br1 + delta); + else { + p = strchr(value, '-'); + if (p) { + delta = - (int)strtoul(p+1, NULL, 0); + DebugOutput("Config: Delta 0x%x.\n", delta); + *p = '\0'; + } + } + g_config.br1 = (PVOID)(DWORD_PTR)strtoul(value, NULL, 0); + if (g_config.br1) { + g_config.debugger = 1; + if (delta) { + DebugOutput("Config: br1 was 0x%x (delta 0x%x).\n", g_config.br1, delta); + g_config.br1 = (PVOID)(DWORD_PTR)((PUCHAR)g_config.br1 + delta); + } + DebugOutput("Config: br1 set to 0x%x (break-on-return)\n", g_config.br1); } - DebugOutput("Config: br1 set to 0x%x (break-on-return)\n", g_config.br1); - } - } - else if (!stricmp(key, "br2")) { - g_config.br2 = (PVOID)(DWORD_PTR)strtoul(value, NULL, 0); - if (g_config.br2) { - g_config.debugger = 1; - DebugOutput("Config: br2 set to 0x%x (break-on-return)\n", g_config.br2); - } - } - else if (!stricmp(key, "br3")) { - g_config.br3 = (PVOID)(DWORD_PTR)strtoul(value, NULL, 0); - if (g_config.br3) { - g_config.debugger = 1; - DebugOutput("Config: br3 set to 0x%x (break-on-return)\n", g_config.br3); } } else if (!stricmp(key, "sysbp")) { From f01664d532f6f68113c8fb388d5c7ce275c0840c Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 23 May 2025 16:54:51 +0100 Subject: [PATCH 029/148] Trace: do not wrap GetExportNameByAddress() in try/catch and do not use StepOverRegister in BreakOnReturnCallback() --- CAPE/Trace.c | 39 ++++++--------------------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 9e56e056..4040da37 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -45,7 +45,6 @@ extern char *convert_address_to_dll_name_and_offset(ULONG_PTR addr, unsigned int extern BOOL is_in_dll_range(ULONG_PTR addr); extern DWORD_PTR FileOffsetToVA(DWORD_PTR ModuleBase, DWORD_PTR dwOffset); extern DWORD_PTR GetEntryPointVA(DWORD_PTR ModuleBase); -extern PCHAR ScyllaGetExportNameByAddress(PVOID Address, PCHAR* ModuleName); extern ULONG_PTR g_our_dll_base; extern BOOL inside_hook(LPVOID Address); extern void loq(int index, const char *category, const char *name, @@ -2101,18 +2100,7 @@ BOOL Trace(struct _EXCEPTION_POINTERS* ExceptionInfo) DebuggerOutput("\n"); } - PCHAR FunctionName = NULL; - __try - { - FunctionName = GetExportNameByAddress(CIP); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - DebugOutput("Trace: Error dereferencing instruction pointer 0x%p.\n", CIP); - FunctionName = NULL; - } ModuleName = convert_address_to_dll_name_and_offset((ULONG_PTR)CIP, &DllRVA); - if (ModuleName) { if (CIP == (PVOID)((PCHAR)_KiUserExceptionDispatcher+1)) @@ -2126,6 +2114,7 @@ BOOL Trace(struct _EXCEPTION_POINTERS* ExceptionInfo) PVOID ImageBase = (PVOID)((PUCHAR)CIP - DllRVA); if (FilterTrace) DebuggerOutput("\n"); + PCHAR FunctionName = GetExportNameByAddress(CIP); if (FunctionName) { DebuggerOutput("Break at 0x%p in %s::%s (RVA 0x%x, thread %d, Stack 0x%p-0x%p, ImageBase 0x%p)\n", CIP, ModuleName, FunctionName, DllRVA, GetCurrentThreadId(), get_stack_bottom(), get_stack_top(), ImageBase); @@ -2333,19 +2322,10 @@ BOOL BreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINT { if (!PreviousModuleName || strncmp(ModuleName, PreviousModuleName, strlen(ModuleName))) { - PCHAR FunctionName; - PVOID ImageBase = (PVOID)((PUCHAR)CIP - DllRVA); - - __try - { - FunctionName = GetExportNameByAddress(CIP); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - DebugOutput("BreakpointCallback: Error dereferencing instruction pointer 0x%p.\n", CIP); - } if (FilterTrace) DebuggerOutput("\n"); + PVOID ImageBase = (PVOID)((PUCHAR)CIP - DllRVA); + PCHAR FunctionName = GetExportNameByAddress(CIP); if (FunctionName) { DebuggerOutput("Break at 0x%p in %s::%s (RVA 0x%x, thread %d, Stack 0x%p-0x%p, ImageBase 0x%p)\n", CIP, ModuleName, FunctionName, DllRVA, GetCurrentThreadId(), get_stack_bottom(), get_stack_top(), ImageBase); @@ -2577,6 +2557,7 @@ BOOL SoftwareBreakpointCallback(struct _EXCEPTION_POINTERS* ExceptionInfo) BOOL BreakOnReturnCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS* ExceptionInfo) { PVOID CIP; + int Register; unsigned int DllRVA; BreakpointsHit = TRUE; @@ -2593,22 +2574,14 @@ BOOL BreakOnReturnCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_PO if (ModuleName) { - PCHAR FunctionName; - __try - { - FunctionName = GetExportNameByAddress(CIP); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - DebugOutput("BreakOnReturnCallback: Error dereferencing instruction pointer 0x%p.\n", CIP); - } + PCHAR FunctionName = GetExportNameByAddress(CIP); if (FunctionName) DebuggerOutput("\nBreak at 0x%p in %s::%s (RVA 0x%x, thread %d), releasing until return address 0x%p\n", CIP, ModuleName, FunctionName, DllRVA, GetCurrentThreadId(), ReturnAddress); else DebuggerOutput("\nBreak at 0x%p in %s (RVA 0x%x, thread %d), releasing until return address 0x%p\n", CIP, ModuleName, DllRVA, GetCurrentThreadId(), ReturnAddress); } - if (!ContextSetNextAvailableBreakpoint(ExceptionInfo->ContextRecord, &StepOverRegister, 0, (BYTE*)ReturnAddress, BP_EXEC, 1, BreakpointCallback)) + if (!ContextSetNextAvailableBreakpoint(ExceptionInfo->ContextRecord, &Register, 0, (BYTE*)ReturnAddress, BP_EXEC, 1, BreakpointCallback)) DebugOutput("BreakOnReturnCallback: Failed to set breakpoint on return address at 0x%p.\n", ReturnAddress); ReturnAddress = NULL; From 2ff418799e5a15138276efdae44080cbd24b9c9e Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 3 Jun 2025 12:52:49 +0100 Subject: [PATCH 030/148] Fix bug in retarget_relative_displacement() relative offset calculation --- hooking_64.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/hooking_64.c b/hooking_64.c index 8e908935..ef70d9c7 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -172,23 +172,23 @@ static int addr_is_in_range(ULONG_PTR addr, const unsigned char *buf, DWORD size static void retarget_relative_displacement(unsigned char **tramp, unsigned char **addr, _DInst *insn) { - unsigned short length = insn->size; - unsigned char *newtramp = *tramp; - unsigned char *newaddr = *addr; - - unsigned char offset = (unsigned char)(length - insn->imm_encoded_size - sizeof(int)); - ULONG_PTR target = (ULONG_PTR)(newaddr + length + *(int *)(newaddr + offset)); - int64_t rel = (int64_t)(target - (ULONG_PTR)(newtramp + length)); - - if (rel >= INT_MIN && rel <= INT_MAX) { - while (length-- != 0) - *newtramp++ = *newaddr++; - *(int64_t *)(newtramp - insn->imm_encoded_size - sizeof(int)) = rel; - } + unsigned short length = insn->size; + unsigned char *newtramp = *tramp; + unsigned char *newaddr = *addr; + + unsigned char offset = (unsigned char)(length - insn->imm_encoded_size - sizeof(int)); + ULONG_PTR target = (ULONG_PTR)(newaddr + length + *(int *)(newaddr + offset)); + int64_t rel = (int64_t)(target - (ULONG_PTR)newtramp); + + if (rel >= INT_MIN && rel <= INT_MAX) { + while (length-- != 0) + *newtramp++ = *newaddr++; + *(int64_t *)(newtramp - insn->imm_encoded_size - sizeof(int)) = rel; + } else { // mov r11, far target - *((WORD*)newtramp)++ = 0xBB49; - *((ULONG_PTR *)newtramp)++ = (ULONG_PTR)target; + *((WORD*)newtramp)++ = 0xBB49; + *((ULONG_PTR *)newtramp)++ = (ULONG_PTR)target; if (*newaddr == 0xE8) { // replace call near target with call far r11 *((WORD*)newtramp)++ = 0xFF41; From 02fe0bd4f23640ce72495761eca657aecf08dfee Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 3 Jun 2025 17:18:25 +0100 Subject: [PATCH 031/148] Correction to previous commit - fix the bad fix --- hooking_64.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooking_64.c b/hooking_64.c index ef70d9c7..d3bb3427 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -178,12 +178,12 @@ static void retarget_relative_displacement(unsigned char **tramp, unsigned char unsigned char offset = (unsigned char)(length - insn->imm_encoded_size - sizeof(int)); ULONG_PTR target = (ULONG_PTR)(newaddr + length + *(int *)(newaddr + offset)); - int64_t rel = (int64_t)(target - (ULONG_PTR)newtramp); + int64_t rel = (int64_t)(target - (ULONG_PTR)(newtramp + length)); if (rel >= INT_MIN && rel <= INT_MAX) { while (length-- != 0) *newtramp++ = *newaddr++; - *(int64_t *)(newtramp - insn->imm_encoded_size - sizeof(int)) = rel; + *(int *)(newtramp - insn->imm_encoded_size - sizeof(int)) = (int)rel; } else { // mov r11, far target From e627d575c8c06ab972d4023d62e93e74e4652d21 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 4 Jun 2025 11:03:33 +0100 Subject: [PATCH 032/148] Add comments to retarget_relative_displacement() (hooking_64.c) --- hooking_64.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hooking_64.c b/hooking_64.c index d3bb3427..2d48a309 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -182,7 +182,7 @@ static void retarget_relative_displacement(unsigned char **tramp, unsigned char if (rel >= INT_MIN && rel <= INT_MAX) { while (length-- != 0) - *newtramp++ = *newaddr++; + *newtramp++ = *newaddr++; *(int *)(newtramp - insn->imm_encoded_size - sizeof(int)) = (int)rel; } else { @@ -200,9 +200,12 @@ static void retarget_relative_displacement(unsigned char **tramp, unsigned char *newtramp++ = 0xE3; } else if (insn->flags & FLAG_RIP_RELATIVE) { + // rewrite instruction to use rll if ((*newaddr & 0xF0) == 0x40) + // modify REX prefix to use r11 *newtramp++ = *newaddr++ | 0x41; *newtramp++ = *newaddr++; + // modify ModR/M byte to use R11 *newtramp++ = (*newaddr++ & 0xF8) | 3; } newaddr += 4; @@ -260,7 +263,6 @@ static int hook_create_trampoline(unsigned char *addr, int len, if (addr[0] == 0xe9 && len > 0) goto error; } - else if (addr[0] == 0xeb) { target = get_short_rel_target(addr); if (addr_is_in_range(target, origaddr, stoleninstrlen)) @@ -279,9 +281,8 @@ static int hook_create_trampoline(unsigned char *addr, int len, } // return instruction, indicates end of basic block as well, so we // have to check if we already have enough space for our hook.. - else if ((addr[0] == 0xc3 || addr[0] == 0xc2) && len > 0) { + else if ((addr[0] == 0xc3 || addr[0] == 0xc2) && len > 0) goto error; - } else { // copy the instruction directly to the trampoline while (length-- != 0) { From 31c7a34f266dd144911676f6adb4d3cb05929ee2 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 6 Jun 2025 12:25:37 +0100 Subject: [PATCH 033/148] Fix format string vulnerability in debugger StringsOutput() function --- CAPE/Output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAPE/Output.c b/CAPE/Output.c index e2ca289b..db547ada 100644 --- a/CAPE/Output.c +++ b/CAPE/Output.c @@ -344,7 +344,7 @@ void StringsOutput(_In_ LPCTSTR lpOutputString, ...) Character++; } - DebuggerOutput(StringsLine); + DebuggerOutput("%.64s", StringsLine); if (strlen(StringsLine) + 1 < MAX_PATH) *Character = 0x0a; From de0e47d551941374fa60b2de62acdfc541c5a73a Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 10 Jun 2025 16:09:21 +0100 Subject: [PATCH 034/148] WMI hooks: add handling for VT_NULL and enable WMI_Get logging --- hook_wmi.c | 3 +-- log.c | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/hook_wmi.c b/hook_wmi.c index 44b9b5fc..f8b7eedf 100644 --- a/hook_wmi.c +++ b/hook_wmi.c @@ -11,8 +11,7 @@ HOOKDEF(HRESULT, WINAPI, WMI_Get, ) { HRESULT ret; ret = Old_WMI_Get(_this, wszName, lFlags, pVal, pType, plFlavor); - LOQ_hresult("system", "u", "Name", wszName); - //LOQ_hresult("system", "un", "Name", wszName, "Value", pVal); + LOQ_hresult("system", "un", "Name", wszName, "Value", pVal); return ret; } diff --git a/log.c b/log.c index d039fb94..1cfbbdc2 100644 --- a/log.c +++ b/log.c @@ -294,6 +294,9 @@ static void log_variant(VARIANT* var) { char log_msg[32]; __try { switch (var->vt) { + case VT_NULL: + log_string("NULL", -1); + break; case 74: // Undocumented, likely internal Variant Type in vbscript engine // Observed with: @@ -404,7 +407,7 @@ static void log_variant(VARIANT* var) { break; default: snprintf(log_msg, 32, "Unhandled VARIANT Type: %hu", var->vt); - log_string((const char*)var->vt, -1); + //log_string((const char*)var->vt, -1); break; } } From 8ca9c5653b158b34ea4182a8a76832d17cbca24c Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 25 Jun 2025 15:56:23 +0100 Subject: [PATCH 035/148] Trace: don't print empty lines to strings output in DoOutputString() --- CAPE/Trace.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 4040da37..8e6e1494 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -272,8 +272,6 @@ void DoOutputString(PVOID PossibleString) StringsOutput("%.256ws...", (PWCHAR)OutputBufferW); else if (Size > 1) StringsOutput("%.256ws", (PWCHAR)OutputBufferW); - else - StringsOutput(""); } } From 6b66e546fe913cd5eba6d92d7a5fd7267c4f0ae8 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 25 Jun 2025 16:00:29 +0100 Subject: [PATCH 036/148] Trace: add flag changes to trace output as part of OutputRegisterChanges() --- CAPE/Trace.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 8e6e1494..ada9672a 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -585,6 +585,36 @@ PVOID GetTarget(PCONTEXT Context, _DecodedInst DecodedInstruction) return Target; } +void OutputFlagChanges(DWORD OldFlags, DWORD NewFlags) +{ + if (OldFlags == NewFlags) return; + + char FlagChanges[32] = {0}; + int pos = 0; + + #define CHECK_FLAG(flag, ch) \ + if ((OldFlags & flag) != (NewFlags & flag)) \ + FlagChanges[pos++] = (NewFlags & flag) ? toupper(ch) : tolower(ch) + + CHECK_FLAG(FL_CF, 'c'); // Carry: C/c + CHECK_FLAG(FL_PF, 'p'); // Parity: P/p + CHECK_FLAG(FL_AF, 'a'); // Aux: A/a + CHECK_FLAG(FL_ZF, 'z'); // Zero: Z/z + CHECK_FLAG(FL_SF, 's'); // Sign: S/s + CHECK_FLAG(FL_TF, 't'); // Trap: T/t + CHECK_FLAG(FL_IF, 'i'); // Interrupt: I/i + CHECK_FLAG(FL_DF, 'd'); // Direction: D/d + CHECK_FLAG(FL_OF, 'o'); // Overflow: O/o + + #undef CHECK_FLAG + + if (pos > 0) + { + FlagChanges[pos] = '\0'; + DebuggerOutput(" %s", FlagChanges); + } +} + OutputRegisterChanges(PCONTEXT Context) { #ifdef _WIN64 @@ -768,6 +798,9 @@ OutputRegisterChanges(PCONTEXT Context) } } #endif + + OutputFlagChanges(LastContext.EFlags, Context->EFlags); + if (g_config.trace_times) { FILETIME CurrentTime; From fe05e4f9775dd5e0ac9592e1aca1ef0f543ff822 Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Mon, 14 Jul 2025 04:50:52 -0400 Subject: [PATCH 037/148] Better NtDeviceIoControlFile logging for ntsockets The code is kinda ugly with goto, but it's about as clean as I can get it to re-use existing log formatter in cases where exceptions may occur / sanity checks fail. --- hook_file.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++-- hook_file.h | 6 ++ misc.c | 104 +++++++++++++++++++ misc.h | 68 +++++++++++++ 4 files changed, 452 insertions(+), 8 deletions(-) diff --git a/hook_file.c b/hook_file.c index a7e1b8be..63ee4b25 100644 --- a/hook_file.c +++ b/hook_file.c @@ -26,6 +26,7 @@ along with this program. If not, see . #include "misc.h" #include "ignore.h" #include "lookup.h" +#include "hook_file.h" #include "config.h" #define DUMP_FILE_MASK ((GENERIC_ALL | GENERIC_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | MAXIMUM_ALLOWED) & ~SYNCHRONIZE) @@ -605,25 +606,290 @@ HOOKDEF(NTSTATUS, WINAPI, NtDeviceIoControlFile, __out PVOID OutputBuffer, __in ULONG OutputBufferLength ) { + lasterror_t lasterrors; + get_lasterrors(&lasterrors); ULONG_PTR length; - NTSTATUS ret = Old_NtDeviceIoControlFile(FileHandle, Event, - ApcRoutine, ApcContext, IoStatusBlock, IoControlCode, - InputBuffer, InputBufferLength, OutputBuffer, - OutputBufferLength); + ULONG origbufferloglen = min((ULONG)buffer_log_max, InputBufferLength); + PCHAR origbuffer = malloc(origbufferloglen); + BOOLEAN hasorigbuffer = TRUE; + NTSTATUS ret; + + // Save off a copy of the buffer before calling the hook, as it may not be the same after the call + if (origbuffer) { + __try { + memcpy(origbuffer, InputBuffer, origbufferloglen); + } + __except (EXCEPTION_EXECUTE_HANDLER) { + hasorigbuffer = FALSE; + } + } + + + /* + * Check for AFD_SEND, we need to aggregate the buffer before calling the hook as calling the hook will clobber InputBuffer + * Simply do the logic to generate the buffer we want to log here, we'll log it later with other IOCTL logging. + */ + PCHAR aggregated_send_payload = NULL; + ULONG send_payload_size_to_log = 0; + if (IoControlCode == IOCTL_AFD_SEND) { + __try { + if (InputBuffer && InputBufferLength >= sizeof(AFD_SEND_INFO)) { + PAFD_SEND_INFO sendInfo = (PAFD_SEND_INFO)InputBuffer; + if (sendInfo->AfdBufferArray && sendInfo->AfdBufferCount > 0) { + ULONG total_send_size = 0; + for (ULONG i = 0; i < sendInfo->AfdBufferCount; i++) { + total_send_size += sendInfo->AfdBufferArray[i].len; + } + + if (total_send_size > 0) { + send_payload_size_to_log = min((ULONG)buffer_log_max, total_send_size); + aggregated_send_payload = (PCHAR)malloc(send_payload_size_to_log); + if (aggregated_send_payload) { + PCHAR current_pos = aggregated_send_payload; + ULONG bytes_copied = 0; + for (ULONG i = 0; i < sendInfo->AfdBufferCount && bytes_copied < send_payload_size_to_log; i++) { + PAFD_WSABUF current_source_buf = &sendInfo->AfdBufferArray[i]; + ULONG bytes_to_copy = min(current_source_buf->len, send_payload_size_to_log - bytes_copied); + if (bytes_to_copy > 0) { + memcpy(current_pos, current_source_buf->buf, bytes_to_copy); + current_pos += bytes_to_copy; + bytes_copied += bytes_to_copy; + } + } + } + } + } + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // On exception, null out the buffer (if needed) so we can log generically later + if (aggregated_send_payload) { + free(aggregated_send_payload); + aggregated_send_payload = NULL; + } + } + } + set_lasterrors(&lasterrors); + + ret = Old_NtDeviceIoControlFile(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, IoControlCode, InputBuffer, InputBufferLength, + OutputBuffer, OutputBufferLength); if (NT_SUCCESS(ret)) length = IoStatusBlock->Information; else length = 0; - LOQ_ntstatus("device", "phbb", "FileHandle", FileHandle, - "IoControlCode", IoControlCode, - "InputBuffer", InputBufferLength, InputBuffer, - "OutputBuffer", length, OutputBuffer); + + get_lasterrors(&lasterrors); + + wchar_t* fname = NULL; + fname = calloc(32768, sizeof(wchar_t)); + path_from_handle(FileHandle, fname, 32768); + + switch (IoControlCode) { + case IOCTL_AFD_BIND: + if (InputBufferLength >= sizeof(AFD_BindDataStruct) && hasorigbuffer) { + PAFD_BindDataStruct buf = (PAFD_BindDataStruct)origbuffer; + __try { + in_sockaddr* sockAddr = &buf->SockAddr; + char ipString[16]; + our_inet_ntop(AF_INET, &sockAddr->sin_addr, ipString, sizeof(ipString)); + unsigned short port = our_ntohs(sockAddr->sin_port); + LOQ_ntstatus( + "network", "pFhsi", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "ip", ipString, + "port", port + ); + break; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // If we're here, just use generic logging for NtDeviceIoControlFile + goto generic_log; + } + } + else { + goto generic_log; + } + + case IOCTL_AFD_CONNECT: + if (InputBufferLength >= sizeof(AFD_ConnectDataStruct) && hasorigbuffer) { + AFD_ConnectDataStruct* buf = (AFD_ConnectDataStruct*)origbuffer; + __try { + in_sockaddr* sockAddr = &buf->SockAddr; + char ipString[16]; + our_inet_ntop(AF_INET, &sockAddr->sin_addr, ipString, sizeof(ipString)); + unsigned short port = our_ntohs(sockAddr->sin_port); + LOQ_ntstatus( + "network", "pFhsi", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "ip", ipString, + "port", port + ); + break; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // If we're here, just use generic logging for NtDeviceIoControlFile + goto generic_log; + } + } + else { + // Unexpected InputBufferLength for this IOCTL, goto generic log + goto generic_log; + } + + case IOCTL_AFD_RECV: + __try { + DWORD wait_status = -1; // Init to -1 as WAIT_OBJECT_0 is 0 + if (InputBuffer && InputBufferLength >= sizeof(AFD_RECV_INFO)) { + if (ret == STATUS_PENDING && Event) { + /* + * We make some assumptions here; If we have an event it is an async call so we also assume: + * 1) The callee reset the event prior to calling this NtDeviceIoControlFile API + * 2) The callee will use NtWaitForSingleObject or similar, after calling NtDeviceIoControlFile + * + * NtWaitForSingleObject will wait on a handle, and if the callee is expecting to use NtWaitForSingleObject + * (as done in NTSockets) we'll end up deadlocking unless we also use SetEvent. This is why we use our own + * function to check for the event being signaled as it we do not call any APIs which interfere with the + * status of the event itself, so it is safe for the above assumptions as well as cases where the assumption + * may be wrong. + */ + wait_status = wait_for_event_to_be_signaled(Event, 5000); // Allow 5 seconds of time for the event to trigger + if (wait_status == WAIT_OBJECT_0) { + ret = IoStatusBlock->Status; + if (NT_SUCCESS(ret)) + length = IoStatusBlock->Information; + else + length = 0; + } + } + if (length == 0) { + /* + * This can happen if we have a NTSTATUS of 0x00000103 (STATUS_PENDING) + * Unideal, likely in the background kernel has provided the recv buffer to the API + */ + if (wait_status == WAIT_TIMEOUT) { + LOQ_ntstatus( + "network", "pFhs", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "Event", "Timeout waiting for recv, buffer will likely missing from API logs" + ); + } + else { + LOQ_ntstatus( + "network", "pFhs", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "Event", "Zero-byte receive" + ); + } + break; + } + + PAFD_RECV_INFO recvInfo = (PAFD_RECV_INFO)InputBuffer; + if (recvInfo->AfdBufferArray && recvInfo->AfdBufferCount > 0) { + ULONG total_payload_to_log = min((ULONG)buffer_log_max, (ULONG)length); + PCHAR aggregated_payload = (PCHAR)malloc(total_payload_to_log); + if (!aggregated_payload) { + // Sanity check + goto generic_log; + } + + PCHAR current_pos = aggregated_payload; + ULONG bytes_copied = 0; + ULONG bytes_remaining_from_total = (ULONG)length; + for (ULONG i = 0; i < recvInfo->AfdBufferCount && bytes_copied < total_payload_to_log; i++) { + PAFD_WSABUF current_source_buf = &recvInfo->AfdBufferArray[i]; + ULONG bytes_in_source = min(bytes_remaining_from_total, current_source_buf->len); + ULONG bytes_to_copy = min(bytes_in_source, total_payload_to_log - bytes_copied); + if (bytes_to_copy > 0) { + memcpy(current_pos, current_source_buf->buf, bytes_to_copy); + current_pos += bytes_to_copy; + bytes_copied += bytes_to_copy; + } + bytes_remaining_from_total -= bytes_in_source; + } + + LOQ_ntstatus( + "network", "pFhbi", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "buffer", (size_t)bytes_copied, aggregated_payload, + "length", length + ); + free(aggregated_payload); + break; + } + } + else { + // If the initial InputBuffer validation fails, go to the generic logger. + goto generic_log; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // On exception, log generically + goto generic_log; + } + + case IOCTL_AFD_SEND: + // We parsed the send buffer prior to calling the hook, if the buffer is non-null, log it, otherwise log generically + if (aggregated_send_payload) { + LOQ_ntstatus( + "network", "pFhbi", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "buffer", (size_t)send_payload_size_to_log, aggregated_send_payload, + "length", (ULONG)length // Log the actual bytes sent from the result + ); + free(aggregated_send_payload); + break; + } + else { + goto generic_log; + } + default: + generic_log: + if (fname) { + LOQ_ntstatus( + "device", "pFhbb", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "InputBuffer", InputBufferLength, InputBuffer, + "OutputBuffer", length, OutputBuffer + ); + } + else { + LOQ_ntstatus( + "device", "phbb", + "HandleName", fname, + "IoControlCode", IoControlCode, + "InputBuffer", InputBufferLength, InputBuffer, + "OutputBuffer", length, OutputBuffer + ); + } + } if (!g_config.no_stealth && NT_SUCCESS(ret) && OutputBuffer) perform_device_fakery(OutputBuffer, (ULONG)length, IoControlCode); + if (origbuffer) + free(origbuffer); + + if (fname) + free(fname); + + set_lasterrors(&lasterrors); + return ret; } diff --git a/hook_file.h b/hook_file.h index a8dbb063..8b8678d2 100644 --- a/hook_file.h +++ b/hook_file.h @@ -21,3 +21,9 @@ void file_init(); void file_close(HANDLE file_handle); void handle_duplicate(HANDLE old_handle, HANDLE new_handle); void remove_file_from_log_tracking(HANDLE fhandle); + +#define IOCTL_AFD_BIND 0x00012003 +#define IOCTL_AFD_CONNECT 0x00012007 +#define IOCTL_AFD_SEND 0x0001201f +#define IOCTL_AFD_RECV 0x00012017 +#define IOCTL_WMIQUERYALLDATA 0x00224000 \ No newline at end of file diff --git a/misc.c b/misc.c index e224e72a..f16a78b1 100644 --- a/misc.c +++ b/misc.c @@ -51,6 +51,7 @@ _NtFreeVirtualMemory pNtFreeVirtualMemory; _LdrRegisterDllNotification pLdrRegisterDllNotification; _RtlNtStatusToDosError pRtlNtStatusToDosError; _RtlCompareMemory pRtlCompareMemory; +_NtQueryEvent pNtQueryEvent; void resolve_runtime_apis(void) { @@ -2358,3 +2359,106 @@ void prevent_module_reloading(PVOID *BaseAddress) { free(absolutepath); } + +static size_t append_octet(char** p, size_t* remaining, unsigned char octet) { + char* start = *p; + size_t written_chars = 0; + + // A temporary buffer to hold the characters of the octet (max 3 chars for 0-255) + char temp_buffer[3]; + int i = 0; + + // Handle 0 + if (octet == 0) { + temp_buffer[i++] = '0'; + } + else { + // Extract digits in reverse order + unsigned char val = octet; + while (val > 0) { + temp_buffer[i++] = (val % 10) + '0'; + val /= 10; + } + } + + // Write the digits to the destination buffer in the correct order + written_chars = i; + if (*remaining <= written_chars) { // Check if there's enough space (including null terminator) + return 0; + } + + while (i > 0) { + *(*p)++ = temp_buffer[--i]; + } + + *remaining -= written_chars; + return written_chars; +} + +const char* our_inet_ntop(int af, const void* src, char* dst, size_t size) { + if (src == NULL || dst == NULL) { + return NULL; + } + + if (af != AF_INET) { + return NULL; + } + + if (size < OUR_INET_ADDRSTRLEN) { + return NULL; + } + + // Cast the source to a pointer to raw bytes (unsigned char). + const unsigned char* p_addr = (const unsigned char*)src; + char* p = dst; + size_t remaining = size; + + for (int i = 0; i < 4; ++i) { + // Read the i-th byte directly from memory. This avoids all endianness problems. + unsigned char octet = p_addr[i]; + if (append_octet(&p, &remaining, octet) == 0) { + return NULL; + } + + if (i < 3) { + if (remaining <= 1) { + return NULL; + } + *p++ = '.'; + remaining--; + } + } + + *p = '\0'; + return dst; +} + +unsigned short our_ntohs(unsigned short netshort) { + return (netshort >> 8) | (netshort << 8); +} + +DWORD wait_for_event_to_be_signaled(HANDLE hEvent, DWORD dwTimeout) { + ULONGLONG startTime = raw_gettickcount(); + ULONGLONG currentTime; + NTSTATUS status; + EVENT_BASIC_INFORMATION eventInfo; + ULONG returnLength; + + while (TRUE) { + status = pNtQueryEvent(hEvent, EventBasicInformation, &eventInfo, sizeof(eventInfo), &returnLength); + if (status == STATUS_SUCCESS) { + // Check the state. 1 means signaled. + if (eventInfo.EventState == 1) { + return WAIT_OBJECT_0; + } + } + + // Check for timeout. + currentTime = raw_gettickcount(); + if ((currentTime - startTime) > dwTimeout) { + return WAIT_TIMEOUT; + } + + raw_sleep(250); + } +} \ No newline at end of file diff --git a/misc.h b/misc.h index 19c0af7c..014d377d 100644 --- a/misc.h +++ b/misc.h @@ -126,6 +126,28 @@ typedef SIZE_T (WINAPI *_RtlCompareMemory)( _In_ SIZE_T Length ); +typedef enum _EVENT_INFORMATION_CLASS { + EventBasicInformation +} EVENT_INFORMATION_CLASS; + +typedef enum _EVENT_TYPE { + NotificationEvent, // manual-reset event + SynchronizationEvent // auto-reset event +} EVENT_TYPE; + +typedef struct _EVENT_BASIC_INFORMATION { + EVENT_TYPE EventType; + LONG EventState; +} EVENT_BASIC_INFORMATION, * PEVENT_BASIC_INFORMATION; + +typedef NTSTATUS(NTAPI* _NtQueryEvent)( + _In_ HANDLE EventHandle, + _In_ EVENT_INFORMATION_CLASS EventInformationClass, + _Out_writes_bytes_(EventInformationLength) PVOID EventInformation, + _In_ ULONG EventInformationLength, + _Out_opt_ PULONG ReturnLength + ); + _NtSetInformationProcess pNtSetInformationProcess; _NtMapViewOfSection pNtMapViewOfSection; _NtUnmapViewOfSection pNtUnmapViewOfSection; @@ -269,3 +291,49 @@ struct envstruct { }; const char* GetLanguageName(LANGID langID); + +#define OUR_INET_ADDRSTRLEN 16 + +typedef struct _in_sockaddr { + short sin_family; // e.g. AF_INET + unsigned short sin_port; // e.g. htons(3490) + unsigned long sin_addr; // see struct in_addr, below + char sin_zero[8]; // zero this if you want to +} in_sockaddr; + +typedef struct _AFD_ConnectDataStruct +{ + DWORD dwUnknown1; + DWORD dwUnknown2; + DWORD dwUnknown3; + in_sockaddr SockAddr; +} AFD_ConnectDataStruct; + +typedef struct _AFD_BindDataStruct +{ + DWORD dwUnknown1; + in_sockaddr SockAddr; +} AFD_BindDataStruct, * PAFD_BindDataStruct; + +typedef struct _AFD_WSABUF { + ULONG len; + PCHAR buf; +} AFD_WSABUF, * PAFD_WSABUF; + +typedef struct _AFD_RECV_INFO { + PAFD_WSABUF AfdBufferArray; + ULONG AfdBufferCount; + ULONG AfdFlags; + ULONG TdiFlags; +} AFD_RECV_INFO, * PAFD_RECV_INFO; + +typedef struct _AFD_SEND_INFO { + PAFD_WSABUF AfdBufferArray; + ULONG AfdBufferCount; + ULONG TdiFlags; +} AFD_SEND_INFO, * PAFD_SEND_INFO; + +static size_t append_octet(char** p, size_t* remaining, unsigned char octet); +const char* our_inet_ntop(int af, const void* src, char* dst, size_t size); +unsigned short our_ntohs(unsigned short netshort); +DWORD wait_for_event_to_be_signaled(HANDLE hEvent, DWORD dwTimeout); \ No newline at end of file From b5b1349626e316d016bc3e0942a600d08566d12d Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Mon, 14 Jul 2025 05:06:22 -0400 Subject: [PATCH 038/148] Fix Gemini suggestions --- hook_file.c | 9 ++++++--- hook_file.h | 2 +- misc.c | 2 +- misc.h | 3 +-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/hook_file.c b/hook_file.c index 63ee4b25..0a667450 100644 --- a/hook_file.c +++ b/hook_file.c @@ -611,13 +611,14 @@ HOOKDEF(NTSTATUS, WINAPI, NtDeviceIoControlFile, ULONG_PTR length; ULONG origbufferloglen = min((ULONG)buffer_log_max, InputBufferLength); PCHAR origbuffer = malloc(origbufferloglen); - BOOLEAN hasorigbuffer = TRUE; + BOOLEAN hasorigbuffer = FALSE; NTSTATUS ret; // Save off a copy of the buffer before calling the hook, as it may not be the same after the call if (origbuffer) { __try { memcpy(origbuffer, InputBuffer, origbufferloglen); + hasorigbuffer = TRUE; } __except (EXCEPTION_EXECUTE_HANDLER) { hasorigbuffer = FALSE; @@ -684,7 +685,9 @@ HOOKDEF(NTSTATUS, WINAPI, NtDeviceIoControlFile, wchar_t* fname = NULL; fname = calloc(32768, sizeof(wchar_t)); - path_from_handle(FileHandle, fname, 32768); + if (fname) { + path_from_handle(FileHandle, fname, 32768); + } switch (IoControlCode) { case IOCTL_AFD_BIND: @@ -871,7 +874,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtDeviceIoControlFile, else { LOQ_ntstatus( "device", "phbb", - "HandleName", fname, + "FileHandle", FileHandle, "IoControlCode", IoControlCode, "InputBuffer", InputBufferLength, InputBuffer, "OutputBuffer", length, OutputBuffer diff --git a/hook_file.h b/hook_file.h index 8b8678d2..25d63f14 100644 --- a/hook_file.h +++ b/hook_file.h @@ -26,4 +26,4 @@ void remove_file_from_log_tracking(HANDLE fhandle); #define IOCTL_AFD_CONNECT 0x00012007 #define IOCTL_AFD_SEND 0x0001201f #define IOCTL_AFD_RECV 0x00012017 -#define IOCTL_WMIQUERYALLDATA 0x00224000 \ No newline at end of file +#define IOCTL_WMIQUERYALLDATA 0x00224000 diff --git a/misc.c b/misc.c index f16a78b1..37e73b4d 100644 --- a/misc.c +++ b/misc.c @@ -2461,4 +2461,4 @@ DWORD wait_for_event_to_be_signaled(HANDLE hEvent, DWORD dwTimeout) { raw_sleep(250); } -} \ No newline at end of file +} diff --git a/misc.h b/misc.h index 014d377d..1d99cb3c 100644 --- a/misc.h +++ b/misc.h @@ -333,7 +333,6 @@ typedef struct _AFD_SEND_INFO { ULONG TdiFlags; } AFD_SEND_INFO, * PAFD_SEND_INFO; -static size_t append_octet(char** p, size_t* remaining, unsigned char octet); const char* our_inet_ntop(int af, const void* src, char* dst, size_t size); unsigned short our_ntohs(unsigned short netshort); -DWORD wait_for_event_to_be_signaled(HANDLE hEvent, DWORD dwTimeout); \ No newline at end of file +DWORD wait_for_event_to_be_signaled(HANDLE hEvent, DWORD dwTimeout); From b2bb4305370556f1351bf331b23be9387c46fc8c Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 22 Jul 2025 12:49:19 +0100 Subject: [PATCH 039/148] Improve NtGetContextThread & NtSetContextThread hooks to handle e.g. a2d4e1c831808d0a791608db40cd1e4df598e5fee4bac1b239d4f8194f8e2d4a (fixes #100) --- CAPE/Injection.c | 38 +++++++++++++++++++++++++++++-- hook_thread.c | 58 +++++++++++++----------------------------------- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index 248b0836..bed023fe 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -514,8 +514,11 @@ void DumpSectionViewsForHandle(HANDLE SectionHandle) return; } -void GetThreadContextHandler(DWORD Pid, LPCONTEXT Context) +void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context) { + DebugOutput("GetThreadContextHandler"); + DWORD Pid = pid_from_thread_handle(ThreadHandle); + if (Context && Context->ContextFlags & CONTEXT_CONTROL) { struct InjectionInfo *CurrentInjectionInfo = GetInjectionInfo(Pid); @@ -527,13 +530,44 @@ void GetThreadContextHandler(DWORD Pid, LPCONTEXT Context) CurrentInjectionInfo->StackPointer = (LPVOID)Context->Esp; #endif } + + if (g_config.debugger) + { + Context->Dr0 = 0; + Context->Dr1 = 0; + Context->Dr2 = 0; + Context->Dr3 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } } -void SetThreadContextHandler(DWORD Pid, const CONTEXT *Context) +void SetThreadContextHandler(HANDLE ThreadHandle, CONTEXT *Context) { if (!Context || !(Context->ContextFlags & CONTEXT_CONTROL)) return; + DWORD Pid = pid_from_thread_handle(ThreadHandle); + DWORD Tid = tid_from_thread_handle(ThreadHandle); + + DebugOutput("SetThreadContextHandler: Pid %d", GetCurrentProcessId()); + + if (g_config.debugger && Pid == GetCurrentProcessId()) + { + PTHREADBREAKPOINTS ThreadBreakpoints = GetThreadBreakpoints(Tid); + if (ThreadBreakpoints) + { + DebugOutput("SetThreadContextHandler: Protecting breakpoints for thread %d: 0x%p, 0x%p, 0x%p, 0x%p.\n", Tid, ThreadBreakpoints->BreakpointInfo[0].Address, ThreadBreakpoints->BreakpointInfo[1].Address, ThreadBreakpoints->BreakpointInfo[2].Address, ThreadBreakpoints->BreakpointInfo[3].Address); + ContextSetThreadBreakpointsEx(Context, ThreadBreakpoints, TRUE); + } +#ifdef DEBUG_COMMENTS + else + DebugOutput("SetThreadContextHandler hook: No breakpoints to protect for thread %d.\n", Tid); +#endif + } + else + DebugOutput("SetThreadContextHandler: not taken #1"); + MEMORY_BASIC_INFORMATION MemoryInfo; struct InjectionInfo *CurrentInjectionInfo = GetInjectionInfo(Pid); diff --git a/hook_thread.c b/hook_thread.c index e8cf802a..9cf1ddb8 100644 --- a/hook_thread.c +++ b/hook_thread.c @@ -31,8 +31,8 @@ along with this program. If not, see . extern _RtlNtStatusToDosError pRtlNtStatusToDosError; extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); -extern void GetThreadContextHandler(DWORD Pid, LPCONTEXT Context); -extern void SetThreadContextHandler(DWORD Pid, const CONTEXT *Context); +extern void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context); +extern void SetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context); extern void ResumeThreadHandler(DWORD Pid); extern void CreateRemoteThreadHandler(DWORD Pid); extern void NtContinueHandler(PCONTEXT ThreadContext); @@ -300,33 +300,22 @@ HOOKDEF(NTSTATUS, WINAPI, NtGetContextThread, __in HANDLE ThreadHandle, __inout LPCONTEXT Context ) { - ENSURE_HANDLE(ThreadHandle); - ENSURE_STRUCT(Context, CONTEXT); DWORD tid = tid_from_thread_handle(ThreadHandle); + DWORD pid = pid_from_thread_handle(ThreadHandle); NTSTATUS ret = Old_NtGetContextThread(ThreadHandle, Context); - DWORD pid = pid_from_thread_handle(ThreadHandle); + if (Context && Context->ContextFlags & CONTEXT_CONTROL) #ifdef _WIN64 - LOQ_ntstatus("threading", "pppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", - Context->Rcx, "CurrentInstructionPointer", Context->Rip, "ProcessId", pid); + LOQ_ntstatus("threading", "pppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Rcx, "CurrentInstructionPointer", Context->Rip, "ProcessId", pid); #else - LOQ_ntstatus("threading", "pppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", - Context->Eax, "CurrentInstructionPointer", Context->Eip, "ProcessId", pid); + LOQ_ntstatus("threading", "pppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Eax, "CurrentInstructionPointer", Context->Eip, "ProcessId", pid); #endif else LOQ_ntstatus("threading", "pi", "ThreadHandle", ThreadHandle, "ProcessId", pid); - GetThreadContextHandler(pid, Context); - - if (g_config.debugger) { - Context->Dr0 = 0; - Context->Dr1 = 0; - Context->Dr2 = 0; - Context->Dr3 = 0; - Context->Dr6 = 0; - Context->Dr7 = 0; - } + if (tid) + GetThreadContextHandler(ThreadHandle, Context); return ret; } @@ -335,8 +324,6 @@ HOOKDEF(NTSTATUS, WINAPI, RtlWow64GetThreadContext, __in HANDLE ThreadHandle, __inout PWOW64_CONTEXT Context ) { - ENSURE_HANDLE(ThreadHandle); - ENSURE_STRUCT(Context, WOW64_CONTEXT); DWORD tid = tid_from_thread_handle(ThreadHandle); DWORD pid = pid_from_thread_handle(ThreadHandle); @@ -351,37 +338,24 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetContextThread, __in HANDLE ThreadHandle, __in CONTEXT *Context ) { - NTSTATUS ret; DWORD pid = pid_from_thread_handle(ThreadHandle); - DWORD tid = tid_from_thread_handle(ThreadHandle); + DWORD tid = tid_from_thread_handle(NULL); - if (pid == GetCurrentProcessId() && g_config.debugger && Context) { - PTHREADBREAKPOINTS ThreadBreakpoints = GetThreadBreakpoints(tid); - if (ThreadBreakpoints) - { - DebugOutput("NtSetContextThread: Protecting breakpoints for thread %d: 0x%p, 0x%p, 0x%p, 0x%p.\n", tid, ThreadBreakpoints->BreakpointInfo[0].Address, ThreadBreakpoints->BreakpointInfo[1].Address, ThreadBreakpoints->BreakpointInfo[2].Address, ThreadBreakpoints->BreakpointInfo[3].Address); - ContextSetThreadBreakpointsEx(Context, ThreadBreakpoints, TRUE); - } -#ifdef DEBUG_COMMENTS - else - DebugOutput("NtSetContextThread hook: No breakpoints to protect for thread %d.\n", tid); -#endif - } + SetThreadContextHandler(ThreadHandle, Context); - ret = Old_NtSetContextThread(ThreadHandle, Context); + NTSTATUS ret = Old_NtSetContextThread(ThreadHandle, Context); if (Context && Context->ContextFlags & CONTEXT_CONTROL) #ifdef _WIN64 - LOQ_ntstatus("threading", "pppp", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Rcx, "CurrentInstructionPointer", Context->Rip, "Flags", Context->ContextFlags); + LOQ_ntstatus("threading", "ppppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Rcx, "CurrentInstructionPointer", Context->Rip, "Flags", Context->ContextFlags, "ProcessId", pid); #else - LOQ_ntstatus("threading", "pppp", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Eax, "CurrentInstructionPointer", Context->Eip, "Flags", Context->ContextFlags); + LOQ_ntstatus("threading", "ppppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Eax, "CurrentInstructionPointer", Context->Eip, "Flags", Context->ContextFlags, "ProcessId", pid); #endif else - LOQ_ntstatus("threading", "p", "ThreadHandle", ThreadHandle); + LOQ_ntstatus("threading", "pi", "ThreadHandle", ThreadHandle, "ProcessId", pid); - SetThreadContextHandler(pid, Context); - if (pid != GetCurrentProcessId()) - ProcessMessage(pid, 0); + if (tid) + GetThreadContextHandler(ThreadHandle, Context); return ret; } From 1ee2117d1eca6b07cfbd40d3477ebb1df3740782 Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Thu, 24 Jul 2025 11:50:16 +0200 Subject: [PATCH 040/148] Improve standalone mode --- capemon.c | 14 ++++++++++---- config.c | 4 ++-- pipe.c | 26 ++++++++++++++++---------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/capemon.c b/capemon.c index e0f408bb..132ffc32 100644 --- a/capemon.c +++ b/capemon.c @@ -625,10 +625,16 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved) read_config(); if (g_config.standalone) { - CAPE_init(); - DebugOutput("Standalone mode initialised.\n"); - return TRUE; - + // initialize these because some hooks behave badly when they are empty + if (!g_config.w_analyzer[0]) { + for (i = 0; i < ARRAYSIZE(g_config.analyzer); i++) + g_config.w_analyzer[i] = (wchar_t)(unsigned short)g_config.analyzer[i]; + } + if (!g_config.w_results[0]) { + for (i = 0; i < ARRAYSIZE(g_config.results); i++) + g_config.w_results[i] = (wchar_t)(unsigned short)g_config.results[i]; + } + DebugOutput("Running in standalone mode.\n"); } // don't inject into our own binaries run out of the analyzer directory unless they're the first process (intended) diff --git a/config.c b/config.c index e0715656..c7a96909 100644 --- a/config.c +++ b/config.c @@ -1414,8 +1414,6 @@ void read_config(void) StepLimit = SINGLE_STEP_LIMIT; - strcpy(g_config.results, g_config.analyzer); - memset(g_config.str, 0, MAX_PATH); memset(g_config.pythonpath, 0, MAX_PATH); memset(g_config.w_results, 0, sizeof(WCHAR)*MAX_PATH); @@ -1428,6 +1426,8 @@ void read_config(void) PathRemoveFileSpec(g_config.analyzer); // remove filename sprintf(config_fname, "%s\\%u.ini", g_config.analyzer, GetCurrentProcessId()); + strcpy(g_config.results, g_config.analyzer); + fp = fopen(config_fname, "r"); // backward compatibility diff --git a/pipe.c b/pipe.c index 3fbbcdc4..ddfeccab 100644 --- a/pipe.c +++ b/pipe.c @@ -224,17 +224,23 @@ int pipe2(void *out, int *outlen, const char *fmt, ...) va_list args; int len; int ret = -1; - va_start(args, fmt); - len = _pipe_sprintf(NULL, fmt, args); - if(len > 0) { - char *buf = calloc(1, len + 1); - _pipe_sprintf(buf, fmt, args); - va_end(args); - if(CallNamedPipeW(g_config.pipe_name, buf, len, out, *outlen, - (DWORD *) outlen, NMPWAIT_WAIT_FOREVER) != 0) - ret = 0; - free(buf); + if (g_config.standalone) { + *outlen = 0; + } + else { + va_start(args, fmt); + len = _pipe_sprintf(NULL, fmt, args); + if(len > 0) { + char *buf = calloc(1, len + 1); + _pipe_sprintf(buf, fmt, args); + va_end(args); + + if(CallNamedPipeW(g_config.pipe_name, buf, len, out, *outlen, + (DWORD *) outlen, NMPWAIT_WAIT_FOREVER) != 0) + ret = 0; + free(buf); + } } return ret; } From b8a974ba068ca95d80bf2e250a6ac6af515cffc6 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 25 Jul 2025 16:00:19 +0100 Subject: [PATCH 041/148] Improvements to NtGetContextThread & NtSetContextThread hooks in light of #100 --- CAPE/Injection.c | 2 +- hook_thread.c | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index bed023fe..5008239b 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -514,7 +514,7 @@ void DumpSectionViewsForHandle(HANDLE SectionHandle) return; } -void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context) +__declspec(noinline) void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context) { DebugOutput("GetThreadContextHandler"); DWORD Pid = pid_from_thread_handle(ThreadHandle); diff --git a/hook_thread.c b/hook_thread.c index 9cf1ddb8..9eaa0ca1 100644 --- a/hook_thread.c +++ b/hook_thread.c @@ -296,26 +296,26 @@ HOOKDEF(NTSTATUS, WINAPI, NtOpenThread, return ret; } +// This hook needs to be strictly matched to the NtSetContextThread hook in terms of stack frame size HOOKDEF(NTSTATUS, WINAPI, NtGetContextThread, __in HANDLE ThreadHandle, __inout LPCONTEXT Context ) { - DWORD tid = tid_from_thread_handle(ThreadHandle); DWORD pid = pid_from_thread_handle(ThreadHandle); NTSTATUS ret = Old_NtGetContextThread(ThreadHandle, Context); if (Context && Context->ContextFlags & CONTEXT_CONTROL) #ifdef _WIN64 - LOQ_ntstatus("threading", "pppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Rcx, "CurrentInstructionPointer", Context->Rip, "ProcessId", pid); + LOQ_ntstatus("threading", "ppppi", "ThreadHandle", ThreadHandle, "RCX", Context->Rcx, "RIP", Context->Rip, "Flags", Context->ContextFlags, "ProcessId", pid); #else - LOQ_ntstatus("threading", "pppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Eax, "CurrentInstructionPointer", Context->Eip, "ProcessId", pid); + LOQ_ntstatus("threading", "ppppi", "ThreadHandle", ThreadHandle, "EAX", Context->Eax, "EIP", Context->Eip, "Flags", Context->ContextFlags, "ProcessId", pid); #endif else LOQ_ntstatus("threading", "pi", "ThreadHandle", ThreadHandle, "ProcessId", pid); - if (tid) - GetThreadContextHandler(ThreadHandle, Context); + // This needs to be __declspec(noinline) to prevent inlining + GetThreadContextHandler(ThreadHandle, Context); return ret; } @@ -324,7 +324,6 @@ HOOKDEF(NTSTATUS, WINAPI, RtlWow64GetThreadContext, __in HANDLE ThreadHandle, __inout PWOW64_CONTEXT Context ) { - DWORD tid = tid_from_thread_handle(ThreadHandle); DWORD pid = pid_from_thread_handle(ThreadHandle); NTSTATUS ret = Old_RtlWow64GetThreadContext(ThreadHandle, Context); @@ -339,7 +338,6 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetContextThread, __in CONTEXT *Context ) { DWORD pid = pid_from_thread_handle(ThreadHandle); - DWORD tid = tid_from_thread_handle(NULL); SetThreadContextHandler(ThreadHandle, Context); @@ -347,16 +345,13 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetContextThread, if (Context && Context->ContextFlags & CONTEXT_CONTROL) #ifdef _WIN64 - LOQ_ntstatus("threading", "ppppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Rcx, "CurrentInstructionPointer", Context->Rip, "Flags", Context->ContextFlags, "ProcessId", pid); + LOQ_ntstatus("threading", "ppppi", "ThreadHandle", ThreadHandle, "RCX", Context->Rcx, "RIP", Context->Rip, "Flags", Context->ContextFlags, "ProcessId", pid); #else - LOQ_ntstatus("threading", "ppppi", "ThreadHandle", ThreadHandle, "HollowedInstructionPointer", Context->Eax, "CurrentInstructionPointer", Context->Eip, "Flags", Context->ContextFlags, "ProcessId", pid); + LOQ_ntstatus("threading", "ppppi", "ThreadHandle", ThreadHandle, "EAX", Context->Eax, "EIP", Context->Eip, "Flags", Context->ContextFlags, "ProcessId", pid); #endif else LOQ_ntstatus("threading", "pi", "ThreadHandle", ThreadHandle, "ProcessId", pid); - if (tid) - GetThreadContextHandler(ThreadHandle, Context); - return ret; } From 8c58f4aa503e5347c394c6e6a509f30f1bdf45ef Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 31 Jul 2025 16:43:01 +0100 Subject: [PATCH 042/148] Fix missing ProcessMessage() from recently updated SetThreadContextHandler() --- CAPE/Injection.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index 96a5a668..89f384e8 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -46,6 +46,8 @@ extern char *our_process_name; extern void hook_disable(); extern void hook_enable(); +void ProcessMessage(DWORD ProcessId, DWORD ThreadId); + //************************************************************************************** PINJECTIONINFO GetInjectionInfo(DWORD ProcessId) //************************************************************************************** @@ -516,7 +518,6 @@ void DumpSectionViewsForHandle(HANDLE SectionHandle) __declspec(noinline) void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context) { - DebugOutput("GetThreadContextHandler"); DWORD Pid = pid_from_thread_handle(ThreadHandle); if (Context && Context->ContextFlags & CONTEXT_CONTROL) @@ -550,7 +551,8 @@ __declspec(noinline) void SetThreadContextHandler(HANDLE ThreadHandle, CONTEXT * DWORD Pid = pid_from_thread_handle(ThreadHandle); DWORD Tid = tid_from_thread_handle(ThreadHandle); - DebugOutput("SetThreadContextHandler: Pid %d", GetCurrentProcessId()); + if (Pid != GetCurrentProcessId()) + ProcessMessage(Pid, 0); if (g_config.debugger && Pid == GetCurrentProcessId()) { From 11c0e05a0750930c68795dc80abe36a0dcaf4926 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 1 Aug 2025 12:49:10 +0100 Subject: [PATCH 043/148] Create DumpStrings debugger action for corresponding function --- CAPE/CAPE.h | 1 + CAPE/Trace.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CAPE/CAPE.h b/CAPE/CAPE.h index 61f1d821..3cadd931 100644 --- a/CAPE/CAPE.h +++ b/CAPE/CAPE.h @@ -77,6 +77,7 @@ int DumpImageInCurrentProcess(PVOID ImageBase); void DumpSectionViewsForPid(DWORD Pid); BOOL DumpRange(PVOID Address, SIZE_T Size); BOOL DumpStackRegion(void); +void DumpStrings(void); BOOL ProcessDumped; unsigned int DumpCount, DotNetCacheDumpCount; diff --git a/CAPE/Trace.c b/CAPE/Trace.c index ada9672a..27473e11 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -1631,6 +1631,10 @@ void ActionDispatcher(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst De DumpAddress = 0; DumpSize = 0; } + else if (!stricmp(Action, "DumpStrings")) + { + DumpStrings(); + } else if (!stricmp(Action, "Step2OEP")) { SetSingleStepMode(ExceptionInfo->ContextRecord, ProcessOEP); From 278497874893d2eb589201147bab2312bfdb3c8b Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 1 Aug 2025 13:35:54 +0100 Subject: [PATCH 044/148] Enhance dynamic patching capability: new PatchBytes() function, submission/yara option patch=
: --- CAPE/Debugger.c | 45 ++++++++++++++++---------- CAPE/Debugger.h | 2 +- CAPE/Trace.c | 2 +- CAPE/YaraHarness.c | 80 +++++++++++++++++++++++++++------------------- config.c | 17 +++++----- 5 files changed, 85 insertions(+), 61 deletions(-) diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index 7179c5a6..57c78e11 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -104,32 +104,43 @@ HANDLE GetThreadHandle(DWORD ThreadId) } //************************************************************************************** -BOOL PatchByte(LPVOID Address, BYTE Byte) +BOOL PatchBytes(LPVOID Address, const char* HexBytes) //************************************************************************************** { - DWORD OldProtect; + if (!Address || !HexBytes || !IsAddressAccessible(Address)) + { + DebugOutput("PatchBytes: Invalid address or hex string"); + return FALSE; + } - if (!Address || !IsAddressAccessible(Address)) - return FALSE; + SIZE_T HexLen = strlen(HexBytes); + if (HexLen == 0 || HexLen % 2 != 0) + { + DebugOutput("PatchBytes: Invalid hex string length %d", HexLen); + return FALSE; + } - if (!VirtualProtect(Address, 1, PAGE_EXECUTE_READWRITE, &OldProtect)) + SIZE_T ByteCount = HexLen / 2; + DWORD OldProtect; + if (!VirtualProtect(Address, ByteCount, PAGE_EXECUTE_READWRITE, &OldProtect)) { - DebugOutput("PatchByte: Unable to change memory protection at 0x%p", Address); - return FALSE; - } + DebugOutput("PatchBytes: Failed to change memory protection at 0x%p", Address); + return FALSE; + } -#ifdef DEBUG_COMMENTS - DebugOutput("PatchByte: Changed memory protection at 0x%p", Address); -#endif + PBYTE Dest = (PBYTE)Address; + for (SIZE_T i = 0; i < HexLen; i += 2) + { + char HexByte[3] = { HexBytes[i], HexBytes[i + 1], '\0' }; + BYTE Byte = (BYTE)strtoul(HexByte, NULL, 16); + *Dest++ = Byte; + } - *(PBYTE)Address = Byte; + VirtualProtect(Address, ByteCount, OldProtect, &OldProtect); -#ifdef DEBUG_COMMENTS - DebugOutput("PatchByte: New instruction byte at 0x%p: 0x%x", Address, *(PBYTE)Address); -#endif - VirtualProtect(Address, 1, OldProtect, &OldProtect); + DebugOutput("PatchBytes: Patched %zu bytes at 0x%p: %s", ByteCount, Address, HexBytes); - return TRUE; + return TRUE; } //************************************************************************************** diff --git a/CAPE/Debugger.h b/CAPE/Debugger.h index a0f69dfa..0b990c6f 100644 --- a/CAPE/Debugger.h +++ b/CAPE/Debugger.h @@ -181,7 +181,7 @@ BOOL InitialiseDebugger(void); BOOL ResumeFromBreakpoint(PCONTEXT Context); void OutputThreadBreakpoints(DWORD ThreadId); void DebugOutputThreadBreakpoints(); -BOOL PatchByte(LPVOID Address, BYTE Byte); +BOOL PatchBytes(LPVOID Address, const char* HexBytes); void ShowStack(DWORD_PTR StackPointer, unsigned int NumberOfRecords); diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 27473e11..a767810c 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -2025,7 +2025,7 @@ void InstructionHandler(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst #endif SkipInstruction(ExceptionInfo->ContextRecord); if (lookup_get(&SoftBPs, (ULONG_PTR)CIP, 0)) - PatchByte(CIP, 0xCC); + PatchBytes(CIP, "CC"); } else if (!strcmp(DecodedInstruction.mnemonic.p, "RET")) { diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index 6a78cc89..38630acf 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -114,21 +114,25 @@ void ScannerError(int Error) } } -BOOL ParseOptionLine(char* Line, char* Identifier, YR_MATCH* Match) +void ParseOptionLine(char* Line, char* Identifier, YR_MATCH* Match, void* user_data) { char *Value, *Key, *p, *q, *r, c = 0; - unsigned int ValueLength; - int delta=0; + ULONG_PTR delta=0; + SIZE_T ValueLength = 0; + if (!Line || !Identifier) - return FALSE; + return; + p = strchr(Line, '$'); if (!p) - return FALSE; + return; + p = strchr(Line, '='); if (!p) - return FALSE; + return; + r = strchr(p, ':'); - if (r) + if (r && *(r + 1) == '$') Value = r + 1; else Value = p + 1; @@ -151,12 +155,12 @@ BOOL ParseOptionLine(char* Line, char* Identifier, YR_MATCH* Match) } if (q) { - ValueLength = (unsigned int)(DWORD_PTR)(q-(DWORD_PTR)Value); + ValueLength = (SIZE_T)(DWORD_PTR)(q-(DWORD_PTR)Value); if (*(q-1) == '*') ValueLength--; } else - ValueLength = (unsigned int)strlen(Value); + ValueLength = (SIZE_T)strlen(Value); if (*(Value+ValueLength-1) == '*') { @@ -164,28 +168,44 @@ BOOL ParseOptionLine(char* Line, char* Identifier, YR_MATCH* Match) delta += Match->match_length - 1; } - if (strncmp(Value, Identifier, ValueLength)) - return FALSE; + SIZE_T IdentifierLength = strlen(Identifier); + if (strncmp(Value, Identifier, IdentifierLength < ValueLength ? IdentifierLength : ValueLength)) + return; Key = Line; - if (r) { + if (r && *(r + 1) == '$') + { c = *r; *r = 0; } - else { + else + { c = *p; *p = 0; } + + if (_strnicmp(Line, "bp", 2) && strncmp(Line, "br", 2)) + delta += (ULONG_PTR)user_data; + memset(NewLine, 0, sizeof(NewLine)); - sprintf(NewLine, "%s%c0x%p\0", Key, c, (PUCHAR)Match->offset+delta); if (r) + sprintf(NewLine, "%s%c0x%p%s\0", Key, c, (PUCHAR)Match->offset+delta, r); + else + sprintf(NewLine, "%s%c0x%p\0", Key, c, (PUCHAR)Match->offset+delta); + + if (r && *(r + 1) == '$') *r = c; else *p = c; - p = strchr(NewLine, '$'); - if (p) - return FALSE; - return TRUE; + +#ifdef DEBUG_COMMENTS + DebugOutput("ParseOptionLine: %s", NewLine); +#endif + + if (!strchr(NewLine, '$')) + parse_config_line(NewLine); + + return; } int YaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void* user_data) @@ -219,27 +239,23 @@ int YaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void char *p = strchr(OptionLine, ','); if (p) *p = 0; - yr_rule_strings_foreach(Rule, String) + if (!strchr(OptionLine, '$')) + parse_config_line(OptionLine); + else { - yr_string_matches_foreach(context, String, Match) + yr_rule_strings_foreach(Rule, String) { -#ifdef DEBUG_COMMENTS - DebugOutput("YaraScan match: %s, %s (0x%x)", OptionLine, String->identifier, Match->offset); -#endif - if (ParseOptionLine(OptionLine, (char*)String->identifier, Match)) + yr_string_matches_foreach(context, String, Match) { #ifdef DEBUG_COMMENTS - DebugOutput("YaraScan: NewLine %s", NewLine); + DebugOutput("YaraScan match: %s, %s (0x%x)", OptionLine, String->identifier, Match->offset); #endif - parse_config_line(NewLine); - SetBreakpoints = TRUE; + ParseOptionLine(OptionLine, (char*)String->identifier, Match, user_data); } - else if (!strchr(OptionLine, '$') && _strnicmp(OptionLine, "bp", 2) || strncmp(OptionLine, "br", 2)) - SetBreakpoints = TRUE; - } } - + if (!_strnicmp(OptionLine, "bp", 2) || !strncmp(OptionLine, "br", 2)) + SetBreakpoints = TRUE; if (!_stricmp("dump", OptionLine)) DoDumpRegion = TRUE; if (!_stricmp("clear", OptionLine)) @@ -258,8 +274,6 @@ int YaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void memset(Action2, 0, MAX_PATH); memset(Action3, 0, MAX_PATH); } - if (!strchr(OptionLine, '$')) - parse_config_line(OptionLine); if (p) { *p = ','; diff --git a/config.c b/config.c index c7a96909..a7db0894 100644 --- a/config.c +++ b/config.c @@ -26,6 +26,7 @@ along with this program. If not, see . #include "unhook.h" #include "Shlwapi.h" #include "CAPE\CAPE.h" +#include "CAPE\Debugger.h" #define SINGLE_STEP_LIMIT 0x4000 // default unless specified in web ui #define DROPPED_LIMIT 100 @@ -42,7 +43,6 @@ along with this program. If not, see . extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); extern char *our_dll_path; extern char *our_process_name; -extern BOOL PatchByte(LPVOID Address, BYTE Byte); extern wchar_t *our_process_path_w; extern int EntryPointRegister; extern unsigned int TraceDepthLimit, StepLimit, Type0, Type1, Type2; @@ -385,32 +385,31 @@ void parse_config_line(char* line) if (p) { *p = '\0'; char *p2 = p+1; - unsigned int byte = strtoul(value, NULL, 0); int delta=0; - p = strchr(p2, '+'); + p = strchr(value, '+'); if (p) { delta = strtoul(p+1, NULL, 0); DebugOutput("Config: Delta 0x%x.\n", delta); *p = '\0'; } else { - p = strchr(p2, '-'); + p = strchr(value, '-'); if (p) { delta = - (int)strtoul(p+1, NULL, 0); DebugOutput("Config: Delta 0x%x.\n", delta); *p = '\0'; } } - PVOID address = (PVOID)(DWORD_PTR)strtoul(p2, NULL, 0); + PVOID address = (PVOID)(DWORD_PTR)strtoull(value, NULL, 0); if (address) { - DebugOutput("Config: patching address 0x%p with byte 0x%x", address, byte); - PatchByte(address, (BYTE)byte); + DebugOutput("Config: patching address 0x%p with bytes %s", address, p2); + PatchBytes(address, p2); } else - DebugOutput("Config: patch address missing invalid: %s", value); + DebugOutput("Config: patch address missing or invalid: %s", value); } else - DebugOutput("Config: patch byte missing"); + DebugOutput("Config: patch bytes missing"); } else if (!stricmp(key, "bp")) { unsigned int x = 0; From a923abb88bd305525d50cbc9d3a56c0610ff6251 Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Mon, 11 Aug 2025 21:08:09 -0400 Subject: [PATCH 045/148] Initial support for hooking FindFixAndRun in cmd.exe Untested :) While capemon had some RVA hook capabilities before, they were used for DLLs (which has been replaced and seems to be no longer used). This commit adds RVA hooking capabilities for executables. The DLL RVA hooks "sanity check" the DLL using a timestamp, however I've opted to use a quick code scan of the functions first few bytes. I've also added in a NtQueryVirtualMemory version of "IsBadReadPtr" to handle sanity checking a bit more strictly than the existing function in capemon (is_valid_address_range). The hooked function itself is inspired from the code over at https://github.com/KingKDot/Exorcism, just ported into a hook/loq that capemon likes. Please do check out his github if you're curious of this hooks usefulness. --- capemon.vcxproj | 1 + capemon.vcxproj.filters | 3 ++ hook_special.c | 11 +++++ hooks.c | 90 +++++++++++++++++++++++++++++++++++++++++ hooks.h | 5 +++ misc.c | 36 +++++++++++++++++ misc.h | 15 +++++++ ntapi.h | 16 ++++++++ undoc_defs.h | 37 +++++++++++++++++ 9 files changed, 214 insertions(+) create mode 100644 undoc_defs.h diff --git a/capemon.vcxproj b/capemon.vcxproj index e01a1f72..1814d098 100644 --- a/capemon.vcxproj +++ b/capemon.vcxproj @@ -472,6 +472,7 @@ + diff --git a/capemon.vcxproj.filters b/capemon.vcxproj.filters index e54f9002..5b45cbce 100644 --- a/capemon.vcxproj.filters +++ b/capemon.vcxproj.filters @@ -452,6 +452,9 @@ Header Files\CAPE + + Header Files + diff --git a/hook_special.c b/hook_special.c index 687e5376..472c6704 100644 --- a/hook_special.c +++ b/hook_special.c @@ -29,6 +29,7 @@ along with this program. If not, see . #include "CAPE\CAPE.h" #include "CAPE\YaraHarness.h" #include +#include "undoc_defs.h" extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); extern int DoProcessDump(PVOID CallerBase); @@ -203,6 +204,16 @@ HOOKDEF(BOOL, WINAPI, LdrpCallInitRoutine, return ret; } +HOOKDEF(int, __fastcall, FindFixAndRun, + struct cmdnode *cmdnode +) { + int ret = 0; + if (!our_isbadreadptr(cmdnode, sizeof(cmdnode)) && cmdnode->cmdline != NULL) { + LOQ_zero("system", "uui", "Command", cmdnode->cmdline, "Arguments", cmdnode->argptr, "ArgType", cmdnode->type); + } + return Old_FindFixAndRun(cmdnode); +} + void end_transparent_hooks(){transparency_dummy--;} HOOKDEF(BOOL, WINAPI, CreateProcessInternalW, diff --git a/hooks.c b/hooks.c index 2f321063..c4b5972b 100644 --- a/hooks.c +++ b/hooks.c @@ -56,6 +56,9 @@ void disable_tail_call_optimization(void) #define HOOK_FUNCRVA(library, funcname, timestamp, rva) {L###library, #funcname, NULL, NULL, \ &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE, timestamp, rva} +#define HOOK_EXERVA(funcname, addr) {NULL, NULL, (void *) addr, NULL, \ + &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE} + hook_t full_hooks[] = { // Process Hooks @@ -1620,6 +1623,26 @@ hook_t test_hooks[] = { HOOK_SPECIAL(ntdll, NtContinue), }; +// .exe RVA hooks +#ifdef _WIN64 +// cmd.exe (Win10x64: LTSC, 21H2) +static hook_t g_exe_cmd_win10_x64_hooks[] = { + HOOK_EXERVA(FindFixAndRun, 0xc620), +}; +#define TARGET_EXE_PATH_CMD_WIN10_x64 L"C:\\Windows\\System32\\cmd.exe" +#define TARGET_EXE_PATH_CMD_WIN10_x64_ALT L"C:\\Windows\\Systemnative\\cmd.exe" +#define TARGET_EXE_CODE_CMD_WIN10_x64 "\x48\x89\x5C\x24\x10\x48\x89\x74\x24\x18" + +#else +// cmd.exe (Win10x64: LTSC, 21H2) +static hook_t g_exe_cmd_win10_x86_hooks[] = { + HOOK_EXERVA(FindFixAndRun, 0xad60), +}; +#define TARGET_EXE_PATH_CMD_WIN10_x86 L"C:\\Windows\\SysWOW64\\cmd.exe" +#define TARGET_EXE_CODE_CMD_WIN10_x86 "\x8B\xFF\x55\x8B\xEC\x6A\xFE\x68\xF0\xC9" +#endif + + BOOL inside_hook(LPVOID Address) { for (unsigned int i = 0; i < hooks_arraysize; i++) { @@ -1686,6 +1709,71 @@ void revalidate_all_hooks(void) } } +BOOLEAN try_to_set_exe_hooks( + unsigned long long ullExeBase, + UNICODE_STRING* pusFullPath, + const char* szExeName, + const wchar_t* wszExeTargetPath, + const wchar_t* wszAltExeTargetPath, + const unsigned char* pExeTargetCode, + hook_t* exe_hooks, + unsigned int num_hooks, + unsigned hook_type +) { + BOOLEAN found_exe = FALSE; + if (!wcsicmp(wszExeTargetPath, pusFullPath->Buffer) || (wszAltExeTargetPath && !wcsicmp(wszAltExeTargetPath, pusFullPath->Buffer))) { + unsigned char* pTestCode = ((unsigned char*)exe_hooks[0].addr) + ullExeBase; + if (!our_isbadreadptr(pTestCode, 0xa)) { + if (!memcmp(pTestCode, pExeTargetCode, 0xa)) { + DebugOutput("Target module path and code matches type: [%z]; shall set hooks", szExeName); + found_exe = TRUE; + DWORD old_protect; + VirtualProtect(exe_hooks, num_hooks * sizeof(exe_hooks[0]), PAGE_EXECUTE_READWRITE, &old_protect); + for (unsigned int i = 0; i < num_hooks; i++) { + hook_t* hook = &exe_hooks[i]; + // Hook addr is relative, so adjust it for wherever we got loaded + hook->addr = (void*)((unsigned char*)(hook->addr) + ullExeBase); + int ret2 = hook_api(hook, hook_type); + if (ret2 == -1) + DebugOutput("Warning: Unable to hook 0x%x", hook->addr); + } + } + } + } + return found_exe; +} + +#ifdef _WIN64 +BOOLEAN set_unexported_cmd_win10_x64_hooks(unsigned long long ullExeBase, UNICODE_STRING* pusFullPath) +{ + return try_to_set_exe_hooks(ullExeBase, pusFullPath, "cmd", TARGET_EXE_PATH_CMD_WIN10_x64, NULL, TARGET_EXE_CODE_CMD_WIN10_x64, g_exe_cmd_win10_x64_hooks, ARRAYSIZE(g_exe_cmd_win10_x64_hooks), g_config.hook_type); +} +#else +BOOLEAN set_unexported_cmd_win10_x86_hooks(unsigned long long ullExeBase, UNICODE_STRING* pusFullPath) +{ + return try_to_set_exe_hooks(ullExeBase, pusFullPath, "cmd", TARGET_EXE_PATH_CMD_WIN10_x86, NULL, TARGET_EXE_CODE_CMD_WIN10_x86, g_exe_cmd_win10_x86_hooks, ARRAYSIZE(g_exe_cmd_win10_x86_hooks), g_config.hook_type); +} +#endif + +void set_exe_hooks(void) +{ + BOOLEAN found_exe = FALSE; + LDR_MODULE* mod; PEB* peb = (PEB*)get_peb(); + mod = (LDR_MODULE*)peb->LoaderData->InLoadOrderModuleList.Flink; + unsigned long long ullExeBase = (unsigned long long)(mod->BaseAddress); + UNICODE_STRING usFullPath = mod->FullDllName; + +#ifdef _WIN64 + if (!found_exe) { + found_exe = set_unexported_cmd_win10_x64_hooks(ullExeBase, &usFullPath); + } +#else + if (!found_exe) { + found_exe = set_unexported_cmd_win10_x86_hooks(ullExeBase, &usFullPath); + } +#endif // _WIN64 +} + PVOID g_dll_notify_cookie; extern _LdrRegisterDllNotification pLdrRegisterDllNotification; @@ -1804,6 +1892,8 @@ void set_hooks() Hooked++; } + set_exe_hooks(); + for (unsigned int i = 0; i < num_suspended_threads; i++) { ResumeThread(suspended_threads[i]); CloseHandle(suspended_threads[i]); diff --git a/hooks.h b/hooks.h index 58502ac0..927af8b4 100644 --- a/hooks.h +++ b/hooks.h @@ -22,6 +22,7 @@ along with this program. If not, see . #include "ntapi.h" #include #include +#include "undoc_defs.h" // // File Hooks @@ -3737,4 +3738,8 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, __in BOOLEAN HandleBreakpoint ); +HOOKDEF(int, __fastcall, FindFixAndRun, + struct cmdnode *cmdnode +); + #include "hook_vbscript.h" diff --git a/misc.c b/misc.c index e224e72a..ff90f618 100644 --- a/misc.c +++ b/misc.c @@ -51,6 +51,7 @@ _NtFreeVirtualMemory pNtFreeVirtualMemory; _LdrRegisterDllNotification pLdrRegisterDllNotification; _RtlNtStatusToDosError pRtlNtStatusToDosError; _RtlCompareMemory pRtlCompareMemory; +_NtQueryVirtualMemory pNtQueryVirtualMemory; void resolve_runtime_apis(void) { @@ -632,6 +633,41 @@ BOOLEAN is_valid_address_range(ULONG_PTR start, DWORD len) return TRUE; } +BOOLEAN our_isbadreadptr(const void* addr, ULONG len) +{ + SIZE_T reslen; + PUCHAR startaddr = (PUCHAR)addr; + PUCHAR endaddr = startaddr + len; + PUCHAR p; + MEMORY_BASIC_INFORMATION meminfo; + lasterror_t lasterror; + BOOLEAN ret = FALSE; + + /* check for overflow */ + if ((ULONG_PTR)endaddr < (ULONG_PTR)startaddr) + return TRUE; + + get_lasterrors(&lasterror); + for (p = startaddr; p < endaddr; p = (PUCHAR)meminfo.BaseAddress + meminfo.RegionSize) { + memset(&meminfo, 0, sizeof(meminfo)); + if (pNtQueryVirtualMemory(NtCurrentProcess(), p, MemoryBasicInformation, &meminfo, sizeof(meminfo), &reslen)) { + ret = TRUE; + break; + } + if (!(meminfo.State & MEM_COMMIT) || !(meminfo.Type & (MEM_IMAGE | MEM_MAPPED | MEM_PRIVATE))) { + ret = TRUE; + break; + } + if ((meminfo.Protect & (PAGE_GUARD | PAGE_NOACCESS)) && (meminfo.Type != MEM_IMAGE || meminfo.Protect != PAGE_NOACCESS)) { + ret = TRUE; + break; + } + } + + set_lasterrors(&lasterror); + return ret; +} + DWORD parent_process_id() // By Napalm @ NetCore2K (rohitab.com) { PROCESS_BASIC_INFORMATION pbi; diff --git a/misc.h b/misc.h index 19c0af7c..6bf8357b 100644 --- a/misc.h +++ b/misc.h @@ -126,6 +126,20 @@ typedef SIZE_T (WINAPI *_RtlCompareMemory)( _In_ SIZE_T Length ); +typedef enum { + MemoryBasicInformation = 0, + MemorySectionName = 2, +} MEMORY_INFORMATION_CLASS; + +typedef NTSTATUS(WINAPI* _NtQueryVirtualMemory)( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ MEMORY_INFORMATION_CLASS MemoryInformationClass, + _Out_ PVOID Buffer, + _In_ SIZE_T Length, + _Out_opt_ PSIZE_T ResultLength +); + _NtSetInformationProcess pNtSetInformationProcess; _NtMapViewOfSection pNtMapViewOfSection; _NtUnmapViewOfSection pNtUnmapViewOfSection; @@ -269,3 +283,4 @@ struct envstruct { }; const char* GetLanguageName(LANGID langID); +BOOLEAN our_isbadreadptr(const void* addr, ULONG len); diff --git a/ntapi.h b/ntapi.h index 69eed2ae..e1efda16 100644 --- a/ntapi.h +++ b/ntapi.h @@ -524,6 +524,22 @@ typedef struct _PROC_THREAD_ATTRIBUTE_LIST typedef void *PVOID, **PPVOID; +typedef struct _LDR_MODULE { + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID BaseAddress; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; + ULONG Flags; + SHORT LoadCount; + SHORT TlsIndex; + LIST_ENTRY HashTableEntry; + ULONG TimeDateStamp; +} LDR_MODULE, * PLDR_MODULE; + typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; diff --git a/undoc_defs.h b/undoc_defs.h new file mode 100644 index 00000000..39110f14 --- /dev/null +++ b/undoc_defs.h @@ -0,0 +1,37 @@ +typedef HANDLE CRTHANDLE; + +/* +* Structure defs for FindFixAndSave hook in cmd.exe +* From https://github.com/KingKDot/Exorcism/blob/54a44302469160aa7b93f4b72e93206d06a786ac/cmdtest/cmdtest/dllmain.cpp#L70 +*/ +struct savtype { + TCHAR* saveptrs[12]; +}; + +struct relem { + CRTHANDLE rdhndl; // handle to be redirected + TCHAR* fname; // filename (or &n) + CRTHANDLE svhndl; // where orig handle is saved + int flag; // Append flag + TCHAR rdop; // Type ('>' | '<') + struct relem* nxt; // Next structure +}; + +struct node { // Used for operators + int type; // Type of operator + struct savtype save; // FOR processor saves orig strings here + struct relem* rio; // M022 - Linked redirection list + struct node* lhs; // Ptr to left hand side of the operator + struct node* rhs; // Ptr to right hand side of the operator + INT_PTR extra[4]; // M022 - Padding now needed +}; + +struct cmdnode { + int type; // Type of command + struct savtype save; // FOR processor saves orig strings here + struct relem* rio; // M022 - Linked redirection list + PTCHAR cmdline; // Ptr to command line + PTCHAR argptr; // Ptr to type of command + int flag; // M022 - Valid for cond and goto types + int cmdarg; // M022 - Argument to STRTYP routine +}; From 1afaea8223db5d2cffaaf01125b5d05e5fc5e98c Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Mon, 11 Aug 2025 21:25:29 -0400 Subject: [PATCH 046/148] Fixes for stuff from Gemini --- hook_special.c | 3 ++- hooks.c | 4 ++-- misc.c | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/hook_special.c b/hook_special.c index 472c6704..82cd10cb 100644 --- a/hook_special.c +++ b/hook_special.c @@ -207,8 +207,9 @@ HOOKDEF(BOOL, WINAPI, LdrpCallInitRoutine, HOOKDEF(int, __fastcall, FindFixAndRun, struct cmdnode *cmdnode ) { + // ret is unused in the hook, but set it to 0 for LOQ to have "success" as the return status int ret = 0; - if (!our_isbadreadptr(cmdnode, sizeof(cmdnode)) && cmdnode->cmdline != NULL) { + if (cmdnode && !our_isbadreadptr(cmdnode, sizeof(struct cmdnode)) && cmdnode->cmdline != NULL) { LOQ_zero("system", "uui", "Command", cmdnode->cmdline, "Arguments", cmdnode->argptr, "ArgType", cmdnode->type); } return Old_FindFixAndRun(cmdnode); diff --git a/hooks.c b/hooks.c index c4b5972b..54a128e3 100644 --- a/hooks.c +++ b/hooks.c @@ -1725,7 +1725,7 @@ BOOLEAN try_to_set_exe_hooks( unsigned char* pTestCode = ((unsigned char*)exe_hooks[0].addr) + ullExeBase; if (!our_isbadreadptr(pTestCode, 0xa)) { if (!memcmp(pTestCode, pExeTargetCode, 0xa)) { - DebugOutput("Target module path and code matches type: [%z]; shall set hooks", szExeName); + DebugOutput("Target module path and code matches type: '%s'; shall set hooks", szExeName); found_exe = TRUE; DWORD old_protect; VirtualProtect(exe_hooks, num_hooks * sizeof(exe_hooks[0]), PAGE_EXECUTE_READWRITE, &old_protect); @@ -1746,7 +1746,7 @@ BOOLEAN try_to_set_exe_hooks( #ifdef _WIN64 BOOLEAN set_unexported_cmd_win10_x64_hooks(unsigned long long ullExeBase, UNICODE_STRING* pusFullPath) { - return try_to_set_exe_hooks(ullExeBase, pusFullPath, "cmd", TARGET_EXE_PATH_CMD_WIN10_x64, NULL, TARGET_EXE_CODE_CMD_WIN10_x64, g_exe_cmd_win10_x64_hooks, ARRAYSIZE(g_exe_cmd_win10_x64_hooks), g_config.hook_type); + return try_to_set_exe_hooks(ullExeBase, pusFullPath, "cmd", TARGET_EXE_PATH_CMD_WIN10_x64, TARGET_EXE_PATH_CMD_WIN10_x64_ALT, TARGET_EXE_CODE_CMD_WIN10_x64, g_exe_cmd_win10_x64_hooks, ARRAYSIZE(g_exe_cmd_win10_x64_hooks), g_config.hook_type); } #else BOOLEAN set_unexported_cmd_win10_x86_hooks(unsigned long long ullExeBase, UNICODE_STRING* pusFullPath) diff --git a/misc.c b/misc.c index ff90f618..eeeb0988 100644 --- a/misc.c +++ b/misc.c @@ -80,6 +80,7 @@ void resolve_runtime_apis(void) *(FARPROC *)&pRtlAdjustPrivilege = GetProcAddress(ntdllbase, "RtlAdjustPrivilege"); *(FARPROC *)&pRtlNtStatusToDosError = GetProcAddress(ntdllbase, "RtlNtStatusToDosError"); *(FARPROC *)&pRtlCompareMemory = GetProcAddress(ntdllbase, "RtlCompareMemory"); + *(FARPROC*)&pNtQueryVirtualMemory = GetProcAddress(ntdllbase, "NtQueryVirtualMemory"); } ULONG_PTR g_our_dll_base; From 54f032863dabd194b7f945199121696973e7d519 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 12 Aug 2025 09:32:51 +0100 Subject: [PATCH 047/148] Fix internal WMI_GetObjectAsync yara --- CAPE/YaraHarness.c | 1 + 1 file changed, 1 insertion(+) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index 38630acf..0ef75e8c 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -68,6 +68,7 @@ char InternalYara[] = "condition:uint16(0) == 0x5a4d and any of them}" "rule WMI_GetObjectAsync" "{strings:$function = {48 8B C4 56 57 41 54 41 56 41 57 48 83 EC 40 48 C7 40 C8 FE FF FF FF 48 89 58 10 48 89 68 18 4D 8B F9 45 8B E0 48 8B EA 48 8B F1 48 8B 41 08 48 83 78 20 00 75 0A B8 08 01 01 80 E9}" + "condition:uint16(0) == 0x5a4d and any of them}" "rule vDbgPrintExWithPrefixInternal" #ifdef _WIN64 "{strings:$function = {40 55 53 56 41 54 41 55 41 56 41 57 48 81 EC 20 01 00 00 48 8D 6C 24 20 48 8B 05 [4] 48 33 C5 48 89 85 ?? 00 00 00 4C 89 4D ?? 44 89 45 ?? 44 8B E2 89 55 ?? 48 8B D1 48 89 4D ?? 48 8B 85}" From 998f89a5e4e71f1eff0cb216ea21dc5148cf6806 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 18 Aug 2025 14:02:50 +0100 Subject: [PATCH 048/148] YaraHarness: fix issue with ParseOptionLine() adding imagebase to sysbps (thanks @ClaudioWayne) --- CAPE/YaraHarness.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index 0ef75e8c..dd9c88b2 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -185,7 +185,7 @@ void ParseOptionLine(char* Line, char* Identifier, YR_MATCH* Match, void* user_d *p = 0; } - if (_strnicmp(Line, "bp", 2) && strncmp(Line, "br", 2)) + if (_strnicmp(Line, "bp", 2) && strncmp(Line, "br", 2) && strncmp(Line, "sysbp", 5)) delta += (ULONG_PTR)user_data; memset(NewLine, 0, sizeof(NewLine)); From 91120f873daf897719cd689285ccc47adf9c6d70 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 18 Aug 2025 14:11:29 +0100 Subject: [PATCH 049/148] YaraHarness: ensure YaraCallback calls SetInitialBreakpoints() for sysbps --- CAPE/YaraHarness.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index dd9c88b2..e8717d70 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -255,7 +255,7 @@ int YaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void } } } - if (!_strnicmp(OptionLine, "bp", 2) || !strncmp(OptionLine, "br", 2)) + if (!_strnicmp(OptionLine, "bp", 2) || !strncmp(OptionLine, "br", 2) || !strncmp(OptionLine, "sysbp", 5)) SetBreakpoints = TRUE; if (!_stricmp("dump", OptionLine)) DoDumpRegion = TRUE; From 1ca0a99465e0550c4114e24392abbab481cae647 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 19 Aug 2025 10:04:18 +0100 Subject: [PATCH 050/148] FindFixAndRun hook: adapt to yara-based function address lookup with HOOK_EXE, fallback option HOOK_EXERVA --- CAPE/CAPE.c | 19 +++- CAPE/CAPE.h | 1 + CAPE/YaraHarness.c | 208 +++++++++++++++++++++++----------------- CAPE/YaraHarness.h | 7 ++ capemon.vcxproj | 1 - capemon.vcxproj.filters | 3 - hook_special.c | 1 - hooking_32.c | 12 ++- hooking_64.c | 13 ++- hooks.c | 154 +++++++++++++---------------- hooks.h | 1 - misc.h | 38 ++++++++ undoc_defs.h | 37 ------- 13 files changed, 263 insertions(+), 232 deletions(-) delete mode 100644 undoc_defs.h diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 3f60efea..d770dec7 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -695,7 +695,7 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) return GetNonExportedFunctionAddress(ModuleBase, RuntimeTable[i].ExportName, RuntimeTable[i].Offset); } #endif - const char *YaraFunctions[] = + char *YaraFunctions[] = { "LdrpCallInitRoutine", "WMI_ExecQuery", @@ -707,9 +707,20 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) "vDbgPrintExWithPrefixInternal", }; - for (int i = 0; i < sizeof(YaraFunctions) / sizeof(YaraFunctions[0]); i++) - if (strcmp(YaraFunctions[i], FunctionName) == 0) - return GetAddressByYara(ModuleBase, FunctionName); + SIZE_T FoundCount = 0, FuncCount = sizeof(YaraFunctions) / sizeof(YaraFunctions[0]); + NameByAddress* results = GetAddressesByYara(ModuleBase, YaraFunctions, FuncCount, &FoundCount); + + if (!results || FoundCount == 0) + { + if (results) + free(results); + return NULL; + } + + for (SIZE_T i = 0; i < FuncCount; i++) + for (SIZE_T j = 0; j < FoundCount; j++) + if (results[j].FunctionName && results[j].Address && !strcmp(results[j].FunctionName, YaraFunctions[i])) + return results[j].Address; return NULL; } diff --git a/CAPE/CAPE.h b/CAPE/CAPE.h index 3cadd931..1d04b93d 100644 --- a/CAPE/CAPE.h +++ b/CAPE/CAPE.h @@ -42,6 +42,7 @@ PVOID GetAllocationBase(PVOID Address); SIZE_T GetRegionSize(PVOID Address); SIZE_T GetAllocationSize(PVOID Address); SIZE_T GetAccessibleSize(PVOID Address); +PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName); PVOID GetFunctionAddress(HMODULE ModuleBase, PCHAR FunctionName); BOOL IsAddressAccessible(PVOID Address); BOOL IsAddressExecutable(PVOID Address); diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index 38630acf..a0c42adc 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -23,6 +23,7 @@ along with this program.If not, see . #include "Debugger.h" #include "YaraHarness.h" #include "..\config.h" +#include "..\alloc.h" extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); extern void ErrorOutput(_In_ LPCTSTR lpOutputString, ...); @@ -68,13 +69,22 @@ char InternalYara[] = "condition:uint16(0) == 0x5a4d and any of them}" "rule WMI_GetObjectAsync" "{strings:$function = {48 8B C4 56 57 41 54 41 56 41 57 48 83 EC 40 48 C7 40 C8 FE FF FF FF 48 89 58 10 48 89 68 18 4D 8B F9 45 8B E0 48 8B EA 48 8B F1 48 8B 41 08 48 83 78 20 00 75 0A B8 08 01 01 80 E9}" - "rule vDbgPrintExWithPrefixInternal" + "condition:uint16(0) == 0x5a4d and any of them}" #ifdef _WIN64 + "rule vDbgPrintExWithPrefixInternal" "{strings:$function = {40 55 53 56 41 54 41 55 41 56 41 57 48 81 EC 20 01 00 00 48 8D 6C 24 20 48 8B 05 [4] 48 33 C5 48 89 85 ?? 00 00 00 4C 89 4D ?? 44 89 45 ?? 44 8B E2 89 55 ?? 48 8B D1 48 89 4D ?? 48 8B 85}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule FindFixAndRun" + "{strings:$function = {48 89 5C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 81 EC [2] 00 00 48 8B 05 [4] 48 33 C4 48 89 84 [5] 4C 8B F1 BF 04 01 00 00 8B D7}" + "condition:uint16(0) == 0x5a4d and any of them}" #else + "rule vDbgPrintExWithPrefixInternal" "{strings:$function = {68 90 00 00 00 68 [4] E8 [4] 89 95 [4] 89 8D [4] 8B 45 ?? 89 85 [4] 8B 45 ?? 89 85 [4] 64 A1 18 00 00 00 89 45 ?? 83 FA FF 0F 84}" -#endif "condition:uint16(0) == 0x5a4d and any of them}" + "rule FindFixAndRun" + "{strings:$function = {8B FF 55 8B EC 6A FE 68 [4] 68 [4] 64 A1 00 00 00 00 50 81 EC [17] 53 56 57 50 8D 45 ?? 64 A3 00 00 00 00 8B F9}" + "condition:uint16(0) == 0x5a4d and any of them}" +#endif "rule capemon" "{strings:$hash = {d3 b9 46 1d 9a 14 bc 44 a1 61 c3 47 6a 0e 35 90 00 2c 28 81 dc a0 36 dc 2c 92 0c 7c b6 84 39 59}" "condition:all of them}"; @@ -300,109 +310,133 @@ int YaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void return CALLBACK_ERROR; } -typedef struct +int GetAddressesByYaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void* user_data) { - PCHAR FunctionName; - PVOID Address; -} NameByAddress; - -int GetAddressByYaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void* user_data) -{ - switch(message) - { - case CALLBACK_MSG_RULE_NOT_MATCHING: + switch(message) + { + case CALLBACK_MSG_RULE_NOT_MATCHING: #ifdef DEBUG_COMMENTS - DebugOutput("YaraScan rule did not match."); + DebugOutput("YaraScan rule did not match."); #endif - case CALLBACK_MSG_IMPORT_MODULE: - return CALLBACK_CONTINUE; - case CALLBACK_MSG_RULE_MATCHING: - YR_MATCH* Match; - YR_STRING* String; - YR_RULE* Rule = (YR_RULE*)message_data; + case CALLBACK_MSG_IMPORT_MODULE: + return CALLBACK_CONTINUE; -#ifdef DEBUG_COMMENTS - DebugOutput("GetAddressByYaraCallback hit: %s\n", Rule->identifier); -#endif - - yr_rule_strings_foreach(Rule, String) - { - yr_string_matches_foreach(context, String, Match) - { - NameByAddress *AddressInfo = user_data; + case CALLBACK_MSG_RULE_MATCHING: + YR_MATCH* Match; + YR_STRING* String; + YR_RULE* Rule = (YR_RULE*)message_data; + NameByAddress* AddressInfos = (NameByAddress*)user_data; - if (!strcmp(Rule->identifier, AddressInfo->FunctionName)) - { #ifdef DEBUG_COMMENTS - DebugOutput("GetAddressByYaraCallback: Function %s found at RVA 0x%x", AddressInfo->FunctionName, Match->offset); + DebugOutput("GetAddressesByYaraCallback hit: %s\n", Rule->identifier); #endif - AddressInfo->Address = (PVOID)Match->offset; - break; - } + + yr_rule_strings_foreach(Rule, String) + { + yr_string_matches_foreach(context, String, Match) + { + for (SIZE_T i = 0; AddressInfos[i].FunctionName != NULL; i++) + { + if (!strcmp(Rule->identifier, AddressInfos[i].FunctionName)) + { #ifdef DEBUG_COMMENTS - else - DebugOutput("GetAddressByYaraCallback: Function %s not found", AddressInfo->FunctionName); + DebugOutput("GetAddressesByYaraCallback: Found %s at RVA 0x%x", AddressInfos[i].FunctionName, Match->offset); #endif - } - } - return CALLBACK_CONTINUE; - } + AddressInfos[i].Address = (PVOID)Match->offset; + break; + } + } + } + } + return CALLBACK_CONTINUE; + } + + return CALLBACK_ERROR; +} - return CALLBACK_ERROR; +NameByAddress* GetAddressesByYara(HMODULE ModuleBase, PCHAR FunctionNames[], SIZE_T FunctionCount, SIZE_T* OutFoundCount) +{ + if (!YaraActivated || !FunctionNames || FunctionCount == 0) + return NULL; + + SIZE_T Size = GetAccessibleSize(ModuleBase); + if (!Size) + return NULL; + + Size = (SIZE_T)ReverseScanForNonZero(ModuleBase, Size); + if (!Size) + { + if (YaraLogging) + DebugOutput("GetAddressesByYara: Nothing to scan at 0x%p!\n", ModuleBase); + return NULL; + } + + NameByAddress* AddressInfos = (NameByAddress*)calloc(FunctionCount + 1, sizeof(NameByAddress)); + if (!AddressInfos) + return NULL; + + for (SIZE_T i = 0; i < FunctionCount; i++) + { + AddressInfos[i].FunctionName = FunctionNames[i]; + AddressInfos[i].Address = NULL; + } + + int Flags = 0, Timeout = 1, Result = ERROR_SUCCESS; + __try + { + Result = yr_rules_scan_mem(Rules, (PVOID)ModuleBase, Size, Flags, GetAddressesByYaraCallback, AddressInfos, Timeout); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + if (YaraLogging) + DebugOutput("GetAddressesByYara: Unable to scan 0x%p\n", ModuleBase); + free(AddressInfos); + return NULL; + } + + if (Result != ERROR_SUCCESS) + { + if (YaraLogging) + ScannerError(Result); + free(AddressInfos); + return NULL; + } + + SIZE_T FoundCount = 0; + for (SIZE_T i = 0; i < FunctionCount; i++) + { + if (AddressInfos[i].Address) + { + AddressInfos[i].Address = (PVOID)((ULONG_PTR)ModuleBase + (ULONG_PTR)AddressInfos[i].Address); + FoundCount++; + } + } + + if (OutFoundCount) + *OutFoundCount = FoundCount; + + return AddressInfos; } PVOID GetAddressByYara(HMODULE ModuleBase, PCHAR FunctionName) { - if (!YaraActivated) - return NULL; - - int Flags = 0, Timeout = 1, Result = ERROR_SUCCESS; - - SIZE_T Size = GetAccessibleSize(ModuleBase); - - if (!Size) - return NULL; - - Size = (SIZE_T)ReverseScanForNonZero(ModuleBase, Size); - - if (!Size) - { - if (YaraLogging) - DebugOutput("GetAddressByYara: Nothing to scan at 0x%p!\n", ModuleBase); - return NULL; - } - - NameByAddress AddressInfo; - AddressInfo.FunctionName = FunctionName; - AddressInfo.Address = NULL; + if (!YaraActivated) + return NULL; - __try - { - Result = yr_rules_scan_mem(Rules, (PVOID)ModuleBase, Size, Flags, GetAddressByYaraCallback, &AddressInfo, Timeout); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - if (YaraLogging) - DebugOutput("GetAddressByYara: Unable to scan 0x%p\n", ModuleBase); - return NULL; - } - - if (Result != ERROR_SUCCESS) - if (YaraLogging) - ScannerError(Result); -#ifdef DEBUG_COMMENTS - else - DebugOutput("GetAddressByYara: successfully scanned 0x%p\n", ModuleBase); -#endif + PCHAR FunctionNames[] = {FunctionName, NULL}; + SIZE_T FunctionCount = 1; + SIZE_T FoundCount = 0; - if (!AddressInfo.Address) - return NULL; + NameByAddress* Results = GetAddressesByYara(ModuleBase, FunctionNames, FunctionCount, &FoundCount); -#ifdef DEBUG_COMMENTS - DebugOutput("GetAddressByYara: %s found at 0x%p", FunctionName, (ULONG_PTR)ModuleBase + (ULONG_PTR)AddressInfo.Address); -#endif + PVOID FoundAddress = NULL; + if (Results && FoundCount > 0) + { + FoundAddress = Results[0].Address; + free(Results); + } - return (PVOID)((ULONG_PTR)ModuleBase + (ULONG_PTR)AddressInfo.Address); + return FoundAddress; } void YaraShutdown() diff --git a/CAPE/YaraHarness.h b/CAPE/YaraHarness.h index fcec62f0..c5ebcafa 100644 --- a/CAPE/YaraHarness.h +++ b/CAPE/YaraHarness.h @@ -4,9 +4,16 @@ #include "yara.h" +typedef struct +{ + PCHAR FunctionName; + PVOID Address; +} NameByAddress; + BOOL YaraInit(); BOOL ScanForRulesCanary(PVOID Address, SIZE_T Size); void YaraScan(PVOID Address, SIZE_T Size); void SilentYaraScan(PVOID Address, SIZE_T Size); PVOID GetAddressByYara(HMODULE ModuleBase, PCHAR FunctionName); +NameByAddress* GetAddressesByYara(HMODULE ModuleBase, PCHAR FunctionNames[], SIZE_T FunctionCount, SIZE_T* OutFoundCount); void YaraShutdown(); \ No newline at end of file diff --git a/capemon.vcxproj b/capemon.vcxproj index 1814d098..e01a1f72 100644 --- a/capemon.vcxproj +++ b/capemon.vcxproj @@ -472,7 +472,6 @@ - diff --git a/capemon.vcxproj.filters b/capemon.vcxproj.filters index 5b45cbce..e54f9002 100644 --- a/capemon.vcxproj.filters +++ b/capemon.vcxproj.filters @@ -452,9 +452,6 @@ Header Files\CAPE - - Header Files - diff --git a/hook_special.c b/hook_special.c index 82cd10cb..63a51d1f 100644 --- a/hook_special.c +++ b/hook_special.c @@ -29,7 +29,6 @@ along with this program. If not, see . #include "CAPE\CAPE.h" #include "CAPE\YaraHarness.h" #include -#include "undoc_defs.h" extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); extern int DoProcessDump(PVOID CallerBase); diff --git a/hooking_32.c b/hooking_32.c index d4c24c21..5a85fd71 100644 --- a/hooking_32.c +++ b/hooking_32.c @@ -719,12 +719,14 @@ int hook_api(hook_t *h, int type) DebugOutput("hook_api: %s export address 0x%p obtained via GetFunctionAddress\n", h->funcname, addr); } } + } - if (addr == NULL && h->timestamp != 0 && h->rva != 0) { - DWORD timestamp = GetTimeStamp(hmod); - if (timestamp == h->timestamp) - addr = (unsigned char *)hmod + h->rva; - } + if (addr == NULL && h->timestamp != 0 && h->rva != 0) { + if (!hmod) + hmod = GetModuleHandleW(h->library); + DWORD timestamp = GetTimeStamp(hmod); + if (timestamp == h->timestamp) + addr = (unsigned char *)hmod + h->rva; } if (addr == NULL || addr == (unsigned char *)0xffbadd11) { diff --git a/hooking_64.c b/hooking_64.c index 2d48a309..05d16cdc 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -1050,13 +1050,16 @@ int hook_api(hook_t *h, int type) DebugOutput("hook_api: %s export address 0x%p obtained via GetFunctionAddress\n", h->funcname, addr); } } + } - if (addr == NULL && h->timestamp != 0 && h->rva != 0) { - DWORD timestamp = GetTimeStamp(hmod); - if (timestamp == h->timestamp) - addr = (unsigned char *)hmod + h->rva; - } + if (addr == NULL && h->timestamp != 0 && h->rva != 0) { + if (!hmod) + hmod = GetModuleHandleW(h->library); + DWORD timestamp = GetTimeStamp(hmod); + if (timestamp == h->timestamp) + addr = (unsigned char *)hmod + h->rva; } + if (addr == NULL) { // function doesn't exist in this DLL, not a critical error return 0; diff --git a/hooks.c b/hooks.c index 54a128e3..46ab7017 100644 --- a/hooks.c +++ b/hooks.c @@ -19,8 +19,17 @@ along with this program. If not, see . #include "misc.h" #include "hooking.h" #include "hooks.h" +#include "CAPE\CAPE.h" +typedef struct +{ + PCHAR FunctionName; + PVOID Address; +} NameByAddress; + +extern NameByAddress* GetAddressesByYara(HMODULE ModuleBase, PCHAR FunctionNames[], SIZE_T FunctionCount, SIZE_T* OutFoundCount); extern VOID CALLBACK New_DllLoadNotification(ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context); +extern PVOID GetAddressByYara(HMODULE ModuleBase, PCHAR FunctionName); extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); extern void ErrorOutput(_In_ LPCTSTR lpOutputString, ...); extern DWORD GetTimeStamp(LPVOID Address); @@ -56,8 +65,11 @@ void disable_tail_call_optimization(void) #define HOOK_FUNCRVA(library, funcname, timestamp, rva) {L###library, #funcname, NULL, NULL, \ &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE, timestamp, rva} -#define HOOK_EXERVA(funcname, addr) {NULL, NULL, (void *) addr, NULL, \ - &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE} +#define HOOK_EXE(funcname) {NULL, #funcname, NULL, NULL, \ + &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE} + +#define HOOK_EXERVA(funcname, timestamp, rva) {NULL, #funcname, NULL, NULL, \ + &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE, timestamp, rva} hook_t full_hooks[] = { @@ -229,7 +241,7 @@ hook_t full_hooks[] = { HOOK(rstrtmgr, RmStartSession), // Registry Hooks - // Note: Most, if not all, of the Registry API go natively from both the 'A' as well as + // Note: Most, if not all, of the Registry API go natively from both the 'A' as well as // the 'W' versions. So we have to hook all the ascii *and* unicode APIs of those functions. HOOK(advapi32, RegOpenKeyExA), HOOK(advapi32, RegOpenKeyExW), @@ -428,6 +440,7 @@ hook_t full_hooks[] = { HOOK(shlwapi, UrlCanonicalizeW), HOOK_NOTAIL(vbe7, rtcCreateObject2, 3), #endif + HOOK(cmd, FindFixAndRun), // Language related hooks HOOK(ntdll, NtQueryDefaultUILanguage), @@ -1623,25 +1636,9 @@ hook_t test_hooks[] = { HOOK_SPECIAL(ntdll, NtContinue), }; -// .exe RVA hooks -#ifdef _WIN64 -// cmd.exe (Win10x64: LTSC, 21H2) -static hook_t g_exe_cmd_win10_x64_hooks[] = { - HOOK_EXERVA(FindFixAndRun, 0xc620), +hook_t exe_hooks[] = { + HOOK_EXE(FindFixAndRun), }; -#define TARGET_EXE_PATH_CMD_WIN10_x64 L"C:\\Windows\\System32\\cmd.exe" -#define TARGET_EXE_PATH_CMD_WIN10_x64_ALT L"C:\\Windows\\Systemnative\\cmd.exe" -#define TARGET_EXE_CODE_CMD_WIN10_x64 "\x48\x89\x5C\x24\x10\x48\x89\x74\x24\x18" - -#else -// cmd.exe (Win10x64: LTSC, 21H2) -static hook_t g_exe_cmd_win10_x86_hooks[] = { - HOOK_EXERVA(FindFixAndRun, 0xad60), -}; -#define TARGET_EXE_PATH_CMD_WIN10_x86 L"C:\\Windows\\SysWOW64\\cmd.exe" -#define TARGET_EXE_CODE_CMD_WIN10_x86 "\x8B\xFF\x55\x8B\xEC\x6A\xFE\x68\xF0\xC9" -#endif - BOOL inside_hook(LPVOID Address) { @@ -1668,6 +1665,52 @@ BOOL set_hooks_dll(const wchar_t *library) return ret; } +void set_hooks_exe(void) +{ + LDR_MODULE* mod; + PEB* peb = (PEB*)get_peb(); + mod = (LDR_MODULE*)peb->LoaderData->InLoadOrderModuleList.Flink; + HMODULE ullExeBase = (HMODULE)(mod->BaseAddress); + + int hook_count = sizeof(exe_hooks) / sizeof(exe_hooks[0]); + char* func_names[sizeof(exe_hooks) / sizeof(exe_hooks[0])]; + + for (int i = 0; i < hook_count; i++) + func_names[i] = (char*)exe_hooks[i].funcname; + + SIZE_T found_count = 0; + NameByAddress* results = GetAddressesByYara(ullExeBase, func_names, hook_count, &found_count); + + if (!results || found_count == 0) { + if (results) free(results); + return; + } + + for (int i = 0; i < hook_count; i++) { + if (exe_hooks[i].timestamp && exe_hooks[i].rva) { + hook_t* hook = &exe_hooks[i]; + if (hook_api(hook, g_config.hook_type) < 0) + DebugOutput("set_hooks_exe: Failed to hook %s at RVA 0x%x", hook->funcname, hook->rva); + else + DebugOutput("set_hooks_exe: Hooked %s at RVA 0x%x", hook->funcname, hook->rva); + } + else for (SIZE_T j = 0; j < found_count; j++) { + if (results[j].FunctionName && results[j].Address && !strcmp(results[j].FunctionName, exe_hooks[i].funcname)) { + hook_t* hook = &exe_hooks[i]; + hook->addr = results[j].Address; + + if (hook_api(hook, g_config.hook_type) < 0) + DebugOutput("set_hooks_exe: Failed to hook %s at 0x%p", hook->funcname, hook->addr); + else + DebugOutput("set_hooks_exe: Hooked %s at 0x%p", hook->funcname, hook->addr); + } + } + } + + free(results); + +} + void set_hooks_by_export_directory(const wchar_t *exportdirectory, const wchar_t *library) { unsigned int Hooked = 0; @@ -1709,71 +1752,6 @@ void revalidate_all_hooks(void) } } -BOOLEAN try_to_set_exe_hooks( - unsigned long long ullExeBase, - UNICODE_STRING* pusFullPath, - const char* szExeName, - const wchar_t* wszExeTargetPath, - const wchar_t* wszAltExeTargetPath, - const unsigned char* pExeTargetCode, - hook_t* exe_hooks, - unsigned int num_hooks, - unsigned hook_type -) { - BOOLEAN found_exe = FALSE; - if (!wcsicmp(wszExeTargetPath, pusFullPath->Buffer) || (wszAltExeTargetPath && !wcsicmp(wszAltExeTargetPath, pusFullPath->Buffer))) { - unsigned char* pTestCode = ((unsigned char*)exe_hooks[0].addr) + ullExeBase; - if (!our_isbadreadptr(pTestCode, 0xa)) { - if (!memcmp(pTestCode, pExeTargetCode, 0xa)) { - DebugOutput("Target module path and code matches type: '%s'; shall set hooks", szExeName); - found_exe = TRUE; - DWORD old_protect; - VirtualProtect(exe_hooks, num_hooks * sizeof(exe_hooks[0]), PAGE_EXECUTE_READWRITE, &old_protect); - for (unsigned int i = 0; i < num_hooks; i++) { - hook_t* hook = &exe_hooks[i]; - // Hook addr is relative, so adjust it for wherever we got loaded - hook->addr = (void*)((unsigned char*)(hook->addr) + ullExeBase); - int ret2 = hook_api(hook, hook_type); - if (ret2 == -1) - DebugOutput("Warning: Unable to hook 0x%x", hook->addr); - } - } - } - } - return found_exe; -} - -#ifdef _WIN64 -BOOLEAN set_unexported_cmd_win10_x64_hooks(unsigned long long ullExeBase, UNICODE_STRING* pusFullPath) -{ - return try_to_set_exe_hooks(ullExeBase, pusFullPath, "cmd", TARGET_EXE_PATH_CMD_WIN10_x64, TARGET_EXE_PATH_CMD_WIN10_x64_ALT, TARGET_EXE_CODE_CMD_WIN10_x64, g_exe_cmd_win10_x64_hooks, ARRAYSIZE(g_exe_cmd_win10_x64_hooks), g_config.hook_type); -} -#else -BOOLEAN set_unexported_cmd_win10_x86_hooks(unsigned long long ullExeBase, UNICODE_STRING* pusFullPath) -{ - return try_to_set_exe_hooks(ullExeBase, pusFullPath, "cmd", TARGET_EXE_PATH_CMD_WIN10_x86, NULL, TARGET_EXE_CODE_CMD_WIN10_x86, g_exe_cmd_win10_x86_hooks, ARRAYSIZE(g_exe_cmd_win10_x86_hooks), g_config.hook_type); -} -#endif - -void set_exe_hooks(void) -{ - BOOLEAN found_exe = FALSE; - LDR_MODULE* mod; PEB* peb = (PEB*)get_peb(); - mod = (LDR_MODULE*)peb->LoaderData->InLoadOrderModuleList.Flink; - unsigned long long ullExeBase = (unsigned long long)(mod->BaseAddress); - UNICODE_STRING usFullPath = mod->FullDllName; - -#ifdef _WIN64 - if (!found_exe) { - found_exe = set_unexported_cmd_win10_x64_hooks(ullExeBase, &usFullPath); - } -#else - if (!found_exe) { - found_exe = set_unexported_cmd_win10_x86_hooks(ullExeBase, &usFullPath); - } -#endif // _WIN64 -} - PVOID g_dll_notify_cookie; extern _LdrRegisterDllNotification pLdrRegisterDllNotification; @@ -1892,8 +1870,6 @@ void set_hooks() Hooked++; } - set_exe_hooks(); - for (unsigned int i = 0; i < num_suspended_threads; i++) { ResumeThread(suspended_threads[i]); CloseHandle(suspended_threads[i]); @@ -1908,5 +1884,7 @@ void set_hooks() DebugOutput("Hooked %d out of %d functions\n", Hooked, hooks_arraysize); + set_hooks_exe(); + hook_enable(); } diff --git a/hooks.h b/hooks.h index 927af8b4..b4bba5d4 100644 --- a/hooks.h +++ b/hooks.h @@ -22,7 +22,6 @@ along with this program. If not, see . #include "ntapi.h" #include #include -#include "undoc_defs.h" // // File Hooks diff --git a/misc.h b/misc.h index 6bf8357b..5c54f8a0 100644 --- a/misc.h +++ b/misc.h @@ -284,3 +284,41 @@ struct envstruct { const char* GetLanguageName(LANGID langID); BOOLEAN our_isbadreadptr(const void* addr, ULONG len); + +typedef HANDLE CRTHANDLE; + +/* +* Structure defs for FindFixAndSave hook in cmd.exe +* From https://github.com/KingKDot/Exorcism/blob/54a44302469160aa7b93f4b72e93206d06a786ac/cmdtest/cmdtest/dllmain.cpp#L70 +*/ +struct savtype { + TCHAR* saveptrs[12]; +}; + +struct relem { + CRTHANDLE rdhndl; // handle to be redirected + TCHAR* fname; // filename (or &n) + CRTHANDLE svhndl; // where orig handle is saved + int flag; // Append flag + TCHAR rdop; // Type ('>' | '<') + struct relem* nxt; // Next structure +}; + +struct node { // Used for operators + int type; // Type of operator + struct savtype save; // FOR processor saves orig strings here + struct relem* rio; // M022 - Linked redirection list + struct node* lhs; // Ptr to left hand side of the operator + struct node* rhs; // Ptr to right hand side of the operator + INT_PTR extra[4]; // M022 - Padding now needed +}; + +struct cmdnode { + int type; // Type of command + struct savtype save; // FOR processor saves orig strings here + struct relem* rio; // M022 - Linked redirection list + PTCHAR cmdline; // Ptr to command line + PTCHAR argptr; // Ptr to type of command + int flag; // M022 - Valid for cond and goto types + int cmdarg; // M022 - Argument to STRTYP routine +}; diff --git a/undoc_defs.h b/undoc_defs.h deleted file mode 100644 index 39110f14..00000000 --- a/undoc_defs.h +++ /dev/null @@ -1,37 +0,0 @@ -typedef HANDLE CRTHANDLE; - -/* -* Structure defs for FindFixAndSave hook in cmd.exe -* From https://github.com/KingKDot/Exorcism/blob/54a44302469160aa7b93f4b72e93206d06a786ac/cmdtest/cmdtest/dllmain.cpp#L70 -*/ -struct savtype { - TCHAR* saveptrs[12]; -}; - -struct relem { - CRTHANDLE rdhndl; // handle to be redirected - TCHAR* fname; // filename (or &n) - CRTHANDLE svhndl; // where orig handle is saved - int flag; // Append flag - TCHAR rdop; // Type ('>' | '<') - struct relem* nxt; // Next structure -}; - -struct node { // Used for operators - int type; // Type of operator - struct savtype save; // FOR processor saves orig strings here - struct relem* rio; // M022 - Linked redirection list - struct node* lhs; // Ptr to left hand side of the operator - struct node* rhs; // Ptr to right hand side of the operator - INT_PTR extra[4]; // M022 - Padding now needed -}; - -struct cmdnode { - int type; // Type of command - struct savtype save; // FOR processor saves orig strings here - struct relem* rio; // M022 - Linked redirection list - PTCHAR cmdline; // Ptr to command line - PTCHAR argptr; // Ptr to type of command - int flag; // M022 - Valid for cond and goto types - int cmdarg; // M022 - Argument to STRTYP routine -}; From 135d8741b13a02cee962c6a52ba805fd26f82ac6 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 19 Aug 2025 14:21:09 +0100 Subject: [PATCH 051/148] FindFixAndRun hook: remove ArgType from logging --- hook_special.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hook_special.c b/hook_special.c index 63a51d1f..e30cd9c9 100644 --- a/hook_special.c +++ b/hook_special.c @@ -204,12 +204,11 @@ HOOKDEF(BOOL, WINAPI, LdrpCallInitRoutine, } HOOKDEF(int, __fastcall, FindFixAndRun, - struct cmdnode *cmdnode + struct cmdnode* cmdnode ) { - // ret is unused in the hook, but set it to 0 for LOQ to have "success" as the return status int ret = 0; - if (cmdnode && !our_isbadreadptr(cmdnode, sizeof(struct cmdnode)) && cmdnode->cmdline != NULL) { - LOQ_zero("system", "uui", "Command", cmdnode->cmdline, "Arguments", cmdnode->argptr, "ArgType", cmdnode->type); + if (cmdnode && !our_isbadreadptr(cmdnode, sizeof(struct cmdnode)) && cmdnode->cmdline != NULL && cmdnode->type == 0) { + LOQ_zero("system", "uu", "Command", cmdnode->cmdline, "Arguments", cmdnode->argptr); } return Old_FindFixAndRun(cmdnode); } From 8aea1ebd5f32a023256f0594978854fad59e14a7 Mon Sep 17 00:00:00 2001 From: enzok <7831008+enzok@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:14:31 -0400 Subject: [PATCH 052/148] Add new hook for MapFileAndCheckSumA --- hook_misc.c | 18 ++++++++++++++++++ hooks.c | 1 + hooks.h | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/hook_misc.c b/hook_misc.c index e532d1c5..cb43e91e 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1961,3 +1961,21 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, return Old_vDbgPrintExWithPrefixInternal(Prefix, ComponentId, Level, Format, arglist, HandleBreakpoint); } + +HOOKDEF(DWORD, WINAPI, MapFileAndCheckSumA, + _In_ PCSTR Filename, + _Out_ PDWORD HeaderSum, + _Out_ PDWORD CheckSum +) { + + DWORD ret = Old_MapFileAndCheckSumA(Filename, HeaderSum, CheckSum); + *CheckSum = *HeaderSum; + + LOQ_zero("imagehlp", "fLL", + "path", Filename, + "header_sum", (long*)HeaderSum, + "checksum", (long*)CheckSum + ); + + return ret; +} diff --git a/hooks.c b/hooks.c index 2f321063..f5857d96 100644 --- a/hooks.c +++ b/hooks.c @@ -1333,6 +1333,7 @@ hook_t office_hooks[] = { HOOK(oleaut32, VarBstrCat), HOOK_NOTAIL(usp10, ScriptIsComplex, 3), HOOK_NOTAIL(inseng,DownloadFile,3), + HOOK(imagehlp, MapFileAndCheckSumA), #ifndef _WIN64 HOOK(ntdll, RtlDosPathNameToNtPathName_U), HOOK(ntdll, NtQueryLicenseValue), diff --git a/hooks.h b/hooks.h index 58502ac0..f3865099 100644 --- a/hooks.h +++ b/hooks.h @@ -3737,4 +3737,10 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, __in BOOLEAN HandleBreakpoint ); +HOOKDEF(DWORD, WINAPI, MapFileAndCheckSumA, + _In_ PCSTR Filename, + _Out_ PDWORD HeaderSum, + _Out_ PDWORD CheckSum +); + #include "hook_vbscript.h" From 517cbedbe47434fa2519614896fe10aa0865ab42 Mon Sep 17 00:00:00 2001 From: mhdo Date: Thu, 21 Aug 2025 17:24:07 -0400 Subject: [PATCH 053/148] Added hook for GetPwrCapabilities to bypass power_capabilities (al-khaser) In case VMs don't support S1-S4 power states, this hooks GetPwrCapabilities and spoofs the return result. --- hook_power.c | 15 +++++++++++++++ hooks.c | 2 ++ hooks.h | 4 ++++ 3 files changed, 21 insertions(+) create mode 100644 hook_power.c diff --git a/hook_power.c b/hook_power.c new file mode 100644 index 00000000..c1c132bd --- /dev/null +++ b/hook_power.c @@ -0,0 +1,15 @@ +#include +#include "ntapi.h" +#include +#include "hooking.h" +#include "log.h" +#include "CAPE\CAPE.h" + +HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, + _Out_ PSYSTEM_POWER_CAPABILITIES lpspc +){ + BOOL ret = Old_GetPwrCapabilities(lpspc); + (*lpspc).SystemS4 = 1; + LOQ_bool("device", "iiii", "S1", lpspc->SystemS1, "S2", lpspc->SystemS2, "S3", lpspc->SystemS3, "S4", lpspc->SystemS4); + return ret; +} \ No newline at end of file diff --git a/hooks.c b/hooks.c index 2f321063..e8888e23 100644 --- a/hooks.c +++ b/hooks.c @@ -648,6 +648,8 @@ hook_t full_hooks[] = { HOOK(cryptsp, CryptGenRandom), HOOK(cryptsp, CryptImportKey), + HOOK(powrprof, GetPwrCapabilities), + // VBScript hooks HOOK_SPECIAL(vbscript, VbsCCur), HOOK_SPECIAL(vbscript, VbsCInt), diff --git a/hooks.h b/hooks.h index 58502ac0..33a70378 100644 --- a/hooks.h +++ b/hooks.h @@ -3735,6 +3735,10 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, __in PCHAR Format, __in va_list arglist, __in BOOLEAN HandleBreakpoint +); + +HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, + _Out_ PSYSTEM_POWER_CAPABILITIES lpspc ); #include "hook_vbscript.h" From d4aa77055fc8435ce3dc4531480aea991e12059d Mon Sep 17 00:00:00 2001 From: mhdo Date: Fri, 22 Aug 2025 12:07:22 +0700 Subject: [PATCH 054/148] Update hook_power.c for more accurate anti-anti-vm measures Most Windows VMs don't support either S0 or S3 sleep out of the box, which can be used as an anti-VM measure. --- hook_power.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/hook_power.c b/hook_power.c index c1c132bd..2ab1a3c6 100644 --- a/hook_power.c +++ b/hook_power.c @@ -9,7 +9,13 @@ HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, _Out_ PSYSTEM_POWER_CAPABILITIES lpspc ){ BOOL ret = Old_GetPwrCapabilities(lpspc); - (*lpspc).SystemS4 = 1; - LOQ_bool("device", "iiii", "S1", lpspc->SystemS1, "S2", lpspc->SystemS2, "S3", lpspc->SystemS3, "S4", lpspc->SystemS4); + if (ret) { + // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. + // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. + (*lpspc).SystemAoAc = 1; + (*lpspc).SystemS4 = 1; + (*lpspc).SystemS5 = 1; + } + LOQ_bool("device", ""); return ret; -} \ No newline at end of file +} From d9cd7cf9ddc2a7dfc137a02ceb44c30add8a711f Mon Sep 17 00:00:00 2001 From: mhdo Date: Fri, 22 Aug 2025 13:04:16 +0700 Subject: [PATCH 055/148] Update hook_power.c Corrected field name --- hook_power.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hook_power.c b/hook_power.c index 2ab1a3c6..9316793c 100644 --- a/hook_power.c +++ b/hook_power.c @@ -12,7 +12,7 @@ HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, if (ret) { // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. - (*lpspc).SystemAoAc = 1; + (*lpspc).AoAc = 1; (*lpspc).SystemS4 = 1; (*lpspc).SystemS5 = 1; } From 27a7d3d77e4da9e1bfcc1d92f4cf468bd382c697 Mon Sep 17 00:00:00 2001 From: mhdo Date: Fri, 22 Aug 2025 16:16:15 +0700 Subject: [PATCH 056/148] Moved hook declaration to misc hooks --- hooks.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hooks.h b/hooks.h index 33a70378..184ea474 100644 --- a/hooks.h +++ b/hooks.h @@ -2047,6 +2047,11 @@ HOOKDEF(HRESULT, WINAPI, PStoreCreateInstance, _In_ DWORD dwFlags ); +HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, + _Out_ PSYSTEM_POWER_CAPABILITIES lpspc +); + + // // Network Hooks // @@ -3737,8 +3742,4 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, __in BOOLEAN HandleBreakpoint ); -HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, - _Out_ PSYSTEM_POWER_CAPABILITIES lpspc -); - #include "hook_vbscript.h" From 04d21b30a3e9cf37989c1a64a4192f0c2a9449a6 Mon Sep 17 00:00:00 2001 From: mhdo Date: Fri, 22 Aug 2025 16:17:50 +0700 Subject: [PATCH 057/148] Moved hook declaration to misc hooks --- hooks.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hooks.c b/hooks.c index e8888e23..4c8ff34f 100644 --- a/hooks.c +++ b/hooks.c @@ -425,6 +425,7 @@ hook_t full_hooks[] = { HOOK(shlwapi, UrlCanonicalizeW), HOOK_NOTAIL(vbe7, rtcCreateObject2, 3), #endif + HOOK(powrprof, GetPwrCapabilities), // Language related hooks HOOK(ntdll, NtQueryDefaultUILanguage), @@ -648,8 +649,6 @@ hook_t full_hooks[] = { HOOK(cryptsp, CryptGenRandom), HOOK(cryptsp, CryptImportKey), - HOOK(powrprof, GetPwrCapabilities), - // VBScript hooks HOOK_SPECIAL(vbscript, VbsCCur), HOOK_SPECIAL(vbscript, VbsCInt), From 1e84647d7c3d3c57782d82aea037b6805228fd71 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 22 Aug 2025 10:46:53 +0100 Subject: [PATCH 058/148] Fix GetMinPESize() parameter (PIMAGE_DOS_HEADER) --- CAPE/CAPE.c | 14 ++++++++++++-- CAPE/CAPE.h | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 3f60efea..83b92bb3 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -2195,13 +2195,23 @@ BOOL TestPERequirements(PIMAGE_NT_HEADERS pNtHeader) } //************************************************************************************** -SIZE_T GetMinPESize(PIMAGE_NT_HEADERS pNtHeader) +SIZE_T GetMinPESize(PIMAGE_DOS_HEADER pDosHeader) //************************************************************************************** { - SIZE_T MinSize; + SIZE_T MinSize = 0; + PIMAGE_NT_HEADERS pNtHeader = NULL; __try { + if (!IsAddressAccessible(pDosHeader)) + return 0; + + if (pDosHeader->e_lfanew && (ULONG)pDosHeader->e_lfanew < PE_HEADER_LIMIT && ((ULONG)pDosHeader->e_lfanew & 3) == 0) + pNtHeader = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + (ULONG)pDosHeader->e_lfanew); + + if (!pNtHeader || !TestPERequirements(pNtHeader)) + return 0; + PIMAGE_SECTION_HEADER NtSection; if ((pNtHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) && (pNtHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)) diff --git a/CAPE/CAPE.h b/CAPE/CAPE.h index 3cadd931..d12faccd 100644 --- a/CAPE/CAPE.h +++ b/CAPE/CAPE.h @@ -46,7 +46,7 @@ PVOID GetFunctionAddress(HMODULE ModuleBase, PCHAR FunctionName); BOOL IsAddressAccessible(PVOID Address); BOOL IsAddressExecutable(PVOID Address); BOOL TestPERequirements(PIMAGE_NT_HEADERS pNtHeader); -SIZE_T GetMinPESize(PIMAGE_NT_HEADERS pNtHeader); +SIZE_T GetMinPESize(PIMAGE_DOS_HEADER pDosHeader); double GetEntropy(PUCHAR Buffer); void SanitiseString(char *Dst, const char *Src, size_t Size); PCHAR TranslatePathFromDeviceToLetter(PCHAR DeviceFilePath); From d4781c064f0ac159a702b94da395cccb8e84bbde Mon Sep 17 00:00:00 2001 From: mhdo Date: Fri, 22 Aug 2025 09:52:17 -0400 Subject: [PATCH 059/148] Changed according to comments --- hook_misc.c | 16 ++++++++++++++++ hook_power.c | 21 --------------------- 2 files changed, 16 insertions(+), 21 deletions(-) delete mode 100644 hook_power.c diff --git a/hook_misc.c b/hook_misc.c index e532d1c5..c4a675b1 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -26,6 +26,7 @@ along with this program. If not, see . #include "hook_sleep.h" #include "config.h" #include "ignore.h" +#include "powerbase.h" #include "CAPE\CAPE.h" #include "CAPE\Injection.h" #include "CAPE\Debugger.h" @@ -1961,3 +1962,18 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, return Old_vDbgPrintExWithPrefixInternal(Prefix, ComponentId, Level, Format, arglist, HandleBreakpoint); } + +HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, + _Out_ PSYSTEM_POWER_CAPABILITIES lpspc +) { + BOOL ret = Old_GetPwrCapabilities(lpspc); + if (ret) { + // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. + // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. + (*lpspc).AoAc = 1; + (*lpspc).SystemS4 = 1; + (*lpspc).SystemS5 = 1; + } + LOQ_bool("device", ""); + return ret; +} diff --git a/hook_power.c b/hook_power.c deleted file mode 100644 index 9316793c..00000000 --- a/hook_power.c +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include "ntapi.h" -#include -#include "hooking.h" -#include "log.h" -#include "CAPE\CAPE.h" - -HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, - _Out_ PSYSTEM_POWER_CAPABILITIES lpspc -){ - BOOL ret = Old_GetPwrCapabilities(lpspc); - if (ret) { - // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. - // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. - (*lpspc).AoAc = 1; - (*lpspc).SystemS4 = 1; - (*lpspc).SystemS5 = 1; - } - LOQ_bool("device", ""); - return ret; -} From eda5cc8918edd7a2a155dc6958e80e6723877a93 Mon Sep 17 00:00:00 2001 From: mhdo Date: Fri, 22 Aug 2025 09:54:57 -0400 Subject: [PATCH 060/148] Moved back --- hooks.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hooks.h b/hooks.h index 184ea474..b49b8ddb 100644 --- a/hooks.h +++ b/hooks.h @@ -2047,14 +2047,11 @@ HOOKDEF(HRESULT, WINAPI, PStoreCreateInstance, _In_ DWORD dwFlags ); -HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, - _Out_ PSYSTEM_POWER_CAPABILITIES lpspc -); - // // Network Hooks // + HOOKDEF(DWORD, WINAPI, InternetConfirmZoneCrossingA, _In_ HWND hWnd, _In_ LPTSTR szUrlPrev, @@ -3742,4 +3739,8 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, __in BOOLEAN HandleBreakpoint ); +HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, + _Out_ PSYSTEM_POWER_CAPABILITIES lpspc +); + #include "hook_vbscript.h" From c2e8fddeca67b60cdb555cc9b0482c00e976c745 Mon Sep 17 00:00:00 2001 From: mhdo Date: Fri, 22 Aug 2025 09:56:35 -0400 Subject: [PATCH 061/148] Update hooks.h --- hooks.h | 1 - 1 file changed, 1 deletion(-) diff --git a/hooks.h b/hooks.h index b49b8ddb..2ce67c30 100644 --- a/hooks.h +++ b/hooks.h @@ -2047,7 +2047,6 @@ HOOKDEF(HRESULT, WINAPI, PStoreCreateInstance, _In_ DWORD dwFlags ); - // // Network Hooks // From 438141a0e1a7eb75870c13b1bc41422f093c93e1 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 26 Aug 2025 11:59:00 +0100 Subject: [PATCH 062/148] Remove Scylla code preventing dumping of MEW executables (fixes #106) --- CAPE/Scylla/PeParser.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CAPE/Scylla/PeParser.cpp b/CAPE/Scylla/PeParser.cpp index 20b9f0fb..9e656fb6 100644 --- a/CAPE/Scylla/PeParser.cpp +++ b/CAPE/Scylla/PeParser.cpp @@ -554,11 +554,6 @@ void PeParser::getDosAndNtHeader(BYTE* memory, LONG size) //DebugOutput("PeParser: getDosAndNtHeader: dosStubSize size 0x%x.\n", dosStubSize); #endif } - else if (pDosHeader->e_lfanew < sizeof(IMAGE_DOS_HEADER)) - { - //Overlapped Headers, e.g. Spack (by Bagie) - pDosHeader->e_lfanew = sizeof(IMAGE_DOS_HEADER); - } } if (!pDosHeader->e_lfanew) From b52cb56d7664bb6f38ad4d1ed14d466d1494077e Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 26 Aug 2025 14:00:13 +0100 Subject: [PATCH 063/148] FindFixAndRun hook: improve FindFixAndRun yara signatures --- CAPE/YaraHarness.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index 92ddd483..9bfd1751 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -75,14 +75,14 @@ char InternalYara[] = "{strings:$function = {40 55 53 56 41 54 41 55 41 56 41 57 48 81 EC 20 01 00 00 48 8D 6C 24 20 48 8B 05 [4] 48 33 C5 48 89 85 ?? 00 00 00 4C 89 4D ?? 44 89 45 ?? 44 8B E2 89 55 ?? 48 8B D1 48 89 4D ?? 48 8B 85}" "condition:uint16(0) == 0x5a4d and any of them}" "rule FindFixAndRun" - "{strings:$function = {48 89 5C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 81 EC [2] 00 00 48 8B 05 [4] 48 33 C4 48 89 84 [5] 4C 8B F1 BF 04 01 00 00 8B D7}" + "{strings:$function = {48 89 5C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 81 EC [80-110] 85 C0 0F 88 [2] 00 00 49 8B 4? 70 66 83 (78|79) 02 3A 0F 84 [2] 00 00 48 8D 54 24 20 49 8B CE E8}" "condition:uint16(0) == 0x5a4d and any of them}" #else "rule vDbgPrintExWithPrefixInternal" "{strings:$function = {68 90 00 00 00 68 [4] E8 [4] 89 95 [4] 89 8D [4] 8B 45 ?? 89 85 [4] 8B 45 ?? 89 85 [4] 64 A1 18 00 00 00 89 45 ?? 83 FA FF 0F 84}" "condition:uint16(0) == 0x5a4d and any of them}" "rule FindFixAndRun" - "{strings:$function = {8B FF 55 8B EC 6A FE 68 [4] 68 [4] 64 A1 00 00 00 00 50 81 EC [17] 53 56 57 50 8D 45 ?? 64 A3 00 00 00 00 8B F9}" + "{strings:$function = {8B FF 55 8B EC 6A FE 68 [4] 68 [4] 64 A1 00 00 00 00 50 81 EC [90-96] 00 0F 84 [4] B8 E7 7F 00 00 50 8D 8D [4] E8 [4] 85 C0 0F 88 [4] 8B 4? 38 66 83 7? 02 3A 0F 84}" "condition:uint16(0) == 0x5a4d and any of them}" #endif "rule capemon" From 501087801998fea360f9954de4b9ee866cae9b14 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 26 Aug 2025 14:49:24 +0100 Subject: [PATCH 064/148] hook_api (64-bit): test h->library before deref (exe hooks) --- hooking_64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooking_64.c b/hooking_64.c index 05d16cdc..3d99a00b 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -1103,7 +1103,7 @@ int hook_api(hook_t *h, int type) addr = handle_stub(h, addr); - if (!wcscmp(h->library, L"ntdll") && !memcmp(addr, "\x4c\x8b\xd1\xb8", 4) && memcmp(addr+8, "\x0f\x05", 2)) { + if (h->library && !wcscmp(h->library, L"ntdll") && !memcmp(addr, "\x4c\x8b\xd1\xb8", 4) && memcmp(addr+8, "\x0f\x05", 2)) { // hooking a native API, leave in the mov eax, instruction // as some malware depends on this for direct syscalls // missing a few syscalls is better than crashing and getting no information From 8307b5037ff370df505630f331fe1b282879a9f4 Mon Sep 17 00:00:00 2001 From: mhdo Date: Tue, 26 Aug 2025 14:16:00 -0400 Subject: [PATCH 065/148] Update hook_misc.c --- hook_misc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hook_misc.c b/hook_misc.c index c4a675b1..f2058661 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1967,12 +1967,13 @@ HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, _Out_ PSYSTEM_POWER_CAPABILITIES lpspc ) { BOOL ret = Old_GetPwrCapabilities(lpspc); - if (ret) { + if (ret && lpspc) { // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. (*lpspc).AoAc = 1; (*lpspc).SystemS4 = 1; (*lpspc).SystemS5 = 1; + (*lpspc).ThermalControl = 1; } LOQ_bool("device", ""); return ret; From 883d1c9af94bd69aa8a303356a8c903e3f7f2b13 Mon Sep 17 00:00:00 2001 From: mhdo Date: Tue, 26 Aug 2025 16:31:44 -0400 Subject: [PATCH 066/148] Added hook for NtPowerInformation --- hook_misc.c | 33 +++++++++++++++++++++++++++++---- hooks.c | 2 +- hooks.h | 8 ++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/hook_misc.c b/hook_misc.c index f2058661..a83b1163 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1970,11 +1970,36 @@ HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, if (ret && lpspc) { // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. - (*lpspc).AoAc = 1; - (*lpspc).SystemS4 = 1; - (*lpspc).SystemS5 = 1; - (*lpspc).ThermalControl = 1; + lpspc->AoAc = 1; + lpspc->SystemS4 = 1; + lpspc->SystemS5 = 1; + lpspc->ThermalControl = 1; } LOQ_bool("device", ""); return ret; } + +HOOKDEF(NTSTATUS, WINAPI, NtPowerInformation, + __in POWER_INFORMATION_LEVEL InformationLevel, + __in_opt PVOID InputBuffer, + __in ULONG InputBufferLength, + __out_opt PVOID OutputBuffer, + __in ULONG OutputBufferLength +) { + NTSTATUS ret = Old_NtPowerInformation(InformationLevel, InputBuffer, InputBufferLength, OutputBuffer, OutputBufferLength); + if (ret == 0 && OutputBuffer) { + // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. + // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. + SYSTEM_POWER_CAPABILITIES* ptr = (SYSTEM_POWER_CAPABILITIES *)OutputBuffer; + ptr->AoAc = 1; + ptr->SystemS4 = 1; + ptr->SystemS5 = 1; + ptr->ThermalControl = 1; + LOQ_ntstatus("device", "ibb", + "InformationLevel", InformationLevel, + "InputBuffer", InputBufferLength, InputBuffer, + "OutputBuffer", OutputBufferLength, OutputBuffer); + } + + return ret; +} \ No newline at end of file diff --git a/hooks.c b/hooks.c index 4c8ff34f..5a13f788 100644 --- a/hooks.c +++ b/hooks.c @@ -426,7 +426,7 @@ hook_t full_hooks[] = { HOOK_NOTAIL(vbe7, rtcCreateObject2, 3), #endif HOOK(powrprof, GetPwrCapabilities), - + HOOK(ntdll, NtPowerInformation), // Language related hooks HOOK(ntdll, NtQueryDefaultUILanguage), HOOK(ntdll, NtQueryInstallUILanguage), diff --git a/hooks.h b/hooks.h index 2ce67c30..0d4461fb 100644 --- a/hooks.h +++ b/hooks.h @@ -3742,4 +3742,12 @@ HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, _Out_ PSYSTEM_POWER_CAPABILITIES lpspc ); +HOOKDEF(NTSTATUS, WINAPI, NtPowerInformation, + __in POWER_INFORMATION_LEVEL InformationLevel, + __in_opt PVOID InputBuffer, + __in ULONG InputBufferLength, + __out_opt PVOID OutputBuffer, + __in ULONG OutputBufferLength +); + #include "hook_vbscript.h" From 158e745cd452a2fa2d4467057682c296f29bc675 Mon Sep 17 00:00:00 2001 From: mhdo Date: Tue, 26 Aug 2025 16:44:13 -0400 Subject: [PATCH 067/148] Added more information to logging --- hook_misc.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/hook_misc.c b/hook_misc.c index a83b1163..b0d87ff3 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1975,7 +1975,7 @@ HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, lpspc->SystemS5 = 1; lpspc->ThermalControl = 1; } - LOQ_bool("device", ""); + LOQ_bool("device", "b", "lpspc", sizeof(SYSTEM_POWER_CAPABILITIES), lpspc); return ret; } @@ -1995,11 +1995,10 @@ HOOKDEF(NTSTATUS, WINAPI, NtPowerInformation, ptr->SystemS4 = 1; ptr->SystemS5 = 1; ptr->ThermalControl = 1; - LOQ_ntstatus("device", "ibb", - "InformationLevel", InformationLevel, - "InputBuffer", InputBufferLength, InputBuffer, - "OutputBuffer", OutputBufferLength, OutputBuffer); } - + LOQ_ntstatus("device", "ibb", + "InformationLevel", InformationLevel, + "InputBuffer", InputBufferLength, InputBuffer, + "OutputBuffer", OutputBufferLength, OutputBuffer); return ret; } \ No newline at end of file From 9386ab807237417f60162947576e6daee3341d3f Mon Sep 17 00:00:00 2001 From: mhdo Date: Wed, 27 Aug 2025 20:40:59 -0400 Subject: [PATCH 068/148] Update hooks.c --- hooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks.c b/hooks.c index 481964f9..0e0d4cac 100644 --- a/hooks.c +++ b/hooks.c @@ -441,7 +441,7 @@ hook_t full_hooks[] = { HOOK_NOTAIL(vbe7, rtcCreateObject2, 3), #endif - HOOK(powrprof, GetPwrCapabilities), + HOOK(powrprof, GetPwrCapabilities), HOOK(ntdll, NtPowerInformation), HOOK(cmd, FindFixAndRun), From 10cead2d3b96d481a31fe5e27c1e111c32acfdfd Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 28 Aug 2025 12:19:09 +0100 Subject: [PATCH 069/148] Add 'hook watch' feature to log hooks encountered (even if not executed) to analysis log: hook-watch=1, actionX=hook-watch --- CAPE/Trace.c | 5 +++++ config.c | 5 +++++ config.h | 1 + hooking.c | 3 +++ 4 files changed, 14 insertions(+) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index a767810c..6365bf24 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -1762,6 +1762,11 @@ void ActionDispatcher(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst De DebuggerOutput("ActionDispatcher: Terminating process.\n"); New_NtTerminateProcess(NULL, 1); } + else if (!stricmp(Action, "hook-watch")) + { + g_config.hook_watch = 1; + DebuggerOutput("ActionDispatcher: Hook watch enabled.\n"); + } else if (stricmp(Action, "custom")) DebuggerOutput("ActionDispatcher: Unrecognised action: (%s)\n", Action); diff --git a/config.c b/config.c index a7db0894..fa7b15a3 100644 --- a/config.c +++ b/config.c @@ -1375,6 +1375,11 @@ void parse_config_line(char* line) if (g_config.snaps) DebugOutput("Loader snaps enabled.\n"); } + else if (!stricmp(key, "hook-watch")) { + g_config.hook_watch = value[0] == '1'; + if (g_config.hook_watch) + DebugOutput("Config: Hook watch enabled.\n"); + } else if (stricmp(key, "no-iat")) DebugOutput("Monitor config - unrecognised key %s.\n", key); diff --git a/config.h b/config.h index c10ceae3..194490a6 100644 --- a/config.h +++ b/config.h @@ -308,6 +308,7 @@ struct _g_config { int base_on_caller; int trace_times; char *trace_into_api[EXCLUSION_MAX]; + int hook_watch; }; extern struct _g_config g_config; diff --git a/hooking.c b/hooking.c index de093c8e..8267b468 100644 --- a/hooking.c +++ b/hooking.c @@ -278,6 +278,9 @@ void api_dispatch(hook_t *h, hook_info_t *hookinfo) else BreakpointOnReturn((PVOID)hookinfo->return_address); } + + if (g_config.hook_watch) + DebugOutput("api_dispatch: %s\n", h->funcname); } void add_force_hook_thread_func(const char* function) From 2a41c631422844336f12d96a33322ef581d5cd05 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 28 Aug 2025 16:46:08 +0100 Subject: [PATCH 070/148] Fix logging bug in NtReadVirtualMemory hook --- hook_process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hook_process.c b/hook_process.c index d464057b..2398865d 100644 --- a/hook_process.c +++ b/hook_process.c @@ -912,7 +912,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtReadVirtualMemory, } else { LOQ_ntstatus( - "process", "piphB", + "process", "pphB", "ProcessHandle", ProcessHandle, "BaseAddress", BaseAddress, "Size", NumberOfBytesToRead, From c73909c969a37d64615877b0f8b3fe54b073f625 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 28 Aug 2025 17:01:48 +0100 Subject: [PATCH 071/148] Fix logging bug in NtWriteVirtualMemory hook --- hook_process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hook_process.c b/hook_process.c index 2398865d..1fd9fb14 100644 --- a/hook_process.c +++ b/hook_process.c @@ -989,7 +989,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtWriteVirtualMemory, } else { LOQ_ntstatus( - "process", "pipBhs", + "process", "ppBhs", "ProcessHandle", ProcessHandle, "BaseAddress", BaseAddress, "Buffer", NumberOfBytesWritten, Buffer, From f18dd162262e25e9263205773fd012d273b4384e Mon Sep 17 00:00:00 2001 From: mhdo Date: Thu, 28 Aug 2025 12:37:47 -0400 Subject: [PATCH 072/148] Removed GetPwrCapabilities Hook --- hooks.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/hooks.c b/hooks.c index 0e0d4cac..2421a999 100644 --- a/hooks.c +++ b/hooks.c @@ -440,8 +440,6 @@ hook_t full_hooks[] = { HOOK(shlwapi, UrlCanonicalizeW), HOOK_NOTAIL(vbe7, rtcCreateObject2, 3), #endif - - HOOK(powrprof, GetPwrCapabilities), HOOK(ntdll, NtPowerInformation), HOOK(cmd, FindFixAndRun), From 210ab9c36cf6db40d084fdb54f555b9b7e1fd758 Mon Sep 17 00:00:00 2001 From: mhdo Date: Thu, 28 Aug 2025 12:38:11 -0400 Subject: [PATCH 073/148] Removed GetPwrCapabilities Hook --- hooks.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hooks.h b/hooks.h index b0c1442d..6e88da5f 100644 --- a/hooks.h +++ b/hooks.h @@ -3738,10 +3738,6 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, __in BOOLEAN HandleBreakpoint ); -HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, - _Out_ PSYSTEM_POWER_CAPABILITIES lpspc -); - HOOKDEF(NTSTATUS, WINAPI, NtPowerInformation, __in POWER_INFORMATION_LEVEL InformationLevel, __in_opt PVOID InputBuffer, From 33e78ec2bdfbeb016ec1a0c74f042b96f5210861 Mon Sep 17 00:00:00 2001 From: mhdo Date: Thu, 28 Aug 2025 12:40:58 -0400 Subject: [PATCH 074/148] Removed hook and added more checks --- hook_misc.c | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/hook_misc.c b/hook_misc.c index b0d87ff3..305c3479 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1963,22 +1963,6 @@ HOOKDEF(ULONG, __fastcall, vDbgPrintExWithPrefixInternal, return Old_vDbgPrintExWithPrefixInternal(Prefix, ComponentId, Level, Format, arglist, HandleBreakpoint); } -HOOKDEF(BOOL, WINAPI, GetPwrCapabilities, - _Out_ PSYSTEM_POWER_CAPABILITIES lpspc -) { - BOOL ret = Old_GetPwrCapabilities(lpspc); - if (ret && lpspc) { - // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. - // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. - lpspc->AoAc = 1; - lpspc->SystemS4 = 1; - lpspc->SystemS5 = 1; - lpspc->ThermalControl = 1; - } - LOQ_bool("device", "b", "lpspc", sizeof(SYSTEM_POWER_CAPABILITIES), lpspc); - return ret; -} - HOOKDEF(NTSTATUS, WINAPI, NtPowerInformation, __in POWER_INFORMATION_LEVEL InformationLevel, __in_opt PVOID InputBuffer, @@ -1987,7 +1971,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtPowerInformation, __in ULONG OutputBufferLength ) { NTSTATUS ret = Old_NtPowerInformation(InformationLevel, InputBuffer, InputBufferLength, OutputBuffer, OutputBufferLength); - if (ret == 0 && OutputBuffer) { + if (ret == 0 && OutputBuffer && InformationLevel == SystemPowerCapabilities && OutputBufferLength >= sizeof(SYSTEM_POWER_CAPABILITIES)) { // Most VM systems does not support either S0 or S3 sleep, which can be used to detect the presence of a VM. // S0, S4 and S5 being enabled is typical for a normal Modern Standby machine. SYSTEM_POWER_CAPABILITIES* ptr = (SYSTEM_POWER_CAPABILITIES *)OutputBuffer; @@ -2001,4 +1985,4 @@ HOOKDEF(NTSTATUS, WINAPI, NtPowerInformation, "InputBuffer", InputBufferLength, InputBuffer, "OutputBuffer", OutputBufferLength, OutputBuffer); return ret; -} \ No newline at end of file +} From 45e08a8744e667ab49d7c6607779bf3ac1c970b3 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 28 Aug 2025 18:18:59 +0100 Subject: [PATCH 075/148] AddTrackedRegion: remove unnecessary logging (CAPE.c) --- CAPE/CAPE.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 385e11fb..e98e4603 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -1072,10 +1072,6 @@ PTRACKEDREGION AddTrackedRegion(PVOID Address, ULONG Protect) if (TrackedRegion->EntryPoint) { TrackedRegion->MinPESize = GetMinPESize(TrackedRegion->AllocationBase); - if (TrackedRegion->MinPESize) - DebugOutput("AddTrackedRegion: Min PE size 0x%x", TrackedRegion->MinPESize); - //else - // DebugOutput("AddTrackedRegion: GetMinPESize failed"); #ifdef DEBUG_COMMENTS if (!PageAlreadyTracked) DebugOutput("AddTrackedRegion: New region at 0x%p added to tracked regions: EntryPoint 0x%x, Entropy %e\n", TrackedRegion->AllocationBase, TrackedRegion->EntryPoint, TrackedRegion->Entropy); From b318e5f8aa23d74eb0c55bcf0de8859db69a8e87 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 1 Sep 2025 16:33:31 +0100 Subject: [PATCH 076/148] CoCreateInstance hook: remove disable_sleep_skip() breaking e.g. 493b6a86546ac79b7647bd6f3c3523dd910e6a56e427c45197e957e268df8df2 --- hook_special.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hook_special.c b/hook_special.c index e30cd9c9..e4dc069d 100644 --- a/hook_special.c +++ b/hook_special.c @@ -380,8 +380,6 @@ HOOKDEF(HRESULT, WINAPI, CoCreateInstance, inspect_clsid(&id1); } - disable_sleep_skip(); - set_lasterrors(&lasterror); memcpy(&saved_hookinfo, hook_info(), sizeof(saved_hookinfo)); @@ -432,8 +430,6 @@ HOOKDEF(HRESULT, WINAPI, CoCreateInstanceEx, inspect_clsid(&id1); } - disable_sleep_skip(); - set_lasterrors(&lasterror); memcpy(&saved_hookinfo, hook_info(), sizeof(saved_hookinfo)); From cd9307d688496b0d447487399c29684a66186e7b Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 2 Sep 2025 15:12:34 +0100 Subject: [PATCH 077/148] Loader: use NewImportDirectorySize for patched import directory size to allow RestoreHeaders() to work properly, and hence VMProtect binaries --- loader/loader/Loader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader/loader/Loader.c b/loader/loader/Loader.c index 4359624a..99aac950 100644 --- a/loader/loader/Loader.c +++ b/loader/loader/Loader.c @@ -1139,7 +1139,7 @@ static int InjectDllViaIAT(HANDLE ProcessHandle, HANDLE ThreadHandle, const char } // Now set the import table directory entry to point to the new table - NtHeader.IMPORT_DIRECTORY.Size = NewSizeOfImportDescriptors; + NtHeader.IMPORT_DIRECTORY.Size = NewImportDirectorySize; NtHeader.IMPORT_DIRECTORY.VirtualAddress = NewImportsRVA; // Set bound imports values to zero to prevent them overriding our new import table From cc2e89e816df9898ef2b2cbc2f3673bb2ae04970 Mon Sep 17 00:00:00 2001 From: enzo <7831008+enzok@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:59:06 -0400 Subject: [PATCH 078/148] Update hook_misc.c --- hook_misc.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/hook_misc.c b/hook_misc.c index cb43e91e..46c06653 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1967,15 +1967,11 @@ HOOKDEF(DWORD, WINAPI, MapFileAndCheckSumA, _Out_ PDWORD HeaderSum, _Out_ PDWORD CheckSum ) { - DWORD ret = Old_MapFileAndCheckSumA(Filename, HeaderSum, CheckSum); - *CheckSum = *HeaderSum; + if (HeaderSum && CheckSum) + *CheckSum = *HeaderSum; - LOQ_zero("imagehlp", "fLL", - "path", Filename, - "header_sum", (long*)HeaderSum, - "checksum", (long*)CheckSum - ); + LOQ_zero("misc", "fhh", "Filename", Filename, "HeaderSum", *HeaderSum, "CheckSum", *CheckSum); return ret; } From 41cf95c8a88a9e7bcc0d1c3e9effefa2966464a8 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 10 Sep 2025 17:30:30 +0100 Subject: [PATCH 079/148] MapFileAndCheckSumA hook: add more pointer checks (hook_misc.c) --- hook_misc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hook_misc.c b/hook_misc.c index 46c06653..f01184c9 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1968,10 +1968,14 @@ HOOKDEF(DWORD, WINAPI, MapFileAndCheckSumA, _Out_ PDWORD CheckSum ) { DWORD ret = Old_MapFileAndCheckSumA(Filename, HeaderSum, CheckSum); + if (HeaderSum && CheckSum) *CheckSum = *HeaderSum; - LOQ_zero("misc", "fhh", "Filename", Filename, "HeaderSum", *HeaderSum, "CheckSum", *CheckSum); + if (HeaderSum && CheckSum) + LOQ_zero("misc", "fhh", "Filename", Filename, "HeaderSum", *HeaderSum, "CheckSum", *CheckSum); + else + LOQ_zero("misc", "f", "Filename", Filename); return ret; } From 0d875bd1ad5eaa463479d84c8ebd8670aca32d12 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 15 Sep 2025 22:15:24 +0100 Subject: [PATCH 080/148] Fix indentation in CAPE.c --- CAPE/CAPE.c | 264 ++++++++++++++++++++++++++-------------------------- 1 file changed, 132 insertions(+), 132 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index e98e4603..4259d1b5 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -109,9 +109,9 @@ typedef struct _hook_info_t { } hook_info_t; typedef SIZE_T (WINAPI *_RtlCompareMemory)( - _In_ const VOID* Source1, - _In_ const VOID* Source2, - _In_ SIZE_T Length + _In_ const VOID* Source1, + _In_ const VOID* Source2, + _In_ SIZE_T Length ); extern _RtlCompareMemory pRtlCompareMemory; @@ -259,33 +259,33 @@ PVOID GetHookCallerBase() void SanitiseString(char *Dst, const char *Src, size_t Size) //************************************************************************************** { - size_t Length = strlen(Src); - size_t NewLength = 0; + size_t Length = strlen(Src); + size_t NewLength = 0; - for (size_t i = 0; i < Length && NewLength < Size - 1; ++i) - if (Src[i] == '%') - NewLength += 2; - else - NewLength++; + for (size_t i = 0; i < Length && NewLength < Size - 1; ++i) + if (Src[i] == '%') + NewLength += 2; + else + NewLength++; - if (NewLength >= Size) - NewLength = Size - 1; + if (NewLength >= Size) + NewLength = Size - 1; - Dst[NewLength] = '\0'; + Dst[NewLength] = '\0'; - for (int i = (int)Length - 1, j = (int)NewLength - 1; i >= 0 && j >= 0; --i) - if (Src[i] == '%') - { - if (j >= 1) - { - Dst[j--] = '%'; - Dst[j--] = '%'; - } - } - else if ((unsigned char)Src[i] < 0x0a || (unsigned char)Src[i] > 0x7E) - Dst[j--] = '?'; - else - Dst[j--] = Src[i]; + for (int i = (int)Length - 1, j = (int)NewLength - 1; i >= 0 && j >= 0; --i) + if (Src[i] == '%') + { + if (j >= 1) + { + Dst[j--] = '%'; + Dst[j--] = '%'; + } + } + else if ((unsigned char)Src[i] < 0x0a || (unsigned char)Src[i] > 0x7E) + Dst[j--] = '?'; + else + Dst[j--] = Src[i]; } //************************************************************************************** @@ -655,18 +655,18 @@ PVOID GetNonExportedFunctionAddress(HMODULE ModuleBase, PCHAR ExportName, int Of if (!ModuleBase || !ExportName) return NULL; - DWORD ExportRVA = (DWORD)((ULONG_PTR)GetProcAddress(ModuleBase, ExportName) - (ULONG_PTR)ModuleBase); - if (!ExportRVA) - return NULL; + DWORD ExportRVA = (DWORD)((ULONG_PTR)GetProcAddress(ModuleBase, ExportName) - (ULONG_PTR)ModuleBase); + if (!ExportRVA) + return NULL; PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PUCHAR)ModuleBase + (ULONG)((PIMAGE_DOS_HEADER)ModuleBase)->e_lfanew); - PIMAGE_RUNTIME_FUNCTION_ENTRY Table = (PIMAGE_RUNTIME_FUNCTION_ENTRY)((PUCHAR)ModuleBase + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress); + PIMAGE_RUNTIME_FUNCTION_ENTRY Table = (PIMAGE_RUNTIME_FUNCTION_ENTRY)((PUCHAR)ModuleBase + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress); for (unsigned int i = 0; Table[i].BeginAddress; i++) if (Table[i].BeginAddress == ExportRVA) return (PVOID)((PBYTE)ModuleBase + Table[Offset + i].BeginAddress); - return NULL; + return NULL; } #endif @@ -707,18 +707,18 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) "vDbgPrintExWithPrefixInternal", }; - SIZE_T FoundCount = 0, FuncCount = sizeof(YaraFunctions) / sizeof(YaraFunctions[0]); - NameByAddress* results = GetAddressesByYara(ModuleBase, YaraFunctions, FuncCount, &FoundCount); + SIZE_T FoundCount = 0, FuncCount = sizeof(YaraFunctions) / sizeof(YaraFunctions[0]); + NameByAddress* results = GetAddressesByYara(ModuleBase, YaraFunctions, FuncCount, &FoundCount); - if (!results || FoundCount == 0) - { + if (!results || FoundCount == 0) + { if (results) free(results); - return NULL; - } + return NULL; + } - for (SIZE_T i = 0; i < FuncCount; i++) - for (SIZE_T j = 0; j < FoundCount; j++) + for (SIZE_T i = 0; i < FuncCount; i++) + for (SIZE_T j = 0; j < FoundCount; j++) if (results[j].FunctionName && results[j].Address && !strcmp(results[j].FunctionName, YaraFunctions[i])) return results[j].Address; @@ -2466,12 +2466,12 @@ DWORD GetTimeStamp(PVOID Address) int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) //************************************************************************************** { - IMAGE_DOS_HEADER DosHeader; - IMAGE_NT_HEADERS NtHeaders; + IMAGE_DOS_HEADER DosHeader; + IMAGE_NT_HEADERS NtHeaders; PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNtHeader = NULL; PIMAGE_SECTION_HEADER SectionHeaders = NULL; - DWORD bytesRead; + DWORD bytesRead; int RetVal = -1; @@ -2513,17 +2513,17 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) DWORD SizeOfHeaders = pDosHeader->e_lfanew + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader; PIMAGE_SECTION_HEADER pSectionHeaders = (PIMAGE_SECTION_HEADER)((PBYTE)ImageBase + SizeOfHeaders); - HANDLE hFile = CreateFileW(Path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE hFile = CreateFileW(Path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) + if (hFile == INVALID_HANDLE_VALUE) { #ifdef DEBUG_COMMENTS ErrorOutput("VerifyHeaders: Error opening file %ws", Path); #endif return RetVal; - } + } - if (!ReadFile(hFile, &DosHeader, sizeof(IMAGE_DOS_HEADER), &bytesRead, NULL)) + if (!ReadFile(hFile, &DosHeader, sizeof(IMAGE_DOS_HEADER), &bytesRead, NULL)) { #ifdef DEBUG_COMMENTS ErrorOutput("VerifyHeaders: Error reading file %ws", Path); @@ -2531,7 +2531,7 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) goto end; } - if (DosHeader.e_magic != IMAGE_DOS_SIGNATURE) + if (DosHeader.e_magic != IMAGE_DOS_SIGNATURE) { #ifdef DEBUG_COMMENTS DebugOutput("VerifyHeaders: IMAGE_DOS_SIGNATURE"); @@ -2541,7 +2541,7 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) SetFilePointer(hFile, DosHeader.e_lfanew, 0, FILE_BEGIN); - if (!ReadFile(hFile, &NtHeaders, sizeof(IMAGE_NT_HEADERS), &bytesRead, NULL)) + if (!ReadFile(hFile, &NtHeaders, sizeof(IMAGE_NT_HEADERS), &bytesRead, NULL)) { #ifdef DEBUG_COMMENTS DebugOutput("VerifyHeaders: Error reading header of %ws", Path); @@ -2549,13 +2549,13 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) goto end; } - if (NtHeaders.FileHeader.NumberOfSections != pNtHeader->FileHeader.NumberOfSections) + if (NtHeaders.FileHeader.NumberOfSections != pNtHeader->FileHeader.NumberOfSections) { DebugOutput("VerifyHeaders: Number of sections mismatch: %d vs %d", NtHeaders.FileHeader.NumberOfSections, pNtHeader->FileHeader.NumberOfSections); goto end; } - if (!NtHeaders.FileHeader.NumberOfSections) + if (!NtHeaders.FileHeader.NumberOfSections) { DebugOutput("VerifyHeaders: Number of sections zero"); goto end; @@ -2563,16 +2563,16 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) SetFilePointer(hFile, SizeOfHeaders, 0, FILE_BEGIN); - SectionHeaders = calloc(NtHeaders.FileHeader.NumberOfSections, sizeof(IMAGE_SECTION_HEADER)); - if (SectionHeaders == NULL) + SectionHeaders = calloc(NtHeaders.FileHeader.NumberOfSections, sizeof(IMAGE_SECTION_HEADER)); + if (SectionHeaders == NULL) { DebugOutput("VerifyHeaders: Error allocating memory for %d section headers", NtHeaders.FileHeader.NumberOfSections); return RetVal; - } + } SIZE_T SizeOfSectionHeaders = sizeof(IMAGE_SECTION_HEADER) * NtHeaders.FileHeader.NumberOfSections; - if (!ReadFile(hFile, SectionHeaders, (DWORD)SizeOfSectionHeaders, &bytesRead, NULL)) + if (!ReadFile(hFile, SectionHeaders, (DWORD)SizeOfSectionHeaders, &bytesRead, NULL)) { DebugOutput("VerifyHeaders: Error reading section headers of %ws", Path); goto end; @@ -2580,25 +2580,25 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) SIZE_T Matching = pRtlCompareMemory((PVOID)SectionHeaders, pSectionHeaders, SizeOfSectionHeaders); - if (Matching == SizeOfSectionHeaders) + if (Matching == SizeOfSectionHeaders) { #ifdef DEBUG_COMMENTS - DebugOutput("VerifyHeaders: PE header matches.\n"); + DebugOutput("VerifyHeaders: PE header matches.\n"); #endif RetVal = 1; - } + } else { - DebugOutput("VerifyHeaders: PE header does not match, 0x%x of 0x%x matching\n", Matching, SizeOfSectionHeaders); + DebugOutput("VerifyHeaders: PE header does not match, 0x%x of 0x%x matching\n", Matching, SizeOfSectionHeaders); RetVal = 0; - } + } end: if (SectionHeaders) free(SectionHeaders); - CloseHandle(hFile); + CloseHandle(hFile); - return RetVal; + return RetVal; } //************************************************************************************** @@ -2655,17 +2655,17 @@ int VerifyCodeSection(PVOID ImageBase, LPCWSTR Path) DWORD SizeOfHeaders = pDosHeader->e_lfanew + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader; PIMAGE_SECTION_HEADER pFirstSectionHeader = (PIMAGE_SECTION_HEADER)((PBYTE)ImageBase + SizeOfHeaders); - HANDLE hFile = CreateFileW(Path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE hFile = CreateFileW(Path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) + if (hFile == INVALID_HANDLE_VALUE) { #ifdef DEBUG_COMMENTS ErrorOutput("VerifyCodeSection: Error opening file %ws", Path); #endif return RetVal; - } + } - if (!ReadFile(hFile, &DosHeader, sizeof(IMAGE_DOS_HEADER), &bytesRead, NULL)) + if (!ReadFile(hFile, &DosHeader, sizeof(IMAGE_DOS_HEADER), &bytesRead, NULL)) { #ifdef DEBUG_COMMENTS ErrorOutput("VerifyCodeSection: Error reading file %ws", Path); @@ -2673,7 +2673,7 @@ int VerifyCodeSection(PVOID ImageBase, LPCWSTR Path) goto end; } - if (DosHeader.e_magic != IMAGE_DOS_SIGNATURE) + if (DosHeader.e_magic != IMAGE_DOS_SIGNATURE) { #ifdef DEBUG_COMMENTS DebugOutput("VerifyCodeSection: IMAGE_DOS_SIGNATURE"); @@ -2683,7 +2683,7 @@ int VerifyCodeSection(PVOID ImageBase, LPCWSTR Path) SetFilePointer(hFile, DosHeader.e_lfanew, 0, FILE_BEGIN); - if (!ReadFile(hFile, &NtHeaders, sizeof(IMAGE_NT_HEADERS), &bytesRead, NULL)) + if (!ReadFile(hFile, &NtHeaders, sizeof(IMAGE_NT_HEADERS), &bytesRead, NULL)) { #ifdef DEBUG_COMMENTS DebugOutput("VerifyCodeSection: Error reading header of %ws", Path); @@ -2693,8 +2693,8 @@ int VerifyCodeSection(PVOID ImageBase, LPCWSTR Path) SetFilePointer(hFile, SizeOfHeaders, 0, FILE_BEGIN); - IMAGE_SECTION_HEADER FirstSectionHeader; - if (!ReadFile(hFile, &FirstSectionHeader, sizeof(IMAGE_SECTION_HEADER), &bytesRead, NULL)) + IMAGE_SECTION_HEADER FirstSectionHeader; + if (!ReadFile(hFile, &FirstSectionHeader, sizeof(IMAGE_SECTION_HEADER), &bytesRead, NULL)) { #ifdef DEBUG_COMMENTS DebugOutput("VerifyCodeSection: Error reading first section of %ws", Path); @@ -2702,31 +2702,31 @@ int VerifyCodeSection(PVOID ImageBase, LPCWSTR Path) goto end; } - if (!FirstSectionHeader.SizeOfRawData) + if (!FirstSectionHeader.SizeOfRawData) { DebugOutput("VerifyCodeSection: SizeOfRawData zero.\n"); goto end; } - CodeSectionBuffer = (PBYTE)calloc(FirstSectionHeader.SizeOfRawData, sizeof(BYTE)); - if (CodeSectionBuffer == NULL) + CodeSectionBuffer = (PBYTE)calloc(FirstSectionHeader.SizeOfRawData, sizeof(BYTE)); + if (CodeSectionBuffer == NULL) { #ifdef DEBUG_COMMENTS DebugOutput("VerifyCodeSection: Error allocating memory"); #endif return RetVal; - } + } SetFilePointer(hFile, FirstSectionHeader.PointerToRawData, 0, FILE_BEGIN); - DWORD BytesReadInSection; - if (!ReadFile(hFile, CodeSectionBuffer, FirstSectionHeader.SizeOfRawData, &BytesReadInSection, NULL)) + DWORD BytesReadInSection; + if (!ReadFile(hFile, CodeSectionBuffer, FirstSectionHeader.SizeOfRawData, &BytesReadInSection, NULL)) { #ifdef DEBUG_COMMENTS ErrorOutput("VerifyCodeSection: Error reading code section of %ws", Path); #endif return RetVal; - } + } Relocations = (PIMAGE_BASE_RELOCATION)((PBYTE)ImageBase + NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); RelocationSize = NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; @@ -2810,25 +2810,25 @@ int VerifyCodeSection(PVOID ImageBase, LPCWSTR Path) SIZE_T Matching = pRtlCompareMemory((PVOID)CodeSectionBuffer, pFirstSection, SizeOfSection); - if (Matching == SizeOfSection) + if (Matching == SizeOfSection) { #ifdef DEBUG_COMMENTS - DebugOutput("VerifyCodeSection: Executable code matches.\n"); + DebugOutput("VerifyCodeSection: Executable code matches.\n"); #endif RetVal = 1; - } + } else { - DebugOutput("VerifyCodeSection: Executable code does not match, 0x%x of 0x%x matching\n", Matching, SizeOfSection); + DebugOutput("VerifyCodeSection: Executable code does not match, 0x%x of 0x%x matching\n", Matching, SizeOfSection); RetVal = 0; - } + } end: if (CodeSectionBuffer) free(CodeSectionBuffer); - CloseHandle(hFile); + CloseHandle(hFile); - return RetVal; + return RetVal; } //************************************************************************************** @@ -3182,57 +3182,57 @@ int DumpImageInCurrentProcess(PVOID Address) return 0; } - if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE || (*(DWORD*)((BYTE*)pDosHeader + pDosHeader->e_lfanew) != IMAGE_NT_SIGNATURE)) - { - // We want to fix the PE header in the dump (for e.g. disassembly etc) + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE || (*(DWORD*)((BYTE*)pDosHeader + pDosHeader->e_lfanew) != IMAGE_NT_SIGNATURE)) + { + // We want to fix the PE header in the dump (for e.g. disassembly etc) SIZE_T RegionSize = GetAccessibleSize(Address); - RegionCopy = calloc(RegionSize, sizeof(BYTE)); - - if (!RegionCopy) - { - ErrorOutput("DumpImageInCurrentProcess: Failed to allocate memory page for PE header.\n"); - return 0; - } - - __try - { - memcpy(RegionCopy, Address, RegionSize); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - DebugOutput("DumpImageInCurrentProcess: Exception occured copying PE header at 0x%p\n", Address); - free(RegionCopy); - return 0; - } - - pDosHeader = (PIMAGE_DOS_HEADER)RegionCopy; - - DebugOutput("DumpImageInCurrentProcess: Disguised PE image (bad MZ and/or PE headers) at 0x%p\n", Address); - - if (!pDosHeader->e_lfanew) - { - // In case the header until and including 'PE' has been zeroed - WORD* MachineProbe = (WORD*)&pDosHeader->e_lfanew; - while ((PUCHAR)MachineProbe < (PUCHAR)pDosHeader + (PE_HEADER_LIMIT - offsetof(IMAGE_DOS_HEADER, e_lfanew))) - { - if (*MachineProbe == IMAGE_FILE_MACHINE_I386 || *MachineProbe == IMAGE_FILE_MACHINE_AMD64) - { - if ((PUCHAR)MachineProbe > (PUCHAR)pDosHeader + 3) - pNtHeader = (PIMAGE_NT_HEADERS)((PUCHAR)MachineProbe - 4); - } - MachineProbe += sizeof(WORD); - } - - if (pNtHeader) - pDosHeader->e_lfanew = (LONG)((PUCHAR)pNtHeader - (PUCHAR)pDosHeader); - } - - if (!pDosHeader->e_lfanew || pDosHeader->e_lfanew > PE_MAX_SIZE) - { - DebugOutput("DumpImageInCurrentProcess: Bad e_lfanew 0x%x\n", pDosHeader->e_lfanew); - goto end; - } + RegionCopy = calloc(RegionSize, sizeof(BYTE)); + + if (!RegionCopy) + { + ErrorOutput("DumpImageInCurrentProcess: Failed to allocate memory page for PE header.\n"); + return 0; + } + + __try + { + memcpy(RegionCopy, Address, RegionSize); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + DebugOutput("DumpImageInCurrentProcess: Exception occured copying PE header at 0x%p\n", Address); + free(RegionCopy); + return 0; + } + + pDosHeader = (PIMAGE_DOS_HEADER)RegionCopy; + + DebugOutput("DumpImageInCurrentProcess: Disguised PE image (bad MZ and/or PE headers) at 0x%p\n", Address); + + if (!pDosHeader->e_lfanew) + { + // In case the header until and including 'PE' has been zeroed + WORD* MachineProbe = (WORD*)&pDosHeader->e_lfanew; + while ((PUCHAR)MachineProbe < (PUCHAR)pDosHeader + (PE_HEADER_LIMIT - offsetof(IMAGE_DOS_HEADER, e_lfanew))) + { + if (*MachineProbe == IMAGE_FILE_MACHINE_I386 || *MachineProbe == IMAGE_FILE_MACHINE_AMD64) + { + if ((PUCHAR)MachineProbe > (PUCHAR)pDosHeader + 3) + pNtHeader = (PIMAGE_NT_HEADERS)((PUCHAR)MachineProbe - 4); + } + MachineProbe += sizeof(WORD); + } + + if (pNtHeader) + pDosHeader->e_lfanew = (LONG)((PUCHAR)pNtHeader - (PUCHAR)pDosHeader); + } + + if (!pDosHeader->e_lfanew || pDosHeader->e_lfanew > PE_MAX_SIZE) + { + DebugOutput("DumpImageInCurrentProcess: Bad e_lfanew 0x%x\n", pDosHeader->e_lfanew); + goto end; + } *(WORD*)pDosHeader = IMAGE_DOS_SIGNATURE; *(DWORD*)((PUCHAR)pDosHeader + pDosHeader->e_lfanew) = IMAGE_NT_SIGNATURE; From ff4fba9dde17ca5eee0f1be69285beb670d336ee Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 17 Sep 2025 12:03:22 +0100 Subject: [PATCH 081/148] minhook: make hook order more consistent with full hookset --- hooks.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/hooks.c b/hooks.c index 0f15d80b..e0dd4459 100644 --- a/hooks.c +++ b/hooks.c @@ -72,14 +72,15 @@ void disable_tail_call_optimization(void) &New_##funcname, (void **) &Old_##funcname, NULL, FALSE, FALSE, 0, FALSE, timestamp, rva} hook_t full_hooks[] = { - // Process Hooks HOOK_NOTAIL_ALT(ntdll, RtlDispatchException, 2), HOOK_NOTAIL(ntdll, NtRaiseException, 3), + HOOK_NOTAIL_ALT(ntdll, LdrLoadDll, 4), HOOK_NOTAIL(ntdll, LdrUnloadDll, 1), HOOK_SPECIAL(ntdll, NtCreateUserProcess), HOOK_SPECIAL(kernel32, CreateProcessInternalW), + HOOK(ntdll, LdrpCallInitRoutine), HOOK(ntdll, NtAllocateVirtualMemory), HOOK(ntdll, NtAllocateVirtualMemoryEx), @@ -925,24 +926,14 @@ hook_t native_hooks[] = { // to follow the execution chain with base functionality hook_t min_hooks[] = { + HOOK_NOTAIL_ALT(ntdll, RtlDispatchException, 2), + HOOK_NOTAIL(ntdll, NtRaiseException, 3), + HOOK_NOTAIL_ALT(ntdll, LdrLoadDll, 4), HOOK_NOTAIL(ntdll, LdrUnloadDll, 1), HOOK_SPECIAL(ntdll, NtCreateUserProcess), HOOK_SPECIAL(kernel32, CreateProcessInternalW), - HOOK_SPECIAL(clrjit, compileMethod), - HOOK_SPECIAL(ole32, CoCreateInstance), - HOOK_SPECIAL(ole32, CoCreateInstanceEx), - HOOK_SPECIAL(ole32, CoGetClassObject), - HOOK_SPECIAL(ole32, CoGetObject), - HOOK_SPECIAL(combase, CoCreateInstance), - HOOK_SPECIAL(combase, CoCreateInstanceEx), - HOOK_SPECIAL(combase, CoGetClassObject), - HOOK_SPECIAL(combase, CoGetObject), - - HOOK_NOTAIL_ALT(ntdll, RtlDispatchException, 2), - HOOK_NOTAIL(ntdll, NtRaiseException, 3), - HOOK(ntdll, NtCreateProcess), HOOK(ntdll, NtCreateProcessEx), HOOK(ntdll, RtlCreateUserProcess), @@ -969,13 +960,23 @@ hook_t min_hooks[] = { HOOK(ntdll, RtlCreateUserThread), HOOK(kernel32, CreateRemoteThread), HOOK(kernel32, CreateRemoteThreadEx), + + HOOK_SPECIAL(clrjit, compileMethod), + HOOK_SPECIAL(ole32, CoCreateInstance), + HOOK_SPECIAL(ole32, CoCreateInstanceEx), + HOOK_SPECIAL(ole32, CoGetClassObject), + HOOK_SPECIAL(ole32, CoGetObject), + HOOK_SPECIAL(combase, CoCreateInstance), + HOOK_SPECIAL(combase, CoCreateInstanceEx), + HOOK_SPECIAL(combase, CoGetClassObject), + HOOK_SPECIAL(combase, CoGetObject), + HOOK(user32, SendNotifyMessageA), HOOK(user32, SendNotifyMessageW), HOOK(user32, SetWindowLongA), HOOK(user32, SetWindowLongW), HOOK(user32, SetWindowLongPtrA), HOOK(user32, SetWindowLongPtrW), - HOOK(user32, SetWindowsHookExA), HOOK(user32, SetWindowsHookExW), From d5415c9ab2ff14a28a6c0fcc77a61f9593669cdd Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 18 Sep 2025 09:28:22 +0100 Subject: [PATCH 082/148] Enhance VerifyHeaders() to include EP bytes to detect shellcode overwrites (e.g. HijackLoader) --- CAPE/CAPE.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 4259d1b5..d9621554 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -2471,6 +2471,7 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNtHeader = NULL; PIMAGE_SECTION_HEADER SectionHeaders = NULL; + PBYTE EntryPointBytes = NULL; DWORD bytesRead; int RetVal = -1; @@ -2512,6 +2513,7 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) DWORD SizeOfHeaders = pDosHeader->e_lfanew + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader; PIMAGE_SECTION_HEADER pSectionHeaders = (PIMAGE_SECTION_HEADER)((PBYTE)ImageBase + SizeOfHeaders); + PBYTE pEntryPointBytes = (PBYTE)ImageBase + pNtHeader->OptionalHeader.AddressOfEntryPoint; HANDLE hFile = CreateFileW(Path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -2593,9 +2595,42 @@ int VerifyHeaders(PVOID ImageBase, LPCWSTR Path) RetVal = 0; } + SetFilePointer(hFile, NtHeaders.OptionalHeader.AddressOfEntryPoint, 0, FILE_BEGIN); + + unsigned int ChunkSize = 0x10; + EntryPointBytes = calloc(ChunkSize, sizeof(BYTE)); + if (EntryPointBytes == NULL) + { + DebugOutput("VerifyHeaders: Error allocating memory for entry point check"); + return RetVal; + } + + if (!ReadFile(hFile, EntryPointBytes, (DWORD)ChunkSize, &bytesRead, NULL)) + { + DebugOutput("VerifyHeaders: Error reading section headers of %ws", Path); + goto end; + } + + Matching = pRtlCompareMemory((PVOID)EntryPointBytes, pEntryPointBytes, ChunkSize); + + if (Matching == ChunkSize) + { +#ifdef DEBUG_COMMENTS + DebugOutput("VerifyHeaders: Entry point matches.\n"); +#endif + RetVal = 1; + } + else + { + DebugOutput("VerifyHeaders: Entry point does not match, 0x%x of 0x%x matching\n", Matching, ChunkSize); + RetVal = 0; + } + end: if (SectionHeaders) free(SectionHeaders); + if (EntryPointBytes) + free(EntryPointBytes); CloseHandle(hFile); return RetVal; From 273bdc82d1d60941f0ac8e8ae7d2dfe3a1684b98 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 22 Sep 2025 17:02:25 +0100 Subject: [PATCH 083/148] Tweak tracking of regions from syscall capture (InstrCallback.c) --- CAPE/InstrCallback.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CAPE/InstrCallback.c b/CAPE/InstrCallback.c index 3c4ed62e..1a3fd08f 100644 --- a/CAPE/InstrCallback.c +++ b/CAPE/InstrCallback.c @@ -170,8 +170,9 @@ VOID InstrumentationCallback(PVOID CIP, unsigned int ReturnValue) if (!TrackedRegion) DebugOutput("InstrumentationCallback: Failed to add region at 0x%p to tracked regions list (thread %d).\n", AllocationBase, GetCurrentThreadId()); else { - DebugOutput("InstrumentationCallback: Added region at 0x%p to tracked regions list (thread %d).\n", AllocationBase, GetCurrentThreadId()); - TrackedRegion->Address = (PVOID)CIP; + DebugOutput("InstrumentationCallback: Added region at 0x%p (base 0x%p) to tracked regions list (thread %d).\n", CIP, AllocationBase, GetCurrentThreadId()); + TrackedRegion->Caller = (PVOID)CIP; + TrackedRegion->Address = AllocationBase; } } if (TrackedRegion) From cc8e0ce54ac8e42600b061246b1e046c02be8769 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 22 Sep 2025 17:03:09 +0100 Subject: [PATCH 084/148] Trace: don't step over named exported functions if trace-all set --- CAPE/Trace.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 6365bf24..ed09a088 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -1829,7 +1829,9 @@ void InstructionHandler(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst if (!FilterTrace || g_config.trace_all) TraceOutputFuncName(CIP, DecodedInstruction, ExportName); - *StepOver = TRUE; + if (is_in_dll_range((ULONG_PTR)CallTarget) && !g_config.trace_all) + *StepOver = TRUE; + *ForceStepOver = DoStepOver(ExportName); for (unsigned int i = 0; i < ARRAYSIZE(g_config.trace_into_api); i++) @@ -2897,7 +2899,10 @@ BOOL SetInitialBreakpoints(PVOID ImageBase) { BreakpointVA = (PVOID)((DWORD_PTR)ImageBase + (DWORD_PTR)g_config.bp[i]); if (SetSoftwareBreakpoint(&SoftBPs, BreakpointVA)) + { DebugOutput("SetInitialBreakpoints: Software breakpoint %d set at 0x%p", i, BreakpointVA); + BreakpointsSet = TRUE; + } g_config.bp[i] = 0; } } From beb19462df02633432745ba867ac8006547a69df Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 22 Sep 2025 17:03:34 +0100 Subject: [PATCH 085/148] config: fix issue parsing multiple sysbp addresses supplied via submission option --- config.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config.c b/config.c index fa7b15a3..d2fde12b 100644 --- a/config.c +++ b/config.c @@ -892,18 +892,18 @@ void parse_config_line(char* line) *p2 = '\0'; } int delta=0; - p2 = strchr(value, '+'); - if (p2) { - delta = strtoul(p2+1, NULL, 0); + char *p3 = strchr(value, '+'); + if (p3) { + delta = strtoul(p3+1, NULL, 0); DebugOutput("Config: Delta 0x%x.\n", delta); - *p2 = '\0'; + *p3 = '\0'; } else { - p2 = strchr(value, '-'); - if (p2) { - delta = - (int)strtoul(p2+1, NULL, 0); + p3 = strchr(value, '-'); + if (p3) { + delta = - (int)strtoul(p3+1, NULL, 0); DebugOutput("Config: Delta 0x%x.\n", delta); - *p2 = '\0'; + *p3 = '\0'; } } for (unsigned int i = 0; i < ARRAYSIZE(g_config.sysbp); i++) { From de0747305502115445e4c6b9b71a2ed85f45bb31 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 22 Sep 2025 17:03:54 +0100 Subject: [PATCH 086/148] unhook: attempt restoration of detected hook modifications --- unhook.c | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/unhook.c b/unhook.c index dc53fbef..e44f6646 100644 --- a/unhook.c +++ b/unhook.c @@ -135,8 +135,8 @@ void invalidate_regions_for_hook(const hook_t *hook) void remove_hook(const char *funcname) { for (uint32_t idx = 0; idx < g_index; idx++) { - DWORD old_protect; if (g_addr[idx] && !stricmp(g_unhook_hooks[idx]->funcname, funcname)) { + DWORD old_protect; if (!VirtualProtect(g_addr[idx], g_length[idx], PAGE_EXECUTE_READWRITE, &old_protect)) return; memcpy(g_addr[idx], g_orig[idx], g_length[idx]); @@ -157,10 +157,40 @@ void restore_hooks_on_range(ULONG_PTR start, ULONG_PTR end) __try { for (idx = 0; idx < g_index; idx++) { + DWORD old_protect; if ((ULONG_PTR)g_addr[idx] < start || ((ULONG_PTR)g_addr[idx] + g_length[idx]) > end) continue; if (!memcmp(g_orig[idx], g_addr[idx], g_length[idx])) { + if (!VirtualProtect(g_addr[idx], g_length[idx], PAGE_EXECUTE_READWRITE, &old_protect)) + return; memcpy(g_addr[idx], g_our[idx], g_length[idx]); + VirtualProtect(g_addr[idx], g_length[idx], old_protect, &old_protect); + log_hook_restoration(g_unhook_hooks[idx]); + } + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + ; + } + + set_lasterrors(&lasterror); +} + + +void restore_hook(uint32_t idx) +{ + lasterror_t lasterror; + + get_lasterrors(&lasterror); + + __try { + for (uint32_t i = 0; i < g_index; i++) { + if (i == idx) { + DWORD old_protect; + if (!VirtualProtect(g_addr[idx], g_length[idx], PAGE_EXECUTE_READWRITE, &old_protect)) + return; + memcpy(g_addr[idx], g_our[idx], g_length[idx]); + VirtualProtect(g_addr[idx], g_length[idx], old_protect, &old_protect); log_hook_restoration(g_unhook_hooks[idx]); } } @@ -195,15 +225,18 @@ static DWORD WINAPI _unhook_detect_thread(LPVOID param) for (idx = 0; idx < g_index; idx++) { if (g_unhook_hooks[idx]->is_hooked && g_hook_reported[idx] == 0) { char *tmpbuf = NULL; - if (!is_valid_address_range((ULONG_PTR)g_addr[idx], g_length[idx])) { + if (!is_valid_address_range((ULONG_PTR)g_addr[idx], g_length[idx])) continue; - } __try { int is_modification = 1; // Check whether this memory region still equals what we made it. - if (!memcmp(g_addr[idx], g_our[idx], g_length[idx])) { + if (!memcmp(g_addr[idx], g_our[idx], g_length[idx])) + continue; + + // Attempt restoration + restore_hook(idx); + if (!memcmp(g_addr[idx], g_our[idx], g_length[idx])) continue; - } // If the memory region matches the original contents, then it // has been restored to its original state. From 2ad14bfb96dd29439547a96ebcf00d0e52c79e3c Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 23 Sep 2025 14:38:58 +0100 Subject: [PATCH 087/148] Add ProcessParameters -> DllPath to log output for NtCreateUserProcess & RtlCreateUserProcess hooks --- hook_process.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/hook_process.c b/hook_process.c index 1fd9fb14..3efe80bb 100644 --- a/hook_process.c +++ b/hook_process.c @@ -317,7 +317,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtCreateUserProcess, DWORD pid = pid_from_process_handle(*ProcessHandle); - LOQ_ntstatus("process", "PPhhOOool", "ProcessHandle", ProcessHandle, + LOQ_ntstatus("process", "PPhhOOoool", "ProcessHandle", ProcessHandle, "ThreadHandle", ThreadHandle, "ProcessDesiredAccess", ProcessDesiredAccess, "ThreadDesiredAccess", ThreadDesiredAccess, @@ -325,6 +325,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtCreateUserProcess, "ThreadName", ThreadObjectAttributes, "ImagePathName", &ProcessParameters->ImagePathName, "CommandLine", &ProcessParameters->CommandLine, + "DllPath", &ProcessParameters->DllPath, "ProcessId", pid); if (NT_SUCCESS(ret)) { @@ -362,9 +363,17 @@ HOOKDEF(NTSTATUS, WINAPI, RtlCreateUserProcess, ProcessParameters, ProcessSecurityDescriptor, ThreadSecurityDescriptor, ParentProcess, InheritHandles, DebugPort, ExceptionPort, ProcessInformation); + DWORD pid = pid_from_process_handle(ProcessInformation->ProcessHandle); - LOQ_ntstatus("process", "ohpl", "ImagePath", ImagePath, "ObjectAttributes", ObjectAttributes, - "ParentHandle", ParentProcess, "ProcessId", pid); + + LOQ_ntstatus("process", "ohpoool", "ImagePath", ImagePath, + "ObjectAttributes", ObjectAttributes, + "ParentHandle", ParentProcess, + "ImagePathName", &ProcessParameters->ImagePathName, + "CommandLine", &ProcessParameters->CommandLine, + "DllPath", &ProcessParameters->DllPath, + "ProcessId", pid); + if (NT_SUCCESS(ret)) { DWORD tid = tid_from_thread_handle(ProcessInformation->ThreadHandle); if (!g_config.single_process) { From e2cfeff8b6606c55fdeb658dbe77e5ceed9e0884 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 26 Sep 2025 12:17:04 +0100 Subject: [PATCH 088/148] Add hook_restore option to enable/disable recently added hook restoration in unhook thread, disable for office & browsers --- config.c | 11 +++++++++++ config.h | 3 +++ unhook.c | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/config.c b/config.c index d2fde12b..7f0f2178 100644 --- a/config.c +++ b/config.c @@ -161,6 +161,11 @@ void parse_config_line(char* line) } } #endif + else if (!stricmp(key, "hook-restore")) { + g_config.hook_restore = value[0] == '1'; + if (g_config.hook_restore) + DebugOutput("Config: Attempt hook restoration of modifications detected by unhook thread\n"); + } else if (!strcmp(key, "disable_hook_content")) { //Set to 1 to remove functionality of all hooks except those critical for monitoring other processes. Set to 2 to apply to all hooks. g_config.disable_hook_content = atoi(value); } @@ -1401,6 +1406,7 @@ void read_config(void) #else g_config.hook_type = HOOK_HOTPATCH_JMP_INDIRECT; #endif + g_config.hook_restore = 1; g_config.protected_pids = 1; g_config.ntdll_protect = 1; g_config.ntdll_remap = 1; @@ -1533,6 +1539,7 @@ void read_config(void) g_config.procmemdump = 0; g_config.yarascan = 0; g_config.ntdll_protect = 0; + g_config.hook_restore = 0; DebugOutput("Firefox-specific hook-set enabled.\n"); } if (!_stricmp(our_process_name, "iexplore.exe")) @@ -1543,6 +1550,7 @@ void read_config(void) g_config.ntdll_protect = 0; g_config.procmemdump = 0; g_config.yarascan = 0; + g_config.hook_restore = 0; DebugOutput("Internet Explorer-specific hook-set enabled.\n"); } @@ -1554,6 +1562,7 @@ void read_config(void) g_config.ntdll_protect = 0; g_config.yarascan = 0; g_config.procmemdump = 0; + g_config.hook_restore = 0; DebugOutput("Edge-specific hook-set enabled.\n"); } @@ -1565,6 +1574,7 @@ void read_config(void) g_config.ntdll_protect = 0; g_config.yarascan = 0; g_config.procmemdump = 0; + g_config.hook_restore = 0; DebugOutput("Chrome-specific hook-set enabled.\n"); } @@ -1576,6 +1586,7 @@ void read_config(void) g_config.procmemdump = 0; g_config.yarascan = 0; g_config.ntdll_protect = 0; + g_config.hook_restore = 0; DebugOutput("Microsoft Office settings enabled.\n"); } } diff --git a/config.h b/config.h index 194490a6..01132cb1 100644 --- a/config.h +++ b/config.h @@ -120,6 +120,9 @@ struct _g_config { // Hook trampoline allocated in low (<2GB) memory (64-bit) int hook_low; + // Attempt to restore modified hooks detected by unhook thread + int hook_restore; + // Disable hook content int disable_hook_content; diff --git a/unhook.c b/unhook.c index e44f6646..45b8c726 100644 --- a/unhook.c +++ b/unhook.c @@ -235,7 +235,7 @@ static DWORD WINAPI _unhook_detect_thread(LPVOID param) // Attempt restoration restore_hook(idx); - if (!memcmp(g_addr[idx], g_our[idx], g_length[idx])) + if (g_config.hook_restore && !memcmp(g_addr[idx], g_our[idx], g_length[idx])) continue; // If the memory region matches the original contents, then it From c9b8830cf61b24f7ffc875560518ecd7b4ab53a5 Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Mon, 29 Sep 2025 18:09:12 +0200 Subject: [PATCH 089/148] Add some function patterns for Win11 24H2 --- CAPE/CAPE.c | 2 +- CAPE/YaraHarness.c | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index d9621554..7824b406 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -688,7 +688,7 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) {"LdrpCallInitRoutine", "RtlActivateActivationContextUnsafeFast", 1}, }; - if ((OSVersion.dwMajorVersion == 6 && OSVersion.dwMinorVersion > 1) || OSVersion.dwMajorVersion > 6) + if ((OSVersion.dwMajorVersion == 6 && OSVersion.dwMinorVersion > 1) || (OSVersion.dwMajorVersion > 6 && OSVersion.dwBuildNumber < 26000)) { for (int i = 0; i < sizeof(RuntimeTable) / sizeof(RuntimeTableMapping); i++) if (strcmp(RuntimeTable[i].FunctionName, FunctionName) == 0) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index 9bfd1751..8c92ce12 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -45,12 +45,14 @@ static char NewLine[MAX_PATH]; char InternalYara[] = "rule RtlInsertInvertedFunctionTable" - "{strings:$10_0_19041_662 = {48 8D 0D [4] E8 [4] [7] 8B 44 24 ?? 44 8B CB 4C 8B 44 24 ?? 48 8B D7 89 44 24 ?? E8}" + "{strings:$10_0_26100_3476 = {48 8D 0D [4] 49 F7 D8 48 8B F8 1B DB 23 5C 24 ?? E8 [4] 33 C9 E8}" + "$10_0_19041_662 = {48 8D 0D [4] E8 [4] [7] 8B 44 24 ?? 44 8B CB 4C 8B 44 24 ?? 48 8B D7 89 44 24 ?? E8}" "$10_0_18362_1350 = {48 8D 0D [4] 33 D2 85 C0 48 0F 48 DA E8 [4] 33 C9 E8 [4] 8B 44 24 ?? 44 8B CF 4C 8B C3 89 44 24 ?? 48 8B D6 E8}" "$10_0_10240_16384 = {48 8D 0D [4] 48 8B E8 E8 [4] 33 C9 E8 [4] 8B 15 [4] 3B 15 [4] 0F 84}" "condition:uint16(0) == 0x5a4d and any of them}" "rule LdrpCallInitRoutine" - "{strings:$function = {55 8B EC 56 57 53 8B F4 [0-2] FF 75 14 FF 75 10 FF 75 0C FF 55 08 8B E6 5B 5F 5E 5D C2 10 00}" + "{strings:$10_0_26100_3476 = {40 53 56 57 41 54 41 55 [65-85] 84 03 FE 7F}" + "$function = {55 8B EC 56 57 53 8B F4 [0-2] FF 75 14 FF 75 10 FF 75 0C FF 55 08 8B E6 5B 5F 5E 5D C2 10 00}" "condition:uint16(0) == 0x5a4d and any of them}" "rule WMI_ExecQuery" "{strings:$function = {4C 8B DC 56 57 41 54 41 56 41 57 48 83 EC 60 49 C7 43 B8 FE FF FF FF 49 89 5B 10 49 89 6B 18 45 8B E1 4D 8B F0 4C 8B F9 48 8B 41 08 48 83 78 20 00 0F 84}" @@ -72,7 +74,8 @@ char InternalYara[] = "condition:uint16(0) == 0x5a4d and any of them}" #ifdef _WIN64 "rule vDbgPrintExWithPrefixInternal" - "{strings:$function = {40 55 53 56 41 54 41 55 41 56 41 57 48 81 EC 20 01 00 00 48 8D 6C 24 20 48 8B 05 [4] 48 33 C5 48 89 85 ?? 00 00 00 4C 89 4D ?? 44 89 45 ?? 44 8B E2 89 55 ?? 48 8B D1 48 89 4D ?? 48 8B 85}" + "{strings:$10_0_26100_3476 = {48 8B C4 48 89 58 08 48 89 68 10 48 89 70 18 48 89 78 20 41 54 41 56 41 57 48 83 EC 40 44 8A BC 24}" + "$function = {40 55 53 56 41 54 41 55 41 56 41 57 48 81 EC 20 01 00 00 48 8D 6C 24 20 48 8B 05 [4] 48 33 C5 48 89 85 ?? 00 00 00 4C 89 4D ?? 44 89 45 ?? 44 8B E2 89 55 ?? 48 8B D1 48 89 4D ?? 48 8B 85}" "condition:uint16(0) == 0x5a4d and any of them}" "rule FindFixAndRun" "{strings:$function = {48 89 5C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 81 EC [80-110] 85 C0 0F 88 [2] 00 00 49 8B 4? 70 66 83 (78|79) 02 3A 0F 84 [2] 00 00 48 8D 54 24 20 49 8B CE E8}" From 8fdc755d57bdaf22668df83ce1862e322ff06a81 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 3 Oct 2025 17:42:04 +0100 Subject: [PATCH 090/148] Fix issue with hook_restore config setting being ignored --- unhook.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/unhook.c b/unhook.c index 45b8c726..f35b3cf1 100644 --- a/unhook.c +++ b/unhook.c @@ -234,9 +234,11 @@ static DWORD WINAPI _unhook_detect_thread(LPVOID param) continue; // Attempt restoration - restore_hook(idx); - if (g_config.hook_restore && !memcmp(g_addr[idx], g_our[idx], g_length[idx])) - continue; + if (g_config.hook_restore) { + restore_hook(idx); + if (!memcmp(g_addr[idx], g_our[idx], g_length[idx])) + continue; + } // If the memory region matches the original contents, then it // has been restored to its original state. From ed158f844e4ed806ca46ae15cccaa01d32288330 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 6 Oct 2025 11:50:20 +0100 Subject: [PATCH 091/148] Improve debugger breakpoint protection: fixes instruction counting anti-debug detection in CheckPoint ShowStopper (thanks @cccs-mog) --- CAPE/Injection.c | 52 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index 89f384e8..24034edc 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -519,6 +519,7 @@ void DumpSectionViewsForHandle(HANDLE SectionHandle) __declspec(noinline) void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context) { DWORD Pid = pid_from_thread_handle(ThreadHandle); + DWORD Tid = tid_from_thread_handle(ThreadHandle); if (Context && Context->ContextFlags & CONTEXT_CONTROL) { @@ -532,14 +533,36 @@ __declspec(noinline) void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT #endif } - if (g_config.debugger) + if (g_config.debugger && Pid == GetCurrentProcessId()) { - Context->Dr0 = 0; - Context->Dr1 = 0; - Context->Dr2 = 0; - Context->Dr3 = 0; - Context->Dr6 = 0; - Context->Dr7 = 0; + PTHREADBREAKPOINTS ThreadBreakpoints = GetThreadBreakpoints(Tid); + if (ThreadBreakpoints) + { + if (ThreadBreakpoints->BreakpointInfo[0].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[0].Address == Context->Dr0) + { + Context->Dr0 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } + if (ThreadBreakpoints->BreakpointInfo[1].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[1].Address == Context->Dr1) + { + Context->Dr1 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } + if (ThreadBreakpoints->BreakpointInfo[2].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[2].Address == Context->Dr2) + { + Context->Dr2 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } + if (ThreadBreakpoints->BreakpointInfo[3].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[3].Address == Context->Dr3) + { + Context->Dr3 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } + } } } @@ -559,16 +582,23 @@ __declspec(noinline) void SetThreadContextHandler(HANDLE ThreadHandle, CONTEXT * PTHREADBREAKPOINTS ThreadBreakpoints = GetThreadBreakpoints(Tid); if (ThreadBreakpoints) { - DebugOutput("SetThreadContextHandler: Protecting breakpoints for thread %d: 0x%p, 0x%p, 0x%p, 0x%p.\n", Tid, ThreadBreakpoints->BreakpointInfo[0].Address, ThreadBreakpoints->BreakpointInfo[1].Address, ThreadBreakpoints->BreakpointInfo[2].Address, ThreadBreakpoints->BreakpointInfo[3].Address); - ContextSetThreadBreakpointsEx(Context, ThreadBreakpoints, TRUE); + if + ( + (ThreadBreakpoints->BreakpointInfo[0].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[0].Address != Context->Dr0) || + (ThreadBreakpoints->BreakpointInfo[1].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[1].Address != Context->Dr1) || + (ThreadBreakpoints->BreakpointInfo[2].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[2].Address != Context->Dr2) || + (ThreadBreakpoints->BreakpointInfo[3].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[3].Address != Context->Dr3) + ) + { + DebugOutput("SetThreadContextHandler: Protecting breakpoints for thread %d: 0x%p, 0x%p, 0x%p, 0x%p.\n", Tid, ThreadBreakpoints->BreakpointInfo[0].Address, ThreadBreakpoints->BreakpointInfo[1].Address, ThreadBreakpoints->BreakpointInfo[2].Address, ThreadBreakpoints->BreakpointInfo[3].Address); + ContextSetThreadBreakpointsEx(Context, ThreadBreakpoints, TRUE); + } } #ifdef DEBUG_COMMENTS else DebugOutput("SetThreadContextHandler hook: No breakpoints to protect for thread %d.\n", Tid); #endif } - else - DebugOutput("SetThreadContextHandler: not taken #1"); MEMORY_BASIC_INFORMATION MemoryInfo; struct InjectionInfo *CurrentInjectionInfo = GetInjectionInfo(Pid); From 1dbb0b1e7b7542f4c869918692230e38a0a73081 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 8 Oct 2025 14:49:57 +0100 Subject: [PATCH 092/148] Fix bugs in GetFunctionByName() (thanks @heck-gd) --- CAPE/CAPE.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 7824b406..e37b74cd 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -707,6 +707,19 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) "vDbgPrintExWithPrefixInternal", }; + BOOL InYaraFunctions = FALSE; + for (SIZE_T i = 0; YaraFunctions[i]; i++) + { + if (!strcmp(YaraFunctions[i], FunctionName)) + { + InYaraFunctions = TRUE; + break; + } + } + + if (!InYaraFunctions) + return NULL; + SIZE_T FoundCount = 0, FuncCount = sizeof(YaraFunctions) / sizeof(YaraFunctions[0]); NameByAddress* results = GetAddressesByYara(ModuleBase, YaraFunctions, FuncCount, &FoundCount); @@ -717,10 +730,11 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) return NULL; } - for (SIZE_T i = 0; i < FuncCount; i++) - for (SIZE_T j = 0; j < FoundCount; j++) - if (results[j].FunctionName && results[j].Address && !strcmp(results[j].FunctionName, YaraFunctions[i])) - return results[j].Address; + for (SIZE_T j = 0; j < FoundCount; j++) + if (results[j].FunctionName && results[j].Address && !strcmp(results[j].FunctionName, FunctionName)) + return results[j].Address; + + free(results); return NULL; } From 04387f3a025ef932f06ae0061ce47824d82e9c7d Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 8 Oct 2025 15:55:05 +0100 Subject: [PATCH 093/148] Further improvements to GetFunctionByName() - thanks @heck-gd --- CAPE/CAPE.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index e37b74cd..28d414e1 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -705,6 +705,7 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) "WMI_GetObject", "WMI_GetObjectAsync", "vDbgPrintExWithPrefixInternal", + NULL }; BOOL InYaraFunctions = FALSE; @@ -730,13 +731,14 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) return NULL; } + PVOID Address = NULL; for (SIZE_T j = 0; j < FoundCount; j++) if (results[j].FunctionName && results[j].Address && !strcmp(results[j].FunctionName, FunctionName)) - return results[j].Address; + Address = results[j].Address; free(results); - return NULL; + return Address; } //************************************************************************************** From 758f68b57d84fcaf747b13526e1a35dd4b93e77e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 8 Oct 2025 12:35:19 -0700 Subject: [PATCH 094/148] Include `` instead of ``. --- CAPE/Scylla/ApiReader.h | 2 +- CAPE/Scylla/ImportsHandling.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CAPE/Scylla/ApiReader.h b/CAPE/Scylla/ApiReader.h index 8028ed5f..b08805fc 100644 --- a/CAPE/Scylla/ApiReader.h +++ b/CAPE/Scylla/ApiReader.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include "ProcessAccessHelp.h" #include "Thunks.h" diff --git a/CAPE/Scylla/ImportsHandling.h b/CAPE/Scylla/ImportsHandling.h index 3c1ee9bf..e34ef0cf 100644 --- a/CAPE/Scylla/ImportsHandling.h +++ b/CAPE/Scylla/ImportsHandling.h @@ -2,7 +2,7 @@ #include #include -#include +#include class ImportThunk; class ImportModuleThunk; From 7f445f44c534cb7fc3dc334e8f5dfb8e84e4312d Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 8 Oct 2025 12:38:54 -0700 Subject: [PATCH 095/148] Use `std::unordered_multimap` instead of `stdext::hash_multimap`. --- CAPE/Scylla/ApiReader.cpp | 6 +++--- CAPE/Scylla/ApiReader.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CAPE/Scylla/ApiReader.cpp b/CAPE/Scylla/ApiReader.cpp index e3df4163..b66a9771 100644 --- a/CAPE/Scylla/ApiReader.cpp +++ b/CAPE/Scylla/ApiReader.cpp @@ -12,7 +12,7 @@ extern "C" void DebugOutput(_In_ LPCTSTR lpOutputString, ...); extern "C" void ErrorOutput(_In_ LPCTSTR lpOutputString, ...); extern "C" SIZE_T GetAllocationSize(PVOID Address); -stdext::hash_multimap ApiReader::apiList; //api look up table +std::unordered_multimap ApiReader::apiList; //api look up table std::map * ApiReader::moduleThunkList; //store found apis DWORD_PTR ApiReader::minApiAddress = (DWORD_PTR)-1; @@ -690,7 +690,7 @@ bool ApiReader::isApiAddressValid(DWORD_PTR virtualAddress) ApiInfo * ApiReader::getApiByVirtualAddress(DWORD_PTR virtualAddress, bool * isSuspect) { - stdext::hash_multimap::iterator it1, it2; + std::unordered_multimap::iterator it1, it2; size_t c = 0; size_t countDuplicates = apiList.count(virtualAddress); int countHighPriority = 0; @@ -773,7 +773,7 @@ ApiInfo * ApiReader::getApiByVirtualAddress(DWORD_PTR virtualAddress, bool * isS return (ApiInfo *) 1; } -ApiInfo * ApiReader::getScoredApi(stdext::hash_multimap::iterator it1,size_t countDuplicates, bool hasName, bool hasUnicodeAnsiName, bool hasNoUnderlineInName, bool hasPrioDll,bool hasPrio0Dll,bool hasPrio1Dll, bool hasPrio2Dll, bool firstWin ) +ApiInfo * ApiReader::getScoredApi(std::unordered_multimap::iterator it1,size_t countDuplicates, bool hasName, bool hasUnicodeAnsiName, bool hasNoUnderlineInName, bool hasPrioDll,bool hasPrio0Dll,bool hasPrio1Dll, bool hasPrio2Dll, bool firstWin ) { ApiInfo * foundApi = 0; ApiInfo * foundMatchingApi = 0; diff --git a/CAPE/Scylla/ApiReader.h b/CAPE/Scylla/ApiReader.h index b08805fc..d51308d3 100644 --- a/CAPE/Scylla/ApiReader.h +++ b/CAPE/Scylla/ApiReader.h @@ -11,7 +11,7 @@ typedef std::pair API_Pair; class ApiReader : public ProcessAccessHelp { public: - static stdext::hash_multimap apiList; //api look up table + static std::unordered_multimap apiList; //api look up table static std::map * moduleThunkList; //store found apis From 077fd913c2c06a87bd0b2d7a47c6c03007424520 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 8 Oct 2025 12:42:26 -0700 Subject: [PATCH 096/148] Fix: Use `unordered_multimap::iterator` instead of `hash_map::iterator`. In ApiReader.cpp, ApiReader::clearAll() is iterating through apiList, which is multi. In ApiReader.h, ApiReader::getScoredApi() should be declared as taking multi to match its definition in ApiReader.cpp, as it's taking iterators into apiList. This was compiling despite the single/multi mismatch because in MSVC's implementation, for both legacy hash_map and Standard unordered_map, the containers have the same iterator types regardless of single/multi, but it's still a conformance issue and should be fixed. --- CAPE/Scylla/ApiReader.cpp | 2 +- CAPE/Scylla/ApiReader.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CAPE/Scylla/ApiReader.cpp b/CAPE/Scylla/ApiReader.cpp index b66a9771..508f0308 100644 --- a/CAPE/Scylla/ApiReader.cpp +++ b/CAPE/Scylla/ApiReader.cpp @@ -1116,7 +1116,7 @@ void ApiReader::clearAll() minApiAddress = (DWORD_PTR)-1; maxApiAddress = 0; - for ( stdext::hash_map::iterator it = apiList.begin(); it != apiList.end(); ++it ) + for ( std::unordered_multimap::iterator it = apiList.begin(); it != apiList.end(); ++it ) { delete it->second; } diff --git a/CAPE/Scylla/ApiReader.h b/CAPE/Scylla/ApiReader.h index d51308d3..008e909b 100644 --- a/CAPE/Scylla/ApiReader.h +++ b/CAPE/Scylla/ApiReader.h @@ -70,6 +70,6 @@ class ApiReader : public ProcessAccessHelp bool isApiBlacklisted( const char * functionName ); bool isWinSxSModule( ModuleInfo * module ); - ApiInfo * getScoredApi(stdext::hash_map::iterator it1,size_t countDuplicates, bool hasName, bool hasUnicodeAnsiName, bool hasNoUnderlineInName, bool hasPrioDll,bool hasPrio0Dll,bool hasPrio1Dll, bool hasPrio2Dll, bool firstWin ); + ApiInfo * getScoredApi(std::unordered_multimap::iterator it1,size_t countDuplicates, bool hasName, bool hasUnicodeAnsiName, bool hasNoUnderlineInName, bool hasPrioDll,bool hasPrio0Dll,bool hasPrio1Dll, bool hasPrio2Dll, bool firstWin ); }; From 11fe56c7bdeccda1ed95e8bb8375375777239501 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 8 Oct 2025 12:51:10 -0700 Subject: [PATCH 097/148] Remove `_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS`. --- capemon.vcxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/capemon.vcxproj b/capemon.vcxproj index e01a1f72..b67161d8 100644 --- a/capemon.vcxproj +++ b/capemon.vcxproj @@ -97,7 +97,7 @@ - CUCKOODBG;WIN32;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS_DEBUG;_WINDOWS;_USRDLL;MONGO_HAVE_STDINT;MONGO_STATIC_BUILD;%(PreprocessorDefinitions);_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS + CUCKOODBG;WIN32;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS_DEBUG;_WINDOWS;_USRDLL;MONGO_HAVE_STDINT;MONGO_STATIC_BUILD;%(PreprocessorDefinitions) MultiThreadedDebug Level3 ProgramDatabase @@ -122,7 +122,7 @@ - CUCKOODBG;WIN32;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS_DEBUG;_WINDOWS;_USRDLL;MONGO_HAVE_STDINT;MONGO_STATIC_BUILD;%(PreprocessorDefinitions);_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS + CUCKOODBG;WIN32;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS_DEBUG;_WINDOWS;_USRDLL;MONGO_HAVE_STDINT;MONGO_STATIC_BUILD;%(PreprocessorDefinitions) MultiThreadedDebug Level3 ProgramDatabase @@ -145,7 +145,7 @@ - WIN32;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGSNDEBUG;_WINDOWS;_USRDLL;MONGO_HAVE_STDINT;MONGO_STATIC_BUILD;_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS;%(PreprocessorDefinitions);_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS + WIN32;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGSNDEBUG;_WINDOWS;_USRDLL;MONGO_HAVE_STDINT;MONGO_STATIC_BUILD;%(PreprocessorDefinitions) MultiThreaded Level3 ProgramDatabase @@ -180,7 +180,7 @@ - WIN32;_WIN64;_CRT_SECURE_NO_WARNINGS;_WINDOWS;_USRDLL;MONGO_HAVE_STDINT;MONGO_STATIC_BUILD;_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS;%(PreprocessorDefinitions) + WIN32;_WIN64;_CRT_SECURE_NO_WARNINGS;_WINDOWS;_USRDLL;MONGO_HAVE_STDINT;MONGO_STATIC_BUILD;%(PreprocessorDefinitions) MultiThreaded Level3 ProgramDatabase From fe3289c94feb36f99dfcd3ca9100d4beb6f9cab6 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 9 Oct 2025 22:23:33 +0100 Subject: [PATCH 098/148] Trace: fix issue with long conditional jump target calculation in InstructionHandler() --- CAPE/Trace.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index ed09a088..2891bb64 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -1923,12 +1923,12 @@ void InstructionHandler(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst } else if (!strnicmp(DecodedInstruction.mnemonic.p, "j", 1)) { - int JumpOffset = (int)*((PCHAR)CIP + 1); - PVOID JumpTarget = (PVOID)((PUCHAR)CIP + DecodedInstruction.size + JumpOffset); + PVOID JumpTarget = GetTarget(ExceptionInfo->ContextRecord, DecodedInstruction); if (!FilterTrace || g_config.trace_all) TraceOutputFuncAddress(CIP, DecodedInstruction, JumpTarget); if (g_config.loopskip) { + int JumpOffset = (int)((PUCHAR)JumpTarget - (PUCHAR)CIP); for (unsigned int i = 0; i < 4; i++) { if (JumpOffset < 0 && PreviousJumps[i] == CIP) From 663b0f44025d26fbfc5cca315bc14e21213a241d Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 10 Oct 2025 09:42:57 +0100 Subject: [PATCH 099/148] GetFunctionByName: break out of loop for efficiency --- CAPE/CAPE.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 28d414e1..2fef702a 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -734,7 +734,10 @@ PVOID GetFunctionByName(HMODULE ModuleBase, PCHAR FunctionName) PVOID Address = NULL; for (SIZE_T j = 0; j < FoundCount; j++) if (results[j].FunctionName && results[j].Address && !strcmp(results[j].FunctionName, FunctionName)) + { Address = results[j].Address; + break; + } free(results); From 6d3405d5ba87a6ac2da16bbe3ec6d7eff0f452b2 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 13 Oct 2025 09:51:27 +0100 Subject: [PATCH 100/148] Expand 'DumpImage' debugger action to handle target imagebase address e.g. action0=dumpimage:0x400000,action1=dumpimage:ecx+0x280 --- CAPE/Trace.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 2891bb64..7b044177 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -1537,6 +1537,18 @@ void ActionDispatcher(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst De else DebuggerOutput("ActionDispatcher: Failed to dump breaking module at 0x%p.\n", CallingModule); } + else if (!strnicmp(Action, "DumpImage:", 10)) + { + if (Target) + { + if (DumpImageInCurrentProcess(Target)) + DebuggerOutput("ActionDispatcher: Dumped image at 0x%p.\n", Target); + else + DebuggerOutput("ActionDispatcher: Failed to dump image at 0x%p.\n", Target); + } + else + DebuggerOutput("ActionDispatcher: Failed to dump image - target missing or invalid.\n"); + } else if (!strnicmp(Action, "DumpSize:", 9)) { if (Target) From 5011d4967f596f35390f61e439810853905533d6 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 13 Oct 2025 10:11:21 +0100 Subject: [PATCH 101/148] Fix debug output for dump-limit config setting --- config.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config.c b/config.c index 7f0f2178..4a78d4e4 100644 --- a/config.c +++ b/config.c @@ -249,7 +249,10 @@ void parse_config_line(char* line) } else if (!strcmp(key, "dump-limit")) { //Override the default dump limit of 10 payloads g_config.dump_limit = (unsigned int)strtoul(value, NULL, 10); - DebugOutput("Dropped file limit set to %d.\n", g_config.dump_limit); + if (g_config.dump_limit) + DebugOutput("Payload capture limit set to %d.\n", g_config.dump_limit); + else + DebugOutput("Payload capture limit disabled.\n"); } else if (!strcmp(key, "dropped-limit")) { //Override the default dropped file limit of 100 files g_config.dropped_limit = (unsigned int)strtoul(value, NULL, 10); From e8ffb9559df06e4733bed38942d110ef057e77f6 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 14 Oct 2025 18:44:43 +0100 Subject: [PATCH 102/148] Fix cast for 64-bit debug registers in get/set thread context handlers --- CAPE/Injection.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index 24034edc..7cbb0480 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -538,25 +538,25 @@ __declspec(noinline) void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT PTHREADBREAKPOINTS ThreadBreakpoints = GetThreadBreakpoints(Tid); if (ThreadBreakpoints) { - if (ThreadBreakpoints->BreakpointInfo[0].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[0].Address == Context->Dr0) + if (ThreadBreakpoints->BreakpointInfo[0].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[0].Address == Context->Dr0) { Context->Dr0 = 0; Context->Dr6 = 0; Context->Dr7 = 0; } - if (ThreadBreakpoints->BreakpointInfo[1].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[1].Address == Context->Dr1) + if (ThreadBreakpoints->BreakpointInfo[1].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[1].Address == Context->Dr1) { Context->Dr1 = 0; Context->Dr6 = 0; Context->Dr7 = 0; } - if (ThreadBreakpoints->BreakpointInfo[2].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[2].Address == Context->Dr2) + if (ThreadBreakpoints->BreakpointInfo[2].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[2].Address == Context->Dr2) { Context->Dr2 = 0; Context->Dr6 = 0; Context->Dr7 = 0; } - if (ThreadBreakpoints->BreakpointInfo[3].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[3].Address == Context->Dr3) + if (ThreadBreakpoints->BreakpointInfo[3].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[3].Address == Context->Dr3) { Context->Dr3 = 0; Context->Dr6 = 0; @@ -584,10 +584,10 @@ __declspec(noinline) void SetThreadContextHandler(HANDLE ThreadHandle, CONTEXT * { if ( - (ThreadBreakpoints->BreakpointInfo[0].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[0].Address != Context->Dr0) || - (ThreadBreakpoints->BreakpointInfo[1].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[1].Address != Context->Dr1) || - (ThreadBreakpoints->BreakpointInfo[2].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[2].Address != Context->Dr2) || - (ThreadBreakpoints->BreakpointInfo[3].Address && (DWORD)ThreadBreakpoints->BreakpointInfo[3].Address != Context->Dr3) + (ThreadBreakpoints->BreakpointInfo[0].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[0].Address != Context->Dr0) || + (ThreadBreakpoints->BreakpointInfo[1].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[1].Address != Context->Dr1) || + (ThreadBreakpoints->BreakpointInfo[2].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[2].Address != Context->Dr2) || + (ThreadBreakpoints->BreakpointInfo[3].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[3].Address != Context->Dr3) ) { DebugOutput("SetThreadContextHandler: Protecting breakpoints for thread %d: 0x%p, 0x%p, 0x%p, 0x%p.\n", Tid, ThreadBreakpoints->BreakpointInfo[0].Address, ThreadBreakpoints->BreakpointInfo[1].Address, ThreadBreakpoints->BreakpointInfo[2].Address, ThreadBreakpoints->BreakpointInfo[3].Address); From 75984e9a25f465eb5ef9323affea933b6470d3ed Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 14 Oct 2025 18:45:48 +0100 Subject: [PATCH 103/148] Improve NtContinueHandler debug register stealth/protection to allow detonation of e.g. 8443224de889424012ba57ec075fec219104fffa8c3ae13a2db27b4ba9d71a3b --- CAPE/Debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index 57c78e11..163384cf 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -2813,7 +2813,7 @@ void NtContinueHandler(PCONTEXT ThreadContext) TrackExecution(CIP); - if (BreakpointsSet) + if (g_config.debugger) { DWORD ThreadId = GetCurrentThreadId(); PTHREADBREAKPOINTS ThreadBreakpoints = GetThreadBreakpoints(ThreadId); From 5b0b58c96db3fd306adb8f9657cbbb670b433eb3 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 15 Oct 2025 13:45:27 +0100 Subject: [PATCH 104/148] Implement Gemini suggestions from #111 --- CAPE/Scylla/ApiReader.cpp | 10 +++++----- CAPE/Scylla/ApiReader.h | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CAPE/Scylla/ApiReader.cpp b/CAPE/Scylla/ApiReader.cpp index 508f0308..e7c7e380 100644 --- a/CAPE/Scylla/ApiReader.cpp +++ b/CAPE/Scylla/ApiReader.cpp @@ -12,7 +12,7 @@ extern "C" void DebugOutput(_In_ LPCTSTR lpOutputString, ...); extern "C" void ErrorOutput(_In_ LPCTSTR lpOutputString, ...); extern "C" SIZE_T GetAllocationSize(PVOID Address); -std::unordered_multimap ApiReader::apiList; //api look up table +ApiReader::ApiList ApiReader::apiList; //api look up table std::map * ApiReader::moduleThunkList; //store found apis DWORD_PTR ApiReader::minApiAddress = (DWORD_PTR)-1; @@ -690,7 +690,7 @@ bool ApiReader::isApiAddressValid(DWORD_PTR virtualAddress) ApiInfo * ApiReader::getApiByVirtualAddress(DWORD_PTR virtualAddress, bool * isSuspect) { - std::unordered_multimap::iterator it1, it2; + ApiList::iterator it1; size_t c = 0; size_t countDuplicates = apiList.count(virtualAddress); int countHighPriority = 0; @@ -773,7 +773,7 @@ ApiInfo * ApiReader::getApiByVirtualAddress(DWORD_PTR virtualAddress, bool * isS return (ApiInfo *) 1; } -ApiInfo * ApiReader::getScoredApi(std::unordered_multimap::iterator it1,size_t countDuplicates, bool hasName, bool hasUnicodeAnsiName, bool hasNoUnderlineInName, bool hasPrioDll,bool hasPrio0Dll,bool hasPrio1Dll, bool hasPrio2Dll, bool firstWin ) +ApiInfo * ApiReader::getScoredApi(ApiList::iterator it1,size_t countDuplicates, bool hasName, bool hasUnicodeAnsiName, bool hasNoUnderlineInName, bool hasPrioDll,bool hasPrio0Dll,bool hasPrio1Dll, bool hasPrio2Dll, bool firstWin ) { ApiInfo * foundApi = 0; ApiInfo * foundMatchingApi = 0; @@ -1116,9 +1116,9 @@ void ApiReader::clearAll() minApiAddress = (DWORD_PTR)-1; maxApiAddress = 0; - for ( std::unordered_multimap::iterator it = apiList.begin(); it != apiList.end(); ++it ) + for (const auto& pair : apiList) { - delete it->second; + delete pair.second; } apiList.clear(); diff --git a/CAPE/Scylla/ApiReader.h b/CAPE/Scylla/ApiReader.h index 008e909b..7702f049 100644 --- a/CAPE/Scylla/ApiReader.h +++ b/CAPE/Scylla/ApiReader.h @@ -11,7 +11,8 @@ typedef std::pair API_Pair; class ApiReader : public ProcessAccessHelp { public: - static std::unordered_multimap apiList; //api look up table + using ApiList = std::unordered_multimap; + static ApiList apiList; //api look up table static std::map * moduleThunkList; //store found apis @@ -70,6 +71,6 @@ class ApiReader : public ProcessAccessHelp bool isApiBlacklisted( const char * functionName ); bool isWinSxSModule( ModuleInfo * module ); - ApiInfo * getScoredApi(std::unordered_multimap::iterator it1,size_t countDuplicates, bool hasName, bool hasUnicodeAnsiName, bool hasNoUnderlineInName, bool hasPrioDll,bool hasPrio0Dll,bool hasPrio1Dll, bool hasPrio2Dll, bool firstWin ); + ApiInfo * getScoredApi(ApiList::iterator it1,size_t countDuplicates, bool hasName, bool hasUnicodeAnsiName, bool hasNoUnderlineInName, bool hasPrioDll,bool hasPrio0Dll,bool hasPrio1Dll, bool hasPrio2Dll, bool firstWin ); }; From a0fa612ebe4e8ed73319ae851c4313b5d54a158d Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 15 Oct 2025 13:50:38 +0100 Subject: [PATCH 105/148] Deprecate Win7 wow64 breakpoint workaround --- CAPE/Debugger.c | 87 ------- CAPE/w64wow64/internal.h | 73 ------ CAPE/w64wow64/w64wow64.c | 469 ----------------------------------- CAPE/w64wow64/w64wow64.h | 44 ---- CAPE/w64wow64/w64wow64defs.h | 34 --- CAPE/w64wow64/windef.h | 93 ------- CAPE/wow64_fix.c | 225 ----------------- capemon.vcxproj | 2 - capemon.vcxproj.filters | 9 - 9 files changed, 1036 deletions(-) delete mode 100644 CAPE/w64wow64/internal.h delete mode 100644 CAPE/w64wow64/w64wow64.c delete mode 100644 CAPE/w64wow64/w64wow64.h delete mode 100644 CAPE/w64wow64/w64wow64defs.h delete mode 100644 CAPE/w64wow64/windef.h delete mode 100644 CAPE/wow64_fix.c diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index 163384cf..df87079b 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -46,9 +46,6 @@ extern char *convert_address_to_dll_name_and_offset(ULONG_PTR addr, unsigned int extern PCHAR GetNameBySsn(unsigned int Number); extern void log_direct_syscall(const char *function, PVOID addr); extern unsigned int address_is_in_stack(DWORD Address); -extern BOOL WoW64fix(void); -extern BOOL WoW64PatchBreakpoint(unsigned int Register); -extern BOOL WoW64UnpatchBreakpoint(unsigned int Register); extern BOOL SetInitialBreakpoints(PVOID ImageBase), Trace(struct _EXCEPTION_POINTERS* ExceptionInfo), SoftwareBreakpointCallback(struct _EXCEPTION_POINTERS* ExceptionInfo); extern BOOL BreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS* ExceptionInfo); extern void DebuggerOutput(_In_ LPCTSTR lpOutputString, ...), DoTraceOutput(PVOID Address); @@ -635,64 +632,6 @@ LONG WINAPI CAPEExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) DebugOutput("CAPEExceptionFilter: Anomaly detected! bp3 address (0x%p) different to BreakpointInfo (0x%x)!\n", ExceptionInfo->ContextRecord->Dr3, pBreakpointInfo->Address); return EXCEPTION_CONTINUE_SEARCH; } -#ifndef _WIN64 - if (bp == 0 && ((DWORD_PTR)pBreakpointInfo->Type != ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE0)) - { - if (pBreakpointInfo->Type == BP_READWRITE && ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE0 == BP_WRITE && address_is_in_stack((DWORD_PTR)pBreakpointInfo->Address)) - { - DebugOutput("CAPEExceptionFilter: Reinstated BP_READWRITE on breakpoint %d (WoW64 workaround)\n", pBreakpointInfo->Register); - - ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, pBreakpointInfo->Register, pBreakpointInfo->Size, (BYTE*)pBreakpointInfo->Address, pBreakpointInfo->Type, pBreakpointInfo->HitCount, pBreakpointInfo->Callback); - } - else - { - DebugOutput("CAPEExceptionFilter: Anomaly detected! bp0 type (0x%x) different to BreakpointInfo (0x%x)!\n", ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE0, pBreakpointInfo->Type); - CheckDebugRegisters(0, ExceptionInfo->ContextRecord); - } - } - if (bp == 1 && ((DWORD)pBreakpointInfo->Type != ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE1)) - { - if (pBreakpointInfo->Type == BP_READWRITE && ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE1 == BP_WRITE && address_is_in_stack((DWORD_PTR)pBreakpointInfo->Address)) - { - DebugOutput("CAPEExceptionFilter: Reinstated BP_READWRITE on breakpoint %d (WoW64 workaround)\n", pBreakpointInfo->Register); - - ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, pBreakpointInfo->Register, pBreakpointInfo->Size, (BYTE*)pBreakpointInfo->Address, pBreakpointInfo->Type, pBreakpointInfo->HitCount, pBreakpointInfo->Callback); - } - else - { - DebugOutput("CAPEExceptionFilter: Anomaly detected! bp1 type (0x%x) different to BreakpointInfo (0x%x)!\n", ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE1, pBreakpointInfo->Type); - CheckDebugRegisters(0, ExceptionInfo->ContextRecord); - } - } - if (bp == 2 && ((DWORD)pBreakpointInfo->Type != ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE2)) - { - if (pBreakpointInfo->Type == BP_READWRITE && ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE2 == BP_WRITE && address_is_in_stack((DWORD_PTR)pBreakpointInfo->Address)) - { - DebugOutput("CAPEExceptionFilter: Reinstated BP_READWRITE on stack breakpoint %d (WoW64 workaround)\n", pBreakpointInfo->Register); - - ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, pBreakpointInfo->Register, pBreakpointInfo->Size, (BYTE*)pBreakpointInfo->Address, pBreakpointInfo->Type, pBreakpointInfo->HitCount, pBreakpointInfo->Callback); - } - else - { - DebugOutput("CAPEExceptionFilter: Anomaly detected! bp2 type (0x%x) different to BreakpointInfo (0x%x)!\n", ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE2, pBreakpointInfo->Type); - CheckDebugRegisters(0, ExceptionInfo->ContextRecord); - } - } - if (bp == 3 && ((DWORD)pBreakpointInfo->Type != ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE3)) - { - if (pBreakpointInfo->Type == BP_READWRITE && ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE3 == BP_WRITE && address_is_in_stack((DWORD_PTR)pBreakpointInfo->Address)) - { - DebugOutput("CAPEExceptionFilter: Reinstated BP_READWRITE on breakpoint %d (WoW64 workaround)\n", pBreakpointInfo->Register); - - ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, pBreakpointInfo->Register, pBreakpointInfo->Size, (BYTE*)pBreakpointInfo->Address, pBreakpointInfo->Type, pBreakpointInfo->HitCount, pBreakpointInfo->Callback); - } - else - { - DebugOutput("CAPEExceptionFilter: Anomaly detected! bp3 type (0x%x) different to BreakpointInfo (0x%x)!\n", ((PDR7)&(ExceptionInfo->ContextRecord->Dr7))->RWE3, pBreakpointInfo->Type); - CheckDebugRegisters(0, ExceptionInfo->ContextRecord); - } - } -#endif // !_WIN64 if (pBreakpointInfo->HitCount) { @@ -928,11 +867,6 @@ BOOL ContextSetDebugRegisterEx if (Type == BP_EXEC) Length = 0; -#ifndef _WIN64 - if (Type == BP_READWRITE && address_is_in_stack((DWORD_PTR)Address)) - WoW64PatchBreakpoint(Register); -#endif - if (Register == 0) { *Dr0 = (DWORD_PTR)Address; @@ -1074,11 +1008,6 @@ BOOL SetDebugRegister if (Type == BP_EXEC) Length = 0; -#ifndef _WIN64 - if (Type == BP_READWRITE && address_is_in_stack((DWORD_PTR)Address)) - WoW64PatchBreakpoint(Register); -#endif - if (Register == 0) { *Dr0 = (DWORD_PTR)Address; @@ -1383,11 +1312,6 @@ BOOL ContextClearBreakpointEx(PCONTEXT Context, PBREAKPOINTINFO pBreakpointInfo, Dr7->L3 = 0; } -#ifndef _WIN64 - if (pBreakpointInfo->Type == BP_READWRITE && address_is_in_stack((DWORD_PTR)pBreakpointInfo->Address)) - WoW64UnpatchBreakpoint(pBreakpointInfo->Register); -#endif - pBreakpointInfo->Address = 0; pBreakpointInfo->Size = 0; pBreakpointInfo->Type = 0; @@ -1875,11 +1799,6 @@ BOOL ClearDebugRegister Dr7->L3 = 0; } -#ifndef _WIN64 - if (Type == BP_READWRITE && address_is_in_stack((DWORD_PTR)Address)) - WoW64UnpatchBreakpoint(Register); -#endif - Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!SetThreadContext(hThread, &Context)) @@ -2762,12 +2681,6 @@ BOOL InitialiseDebugger(void) ChildProcessId = 0; SingleStepHandler = NULL; -#ifndef _WIN64 - // Ensure wow64 patch is installed if needed - if (!g_config.msi) - WoW64fix(); -#endif - g_config.debugger = 1; DebuggerInitialised = TRUE; diff --git a/CAPE/w64wow64/internal.h b/CAPE/w64wow64/internal.h deleted file mode 100644 index de4fccf5..00000000 --- a/CAPE/w64wow64/internal.h +++ /dev/null @@ -1,73 +0,0 @@ -/** - * - * WOW64Ext Library - * - * Copyright (c) 2012 ReWolf - * http://blog.rewolf.pl/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - * - */ -#pragma once - -#define EMIT(a) __asm __emit (a) - -#define X64_Start_with_CS(_cs) \ - { \ - EMIT(0x6A) EMIT(_cs) /* push _cs */ \ - EMIT(0xE8) EMIT(0) EMIT(0) EMIT(0) EMIT(0) /* call $+5 */ \ - EMIT(0x83) EMIT(4) EMIT(0x24) EMIT(5) /* add dword [esp], 5 */ \ - EMIT(0xCB) /* retf */ \ - } - -#define X64_End_with_CS(_cs) \ - { \ - EMIT(0xE8) EMIT(0) EMIT(0) EMIT(0) EMIT(0) /* call $+5 */ \ - EMIT(0xC7) EMIT(0x44) EMIT(0x24) EMIT(4) EMIT(_cs) EMIT(0) EMIT(0) EMIT(0) /* mov dword [rsp + 4], _cs */ \ - EMIT(0x83) EMIT(4) EMIT(0x24) EMIT(0xD) /* add dword [rsp], 0xD */ \ - EMIT(0xCB) /* retf */ \ - } - -#define X64_Start() X64_Start_with_CS(0x33) -#define X64_End() X64_End_with_CS(0x23) - -#define _RAX 0 -#define _RCX 1 -#define _RDX 2 -#define _RBX 3 -#define _RSP 4 -#define _RBP 5 -#define _RSI 6 -#define _RDI 7 -#define _R8 8 -#define _R9 9 -#define _R10 10 -#define _R11 11 -#define _R12 12 -#define _R13 13 -#define _R14 14 -#define _R15 15 - -#define X64_Push(r) EMIT(0x48 | ((r) >> 3)) EMIT(0x50 | ((r) & 7)) -#define X64_Pop(r) EMIT(0x48 | ((r) >> 3)) EMIT(0x58 | ((r) & 7)) - -#define REX_W EMIT(0x48) __asm - -//to fool M$ inline asm compiler I'm using 2 DWORDs instead of DWORD64 -//use of DWORD64 will generate wrong 'pop word ptr[]' and it will break stack -union reg64 -{ - DWORD64 v; - DWORD dw[2]; -}; diff --git a/CAPE/w64wow64/w64wow64.c b/CAPE/w64wow64/w64wow64.c deleted file mode 100644 index c40a34e3..00000000 --- a/CAPE/w64wow64/w64wow64.c +++ /dev/null @@ -1,469 +0,0 @@ -#ifndef _WIN64 -/* -W64oWoW64 -Copyright (C) 2012 George Nicolaou - -This file is part of W64oWoW64. - -W64oWoW64 is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -W64oWoW64 is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with W64oWoW64. If not, see . -*/ -#include -#include "internal.h" -#include "w64wow64.h" -#include "w64wow64defs.h" -#include "windef.h" - -extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); -extern void ErrorOutput(_In_ LPCTSTR lpOutputString, ...); - -FUNCTIONPTRS sFunctions = { 0 }; - -/** -* -* X64Call Part of WOW64Ext Library -* See internals.h -*/ -extern unsigned __int64 X64Call(DWORD64 func, int argC, ...) -{ - va_list args; - va_start(args, argC); - union reg64 _rcx = { (argC > 0) ? argC--, va_arg(args, DWORD64) : 0 }; - union reg64 _rdx = { (argC > 0) ? argC--, va_arg(args, DWORD64) : 0 }; - union reg64 _r8 = { (argC > 0) ? argC--, va_arg(args, DWORD64) : 0 }; - union reg64 _r9 = { (argC > 0) ? argC--, va_arg(args, DWORD64) : 0 }; - union reg64 _rax = { 0 }; - - union reg64 restArgs = { (DWORD64)&va_arg(args, DWORD64) }; - - // conversion to QWORD for easier use in inline assembly - union reg64 _argC = { (DWORD64)argC }; - DWORD back_esp = 0; - WORD back_fs = 0; - - __asm - { - ;// reset FS segment, to properly handle RFG - mov back_fs, fs - mov eax, 0x2B - mov fs, ax - - ;// keep original esp in back_esp variable - mov back_esp, esp - - ;// align esp to 0x10, without aligned stack some syscalls may return errors ! - ;// (actually, for syscalls it is sufficient to align to 8, but SSE opcodes - ;// requires 0x10 alignment), it will be further adjusted according to the - ;// number of arguments above 4 - and esp, 0xFFFFFFF0 - - X64_Start(); - - ;// below code is compiled as x86 inline asm, but it is executed as x64 code - ;// that's why it need sometimes REX_W() macro, right column contains detailed - ;// transcription how it will be interpreted by CPU - - ;// fill first four arguments - REX_W mov ecx, _rcx.dw[0] ;// mov rcx, qword ptr [_rcx] - REX_W mov edx, _rdx.dw[0] ;// mov rdx, qword ptr [_rdx] - push _r8.v ;// push qword ptr [_r8] - X64_Pop(_R8); ;// pop r8 - push _r9.v ;// push qword ptr [_r9] - X64_Pop(_R9); ;// pop r9 - ;// - REX_W mov eax, _argC.dw[0] ;// mov rax, qword ptr [_argC] - ;// - ;// final stack adjustment, according to the ;// - ;// number of arguments above 4 ;// - test al, 1 ;// test al, 1 - jnz _no_adjust ;// jnz _no_adjust - sub esp, 8 ;// sub rsp, 8 -_no_adjust: ;// - ;// - push edi ;// push rdi - REX_W mov edi, restArgs.dw[0] ;// mov rdi, qword ptr [restArgs] - ;// - ;// put rest of arguments on the stack ;// - REX_W test eax, eax ;// test rax, rax - jz _ls_e ;// je _ls_e - REX_W lea edi, dword ptr [edi + 8*eax - 8] ;// lea rdi, [rdi + rax*8 - 8] - ;// -_ls: ;// - REX_W test eax, eax ;// test rax, rax - jz _ls_e ;// je _ls_e - push dword ptr [edi] ;// push qword ptr [rdi] - REX_W sub edi, 8 ;// sub rdi, 8 - REX_W sub eax, 1 ;// sub rax, 1 - jmp _ls ;// jmp _ls -_ls_e: ;// - ;// - ;// create stack space for spilling registers ;// - REX_W sub esp, 0x20 ;// sub rsp, 20h - ;// - call func ;// call qword ptr [func] - ;// - ;// cleanup stack ;// - REX_W mov ecx, _argC.dw[0] ;// mov rcx, qword ptr [_argC] - REX_W lea esp, dword ptr [esp + 8*ecx + 0x20] ;// lea rsp, [rsp + rcx*8 + 20h] - ;// - pop edi ;// pop rdi - ;// - // set return value ;// - REX_W mov _rax.dw[0], eax ;// mov qword ptr [_rax], rax - - X64_End(); - - mov ax, ds - mov ss, ax - mov esp, back_esp - - ;// restore FS segment - mov ax, back_fs - mov fs, ax - } - return _rax.v; -} -#pragma warning(pop) - -PTEB64 NtTeb64( void ) -{ - X64_Start(); - GETTEB(); - X64_End(); -} - -PLDR_DATA_TABLE_ENTRY64 GetModule64LdrTable( wchar_t * lwcModuleName ) -{ - PTEB64 psTeb = NtTeb64(); - //PPEB64 psPeb = - PPEB_LDR_DATA Ldr = psTeb->ProcessEnvironmentBlock->Ldr; - PLDR_DATA_TABLE_ENTRY64 psDataEntryStart = - (PLDR_DATA_TABLE_ENTRY64)Ldr->InLoadOrderModuleList.Flink; - PLDR_DATA_TABLE_ENTRY64 psDataEntryCurrent = psDataEntryStart; - - do { - if( memcmp( (DWORD64)psDataEntryCurrent->BaseDllName.Buffer, lwcModuleName, - psDataEntryCurrent->BaseDllName.Length ) == 0 ) { - return psDataEntryCurrent; - } - psDataEntryCurrent = - (PLDR_DATA_TABLE_ENTRY64)psDataEntryCurrent->InLoadOrderLinks.Flink; - } while( psDataEntryStart != psDataEntryCurrent && psDataEntryCurrent ); - return NULL; -} - -extern void __cdecl SetLastErrorFromX64Call(DWORD64 status) -{ - typedef ULONG (WINAPI *RtlNtStatusToDosError_t)(NTSTATUS Status); - typedef ULONG (WINAPI *RtlSetLastWin32Error_t)(NTSTATUS Status); - - static RtlNtStatusToDosError_t RtlNtStatusToDosError = NULL; - static RtlSetLastWin32Error_t RtlSetLastWin32Error = NULL; - - if ((NULL == RtlNtStatusToDosError) || (NULL == RtlSetLastWin32Error)) - { - HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); - RtlNtStatusToDosError = (RtlNtStatusToDosError_t)GetProcAddress(ntdll, "RtlNtStatusToDosError"); - RtlSetLastWin32Error = (RtlSetLastWin32Error_t)GetProcAddress(ntdll, "RtlSetLastWin32Error"); - } - - if ((NULL != RtlNtStatusToDosError) && (NULL != RtlSetLastWin32Error)) - { - RtlSetLastWin32Error(RtlNtStatusToDosError((DWORD)status)); - } -} - -extern SIZE_T VirtualQueryEx64(HANDLE hProcess, DWORD64 lpAddress, MEMORY_BASIC_INFORMATION64* lpBuffer, SIZE_T dwLength) -{ - DWORD64 lvpNtdll = GetModuleBase64( L"ntdll.dll" ); - static DWORD ntqvm = 0; - if (0 == ntqvm) - { - ntqvm = (DWORD)GetProcAddress64(lvpNtdll, "NtQueryVirtualMemory"); - if (0 == ntqvm) - return 0; - } - DWORD64 ret = 0; - X64Call(ntqvm, 6, (DWORD64)hProcess, lpAddress, (DWORD64)0, (DWORD64)lpBuffer, (DWORD64)dwLength, (DWORD64)&ret); - return (SIZE_T)ret; -} - -#pragma warning(push) -#pragma warning(disable : 4244) -extern DWORD64 VirtualAllocEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) -{ - DWORD64 lvpNtdll = GetModuleBase64( L"ntdll.dll" ); - static DWORD64 ntavm = 0; - if (0 == ntavm) - { - ntavm = GetProcAddress64(lvpNtdll, "NtAllocateVirtualMemory"); - if (0 == ntavm) - return 0; - } - - DWORD64 tmpAddr = lpAddress; - DWORD64 tmpSize = dwSize; - DWORD64 ret = X64Call(ntavm, 6, (DWORD64)hProcess, (DWORD64)&tmpAddr, (DWORD64)0, (DWORD64)&tmpSize, (DWORD64)flAllocationType, (DWORD64)flProtect); - if (STATUS_SUCCESS != ret) - { - SetLastErrorFromX64Call(ret); - return FALSE; - } - else - return tmpAddr; -} - -extern BOOL VirtualFreeEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD dwFreeType) -{ - DWORD64 lvpNtdll = GetModuleBase64( L"ntdll.dll" ); - static DWORD64 ntfvm = 0; - if (0 == ntfvm) - { - ntfvm = GetProcAddress64(lvpNtdll, "NtFreeVirtualMemory"); - if (0 == ntfvm) - return 0; - } - - DWORD64 tmpAddr = lpAddress; - DWORD64 tmpSize = dwSize; - DWORD64 ret = X64Call(ntfvm, 4, (DWORD64)hProcess, (DWORD64)&tmpAddr, (DWORD64)&tmpSize, (DWORD64)dwFreeType); - if (STATUS_SUCCESS != ret) - { - SetLastErrorFromX64Call(ret); - return FALSE; - } - else - return TRUE; -} - -extern BOOL VirtualProtectEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD* lpflOldProtect) -{ - DWORD64 lvpNtdll = GetModuleBase64( L"ntdll.dll" ); - static DWORD64 ntpvm = 0; - if (0 == ntpvm) - { - ntpvm = GetProcAddress64(lvpNtdll, "NtProtectVirtualMemory"); - if (0 == ntpvm) - return 0; - } - - DWORD64 tmpAddr = lpAddress; - DWORD64 tmpSize = dwSize; - DWORD64 ret = X64Call(ntpvm, 5, (DWORD64)hProcess, (DWORD64)&tmpAddr, (DWORD64)&tmpSize, (DWORD64)flNewProtect, (DWORD64)lpflOldProtect); - if (STATUS_SUCCESS != ret) - { - SetLastErrorFromX64Call(ret); - return FALSE; - } - else - return TRUE; -} -#pragma warning(pop) - -extern BOOL ReadProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead) -{ - DWORD64 lvpNtdll = GetModuleBase64( L"ntdll.dll" ); - static DWORD nrvm = 0; - if (0 == nrvm) - { - nrvm = (DWORD)GetProcAddress64(lvpNtdll, "NtReadVirtualMemory"); - if (0 == nrvm) - return 0; - } - DWORD64 ret = X64Call(nrvm, 5, (DWORD64)hProcess, lpBaseAddress, (DWORD64)lpBuffer, (DWORD64)nSize, (DWORD64)lpNumberOfBytesRead); - if (STATUS_SUCCESS != ret) - return FALSE; - else - return TRUE; -} - -extern BOOL WriteProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten) -{ - DWORD64 lvpNtdll = GetModuleBase64( L"ntdll.dll" ); - static DWORD nrvm = 0; - if (0 == nrvm) - { - nrvm = (DWORD)GetProcAddress64(lvpNtdll, "NtWriteVirtualMemory"); - if (0 == nrvm) - return 0; - } - DWORD64 ret = X64Call(nrvm, 5, (DWORD64)hProcess, lpBaseAddress, (DWORD64)lpBuffer, (DWORD64)nSize, (DWORD64)lpNumberOfBytesWritten); - if (STATUS_SUCCESS != ret) - return FALSE; - else - return TRUE; -} - -DWORD64 GetModuleBase64( wchar_t * lwcModuleName ) -{ - PLDR_DATA_TABLE_ENTRY64 LdrEntry = GetModule64LdrTable( lwcModuleName ); - return (DWORD64)LdrEntry->DllBase; -} - -PIMAGE_NT_HEADERS64 GetModule64NtHeader( DWORD64 lvpBaseAddress ) -{ - PIMAGE_DOS_HEADER psDosHeader = (PIMAGE_DOS_HEADER)lvpBaseAddress; - return (PIMAGE_NT_HEADERS64)( ((__int8 *)lvpBaseAddress) + - psDosHeader->e_lfanew ); -} - -DWORD64 GetModule64PEBaseAddress( DWORD64 lvpBaseAddress ) -{ - PIMAGE_NT_HEADERS64 psNtHeader = GetModule64NtHeader( lvpBaseAddress ); - return (DWORD64)psNtHeader->OptionalHeader.ImageBase; -} - -DWORD64 GetModule64EntryRVA( DWORD64 lvpBaseAddress ) -{ - PIMAGE_NT_HEADERS64 psNtHeader = GetModule64NtHeader( lvpBaseAddress ); - return (DWORD64)psNtHeader->OptionalHeader.AddressOfEntryPoint; -} - -extern DWORD64 GetProcAddress64( DWORD64 lvpBaseAddress, char * lpszProcName ) -{ - PIMAGE_NT_HEADERS64 psNtHeader = GetModule64NtHeader( lvpBaseAddress ); - char * lpcModBase = (char *)lvpBaseAddress; - PIMAGE_EXPORT_DIRECTORY psExportDir = (PIMAGE_EXPORT_DIRECTORY)( lpcModBase + - psNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress ); - - int nNumberOfNames = psExportDir->NumberOfNames; - unsigned long * lpulFunctions = - (unsigned long *)( lpcModBase + psExportDir->AddressOfFunctions ); - - unsigned long * lpulNames = - (unsigned long *)( lpcModBase + psExportDir->AddressOfNames ); - - unsigned short * lpusOrdinals = - (unsigned short *) ( lpcModBase + psExportDir->AddressOfNameOrdinals ); - - int i; - char * lpszFunctionName; - for( i = 0; i < nNumberOfNames; i++ ) { - lpszFunctionName = ((__int8 *)lpulNames[i]) + (int)lvpBaseAddress; - if( strcmp( lpszFunctionName, lpszProcName ) == 0 ) { - return ( (__int8 *)lvpBaseAddress ) + - lpulFunctions[ lpusOrdinals[i] ]; - } - } - return NULL; -} - -BOOL FreeKnownDllPage( wchar_t * lpwzKnownDllName ) -{ - DWORD64 hSection = 0; - DWORD64 lvpBaseAddress = 0; - DWORD64 lvpRealBaseAddress = 0; - DWORD64 stViewSize = 0; - DWORD64 stRegionSize = 0; - PTEB64 psTeb; - X64Call( sFunctions.LdrGetKnownDllSectionHandle, 3, - (DWORD64)lpwzKnownDllName, - (DWORD64)0, - (DWORD64)&hSection ); - - psTeb = NtTeb64(); - psTeb->NtTib.ArbitraryUserPointer = (DWORD64)lpwzKnownDllName; - - X64Call( sFunctions.NtMapViewOfSection, 10, - (DWORD64)hSection, - (DWORD64)-1, - (DWORD64)&lvpBaseAddress, - (DWORD64)0, - (DWORD64)0, - (DWORD64)0, - (DWORD64)&stViewSize, - (DWORD64)ViewUnmap, - (DWORD64)0, - (DWORD64)PAGE_READONLY ); - - lvpRealBaseAddress = - (DWORD64)GetModule64PEBaseAddress( (DWORD64)lvpBaseAddress ); - - X64Call( sFunctions.NtFreeVirtualMemory, 4, - (DWORD64)-1, - (DWORD64)&lvpRealBaseAddress, - (DWORD64)&stRegionSize, - (DWORD64)MEM_RELEASE ); - - X64Call( sFunctions.NtUnmapViewOfSection, 2, (DWORD64)-1, - (DWORD64)lvpBaseAddress ); - return TRUE; -} - -extern DWORD64 LoadLibrary64A( char * lpcLibraryName ) -{ - if( sFunctions.LoadLibraryA == NULL ) { - sFunctions.LoadLibraryA = - GetProcAddress64( GetModuleBase64( L"kernel32.dll" ), "LoadLibraryA" ); - } - return (DWORD64)X64Call( sFunctions.LoadLibraryA, 1, (DWORD64)lpcLibraryName ); -} - -extern BOOL InitializeW64oWoW64() -{ - DWORD64 lvpNtdll = GetModuleBase64( L"ntdll.dll" ); - UNICODE_STRING64 sUnicodeString; - __int8 * lvpKernelBaseBase; - __int8 * lvpKernel32Base; - PLDR_DATA_TABLE_ENTRY64 lpsKernel32Ldr; - PLDR_DATA_TABLE_ENTRY64 lpsKernelBaseLdr; - - sFunctions.LdrGetKnownDllSectionHandle = GetProcAddress64( lvpNtdll, - "LdrGetKnownDllSectionHandle" ); - sFunctions.NtFreeVirtualMemory = GetProcAddress64( lvpNtdll, - "NtFreeVirtualMemory" ); - sFunctions.NtMapViewOfSection = GetProcAddress64( lvpNtdll, - "NtMapViewOfSection" ); - sFunctions.NtUnmapViewOfSection = GetProcAddress64( lvpNtdll, - "NtUnmapViewOfSection" ); - - if( FreeKnownDllPage( L"kernel32.dll" ) == FALSE) return FALSE; - if( FreeKnownDllPage( L"user32.dll" ) == FALSE ) return FALSE; - - sUnicodeString.Length = 0x18; - sUnicodeString.MaximumLength = 0x1a; - sUnicodeString.Buffer = (DWORD64)L"kernel32.dll"; - if( X64Call( GetProcAddress64( lvpNtdll, "LdrLoadDll" ), 4, - (DWORD64)0, - (DWORD64)0, - (DWORD64)&sUnicodeString, - (DWORD64)&lvpKernel32Base ) != NULL ) { - ErrorOutput("Failed to load 64-bit kernel32.dll"); - return FALSE; - } - - lvpKernelBaseBase = (__int8 *)GetModuleBase64( L"KERNELBASE.dll"); - X64Call( ( lvpKernelBaseBase + (int)GetModule64EntryRVA( lvpKernelBaseBase ) ), - 3, - (DWORD64)lvpKernelBaseBase, - (DWORD64)DLL_PROCESS_ATTACH, - (DWORD64)0 ); - - X64Call( ( lvpKernel32Base + (int)GetModule64EntryRVA( lvpKernel32Base ) ), - 3, - (DWORD64)lvpKernel32Base, - (DWORD64)DLL_PROCESS_ATTACH, - (DWORD64)0 ); - - lpsKernel32Ldr = GetModule64LdrTable( L"kernel32.dll" ); - lpsKernel32Ldr->LoadCount = 0xffff; - lpsKernel32Ldr->Flags += LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED; - - lpsKernelBaseLdr = GetModule64LdrTable( L"KERNELBASE.dll" ); - lpsKernelBaseLdr->LoadCount = 0xffff; - lpsKernelBaseLdr->Flags += LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED; - - return TRUE; -} - -#endif \ No newline at end of file diff --git a/CAPE/w64wow64/w64wow64.h b/CAPE/w64wow64/w64wow64.h deleted file mode 100644 index ffdbe375..00000000 --- a/CAPE/w64wow64/w64wow64.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -W64oWoW64 -Copyright (C) 2012 George Nicolaou - -This file is part of W64oWoW64. - -W64oWoW64 is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -W64oWoW64 is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with W64oWoW64. If not, see . -*/ -#pragma once - -#include - -#ifndef STATUS_SUCCESS -# define STATUS_SUCCESS 0 -#endif - -#ifndef __W64WOW64_H_ -#define __W64WOW64_H_ - -unsigned __int64 X64Call( DWORD64 lvpFunctionPtr, int nArgc, ... ); -void __cdecl SetLastErrorFromX64Call(DWORD64 status); -DWORD64 GetProcAddress64( DWORD64 lvpBaseAddress, char * lpszProcName ); -DWORD64 LoadLibrary64A( char * lpcLibraryName ); -DWORD64 GetModuleBase64( wchar_t * lwcModuleName ); - -DWORD64 VirtualAllocEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); -BOOL VirtualFreeEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD* lpflOldProtect); -BOOL VirtualProtectEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD* lpflOldProtect); -BOOL ReadProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead); -BOOL WriteProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten); - -extern BOOL InitializeW64oWoW64(void); -#endif \ No newline at end of file diff --git a/CAPE/w64wow64/w64wow64defs.h b/CAPE/w64wow64/w64wow64defs.h deleted file mode 100644 index d5cd3780..00000000 --- a/CAPE/w64wow64/w64wow64defs.h +++ /dev/null @@ -1,34 +0,0 @@ -/* -W64oWoW64 -Copyright (C) 2012 George Nicolaou - -This file is part of W64oWoW64. - -W64oWoW64 is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -W64oWoW64 is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with W64oWoW64. If not, see . -*/ -#define GETTEB() \ -{ \ - EMIT(0x65) EMIT(0x48) EMIT(0x8b) EMIT(0x04) EMIT(0x25) EMIT(0x30) EMIT(0x00) EMIT(0x00) EMIT(0x00) \ -} - - -typedef struct { - void * LdrGetKnownDllSectionHandle; - void * NtMapViewOfSection; - void * NtFreeVirtualMemory; - void * NtUnmapViewOfSection; - void * LoadLibraryA; -} FUNCTIONPTRS; - -BOOL InitializeW64oWoW64( void ); \ No newline at end of file diff --git a/CAPE/w64wow64/windef.h b/CAPE/w64wow64/windef.h deleted file mode 100644 index fd7b4872..00000000 --- a/CAPE/w64wow64/windef.h +++ /dev/null @@ -1,93 +0,0 @@ -/* -W64oWoW64 -Copyright (C) 2012 George Nicolaou - -This file is part of W64oWoW64. - -W64oWoW64 is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -W64oWoW64 is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with W64oWoW64. If not, see . -*/ -#include - -typedef struct _CLIENT_ID64 -{ - DWORD64 UniqueProcess; - DWORD64 UniqueThread; -} CLIENT_ID64, *PCLIENT_ID64; - - -typedef struct _PEB_LDR_DATA -{ - ULONG Length; - UCHAR Initialized; - DWORD64 SsHandle; - LIST_ENTRY64 InLoadOrderModuleList; - LIST_ENTRY64 InMemoryOrderModuleList; - LIST_ENTRY64 InInitializationOrderModuleList; - DWORD64 EntryInProgress; -} PEB_LDR_DATA, *PPEB_LDR_DATA; - - -typedef struct _PEB64 -{ - UCHAR InheritedAddressSpace; - UCHAR ReadImageFileExecOptions; - UCHAR BeingDebugged; - UCHAR BitField; - ULONG ImageUsesLargePages: 1; - ULONG IsProtectedProcess: 1; - ULONG IsLegacyProcess: 1; - ULONG IsImageDynamicallyRelocated: 1; - ULONG SpareBits: 4; - DWORD64 Mutant; - DWORD64 ImageBaseAddress; - PPEB_LDR_DATA Ldr; -} PEB64, *PPEB64; - -typedef struct _LSA_UNICODE_STRING { - USHORT Length; - USHORT MaximumLength; - DWORD64 Buffer; -} UNICODE_STRING64, * PUNICODE_STRING64; - -typedef struct _TEB64 -{ - NT_TIB64 NtTib; - DWORD64 EnvironmentPointer; - CLIENT_ID64 ClientId; - DWORD64 ActiveRpcHandle; - DWORD64 ThreadLocalStoragePointer; - PPEB64 ProcessEnvironmentBlock; -} TEB64, *PTEB64; - -typedef struct _LDR_DATA_TABLE_ENTRY64 -{ - LIST_ENTRY64 InLoadOrderLinks; - LIST_ENTRY64 InMemoryOrderLinks; - LIST_ENTRY64 InInitializationOrderLinks; - DWORD64 DllBase; - DWORD64 EntryPoint; - ULONG SizeOfImage; - UNICODE_STRING64 FullDllName; - UNICODE_STRING64 BaseDllName; - ULONG Flags; - WORD LoadCount; -} LDR_DATA_TABLE_ENTRY54, *PLDR_DATA_TABLE_ENTRY64; - -#define LDRP_PROCESS_ATTACH_CALLED 0x000080000 -#define LDRP_ENTRY_PROCESSED 0x000004000 - -typedef enum _SECTION_INHERIT { - ViewShare = 1, - ViewUnmap = 2 -} SECTION_INHERIT, * PSECTION_INHERIT; \ No newline at end of file diff --git a/CAPE/wow64_fix.c b/CAPE/wow64_fix.c deleted file mode 100644 index fa7457c8..00000000 --- a/CAPE/wow64_fix.c +++ /dev/null @@ -1,225 +0,0 @@ -/* -CAPE - Config And Payload Extraction -Copyright(C) 2015, 2016 Context Information Security. (kevin.oreilly@contextis.com) - -This program is free software : you can redistribute it and / or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program.If not, see . -*/ -#ifndef _WIN64 -#include "w64wow64\w64wow64.h" -// Based upon ReWolf's wow64ext library: -// https://github.com/rwfpl/rewolf-wow64ext - -#define DR7_MASK_RWE0 0xFFFDFFFF // 11111111111111011111111111111111 -#define DR7_MASK_RWE1 0xFFDFFFFF // 11111111110111111111111111111111 -#define DR7_MASK_RWE2 0xFDFFFFFF // 11111101111111111111111111111111 -#define DR7_MASK_RWE3 0xDFFFFFFF // 11011111111111111111111111111111 - -const int PAGE_SIZE = 0x1000; - -extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); -extern void ErrorOutput(_In_ LPCTSTR lpOutputString, ...); - -BOOL WoW64HookInstalled; - -DWORD64 lpHookCode = (DWORD64)NULL; -DWORD64 lpNewJumpLocation; - -DWORD64 pfnKiUserExceptionDispatcher; -DWORD64 pfnNtSetContextThread; - -//************************************************************************************** -extern BOOL WoW64PatchBreakpoint(unsigned int Register) -//************************************************************************************** -{ - if (WoW64HookInstalled == FALSE) - return FALSE; - - DebugOutput("WoW64PatchBreakpoint entry, debug register: %d, current DR7 mask = 0x%x\n", Register, *(DWORD*)(((PBYTE)lpNewJumpLocation)+37)); - - switch(Register) - { - case 0: - *(DWORD*)(((PBYTE)lpNewJumpLocation)+37) = (*(DWORD*)(((PBYTE)lpNewJumpLocation)+37) & DR7_MASK_RWE0); - break; - case 1: - *(DWORD*)(((PBYTE)lpNewJumpLocation)+37) = (*(DWORD*)(((PBYTE)lpNewJumpLocation)+37) & DR7_MASK_RWE1); - break; - case 2: - *(DWORD*)(((PBYTE)lpNewJumpLocation)+37) = (*(DWORD*)(((PBYTE)lpNewJumpLocation)+37) & DR7_MASK_RWE2); - break; - case 3: - *(DWORD*)(((PBYTE)lpNewJumpLocation)+37) = (*(DWORD*)(((PBYTE)lpNewJumpLocation)+37) & DR7_MASK_RWE3); - break; - } - - DebugOutput("WoW64PatchBreakpoint: patched DR7 mask = 0x%x\n", *(DWORD*)(((PBYTE)lpNewJumpLocation)+37)); - - return TRUE; -} - -//************************************************************************************** -extern BOOL WoW64UnpatchBreakpoint(unsigned int Register) -//************************************************************************************** -{ - if (WoW64HookInstalled == FALSE) - return FALSE; - - DebugOutput("WoW64UnpatchBreakpoint entry, debug register: %d, current DR7 mask = 0x%x\n", Register, *(DWORD*)(((PBYTE)lpNewJumpLocation)+37)); - - switch(Register) - { - case 0: - *(DWORD*)(((PBYTE)lpNewJumpLocation)+37) = (*(DWORD*)(((PBYTE)lpNewJumpLocation)+37) | ~DR7_MASK_RWE0); - break; - case 1: - *(DWORD*)(((PBYTE)lpNewJumpLocation)+37) = (*(DWORD*)(((PBYTE)lpNewJumpLocation)+37) | ~DR7_MASK_RWE1); - break; - case 2: - *(DWORD*)(((PBYTE)lpNewJumpLocation)+37) = (*(DWORD*)(((PBYTE)lpNewJumpLocation)+37) | ~DR7_MASK_RWE2); - break; - case 3: - *(DWORD*)(((PBYTE)lpNewJumpLocation)+37) = (*(DWORD*)(((PBYTE)lpNewJumpLocation)+37) | ~DR7_MASK_RWE3); - break; - } - - DebugOutput("WoW64UnpatchBreakpoint: unpatched DR7 mask = 0x%x\n", *(DWORD*)(((PBYTE)lpNewJumpLocation)+37)); - - return TRUE; -} - -//************************************************************************************** -const DWORD64 CreateHook(const DWORD_PTR pKiUserExceptionDispatcher, const DWORD_PTR pNtSetContextThread64, const DWORD_PTR pWow64PrepareForException) -//************************************************************************************** -// credit to Omega Red http://pastebin.ca/raw/475547 -{ - unsigned char HookBytes[] = - { - 0x81, 0xBC, 0x24, 0xF0, 0x04, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x40, //cmp dword [rsp+0x4f0], 0x4000001e ; wow64 single step? 0 - 0x75, 0x37, //jne hook_end 11 - - 0x49, 0x89, 0xCC, //mov r12, rcx 13 - 0x49, 0x89, 0xD5, //mov r13, rdx 16 - 0x4D, 0x89, 0xC6, //mov r14, r8 19 - 0x4D, 0x89, 0xCF, //mov r15, r9 22 - 0xC7, 0x44, 0x24, 0x30, 0x10, 0x00, 0x10, 0x00, //mov dword ptr [rsp+30h], 100010h 25 - 0x81, 0x64, 0x24, 0x70, 0xFF, 0xFF, 0xFF, 0xFF, //and dword ptr [rsp+70h], 0xFFFFFFFF 33 - 0x48, 0xC7, 0xC1, 0xFE, 0xFF, 0xFF, 0xFF, //mov rcx, 0FFFFFFFFFFFFFFFEh 41 - 0x48, 0x89, 0xE2, //mov rdx, rsp 48 - 0xE8, 0xBE, 0x07, 0xAF, 0x77, //call near ptr Xh 51 - 0x4C, 0x89, 0xE1, //mov rcx, r12 56 - 0x4C, 0x89, 0xEA, //mov rdx, r13 59 - 0x4D, 0x89, 0xF0, //mov r8, r14 62 - 0x4D, 0x89, 0xF9, //mov r9, r15 65 -//hook_end - 0xFC, //cld - first two instructions from KiUserExceptionDispatcher 68 - 0x48, 0xB8, 0xDD, 0xCC, 0xBB, 0xAA, 0x00, 0x00, 0x00, 0x00, //mov rax, 0AABBCCDDh 69 - 0x50, //push rax - jump back to KiUserExceptionDispatcher+8 79 - 0x48, 0xB8, 0xDD, 0xCC, 0xBB, 0xAA, 0x00, 0x00, 0x00, 0x00, //mov rax, 0AABBCCDDh 80 - 0x48, 0x87, 0x04, 0x24, //xchg rax, [rsp] 90 - 0xC3 //ret 94 - }; // 86 - - lpHookCode = VirtualAllocEx64((HANDLE) -1, (DWORD64)NULL, PAGE_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); - - //insert relative address of NtSetContextThread64 from instruction after call - DWORD RelativeOffset = pNtSetContextThread64 - ((DWORD)lpHookCode + 56); - memcpy(&HookBytes[52], &RelativeOffset, sizeof(DWORD_PTR)); - - //insert VA of Wow64PrepareForException - memcpy(&HookBytes[71], &pWow64PrepareForException, sizeof(DWORD_PTR)); - - //insert address to return to from hook code at 8 bytes into KiUserExceptionDispatcher - DWORD ReturnAddress = pKiUserExceptionDispatcher + 8; - memcpy(&HookBytes[82], &ReturnAddress, sizeof(DWORD_PTR)); //(8 is address of third instruction) - - //copy it to newly created page - memcpy((LPVOID)lpHookCode, (const void *)HookBytes, sizeof(HookBytes)); - - return lpHookCode; -} - -//************************************************************************************** -const void EnableWow64Hook() -//************************************************************************************** -{ - unsigned char trampolineBytes[] = - { - 0xE9, 0xDD, 0xCC, 0xBB, 0xAA, // jmp +0xAABBCCDDEE+5 - 0xCC, 0xCC, 0xCC // - }; - DWORD pNew = (DWORD)lpNewJumpLocation; - DWORD pOrig = (DWORD)pfnKiUserExceptionDispatcher + 5; - DWORD RelativeOffset = pNew - pOrig; - memcpy(&trampolineBytes[1], (PVOID)&RelativeOffset, sizeof(DWORD_PTR)); - - DWORD dwOldProtect = 0; - if (!VirtualProtectEx64((HANDLE)-1, (DWORD64)pfnKiUserExceptionDispatcher, PAGE_SIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect)) - { - ErrorOutput("VirtualProtectEx64 failed to set PAGE_EXECUTE_READWRITE"); - return; - } - - memcpy((PVOID)pfnKiUserExceptionDispatcher, &trampolineBytes, sizeof(trampolineBytes)); - - if (!VirtualProtectEx64((HANDLE)-1, (DWORD64)pfnKiUserExceptionDispatcher, PAGE_SIZE, dwOldProtect, &dwOldProtect)) - { - ErrorOutput("VirtualProtect failed to restore dwOldProtect"); - return; - } -} - -//************************************************************************************** -extern BOOL WoW64fix(void) -//************************************************************************************** -{ - OSVERSIONINFO OSVersion; - OSVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - -#pragma warning(suppress : 4996) - if (!GetVersionEx(&OSVersion)) - { - ErrorOutput("WoW64fix: Failed to get OS version"); - return FALSE; - } - - if ((OSVersion.dwMajorVersion == 6 && OSVersion.dwMinorVersion > 1) || OSVersion.dwMajorVersion > 6) - { - DebugOutput("WoW64fix: Windows version %d.%d not supported.\n", OSVersion.dwMajorVersion, OSVersion.dwMinorVersion); - return FALSE; - } - - IsWow64Process(GetCurrentProcess(), &WoW64HookInstalled); - if (WoW64HookInstalled == FALSE) - { - DebugOutput("WoW64 not detected.\n"); - return FALSE; - } - - DWORD64 ntdll64 = GetModuleBase64(L"ntdll.dll"); - DWORD64 wow64dll = GetModuleBase64(L"wow64.dll"); - DWORD64 pfnWow64PrepareForException = GetProcAddress64(wow64dll, "Wow64PrepareForException"); - pfnKiUserExceptionDispatcher = GetProcAddress64(ntdll64, "KiUserExceptionDispatcher"); - pfnNtSetContextThread = GetProcAddress64(ntdll64, "NtSetContextThread"); - - DebugOutput("WoW64 detected: 64-bit ntdll base: 0x%x, KiUserExceptionDispatcher: 0x%x, NtSetContextThread: 0x%x, Wow64PrepareForException: 0x%x\n", ntdll64, pfnKiUserExceptionDispatcher, pfnNtSetContextThread, pfnWow64PrepareForException); - - lpNewJumpLocation = CreateHook((DWORD_PTR)pfnKiUserExceptionDispatcher, (DWORD_PTR)pfnNtSetContextThread, (DWORD_PTR)pfnWow64PrepareForException); - - EnableWow64Hook(); - - DebugOutput("WoW64 workaround: KiUserExceptionDispatcher hook installed at: 0x%x\n", lpNewJumpLocation); - - return TRUE; -} -#endif \ No newline at end of file diff --git a/capemon.vcxproj b/capemon.vcxproj index b67161d8..3178bdbf 100644 --- a/capemon.vcxproj +++ b/capemon.vcxproj @@ -230,8 +230,6 @@ - - diff --git a/capemon.vcxproj.filters b/capemon.vcxproj.filters index e54f9002..95be3a61 100644 --- a/capemon.vcxproj.filters +++ b/capemon.vcxproj.filters @@ -31,9 +31,6 @@ {2870ff64-a1ae-437b-b93d-b815724102e4} - - {cf36b66e-1611-4be7-abe4-482a5fbacbe2} - @@ -261,12 +258,6 @@ Source Files\CAPE\Scylla - - Source Files\CAPE\w64wow64 - - - Source Files\CAPE - Source Files\CAPE From 740ca874618586f017d997a99ee305a1a5cf8af6 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 20 Oct 2025 15:43:49 +0100 Subject: [PATCH 106/148] YaraHarness: simplify 'dump' option --- CAPE/YaraHarness.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index 8c92ce12..92d818ca 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -270,7 +270,10 @@ int YaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void if (!_strnicmp(OptionLine, "bp", 2) || !strncmp(OptionLine, "br", 2) || !strncmp(OptionLine, "sysbp", 5)) SetBreakpoints = TRUE; if (!_stricmp("dump", OptionLine)) - DoDumpRegion = TRUE; + { + DebugOutput("YaraScan: Dump of region at 0x%p triggered by Yara.", user_data); + DumpRegion(user_data); + } if (!_stricmp("clear", OptionLine)) { BreakpointsHit = FALSE; @@ -301,12 +304,6 @@ int YaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void if (DebuggerInitialised && SetBreakpoints) SetInitialBreakpoints(user_data); - if (DoDumpRegion) - { - DebugOutput("YaraScan: Dump of region at 0x%p triggered by Yara.", user_data); - DumpRegion(user_data); - } - return CALLBACK_CONTINUE; } From 441abea1cb2e534652b78d933f55b565e35b46a6 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 20 Oct 2025 16:09:15 +0100 Subject: [PATCH 107/148] YaraHarness: write rules canary detection to analysis log --- CAPE/YaraHarness.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index 92d818ca..b0552cb7 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -523,7 +523,10 @@ BOOL ScanForRulesCanary(PVOID Address, SIZE_T Size) YaraLogging = FALSE; BOOL CapemonRulesDetected = FALSE; if (GetAddressByYara(Address, "capemon")) + { CapemonRulesDetected = TRUE; + DebugOutput("ScanForRulesCanary: capemon rules detected"); + } YaraLogging = PreviousYaraLogging; return CapemonRulesDetected; } From 46eb93b27fa023747ece7afb96a3b16f906d7d11 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 22 Oct 2025 15:46:07 +0100 Subject: [PATCH 108/148] Experimental debugger action 'guard' to trap on guard violation --- CAPE/Debugger.c | 12 ++++- CAPE/Trace.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index df87079b..e607ecb3 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -48,12 +48,14 @@ extern void log_direct_syscall(const char *function, PVOID addr); extern unsigned int address_is_in_stack(DWORD Address); extern BOOL SetInitialBreakpoints(PVOID ImageBase), Trace(struct _EXCEPTION_POINTERS* ExceptionInfo), SoftwareBreakpointCallback(struct _EXCEPTION_POINTERS* ExceptionInfo); extern BOOL BreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS* ExceptionInfo); +extern BOOL GuardPageCallback(struct _EXCEPTION_POINTERS* ExceptionInfo); extern void DebuggerOutput(_In_ LPCTSTR lpOutputString, ...), DoTraceOutput(PVOID Address); extern BOOL TraceRunning, BreakpointsSet, BreakpointsHit, StopTrace, BreakOnNtContinue, SyscallBreakpointSet; extern PVECTORED_EXCEPTION_HANDLER SampleVectoredHandler; extern int StepOverRegister; extern int process_shutting_down; extern HANDLE DebuggerLog; +extern PVOID GuardedPages; struct ThreadBreakpoints *MainThreadBreakpointList; unsigned int TrapIndex, DepthCount; @@ -504,7 +506,7 @@ BOOL SyscallBreakpointHandler(struct _EXCEPTION_POINTERS* ExceptionInfo) } #ifdef DEBUG_COMMENTS else - DebugOutput("SyscallBreakpointHandler: Calling SSN 0x%x -> 0x%p: %s\n", SSN, Function, FunctionName); + DebugOutput("SyscallBreakpointHandler: Syscall at 0x%p, SSN 0x%x -> 0x%p: %s\n", ExceptionInfo->ExceptionRecord->ExceptionAddress, SSN, Function, FunctionName); #endif log_direct_syscall(FunctionName, (PVOID)CIP); @@ -760,6 +762,14 @@ LONG WINAPI CAPEExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) #endif #endif } + else if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) + { +#ifdef DEBUG_COMMENTS + DebugOutput("CAPEExceptionFilter: Guard page violation at 0x%p\n", ExceptionInfo->ExceptionRecord->ExceptionAddress); +#endif + if (GetAllocationBase(ExceptionInfo->ExceptionRecord->ExceptionAddress) == GuardedPages && GuardPageCallback(ExceptionInfo)) + return EXCEPTION_CONTINUE_EXECUTION; + } // Exceptions in capemon if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress >= g_our_dll_base && (ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress < (g_our_dll_base + g_our_dll_size)) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 7b044177..76accf36 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -54,7 +54,7 @@ extern PVOID _KiUserExceptionDispatcher; extern lookup_t SoftBPs, SyscallBPs; char *ModuleName, *PreviousModuleName; -PVOID ModuleBase, DumpAddress, ReturnAddress, BreakOnReturnAddress, BreakOnNtContinueCallback, PreviousJumps[4]; +PVOID ModuleBase, DumpAddress, ReturnAddress, BreakOnReturnAddress, BreakOnNtContinueCallback, PreviousJumps[4], GuardedPages; BOOL BreakpointsSet, BreakpointsHit, FilterTrace, StopTrace, ReDisassemble, SyscallBreakpointSet, TraceRunning, BreakOnNtContinue; unsigned int Correction, StepCount, StepLimit, TraceDepthLimit, BreakOnReturnRegister, JumpCount; char Action0[MAX_PATH], Action1[MAX_PATH], Action2[MAX_PATH], Action3[MAX_PATH]; @@ -1505,6 +1505,27 @@ void ActionDispatcher(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst De DebuggerOutput("ActionDispatcher: Nothing to print at 0x%p\n", Target); } #endif + else if (!stricmp(Action, "Guard")) + { + DWORD OldProtect; + MEMORY_BASIC_INFORMATION MemInfo; + if (!SystemInfo.dwPageSize) + GetSystemInfo(&SystemInfo); + SIZE_T GuardedSize = SystemInfo.dwPageSize; + GuardedPages = GetAllocationBase(CIP); + if (VirtualQuery(GuardedPages, &MemInfo, GuardedSize)) + { + if (VirtualProtect(GuardedPages, GuardedSize, MemInfo.Protect | PAGE_GUARD, &OldProtect)) + DebuggerOutput("ActionDispatcher: Instated guard page(s) at 0x%p size 0x%x", GuardedPages, GuardedSize); + else + { + ErrorOutput("ActionDispatcher: Failed to instate guard page(s) at 0x%p size 0x%x", GuardedPages, GuardedSize); + DebuggerOutput("ActionDispatcher: Failed to instate guard page(s) at 0x%p size 0x%x", GuardedPages, GuardedSize); + } + } + else + DebuggerOutput("Problem calling VirtualQuery on 0x%p", GuardedPages); + } else if (!stricmp(Action, "DumpImage")) { #ifdef _WIN64 @@ -2608,6 +2629,104 @@ BOOL SoftwareBreakpointCallback(struct _EXCEPTION_POINTERS* ExceptionInfo) return TRUE; } +BOOL GuardPageCallback(struct _EXCEPTION_POINTERS* ExceptionInfo) +{ + PVOID CIP; + _DecodeType DecodeType; + _DecodeResult Result; + _OffsetType Offset = 0; + _DecodedInst DecodedInstruction; + unsigned int DecodedInstructionsCount = 0; + BOOL StepOver = FALSE, ForceStepOver = FALSE; + + StopTrace = FALSE; + + BreakpointsHit = TRUE; + + DebuggerOutput("Guard break hit by instruction at 0x%p (thread %d)", ExceptionInfo->ExceptionRecord->ExceptionAddress, GetCurrentThreadId()); + +#ifdef _WIN64 + CIP = (PVOID)ExceptionInfo->ContextRecord->Rip; + DecodeType = Decode64Bits; +#else + CIP = (PVOID)ExceptionInfo->ContextRecord->Eip; + DecodeType = Decode32Bits; +#endif + + if (g_config.log_breakpoints) + { + // Log breakpoint to behavior log + memset(DebuggerBuffer, 0, MAX_PATH*sizeof(CHAR)); + _snprintf_s(DebuggerBuffer, MAX_PATH, _TRUNCATE, "Breakpoint hit at 0x%p", CIP); + log_breakpoint("Debugger", DebuggerBuffer); + } + + FilterTrace = FALSE; + + if (InsideMonitor(NULL, CIP) && g_config.trace_all == 1) + FilterTrace = TRUE; + + if (inside_hook(CIP) && !g_config.trace_all) + FilterTrace = TRUE; + + if (is_in_dll_range((ULONG_PTR)CIP) && !g_config.trace_all) + FilterTrace = TRUE; + + //StepCount++; + + OutputRegisterChanges(ExceptionInfo->ContextRecord); + + if (!FilterTrace) + DebuggerOutput("\n"); + + if (CIP) + Result = distorm_decode(Offset, (const unsigned char*)CIP, CHUNKSIZE, DecodeType, &DecodedInstruction, 1, &DecodedInstructionsCount); + + // Instruction handling + InstructionHandler(ExceptionInfo, DecodedInstruction, &StepOver, &ForceStepOver); + + LastContext = *ExceptionInfo->ContextRecord; + + if (!StepLimit || StepCount > StepLimit || StopTrace) + { + if (StepLimit) + DebuggerOutput("\nGuardPageCallback: Single-step limit reached (%d), releasing.\n", StepLimit); + memset(&LastContext, 0, sizeof(CONTEXT)); + StopTrace = TRUE; + StepCount = 0; + TraceRunning = FALSE; + ReturnAddress = NULL; + } + else if (ReturnAddress && (StepOver == TRUE && !g_config.trace_all) || ForceStepOver) + { + if (ContextSetNextAvailableBreakpoint(ExceptionInfo->ContextRecord, &StepOverRegister, 0, (BYTE*)ReturnAddress, BP_EXEC, 1, BreakpointCallback)) + { +#ifdef DEBUG_COMMENTS + DebugOutput("GuardPageCallback: Set breakpoint on return address 0x%p\n", ReturnAddress); +#endif + ReturnAddress = NULL; + } + else + DebugOutput("GuardPageCallback: Failed to set breakpoint on return address 0x%p\n", ReturnAddress); + } + else + { + DWORD OldProtect; + MEMORY_BASIC_INFORMATION MemInfo; + GuardedPages = GetAllocationBase(CIP); + SIZE_T AllocationSize = GetAllocationSize(GuardedPages); + if (VirtualQuery(GuardedPages, &MemInfo, AllocationSize)) + { + if (VirtualProtect(GuardedPages, AllocationSize, MemInfo.Protect | PAGE_GUARD, &OldProtect)) + DebuggerOutput("GuardPageCallback: Instated guard page(s) at 0x%p size 0x%x", GuardedPages, AllocationSize); + else + DebuggerOutput("GuardPageCallback: Failed to instate guard page(s) at 0x%p size 0x%x", GuardedPages, AllocationSize); + } + } + + return TRUE; +} + BOOL BreakOnReturnCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS* ExceptionInfo) { PVOID CIP; From e4489544e5da046ee8906caa632462ca6d2a1e93 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 22 Oct 2025 15:56:39 +0100 Subject: [PATCH 109/148] Cap per-process messages to prevent detonation slow-down & failure in e.g. 9f8333d81c13ea426953b758140836cff2cf7e7f32e36738f118c6257c6efd34 --- CAPE/Injection.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index 7cbb0480..7bc022a1 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -1003,7 +1003,7 @@ void WriteMemoryHandler(HANDLE ProcessHandle, LPVOID BaseAddress, LPCVOID Buffer if (IsDisguisedPEHeader((PVOID)Buffer)) { CurrentInjectionInfo->ImageBase = (DWORD_PTR)BaseAddress; - DebugOutput("WriteMemoryHandler: Executable binary injected into process %d (ImageBase 0x%x)\n", Pid, CurrentInjectionInfo->ImageBase); + DebugOutput("WriteMemoryHandler: Executable binary injected from 0x%p (size 0x%x) into process %d at 0x%p.\n", Buffer, NumberOfBytesWritten, Pid, BaseAddress); if (CurrentInjectionInfo->ImageDumped == FALSE) { @@ -1045,7 +1045,7 @@ void WriteMemoryHandler(HANDLE ProcessHandle, LPVOID BaseAddress, LPCVOID Buffer } else { - DebugOutput("WriteMemoryHandler: shellcode at 0x%p (size 0x%x) injected into process %d.\n", Buffer, NumberOfBytesWritten, Pid); + DebugOutput("WriteMemoryHandler: shellcode at 0x%p (size 0x%x) injected into process %d at 0x%p.\n", Buffer, NumberOfBytesWritten, Pid, BaseAddress); // dump injected code/data CapeMetaData->DumpType = INJECTION_SHELLCODE; @@ -1177,11 +1177,26 @@ void TerminateHandler() } } +#define ProcessMessageLimit 0x20 +DWORD PreviousPid; +unsigned int ProcessMessageCount; + void ProcessMessage(DWORD ProcessId, DWORD ThreadId) { if (ProcessId == GetCurrentProcessId()) return; + if (ProcessId == PreviousPid) + ProcessMessageCount++; + else + { + PreviousPid = ProcessId; + ProcessMessageCount = 0; + } + + if (ProcessMessageCount >= ProcessMessageLimit) + return; + PINJECTIONINFO CurrentInjectionInfo = GetInjectionInfo(ProcessId); if (CurrentInjectionInfo && !ThreadId) From 8b9cd28b725c7734aeea5216bee4527d8e06dbe6 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 22 Oct 2025 15:57:09 +0100 Subject: [PATCH 110/148] Remove obsolete 'suspended' parameter from PROCESS monitor message --- CAPE/Injection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index 7bc022a1..0c9804e4 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -1288,5 +1288,5 @@ void ProcessMessage(DWORD ProcessId, DWORD ThreadId) hook_enable(); } else - pipe("PROCESS:0:%d,%d", ProcessId, ThreadId); + pipe("PROCESS:%d,%d", ProcessId, ThreadId); } \ No newline at end of file From db61173c9127628583e56156bd80dd9420bd95a7 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 22 Oct 2025 15:58:00 +0100 Subject: [PATCH 111/148] WriteMemoryHandler: prevent analysis log spam for small PE writes --- CAPE/Injection.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index 0c9804e4..9534f7f0 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -1041,7 +1041,8 @@ void WriteMemoryHandler(HANDLE ProcessHandle, LPVOID BaseAddress, LPCVOID Buffer { // Looks like a previously dumped PE image is being written a section at a time to the target process. // We don't want to dump these writes. - DebugOutput("WriteMemoryHandler: injection of section of PE image which has already been dumped.\n"); + if (NumberOfBytesWritten >= 0x1000) + DebugOutput("WriteMemoryHandler: injection of section of PE image which has already been dumped.\n"); } else { From 2d82cd5052a57dafdfaaf96f939e23b9336a3c0e Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 24 Oct 2025 16:47:58 +0100 Subject: [PATCH 112/148] Apply update to PROCESS: monitor message in loader CreateMonitorPipe() --- loader/loader/Loader.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/loader/loader/Loader.c b/loader/loader/Loader.c index 99aac950..a4cd4d1b 100644 --- a/loader/loader/Loader.c +++ b/loader/loader/Loader.c @@ -114,7 +114,7 @@ int ScanForNonZero(LPVOID Buffer, SIZE_T Size) } __except(EXCEPTION_EXECUTE_HANDLER) { - DebugOutput("ScanForNonZero: Exception occured reading memory address 0x%x\n", (char*)Buffer+p); + DebugOutput("ScanForNonZero: Exception occurred reading memory address 0x%x\n", (char*)Buffer+p); return 0; } @@ -1357,8 +1357,8 @@ int CreateMonitorPipe(char* Name, char* Dll) char *p; if ((p = strchr(buf, ','))) { *p = '\0'; - ProcessId = atoi(&buf[10]); // skipping the '0:' or '1:' suspended flag - ThreadId = atoi(p + 1); // (soon to be deprecated) + ProcessId = atoi(&buf[8]); + ThreadId = atoi(p + 1); } else { ProcessId = atoi(&buf[10]); From 173d144b24f6a36d7c9e0b643bd2f788f0ebf490 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 24 Oct 2025 16:52:47 +0100 Subject: [PATCH 113/148] Deprecate ScyllaGetExportDirectory & ScyllaGetExportNameByScan, add GetExportDirectory --- CAPE/CAPE.c | 40 +++++++++ CAPE/ScyllaHarness.cpp | 193 ----------------------------------------- capemon.c | 37 ++------ 3 files changed, 49 insertions(+), 221 deletions(-) diff --git a/CAPE/CAPE.c b/CAPE/CAPE.c index 2fef702a..fc138361 100644 --- a/CAPE/CAPE.c +++ b/CAPE/CAPE.c @@ -2139,6 +2139,46 @@ PCHAR ScanForExport(PVOID Address, SIZE_T ScanMax) return NULL; } +//************************************************************************************** +PCHAR GetExportDirectory(PVOID Address) +//************************************************************************************** +{ + if (!Address) + return NULL; + + __try + { + PVOID Base = GetAllocationBase(Address); + if (!Base || !IsAddressAccessible(Base)) + return NULL; + + PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)Base; + if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + + PIMAGE_NT_HEADERS NtHeader = (PIMAGE_NT_HEADERS)((PUCHAR)Base + DosHeader->e_lfanew); + if (NtHeader->Signature != IMAGE_NT_SIGNATURE) + return NULL; + + IMAGE_DATA_DIRECTORY ExportDirEntry = NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (ExportDirEntry.VirtualAddress == 0 || ExportDirEntry.Size == 0) + return NULL; + + PIMAGE_EXPORT_DIRECTORY ExportDir = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)Base + ExportDirEntry.VirtualAddress); + if (!IsAddressAccessible(ExportDir)) + return NULL; + + if (ExportDir && ExportDir->Name) + return ((PCHAR)Base + ExportDir->Name); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return NULL; + } + + return NULL; +} + //************************************************************************************** PCHAR GetExportNameByAddress(PVOID Address) //************************************************************************************** diff --git a/CAPE/ScyllaHarness.cpp b/CAPE/ScyllaHarness.cpp index 389e1ab8..dc7ad568 100644 --- a/CAPE/ScyllaHarness.cpp +++ b/CAPE/ScyllaHarness.cpp @@ -706,196 +706,3 @@ extern "C" int IsPeImageRaw(DWORD_PTR Buffer) delete peFile; return 0; } - -//************************************************************************************** -extern "C" BOOL ScyllaGetSectionByName(PVOID ImageBase, char* Name, PVOID* SectionData, SIZE_T* SectionSize) -//************************************************************************************** -{ - ScyllaInit(NULL); - - PeParser *peFile = new PeParser((DWORD_PTR)ImageBase, true); - - if (!peFile->isValidPeFile()) - { - DebugOutput("ScyllaGetSectionByName: Invalid PE image.\n"); - return 0; - } - - if (!peFile->readPeSectionsFromProcess()) - { - DebugOutput("ScyllaGetSectionByName: Failed to read PE sections from image.\n"); - return 0; - } - - unsigned int NumberOfSections = peFile->getNumberOfSections(); - - for (unsigned int i = 0; i < NumberOfSections; i++) - { - if (!strcmp((char*)peFile->listPeSection[i].sectionHeader.Name, Name)) - { - *SectionData = peFile->listPeSection[i].sectionHeader.VirtualAddress + (PUCHAR)ImageBase; - *SectionSize = peFile->listPeSection[i].sectionHeader.Misc.VirtualSize; - DebugOutput("ScyllaGetSectionByName: %s section at 0x%p size 0x%x.\n", Name, *SectionData, *SectionSize); - return TRUE; - } - } - - return FALSE; -} - -//************************************************************************************** -extern "C" PCHAR ScyllaGetExportNameByScan(PVOID Address, PCHAR* ModuleName, SIZE_T ScanSize) -//************************************************************************************** -{ - ApiReader apiReader; - ApiInfo* apiInfo = NULL; - unsigned int ModuleIndex = 0; - bool dummy = 0; - - ScyllaInit(NULL); - - for (unsigned int i = 0; i < ProcessAccessHelp::ownModuleList.size(); i++) { - if ((DWORD_PTR)Address >= ProcessAccessHelp::ownModuleList[i].modBaseAddr && (DWORD_PTR)Address < (ProcessAccessHelp::ownModuleList[i].modBaseAddr + ProcessAccessHelp::ownModuleList[i].modBaseSize)) - ModuleIndex = i+1; - } - - if (!ModuleIndex) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportNameByScan: Address 0x%p not within loaded modules.\n", Address); -#endif - return NULL; - } - - PVOID ModuleBase = GetAllocationBase(Address); - - if (!ModuleBase) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportNameByScan: GetAllocationBase failed for 0x%p.\n", Address); -#endif - return NULL; - } -#ifdef DEBUG_COMMENTS - else - DebugOutput("ScyllaGetExportNameByScan: AllocationBase 0x%p for 0x%p.\n", ModuleBase, Address); -#endif - - PeParser *peFile = new PeParser((DWORD_PTR)ModuleBase, true); - - if (!peFile->isValidPeFile()) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportNameByScan: Invalid PE image at 0x%p.\n", Address); -#endif - delete peFile; - return NULL; - } - - if (!peFile->hasExportDirectory()) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportNameByScan: Module has no exports.\n"); -#endif - delete peFile; - return NULL; - } - - apiReader.clearAll(); - - // This creates moduleInfo->apiList - apiReader.parseModuleWithOwnProcess(&ProcessAccessHelp::ownModuleList[ModuleIndex-1]); - - for (unsigned int i=0; i < ScanSize; i++) - { - apiInfo = apiReader.getApiByVirtualAddress((DWORD_PTR)Address-i, &dummy); - if (apiInfo) - break; - } - - if (apiInfo) - { - if (ModuleName) - *ModuleName = apiInfo->module->fullPath; -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportNameByScan: Located function %s within module %s.\n", apiInfo->name, apiInfo->module->fullPath); -#endif - delete peFile; - return (PCHAR)apiInfo->name; - } -#ifdef DEBUG_COMMENTS - else - DebugOutput("ScyllaGetExportNameByScan: Failed to locate function among module exports.\n"); -#endif - - delete peFile; - return NULL; -} - -//************************************************************************************** -extern "C" PCHAR ScyllaGetExportNameByAddress(PVOID Address, PCHAR* ModuleName) -//************************************************************************************** -{ - return ScyllaGetExportNameByScan(Address, ModuleName, 1); -} - -//************************************************************************************** -extern "C" PCHAR ScyllaGetExportDirectory(PVOID Address) -//************************************************************************************** -{ - unsigned int ModuleIndex = 0; - - ScyllaInit(NULL); - - for (unsigned int i = 0; i < ProcessAccessHelp::ownModuleList.size(); i++) { - if ((DWORD_PTR)Address >= ProcessAccessHelp::ownModuleList[i].modBaseAddr && (DWORD_PTR)Address < (ProcessAccessHelp::ownModuleList[i].modBaseAddr + ProcessAccessHelp::ownModuleList[i].modBaseSize)) - ModuleIndex = i+1; - } - - if (!ModuleIndex) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportDirectory: Address 0x%p not within loaded modules.\n", Address); -#endif - return NULL; - } - - PVOID ModuleBase = GetAllocationBase(Address); - - if (!ModuleBase) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportDirectory: GetAllocationBase failed for 0x%p.\n", Address); -#endif - return NULL; - } - - PeParser *peFile = new PeParser((DWORD_PTR)ModuleBase, true); - - if (!peFile->isValidPeFile()) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportDirectory: Invalid PE image at 0x%p.\n", Address); -#endif - delete peFile; - return NULL; - } - - char* DirectoryName = peFile->getExportDirectory(); - - if (DirectoryName) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ScyllaGetExportDirectory: Export directory name %s.\n", DirectoryName); -#endif - delete peFile; - return (PCHAR)DirectoryName; - } -#ifdef DEBUG_COMMENTS - else - DebugOutput("ScyllaGetExportDirectory: Failed to locate export directory name.\n"); -#endif - - delete peFile; - return NULL; -} diff --git a/capemon.c b/capemon.c index 132ffc32..3f0f7f01 100644 --- a/capemon.c +++ b/capemon.c @@ -59,8 +59,8 @@ extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); extern LONG WINAPI CAPEExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo); extern ULONG_PTR base_of_dll_of_interest; extern BOOL BreakpointsHit, SetInitialBreakpoints(PVOID ImageBase); -extern PCHAR ScyllaGetExportDirectory(PVOID Address); -extern PCHAR ScyllaGetExportNameByScan(PVOID Address, PCHAR* ModuleName, SIZE_T ScanSize); +extern PCHAR GetExportDirectory(PVOID Address); +extern PCHAR ScanForExport(PVOID Address, SIZE_T ScanMax); extern void YaraScan(PVOID Address, SIZE_T Size); extern BOOL IsAddressAccessible(PVOID Address); @@ -164,7 +164,7 @@ VOID CALLBACK New_DllLoadNotification( add_dll_range((ULONG_PTR)NotificationData->Loaded.DllBase, (ULONG_PTR)NotificationData->Loaded.DllBase + GetAllocationSize(NotificationData->Loaded.DllBase)); if (!set_hooks_dll(dllname)) { - exportdirectory = ScyllaGetExportDirectory(NotificationData->Loaded.DllBase); + exportdirectory = GetExportDirectory(NotificationData->Loaded.DllBase); if (exportdirectory) { size = strlen(exportdirectory); mbstowcs_s(&numconverted, exportdirectory_w, MAX_PATH, exportdirectory, size+1); @@ -196,12 +196,7 @@ static int parse_stack_trace(void *msg, ULONG_PTR addr) char *buf = convert_address_to_dll_name_and_offset(addr, &offset); if (buf) { PCHAR funcname; - __try { - funcname = ScyllaGetExportNameByScan((PVOID)addr, NULL, 0x50); - } - __except(EXCEPTION_EXECUTE_HANDLER) { - ; - } + funcname = ScanForExport((PVOID)addr, 0x50); if (funcname) snprintf((char *)msg + strlen(msg), sizeof(msg) - strlen(msg) - 1, "%s::%s(0x%x)\n", buf, funcname, offset); else @@ -372,12 +367,7 @@ LONG WINAPI capemon_exception_handler(__in struct _EXCEPTION_POINTERS *Exception sprintf(msg, "Exception Caught! PID: %u EIP:", GetCurrentProcessId()); if (dllname) { PCHAR FunctionName; - __try { - FunctionName = ScyllaGetExportNameByScan((PVOID)eip, NULL, 0x50); - } - __except(EXCEPTION_EXECUTE_HANDLER) { - ; - } + FunctionName = ScanForExport((PVOID)eip, 0x50); if (FunctionName) snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg) - 1, " %s::%s(0x%x)", dllname, FunctionName, offset); else @@ -418,12 +408,7 @@ LONG WINAPI capemon_exception_handler(__in struct _EXCEPTION_POINTERS *Exception char *buf = convert_address_to_dll_name_and_offset(stack[i], &offset); if (buf) { PCHAR funcname = NULL; - __try { - funcname = ScyllaGetExportNameByScan((PVOID)eip, NULL, 0x50); - } - __except(EXCEPTION_EXECUTE_HANDLER) { - ; - } + funcname = ScanForExport((PVOID)eip, 0x50); if (funcname) snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg) - 1, " %s::%s(0x%x)\n", buf, funcname, offset); else @@ -455,13 +440,9 @@ LONG WINAPI capemon_exception_handler(__in struct _EXCEPTION_POINTERS *Exception #endif Result = distorm_decode(Offset, (const unsigned char*)eip, 0x100, DecodeType, &DecodedInstruction, 1, &DecodedInstructionsCount); - if (dllname) { - __try { - FunctionName = ScyllaGetExportNameByScan((PVOID)eip, NULL, 0x40); - } - __except(EXCEPTION_EXECUTE_HANDLER) { - ; - } + if (dllname) + { + FunctionName = ScanForExport((PVOID)eip, 0x40); if (FunctionName) { DebugOutput("%s::%s (`) %-20s %-6s%-4s%-30s\n", dllname, FunctionName, (DWORD_PTR)eip, (char*)DecodedInstruction.instructionHex.p, (char*)DecodedInstruction.mnemonic.p, DecodedInstruction.operands.length != 0 ? " " : "", (char*)DecodedInstruction.operands.p); From 509ba7327279520ac46e3dd4221f9e531334b5f3 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 24 Oct 2025 16:54:17 +0100 Subject: [PATCH 114/148] Add exported function name logging to thread hooks: NtCreateThreadEx, CreateThread, NtQueueApcThread, NtQueueApcThreadEx --- hook_thread.c | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/hook_thread.c b/hook_thread.c index 4a020e27..fa4f94b4 100644 --- a/hook_thread.c +++ b/hook_thread.c @@ -86,7 +86,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtQueueApcThread, ) { DWORD pid = pid_from_thread_handle(ThreadHandle); DWORD tid = tid_from_thread_handle(ThreadHandle); - char *module_name = NULL; + char *module_name = NULL, *function_name = NULL; unsigned int offset; NTSTATUS ret; @@ -96,8 +96,11 @@ HOOKDEF(NTSTATUS, WINAPI, NtQueueApcThread, ret = Old_NtQueueApcThread(ThreadHandle, ApcRoutine, ApcRoutineContext, ApcStatusBlock, ApcReserved); module_name = convert_address_to_dll_name_and_offset((ULONG_PTR)ApcRoutine, &offset); + function_name = GetExportNameByAddress((PVOID)ApcRoutine); - if (module_name) + if (function_name && module_name) + LOQ_ntstatus("threading", "iippss", "ProcessId", pid, "ThreadId", tid, "ThreadHandle", ThreadHandle, "ApcRoutine", ApcRoutine, "Module", module_name, "Name", function_name); + else if (module_name) LOQ_ntstatus("threading", "iipps", "ProcessId", pid, "ThreadId", tid, "ThreadHandle", ThreadHandle, "ApcRoutine", ApcRoutine, "Module", module_name); else LOQ_ntstatus("threading", "iipp", "ProcessId", pid, "ThreadId", tid, "ThreadHandle", ThreadHandle, "ApcRoutine", ApcRoutine); @@ -121,7 +124,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtQueueApcThreadEx, ) { DWORD pid = pid_from_thread_handle(ThreadHandle); DWORD tid = tid_from_thread_handle(ThreadHandle); - char *module_name = NULL; + char *module_name = NULL, *function_name = NULL; unsigned int offset; NTSTATUS ret; @@ -131,8 +134,11 @@ HOOKDEF(NTSTATUS, WINAPI, NtQueueApcThreadEx, ret = Old_NtQueueApcThreadEx(ThreadHandle, UserApcReserveHandle, ApcRoutine, ApcRoutineContext, ApcStatusBlock, ApcReserved); module_name = convert_address_to_dll_name_and_offset((ULONG_PTR)ApcRoutine, &offset); + function_name = GetExportNameByAddress((PVOID)ApcRoutine); - if (module_name) + if (function_name && module_name) + LOQ_ntstatus("threading", "iippss", "ProcessId", pid, "ThreadId", tid, "ThreadHandle", ThreadHandle, "ApcRoutine", ApcRoutine, "Module", module_name, "Name", function_name); + else if (module_name) LOQ_ntstatus("threading", "iipps", "ProcessId", pid, "ThreadId", tid, "ThreadHandle", ThreadHandle, "ApcRoutine", ApcRoutine, "Module", module_name); else LOQ_ntstatus("threading", "iipp", "ProcessId", pid, "ThreadId", tid, "ThreadHandle", ThreadHandle, "ApcRoutine", ApcRoutine); @@ -211,7 +217,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtCreateThreadEx, OUT PVOID lpBytesBuffer ) { DWORD pid = pid_from_process_handle(ProcessHandle); - char *module_name = NULL; + char *module_name = NULL, *function_name = NULL; unsigned int offset; disable_sleep_skip(); @@ -221,6 +227,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtCreateThreadEx, lpBytesBuffer); module_name = convert_address_to_dll_name_and_offset((ULONG_PTR)lpStartAddress, &offset); + function_name = GetExportNameByAddress((PVOID)lpStartAddress); if (NT_SUCCESS(ret)) { DWORD tid = tid_from_thread_handle(*hThread); @@ -245,7 +252,11 @@ HOOKDEF(NTSTATUS, WINAPI, NtCreateThreadEx, set_lasterrors(&lasterror); } - if (module_name) + if (function_name && module_name) + LOQ_ntstatus("threading", "Pppphiiss", "ThreadHandle", hThread, "ProcessHandle", ProcessHandle, + "StartAddress", lpStartAddress, "Parameter", lpParameter, "CreateFlags", CreateFlags, "ThreadId", tid, + "ProcessId", pid, "Module", module_name, "Name", function_name); + else if (module_name) LOQ_ntstatus("threading", "Pppphiis", "ThreadHandle", hThread, "ProcessHandle", ProcessHandle, "StartAddress", lpStartAddress, "Parameter", lpParameter, "CreateFlags", CreateFlags, "ThreadId", tid, "ProcessId", pid, "Module", module_name); @@ -255,7 +266,10 @@ HOOKDEF(NTSTATUS, WINAPI, NtCreateThreadEx, "ProcessId", pid); } else { - if (module_name) + if (function_name && module_name) + LOQ_ntstatus("threading", "Pppphss", "ThreadHandle", hThread, "ProcessHandle", ProcessHandle, + "StartAddress", lpStartAddress, "Parameter", lpParameter, "CreateFlags", CreateFlags, "Module", module_name, "Name", function_name); + else if (module_name) LOQ_ntstatus("threading", "Pppphs", "ThreadHandle", hThread, "ProcessHandle", ProcessHandle, "StartAddress", lpStartAddress, "Parameter", lpParameter, "CreateFlags", CreateFlags, "Module", module_name); else @@ -606,9 +620,10 @@ HOOKDEF(HANDLE, WINAPI, CreateThread, ENSURE_DWORD(lpThreadId); unsigned int DllRVA; - char *module_name = NULL; + char *module_name = NULL, *function_name = NULL; module_name = convert_address_to_dll_name_and_offset((ULONG_PTR)lpStartAddress, &DllRVA); + function_name = GetExportNameByAddress((PVOID)*lpStartAddress); disable_sleep_skip(); ret = Old_CreateThread(lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags | CREATE_SUSPENDED, lpThreadId); @@ -628,14 +643,20 @@ HOOKDEF(HANDLE, WINAPI, CreateThread, set_lasterrors(&lasterror); } - if (module_name) + if (function_name && module_name) + LOQ_nonnull("threading", "pssphI", "StartRoutine", lpStartAddress, "ModuleName", module_name, "Name", function_name, "Parameter", lpParameter, "CreationFlags", dwCreationFlags, "ThreadId", lpThreadId); + else if (module_name) LOQ_nonnull("threading", "psphI", "StartRoutine", lpStartAddress, "ModuleName", module_name, "Parameter", lpParameter, "CreationFlags", dwCreationFlags, "ThreadId", lpThreadId); else LOQ_nonnull("threading", "pphI", "StartRoutine", lpStartAddress, "Parameter", lpParameter, "CreationFlags", dwCreationFlags, "ThreadId", lpThreadId); } else - LOQ_nonnull("threading", "pph", "StartRoutine", lpStartAddress, "Parameter", lpParameter, - "CreationFlags", dwCreationFlags); + if (function_name && module_name) + LOQ_nonnull("threading", "pssph", "StartRoutine", lpStartAddress, "ModuleName", module_name, "Name", function_name, "Parameter", lpParameter, "CreationFlags", dwCreationFlags); + else if (module_name) + LOQ_nonnull("threading", "psph", "StartRoutine", lpStartAddress, "ModuleName", module_name, "Parameter", lpParameter, "CreationFlags", dwCreationFlags); + else + LOQ_nonnull("threading", "pph", "StartRoutine", lpStartAddress, "Parameter", lpParameter, "CreationFlags", dwCreationFlags); if (module_name) free(module_name); From 6498c3de1f84d881b18490b711111cb8709aa4f8 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 28 Oct 2025 22:14:20 +0000 Subject: [PATCH 115/148] Harden our_stackwalk() against invalid stack pointers (hooking_64) --- hooking_64.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hooking_64.c b/hooking_64.c index 3d99a00b..b7b7a435 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -1224,6 +1224,8 @@ static int our_stackwalk(ULONG_PTR _rip, ULONG_PTR sp, PVOID *backtrace, unsigne runfunc = RtlLookupFunctionEntry(ctx.Rip, &imgbase, NULL); // needs LdrpInvertedFunctionTableSRWLock on Win10 memset(&nvctx, 0, sizeof(nvctx)); if (runfunc == NULL) { + if (our_isbadreadptr((PVOID)ctx.Rsp, sizeof(PVOID))) + break; ctx.Rip = (ULONG_PTR)(*(ULONG_PTR *)ctx.Rsp); ctx.Rsp += 8; } From 92461869b783c2267039d7096801dc5e232d57af Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 28 Oct 2025 22:21:21 +0000 Subject: [PATCH 116/148] Tweak KiUserExceptionDispatcher variable name to remove underscore --- CAPE/Debugger.c | 9 +++++---- CAPE/Trace.c | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index e607ecb3..b2fd9f7f 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -59,7 +59,7 @@ extern PVOID GuardedPages; struct ThreadBreakpoints *MainThreadBreakpointList; unsigned int TrapIndex, DepthCount; -PVOID _KiUserExceptionDispatcher; +PVOID KiUserExceptionDispatcher; BOOL SetSingleStepMode(PCONTEXT Context, PVOID Handler), ClearSingleStepMode(PCONTEXT Context); lookup_t SoftBPs, SyscallBPs; SOFTBP SyscallBP; @@ -2675,16 +2675,17 @@ BOOL InitialiseDebugger(void) return FALSE; } + HMODULE ntdll = GetModuleHandle("ntdll"); // Store address of KiUserExceptionDispatcher - _KiUserExceptionDispatcher = GetProcAddress(GetModuleHandle("ntdll"), "KiUserExceptionDispatcher"); + KiUserExceptionDispatcher = (PDWORD)GetProcAddress(ntdll, "KiUserExceptionDispatcher"); - if (_KiUserExceptionDispatcher == NULL) + if (KiUserExceptionDispatcher == NULL) { DebugOutput("InitialiseDebugger error: could not resolve ntdll::KiUserExceptionDispatcher.\n"); return FALSE; } #ifdef DEBUG_COMMENTS - else DebugOutput("InitialiseDebugger: ntdll::KiUserExceptionDispatcher = 0x%p\n", _KiUserExceptionDispatcher); + else DebugOutput("InitialiseDebugger: ntdll::KiUserExceptionDispatcher = 0x%p\n", KiUserExceptionDispatcher); #endif // Initialise global variables diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 76accf36..22ccb39e 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -50,7 +50,7 @@ extern BOOL inside_hook(LPVOID Address); extern void loq(int index, const char *category, const char *name, int is_success, ULONG_PTR return_value, const char *fmt, ...); extern void log_flush(); -extern PVOID _KiUserExceptionDispatcher; +extern PVOID KiUserExceptionDispatcher; extern lookup_t SoftBPs, SyscallBPs; char *ModuleName, *PreviousModuleName; @@ -2178,7 +2178,7 @@ BOOL Trace(struct _EXCEPTION_POINTERS* ExceptionInfo) ModuleName = convert_address_to_dll_name_and_offset((ULONG_PTR)CIP, &DllRVA); if (ModuleName) { - if (CIP == (PVOID)((PCHAR)_KiUserExceptionDispatcher+1)) + if (CIP == (PVOID)((PCHAR)KiUserExceptionDispatcher+1)) { DebugOutput("Trace: Stepping out of KiUserExceptionDispatcher\n"); ForceStepOver = TRUE; From ad0557e3d0aeb476bb39c0a9ae2268e8dbca7aee Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 28 Oct 2025 22:25:30 +0000 Subject: [PATCH 117/148] Fix/improve exception handling code which queries current SEH handler --- capemon.c | 9 ++++++--- hook_process.c | 17 ++++++++++------- ntapi.h | 16 +++++++++++++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/capemon.c b/capemon.c index 3f0f7f01..d2b0c8ec 100644 --- a/capemon.c +++ b/capemon.c @@ -336,9 +336,12 @@ LONG WINAPI capemon_exception_handler(__in struct _EXCEPTION_POINTERS *Exception stack = (ULONG_PTR *)(ULONG_PTR)(ExceptionInfo->ContextRecord->Esp); ebp_or_rip = (ULONG_PTR)(ExceptionInfo->ContextRecord->Ebp); { - DWORD *tebtmp = (DWORD *)NtCurrentTeb(); - if (tebtmp[0] != 0xffffffff) - seh = ((DWORD *)tebtmp[0])[1]; + PTEB teb = NtCurrentTeb(); + if (teb) { + PEXCEPTION_REGISTRATION_RECORD sehrecord = teb->NtTib.ExceptionList; + if (sehrecord && sehrecord != EXCEPTION_CHAIN_END) + seh = (ULONG_PTR)sehrecord->Handler; + } } #endif diff --git a/hook_process.c b/hook_process.c index 3efe80bb..9b966678 100644 --- a/hook_process.c +++ b/hook_process.c @@ -1375,13 +1375,16 @@ HOOKDEF_NOTAIL(WINAPI, RtlDispatchException, if (ExceptionRecord && (ULONG_PTR)ExceptionRecord->ExceptionAddress >= g_our_dll_base && (ULONG_PTR)ExceptionRecord->ExceptionAddress < (g_our_dll_base + g_our_dll_size)) { if (!(g_config.debugger && ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)) { char buf[160]; - ULONG_PTR seh = 0; - DWORD *tebtmp = (DWORD *)NtCurrentTeb(); - if (tebtmp[0] != 0xffffffff) - seh = ((DWORD*)(DWORD_PTR)tebtmp[0])[1]; - if (seh < g_our_dll_base || seh >= (g_our_dll_base + g_our_dll_size)) { - _snprintf(buf, sizeof(buf), "Exception 0x%x reported at offset 0x%x in capemon itself while accessing 0x%p from hook %s", ExceptionRecord->ExceptionCode, (DWORD)((ULONG_PTR)ExceptionRecord->ExceptionAddress - g_our_dll_base), (PVOID)ExceptionRecord->ExceptionInformation[1], hook_info()->current_hook ? hook_info()->current_hook->funcname : "unknown"); - log_anomaly("capemon crash", buf); + PTEB Teb = NtCurrentTeb(); + if (Teb) { + PEXCEPTION_REGISTRATION_RECORD SehRecord = Teb->NtTib.ExceptionList; + if (SehRecord && SehRecord != EXCEPTION_CHAIN_END) { + ULONG_PTR HandlerAddress = (ULONG_PTR)SehRecord->Handler; + if (HandlerAddress < g_our_dll_base || HandlerAddress >= (g_our_dll_base + g_our_dll_size)) { + _snprintf(buf, sizeof(buf), "Exception 0x%x reported at offset 0x%x in capemon itself while accessing 0x%p from hook %s", ExceptionRecord->ExceptionCode, (DWORD)((ULONG_PTR)ExceptionRecord->ExceptionAddress - g_our_dll_base), (PVOID)ExceptionRecord->ExceptionInformation[1], hook_info()->current_hook ? hook_info()->current_hook->funcname : "unknown"); + log_anomaly("capemon crash", buf); + } + } } } } diff --git a/ntapi.h b/ntapi.h index e1efda16..dabc8561 100644 --- a/ntapi.h +++ b/ntapi.h @@ -30,6 +30,8 @@ along with this program. If not, see . #define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define EXCEPTION_CHAIN_END ((PEXCEPTION_REGISTRATION_RECORD)-1) + #ifndef _MSC_VER #define __out #define __in @@ -620,7 +622,7 @@ typedef struct _PEB { PVOID PostProcessInitRoutine; BYTE Reserved4[136]; ULONG SessionId; -} PEB; +} PEB, *PPEB; #else typedef struct _PEB { BOOLEAN InheritedAddressSpace; @@ -680,6 +682,18 @@ typedef struct _PEB { } PEB, *PPEB; #endif +typedef struct _TEB +{ + NT_TIB NtTib; + PVOID EnvironmentPointer; + CLIENT_ID ClientId; + PVOID ActiveRpcHandle; + PVOID ThreadLocalStoragePointer; + PPEB ProcessEnvironmentBlock; + ULONG LastErrorValue; +// truncated +} TEB, *PTEB; + typedef enum _DBG_STATE { DbgIdle, From d9bdb295652bc178cc0958d018005a8c1b9109ac Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 28 Oct 2025 22:34:57 +0000 Subject: [PATCH 118/148] Fix KiUserExceptionDispatcher pointer type --- CAPE/Debugger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index b2fd9f7f..ffe67aca 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -2675,9 +2675,9 @@ BOOL InitialiseDebugger(void) return FALSE; } - HMODULE ntdll = GetModuleHandle("ntdll"); // Store address of KiUserExceptionDispatcher - KiUserExceptionDispatcher = (PDWORD)GetProcAddress(ntdll, "KiUserExceptionDispatcher"); + HMODULE ntdll = GetModuleHandle("ntdll"); + KiUserExceptionDispatcher = GetProcAddress(ntdll, "KiUserExceptionDispatcher"); if (KiUserExceptionDispatcher == NULL) { From 15cd125dd4507950694d6b5b6909edc22cb76b9b Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 6 Nov 2025 13:44:10 +0000 Subject: [PATCH 119/148] path_from_object_attributes(): fix issue with memcpy from bad ObjectName->Buffer (e.g. 0a9d9b402fb39cf8df21ca4e68b84577c39b3ecf00415c999b28fcc92a695663) --- misc.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/misc.c b/misc.c index f9a97379..1f3ed440 100644 --- a/misc.c +++ b/misc.c @@ -1125,20 +1125,24 @@ uint32_t path_from_handle(HANDLE handle, return length; } -uint32_t path_from_object_attributes(const OBJECT_ATTRIBUTES *obj, - wchar_t *path, uint32_t buffer_length) +uint32_t path_from_object_attributes(const OBJECT_ATTRIBUTES *obj, wchar_t *path, uint32_t buffer_length) { uint32_t copylen, obj_length, length; - if (obj->ObjectName == NULL || obj->ObjectName->Buffer == NULL) { - return path_from_handle(obj->RootDirectory, path, buffer_length);; - } + if (obj == NULL) + return 0; + + if (obj->ObjectName == NULL || obj->ObjectName->Buffer == NULL) + return path_from_handle(obj->RootDirectory, path, buffer_length); // ObjectName->Length is actually the size in bytes. obj_length = obj->ObjectName->Length / sizeof(wchar_t); copylen = min(obj_length, buffer_length - 1); + if (our_isbadreadptr(obj->ObjectName->Buffer, copylen * sizeof(wchar_t))) + return 0; + if (obj->RootDirectory == NULL) { memcpy(path, obj->ObjectName->Buffer, copylen * sizeof(wchar_t)); path[copylen] = L'\0'; From 6703aff0e736e3cd52f28600986a60006c6e0de9 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 7 Nov 2025 10:15:53 +0000 Subject: [PATCH 120/148] hook_create_pre_tramp_notail(): Improve assembly comments (hooking_64) --- hooking_64.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hooking_64.c b/hooking_64.c index b7b7a435..9cb7ab13 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -587,7 +587,7 @@ static void hook_create_pre_tramp_notail(hook_t *h) unsigned char pre_tramp2[] = { // test eax, eax 0x85, 0xc0, - // jnz 0x21 + // jnz 0x21 -> pre_tramp3_nostack 0x75, 0x21, // add rsp, 0x20 0x48, 0x83, 0xc4, 0x20, @@ -666,7 +666,7 @@ static void hook_create_pre_tramp_notail(hook_t *h) unsigned char pre_tramp4_nostack[] = { // test eax, eax 0x85, 0xc0, - // jnz 0x21 + // jnz 0x21 -> pre_tramp4_stack 0x75, 0x21, // add rsp, 0x20 (from pre_tramp12) 0x48, 0x83, 0xc4, 0x20, @@ -686,7 +686,7 @@ static void hook_create_pre_tramp_notail(hook_t *h) unsigned char pre_tramp4_stack[] = { // test eax, eax 0x85, 0xc0, - // jnz 0x3c + // jnz 0x3c -> pre_tramp5_nostack 0x75, 0x3c, // mov eax, numargs 0xb8, h->numargs, 0x00, 0x00, 0x00, From 4d5dbd1e8f22662746dbe9db3418165ff86fcf12 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 7 Nov 2025 10:16:58 +0000 Subject: [PATCH 121/148] CAPEExceptionFilter: refine anomaly logging (Debugger) --- CAPE/Debugger.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index ffe67aca..8d2d3a2b 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -613,25 +613,29 @@ LONG WINAPI CAPEExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) if (bp == 0 && ((DWORD_PTR)pBreakpointInfo->Address != ExceptionInfo->ContextRecord->Dr0)) { - DebugOutput("CAPEExceptionFilter: Anomaly detected! bp0 address (0x%p) different to BreakpointInfo (0x%x)!\n", ExceptionInfo->ContextRecord->Dr0, pBreakpointInfo->Address); + if (pBreakpointInfo->Address) + DebugOutput("CAPEExceptionFilter: Anomaly detected! bp0 address 0x%p different to internal breakpoint 0x%p\n", ExceptionInfo->ContextRecord->Dr0, pBreakpointInfo->Address); return EXCEPTION_CONTINUE_SEARCH; } if (bp == 1 && ((DWORD_PTR)pBreakpointInfo->Address != ExceptionInfo->ContextRecord->Dr1)) { - DebugOutput("CAPEExceptionFilter: Anomaly detected! bp1 address (0x%p) different to BreakpointInfo (0x%x)!\n", ExceptionInfo->ContextRecord->Dr1, pBreakpointInfo->Address); + if (pBreakpointInfo->Address) + DebugOutput("CAPEExceptionFilter: Anomaly detected! bp1 address 0x%p different to internal breakpoint 0x%p\n", ExceptionInfo->ContextRecord->Dr1, pBreakpointInfo->Address); return EXCEPTION_CONTINUE_SEARCH; } if (bp == 2 && ((DWORD_PTR)pBreakpointInfo->Address != ExceptionInfo->ContextRecord->Dr2)) { - DebugOutput("CAPEExceptionFilter: Anomaly detected! bp2 address (0x%p) different to BreakpointInfo (0x%x)!\n", ExceptionInfo->ContextRecord->Dr2, pBreakpointInfo->Address); + if (pBreakpointInfo->Address) + DebugOutput("CAPEExceptionFilter: Anomaly detected! bp2 address 0x%p different to internal breakpoint 0x%p\n", ExceptionInfo->ContextRecord->Dr2, pBreakpointInfo->Address); return EXCEPTION_CONTINUE_SEARCH; } if (bp == 3 && ((DWORD_PTR)pBreakpointInfo->Address != ExceptionInfo->ContextRecord->Dr3)) { - DebugOutput("CAPEExceptionFilter: Anomaly detected! bp3 address (0x%p) different to BreakpointInfo (0x%x)!\n", ExceptionInfo->ContextRecord->Dr3, pBreakpointInfo->Address); + if (pBreakpointInfo->Address) + DebugOutput("CAPEExceptionFilter: Anomaly detected! bp3 address 0x%p different to internal breakpoint 0x%p\n", ExceptionInfo->ContextRecord->Dr3, pBreakpointInfo->Address); return EXCEPTION_CONTINUE_SEARCH; } From d408eea4eaecb20e6710ed8e1612b3439c329e09 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 7 Nov 2025 10:17:48 +0000 Subject: [PATCH 122/148] Trace: remove obsolete global variables --- CAPE/Trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 22ccb39e..17d7c4f3 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -54,7 +54,7 @@ extern PVOID KiUserExceptionDispatcher; extern lookup_t SoftBPs, SyscallBPs; char *ModuleName, *PreviousModuleName; -PVOID ModuleBase, DumpAddress, ReturnAddress, BreakOnReturnAddress, BreakOnNtContinueCallback, PreviousJumps[4], GuardedPages; +PVOID ModuleBase, DumpAddress, ReturnAddress, BreakOnReturnAddress, PreviousJumps[4], GuardedPages; BOOL BreakpointsSet, BreakpointsHit, FilterTrace, StopTrace, ReDisassemble, SyscallBreakpointSet, TraceRunning, BreakOnNtContinue; unsigned int Correction, StepCount, StepLimit, TraceDepthLimit, BreakOnReturnRegister, JumpCount; char Action0[MAX_PATH], Action1[MAX_PATH], Action2[MAX_PATH], Action3[MAX_PATH]; From e1ba2ff3d93e2925938e514fbbca0a61bad6ea1a Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 17 Nov 2025 11:55:23 +0000 Subject: [PATCH 123/148] Add config option for monitor injection into supplied pid or "explorer" for shell: monitor= --- config.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config.c b/config.c index 4a78d4e4..3a4dbd75 100644 --- a/config.c +++ b/config.c @@ -41,6 +41,7 @@ along with this program. If not, see . #define PrintEAX 3 extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); +extern void ProcessMessage(DWORD ProcessId, DWORD ThreadId); extern char *our_dll_path; extern char *our_process_name; extern wchar_t *our_process_path_w; @@ -1388,6 +1389,15 @@ void parse_config_line(char* line) if (g_config.hook_watch) DebugOutput("Config: Hook watch enabled.\n"); } + else if (!stricmp(key, "monitor")) { + DWORD pid = (unsigned int)strtoul(value, NULL, 10); + if (!pid && !stricmp(value, "explorer")) + GetWindowThreadProcessId(GetShellWindow(), &pid); + if (pid) { + ProcessMessage(pid, 0); + DebugOutput("Config: Injected monitor into pid %d.\n", pid); + } + } else if (stricmp(key, "no-iat")) DebugOutput("Monitor config - unrecognised key %s.\n", key); From 1de5cf83f12816f1f9993e7eea127b1cdfe800d2 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 21 Nov 2025 14:55:36 +0000 Subject: [PATCH 124/148] Trace: fix issue with depth count being incremented when stepping over calls to named functions --- CAPE/Trace.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 17d7c4f3..1edf35ba 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -55,7 +55,7 @@ extern lookup_t SoftBPs, SyscallBPs; char *ModuleName, *PreviousModuleName; PVOID ModuleBase, DumpAddress, ReturnAddress, BreakOnReturnAddress, PreviousJumps[4], GuardedPages; -BOOL BreakpointsSet, BreakpointsHit, FilterTrace, StopTrace, ReDisassemble, SyscallBreakpointSet, TraceRunning, BreakOnNtContinue; +BOOL BreakpointsSet, BreakpointsHit, FilterTrace, StopTrace, ReDisassemble, SyscallBreakpointSet, TraceRunning; unsigned int Correction, StepCount, StepLimit, TraceDepthLimit, BreakOnReturnRegister, JumpCount; char Action0[MAX_PATH], Action1[MAX_PATH], Action2[MAX_PATH], Action3[MAX_PATH]; char *Instruction0, *Instruction1, *Instruction2, *Instruction3, *procname0; @@ -1890,7 +1890,8 @@ void InstructionHandler(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst if (ReturnAddress && (unsigned int)abs(TraceDepthCount) >= TraceDepthLimit) *StepOver = TRUE; - else + + if (*StepOver == FALSE && *ForceStepOver == FALSE) TraceDepthCount++; } else if (!strcmp(DecodedInstruction.mnemonic.p, "JMP")) From bf3b0dc20fa0eb4971e78f8b0b5ac53753ddeb61 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 24 Nov 2025 14:00:48 +0000 Subject: [PATCH 125/148] Replace BreakOnNtContinue as global variable (not extern) --- CAPE/Debugger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index 8d2d3a2b..1eda6b29 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -50,7 +50,7 @@ extern BOOL SetInitialBreakpoints(PVOID ImageBase), Trace(struct _EXCEPTION_POIN extern BOOL BreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS* ExceptionInfo); extern BOOL GuardPageCallback(struct _EXCEPTION_POINTERS* ExceptionInfo); extern void DebuggerOutput(_In_ LPCTSTR lpOutputString, ...), DoTraceOutput(PVOID Address); -extern BOOL TraceRunning, BreakpointsSet, BreakpointsHit, StopTrace, BreakOnNtContinue, SyscallBreakpointSet; +extern BOOL TraceRunning, BreakpointsSet, BreakpointsHit, StopTrace, SyscallBreakpointSet; extern PVECTORED_EXCEPTION_HANDLER SampleVectoredHandler; extern int StepOverRegister; extern int process_shutting_down; @@ -60,7 +60,7 @@ extern PVOID GuardedPages; struct ThreadBreakpoints *MainThreadBreakpointList; unsigned int TrapIndex, DepthCount; PVOID KiUserExceptionDispatcher; -BOOL SetSingleStepMode(PCONTEXT Context, PVOID Handler), ClearSingleStepMode(PCONTEXT Context); +BOOL BreakOnNtContinue, SetSingleStepMode(PCONTEXT Context, PVOID Handler), ClearSingleStepMode(PCONTEXT Context); lookup_t SoftBPs, SyscallBPs; SOFTBP SyscallBP; From 0895638e87dd6d06110945029495086aa2a957c2 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Mon, 24 Nov 2025 14:01:28 +0000 Subject: [PATCH 126/148] Fix issue with RESUME: monitor message from NtResumeProcess hook --- hook_process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hook_process.c b/hook_process.c index 9b966678..d5f47800 100644 --- a/hook_process.c +++ b/hook_process.c @@ -569,7 +569,7 @@ HOOKDEF(NTSTATUS, WINAPI, NtResumeProcess, DWORD pid = pid_from_process_handle(ProcessHandle); if (g_config.injection) ResumeProcessHandler(ProcessHandle, pid); - pipe("RESUME:%d", pid); + pipe("RESUME:%d,0", pid); ret = Old_NtResumeProcess(ProcessHandle); LOQ_ntstatus("process", "pl", "ProcessHandle", ProcessHandle, "ProcessId", pid); return ret; From b362bd6e78b8cd29cf3fea0f31188377fffe79c9 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 28 Nov 2025 16:08:56 +0000 Subject: [PATCH 127/148] Setbp debugger actions: don't attempt to detect RVA and add imagebase --- CAPE/Trace.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 1edf35ba..4f580b36 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -1686,8 +1686,6 @@ void ActionDispatcher(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst De PVOID Base = GetAllocationBase(CIP); if (Target) { - if ((PUCHAR)Target < (PUCHAR)Base) - Target = (PVOID)((PUCHAR)Target + (DWORD_PTR)Base); ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, 0, 0, Target, BP_EXEC, 0, BreakpointCallback); DebuggerOutput("SetBp0: Breakpoint 0 set to 0x%p.\n", Target); } @@ -1699,8 +1697,6 @@ void ActionDispatcher(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst De PVOID Base = GetAllocationBase(CIP); if (Target) { - if ((PUCHAR)Target < (PUCHAR)Base) - Target = (PVOID)((PUCHAR)Target + (DWORD_PTR)Base); ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, 1, 0, Target, BP_EXEC, 0, BreakpointCallback); DebuggerOutput("SetBp1: Breakpoint 1 set to 0x%p.\n", Target); } @@ -1712,8 +1708,6 @@ void ActionDispatcher(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst De PVOID Base = GetAllocationBase(CIP); if (Target) { - if ((PUCHAR)Target < (PUCHAR)Base) - Target = (PVOID)((PUCHAR)Target + (DWORD_PTR)Base); ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, 2, 0, Target, BP_EXEC, 0, BreakpointCallback); DebuggerOutput("SetBp2: Breakpoint 2 set to 0x%p.\n", Target); } @@ -1725,8 +1719,6 @@ void ActionDispatcher(struct _EXCEPTION_POINTERS* ExceptionInfo, _DecodedInst De PVOID Base = GetAllocationBase(CIP); if (Target) { - if ((PUCHAR)Target < (PUCHAR)Base) - Target = (PVOID)((PUCHAR)Target + (DWORD_PTR)Base); ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, 3, 0, Target, BP_EXEC, 0, BreakpointCallback); DebuggerOutput("SetBp3: Breakpoint 3 set to 0x%p.\n", Target); } From 46b2bddaf6ebb0a236a81364a76a2dd16e7164ec Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 2 Dec 2025 17:01:40 +0000 Subject: [PATCH 128/148] ScyllaDumpProcess: fix entry point value in logs (RVA->VA) --- CAPE/ScyllaHarness.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAPE/ScyllaHarness.cpp b/CAPE/ScyllaHarness.cpp index dc7ad568..c2ee8096 100644 --- a/CAPE/ScyllaHarness.cpp +++ b/CAPE/ScyllaHarness.cpp @@ -179,8 +179,8 @@ extern "C" int ScyllaDumpProcess(HANDLE hProcess, DWORD_PTR ModuleBase, DWORD_PT } else { - DebugOutput("DumpProcess: Module entry point VA is 0x%p.\n", entrypoint); entrypoint = entrypoint + (DWORD_PTR)ModuleBase; + DebugOutput("DumpProcess: Module entry point VA is 0x%p.\n", entrypoint); } if (!FixImports) From 08d2218e94c91375c25e0b27ef2d226dc30d755a Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Wed, 3 Dec 2025 14:06:48 +0000 Subject: [PATCH 129/148] YaraHarness: prioritise internal sigs & capemon canary to load/scan first, complete scan on canary detection --- CAPE/YaraHarness.c | 51 ++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/CAPE/YaraHarness.c b/CAPE/YaraHarness.c index b0552cb7..e5d2935f 100644 --- a/CAPE/YaraHarness.c +++ b/CAPE/YaraHarness.c @@ -44,6 +44,25 @@ extern PVOID LdrpInvertedFunctionTableSRWLock; static char NewLine[MAX_PATH]; char InternalYara[] = + "rule capemon" + "{strings:$hash = {d3 b9 46 1d 9a 14 bc 44 a1 61 c3 47 6a 0e 35 90 00 2c 28 81 dc a0 36 dc 2c 92 0c 7c b6 84 39 59}" + "condition:all of them}" +#ifdef _WIN64 + "rule vDbgPrintExWithPrefixInternal" + "{strings:$10_0_26100_3476 = {48 8B C4 48 89 58 08 48 89 68 10 48 89 70 18 48 89 78 20 41 54 41 56 41 57 48 83 EC 40 44 8A BC 24}" + "$function = {40 55 53 56 41 54 41 55 41 56 41 57 48 81 EC 20 01 00 00 48 8D 6C 24 20 48 8B 05 [4] 48 33 C5 48 89 85 ?? 00 00 00 4C 89 4D ?? 44 89 45 ?? 44 8B E2 89 55 ?? 48 8B D1 48 89 4D ?? 48 8B 85}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule FindFixAndRun" + "{strings:$function = {48 89 5C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 81 EC [80-110] 85 C0 0F 88 [2] 00 00 49 8B 4? 70 66 83 (78|79) 02 3A 0F 84 [2] 00 00 48 8D 54 24 20 49 8B CE E8}" + "condition:uint16(0) == 0x5a4d and any of them}" +#else + "rule vDbgPrintExWithPrefixInternal" + "{strings:$function = {68 90 00 00 00 68 [4] E8 [4] 89 95 [4] 89 8D [4] 8B 45 ?? 89 85 [4] 8B 45 ?? 89 85 [4] 64 A1 18 00 00 00 89 45 ?? 83 FA FF 0F 84}" + "condition:uint16(0) == 0x5a4d and any of them}" + "rule FindFixAndRun" + "{strings:$function = {8B FF 55 8B EC 6A FE 68 [4] 68 [4] 64 A1 00 00 00 00 50 81 EC [90-96] 00 0F 84 [4] B8 E7 7F 00 00 50 8D 8D [4] E8 [4] 85 C0 0F 88 [4] 8B 4? 38 66 83 7? 02 3A 0F 84}" + "condition:uint16(0) == 0x5a4d and any of them}" +#endif "rule RtlInsertInvertedFunctionTable" "{strings:$10_0_26100_3476 = {48 8D 0D [4] 49 F7 D8 48 8B F8 1B DB 23 5C 24 ?? E8 [4] 33 C9 E8}" "$10_0_19041_662 = {48 8D 0D [4] E8 [4] [7] 8B 44 24 ?? 44 8B CB 4C 8B 44 24 ?? 48 8B D7 89 44 24 ?? E8}" @@ -71,26 +90,7 @@ char InternalYara[] = "condition:uint16(0) == 0x5a4d and any of them}" "rule WMI_GetObjectAsync" "{strings:$function = {48 8B C4 56 57 41 54 41 56 41 57 48 83 EC 40 48 C7 40 C8 FE FF FF FF 48 89 58 10 48 89 68 18 4D 8B F9 45 8B E0 48 8B EA 48 8B F1 48 8B 41 08 48 83 78 20 00 75 0A B8 08 01 01 80 E9}" - "condition:uint16(0) == 0x5a4d and any of them}" -#ifdef _WIN64 - "rule vDbgPrintExWithPrefixInternal" - "{strings:$10_0_26100_3476 = {48 8B C4 48 89 58 08 48 89 68 10 48 89 70 18 48 89 78 20 41 54 41 56 41 57 48 83 EC 40 44 8A BC 24}" - "$function = {40 55 53 56 41 54 41 55 41 56 41 57 48 81 EC 20 01 00 00 48 8D 6C 24 20 48 8B 05 [4] 48 33 C5 48 89 85 ?? 00 00 00 4C 89 4D ?? 44 89 45 ?? 44 8B E2 89 55 ?? 48 8B D1 48 89 4D ?? 48 8B 85}" - "condition:uint16(0) == 0x5a4d and any of them}" - "rule FindFixAndRun" - "{strings:$function = {48 89 5C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 81 EC [80-110] 85 C0 0F 88 [2] 00 00 49 8B 4? 70 66 83 (78|79) 02 3A 0F 84 [2] 00 00 48 8D 54 24 20 49 8B CE E8}" - "condition:uint16(0) == 0x5a4d and any of them}" -#else - "rule vDbgPrintExWithPrefixInternal" - "{strings:$function = {68 90 00 00 00 68 [4] E8 [4] 89 95 [4] 89 8D [4] 8B 45 ?? 89 85 [4] 8B 45 ?? 89 85 [4] 64 A1 18 00 00 00 89 45 ?? 83 FA FF 0F 84}" - "condition:uint16(0) == 0x5a4d and any of them}" - "rule FindFixAndRun" - "{strings:$function = {8B FF 55 8B EC 6A FE 68 [4] 68 [4] 64 A1 00 00 00 00 50 81 EC [90-96] 00 0F 84 [4] B8 E7 7F 00 00 50 8D 8D [4] E8 [4] 85 C0 0F 88 [4] 8B 4? 38 66 83 7? 02 3A 0F 84}" - "condition:uint16(0) == 0x5a4d and any of them}" -#endif - "rule capemon" - "{strings:$hash = {d3 b9 46 1d 9a 14 bc 44 a1 61 c3 47 6a 0e 35 90 00 2c 28 81 dc a0 36 dc 2c 92 0c 7c b6 84 39 59}" - "condition:all of them}"; + "condition:uint16(0) == 0x5a4d and any of them}"; void ScannerError(int Error) { @@ -238,6 +238,9 @@ int YaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void YR_META* Meta; YR_RULE* Rule = (YR_RULE*)message_data; + if (!strcmp(Rule->identifier, "capemon")) + return CALLBACK_CONTINUE; + DebugOutput("YaraScan hit: %s\n", Rule->identifier); // Process cape_options metadata @@ -573,6 +576,10 @@ BOOL YaraInit() goto exit; } + // Add 'internal' yara first + if (yr_compiler_add_string(Compiler, InternalYara, NULL) != 0) + DebugOutput("YaraInit: Failed to add internal yara rules.\n", compiled_rules); + if (g_config.yarascan) { char FindString[MAX_PATH]; @@ -626,10 +633,6 @@ BOOL YaraInit() DebugOutput("YaraInit: Found no Yara rules in %s\n", yara_dir); } - // Add 'internal' yara - if (yr_compiler_add_string(Compiler, InternalYara, NULL) != 0) - DebugOutput("YaraInit: Failed to add internal yara rules.\n", compiled_rules); - Result = yr_compiler_get_rules(Compiler, &Rules); if (Result != ERROR_SUCCESS) From 6703c5559988aa8d8b489397a4a2ad3b599a87ff Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 4 Dec 2025 16:42:46 +0000 Subject: [PATCH 130/148] Debugger: simplify ClearAllBreakpoints() --- CAPE/Debugger.c | 55 ++----------------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/CAPE/Debugger.c b/CAPE/Debugger.c index 1eda6b29..acfd0178 100644 --- a/CAPE/Debugger.c +++ b/CAPE/Debugger.c @@ -1218,59 +1218,8 @@ BOOL ContextClearAllBreakpoints(PCONTEXT Context) BOOL ClearAllBreakpoints() //************************************************************************************** { - CONTEXT Context; - PTHREADBREAKPOINTS ThreadBreakpoints; - unsigned int Register; - - ThreadBreakpoints = MainThreadBreakpointList; - - while (ThreadBreakpoints) - { - if (!ThreadBreakpoints->ThreadId) - { - DebugOutput("ClearAllBreakpoints: Error: no thread id for thread breakpoints 0x%x.\n", ThreadBreakpoints); - return FALSE; - } - - if (!ThreadBreakpoints->ThreadHandle) - { - DebugOutput("ClearAllBreakpoints: Error no thread handle for thread %d.\n", ThreadBreakpoints->ThreadId); - return FALSE; - } - - for (Register = 0; Register < NUMBER_OF_DEBUG_REGISTERS; Register++) - { - ThreadBreakpoints->BreakpointInfo[Register].Size = 0; - ThreadBreakpoints->BreakpointInfo[Register].Address = NULL; - ThreadBreakpoints->BreakpointInfo[Register].Type = 0; - ThreadBreakpoints->BreakpointInfo[Register].HitCount = 0; - ThreadBreakpoints->BreakpointInfo[Register].Callback = NULL; - } - - memset(&Context, 0, sizeof(CONTEXT)); - Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; - - Context.Dr0 = 0; - Context.Dr1 = 0; - Context.Dr2 = 0; - Context.Dr3 = 0; - Context.Dr6 = 0; - Context.Dr7 = 0; - - if (!SetThreadContext(ThreadBreakpoints->ThreadHandle, &Context)) - { -#ifdef DEBUG_COMMENTS - DebugOutput("ClearAllBreakpoints: Error setting thread context (thread %d).\n", ThreadBreakpoints->ThreadId); -#endif - return FALSE; - } -#ifdef DEBUG_COMMENTS - else - DebugOutput("ClearAllBreakpoints: Cleared breakpoints for thread %d (handle 0x%x).\n", ThreadBreakpoints->ThreadId, ThreadBreakpoints->ThreadHandle); -#endif - - ThreadBreakpoints = ThreadBreakpoints->NextThreadBreakpoints; - } + for (unsigned int Register = 0; Register < NUMBER_OF_DEBUG_REGISTERS; Register++) + ClearBreakpoint(Register); ClearSoftwareBreakpoints(); From 1b72bb07d28de59332a1ee44f32ead84a1584d84 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Thu, 4 Dec 2025 20:38:14 +0000 Subject: [PATCH 131/148] RtlWow64SetThreadContext hook, Wow64 get/set handlers --- CAPE/Injection.c | 106 ++++++++++++++++++++++++++++++++++++++++ CAPE/Injection.h | 8 +++ hook_thread.c | 125 ++++++++++++++++++++++++++++++++++++++++------- hooks.c | 20 +++++++- hooks.h | 13 +++-- 5 files changed, 251 insertions(+), 21 deletions(-) diff --git a/CAPE/Injection.c b/CAPE/Injection.c index 9534f7f0..050ff6b0 100644 --- a/CAPE/Injection.c +++ b/CAPE/Injection.c @@ -645,6 +645,112 @@ __declspec(noinline) void SetThreadContextHandler(HANDLE ThreadHandle, CONTEXT * #endif } +#ifdef _WIN64 +__declspec(noinline) void Wow64GetThreadContextHandler(HANDLE ThreadHandle, PWOW64_CONTEXT Context) +{ + DWORD Pid = pid_from_thread_handle(ThreadHandle); + DWORD Tid = tid_from_thread_handle(ThreadHandle); + + if (Context && Context->ContextFlags & CONTEXT_CONTROL) + { + struct InjectionInfo *CurrentInjectionInfo = GetInjectionInfo(Pid); + if (CurrentInjectionInfo && CurrentInjectionInfo->ProcessId == Pid) + CurrentInjectionInfo->StackPointer = (PVOID)(DWORD_PTR)Context->Esp; + } + + if (g_config.debugger && Pid == GetCurrentProcessId()) + { + PTHREADBREAKPOINTS ThreadBreakpoints = GetThreadBreakpoints(Tid); + if (ThreadBreakpoints) + { + if (ThreadBreakpoints->BreakpointInfo[0].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[0].Address == Context->Dr0) + { + Context->Dr0 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } + if (ThreadBreakpoints->BreakpointInfo[1].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[1].Address == Context->Dr1) + { + Context->Dr1 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } + if (ThreadBreakpoints->BreakpointInfo[2].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[2].Address == Context->Dr2) + { + Context->Dr2 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } + if (ThreadBreakpoints->BreakpointInfo[3].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[3].Address == Context->Dr3) + { + Context->Dr3 = 0; + Context->Dr6 = 0; + Context->Dr7 = 0; + } + } + } +} + +__declspec(noinline) void Wow64SetThreadContextHandler(HANDLE ThreadHandle, PWOW64_CONTEXT Context) +{ + if (!Context || !(Context->ContextFlags & CONTEXT_CONTROL)) + return; + + DWORD Pid = pid_from_thread_handle(ThreadHandle); + DWORD Tid = tid_from_thread_handle(ThreadHandle); + + if (Pid != GetCurrentProcessId()) + ProcessMessage(Pid, 0); + + if (g_config.debugger && Pid == GetCurrentProcessId()) + { + PTHREADBREAKPOINTS ThreadBreakpoints = GetThreadBreakpoints(Tid); + if (ThreadBreakpoints) + { + if + ( + (ThreadBreakpoints->BreakpointInfo[0].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[0].Address != Context->Dr0) || + (ThreadBreakpoints->BreakpointInfo[1].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[1].Address != Context->Dr1) || + (ThreadBreakpoints->BreakpointInfo[2].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[2].Address != Context->Dr2) || + (ThreadBreakpoints->BreakpointInfo[3].Address && (DWORD_PTR)ThreadBreakpoints->BreakpointInfo[3].Address != Context->Dr3) + ) + { + DebugOutput("Wow64SetThreadContextHandler: Protecting breakpoints for thread %d: 0x%p, 0x%p, 0x%p, 0x%p.\n", Tid, ThreadBreakpoints->BreakpointInfo[0].Address, ThreadBreakpoints->BreakpointInfo[1].Address, ThreadBreakpoints->BreakpointInfo[2].Address, ThreadBreakpoints->BreakpointInfo[3].Address); + //ContextSetThreadBreakpointsEx(Context, ThreadBreakpoints, TRUE); + } + } +#ifdef DEBUG_COMMENTS + else + DebugOutput("Wow64SetThreadContextHandler hook: No breakpoints to protect for thread %d.\n", Tid); +#endif + } + + MEMORY_BASIC_INFORMATION MemoryInfo; + struct InjectionInfo *CurrentInjectionInfo = GetInjectionInfo(Pid); + + if (!CurrentInjectionInfo) + return; + + if (VirtualQueryEx(CurrentInjectionInfo->ProcessHandle, (PVOID)(DWORD_PTR)Context->Eax, &MemoryInfo, sizeof(MemoryInfo))) + CurrentInjectionInfo->ImageBase = (DWORD_PTR)MemoryInfo.AllocationBase; + else + { + ErrorOutput("Wow64SetThreadContextHandler: Failed to query target process memory at address 0x%x", Context->Eax); + return; + } + + if (!CurrentInjectionInfo || CurrentInjectionInfo->ProcessId != Pid) + return; + + CurrentInjectionInfo->EntryPoint = Context->Eax - CurrentInjectionInfo->ImageBase; // eax holds ep on 32-bit + + if (Context->Eip == (DWORD_PTR)GetProcAddress(GetModuleHandle("ntdll"), "NtMapViewOfSection")) + DebugOutput("Wow64SetThreadContextHandler: Hollow process entry point set to NtMapViewOfSection (process %d).\n", Pid); + else + DebugOutput("Wow64SetThreadContextHandler: Hollow process entry point reset via NtSetContextThread to 0x%p (process %d).\n", CurrentInjectionInfo->EntryPoint, Pid); +} +#endif + BOOL CheckDontMonitorList(WCHAR* TargetProcess) { const wchar_t *DontMonitorList[] = diff --git a/CAPE/Injection.h b/CAPE/Injection.h index 54d4f650..934294a7 100644 --- a/CAPE/Injection.h +++ b/CAPE/Injection.h @@ -71,4 +71,12 @@ void ResumeProcessHandler(HANDLE ProcessHandle, DWORD Pid); void MapSectionViewHandler(HANDLE ProcessHandle, HANDLE SectionHandle, PVOID BaseAddress, SIZE_T ViewSize); void UnmapSectionViewHandler(PVOID BaseAddress); void WriteMemoryHandler(HANDLE ProcessHandle, LPVOID BaseAddress, LPCVOID Buffer, SIZE_T NumberOfBytesWritten); +void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context); +void SetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context); +#ifdef _WIN64 +void Wow64GetThreadContextHandler(HANDLE ThreadHandle, PWOW64_CONTEXT Context); +void Wow64SetThreadContextHandler(HANDLE ThreadHandle, PWOW64_CONTEXT Context); +#endif +void ResumeThreadHandler(DWORD Pid); +void CreateRemoteThreadHandler(DWORD Pid); void TerminateHandler(); diff --git a/hook_thread.c b/hook_thread.c index fa4f94b4..4151c752 100644 --- a/hook_thread.c +++ b/hook_thread.c @@ -28,13 +28,10 @@ along with this program. If not, see . #include "lookup.h" #include "CAPE\CAPE.h" #include "CAPE\Debugger.h" +#include "CAPE\Injection.h" extern _RtlNtStatusToDosError pRtlNtStatusToDosError; extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); -extern void GetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context); -extern void SetThreadContextHandler(HANDLE ThreadHandle, LPCONTEXT Context); -extern void ResumeThreadHandler(DWORD Pid); -extern void CreateRemoteThreadHandler(DWORD Pid); extern void NtContinueHandler(PCONTEXT ThreadContext); extern void ProcessMessage(DWORD ProcessId, DWORD ThreadId); extern BOOL BreakpointsSet; @@ -403,19 +400,6 @@ HOOKDEF(NTSTATUS, WINAPI, NtGetContextThread, return ret; } -HOOKDEF(NTSTATUS, WINAPI, RtlWow64GetThreadContext, - __in HANDLE ThreadHandle, - __inout PWOW64_CONTEXT Context -) { - DWORD pid = pid_from_thread_handle(ThreadHandle); - - NTSTATUS ret = Old_RtlWow64GetThreadContext(ThreadHandle, Context); - - LOQ_ntstatus("threading", "pi", "ThreadHandle", ThreadHandle, "ProcessId", pid); - - return ret; -} - HOOKDEF(NTSTATUS, WINAPI, NtSetContextThread, __in HANDLE ThreadHandle, __in CONTEXT *Context @@ -501,12 +485,119 @@ HOOKDEF(NTSTATUS, WINAPI, NtSetContextThread, ); #endif } + else + + LOQ_ntstatus("threading", "pii", "ThreadHandle", ThreadHandle, "ProcessId", pid, "ThreadId", tid); + + return ret; +} + +#ifdef _WIN64 +HOOKDEF(NTSTATUS, WINAPI, RtlWow64GetThreadContext, + __in HANDLE ThreadHandle, + __inout PWOW64_CONTEXT Context +) { + DWORD pid = pid_from_thread_handle(ThreadHandle); + DWORD tid = tid_from_thread_handle(ThreadHandle); + + NTSTATUS ret = Old_RtlWow64GetThreadContext(ThreadHandle, Context); + + if (Context && (Context->ContextFlags & (CONTEXT_CONTROL | CONTEXT_INTEGER)) == (CONTEXT_CONTROL | CONTEXT_INTEGER)) + LOQ_ntstatus( + "threading", "pppppppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Eip, + "Eax", Context->Eax, + "Ebx", Context->Ebx, + "Ecx", Context->Ecx, + "Edx", Context->Edx, + "Esp", Context->Esp, + "ProcessId", pid, + "ThreadId", tid + ); + else if (Context && (Context->ContextFlags & CONTEXT_INTEGER)) { + LOQ_ntstatus( + "threading", "pppppii", + "ThreadHandle", ThreadHandle, + "Eax", Context->Eax, + "Ebx", Context->Ebx, + "Ecx", Context->Ecx, + "Edx", Context->Edx, + "ProcessId", pid, + "ThreadId", tid + ); + } + else if (Context && (Context->ContextFlags & CONTEXT_CONTROL)) { + LOQ_ntstatus( + "threading", "pppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Eip, + "Esp", Context->Esp, + "ProcessId", pid, + "ThreadId", tid + ); + } else LOQ_ntstatus("threading", "pii", "ThreadHandle", ThreadHandle, "ProcessId", pid, "ThreadId", tid); + Wow64GetThreadContextHandler(ThreadHandle, Context); + return ret; } +HOOKDEF(NTSTATUS, WINAPI, RtlWow64SetThreadContext, + __in HANDLE ThreadHandle, + __inout PWOW64_CONTEXT Context +) { + DWORD pid = pid_from_thread_handle(ThreadHandle); + DWORD tid = tid_from_thread_handle(ThreadHandle); + + Wow64SetThreadContextHandler(ThreadHandle, Context); + + NTSTATUS ret = Old_RtlWow64SetThreadContext(ThreadHandle, Context); + + if (Context && (Context->ContextFlags & (CONTEXT_CONTROL | CONTEXT_INTEGER)) == (CONTEXT_CONTROL | CONTEXT_INTEGER)) + LOQ_ntstatus( + "threading", "pppppppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Eip, + "Eax", Context->Eax, + "Ebx", Context->Ebx, + "Ecx", Context->Ecx, + "Edx", Context->Edx, + "Esp", Context->Esp, + "ProcessId", pid, + "ThreadId", tid + ); + else if (Context && (Context->ContextFlags & CONTEXT_INTEGER)) { + LOQ_ntstatus( + "threading", "pppppii", + "ThreadHandle", ThreadHandle, + "Eax", Context->Eax, + "Ebx", Context->Ebx, + "Ecx", Context->Ecx, + "Edx", Context->Edx, + "ProcessId", pid, + "ThreadId", tid + ); + } + else if (Context && (Context->ContextFlags & CONTEXT_CONTROL)) { + LOQ_ntstatus( + "threading", "pppii", + "ThreadHandle", ThreadHandle, + "InstructionPointer", Context->Eip, + "Esp", Context->Esp, + "ProcessId", pid, + "ThreadId", tid + ); + } + else + LOQ_ntstatus("threading", "pii", "ThreadHandle", ThreadHandle, "ProcessId", pid, "ThreadId", tid); + + return ret; +} +#endif + HOOKDEF(NTSTATUS, WINAPI, NtSuspendThread, __in HANDLE ThreadHandle, __out_opt ULONG *PreviousSuspendCount diff --git a/hooks.c b/hooks.c index efe177f4..cfe2f672 100644 --- a/hooks.c +++ b/hooks.c @@ -138,8 +138,11 @@ hook_t full_hooks[] = { HOOK(ntdll, NtQueueApcThreadEx), HOOK(ntdll, NtOpenThread), HOOK(ntdll, NtGetContextThread), - HOOK(ntdll, RtlWow64GetThreadContext), HOOK(ntdll, NtSetContextThread), +#ifdef _WIN64 + HOOK(ntdll, RtlWow64GetThreadContext), + HOOK(ntdll, RtlWow64SetThreadContext), +#endif HOOK(ntdll, NtSuspendThread), HOOK(ntdll, NtResumeThread), HOOK(ntdll, NtAlertResumeThread), @@ -876,7 +879,10 @@ hook_t native_hooks[] = { HOOK(ntdll, NtQueueApcThreadEx), HOOK(ntdll, NtOpenThread), HOOK(ntdll, NtGetContextThread), +#ifdef _WIN64 HOOK(ntdll, RtlWow64GetThreadContext), + HOOK(ntdll, RtlWow64SetThreadContext), +#endif HOOK(ntdll, NtSetContextThread), HOOK(ntdll, NtSuspendThread), HOOK(ntdll, NtResumeThread), @@ -1716,6 +1722,18 @@ void set_hooks_exe(void) } +BOOL dll_is_hooked(const wchar_t *library) +{ + if (!library) + return FALSE; + BOOL ret = FALSE; + for (unsigned int i = 0; i < hooks_arraysize; i++) { + if (!wcsicmp((hooks+i)->library, library)) + ret = TRUE; + } + return ret; +} + void set_hooks_by_export_directory(const wchar_t *exportdirectory, const wchar_t *library) { unsigned int Hooked = 0; diff --git a/hooks.h b/hooks.h index 2aad8328..782a4917 100644 --- a/hooks.h +++ b/hooks.h @@ -1572,15 +1572,22 @@ HOOKDEF(NTSTATUS, WINAPI, NtGetContextThread, __inout LPCONTEXT Context ); +HOOKDEF(NTSTATUS, WINAPI, NtSetContextThread, + __in HANDLE ThreadHandle, + __in CONTEXT *Context +); + +#ifdef _WIN64 HOOKDEF(NTSTATUS, WINAPI, RtlWow64GetThreadContext, __in HANDLE ThreadHandle, __inout PWOW64_CONTEXT Context ); -HOOKDEF(NTSTATUS, WINAPI, NtSetContextThread, - __in HANDLE ThreadHandle, - __in CONTEXT *Context +HOOKDEF(NTSTATUS, WINAPI, RtlWow64SetThreadContext, + __in HANDLE ThreadHandle, + __inout PWOW64_CONTEXT Context ); +#endif HOOKDEF(NTSTATUS, WINAPI, NtSuspendThread, __in HANDLE ThreadHandle, From c43ec20565d146e7cc9d0be58c9bbcc383d64871 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 5 Dec 2025 09:58:07 +0000 Subject: [PATCH 132/148] RtlUserThreadStart hook --- hook_thread.c | 9 +++++++++ hooks.c | 1 + hooks.h | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/hook_thread.c b/hook_thread.c index 4151c752..6a0d844e 100644 --- a/hook_thread.c +++ b/hook_thread.c @@ -900,6 +900,15 @@ HOOKDEF(NTSTATUS, WINAPI, RtlCreateUserThread, return ret; } +HOOKDEF_NOTAIL(WINAPI, RtlUserThreadStart, + __in LPTHREAD_START_ROUTINE lpStartAddress, + __in LPVOID lpParameter +) { + NTSTATUS ret = 0; + LOQ_void("threading", "pp", "StartAddress", lpStartAddress, "Parameter", lpParameter); + return ret; +} + HOOKDEF(NTSTATUS, WINAPI, NtSetInformationThread, IN HANDLE ThreadHandle, IN THREADINFOCLASS ThreadInformationClass, diff --git a/hooks.c b/hooks.c index cfe2f672..edf2c755 100644 --- a/hooks.c +++ b/hooks.c @@ -139,6 +139,7 @@ hook_t full_hooks[] = { HOOK(ntdll, NtOpenThread), HOOK(ntdll, NtGetContextThread), HOOK(ntdll, NtSetContextThread), + HOOK_NOTAIL(ntdll, RtlUserThreadStart, 2), #ifdef _WIN64 HOOK(ntdll, RtlWow64GetThreadContext), HOOK(ntdll, RtlWow64SetThreadContext), diff --git a/hooks.h b/hooks.h index 782a4917..9912a6e6 100644 --- a/hooks.h +++ b/hooks.h @@ -1639,6 +1639,11 @@ HOOKDEF(HANDLE, WINAPI, CreateRemoteThreadEx, __out_opt LPDWORD lpThreadId ); +HOOKDEF_NOTAIL(WINAPI, RtlUserThreadStart, + __in LPTHREAD_START_ROUTINE lpStartAddress, + __in LPVOID lpParameter +); + HOOKDEF(BOOL, WINAPI, TerminateThread, __inout HANDLE hThread, __in DWORD dwExitCode From 009c5978d902408dbf9dce5f7c2eff5af4665247 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 5 Dec 2025 11:04:06 +0000 Subject: [PATCH 133/148] Add get_module_name() function --- misc.c | 20 ++++++++++++++++++++ misc.h | 1 + 2 files changed, 21 insertions(+) diff --git a/misc.c b/misc.c index 1f3ed440..50c1c1c7 100644 --- a/misc.c +++ b/misc.c @@ -1031,6 +1031,26 @@ char *convert_address_to_dll_name_and_offset(ULONG_PTR addr, unsigned int *offse return NULL; } +UNICODE_STRING* get_module_name(ULONG_PTR addr) +{ + PLDR_DATA_TABLE_ENTRY mod; + PLIST_ENTRY pHeadEntry; + PLIST_ENTRY pListEntry; + PEB *peb = (PEB *)get_peb(); + + pHeadEntry = &peb->LoaderData->InLoadOrderModuleList; + for(pListEntry = pHeadEntry->Flink; + pListEntry != pHeadEntry; + pListEntry = pListEntry->Flink) + { + mod = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderModuleList); + if (addr < (ULONG_PTR)mod->BaseAddress || addr >= ((ULONG_PTR)mod->BaseAddress + mod->SizeOfImage)) + continue; + return &mod->BaseDllName; + } + return NULL; +} + // hide our module from PEB // http://www.openrce.org/blog/view/844/How_to_hide_dll diff --git a/misc.h b/misc.h index da8338ae..eaae1762 100644 --- a/misc.h +++ b/misc.h @@ -181,6 +181,7 @@ void raw_sleep(int msecs); DWORD randint(DWORD min, DWORD max); BOOL is_directory_objattr(const OBJECT_ATTRIBUTES *obj); BOOL file_exists(const OBJECT_ATTRIBUTES *obj); +UNICODE_STRING* get_module_name(ULONG_PTR addr); void hide_module_from_peb(HMODULE module_handle); int path_is_system(const wchar_t *path_w); int path_is_program_files(const wchar_t *path_w); From 32338659257f9572167e157cf8d6741a215b064f Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 5 Dec 2025 11:26:23 +0000 Subject: [PATCH 134/148] Hooking: replace (allocating) convert_address_to_dll_name_and_offset() with non-allocating get_module_name() --- hooking_32.c | 4 ++-- hooking_64.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hooking_32.c b/hooking_32.c index 5a85fd71..6e1f67de 100644 --- a/hooking_32.c +++ b/hooking_32.c @@ -708,8 +708,8 @@ int hook_api(hook_t *h, int type) addr = (unsigned char *)GetProcAddress(hmod, h->funcname); if (exportaddr && addr && (PVOID)addr != exportaddr) { unsigned int offset; - char *module_name = convert_address_to_dll_name_and_offset((ULONG_PTR)addr, &offset); - DebugOutput("hook_api: Warning - %s export address 0x%p differs from GetProcAddress -> 0x%p (%s::0x%x)\n", h->funcname, exportaddr, addr, module_name, offset); + UNICODE_STRING *module_name = get_module_name((ULONG_PTR)addr); + DebugOutput("hook_api: Warning - %s export address 0x%p differs from GetProcAddress -> 0x%p (%wZ::0x%x)\n", h->funcname, exportaddr, addr, module_name, offset); } else if (exportaddr && !addr) { addr = exportaddr; diff --git a/hooking_64.c b/hooking_64.c index 9cb7ab13..3749f2b7 100644 --- a/hooking_64.c +++ b/hooking_64.c @@ -1039,8 +1039,8 @@ int hook_api(hook_t *h, int type) addr = (unsigned char *)GetProcAddress(hmod, h->funcname); if (exportaddr && addr && (PVOID)addr != exportaddr) { unsigned int offset; - char *module_name = convert_address_to_dll_name_and_offset((ULONG_PTR)addr, &offset); - DebugOutput("hook_api: Warning - %s export address 0x%p differs from GetProcAddress -> 0x%p (%s::0x%x)\n", h->funcname, exportaddr, addr, module_name, offset); + UNICODE_STRING *module_name = get_module_name((ULONG_PTR)addr); + DebugOutput("hook_api: Warning - %s export address 0x%p differs from GetProcAddress -> 0x%p (%wZ::0x%x)\n", h->funcname, exportaddr, addr, module_name, offset); } else if (exportaddr && !addr) { addr = exportaddr; From 5a876919402d2fafa2c82b528e72d3d20f5e8ccb Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 5 Dec 2025 11:27:48 +0000 Subject: [PATCH 135/148] Add general hook protection (hook-protect=1) to protect hooks other than ntdll (ntdll-protect) - off by default --- config.c | 7 ++++ config.h | 3 ++ hook_process.c | 93 ++++++++++++++++++++++---------------------------- 3 files changed, 51 insertions(+), 52 deletions(-) diff --git a/config.c b/config.c index 3a4dbd75..f2ea69ed 100644 --- a/config.c +++ b/config.c @@ -273,6 +273,13 @@ void parse_config_line(char* line) else DebugOutput("Config: ntdll write protection disabled."); } + else if (!strcmp(key, "hook-protect")) { + g_config.hook_protect = (unsigned int)strtoul(value, NULL, 10); + if (g_config.hook_protect) + DebugOutput("Config: hook write protection enabled."); + else + DebugOutput("Config: hook write protection disabled."); + } else if (!strcmp(key, "ntdll-remap")) { g_config.ntdll_remap = (unsigned int)strtoul(value, NULL, 10); if (g_config.ntdll_remap) diff --git a/config.h b/config.h index 01132cb1..570057e7 100644 --- a/config.h +++ b/config.h @@ -144,6 +144,9 @@ struct _g_config { // ntdll write protection unsigned int ntdll_protect; + // hook write protection + unsigned int hook_protect; + // ntdll remap protection unsigned int ntdll_remap; diff --git a/hook_process.c b/hook_process.c index d5f47800..1b01726b 100644 --- a/hook_process.c +++ b/hook_process.c @@ -42,12 +42,12 @@ extern void ProtectionHandler(PVOID BaseAddress, ULONG Protect, PULONG OldProtec extern void FreeHandler(PVOID BaseAddress), ProcessMessage(DWORD ProcessId, DWORD ThreadId); extern void ProcessTrackedRegion(), DebuggerShutdown(), DumpStrings(); extern LONG WINAPI mini_handler(__in struct _EXCEPTION_POINTERS *ExceptionInfo); - -extern HANDLE g_terminate_event_handle; +extern BOOL dll_is_hooked(const wchar_t *library); extern BOOL CAPEExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT Context); extern void file_handle_terminate(); extern int DoProcessDump(); extern PVOID GetHookCallerBase(); +extern HANDLE g_terminate_event_handle; extern BOOL ProcessDumped; static BOOL ntdll_protect_logged; @@ -722,21 +722,17 @@ HOOKDEF(NTSTATUS, WINAPI, NtMapViewOfSection, __in ULONG AllocationType, __in ULONG Win32Protect ) { - char *ModuleName = NULL; - unsigned int DllRVA; + NTSTATUS ret = Old_NtMapViewOfSection(SectionHandle, ProcessHandle, BaseAddress, ZeroBits, CommitSize, SectionOffset, ViewSize, InheritDisposition, AllocationType, Win32Protect); - NTSTATUS ret = Old_NtMapViewOfSection(SectionHandle, ProcessHandle, BaseAddress, ZeroBits, - CommitSize, SectionOffset, ViewSize, InheritDisposition, AllocationType, Win32Protect); DWORD pid = pid_from_process_handle(ProcessHandle); + UNICODE_STRING *module_name = get_module_name((ULONG_PTR)*BaseAddress); - ModuleName = convert_address_to_dll_name_and_offset((ULONG_PTR)*BaseAddress, &DllRVA); - - if (!ModuleName) + if (!module_name) LOQ_ntstatus("process", "ppPpPhs", "SectionHandle", SectionHandle,"ProcessHandle", ProcessHandle, "BaseAddress", BaseAddress, "SectionOffset", SectionOffset, "ViewSize", ViewSize, "Win32Protect", Win32Protect, "StackPivoted", is_stack_pivoted() ? "yes" : "no"); else LOQ_ntstatus("process", "ppPspPhs", "SectionHandle", SectionHandle,"ProcessHandle", ProcessHandle, "BaseAddress", BaseAddress, - "ModuleName", ModuleName, "SectionOffset", SectionOffset, "ViewSize", ViewSize, "Win32Protect", Win32Protect, "StackPivoted", is_stack_pivoted() ? "yes" : "no"); + "ModuleName", module_name, "SectionOffset", SectionOffset, "ViewSize", ViewSize, "Win32Protect", Win32Protect, "StackPivoted", is_stack_pivoted() ? "yes" : "no"); if (NT_SUCCESS(ret)) { if (g_config.injection) @@ -745,14 +741,10 @@ HOOKDEF(NTSTATUS, WINAPI, NtMapViewOfSection, ProcessMessage(pid, 0); disable_sleep_skip(); } - else if (g_config.ntdll_remap && ret == STATUS_IMAGE_NOT_AT_BASE && Win32Protect == PAGE_READONLY) { + else if (g_config.ntdll_remap && ret == STATUS_IMAGE_NOT_AT_BASE && Win32Protect == PAGE_READONLY) prevent_module_reloading(BaseAddress); - } } - if (ModuleName) - free(ModuleName); - return ret; } @@ -1130,18 +1122,21 @@ HOOKDEF(NTSTATUS, WINAPI, NtProtectVirtualMemory, NTSTATUS ret; MEMORY_BASIC_INFORMATION meminfo; DWORD OriginalNewAccessProtection = 0; - unsigned int DllRVA; - char *ModuleName = NULL; + UNICODE_STRING *module_name = NULL; if (BaseAddress) - ModuleName = convert_address_to_dll_name_and_offset((ULONG_PTR)*BaseAddress, &DllRVA); - - if (g_config.ntdll_protect && NewAccessProtection == PAGE_EXECUTE_READWRITE && BaseAddress && NumberOfBytesToProtect && - NtCurrentProcess() == ProcessHandle && is_in_dll_range((ULONG_PTR)*BaseAddress) && - ModuleName && !strcmp(ModuleName, "ntdll.dll")) { - // don't allow writes, this will cause memory access violations - // that we are going to handle in the RtlDispatchException hook + module_name = get_module_name((ULONG_PTR)*BaseAddress); + + if (module_name && g_config.ntdll_protect || g_config.hook_protect) { + if (NewAccessProtection == PAGE_EXECUTE_READWRITE && BaseAddress && NumberOfBytesToProtect && + NtCurrentProcess() == ProcessHandle && is_in_dll_range((ULONG_PTR)*BaseAddress)) { + if ((g_config.ntdll_protect && module_name->Length && !wcsncmp(module_name->Buffer, L"ntdll.dll", module_name->Length)) || + (g_config.hook_protect && dll_is_hooked(module_name->Buffer))) { + // don't allow writes, this will cause memory access violations that are handled in the RtlDispatchException hook OriginalNewAccessProtection = NewAccessProtection; NewAccessProtection = PAGE_EXECUTE_READ; + DebugOutput("NtProtectVirtualMemory: Protecting %wZ at 0x%p", module_name, *BaseAddress); + } + } } memset(&meminfo, 0, sizeof(meminfo)); @@ -1186,8 +1181,8 @@ HOOKDEF(NTSTATUS, WINAPI, NtProtectVirtualMemory, "NewAccessProtection", NewAccessProtection, "OldAccessProtection", OldAccessProtection, "StackPivoted", is_stack_pivoted() ? "yes" : "no", "IsStack", "yes"); } - else if (ModuleName) - LOQ_ntstatus("process", "pPsPhhHs", "ProcessHandle", ProcessHandle, "BaseAddress", BaseAddress, "ModuleName", ModuleName, + else if (module_name) + LOQ_ntstatus("process", "pPoPhhHs", "ProcessHandle", ProcessHandle, "BaseAddress", BaseAddress, "ModuleName", module_name, "NumberOfBytesProtected", NumberOfBytesToProtect, "MemoryType", meminfo.Type, "NewAccessProtection", NewAccessProtection, "OldAccessProtection", OldAccessProtection, "StackPivoted", is_stack_pivoted() ? "yes" : "no"); else @@ -1195,9 +1190,6 @@ HOOKDEF(NTSTATUS, WINAPI, NtProtectVirtualMemory, "NumberOfBytesProtected", NumberOfBytesToProtect, "MemoryType", meminfo.Type, "NewAccessProtection", NewAccessProtection, "OldAccessProtection", OldAccessProtection, "StackPivoted", is_stack_pivoted() ? "yes" : "no"); - if (ModuleName) - free(ModuleName); - return ret; } @@ -1211,17 +1203,19 @@ HOOKDEF(BOOL, WINAPI, VirtualProtectEx, BOOL ret; MEMORY_BASIC_INFORMATION meminfo; DWORD OriginalNewProtect = 0; - unsigned int DllRVA; - char *ModuleName = NULL; - ModuleName = convert_address_to_dll_name_and_offset((ULONG_PTR)lpAddress, &DllRVA); - - if (g_config.ntdll_protect && flNewProtect == PAGE_EXECUTE_READWRITE && lpAddress && dwSize && - NtCurrentProcess() == hProcess && is_in_dll_range((ULONG_PTR)lpAddress) && - ModuleName && !strcmp(ModuleName, "ntdll.dll")) { - // don't allow writes, this will cause memory access violations - // that we are going to handle in the RtlDispatchException hook + UNICODE_STRING *module_name = get_module_name((ULONG_PTR)lpAddress); + + if (module_name && g_config.ntdll_protect || g_config.hook_protect) { + if (flNewProtect == PAGE_EXECUTE_READWRITE && lpAddress && dwSize && + GetCurrentProcessId() == our_getprocessid(hProcess) && is_in_dll_range((ULONG_PTR)lpAddress)) { + if ((g_config.ntdll_protect && module_name->Length && !wcsncmp(module_name->Buffer, L"ntdll.dll", module_name->Length)) || + (g_config.hook_protect && dll_is_hooked(module_name->Buffer))) { + // don't allow writes, this will cause memory access violations that are handled in the RtlDispatchException hook OriginalNewProtect = flNewProtect; flNewProtect = PAGE_EXECUTE_READ; + DebugOutput("VirtualProtectEx: Protecting %wZ at 0x%p", module_name, lpAddress); + } + } } memset(&meminfo, 0, sizeof(meminfo)); @@ -1265,17 +1259,14 @@ HOOKDEF(BOOL, WINAPI, VirtualProtectEx, LOQ_bool("process", "ppphhHss", "ProcessHandle", hProcess, "Address", lpAddress, "Size", dwSize, "MemType", meminfo.Type, "Protection", flNewProtect, "OldProtection", lpflOldProtect, "StackPivoted", is_stack_pivoted() ? "yes" : "no", "IsStack", "yes"); } - else if (ModuleName) - LOQ_bool("process", "ppsphhHs", "ProcessHandle", hProcess, "Address", lpAddress, "ModuleName", ModuleName, + else if (module_name) + LOQ_bool("process", "ppophhHs", "ProcessHandle", hProcess, "Address", lpAddress, "ModuleName", module_name, "Size", dwSize, "MemType", meminfo.Type, "Protection", flNewProtect, "OldProtection", lpflOldProtect, "StackPivoted", is_stack_pivoted() ? "yes" : "no"); else LOQ_bool("process", "ppphhHs", "ProcessHandle", hProcess, "Address", lpAddress, "Size", dwSize, "MemType", meminfo.Type, "Protection", flNewProtect, "OldProtection", lpflOldProtect, "StackPivoted", is_stack_pivoted() ? "yes" : "no"); - if (ModuleName) - free(ModuleName); - return ret; } @@ -1405,21 +1396,21 @@ HOOKDEF_ALT(BOOL, WINAPI, RtlDispatchException, if (g_config.log_exceptions > 1 && ExceptionRecord && ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) mini_handler(&ExceptionInfo); - if (g_config.ntdll_protect) { + if (g_config.ntdll_protect || g_config.hook_protect) { if (ExceptionRecord && ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && ExceptionRecord->ExceptionFlags == 0 && ExceptionRecord->NumberParameters == 2 && ExceptionRecord->ExceptionInformation[0] == 1) { - unsigned int offset; - char *dllname = convert_address_to_dll_name_and_offset(ExceptionRecord->ExceptionInformation[1], &offset); - if (dllname && !strcmp(dllname, "ntdll.dll")) { - // if trying to write to ntdll.dll, then just skip the instruction + UNICODE_STRING *module_name = get_module_name((ULONG_PTR)ExceptionRecord->ExceptionInformation[1]); + if ((g_config.ntdll_protect && module_name->Length && !wcsncmp(module_name->Buffer, L"ntdll.dll", module_name->Length)) || + (g_config.hook_protect && module_name && dll_is_hooked(module_name->Buffer))) { + // if trying to write to protected module, skip the instruction if (!ntdll_protect_logged) { ntdll_protect_logged = TRUE; #ifdef _WIN64 - DebugOutput("RtlDispatchException: skipped instruction at 0x%p writing to ntdll (0x%p - 0x%p)\n", Context->Rip, ExceptionRecord->ExceptionInformation[1], offset); + DebugOutput("RtlDispatchException: skipped instruction at 0x%p writing to %wZ (0x%p)\n", Context->Rip, module_name, ExceptionRecord->ExceptionInformation[1]); } Context->Rip += lde((void*)Context->Rip); #else - DebugOutput("RtlDispatchException: skipped instruction at 0x%x writing to ntdll (0x%x - 0x%x)\n", Context->Eip, ExceptionRecord->ExceptionInformation[1], offset); + DebugOutput("RtlDispatchException: skipped instruction at 0x%x writing to %wZ (0x%x)\n", Context->Eip, module_name, ExceptionRecord->ExceptionInformation[1]); } Context->Eip += lde((void*)Context->Eip); #endif @@ -1428,8 +1419,6 @@ HOOKDEF_ALT(BOOL, WINAPI, RtlDispatchException, LOQ_void("system", "pppp", "ExceptionCode", ExceptionRecord->ExceptionCode, "ExceptionAddress", ExceptionRecord->ExceptionAddress, "ExceptionFlags", ExceptionRecord->ExceptionFlags, "ExceptionInformation", ExceptionRecord->ExceptionInformation[0]); return TRUE; } - if (dllname) - free(dllname); } } From ef26a3aa096b5c20b99c45e7cb915643d5f5d842 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 9 Dec 2025 12:24:47 +0000 Subject: [PATCH 136/148] Add ntdll unhook protection (ntdll-unhook=1) via prevent_module_unhooking() from NtReadFile hook --- config.c | 15 +++++++++++---- config.h | 3 +++ hook_file.c | 3 +++ misc.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ misc.h | 1 + 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/config.c b/config.c index f2ea69ed..34bd4978 100644 --- a/config.c +++ b/config.c @@ -268,10 +268,17 @@ void parse_config_line(char* line) } else if (!strcmp(key, "ntdll-protect")) { g_config.ntdll_protect = (unsigned int)strtoul(value, NULL, 10); - if (g_config.ntdll_protect) - DebugOutput("Config: ntdll write protection enabled."); - else - DebugOutput("Config: ntdll write protection disabled."); + if (g_config.ntdll_protect) + DebugOutput("Config: ntdll write protection enabled."); + else + DebugOutput("Config: ntdll write protection disabled."); + } + else if (!strcmp(key, "ntdll-unhook")) { + g_config.ntdll_unhook = (unsigned int)strtoul(value, NULL, 10); + if (g_config.ntdll_unhook) + DebugOutput("Config: ntdll unhook protection enabled."); + else + DebugOutput("Config: ntdll unhook protection disabled."); } else if (!strcmp(key, "hook-protect")) { g_config.hook_protect = (unsigned int)strtoul(value, NULL, 10); diff --git a/config.h b/config.h index 570057e7..8b1bb20d 100644 --- a/config.h +++ b/config.h @@ -144,6 +144,9 @@ struct _g_config { // ntdll write protection unsigned int ntdll_protect; + // ntdll unhook protection (NtReadFile-based) + unsigned int ntdll_unhook; + // hook write protection unsigned int hook_protect; diff --git a/hook_file.c b/hook_file.c index 0a667450..b7e46e75 100644 --- a/hook_file.c +++ b/hook_file.c @@ -508,6 +508,9 @@ HOOKDEF(NTSTATUS, WINAPI, NtReadFile, fname = calloc(32768, sizeof(wchar_t)); path_from_handle(FileHandle, fname, 32768); + if (!g_config.no_stealth && g_config.ntdll_unhook && InitialBufferLength) + prevent_module_unhooking(Buffer, fname); + if (read_count < 50) LOQ_ntstatus("filesystem", "pFbl", "FileHandle", FileHandle, "HandleName", fname, "Buffer", InitialBufferLength, InitialBuffer, "Length", AccumulatedLength); diff --git a/misc.c b/misc.c index 50c1c1c7..64d5b76c 100644 --- a/misc.c +++ b/misc.c @@ -1942,6 +1942,29 @@ static BOOL get_section_bounds(HMODULE mod, const char * sectionname, PUCHAR *st return FALSE; } +static BOOL get_section_file_bounds(HMODULE mod, const char * sectionname, PUCHAR *start, PUCHAR *end) +{ + PUCHAR buf = (PUCHAR)mod; + PIMAGE_DOS_HEADER doshdr; + PIMAGE_NT_HEADERS nthdr; + PIMAGE_SECTION_HEADER sechdr; + unsigned int numsecs, i; + + doshdr = (PIMAGE_DOS_HEADER)buf; + nthdr = (PIMAGE_NT_HEADERS)(buf + doshdr->e_lfanew); + sechdr = (PIMAGE_SECTION_HEADER)((PUCHAR)&nthdr->OptionalHeader + nthdr->FileHeader.SizeOfOptionalHeader); + numsecs = nthdr->FileHeader.NumberOfSections; + + for (i = 0; i < numsecs; i++) { + if (memcmp(sechdr[i].Name, sectionname, strlen(sectionname))) + continue; + *start = buf + sechdr[i].PointerToRawData; + *end = *start + sechdr[i].SizeOfRawData; + return TRUE; + } + return FALSE; +} + ULONG_PTR get_connectex_addr(HMODULE mod) { PUCHAR start, end; @@ -2421,6 +2444,37 @@ void prevent_module_reloading(PVOID *BaseAddress) { free(absolutepath); } +void prevent_module_unhooking(PVOID buffer, wchar_t *filename) +{ + PUCHAR file_start = NULL, file_end = NULL, mem_start = NULL, mem_end = NULL; + + DebugOutput("prevent_module_unhooking buffer 0x%p, filename %ws", buffer, filename); + + wchar_t *whitelist[] = { +#ifdef _WIN64 + L"\\Device\\HarddiskVolume2\\Windows\\System32\\ntdll.dll", +#else + L"\\Device\\HarddiskVolume2\\Windows\\SysWOW64\\ntdll.dll", +#endif + NULL + }; + + for (int i = 0; whitelist[i]; i++) { + if (!wcsicmp(whitelist[i], filename)) { + get_section_file_bounds(buffer, ".text", &file_start, &file_end); + break; + } + } + + if (!file_start) + return; + + if (!get_section_bounds((HMODULE)ntdll_base, ".text", &mem_start, &mem_end)) + return; + + memcpy(file_start, mem_start, (unsigned int)(file_end - file_start)); +} + static size_t append_octet(char** p, size_t* remaining, unsigned char octet) { char* start = *p; size_t written_chars = 0; diff --git a/misc.h b/misc.h index eaae1762..65c82b5d 100644 --- a/misc.h +++ b/misc.h @@ -297,6 +297,7 @@ LONG WINAPI capemon_exception_handler(__in struct _EXCEPTION_POINTERS *Exception BOOLEAN prevent_module_unloading(PVOID BaseAddress); void prevent_module_reloading(PVOID *BaseAddress); +void prevent_module_unhooking(PVOID buffer, wchar_t *filename); struct envstruct { ULONG k; From f31bcf43e2b7d4eb728e18d2433fb865e8beeefe Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 9 Dec 2025 14:50:45 +0000 Subject: [PATCH 137/148] WTGetSignatureInfo hook - thanks @KillerInstinct --- hook_crypto.c | 13 +++++++++++++ hooks.c | 1 + hooks.h | 9 +++++++++ 3 files changed, 23 insertions(+) diff --git a/hook_crypto.c b/hook_crypto.c index 21e95411..9516494b 100644 --- a/hook_crypto.c +++ b/hook_crypto.c @@ -359,6 +359,19 @@ HOOKDEF(HRESULT, WINAPI, HTTPSFinalProv, return ret; } +HOOKDEF(NTSTATUS, WINAPI, WTGetSignatureInfo, + _In_ LPWSTR pszFile, + _In_ HANDLE hFile, + _In_ DWORD sigInfoFlags, + _Out_ PVOID psiginfo, + _Out_ PVOID ppCertContext, + _Out_ PVOID phWVTStateData +) { + NTSTATUS ret = Old_WTGetSignatureInfo(pszFile, hFile, sigInfoFlags, psiginfo, ppCertContext, phWVTStateData); + LOQ_ntstatus("crypto", "uhh", "FileName", pszFile, "hFile", hFile, "InfoFlags", sigInfoFlags); + return ret; +} + HOOKDEF(BOOL, WINAPI, CryptDecodeObjectEx, _In_ DWORD dwCertEncodingType, _In_ LPCSTR lpszStructType, diff --git a/hooks.c b/hooks.c index edf2c755..d024caf2 100644 --- a/hooks.c +++ b/hooks.c @@ -640,6 +640,7 @@ hook_t full_hooks[] = { HOOK(advapi32, CryptImportKey), HOOK(wintrust, HTTPSCertificateTrust), HOOK(wintrust, HTTPSFinalProv), + HOOK(wintrust, WTGetSignatureInfo), HOOK(crypt32, CryptDecodeObjectEx), HOOK(crypt32, CryptImportPublicKeyInfo), HOOK(ncrypt, NCryptImportKey), diff --git a/hooks.h b/hooks.h index 9912a6e6..abb80747 100644 --- a/hooks.h +++ b/hooks.h @@ -3112,6 +3112,15 @@ HOOKDEF(HRESULT, WINAPI, HTTPSFinalProv, PVOID data // PCRYPT_PROVIDER_DATA ); +HOOKDEF(NTSTATUS, WINAPI, WTGetSignatureInfo, + _In_ LPWSTR pszFile, + _In_ HANDLE hFile, + _In_ DWORD sigInfoFlags, + _Out_ PVOID psiginfo, + _Out_ PVOID ppCertContext, + _Out_ PVOID phWVTStateData +); + HOOKDEF(BOOL, WINAPI, CryptDecodeObjectEx, _In_ DWORD dwCertEncodingType, _In_ LPCSTR lpszStructType, From a9c2350cbfa90d8aacebc986599f00b80f30c71b Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Tue, 9 Dec 2025 15:50:37 +0000 Subject: [PATCH 138/148] LdrGetDllHandleEx hook --- hook_misc.c | 15 +++++++++++++++ hooks.c | 1 + hooks.h | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/hook_misc.c b/hook_misc.c index f7cf0e07..1e969ece 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -191,6 +191,21 @@ HOOKDEF(NTSTATUS, WINAPI, LdrGetDllHandle, return ret; } +HOOKDEF(NTSTATUS, WINAPI, LdrGetDllHandleEx, + __in ULONG Flags, + __in_opt PWSTR DllPath, + __in PULONG DllCharacteristics, + __in PUNICODE_STRING DllName, + __out_opt PVOID *DllHandle +) { + NTSTATUS ret = Old_LdrGetDllHandleEx(Flags, DllPath, DllCharacteristics, DllName, DllHandle); + if (DllHandle) + LOQ_ntstatus("system", "oP", "DllName", DllName, "DllHandle", DllHandle); + else + LOQ_ntstatus("system", "o", "DllName", DllName); + return ret; +} + HOOKDEF(NTSTATUS, WINAPI, LdrGetProcedureAddress, __in HMODULE ModuleHandle, __in_opt PANSI_STRING FunctionName, diff --git a/hooks.c b/hooks.c index d024caf2..a05892a3 100644 --- a/hooks.c +++ b/hooks.c @@ -380,6 +380,7 @@ hook_t full_hooks[] = { HOOK(ntdll, RtlAddVectoredExceptionHandler), HOOK(kernel32, SetErrorMode), HOOK(ntdll, LdrGetDllHandle), + HOOK(ntdll, LdrGetDllHandleEx), HOOK(ntdll, LdrGetProcedureAddress), HOOK(ntdll, LdrGetProcedureAddressForCaller), HOOK(kernel32, DeviceIoControl), diff --git a/hooks.h b/hooks.h index abb80747..b6f402f1 100644 --- a/hooks.h +++ b/hooks.h @@ -1760,6 +1760,14 @@ HOOKDEF(NTSTATUS, WINAPI, LdrGetDllHandle, __out PHANDLE pHModule ); +HOOKDEF(NTSTATUS, WINAPI, LdrGetDllHandleEx, + __in ULONG Flags, + __in_opt PWSTR DllPath, + __in PULONG DllCharacteristics, + __in PUNICODE_STRING DllName, + __out_opt PVOID *DllHandle +); + HOOKDEF(NTSTATUS, WINAPI, LdrGetProcedureAddress, __in HMODULE ModuleHandle, __in_opt PANSI_STRING FunctionName, From 46a8e2a0052cf814e87cdbf1dedff365983e5a4c Mon Sep 17 00:00:00 2001 From: KillerInstinct Date: Thu, 11 Dec 2025 22:08:34 -0500 Subject: [PATCH 139/148] Couple new thread hooks --- hook_thread.c | 33 +++++++++++++++++++++++++++++++++ hooks.c | 2 ++ hooks.h | 9 +++++++++ 3 files changed, 44 insertions(+) diff --git a/hook_thread.c b/hook_thread.c index 6a0d844e..3be0314b 100644 --- a/hook_thread.c +++ b/hook_thread.c @@ -1006,3 +1006,36 @@ HOOKDEF(BOOL, WINAPI, NtTestAlert, ret = Old_NtTestAlert(); return ret; } + +HOOKDEF(BOOL, WINAPI, SetThreadStackGuarantee, + _Inout_ PULONG StackSizeInBytes +) { + ULONG inputSize = 0; + if (StackSizeInBytes != NULL) { + __try { + inputSize = *StackSizeInBytes; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + ; + } + } + + BOOL ret = Old_SetThreadStackGuarantee(StackSizeInBytes); + if (ret) { + LOQ_bool("threading", "ii", "InputSize", inputSize, "OutputSize", *StackSizeInBytes); + } + else { + LOQ_bool("threading", "i", "InputSize", inputSize); + } + + return ret; +} + +HOOKDEF(NTSTATUS, WINAPI, SetThreadDescription, + _In_ HANDLE hThread, + _In_ PCWSTR lpThreadDescription +) { + NTSTATUS ret = Old_SetThreadDescription(hThread, lpThreadDescription); + LOQ_ntstatus("threading", "pu", "ThreadHandle", hThread, "ThreadDescription", lpThreadDescription); + return ret; +} \ No newline at end of file diff --git a/hooks.c b/hooks.c index d024caf2..f5f05d11 100644 --- a/hooks.c +++ b/hooks.c @@ -154,6 +154,8 @@ hook_t full_hooks[] = { HOOK(ntdll, NtContinue), HOOK(ntdll, NtContinueEx), HOOK(ntdll, NtTestAlert), + HOOK(kernelbase, SetThreadStackGuarantee), + HOOK(kernelbase, SetThreadDescription), HOOK(kernel32, CreateThread), HOOK(kernel32, CreateRemoteThread), HOOK(kernel32, CreateRemoteThreadEx), diff --git a/hooks.h b/hooks.h index abb80747..a5aa2803 100644 --- a/hooks.h +++ b/hooks.h @@ -1666,6 +1666,15 @@ HOOKDEF(BOOL, WINAPI, NtTestAlert, VOID ); +HOOKDEF(BOOL, WINAPI, SetThreadStackGuarantee, + _Inout_ PULONG StackSizeInBytes +); + +HOOKDEF(NTSTATUS, WINAPI, SetThreadDescription, + _In_ HANDLE hThread, + _In_ PCWSTR lpThreadDescription +); + // // Misc Hooks // From d08ef213b7a00ad3f4d8743f249d927b18055521 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 12 Dec 2025 14:26:16 +0000 Subject: [PATCH 140/148] K32EnumProcesses, WTSEnumerateProcessesW & WTSEnumerateProcessesExW hooks --- hook_process.c | 40 ++++++++++++++++++++++++++++++++++++++++ hooks.c | 3 +++ hooks.h | 22 ++++++++++++++++++++++ ntapi.h | 7 +++++++ 4 files changed, 72 insertions(+) diff --git a/hook_process.c b/hook_process.c index 1b01726b..e563a782 100644 --- a/hook_process.c +++ b/hook_process.c @@ -127,6 +127,46 @@ HOOKDEF(BOOL, WINAPI, Module32FirstW, return ret; } +HOOKDEF(BOOL, WINAPI, WTSEnumerateProcessesW, + _In_ HANDLE hServer, + _In_ DWORD Reserved, + _In_ DWORD Version, + _Out_ PWTS_PROCESS_INFOW* ppProcessInfo, + _Out_ DWORD* pCount +) { + BOOL ret = Old_WTSEnumerateProcessesW(hServer, Reserved, Version, ppProcessInfo, pCount); + + LOQ_bool("process", ""); + + return ret; +} + +HOOKDEF(BOOL, WINAPI, WTSEnumerateProcessesExW, + _In_ HANDLE hServer, + _Inout_ DWORD* pLevel, + _In_ DWORD SessionId, + _Out_ LPWSTR* ppProcessInfo, + _Out_ DWORD* pCount +) { + BOOL ret = Old_WTSEnumerateProcessesExW(hServer, pLevel, SessionId, ppProcessInfo, pCount); + + LOQ_bool("process", ""); + + return ret; +} + +HOOKDEF(BOOL, WINAPI, K32EnumProcesses, + _Out_writes_bytes_(cb) DWORD* lpidProcess, + _In_ DWORD cb, + _Out_ LPDWORD lpcbNeeded +) { + BOOL ret = Old_K32EnumProcesses(lpidProcess, cb, lpcbNeeded); + + LOQ_bool("process", ""); + + return ret; +} + HOOKDEF(UINT, WINAPI, WinExec, __in LPCSTR lpCmdLine, __in UINT uCmdShow diff --git a/hooks.c b/hooks.c index a05892a3..c7d5b9e4 100644 --- a/hooks.c +++ b/hooks.c @@ -120,6 +120,9 @@ hook_t full_hooks[] = { HOOK(kernel32, Process32NextW), HOOK(kernel32, Module32FirstW), HOOK(kernel32, Module32NextW), + HOOK(kernelbase, K32EnumProcesses), + HOOK(wtsapi32, WTSEnumerateProcessesW), + HOOK(wtsapi32, WTSEnumerateProcessesExW), HOOK(kernel32, CreateProcessA), HOOK(kernel32, CreateProcessW), HOOK(kernel32, WinExec), diff --git a/hooks.h b/hooks.h index b6f402f1..8e025683 100644 --- a/hooks.h +++ b/hooks.h @@ -1030,6 +1030,28 @@ HOOKDEF(BOOL, WINAPI, Module32NextW, __out LPMODULEENTRY32W lpme ); +HOOKDEF(BOOL, WINAPI, K32EnumProcesses, + _Out_writes_bytes_(cb) DWORD* lpidProcess, + _In_ DWORD cb, + _Out_ LPDWORD lpcbNeeded +); + +HOOKDEF(BOOL, WINAPI, WTSEnumerateProcessesW, + _In_ HANDLE hServer, + _In_ DWORD Reserved, + _In_ DWORD Version, + _Out_ PWTS_PROCESS_INFOW* ppProcessInfo, + _Out_ DWORD* pCount +); + +HOOKDEF(BOOL, WINAPI, WTSEnumerateProcessesExW, + _In_ HANDLE hServer, + _Inout_ DWORD* pLevel, + _In_ DWORD SessionId, + _Out_ LPWSTR* ppProcessInfo, + _Out_ DWORD* pCount +); + HOOKDEF(UINT, WINAPI, WinExec, __in LPCSTR lpCmdLine, __in UINT uCmdShow diff --git a/ntapi.h b/ntapi.h index dabc8561..bd39c452 100644 --- a/ntapi.h +++ b/ntapi.h @@ -1033,6 +1033,13 @@ typedef struct _TIMER_SET_COALESCABLE_TIMER_INFO { PBOOLEAN PreviousState; } TIMER_SET_COALESCABLE_TIMER_INFO, *PTIMER_SET_COALESCABLE_TIMER_INFO; +typedef struct _WTS_PROCESS_INFOW { + DWORD SessionId; + DWORD ProcessId; + LPWSTR pProcessName; + PSID pUserSid; +} WTS_PROCESS_INFOW, *PWTS_PROCESS_INFOW; + typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); typedef BOOL (WINAPI *PDLL_INIT_ROUTINE)( From 2bbb68a3fc2d6873d1bfab1caf2a712ed76724fd Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 12 Dec 2025 15:11:54 +0000 Subject: [PATCH 141/148] GetPhysicallyInstalledSystemMemory hook --- hook_misc.c | 10 ++++++++++ hooks.c | 1 + hooks.h | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/hook_misc.c b/hook_misc.c index 1e969ece..83e68bc6 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -1196,6 +1196,16 @@ HOOKDEF(BOOL, WINAPI, GlobalMemoryStatusEx, return ret; } +HOOKDEF(BOOL, WINAPI, GetPhysicallyInstalledSystemMemory, + _Out_ PULONGLONG TotalMemoryInKilobytes +) { + BOOL ret = Old_GetPhysicallyInstalledSystemMemory(TotalMemoryInKilobytes); + if (ret && !g_config.no_stealth && (*TotalMemoryInKilobytes * 1024) < 0x400000000) + *TotalMemoryInKilobytes = 0x400000000 / 1024; + LOQ_void("misc", "i", "TotalMemoryInKilobytes", *TotalMemoryInKilobytes); + return ret; +} + HOOKDEF(BOOL, WINAPI, SystemParametersInfoA, _In_ UINT uiAction, _In_ UINT uiParam, diff --git a/hooks.c b/hooks.c index ceadf449..67f2ec6c 100644 --- a/hooks.c +++ b/hooks.c @@ -435,6 +435,7 @@ hook_t full_hooks[] = { //HOOK(ole32, OleConvertOLESTREAMToIStorage), HOOK(kernel32, GlobalMemoryStatus), HOOK(kernel32, GlobalMemoryStatusEx), + HOOK(kernel32, GetPhysicallyInstalledSystemMemory), HOOK(user32, SystemParametersInfoA), HOOK(user32, SystemParametersInfoW), HOOK(pstorec, PStoreCreateInstance), diff --git a/hooks.h b/hooks.h index 9f3b39f6..a301ee2c 100644 --- a/hooks.h +++ b/hooks.h @@ -2077,6 +2077,10 @@ HOOKDEF(BOOL, WINAPI, GlobalMemoryStatusEx, _Out_ LPMEMORYSTATUSEX lpBuffer ); +HOOKDEF(BOOL, WINAPI, GetPhysicallyInstalledSystemMemory, + _Out_ PULONGLONG TotalMemoryInKilobytes +); + HOOKDEF(BOOL, WINAPI, SystemParametersInfoA, _In_ UINT uiAction, _In_ UINT uiParam, From ba960dbe99c39f1bee7ca8b3b6b2a12f4da44560 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 12 Dec 2025 15:30:33 +0000 Subject: [PATCH 142/148] misc: prevent_module_unhooking() & gettib() --- misc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/misc.c b/misc.c index 64d5b76c..61d0e4cd 100644 --- a/misc.c +++ b/misc.c @@ -2448,8 +2448,6 @@ void prevent_module_unhooking(PVOID buffer, wchar_t *filename) { PUCHAR file_start = NULL, file_end = NULL, mem_start = NULL, mem_end = NULL; - DebugOutput("prevent_module_unhooking buffer 0x%p, filename %ws", buffer, filename); - wchar_t *whitelist[] = { #ifdef _WIN64 L"\\Device\\HarddiskVolume2\\Windows\\System32\\ntdll.dll", @@ -2577,3 +2575,11 @@ DWORD wait_for_event_to_be_signaled(HANDLE hEvent, DWORD dwTimeout) { raw_sleep(250); } } + +void* gettib() { +#ifdef _WIN64 + return (void *)__readgsqword(0); +#else + return (void *)__readfsdword(0); +#endif +} From ddf4961025ac69766d0b159f4c1e51a8112e563b Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 12 Dec 2025 16:45:23 +0000 Subject: [PATCH 143/148] hook_misc: use SPOOFED_RAM define rather than constants in memory query hooks --- hook_misc.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/hook_misc.c b/hook_misc.c index 83e68bc6..22c24e00 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -114,6 +114,25 @@ HOOKDEF(LPTOP_LEVEL_EXCEPTION_FILTER, WINAPI, SetUnhandledExceptionFilter, return res; } +#define ALLOW_UNHANDLED_EXCEPTIONS 1 + +HOOKDEF(LONG, WINAPI, UnhandledExceptionFilter, + __in PEXCEPTION_POINTERS ExceptionInfo +) { + LONG ret; + if (ALLOW_UNHANDLED_EXCEPTIONS) + ret = Old_UnhandledExceptionFilter(ExceptionInfo); + else + ret = EXCEPTION_EXECUTE_HANDLER; + if (ExceptionInfo && !ExceptionInfo->ExceptionRecord->NumberParameters && (ExceptionInfo->ExceptionRecord->ExceptionCode >= 0x80000000 || g_config.log_exceptions > 1)) + LOQ_zero("process", "ppp", "ExceptionCode", ExceptionInfo->ExceptionRecord->ExceptionCode, "ExceptionAddress", ExceptionInfo->ExceptionRecord->ExceptionAddress, "ExceptionFlags", ExceptionInfo->ExceptionRecord->ExceptionFlags); + else if (ExceptionInfo->ExceptionRecord->NumberParameters == 1 && (ExceptionInfo->ExceptionRecord->ExceptionCode >= 0x80000000 || g_config.log_exceptions > 1)) + LOQ_zero("process", "pppp", "ExceptionCode", ExceptionInfo->ExceptionRecord->ExceptionCode, "ExceptionAddress", ExceptionInfo->ExceptionRecord->ExceptionAddress, "ExceptionFlags", ExceptionInfo->ExceptionRecord->ExceptionFlags, "ExceptionInformation", ExceptionInfo->ExceptionRecord->ExceptionInformation[0]); + else if (ExceptionInfo->ExceptionRecord->NumberParameters == 2 && (ExceptionInfo->ExceptionRecord->ExceptionCode >= 0x80000000 || g_config.log_exceptions > 1)) + LOQ_zero("process", "ppppp", "ExceptionCode", ExceptionInfo->ExceptionRecord->ExceptionCode, "ExceptionAddress", ExceptionInfo->ExceptionRecord->ExceptionAddress, "ExceptionFlags", ExceptionInfo->ExceptionRecord->ExceptionFlags, "ExceptionInformation[0]", ExceptionInfo->ExceptionRecord->ExceptionInformation[0], "ExceptionInformation[1]", ExceptionInfo->ExceptionRecord->ExceptionInformation[1]); + return ret; +} + PVECTORED_EXCEPTION_HANDLER SampleVectoredHandler; LONG WINAPI New_VectoredExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) @@ -1181,8 +1200,8 @@ HOOKDEF(void, WINAPI, GlobalMemoryStatus, ) { BOOL ret = TRUE; Old_GlobalMemoryStatus(lpBuffer); - if (!g_config.no_stealth && lpBuffer->dwTotalPhys < 0x400000000) - lpBuffer->dwTotalPhys = (SIZE_T)0x400000000; + if (!g_config.no_stealth && lpBuffer->dwTotalPhys < SPOOFED_RAM) + lpBuffer->dwTotalPhys = (SIZE_T)SPOOFED_RAM; LOQ_void("misc", "ii", "MemoryLoad", lpBuffer->dwMemoryLoad, "TotalPhysicalMB", lpBuffer->dwTotalPhys / (1024 * 1024)); } @@ -1190,8 +1209,8 @@ HOOKDEF(BOOL, WINAPI, GlobalMemoryStatusEx, _Out_ LPMEMORYSTATUSEX lpBuffer ) { BOOL ret = Old_GlobalMemoryStatusEx(lpBuffer); - if (ret && !g_config.no_stealth && lpBuffer->ullTotalPhys < 0x400000000) - lpBuffer->ullTotalPhys = 0x400000000; + if (ret && !g_config.no_stealth && lpBuffer->ullTotalPhys < SPOOFED_RAM) + lpBuffer->ullTotalPhys = SPOOFED_RAM; LOQ_void("misc", "ii", "MemoryLoad", lpBuffer->dwMemoryLoad, "TotalPhysicalMB", lpBuffer->ullTotalPhys / (1024 * 1024)); return ret; } @@ -1201,7 +1220,7 @@ HOOKDEF(BOOL, WINAPI, GetPhysicallyInstalledSystemMemory, ) { BOOL ret = Old_GetPhysicallyInstalledSystemMemory(TotalMemoryInKilobytes); if (ret && !g_config.no_stealth && (*TotalMemoryInKilobytes * 1024) < 0x400000000) - *TotalMemoryInKilobytes = 0x400000000 / 1024; + *TotalMemoryInKilobytes = SPOOFED_RAM / 1024; LOQ_void("misc", "i", "TotalMemoryInKilobytes", *TotalMemoryInKilobytes); return ret; } From 6da7bd7e51257e886dfe6269a7e8f15953f54a94 Mon Sep 17 00:00:00 2001 From: Kevin O'Reilly Date: Fri, 12 Dec 2025 16:49:03 +0000 Subject: [PATCH 144/148] Correct previous blundered commit --- hook_misc.c | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/hook_misc.c b/hook_misc.c index 22c24e00..2de97c84 100644 --- a/hook_misc.c +++ b/hook_misc.c @@ -114,25 +114,6 @@ HOOKDEF(LPTOP_LEVEL_EXCEPTION_FILTER, WINAPI, SetUnhandledExceptionFilter, return res; } -#define ALLOW_UNHANDLED_EXCEPTIONS 1 - -HOOKDEF(LONG, WINAPI, UnhandledExceptionFilter, - __in PEXCEPTION_POINTERS ExceptionInfo -) { - LONG ret; - if (ALLOW_UNHANDLED_EXCEPTIONS) - ret = Old_UnhandledExceptionFilter(ExceptionInfo); - else - ret = EXCEPTION_EXECUTE_HANDLER; - if (ExceptionInfo && !ExceptionInfo->ExceptionRecord->NumberParameters && (ExceptionInfo->ExceptionRecord->ExceptionCode >= 0x80000000 || g_config.log_exceptions > 1)) - LOQ_zero("process", "ppp", "ExceptionCode", ExceptionInfo->ExceptionRecord->ExceptionCode, "ExceptionAddress", ExceptionInfo->ExceptionRecord->ExceptionAddress, "ExceptionFlags", ExceptionInfo->ExceptionRecord->ExceptionFlags); - else if (ExceptionInfo->ExceptionRecord->NumberParameters == 1 && (ExceptionInfo->ExceptionRecord->ExceptionCode >= 0x80000000 || g_config.log_exceptions > 1)) - LOQ_zero("process", "pppp", "ExceptionCode", ExceptionInfo->ExceptionRecord->ExceptionCode, "ExceptionAddress", ExceptionInfo->ExceptionRecord->ExceptionAddress, "ExceptionFlags", ExceptionInfo->ExceptionRecord->ExceptionFlags, "ExceptionInformation", ExceptionInfo->ExceptionRecord->ExceptionInformation[0]); - else if (ExceptionInfo->ExceptionRecord->NumberParameters == 2 && (ExceptionInfo->ExceptionRecord->ExceptionCode >= 0x80000000 || g_config.log_exceptions > 1)) - LOQ_zero("process", "ppppp", "ExceptionCode", ExceptionInfo->ExceptionRecord->ExceptionCode, "ExceptionAddress", ExceptionInfo->ExceptionRecord->ExceptionAddress, "ExceptionFlags", ExceptionInfo->ExceptionRecord->ExceptionFlags, "ExceptionInformation[0]", ExceptionInfo->ExceptionRecord->ExceptionInformation[0], "ExceptionInformation[1]", ExceptionInfo->ExceptionRecord->ExceptionInformation[1]); - return ret; -} - PVECTORED_EXCEPTION_HANDLER SampleVectoredHandler; LONG WINAPI New_VectoredExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) @@ -1219,7 +1200,7 @@ HOOKDEF(BOOL, WINAPI, GetPhysicallyInstalledSystemMemory, _Out_ PULONGLONG TotalMemoryInKilobytes ) { BOOL ret = Old_GetPhysicallyInstalledSystemMemory(TotalMemoryInKilobytes); - if (ret && !g_config.no_stealth && (*TotalMemoryInKilobytes * 1024) < 0x400000000) + if (ret && !g_config.no_stealth && (*TotalMemoryInKilobytes * 1024) < SPOOFED_RAM) *TotalMemoryInKilobytes = SPOOFED_RAM / 1024; LOQ_void("misc", "i", "TotalMemoryInKilobytes", *TotalMemoryInKilobytes); return ret; From c820653fc4b5159d40baf4fdd4a36ded94a5e596 Mon Sep 17 00:00:00 2001 From: enzok <7831008+enzok@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:29:03 -0500 Subject: [PATCH 145/148] Add capesolo --- CAPE/Solo.c | 1565 +++++++++++++++++++++++++++++++++++++++ CAPE/uthash.h | 1137 ++++++++++++++++++++++++++++ capemon.vcxproj | 2 + capemon.vcxproj.filters | 11 +- config.c | 5 + config.h | 3 + 6 files changed, 2722 insertions(+), 1 deletion(-) create mode 100644 CAPE/Solo.c create mode 100644 CAPE/uthash.h diff --git a/CAPE/Solo.c b/CAPE/Solo.c new file mode 100644 index 00000000..e4270e5f --- /dev/null +++ b/CAPE/Solo.c @@ -0,0 +1,1565 @@ +/* +CAPE - Config And Payload Extraction +Copyright(C) 2019 kevoreilly@gmail.com + +This program is free software : you can redistribute it and / or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program.If not, see . +*/ +//#define DEBUG_COMMENTS +#include +#include +#include "..\hooking.h" +#include +#include "..\misc.h" +#include "Debugger.h" +#include "CAPE.h" +#include "uthash.h" + +#define SOLO_PIPE "\\\\.\\pipe\\debugger_pipe" +#define BUFFER_SIZE 1024*65 +#define BYTES_PER_LINE 16 +#define MAX_LINES 256 +#define MAX_STACK_SLOTS 512 +#define SLOTS_BEFORE 255 +#define EST_LINE 80 +#define INITIAL_CAPACITY 16 +#define MAX_ENTRIES ((BUFFER_SIZE - 16) / sizeof(MBIEntry)) +#define PAGE_SIZE 4096 +#define OUTPUT_BUFFER_SIZE 2048 +#define CHUNKSIZE 16 + +// Structure for MBI entry +typedef struct +{ + uintptr_t BaseAddress; + SIZE_T RegionSize; + DWORD Protect; +} MBIEntry; + +typedef struct +{ + MBIEntry* data; + size_t size; + size_t capacity; +} MBIEntryArray; + +typedef const char* (*CmdHandler)(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data); + +typedef struct +{ + char name[3]; + CmdHandler handler; + UT_hash_handle hh; +} CommandEntry; + +static BOOL CommandMapInitialized = FALSE; +static CommandEntry* CommandMap = NULL; + +extern DWORD g_terminate_event_thread_id, g_procname_watcher_thread_id, g_unhook_detect_thread_id, g_unhook_watcher_thread_id; +extern _NtQueryInformationThread pNtQueryInformationThread; +extern int StepOverRegister; +extern void DebugOutput(_In_ LPCTSTR lpOutputString, ...); + +BOOL InteractiveBreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS* ExceptionInfo); +char* InteractiveDebuggerPipe(_In_ LPCTSTR lpOutputString, ...); +char* DumpMemoryView(HANDLE hProcess, PCONTEXT ctx, ULONG_PTR RequestedAddr, int numLines); +char* GetStackWindowView(HANDLE hProcess, PCONTEXT ctx, int numSlots); +void PushBack(MBIEntryArray* array, MBIEntry entry); +void FreeArray(MBIEntryArray* array); +static BOOL SetRegister(PCONTEXT Context, char* RegString, PVOID Target); +uint32_t GetPageChecksum(HANDLE hProcess, uintptr_t Address); +BOOL InteractiveTrace(struct _EXCEPTION_POINTERS* ExceptionInfo); +void InitCommands(void); +const char* DispatchCommand(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* Command); +void RegisterCommand(const char* name, CmdHandler func); + +static CONTEXT LastContext; + +static const uint32_t crc32Table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; + +void RegisterCommand(const char* name, CmdHandler func) +{ + if (!name || strlen(name) != 2) return; + + CommandEntry* entry = (CommandEntry*)malloc(sizeof(CommandEntry)); + snprintf(entry->name, sizeof(entry->name), "%.2s", name); + entry->name[2] = '\0'; + entry->handler = func; + HASH_ADD_STR(CommandMap, name, entry); +} + +const char* DispatchCommand(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* Command) +{ + char DbgCmd[3] = { 0 }; + char* CmdData = NULL; + + if (Command && *Command) + { + char* Sep = strchr(Command, ':'); + if (Sep && Sep - Command >= 2) + { + DbgCmd[0] = Command[0]; + DbgCmd[1] = Command[1]; + DbgCmd[2] = '\0'; + CmdData = Sep + 1; + + if (!CmdData) + { + return InteractiveDebuggerPipe("Memory allocation failed for command data.\n"); + } + } + else + { + return InteractiveDebuggerPipe("Malformed command: %s\n", Command); + } + } + else + { + return InteractiveDebuggerPipe("Empty command received.\n"); + } + + CommandEntry* Entry = NULL; + HASH_FIND_STR(CommandMap, DbgCmd, Entry); + + if (Entry && Entry->handler) + { + return Entry->handler(ExceptionInfo, CmdData); + } + else + { + return InteractiveDebuggerPipe("Unknown command: %s\n", DbgCmd); + } +} + + +static BOOL ParseHex(const char* input, ULONG_PTR* output) +{ + int base = 16; + + if (!input || !*input) return FALSE; + + char* endp = NULL; + unsigned long long tmp = strtoull(input, &endp, base); + if (endp == input || *endp != '\0') return FALSE; + + *output = (ULONG_PTR)tmp; + return TRUE; +} + +void VerifyCommandMapInitialized(void) +{ + if (!CommandMapInitialized) + { + InitCommands(); + CommandMapInitialized = TRUE; + } +} + +char* InteractiveDebuggerPipe(_In_ LPCTSTR lpOutputString, ...) +{ + va_list args; + va_start(args, lpOutputString); + + CHAR DebuggerLine[BUFFER_SIZE]; + static CHAR DebuggerCommand[BUFFER_SIZE]; + CHAR TempBuffer[BUFFER_SIZE]; + int BytesRead = 0; + + memset(DebuggerLine, 0, sizeof(DebuggerLine)); + memset(TempBuffer, 0, sizeof(TempBuffer)); + memset(DebuggerCommand, 0, sizeof(DebuggerCommand)); + + _vsnprintf_s(TempBuffer, BUFFER_SIZE, _TRUNCATE, lpOutputString, args); + _snprintf_s(DebuggerLine, BUFFER_SIZE, _TRUNCATE, "BREAK:%s", TempBuffer); + + + char* Character = DebuggerLine; + while (*Character) + { // Restrict to ASCII range + if (*Character < 0x0a || *Character > 0x7E) + *Character = 0x3F; // '?' + Character++; + } + + int Length = (int)strlen(DebuggerLine); + + CallNamedPipe(SOLO_PIPE, DebuggerLine, Length, DebuggerCommand, BUFFER_SIZE, (unsigned long*)&BytesRead, NMPWAIT_WAIT_FOREVER); + + va_end(args); + + return DebuggerCommand; +} + +char* OutputRegisters(PCONTEXT Context) +{ + static char OutputBuffer[OUTPUT_BUFFER_SIZE]; + memset(OutputBuffer, 0, sizeof(OutputBuffer)); + void* teb = NtCurrentTeb(); + +#ifdef _WIN64 + size_t len = _snprintf_s(OutputBuffer, sizeof(OutputBuffer), _TRUNCATE, + "RAX: %016I64X CF:%d\n" + "RBX: %016I64X PF:%d\n" + "RCX: %016I64X AF:%d\n" + "RDX: %016I64X ZF:%d\n" + "RSI: %016I64X SF:%d\n" + "RDI: %016I64X TF:%d\n" + "RSP: %016I64X IF:%d\n" + "RBP: %016I64X DF:%d\n" + "R8 : %016I64X OF:%d\n" + "R9 : %016I64X ID:%d\n" + "R10: %016I64X NT:%d\n" + "R11: %016I64X RF:%d\n" + "R12: %016I64X VM:%d\n" + "R13: %016I64X AC:%d\n" + "R14: %016I64X VIF:%d\n" + "R15: %016I64X VIP:%d\n" + "RIP: %016I64X IOPL:%d\n\n" + "GS: %p\n\n", + Context->Rax, (Context->EFlags & 0x00000001) ? 1 : 0, + Context->Rbx, (Context->EFlags & 0x00000004) ? 1 : 0, + Context->Rcx, (Context->EFlags & 0x00000010) ? 1 : 0, + Context->Rdx, (Context->EFlags & 0x00000040) ? 1 : 0, + Context->Rsi, (Context->EFlags & 0x00000080) ? 1 : 0, + Context->Rdi, (Context->EFlags & 0x00000100) ? 1 : 0, + Context->Rsp, (Context->EFlags & 0x00000200) ? 1 : 0, + Context->Rbp, (Context->EFlags & 0x00000400) ? 1 : 0, + Context->R8, (Context->EFlags & 0x00000800) ? 1 : 0, + Context->R9, (Context->EFlags & 0x00200000) ? 1 : 0, + Context->R10, (Context->EFlags & 0x00004000) ? 1 : 0, + Context->R11, (Context->EFlags & 0x00010000) ? 1 : 0, + Context->R12, (Context->EFlags & 0x00020000) ? 1 : 0, + Context->R13, (Context->EFlags & 0x00040000) ? 1 : 0, + Context->R14, (Context->EFlags & 0x00080000) ? 1 : 0, + Context->R15, (Context->EFlags & 0x00100000) ? 1 : 0, + Context->Rip, (int)((Context->EFlags >> 12) & 0x3), + teb + ); + + const M128A* xmm = &Context->Xmm0; + for (int i = 0; i < 16; ++i) + { + size_t remaining = sizeof(OutputBuffer) > len ? sizeof(OutputBuffer) - len : 0; + if (remaining) + { + len += _snprintf_s(OutputBuffer + len, remaining, _TRUNCATE, + "XMM%02d.Low : %016I64X XMM%02d.High: %016I64X\n", + i, (unsigned __int64)xmm[i].Low, i, (unsigned __int64)xmm[i].High); + } + } +#else + size_t len = _snprintf_s(OutputBuffer, sizeof(OutputBuffer), _TRUNCATE, + "EAX: %08X CF:%d\n" + "EBX: %08X AF:%d\n" + "ECX: %08X SF:%d\n" + "EDX: %08X IF:%d\n" + "ESI: %08X OF:%d\n" + "EDI: %08X NT:%d\n" + "ESP: %08X PF:%d\n" + "EBP: %08X ZF:%d\n" + "EIP: %08X TF:%d IOPL:%d\n\n" + "FS: %p\n\n", + Context->Eax, (Context->EFlags & 0x00000001) ? 1 : 0, + Context->Ebx, (Context->EFlags & 0x00000010) ? 1 : 0, + Context->Ecx, (Context->EFlags & 0x00000080) ? 1 : 0, + Context->Edx, (Context->EFlags & 0x00000200) ? 1 : 0, + Context->Esi, (Context->EFlags & 0x00000800) ? 1 : 0, + Context->Edi, (Context->EFlags & 0x00004000) ? 1 : 0, + Context->Esp, (Context->EFlags & 0x00000004) ? 1 : 0, + Context->Ebp, (Context->EFlags & 0x00000040) ? 1 : 0, + Context->Eip, (Context->EFlags & 0x00000100) ? 1 : 0, + (int)((Context->EFlags >> 12) & 0x3), + teb + ); + + const M128A* xmm = (const M128A*)Context->ExtendedRegisters; + for (int i = 0; i < 8; ++i) + { + len += _snprintf_s(OutputBuffer + len, sizeof(OutputBuffer) - len, _TRUNCATE, + "XMM%02d.Low : %016I64X XMM%02d.High: %016I64X\n", i, xmm[i].Low, i, xmm[i].High); + } +#endif + + return InteractiveDebuggerPipe("%s\n", OutputBuffer); +} + + +uint32_t ComputeCRC32(const uint8_t* buf, size_t len) +{ + uint32_t crc = 0xFFFFFFFFU; + for (size_t i = 0; i < len; ++i) + { + crc = (crc >> 8) ^ crc32Table[(crc ^ buf[i]) & 0xFF]; + } + + return crc ^ 0xFFFFFFFFU; +} + +uint32_t GetPageChecksum(HANDLE hProcess, uintptr_t Address) { + uintptr_t base = Address & ~(PAGE_SIZE - 1); + unsigned char page[PAGE_SIZE]; + SIZE_T BytesRead = 0; + if (!ReadProcessMemory(hProcess, (LPCVOID)base, page, PAGE_SIZE, &BytesRead) || BytesRead != PAGE_SIZE) return 0; + return ComputeCRC32(page, PAGE_SIZE); +} + + +char* GetStackWindowView(HANDLE hProcess, PCONTEXT ctx, int numSlots) +{ + if (numSlots > MAX_STACK_SLOTS) numSlots = MAX_STACK_SLOTS; + + int estimate = numSlots * EST_LINE + 1; + char* out = (char*)malloc(estimate); + if (!out) return NULL; + out[0] = '\0'; + + char* p = out; + int rem = estimate; + ULONG_PTR sp; + +#ifdef _WIN64 + sp = ctx->Rsp; +#else + sp = ctx->Esp; +#endif + + ULONG_PTR offset = (ULONG_PTR)SLOTS_BEFORE * sizeof(ULONG_PTR); + ULONG_PTR base = (sp > offset) ? (sp - offset) : (ULONG_PTR)0; + + + for (int i = 0; i < numSlots; i++) + { + ULONG_PTR addr = base + (ULONG_PTR)i * sizeof(ULONG_PTR); + ULONG_PTR val = 0; + SIZE_T rd = 0; + + if (!ReadProcessMemory(hProcess, (LPCVOID)addr, &val, sizeof(val), &rd) || rd != sizeof(val)) break; + +#ifdef _WIN64 + int n = _snprintf_s(p, rem, _TRUNCATE, "%016I64X,%016I64X\n", (unsigned __int64)addr, (unsigned __int64)val); +#else + int n = _snprintf_s(p, rem, _TRUNCATE, "%08X,%08X\n", (unsigned)addr, (unsigned)val); +#endif + if (n <= 0) break; + + p += n; + rem -= n; + } + + return out; +} + +char* DumpMemoryView(HANDLE hProcess, PCONTEXT ctx, ULONG_PTR RequestedAddr, int numLines) +{ + ULONG_PTR base = RequestedAddr; + unsigned char probe; + SIZE_T rd; + + if (base == 0 || ReadProcessMemory(hProcess, (LPCVOID)base, &probe, 1, &rd) != TRUE || rd != 1) + { +#ifdef _WIN64 + base = ctx->Rsp; +#else + base = ctx->Esp; +#endif + } + + int estimate = numLines * (20 + BYTES_PER_LINE * 3 + 1) + 1; + char* out = (char*)malloc(estimate); + if (!out) return NULL; + + out[0] = '\0'; + + char* p = out; + int rem = estimate; + unsigned char buf[BYTES_PER_LINE]; + + for (int line = 0; line < numLines; line++) + { + ULONG_PTR addr = base + (ULONG_PTR)line * BYTES_PER_LINE; + SIZE_T BytesRead = 0; + + if (!ReadProcessMemory(hProcess, (LPCVOID)addr, buf, BYTES_PER_LINE, &BytesRead) || BytesRead == 0) break; + +#ifdef _WIN64 + int n = _snprintf_s(p, rem, _TRUNCATE, "%016I64X,", (unsigned __int64)addr); +#else + int n = _snprintf_s(p, rem, _TRUNCATE, "%08X,", (unsigned)addr); +#endif + if (n <= 0) break; + p += n; rem -= n; + + for (int i = 0; i < BYTES_PER_LINE; i++) + { + if (i < (int)BytesRead) + n = _snprintf_s(p, rem, _TRUNCATE, "%02X ", buf[i]); + else + n = _snprintf_s(p, rem, _TRUNCATE, " "); + if (n <= 0) break; + p += n; rem -= n; + } + + if (rem < 2) break; + *p++ = '\n'; + *p = '\0'; + rem--; + } + + return out; +} + +char* RetrievePage(HANDLE hProcess, uintptr_t Address) { + uintptr_t base = Address & ~(PAGE_SIZE - 1); + unsigned char page[PAGE_SIZE]; + SIZE_T BytesRead = 0; + + if (!ReadProcessMemory(hProcess, (LPCVOID)base, page, PAGE_SIZE, &BytesRead) || BytesRead != PAGE_SIZE) return NULL; + + char* hexPage = malloc(PAGE_SIZE * 2 + 1); + if (!hexPage) return NULL; + + for (SIZE_T i = 0; i < PAGE_SIZE; ++i) + sprintf(hexPage + i * 2, "%02X", page[i]); + + hexPage[PAGE_SIZE * 2] = '\0'; + + return hexPage; +} + +// Helper functions for dynamic array +static MBIEntryArray CreateArray(void) +{ + MBIEntryArray array = + { + .data = (MBIEntry*)malloc(INITIAL_CAPACITY * sizeof(MBIEntry)), + .size = 0, + .capacity = INITIAL_CAPACITY, + }; + return array; +} + +void PushBack(MBIEntryArray* array, MBIEntry entry) +{ + if (array->size >= MAX_ENTRIES) return; + + if (array->size + 1 >= array->capacity) + { + size_t newCap = array->capacity * 2; + if (newCap > MAX_ENTRIES) newCap = MAX_ENTRIES; + + MBIEntry* p = realloc(array->data, newCap * sizeof(MBIEntry)); + if (!p) return; + + array->data = p; + array->capacity = newCap; + } + + array->data[array->size++] = entry; +} + +void FreeArray(MBIEntryArray* array) +{ + free(array->data); + array->data = NULL; + array->size = 0; + array->capacity = 0; +} + +static BOOL SetRegister(PCONTEXT Context, char* RegString, PVOID Target) +{ + if (!Context || !RegString) + return; + + __try + { +#ifdef _WIN64 + if (!stricmp(RegString, "eax")) + (PVOID)Context->Rax = Target; + else if (!stricmp(RegString, "ebx")) + (PVOID)Context->Rbx = Target; + else if (!stricmp(RegString, "ecx")) + (PVOID)Context->Rcx = Target; + else if (!stricmp(RegString, "edx")) + (PVOID)Context->Rdx = Target; + else if (!stricmp(RegString, "esi")) + (PVOID)Context->Rsi = Target; + else if (!stricmp(RegString, "edi")) + (PVOID)Context->Rdi = Target; + else if (!stricmp(RegString, "esp")) + (PVOID)Context->Rsp = Target; + else if (!stricmp(RegString, "ebp")) + (PVOID)Context->Rbp = Target; + else if (!stricmp(RegString, "eip")) + (PVOID)Context->Rip = Target; + else if (!stricmp(RegString, "rax")) + (PVOID)Context->Rax = Target; + else if (!stricmp(RegString, "rbx")) + (PVOID)Context->Rbx = Target; + else if (!stricmp(RegString, "rcx")) + (PVOID)Context->Rcx = Target; + else if (!stricmp(RegString, "rdx")) + (PVOID)Context->Rdx = Target; + else if (!stricmp(RegString, "rsi")) + (PVOID)Context->Rsi = Target; + else if (!stricmp(RegString, "rdi")) + (PVOID)Context->Rdi = Target; + else if (!stricmp(RegString, "rsp")) + (PVOID)Context->Rsp = Target; + else if (!stricmp(RegString, "rbp")) + (PVOID)Context->Rbp = Target; + else if (!stricmp(RegString, "rip")) + (PVOID)Context->Rip = Target; + else if (!strnicmp(RegString, "r8", 2)) + (PVOID)Context->R8 = Target; + else if (!strnicmp(RegString, "r9", 2)) + (PVOID)Context->R9 = Target; + else if (!strnicmp(RegString, "r10", 3)) + (PVOID)Context->R10 = Target; + else if (!strnicmp(RegString, "r11", 3)) + (PVOID)Context->R11 = Target; + else if (!strnicmp(RegString, "r12", 3)) + (PVOID)Context->R13 = Target; + else if (!strnicmp(RegString, "r13", 3)) + (PVOID)Context->R13 = Target; + else if (!strnicmp(RegString, "r14", 3)) + (PVOID)Context->R14 = Target; + else if (!strnicmp(RegString, "r15", 3)) + (PVOID)Context->R15 = Target; +#else + if (!stricmp(RegString, "eax")) + (PVOID)Context->Eax = Target; + else if (!stricmp(RegString, "ebx")) + (PVOID)Context->Ebx = Target; + else if (!stricmp(RegString, "ecx")) + (PVOID)Context->Ecx = Target; + else if (!stricmp(RegString, "edx")) + (PVOID)Context->Edx = Target; + else if (!stricmp(RegString, "esi")) + (PVOID)Context->Esi = Target; + else if (!stricmp(RegString, "edi")) + (PVOID)Context->Edi = Target; + else if (!stricmp(RegString, "esp")) + (PVOID)Context->Esp = Target; + else if (!stricmp(RegString, "ebp")) + (PVOID)Context->Ebp = Target; + else if (!stricmp(RegString, "eip")) + (PVOID)Context->Eip = Target; +#endif + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + DebugOutput("Failed to set register %s.\n", RegString); + return FALSE; + } + + return TRUE; +} + +void InteractiveCommandHandler(struct _EXCEPTION_POINTERS* ExceptionInfo, char* InitialCommand) +{ + LastContext = *ExceptionInfo->ContextRecord; + const char* Command = InitialCommand; + while (Command && *Command) + { + const char* NextCommand = DispatchCommand(ExceptionInfo, Command); + if (strcmp(NextCommand, "__DONE__") == 0) + break; + + Command = NextCommand; + Sleep(100); + } +} + +const char* HandleInstructionPage(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + ULONG_PTR RequestedAddr = 0; + SIZE_T rd = 0; + unsigned char probe = 0; + HANDLE DebuggerProcessHandle = GetCurrentProcess(); + + if (data && *data) { + if (!ParseHex(data, &RequestedAddr)) + { + return InteractiveDebuggerPipe("Failed with invalid instruction address: %s", data); + } + + if (!ReadProcessMemory(DebuggerProcessHandle, (LPCVOID)RequestedAddr, &probe, 1, &rd) || rd != 1) + { + return InteractiveDebuggerPipe("%p|UNREADABLE", RequestedAddr); + } + } + + char* InstructionPage = RetrievePage(DebuggerProcessHandle, RequestedAddr); + if (InstructionPage) + { + const char* Command = InteractiveDebuggerPipe("%p|%s", RequestedAddr, InstructionPage); + free(InstructionPage); + return Command; + } + else + { + return InteractiveDebuggerPipe("%p|NODATA", RequestedAddr); + } +} + +const char* HandlePageMap(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + MEMORY_BASIC_INFORMATION mbi; + MBIEntryArray entries = CreateArray(); + PBYTE address = NULL; + + while (VirtualQueryEx(GetCurrentProcess(), (LPCVOID)address, &mbi, sizeof(mbi)) == sizeof(mbi)) + { + MBIEntry entry = { (uintptr_t)mbi.BaseAddress, mbi.RegionSize, mbi.Protect }; + PushBack(&entries, entry); + address = (PBYTE)mbi.BaseAddress + mbi.RegionSize; + } + + const char* Command = NULL; + + if (entries.size > 0) + { + size_t cap = entries.size * 64 + 1; + char* payload = malloc(cap); + if (payload) + { + char* p = payload; + for (size_t i = 0; i < entries.size; ++i) + { + int n = sprintf(p, "0x%Ix,%Iu,0x%x", entries.data[i].BaseAddress, entries.data[i].RegionSize, entries.data[i].Protect); + p += n; + if (i + 1 < entries.size) *p++ = '|'; + } + + *p = '\0'; + Command = InteractiveDebuggerPipe("%s\n", payload); + free(payload); + } + } + else + { + return InteractiveDebuggerPipe("Failed with no memory regions found.\n"); + } + + FreeArray(&entries); + return Command; +} + +const char* HandleRegisters(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + return OutputRegisters(&LastContext); +} + +const char* HandleContinue(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + ClearSingleStepMode(ExceptionInfo->ContextRecord); + CommandEntry* entry; + CommandEntry* tmp; + + HASH_ITER(hh, CommandMap, entry, tmp) + { + HASH_DEL(CommandMap, entry); + free(entry); + } + + CommandMap = NULL; + CommandMapInitialized = FALSE; + return "__DONE__"; +} + +const char* HandleStepIn(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) { + if (SetSingleStepMode(ExceptionInfo->ContextRecord, InteractiveTrace)) + { + LastContext = *ExceptionInfo->ContextRecord; + return "__DONE__"; + } + return InteractiveDebuggerPipe("Failed to set single step mode.\n"); +} + +const char* HandleStepOver(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + PVOID cip; +#ifdef _WIN64 + cip = (PVOID)ExceptionInfo->ContextRecord->Rip; +#else + cip = (PVOID)ExceptionInfo->ContextRecord->Eip; +#endif + _DecodedInst inst; + unsigned int count = 0; + + _DecodeResult res = distorm_decode(0, (const unsigned char*)cip, 32, sizeof(void*) == 8 ? Decode64Bits : Decode32Bits, &inst, 1, &count); + + if (inst.size == 0) + { + return InteractiveDebuggerPipe("Failed could not disassemble instruction at 0x%p\n", cip); + } + + PVOID RetAddr = (PVOID)((PUCHAR)cip + inst.size); + if (ContextSetNextAvailableBreakpoint(ExceptionInfo->ContextRecord, &StepOverRegister, 0, (BYTE*)RetAddr, 0, 1, InteractiveBreakpointCallback)) + { + LastContext = *ExceptionInfo->ContextRecord; + ClearSingleStepMode(ExceptionInfo->ContextRecord); + RetAddr = NULL; + return "__DONE__"; + } + else + { + return InteractiveDebuggerPipe("Failed to set step-over breakpoint at 0x%p\n", RetAddr); + } +} + +const char* HandleStepOut(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ +#ifdef _WIN64 + PVOID StackPtr = (PVOID)ExceptionInfo->ContextRecord->Rsp; + SIZE_T AddressSize = sizeof(ULONG64); +#else + PVOID StackPtr = (PVOID)ExceptionInfo->ContextRecord->Esp; + SIZE_T AddressSize = sizeof(ULONG32); +#endif + + ULONG_PTR ReturnAddress = 0; + SIZE_T BytesRead = 0; + + if (!ReadProcessMemory(GetCurrentProcess(), StackPtr, &ReturnAddress, AddressSize, &BytesRead) || BytesRead != AddressSize) + { + return InteractiveDebuggerPipe("Failed to read return address\n"); + } + + if (ContextSetNextAvailableBreakpoint(ExceptionInfo->ContextRecord, &StepOverRegister, 0, (BYTE*)ReturnAddress, 0, 1, InteractiveBreakpointCallback)) + { + ClearSingleStepMode(ExceptionInfo->ContextRecord); + LastContext = *ExceptionInfo->ContextRecord; + return "__DONE__"; + } + + return InteractiveDebuggerPipe("Failed to set step-out breakpoint at 0x%p\n", (PVOID)ReturnAddress); +} + +const char* HandleMemoryDump(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + ULONG_PTR RequestedAddr = 0; + SIZE_T RequestedSize = 0; + SIZE_T BytesRead = 0; + unsigned char Probe = 0; + HANDLE ProcessHandle = GetCurrentProcess(); + + if (data && *data) + { + char* SizeSep = strchr(data, '|'); + if (SizeSep) *SizeSep++ = '\0'; + + if (!ParseHex(data, &RequestedAddr)) + return InteractiveDebuggerPipe("Failed with invalid dump address: %s\n", data); + + if (SizeSep && *SizeSep) + { + if (!ParseHex(SizeSep, &RequestedSize)) + return InteractiveDebuggerPipe("Failed with invalid dump size: %s\n", SizeSep); + + if (RequestedSize > OUTPUT_BUFFER_SIZE) + return InteractiveDebuggerPipe("Failed: requested size %zu exceeds max buffer size %d.\n", RequestedSize, OUTPUT_BUFFER_SIZE); + + unsigned char* Buffer = (unsigned char*)malloc(RequestedSize); + if (!Buffer) + return InteractiveDebuggerPipe("Failed with memory allocation.\n"); + + if (!ReadProcessMemory(ProcessHandle, (LPCVOID)RequestedAddr, Buffer, RequestedSize, &BytesRead) || BytesRead != RequestedSize) + { + free(Buffer); + return InteractiveDebuggerPipe("0x%p|Failed with unreadable memory\n", (PVOID)RequestedAddr); + } + + char* HexOutput = (char*)malloc(RequestedSize * 2 + 1); + if (!HexOutput) + { + free(Buffer); + return InteractiveDebuggerPipe("0x%p|Failed with hex formatting.\n", (PVOID)RequestedAddr); + } + + for (SIZE_T I = 0; I < RequestedSize; ++I) + { + sprintf(HexOutput + I * 2, "%02X", Buffer[I]); + } + + const char* Command = InteractiveDebuggerPipe("0x%p|%s\n", (PVOID)RequestedAddr, HexOutput); + free(HexOutput); + free(Buffer); + return Command; + } + + if (!ReadProcessMemory(ProcessHandle, (LPCVOID)RequestedAddr, &Probe, 1, &BytesRead) || BytesRead != 1) + { + return InteractiveDebuggerPipe("0x%p|Failed with unreadable dump address\n", (PVOID)RequestedAddr); + } + } + + char* MemDump = DumpMemoryView(ProcessHandle, ExceptionInfo->ContextRecord, RequestedAddr, MAX_LINES); + if (MemDump) + { + const char* Command = InteractiveDebuggerPipe("0x%p|%s\n", (PVOID)RequestedAddr, MemDump); + free(MemDump); + return Command; + } + + return InteractiveDebuggerPipe("Failed to dump memory.\n"); +} + + +const char* HandleStackView(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + HANDLE ProcessHandle = GetCurrentProcess(); + char* StackDump = GetStackWindowView(ProcessHandle, ExceptionInfo->ContextRecord, MAX_STACK_SLOTS); + + if (StackDump) + { + const char* Command = InteractiveDebuggerPipe("%s\n", StackDump); + free(StackDump); + return Command; + } + + return InteractiveDebuggerPipe("Failed to dump stack view.\n"); +} + +const char* HandleListBreakpoints(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + CONTEXT* ctx = ExceptionInfo->ContextRecord; + int len = 0; + const int MaxPerLine = 32; + const int MaxEntries = 4; + + + size_t BufSize = MaxEntries * MaxPerLine + 1; + char* Output = (char*)malloc(BufSize); + if (!Output) + { + return InteractiveDebuggerPipe("Failed to allocate memory.\n"); + } + + ULONG_PTR dr[4] = + { + (ULONG_PTR)ctx->Dr0, + (ULONG_PTR)ctx->Dr1, + (ULONG_PTR)ctx->Dr2, + (ULONG_PTR)ctx->Dr3 + }; + + for (int i = 0; i < 4; ++i) + { + if (dr[i]) + { + len += sprintf(Output + len, "%d,%p|", i, (PVOID)dr[i]); + } + } + + if (len == 0) + { + free(Output); + return InteractiveDebuggerPipe("No hardware breakpoints set.\n"); + } + + if (Output[len - 1] == '|') Output[len - 1] = '\0'; + + const char* Command = InteractiveDebuggerPipe("%s\n", Output); + free(Output); + return Command; +} + +const char* HandleFlagMod(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + if (!data || !*data) + { + return InteractiveDebuggerPipe("Failed missing flag directive.\n"); + } + + if (_stricmp(data, "ClearZeroFlag") == 0) ClearZeroFlag(ExceptionInfo->ContextRecord); + else if (_stricmp(data, "SetZeroFlag") == 0) SetZeroFlag(ExceptionInfo->ContextRecord); + else if (_stricmp(data, "FlipZeroFlag") == 0) FlipZeroFlag(ExceptionInfo->ContextRecord); + else if (_stricmp(data, "ClearSignFlag") == 0) ClearSignFlag(ExceptionInfo->ContextRecord); + else if (_stricmp(data, "SetSignFlag") == 0) SetSignFlag(ExceptionInfo->ContextRecord); + else if (_stricmp(data, "FlipSignFlag") == 0) FlipSignFlag(ExceptionInfo->ContextRecord); + else if (_stricmp(data, "ClearCarryFlag") == 0) ClearCarryFlag(ExceptionInfo->ContextRecord); + else if (_stricmp(data, "SetCarryFlag") == 0) SetCarryFlag(ExceptionInfo->ContextRecord); + else if (_stricmp(data, "FlipCarryFlag") == 0) FlipCarryFlag(ExceptionInfo->ContextRecord); + else + { + return InteractiveDebuggerPipe("Failed invalid flag modifier: %s\n", data); + } + + return OutputRegisters(ExceptionInfo->ContextRecord); +} + +const char* HandleRunUntil(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + ULONG_PTR Addr = 0; + if (!data || !ParseHex(data, &Addr)) + { + return InteractiveDebuggerPipe("Failed with invalid run-until address: %s\n", data); + } + + if (ContextSetNextAvailableBreakpoint(ExceptionInfo->ContextRecord, &StepOverRegister, 0, (BYTE*)Addr, 0, 1, InteractiveBreakpointCallback)) + { + ClearSingleStepMode(ExceptionInfo->ContextRecord); + LastContext = *ExceptionInfo->ContextRecord; + return "__DONE__"; + } + + return InteractiveDebuggerPipe("Failed to set run-until breakpoint at 0x%p\n", (PVOID)Addr); +} + +const char* HandleListThreads(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + DWORD ExcludeTids[] = + { + g_terminate_event_thread_id, + g_procname_watcher_thread_id, + g_unhook_detect_thread_id, + g_unhook_watcher_thread_id + }; + + size_t ExcludeTidCount = sizeof(ExcludeTids) / sizeof(ExcludeTids[0]); + + typedef enum _THREADINFOCLASS + { + ThreadBasicInformation, + ThreadQuerySetWin32StartAddress = 9 + } THREADINFOCLASS; + + typedef NTSTATUS(NTAPI* PFN_NTQIT)(HANDLE, THREADINFOCLASS, PVOID, ULONG, PULONG); + + PFN_NTQIT pNtQIT = (PFN_NTQIT)pNtQueryInformationThread; + + DWORD pid = GetCurrentProcessId(); + DWORD CurrentTid = GetCurrentThreadId(); + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + THREADENTRY32 te = { sizeof(te) }; + NTSTATUS status; + + if (hSnap == INVALID_HANDLE_VALUE) + { + return InteractiveDebuggerPipe("Failed to snapshot threads.\n"); + } + + size_t capacity = OUTPUT_BUFFER_SIZE; + char* Output = (char*)malloc(capacity); + + if (!Output) + { + CloseHandle(hSnap); + return InteractiveDebuggerPipe("Faile memory allocation failed.\n"); + } + + int len = 0; + + for (BOOL ok = Thread32First(hSnap, &te); ok; ok = Thread32Next(hSnap, &te)) + { + if (te.th32OwnerProcessID != pid) continue; + + BOOL skip = FALSE; + for (size_t i = 0; i < ExcludeTidCount; ++i) + { + if (te.th32ThreadID == ExcludeTids[i]) + { + skip = TRUE; + break; + } + } + + if (skip) continue; + + const char* mark = (te.th32ThreadID == CurrentTid) ? "+" : "-"; + + HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION, FALSE, te.th32ThreadID); + PVOID StartAddr = NULL; + + if (hThread && pNtQIT) + { + status = pNtQIT(hThread, ThreadQuerySetWin32StartAddress, &StartAddr, sizeof(StartAddr), NULL); + if (!NT_SUCCESS(status)) StartAddr = NULL; + } + + int needed = snprintf(NULL, 0, "%s|%lu|%p\n", mark, te.th32ThreadID, StartAddr); + if ((size_t)(len + needed + 1) >= capacity) + { + capacity *= 2; + char* NewOutput = (char*)realloc(Output, capacity); + if (!NewOutput) + { + free(Output); + CloseHandle(hSnap); + return InteractiveDebuggerPipe("Buffer reallocation failed.\n"); + } + Output = NewOutput; + } + + len += sprintf(Output + len, "%s|%lu|%p\n", mark, te.th32ThreadID, StartAddr); + } + + CloseHandle(hSnap); + + if (len > 0) + { + const char* Command = InteractiveDebuggerPipe("%s\n", Output); + free(Output); + return Command; + } + else + { + free(Output); + return InteractiveDebuggerPipe("Failed no modules found.\n"); + } +} + +const char* HandleListModules(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + DWORD Pid = GetCurrentProcessId(); + HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, Pid); + if (Snapshot == INVALID_HANDLE_VALUE) + return InteractiveDebuggerPipe("Failed to snapshot modules.\n"); + + MODULEENTRY32 Me = { 0 }; + Me.dwSize = sizeof(Me); + + size_t capacity = OUTPUT_BUFFER_SIZE * 2; + char* Output = (char*)malloc(capacity); + if (!Output) + { + CloseHandle(Snapshot); + return InteractiveDebuggerPipe("Memory allocation failed.\n"); + } + + int Len = 0; + + if (Module32First(Snapshot, &Me)) + { + do + { + int needed = snprintf( + NULL, + 0, + "%p,%08X,%s,%s|", + Me.modBaseAddr, + Me.modBaseSize, + Me.szModule, + Me.szExePath + ); + + if ((size_t)(Len + needed + 1) >= capacity) + { + capacity *= 2; + char* NewOutput = (char*)realloc(Output, capacity); + if (!NewOutput) + { + free(Output); + CloseHandle(Snapshot); + return InteractiveDebuggerPipe("Buffer reallocation failed.\n"); + } + Output = NewOutput; + } + + Len += sprintf( + Output + Len, + "%p,%08X,%s,%s|", + Me.modBaseAddr, + Me.modBaseSize, + Me.szModule, + Me.szExePath + ); + + } while (Module32Next(Snapshot, &Me)); + } + + CloseHandle(Snapshot); + + if (Len > 0) + { + if (Output[Len - 1] == '|') Output[Len - 1] = '\0'; + const char* Command = InteractiveDebuggerPipe("%s\n", Output); + free(Output); + return Command; + } + else + { + free(Output); + return InteractiveDebuggerPipe("Failed no modules found.\n"); + } +} + + +const char* HandleSetBreakpoint(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + if (!data && !*data) + return InteractiveDebuggerPipe("Invalid breakpoint command: missing input.\n"); + + char Input[MAX_PATH]; + strncpy_s(Input, sizeof(Input), data, _TRUNCATE); + char* Sep = strchr(Input, '|'); + if (!Sep) + return InteractiveDebuggerPipe("Failed with malformed breakpoint command (missing '|')\n"); + + *Sep = '\0'; + const char* RegStr = Input; + const char* AddrStr = Sep + 1; + int Register = -1; + + if (strcmp(RegStr, "next") != 0) + { + char* Endp = NULL; + long r = strtol(RegStr, &Endp, 0); + if (Endp == RegStr || *Endp != '\0' || r < 0 || r > 3) + return InteractiveDebuggerPipe("Failed with invalid register: %s\n", RegStr); + + Register = (int)r; + } + + char* Endp = NULL; + unsigned long long addr = strtoull(AddrStr, &Endp, 0); + if (Endp == AddrStr || *Endp != '\0') + return InteractiveDebuggerPipe("Failed with invalid breakpoint address: %s\n", AddrStr); + + ULONG_PTR BpAddress = (ULONG_PTR)addr; + if (Register == -1) + { + if (ContextSetNextAvailableBreakpoint(ExceptionInfo->ContextRecord, &StepOverRegister, 0, (BYTE*)BpAddress, BP_EXEC, 0, InteractiveBreakpointCallback)) + { + return InteractiveDebuggerPipe("Breakpoint %d set at 0x%p\n", StepOverRegister, (PVOID)BpAddress); + } + else + { + return InteractiveDebuggerPipe("Failed to set breakpoint %d at 0x%p\n", StepOverRegister, (PVOID)BpAddress); + } + } + else + { + if (ContextSetThreadBreakpoint(ExceptionInfo->ContextRecord, Register, 0, (BYTE*)BpAddress, BP_EXEC, 0, InteractiveBreakpointCallback)) + { + return InteractiveDebuggerPipe("Breakpoint %d set at 0x%p\n", Register, (PVOID)BpAddress); + } + else + { + return InteractiveDebuggerPipe("Failed to set breakpoint %d at 0x%p\n", Register, (PVOID)BpAddress); + } + } +} + +const char* HandleDeleteBreakpoint(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + if (!data || !*data) + return InteractiveDebuggerPipe("Failed to delete breakpoint: missing input.\n"); + + char* Endptr = NULL; + unsigned long idx = strtoul(data, &Endptr, 0); + if (Endptr == data || *Endptr != '\0' || idx > 3) + return InteractiveDebuggerPipe("Failed to delete breakpoint: invalid breakpoint index '%s'\n", data); + + int index = (int)idx; + CONTEXT* ctx = ExceptionInfo->ContextRecord; + ULONG_PTR BpAddress = 0; + switch (index) { + case 0: BpAddress = ctx->Dr0; break; + case 1: BpAddress = ctx->Dr1; break; + case 2: BpAddress = ctx->Dr2; break; + case 3: BpAddress = ctx->Dr3; break; + } + + if (!ContextClearBreakpoint(ExceptionInfo->ContextRecord, index)) + return InteractiveDebuggerPipe("Failed to clear breakpoint index %d\n", index); + + return InteractiveDebuggerPipe("Breakpoint %d cleared at 0x%p\n", index, (PVOID)BpAddress); +} + +const char* HandleExports(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + if (!data || !*data) + return InteractiveDebuggerPipe("Failed to get exports: missing input.\n"); + + char input[MAX_PATH]; + strncpy_s(input, sizeof(input), data, _TRUNCATE); + char* sep = strchr(input, '|'); + + if (!sep) + { + return InteractiveDebuggerPipe("Failed to get exports: malformed command.\n"); + } + + *sep = '\0'; + char OriginalModName[MAX_MODULE_NAME32]; + strcpy_s(OriginalModName, sizeof(OriginalModName), input); + int page = atoi(sep + 1); + char modulePath[MAX_PATH] = { 0 }; + BOOL found = FALSE; + + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetCurrentProcessId()); + MODULEENTRY32 me = { .dwSize = sizeof(me) }; + + if (Module32First(hSnap, &me)) + { + do + { + char modNameLower[MAX_MODULE_NAME32]; + strcpy_s(modNameLower, sizeof(modNameLower), OriginalModName); + _strlwr_s(modNameLower, sizeof(modNameLower)); + + char snapNameLower[MAX_MODULE_NAME32]; + strcpy_s(snapNameLower, sizeof(snapNameLower), me.szModule); + _strlwr_s(snapNameLower, sizeof(snapNameLower)); + + if (strcmp(snapNameLower, modNameLower) == 0) + { + strcpy_s(modulePath, sizeof(modulePath), me.szExePath); + found = TRUE; + break; + } + } while (Module32Next(hSnap, &me)); + } + CloseHandle(hSnap); + + if (!found) + { + return InteractiveDebuggerPipe("Failed to get exports: module not found.\n"); + } + + HMODULE hMod = LoadLibraryExA(modulePath, NULL, DONT_RESOLVE_DLL_REFERENCES); + if (!hMod) + { + return InteractiveDebuggerPipe("Failed to get exports: module not loaded.\n"); + } + + const int MaxSymbols = BUFFER_SIZE; + const size_t MaxEntryLen = 256; + char** entries = (char**)malloc(MaxSymbols * sizeof(char*)); + int EntryCount = 0; + + BYTE* base = (BYTE*)hMod; + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); + + DWORD rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + DWORD RvaSize = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + + if (rva && RvaSize && !IsBadReadPtr(base + rva, RvaSize)) + { + PIMAGE_EXPORT_DIRECTORY ed = (PIMAGE_EXPORT_DIRECTORY)(base + rva); + DWORD* FuncRvas = (DWORD*)(base + ed->AddressOfFunctions); + DWORD* NameRvas = (DWORD*)(base + ed->AddressOfNames); + WORD* ordinals = (WORD*)(base + ed->AddressOfNameOrdinals); + + for (DWORD i = 0; i < ed->NumberOfFunctions && EntryCount < MaxSymbols; ++i) + { + DWORD FuncRva = FuncRvas[i]; + if (!FuncRva) continue; + + const char* name = NULL; + for (DWORD j = 0; j < ed->NumberOfNames; ++j) + { + if (ordinals[j] == i) + { + DWORD NameOffset = NameRvas[j]; + if (NameOffset > nt->OptionalHeader.SizeOfImage) break; + const char* TestName = (const char*)(base + NameOffset); + if (!IsBadReadPtr(TestName, 1)) + name = TestName; + break; + } + } + + char fallback[32]; + if (!name) + { + snprintf(fallback, sizeof(fallback), "ord_%04u", ed->Base + i); + name = fallback; + } + + uintptr_t AbsAddr = (uintptr_t)me.modBaseAddr + FuncRva; + + char* entry = (char*)malloc(MaxEntryLen); + if (!entry) continue; + + int written = snprintf(entry, MaxEntryLen, "%llu,%s", (unsigned long long)AbsAddr, name); + if (written <= 0 || written >= MaxEntryLen) + { + free(entry); + continue; + } + + entries[EntryCount++] = entry; + } + } + + FreeLibrary(hMod); + + const int SymbolsPerPage = 512; + int start = page * SymbolsPerPage; + int end = start + SymbolsPerPage; + int CurLen = 0; + + char* PageBuffer = (char*)malloc(BUFFER_SIZE); + if (!PageBuffer) + { + for (int i = 0; i < EntryCount; i++) free(entries[i]); + free(entries); + return InteractiveDebuggerPipe("Failed memory allocation failed.\n"); + } + + for (int i = start; i < EntryCount && i < end; i++) + { + int len = (int)strlen(entries[i]); + if (CurLen + len + 1 >= BUFFER_SIZE - 64) + break; + + if (CurLen > 0) PageBuffer[CurLen++] = '|'; + memcpy(PageBuffer + CurLen, entries[i], len); + CurLen += len; + } + + BOOL HasMore = (end < EntryCount); + strcat_s(PageBuffer, BUFFER_SIZE, HasMore ? "||MORE" : "||END"); + + for (int i = 0; i < EntryCount; i++) free(entries[i]); + free(entries); + + char* FinalBuffer = (char*)malloc(BUFFER_SIZE); + if (!FinalBuffer) + { + free(PageBuffer); + return InteractiveDebuggerPipe("Memory allocation failed.\n"); + } + + _snprintf_s(FinalBuffer, BUFFER_SIZE, _TRUNCATE, "%s||%s", OriginalModName, PageBuffer); + + free(PageBuffer); + if (strlen(FinalBuffer) >= BUFFER_SIZE - 1) + DebugOutput("Failed: Exports final buffer overflow for %s\n", OriginalModName); + + const char* Command = InteractiveDebuggerPipe("%s\n", FinalBuffer); + free(FinalBuffer); + return Command; +} + +const char* HandleSetRegister(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + if (!data || !*data) + return InteractiveDebuggerPipe("Failed with invalid input data.\n"); + + char InputBuffer[MAX_PATH]; + strncpy_s(InputBuffer, sizeof(InputBuffer), data, _TRUNCATE); + + char* Sep = strchr(InputBuffer, '|'); + if (!Sep) + return InteractiveDebuggerPipe("Failed with malformed command, expected REGISTER|VALUE.\n"); + + *Sep = '\0'; + char RegName[16]; + char ValueStr[32]; + + strcpy_s(RegName, sizeof(RegName), InputBuffer); + strcpy_s(ValueStr, sizeof(ValueStr), Sep + 1); + + ULONG_PTR NewValue = 0; + if (!ParseHex(ValueStr, &NewValue)) + return InteractiveDebuggerPipe("Failed with invalid value for register %s: %s.", RegName, ValueStr); + + LastContext = *ExceptionInfo->ContextRecord; + BOOL result = SetRegister(&LastContext, RegName, NewValue); + if (!result) + return InteractiveDebuggerPipe("Failed to set register %s to %llu.\n", RegName, NewValue); + + return OutputRegisters(&LastContext); +} + +const char* HandleNopInstruction(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + ULONG_PTR Address = 0; + if (!data || !ParseHex(data, &Address)) + return InteractiveDebuggerPipe("Invalid instruction address: %s\n", data); + + _DecodeType DecodeType; + _DecodeResult Result; + _OffsetType Offset = 0; + _DecodedInst DecodedInstruction; + unsigned int DecodedInstructionsCount = 0; + DWORD OldProtect; + +#ifdef _WIN64 + DecodeType = Decode64Bits; +#else + DecodeType = Decode32Bits; +#endif + + if (Address) + Result = distorm_decode(Offset, (const unsigned char*)Address, CHUNKSIZE, DecodeType, &DecodedInstruction, 1, &DecodedInstructionsCount); + + if (!DecodedInstruction.size) + return InteractiveDebuggerPipe("Failed Nop instruction at 0x%p\n", Address); + + VirtualProtect(Address, DecodedInstruction.size, PAGE_EXECUTE_READWRITE, &OldProtect); + for (unsigned int i = 0; i < DecodedInstruction.size; i++) + *((BYTE*)Address + i) = 0x90; + + VirtualProtect(Address, DecodedInstruction.size, OldProtect, &OldProtect); + return InteractiveDebuggerPipe("%p|%u\n", Address, DecodedInstruction.size); +} + +const char* HandlePatchBytes(struct _EXCEPTION_POINTERS* ExceptionInfo, const char* data) +{ + if (!data && !*data) + return InteractiveDebuggerPipe("Failed with invalid input data.\n"); + + char* Sep = strchr(data, '|'); + if (!Sep) + return InteractiveDebuggerPipe("Failed with bad data format\n"); + + *Sep++ = '\0'; + ULONG_PTR Address = 0; + + if (!ParseHex(data, &Address)) + return InteractiveDebuggerPipe("Failed with invalid patch address: %s\n", data); + + size_t HexLen = strlen(Sep); + size_t MaxBytes = HexLen / 2 + 1; + BYTE* Patch = (BYTE*)malloc(MaxBytes); + if (!Patch) + return InteractiveDebuggerPipe("Failed with memory allocation.\n"); + + size_t ByteCount = 0; + char* HexPtr = Sep; + while (HexPtr) + { + while (*HexPtr && isspace((unsigned char)*HexPtr)) HexPtr++; + + if (!HexPtr) break; + + if (!isxdigit((unsigned char)*HexPtr) || !isxdigit((unsigned char)*(HexPtr + 1))) break; + + char HexByte[3] = { *HexPtr, *(HexPtr + 1), '\0' }; + Patch[ByteCount++] = (BYTE)strtol(HexByte, NULL, 16); + HexPtr += 2; + } + + if (ByteCount == 0) + { + free(Patch); + return InteractiveDebuggerPipe("Failed with no bytes to patch.\n"); + } + + if (!Address || !IsAddressAccessible(Address)) + return InteractiveDebuggerPipe("Failed address is not accessible: 0x%p", Address); + + DWORD OldProtect; + if (!VirtualProtect(Address, ByteCount, PAGE_EXECUTE_READWRITE, &OldProtect)) + return InteractiveDebuggerPipe("Failed unable to change memory protection at 0x%p", Address); + + BYTE* dest = (BYTE*)Address; + BYTE* src = Patch; + for (size_t i = 0; i < ByteCount; ++i, ++dest, ++src) + { + *dest = *src; + } + + VirtualProtect(Address, ByteCount, OldProtect, &OldProtect); + free(Patch); + + return InteractiveDebuggerPipe("Patched %p|%u\n", Address, ByteCount); +} + +void InitCommands(void) +{ + RegisterCommand("IN", HandleInstructionPage); + RegisterCommand("PM", HandlePageMap); + RegisterCommand("RG", HandleRegisters); + RegisterCommand("CT", HandleContinue); + RegisterCommand("SI", HandleStepIn); + RegisterCommand("SO", HandleStepOver); + RegisterCommand("OU", HandleStepOut); + RegisterCommand("SK", HandleStackView); + RegisterCommand("MD", HandleMemoryDump); + RegisterCommand("LB", HandleListBreakpoints); + RegisterCommand("FL", HandleFlagMod); + RegisterCommand("RU", HandleRunUntil); + RegisterCommand("TH", HandleListThreads); + RegisterCommand("LM", HandleListModules); + RegisterCommand("BP", HandleSetBreakpoint); + RegisterCommand("DB", HandleDeleteBreakpoint); + RegisterCommand("EX", HandleExports); + RegisterCommand("SR", HandleSetRegister); + RegisterCommand("NI", HandleNopInstruction); + RegisterCommand("PB", HandlePatchBytes); +} + +BOOL InteractiveTrace(struct _EXCEPTION_POINTERS* ExceptionInfo) +{ + return InteractiveBreakpointCallback(NULL, ExceptionInfo); +} + +BOOL InteractiveBreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS* ExceptionInfo) +{ + PVOID CIP; + char* Command = NULL; + +#ifdef _WIN64 + CIP = (PVOID)ExceptionInfo->ContextRecord->Rip; +#else + CIP = (PVOID)ExceptionInfo->ContextRecord->Eip; +#endif + + if (pBreakpointInfo) + { + if (StepOverRegister != -1 && pBreakpointInfo->Register == StepOverRegister) + { + StepOverRegister = -1; + } + Command = InteractiveDebuggerPipe("Breakpoint %i => 0x%p\n", pBreakpointInfo->Register, CIP); + } + else + { + Command = InteractiveDebuggerPipe("Single step at 0x%p\n", CIP); + } + + VerifyCommandMapInitialized(); + InteractiveCommandHandler(ExceptionInfo, Command); + + return TRUE; +} \ No newline at end of file diff --git a/CAPE/uthash.h b/CAPE/uthash.h new file mode 100644 index 00000000..07ae09e0 --- /dev/null +++ b/CAPE/uthash.h @@ -0,0 +1,1137 @@ +/* +Copyright (c) 2003-2025, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_NO_STDINT) && HASH_NO_STDINT +/* The user doesn't have , and must figure out their own way + to provide definitions for uint8_t and uint32_t. */ +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) ((bv[(idx)/8U] & (1U << ((idx)%8U))) != 0) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) 1 +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ \ No newline at end of file diff --git a/capemon.vcxproj b/capemon.vcxproj index 3178bdbf..40811cd7 100644 --- a/capemon.vcxproj +++ b/capemon.vcxproj @@ -228,6 +228,7 @@ + @@ -444,6 +445,7 @@ + diff --git a/capemon.vcxproj.filters b/capemon.vcxproj.filters index 95be3a61..223642a4 100644 --- a/capemon.vcxproj.filters +++ b/capemon.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -294,6 +294,9 @@ Source Files + + Source Files\CAPE + Source Files @@ -443,6 +446,12 @@ Header Files\CAPE + + Header Files + + + Header Files\CAPE + diff --git a/config.c b/config.c index 34bd4978..ebaffbd7 100644 --- a/config.c +++ b/config.c @@ -1393,6 +1393,11 @@ void parse_config_line(char* line) if (g_config.interactive == 1) DebugOutput("Interactive desktop enabled.\n"); } + else if (!stricmp(key, "idbg")) { + g_config.idbg = value[0] == '1'; + if (g_config.idbg) + DebugOutput("Interactive debugger enabled (CAPEsolo)\n"); + } else if (!stricmp(key, "snaps")) { g_config.snaps = value[0] == '1'; if (g_config.snaps) diff --git a/config.h b/config.h index 8b1bb20d..d4377910 100644 --- a/config.h +++ b/config.h @@ -220,6 +220,9 @@ struct _g_config { // Enable debugger int debugger; + // Enable interactive debugger (CAPEsolo) + int idbg; + // Fake RDTSC int fake_rdtsc; From 21d41bed83a177d42f7eb8e8e921f2ee66234091 Mon Sep 17 00:00:00 2001 From: enzok <7831008+enzok@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:32:51 -0500 Subject: [PATCH 146/148] Add interactive breakpoint support and string formatting Introduce `_pipe_sprintf` for formatted string output and `InteractiveBreakpointCallback` for handling interactive breakpoints. Update `SetConfigBP` and `SetInitialBreakpoints` to conditionally use the new callback based on configuration, enhancing breakpoint functionality for interactive debugging. --- CAPE/Trace.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 4f580b36..69fb1a22 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -52,6 +52,8 @@ extern void loq(int index, const char *category, const char *name, extern void log_flush(); extern PVOID KiUserExceptionDispatcher; extern lookup_t SoftBPs, SyscallBPs; +extern int _pipe_sprintf(char *out, const char *fmt, va_list args); +extern BOOL InteractiveBreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS *ExceptionInfo); char *ModuleName, *PreviousModuleName; PVOID ModuleBase, DumpAddress, ReturnAddress, BreakOnReturnAddress, PreviousJumps[4], GuardedPages; @@ -2831,7 +2833,12 @@ BOOL SetConfigBP(PVOID ImageBase, DWORD Register, PVOID Address) HitCount = g_config.hc3; } - if (SetBreakpoint(Register, 0, BreakpointVA, Type, HitCount, BreakpointCallback)) + PVOID Callback = BreakpointCallback; + + if (g_config.idbg) + Callback = InteractiveBreakpointCallback; + + if (SetBreakpoint(Register, 0, BreakpointVA, Type, HitCount, Callback)) { DebugOutput("SetInitialBreakpoints: Breakpoint %d set on address 0x%p (RVA 0x%x, type %d, hit count %d, thread %d)\n", Register, BreakpointVA, Address, Type, HitCount, GetCurrentThreadId()); BreakpointsSet = TRUE; @@ -2887,7 +2894,12 @@ BOOL SetInitialBreakpoints(PVOID ImageBase) // break-on-entrypoint uses bp0 Register = EntryPointRegister - 1; - if (SetBreakpoint(Register, 0, (BYTE*)EntryPoint, BP_EXEC, 1, BreakpointCallback)) + PVOID Callback = BreakpointCallback; + + if (g_config.idbg) + Callback = InteractiveBreakpointCallback; + + if (SetBreakpoint(Register, 0, (BYTE*)EntryPoint, BP_EXEC, 1, Callback)) { DebuggerOutput("Breakpoint %d set on entry point at 0x%p\n", Register, EntryPoint); BreakpointsSet = TRUE; From 8ddd7a0ab8ee3a28103ff971ad5bb57d7ae84f16 Mon Sep 17 00:00:00 2001 From: enzok <7831008+enzok@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:35:37 -0500 Subject: [PATCH 147/148] Remove _pipe_sprintf declaration from Trace.c --- CAPE/Trace.c | 1 - 1 file changed, 1 deletion(-) diff --git a/CAPE/Trace.c b/CAPE/Trace.c index 69fb1a22..4d03131f 100644 --- a/CAPE/Trace.c +++ b/CAPE/Trace.c @@ -52,7 +52,6 @@ extern void loq(int index, const char *category, const char *name, extern void log_flush(); extern PVOID KiUserExceptionDispatcher; extern lookup_t SoftBPs, SyscallBPs; -extern int _pipe_sprintf(char *out, const char *fmt, va_list args); extern BOOL InteractiveBreakpointCallback(PBREAKPOINTINFO pBreakpointInfo, struct _EXCEPTION_POINTERS *ExceptionInfo); char *ModuleName, *PreviousModuleName; From a06e16d0e3b327823db1e2ee3bd8fa259dba52cd Mon Sep 17 00:00:00 2001 From: enzok <7831008+enzok@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:25:45 -0500 Subject: [PATCH 148/148] Refactor _NtQueryInformationThread declaration Moved the declaration of the function pointer _NtQueryInformationThread from static to non-static, allowing broader access outside the current translation unit. --- misc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc.c b/misc.c index 61d0e4cd..ca80210e 100644 --- a/misc.c +++ b/misc.c @@ -34,7 +34,6 @@ along with this program. If not, see . extern char *our_process_name; static _NtQueryInformationProcess pNtQueryInformationProcess; -static _NtQueryInformationThread pNtQueryInformationThread; static _RtlGenRandom pRtlGenRandom; static _NtQueryAttributesFile pNtQueryAttributesFile; static _NtQueryObject pNtQueryObject; @@ -43,6 +42,7 @@ static _NtDelayExecution pNtDelayExecution; static _NtQuerySystemInformation pNtQuerySystemInformation; static _RtlEqualUnicodeString pRtlEqualUnicodeString; static _RtlInitUnicodeString pRtlInitUnicodeString; +_NtQueryInformationThread pNtQueryInformationThread; _NtMapViewOfSection pNtMapViewOfSection; _NtUnmapViewOfSection pNtUnmapViewOfSection; _NtAllocateVirtualMemory pNtAllocateVirtualMemory;