Avez-vous déjà voulu regarder sous le capot du système d'exploitation, regarder la structure interne de ses mécanismes, tourner les vis et regarder les opportunités qui se sont ouvertes? Peut-être qu'ils voulaient même travailler directement avec le matériel, mais pensaient que les pilotes étaient rocketscience?
Je propose de marcher le long du pont jusqu'au cœur et de voir à quelle profondeur le terrier du lapin est.
Je présente donc le framework de pilote pour le piratage du noyau, écrit en C ++ 17, et conçu, si possible, pour supprimer les barrières entre le noyau et le mode utilisateur ou pour aplanir leur présence autant que possible. Et aussi, un ensemble d'API et de wrappers en mode utilisateur et noyau pour un développement rapide et pratique dans Ring0 pour les programmeurs débutants et avancés.
Caractéristiques clés:
- Accès aux ports d'E / S, ainsi que transfert des instructions in , out , cli et sti au mode utilisateur via IOPL
- Enveloppe le tweeter système
- Access MSR (registres spécifiques au modèle)
- Un ensemble de fonctions pour accéder à la mémoire en mode utilisateur d'autres processus et à la mémoire du noyau
- Travailler avec la mémoire physique, DMI / SMBIOS
- Création de flux utilisateurs et nucléaires, livraison APC
- Rappels Ob *** et Ps *** en mode utilisateur et filtres de système de fichiers
- Téléchargez les pilotes non signés et les bibliothèques du noyau
... et bien plus.
Et nous commencerons par charger et connecter le framework à notre projet C ++.
GithubPour l'assemblage, il est fortement recommandé d'utiliser la dernière version de Visual Studio et le dernier WDK (Windows Driver Kit) disponible, qui peut être téléchargé à partir du
site officiel de Microsoft .
Pour les tests, le VMware Player gratuit avec Windows installé, pas moins que Windows 7, de n'importe quelle capacité est parfait.
L'assemblage est trivial et ne posera pas de questions:
- Ouvrez Kernel-Bridge.sln
- Choisissez la profondeur de bits requise
- Ctrl + Maj + B
En conséquence, nous obtenons un pilote, une bibliothèque en mode utilisateur, ainsi que des fichiers utilitaires associés (
* .inf pour une installation manuelle,
* .cab pour signer le pilote sur Microsoft Hardware Certification Publisher, etc.).
Pour installer le pilote (s'il n'y a pas de signature numérique nécessaire pour x64 - le certificat EV correspondant), vous devez mettre le système en mode test, en ignorant la signature numérique des pilotes. Pour ce faire, exécutez la ligne de commande en tant qu'administrateur:
bcdedit.exe /set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe /set TESTSIGNING ON
... et redémarrez la machine. Si tout est fait correctement, une inscription apparaîtra dans le coin inférieur droit que Windows est en mode test.
La configuration de l'environnement de test est terminée, commençons à utiliser l'API dans notre projet.
Le cadre a la hiérarchie suivante:
/ Kernel-Bridge / API - un ensemble de fonctions à utiliser dans les pilotes et
les modules du
noyau , n'ont pas de dépendances externes et peuvent être librement utilisées dans des projets tiers
/ User-Bridge / API - un ensemble de wrappers en mode utilisateur sur le pilote et les fonctions utilitaires pour travailler avec des fichiers PE, des caractères PDB, etc.
/ SharedTypes / - en-têtes en mode utilisateur et nucléaires contenant les types communs nécessaires
Le pilote peut être chargé de deux manières: en tant que pilote standard et en tant que minifiltre. La deuxième méthode est préférée, car donne accès aux fonctionnalités avancées des filtres et des rappels en mode utilisateur pour les événements système.
Donc, créez un projet de console en C ++, connectez les fichiers d'en-tête nécessaires et chargez le pilote:
#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; }
Super! Nous pouvons maintenant utiliser l'API et interagir avec le noyau.
Commençons par les fonctionnalités les plus populaires dans l'environnement des développeurs de triche - lire et écrire la mémoire d'un autre processus:
using namespace Processes::MemoryManagement; constexpr int Size = 64; BYTE Buffer[Size] = {}; BOOL Status = KbReadProcessMemory(
Rien de compliqué! Descendons d'un niveau - lire et écrire la mémoire nucléaire:
using namespace VirtualMemory; constexpr int Size = 64; BYTE Buffer[Size];
Qu'en est-il des fonctions d'interaction avec le fer? Par exemple, les ports d'E / S.
Nous les transmettrons en mode utilisateur en armant 2 bits IOPL dans le registre EFlags, qui sont responsables du niveau de privilège auquel les instructions
in /
out /
cli /
sti sont disponibles.
Ainsi, nous pourrons les exécuter en mode utilisateur sans l'erreur Privileged Instruction:
#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();
Mais qu'en est-il de la vraie liberté? Après tout, on veut souvent exécuter du code arbitraire avec les privilèges du noyau. Nous écrivons tout le code du noyau en mode utilisateur et y transférons le contrôle à partir du noyau (SMEP est désactivé automatiquement, avant d'appeler le pilote enregistre le contexte FPU et l'appel lui-même se produit à l'intérieur du bloc
try..except ):
using namespace KernelShells;
Mais en plus de chouchouter avec des shells, il existe également une fonctionnalité sérieuse qui vous permet de créer des DLP simples basés sur le sous-système de filtres de fichiers, d'objets et de processus.
Le cadre permet de filtrer
CreateFile /
ReadFile /
WriteFile /
DeviceIoControl , ainsi que les événements d'ouverture / de duplication des poignées (
ObRegisterCallbacks ) et les événements de démarrage des processus / threads et le chargement des modules (
PsSet *** NotifyRoutine ). Cela permettra, par exemple, de bloquer l'accès à des fichiers arbitraires ou de remplacer des informations sur les numéros de série du disque dur.
Principe de fonctionnement:
- Le pilote enregistre les filtres de fichiers et installe les rappels Ob *** / Ps ***
- Le pilote ouvre un port de communication auquel les clients souhaitant s'abonner à un événement se connectent
- Les applications en mode utilisateur se connectent au port et reçoivent des données du pilote sur l'événement qui s'est produit, effectuent le filtrage (tronquer les poignées en droits, bloquer l'accès au fichier, etc.) et renvoyer l'événement au noyau
- Le pilote applique les modifications reçues.
Un exemple d'abonnement à
ObRegisterCallbacks et de coupure de l'accès au processus actuel:
#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); // });
Nous avons donc brièvement passé en revue les principaux points de la partie en mode utilisateur du cadre, mais l'API principale est restée dans les coulisses.
Toutes les API et wrappers se trouvent dans le dossier correspondant:
/ Kernel-Bridge / API /Ils incluent l'utilisation de la mémoire, des processus, des chaînes et des verrous, et bien plus encore, simplifiant considérablement le développement de leurs propres pilotes. Les API et les wrappers ne dépendent que d'eux-mêmes et ne dépendent pas de l'environnement externe: vous pouvez les utiliser librement dans votre propre pilote.
Un exemple de travail avec des chaînes dans le noyau est une pierre d'achoppement pour tous les débutants:
#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());
Si vous souhaitez implémenter votre propre gestionnaire pour votre code IOCTL, vous pouvez très facilement le faire selon le schéma suivant:
- Écrivez un gestionnaire dans /Kernel-Bridge/Kernel-Bridge/IOCTLHandlers.cpp
- Dans le même fichier, ajoutez un gestionnaire à la fin du tableau Handlers dans la fonction DispatchIOCTL
- Ajoutez l'index de requête à l' énumération Ctls :: KbCtlIndices dans CtlTypes.h dans la MÊME POSITION que dans le tableau Handlers de l'élément 2
- Appelez votre gestionnaire depuis le mode utilisateur en écrivant un wrapper dans User-Bridge.cpp , en effectuant un appel à l'aide de la fonction KbSendRequest
Les trois types d'E / S sont pris en charge (METHOD_BUFFERED, METHOD_NEITHER et METHOD_IN_DIRECT / METHOD_OUT_DIRECT), par défaut, METHOD_NEITHER est utilisé.
C'est tout! L'article ne couvre qu'une petite fraction de toutes les possibilités. J'espère que le cadre sera utile aux développeurs novices de composants du noyau, aux ingénieurs en rétro-ingénierie, aux développeurs de tricheurs, d'anti-tricheurs et de protections.
Et aussi, tout le monde est invité à participer au développement. Dans les plans futurs:
- Wrappers pour la manipulation directe des enregistrements PTE et la transmission de la mémoire nucléaire au mode utilisateur
- Injecteurs basés sur les fonctionnalités existantes de création et de livraison de flux APC
- Plateforme GUI pour l'ingénierie inverse en direct et la recherche sur le noyau Windows
- Moteur de script pour exécuter des morceaux de code du noyau
- Prise en charge de SEH dans les modules chargés dynamiquement
- Passer les tests HLK
Merci de votre attention!