Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
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
2735418vor 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);

// set our debug information in the Dr registers

SetThreadContext(thd, &context);
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;

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

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

Tool herunterladen