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
UnhookMe — Dynamischer Windows-API-Resolver und Unhooker, der gehackte Funktionen (IAT, EAT, Inline-Patches) erkennt und wiederherstellt, um unbeobachtete Systemaufrufe aus Red-Team-Malware aufzurufen. | Kitploit
Tools/GitHubGitHub/mgeeky/unhookme
DefensivwerkzeugeRed TeamingPayload-Entwicklung
GitHubmgeeky/unhookme

UnhookMe

Dynamischer Windows-API-Resolver und Unhooker, der gehackte Funktionen (IAT, EAT, Inline-Patches) erkennt und wiederherstellt, um unbeobachtete Systemaufrufe aus Red-Team-Malware aufzurufen.

Repository anzeigen
34848vor 4 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

UnhookMe - Dynamischer Unhooking-Import-Resolver

In der Ära aufdringlicher AVs und EDRs, die Hot-Patches in laufende Prozesse einbringen, um ihre erweiterten Sichtbarkeitsanforderungen zu erfüllen, benötigen moderne Angreifer ein robustes Werkzeug, um diese Wächter zu umgehen. Die vorgeschlagene Implementierung eines dynamischen Import-Resolvers, der in der Lage ist, verwendete Funktionen im laufenden Betrieb zu enthaken (unhooking), ist ein weiterer Schritt zur Stärkung der Widerstandsfähigkeit von Angreifern.

Die Lösung, die ich hier vorschlage, besteht darin, von der Verwendung linkeraufgelöster WinAPI-Imports, die in den PE-Headern der kompilierten Executables sichtbar bleiben (insbesondere in der Import Address Table), auf einen vollständig dynamischen Ansatz umzusteigen, der darauf beharrt, Imports nur dynamisch aufzulösen. Ein solcher dynamischer Resolver kann mit einer Unhooking-Logik ausgestattet werden, die im Hintergrund abläuft, ohne jegliche Anleitung von Seiten des Bedieners.


Einfachstes Anwendungsbeispiel

So können Sie sicherstellen, dass MessageBoxW ungehakt und unüberwacht aufgerufen wird:

root@kitploit:~
    RESOLVE(user32, MessageBoxW);
    _MessageBoxW(0, L"Look Ma! I'm unhooked!", L"Third - Unhooked", 0);

Die ganze Magie geschieht innerhalb der Makrodefinition RESOLVE, die ein ImportResolver<T>-Objekt namens _MessageBoxW erstellt.


Vorführung

Unhookme Vorführungsanimation

So funktioniert das UnhookMe-Beispiel:

  1. Es präsentiert uns die erste MessageBoxW, die nicht Gegenstand von Hooking ist.
  2. Dann hooken wir selbst den Prolog von MessageBoxW, um sie immer 0 zurückgeben zu lassen, ohne ihre Nachricht anzuzeigen.
  3. Schließlich lösen wir MessageBoxW dynamisch mit dem UnhookingImportResolver-Resolver auf, der angewandte Prolog-Patches erkennt und die ursprünglichen Bytes wiederherstellt, wodurch die Funktionalität von MessageBoxW effektiv enthakt wird.

Während des Erscheinens von MessageBoxen werden diese Logzeilen auf der Konsole ausgegeben:

