Wollten Sie schon immer einmal unter die Haube des Betriebssystems schauen, die interne Struktur seiner Mechanismen betrachten, die Schrauben drehen und die sich bietenden Möglichkeiten betrachten? Vielleicht wollten sie sogar direkt mit der Hardware arbeiten, dachten aber, die Treiber seien Raketenwissenschaftler?
Ich schlage vor, entlang der Brücke zum Kern zu gehen und zu sehen, wie tief das Kaninchenloch ist.
Daher präsentiere ich das in C ++ 17 geschriebene Treiber-Framework für Kernel-Hacking, das nach Möglichkeit Barrieren zwischen Kernel und Benutzermodus beseitigen oder deren Vorhandensein so weit wie möglich ausgleichen soll. Außerdem eine Reihe von Benutzermodus- und Kernel-APIs und -Wrappern für die schnelle und bequeme Entwicklung in Ring0 für Anfänger und fortgeschrittene Programmierer.
Hauptmerkmale:
- Zugriff auf E / A-Ports sowie Weiterleitung von In-, Out-, Cli- und STI- Anweisungen an den Benutzermodus über IOPL
- Wickelt den System-Hochtöner um
- Zugriff auf MSR (Model-Specific Registers)
- Eine Reihe von Funktionen für den Zugriff auf den Benutzermodus-Speicher anderer Prozesse und den Kernel-Speicher
- Arbeiten Sie mit physischem Speicher, DMI / SMBIOS
- Erstellung von Benutzermodus- und Kernströmen, APC-Bereitstellung
- Benutzermodus Ob *** und Ps *** Rückrufe und Dateisystemfilter
- Laden Sie nicht signierte Treiber und Kernel-Bibliotheken herunter
… und vieles mehr.
Zunächst laden wir das Framework und verbinden es mit unserem C ++ - Projekt.
GithubFür die Montage wird dringend empfohlen, die neueste Version von Visual Studio und das neueste verfügbare WDK (Windows Driver Kit) zu verwenden, das von der
offiziellen Microsoft-Website heruntergeladen werden kann.
Zum Testen ist der kostenlose VMware Player mit installiertem Windows, nicht niedriger als Windows 7, mit jeder Kapazität perfekt.
Die Montage ist trivial und wirft keine Fragen auf:
- Öffnen Sie Kernel-Bridge.sln
- Wählen Sie die gewünschte Bittiefe
- Strg + Umschalt + B.
Als Ergebnis erhalten wir einen Treiber, eine Bibliothek im Benutzermodus sowie zugehörige Dienstprogrammdateien (
* .inf für die manuelle Installation,
* .cab für die Signatur des Treibers in Microsoft Hardware Certification Publisher usw.).
Um den Treiber zu installieren (wenn für x64 keine digitale Signatur erforderlich ist - das entsprechende EV-Zertifikat), müssen Sie das System in den Testmodus versetzen und die digitale Signatur der Treiber ignorieren. Führen Sie dazu die Befehlszeile als Administrator aus:
bcdedit.exe /set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe /set TESTSIGNING ON
... und starten Sie die Maschine neu. Wenn alles richtig gemacht wurde, wird in der unteren rechten Ecke eine Beschriftung angezeigt, dass sich Windows im Testmodus befindet.
Das Einrichten der Testumgebung ist abgeschlossen. Beginnen wir mit der Verwendung der API in unserem Projekt.
Das Framework hat die folgende Hierarchie:
/ Kernel-Bridge / API - Eine Reihe von Funktionen zur Verwendung in Treibern und
Kernelmodulen , die keine externen Abhängigkeiten aufweisen und in Projekten von Drittanbietern frei verwendet werden können
/ User-Bridge / API - Eine Reihe von Wrappern im Benutzermodus über die Treiber- und Dienstprogrammfunktionen für die Arbeit mit PE-Dateien, PDB-Zeichen usw.
/ SharedTypes / - sowohl User-
Mode- als auch Nuclear-Header, die die erforderlichen allgemeinen Typen enthalten
Der Treiber kann auf zwei Arten geladen werden: als normaler Treiber und als Minifilter. Die zweite Methode ist bevorzugt, weil Ermöglicht den Zugriff auf die erweiterten Funktionen von Filtern und Rückrufen im Benutzermodus für Systemereignisse.
Erstellen Sie also ein Konsolenprojekt in C ++, verbinden Sie die erforderlichen Header-Dateien und laden Sie den Treiber:
#include <Windows.h> #include "WdkTypes.h" // x32/x64 WDK #include "CtlTypes.h" // IOCTL- #include "User-Bridge.h" // API, int main() { using namespace KbLoader; BOOL Status = KbLoadAsFilter( L"X:\\Folder\\Path\\To\\Kernel-Bridge.sys", L"260000" // ); if (!Status) return 0; // ! // API ... // : KbUnload(); return 0; }
Großartig! Jetzt können wir die API verwenden und mit dem Kernel interagieren.
Beginnen wir mit der beliebtesten Funktionalität in der Umgebung von Cheat-Entwicklern - dem Lesen und Schreiben des Speichers eines anderen Prozesses:
using namespace Processes::MemoryManagement; constexpr int Size = 64; BYTE Buffer[Size] = {}; BOOL Status = KbReadProcessMemory(
Nichts kompliziertes! Gehen wir eine Ebene tiefer - das Lesen und Schreiben des nuklearen Gedächtnisses:
using namespace VirtualMemory; constexpr int Size = 64; BYTE Buffer[Size];
Was ist mit Funktionen für die Interaktion mit Eisen? Zum Beispiel E / A-Ports.
Wir werden sie in den Benutzermodus weiterleiten, indem wir 2 IOPL-Bits im EFlags-Register spannen, die für die Berechtigungsstufe verantwortlich sind, auf der die Anweisungen
in /
out /
cli /
sti verfügbar sind.
Auf diese Weise können wir sie im Benutzermodus ohne den Fehler "Privilegierte Anweisung" ausführen:
#include <intrin.h> using namespace IO::Iopl; // , ! KbRaiseIopl(); // in/out/cli/sti ! ULONG Frequency = 1000; // 1 kHz ULONG Divider = 1193182 / Frequency; __outbyte(0x43, 0xB6); // // : __outbyte(0x42, static_cast<unsigned char>(Divider)); __outbyte(0x42, static_cast<unsigned char>(Divider >> 8)); __outbyte(0x61, __inbyte(0x61) | 3); // ( ) for (int i = 0; i < 5000; i++); // Sleep(), IOPL ! __outbyte(0x61, __inbyte(0x61) & 252); // KbResetIopl();
Aber was ist mit wahrer Freiheit? Schließlich möchte man oft beliebigen Code mit Kernel-Berechtigungen ausführen. Wir schreiben den gesamten Kernel-Code im Benutzermodus und übertragen die Steuerung vom Kernel an ihn (SMEP wird automatisch
deaktiviert , bevor der Aufruf des Treibers den FPU-Kontext speichert und der Aufruf selbst in einem
try..except- Block erfolgt):
using namespace KernelShells;
Neben dem Verwöhnen mit Shells gibt es auch eine umfangreiche Funktion, mit der Sie einfache DLPs erstellen können, die auf dem Subsystem von Datei-, Objekt- und Prozessfiltern basieren.
Mit dem Framework können Sie
CreateFile /
ReadFile /
WriteFile /
DeviceIoControl sowie Ereignisse beim Öffnen / Duplizieren von Handles (
ObRegisterCallbacks ) und Ereignisse beim Starten von Prozessen / Threads und beim Laden von Modulen (
PsSet *** NotifyRoutine )
filtern . Auf diese Weise können Sie beispielsweise den Zugriff auf beliebige Dateien blockieren oder Informationen zu den Seriennummern der Festplatte ersetzen.
Arbeitsprinzip:
- Der Treiber registriert Dateifilter und installiert Ob *** / Ps *** -Rückrufe
- Der Treiber öffnet einen Kommunikationsport , über den Clients, die eine Ereignisverbindung abonnieren möchten, eine Verbindung herstellen möchten
- Anwendungen im Benutzermodus werden an den Port angehängt und empfangen vom Treiber Daten über das aufgetretene Ereignis, führen eine Filterung durch (Abschneiden von Handles in Rechten, Blockieren des Zugriffs auf die Datei usw.) und geben das Ereignis an den Kernel zurück
- Der Treiber übernimmt die erhaltenen Änderungen.
Ein Beispiel für das Abonnieren von
ObRegisterCallbacks und das
Sperren des Zugriffs auf den aktuellen Prozess:
#include <Windows.h> #include <fltUser.h> #include "CommPort.h" #include "WdkTypes.h" #include "FltTypes.h" #include "Flt-Bridge.h" ... // ObRegisterCallbacks: CommPortListener<KB_FLT_OB_CALLBACK_INFO, KbObCallbacks> ObCallbacks; // PROCESS_VM_READ: Status = ObCallbacks.Subscribe([]( CommPort& Port, MessagePacket<KB_FLT_OB_CALLBACK_INFO>& Message ) -> VOID { auto Data = static_cast<PKB_FLT_OB_CALLBACK_INFO>(Message.GetData()); if (Data->Target.ProcessId == GetCurrentProcessId()) { Data->CreateResultAccess &= ~PROCESS_VM_READ; Data->DuplicateResultAccess &= ~PROCESS_VM_READ; } ReplyPacket<KB_FLT_OB_CALLBACK_INFO> Reply(Message, ERROR_SUCCESS, *Data); Port.Reply(Reply); // });
Wir gingen also kurz auf die Hauptpunkte des Benutzermodus-Teils des Frameworks ein, aber die Kern-API blieb hinter den Kulissen.
Alle APIs und Wrapper befinden sich im entsprechenden Ordner:
/ Kernel-Bridge / API /Dazu gehört die Arbeit mit dem Speicher, mit Prozessen, mit Zeichenfolgen und Sperren und vielem mehr, was die Entwicklung ihrer eigenen Treiber erheblich vereinfacht. APIs und Wrapper hängen nur von sich selbst ab und nicht von der externen Umgebung: Sie können sie frei in Ihrem eigenen Treiber verwenden.
Ein Beispiel für die Arbeit mit Strings im Kernel ist ein Stolperstein für alle Anfänger:
#include <wdm.h> #include <ntstrsafe.h> #include <stdarg.h> #include "StringsAPI.h" WideString wString = L"Some string"; AnsiString aString = wString.GetAnsi().GetLowerCase() + " and another string!"; if (aString.Matches("*another*")) DbgPrint("%s\r\n", aString.GetData());
Wenn Sie einen eigenen Handler für Ihren IOCTL-Code implementieren möchten, können Sie dies ganz einfach nach folgendem Schema tun:
- Schreiben Sie einen Handler in /Kernel-Bridge/Kernel-Bridge/IOCTLHandlers.cpp
- Fügen Sie in derselben Datei am Ende des Handlers- Arrays in der DispatchIOCTL- Funktion einen Handler hinzu
- Fügen Sie den Abfrageindex zur Aufzählung Ctls :: KbCtlIndices in CtlTypes.h in der GLEICHEN POSITION wie im Array Handlers in Element 2 hinzu
- Rufen Sie Ihren Handler aus dem Benutzermodus auf, indem Sie einen Wrapper in User-Bridge.cpp schreiben und mit der Funktion KbSendRequest einen Aufruf tätigen
Alle drei E / A-Typen werden unterstützt (METHOD_BUFFERED, METHOD_NEITHER und METHOD_IN_DIRECT / METHOD_OUT_DIRECT). Standardmäßig wird METHOD_NEITHER verwendet.
Das ist alles! Der Artikel behandelt nur einen kleinen Bruchteil aller Möglichkeiten. Ich hoffe, dass das Framework für unerfahrene Entwickler von Kernel-Komponenten, Reverse Engineers, Entwickler von Cheats, Anti-Cheats und Schutzmaßnahmen nützlich sein wird.
Außerdem ist jeder eingeladen, sich an der Entwicklung zu beteiligen. In den Zukunftsplänen:
- Wrapper zur direkten Manipulation von PTE-Datensätzen und zur Weiterleitung des Kernspeichers an den Benutzermodus
- Injektoren basierend auf vorhandenen Funktionen zur Erstellung und Lieferung von APC-Flüssen
- GUI-Plattform für Live-Reverse Engineering und Windows-Kernel-Forschung
- Skript-Engine zum Ausführen von Kernel-Code-Teilen
- Unterstützung für SEH in dynamisch geladenen Modulen
- HLK-Tests bestehen
Danke für die Aufmerksamkeit!