
Dynamischer Windows-API-Resolver und Unhooker, der gehackte Funktionen (IAT, EAT, Inline-Patches) erkennt und wiederherstellt, um unbeobachtete Systemaufrufe aus Red-Team-Malware aufzurufen.
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.
So können Sie sicherstellen, dass MessageBoxW ungehakt und unüberwacht aufgerufen wird:
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.

So funktioniert das UnhookMe-Beispiel:
MessageBoxW, die nicht Gegenstand von Hooking ist.MessageBoxW, um sie immer 0 zurückgeben zu lassen, ohne ihre Nachricht anzuzeigen.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:
[~] 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
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ältresolver.cpp - Quellcode mit definierten globalen Optionenusings.h - eine große und umfangreiche Header-Datei mit Dutzenden von using-Typdefinitionen für häufig verwendete WinAPIsPE.cpp - benutzerdefinierte PE-Parser-Quellcode-DateiPE.h - benutzerdefinierte PE-Parser-Header-DateiIhr Programm benötigt nur zwei einzubindende Header:
#include "usings.h"
#include "resolver.h"
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ünschenglobalVerboseOption - auf true setzen, wenn Sie eine detaillierte ausführliche Ausgabe wünschenglobalAntiSplicingOption - aufgelöste Funktionen enthaken, wenn sie gehakt sind.globalLogFilePath - wohin die Ausgabe-Logzeilen umgeleitet werden sollen. Bei Leerzeichen wird stdout verwendet.bool globalQuietOption = false;
bool globalVerboseOption = true;
bool globalAntiSplicingOption = true;
wchar_t globalLogFilePath[MAX_PATH] = L"";
Um den Resolver zu verwenden, muss zunächst ein Funktionszeigertyp mit einer using-Anweisung in strenger Form deklariert werden:
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.
Nachdem der Funktionszeigertyp wie oben angegeben definiert wurde, können wir ihn wie folgt verwenden:
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:
#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:
template<typename Ret, typename ...Args>
ImportResolver<Ret WINAPI(Args...)>(
std::string dllName,
std::string funcName,
bool _verbose = false,
bool _unhook = false,
bool *_wasItHooked = nullptr
)
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:
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.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.
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! 💪
Mariusz Banach / mgeeky, 21
<mb [at] binary-offensive.com>
(https://github.com/mgeeky)