root@kitploit:~
[~] Resolved symbol kernel32.dll!CreateFileA
[~] Resolved symbol kernel32.dll!ReadProcessMemory
[~] Resolved symbol kernel32.dll!MapViewOfFile
[~] Resolved symbol kernel32.dll!VirtualProtectEx
[#] Found trampoline hook in symbol: MessageBoxW . Restored original bytes from file.
[~] Resolved symbol user32.dll!MessageBoxW

Wie verwendet man es?

Insgesamt gibt es 5 C++-Quellcode-/Header-Dateien, die Ihre Lösung einbinden muss. Ihre Hauptprogrammdatei muss jedoch nur zwei erforderliche Header einbinden, wie unten beschrieben.

  • resolver.h - Header, der den Großteil der UnhookingImportResolver-Implementierung und praktische Makrodefinitionen enthält
  • resolver.cpp - Quellcode mit definierten globalen Optionen
  • usings.h - eine große und umfangreiche Header-Datei mit Dutzenden von using-Typdefinitionen für häufig verwendete WinAPIs
  • PE.cpp - benutzerdefinierte PE-Parser-Quellcode-Datei
  • PE.h - benutzerdefinierte PE-Parser-Header-Datei

Erforderliche Header

Ihr Programm benötigt nur zwei einzubindende Header:

root@kitploit:~
#include "usings.h"
#include "resolver.h"

Globale Optionen

Es gibt einige globale Optionen, die geändert werden können und die Art und Weise beeinflussen, wie der Resolver arbeitet oder seine Aktivität meldet. Diese sind ganz am Anfang der Datei resolver.cpp definiert:

Resolver globale Optionen:

  • globalQuietOption - auf true setzen, wenn Sie keine Ausgabe wünschen
  • globalVerboseOption - auf true setzen, wenn Sie eine detaillierte ausführliche Ausgabe wünschen
  • globalAntiSplicingOption - aufgelöste Funktionen enthaken, wenn sie gehakt sind.
  • globalLogFilePath - wohin die Ausgabe-Logzeilen umgeleitet werden sollen. Bei Leerzeichen wird stdout verwendet.
root@kitploit:~
bool globalQuietOption = false;
bool globalVerboseOption = true;
bool globalAntiSplicingOption = true;

wchar_t globalLogFilePath[MAX_PATH] = L"";

Spezifikation benutzerdefinierter API-Typen

Um den Resolver zu verwenden, muss zunächst ein Funktionszeigertyp mit einer using-Anweisung in strenger Form deklariert werden:

root@kitploit:~
    using fn_FunctionName = ReturnType WINAPI (
        ParamType1 paramName1,
        ...,
        ParamTypeN paramNameN,
    );

Dieses Repository enthält die Headerdatei usings.h mit vordefinierten Using-Typen für Dutzende beliebter Windows-APIs.

Der FunctionName entspricht der WinAPI, die der ImportResolver auflösen soll, und dieser Funktionszeiger muss als mit der WINAPI-Aufrufkonvention ( __stdcall auf x86 und __fastcall auf x64) markiert sein. Der ReturnType muss dem Typmodifizierer WINAPI vorausgehen.

Funktionsauflösung und -verwendung

Nachdem der Funktionszeigertyp wie oben angegeben definiert wurde, können wir ihn wie folgt verwenden:

root@kitploit:~
    RESOLVE(libraryName, FunctionName);
    ReturnType output = _FunctionName(param1, ..., paramN);

Das Makro RESOLVE kümmert sich um die Instanziierung des templatisierten ImportResolver-Objekts und passt den Namen der angegebenen Bibliothek an.

Der Resolver führt mehrere weitere Makrodefinitionen ein, die eine benutzerfreundliche Konstruktoraufruf in verschiedenen Situationen bieten:

root@kitploit:~
#define RESOLVE(mod, func)                    RESOLVE_PARAMETERIZED(mod, func, ::globalVerboseOption, ::globalAntiSplicingOption)
#define RESOLVE_NO_UNHOOK(mod, func)          RESOLVE_PARAMETERIZED(mod, func, ::globalVerboseOption, false)

#define RESOLVE_VERBOSE_UNHOOK(mod, func)     RESOLVE_PARAMETERIZED(mod, func, true, true)
#define RESOLVE_VERBOSE_NOUNHOOK(mod, func)   RESOLVE_PARAMETERIZED(mod, func, true, false)
#define RESOLVE_NOVERBOSE_UNHOOK(mod, func)   RESOLVE_PARAMETERIZED(mod, func, false, true)
#define RESOLVE_NOVERBOSE_NOUNHOOK(mod, func) RESOLVE_PARAMETERIZED(mod, func, false, false)

Resolver-Konstruktor:

root@kitploit:~
    template<typename Ret, typename ...Args>
    ImportResolver<Ret WINAPI(Args...)>(
            std::string dllName,
            std::string funcName,
            bool _verbose = false,
            bool _unhook = false,
            bool *_wasItHooked = nullptr
        )

Wie funktioniert es?

Der zugrunde liegende Resolver nutzt einen benutzerdefinierten PE-Header-Parser, der jedes referenzierte DLL-Modul verarbeitet, um deren Exporte zu kartieren und die Integrität der PE-Header des Moduls sowie die Integrität der Stub-Bytes der referenzierten Funktion zu überprüfen.

Die Idee ist folgende:

  1. Zuerst rufen wir LoadLibrary auf, um die vom Benutzer referenzierte Bibliothek (diejenige, die als erster Parameter für das Makro RESOLVE angegeben wurde) zu laden, falls sie nicht über GetModuleHandle erreicht werden kann.
  2. Dann verarbeiten wir die PE-Header der geladenen/referenzierten Bibliothek, kartieren ihre Exporte, rufen das Array der Exportadressen ab und berechnen diese Adressen selbst zur Kreuzverifikation.
  3. Wenn die Adresse einer Routine, die in der Export Address Table der DLL definiert ist, nicht dem entspricht, was wir erwarten, gilt der Export als EAT-gehakt. Gleiches gilt, wenn der Eintrag unserer Executable Import Address Table (IAT) für diese Funktion geändert wurde und nicht mehr auf die richtige Stelle im Codeabschnitt der DLL zeigt – dann gilt die Funktion als IAT-gehakt.
  4. Angenommen, es wurden bisher keine Hooks gefunden, holen wir die ersten N Bytes des Funktionsprologs und vergleichen sie mit dem, was in der DLL-Datei auf der Festplatte gespeichert ist. Bei einer Abweichung zwischen den aus dem Speicher und der Datei geholten Bytes gehen wir davon aus, dass die Funktion inline gepatcht (hot-patched) wurde.
  5. Wenn die Funktion als gehakt betrachtet wurde, geben wir die ursprüngliche Exportadresse (die von uns berechnete) zurück und/oder enthaken den Eintrag. Falls Patch-Bytes vorhanden waren, stellen wir sie wieder her.
  6. Schließlich, um die Leistungsauswirkung des Resolvers zu optimieren, cachen wir die Imagebasen aller geladenen Module und die aufgelösten Funktionsadressen und geben sie bei späteren Treffern aus einem Cache ( std::map ) zurück.

Zu den Problemen, mit denen ein solcher dynamisch enthakender Resolver konfrontiert ist, gehören die Probleme beim Durchlaufen von weitergeleiteten APIs (eine DLL kann einen Export-Thunk enthalten, der besagt, dass diese Funktion nicht in diesem Modul, sondern in einem anderen implementiert ist) – obwohl diese Implementierung dafür Unterstützung bietet, scheitert manchmal ihre Durchlauflogik.


☕ Unterstützung zeigen ☕

Dieses und andere Projekte sind das Ergebnis schlafloser Nächte und viel harter Arbeit. Wenn Ihnen gefällt, was ich tue, und Sie schätzen, dass ich immer etwas an die Community zurückgebe, überlegen Sie mir einen Kaffee zu kaufen (oder besser ein Bier) , nur um Danke zu sagen! 💪


Autor

root@kitploit:~
   Mariusz Banach / mgeeky, 21
   <mb [at] binary-offensive.com>
   (https://github.com/mgeeky)
Tool herunterladen