NtUtils est un framework pour la programmation système Windows dans Delphi qui fournit un ensemble de fonctions avec une meilleure gestion des erreurs et une meilleure intégration du langage que les en-têtes Winapi/Ntapi classiques, combinés avec des extraits de code fréquemment utilisés et des types de données intelligents.
Vous pouvez trouver des exemples de code dans un référentiel dédié .
La bibliothèque a une structure en couches avec trois couches au total :
Winapi.*.pas
et la bibliothèque Ntapi.*.pas
dans votre programme ; cependant, cela peut nécessiter de spécifier explicitement le préfixe de l'espace de noms en cas de noms contradictoires.System.SysUtils
, System.Rtti
et System.Generics.Collections
.Par conséquent, tout ce dont vous avez besoin est déjà inclus dans la dernière version gratuite de Delphi. En prime, la compilation d'applications console sans RTTI (c'est-à-dire la réflexion) produit des exécutables extrêmement petits. Voir les exemples pour plus de détails.
Étant donné que l'inclusion de chaque fichier de la bibliothèque dans vos projets est généralement redondante, vous pouvez configurer Delphi pour la découverte automatique des fichiers. De cette façon, vous pouvez spécifier une unité dans la section uses
, et Delphi l'inclura automatiquement ainsi que ses dépendances dans le projet. Pour configurer les dossiers dans lesquels Delphi effectue la recherche, accédez à Projet -> Options -> Bâtiment -> Compilateur Delphi et ajoutez les lignes suivantes dans le chemin de recherche :
.NtUtilsLibrary
.NtUtilsLibraryHeaders
.NtUtilsLibraryNtUiLib
Si les noms ou emplacements des dossiers sont différents pour votre projet, vous devez ajuster ces lignes en conséquence.
La bibliothèque indique les échecs à l'appelant en renvoyant les valeurs TNtxStatus infructueuses. TNtxStatus
(défini dans NtUtils.pas) est une structure qui stocke un code d'erreur (compatible avec NTSTATUS
, HRESULT
et Win32 Errors) ainsi que des métadonnées sur la nature de l'opération tentée, telles que l'emplacement de l'échec, une trace de pile et d'autres détails comme le masque d'accès attendu/demandé pour les appels ouverts ou la valeur de la classe d'informations pour les appels de requête/définition. Pour vérifier si TNtxStatus
réussit, utilisez sa méthode IsSuccess
. Pour accéder ou définir le code d'erreur sous-jacent (en fonction de son type et des préférences de l'appelant), utilisez des propriétés telles que Status
, HResult
, HResultAllowFalse
, Win32Error
, Win32ErrorOrSuccess
, IsHResult
, IsWin32
, etc.
Si vous préférez utiliser des exceptions, vous pouvez toujours appeler RaiseOnError()
sur un TNtxStatus
donné. Notez qu'à moins que vous ne souhaitiez vraiment utiliser des exceptions sans importer System.SysUtils
(ce qui est possible), il est préférable d'inclure NtUiLib.Exceptions qui apporte une classe d'exception ENtError
dédiée (dérivée du EOSError
intégré).
NtUiLib.Errors attache quatre méthodes pour représenter les valeurs TNtxStatus
sous forme de chaînes. Par exemple, si l'erreur avec la valeur 0xC0000061
provient d'une tentative de modification de l'ID de session d'un jeton, ces méthodes renverront les informations suivantes :
Méthode | Chaîne renvoyée |
---|---|
Name | STATUS_PRIVILEGE_NOT_HELD |
Description | A required privilege is not held by the client |
Summary | Privilege Not Held |
ToString | NtSetInformationToken returned STATUS_PRIVILEGE_NOT_HELD |
Si vous souhaitez aller encore plus loin et afficher une jolie boîte de message à l'utilisateur, NtUiLib.Errors.Dialog propose ShowNtxStatus()
. De plus, l'inclusion de NtUiLib.Exceptions.Dialog apportera le support de réflexion nécessaire et enrichira encore davantage le dialogue. Voici un exemple de ce à quoi cela pourrait ressembler :
TNtxStatus
prend en charge la capture des traces de pile (désactivée par défaut). Pour l'activer, définissez NtUtils.CaptureStackTraces
sur True. Gardez à l'esprit que l'affichage des traces de pile de manière significative nécessite la configuration de la génération de symboles de débogage pour votre exécutable. Malheureusement, Delphi ne peut générer que des fichiers .map
(configurés via Projet -> Options -> Construction -> Compilateur Delphi -> Liaison -> Fichier de carte), ce qui n'est généralement pas suffisant. Vous aurez besoin d'un outil map2dbg tiers pour les convertir en fichiers .dbg
, afin que l'API de symboles puisse les comprendre. Bien que les fichiers .dbg
puissent suffire, il est préférable de les traiter encore plus en les convertissant en .pdb
moderne via cv2pdb .
Pour générer automatiquement des symboles de débogage, ajoutez les événements post-build suivants dans votre projet :
map2dbg.exe $(OUTPUTPATH)
cv2pdb64.exe -n -s. -p$(OUTPUTNAME).pdb $(OUTPUTPATH)
Delphi n'inclut pas de garbage collector, donc seuls quelques types sont gérés directement : les enregistrements, les chaînes, les tableaux dynamiques et les interfaces. Les classes et les pointeurs, en revanche, nécessitent un nettoyage explicite qui (sous sa forme sûre) nécessite l'utilisation de blocs try-finally et complique donc considérablement le programme. Pour résoudre ce problème, la bibliothèque inclut des fonctionnalités de gestion automatique de la durée de vie de la mémoire et d'autres ressources, implémentées dans DelphiUtils.AutoObjects. En utilisant les types de ce module, nous demandons au compilateur de générer automatiquement du code sans exception pour compter les références et libérer automatiquement les objets dans les épilogues de fonctions. Ce module définit plusieurs interfaces pour différents types de ressources pouvant nécessiter un nettoyage. Il introduit la hiérarchie suivante :
graphique LR ;
sous-graphe id1 [Toute ressource]
IAutoReleasable
fin
sous-graphe id2 [Une valeur THandle]
IPoignée
fin
sous-graphe id3 [Une classe Delphi]
IAutoObjet[IAutoObject<T>]
fin
sous-graphe id4[Un pointeur]
IAutoPointer[IAutoPointer<P>]
fin
sous-graphe id5[Une région mémoire]
IMémoire[IMémoire<P>]
fin
IAutoReleasable --> IHandle ;
IAutoReleasable --> IAutoObject ;
IAutoReleasable --> IAutoPointer ;
IAutoPointer --> IMémoire ;
IAutoReleasable
est le type de base pour toutes les ressources qui nécessitent une action de nettoyage (automatique). IHandle
sert de wrapper pour les ressources définies par une valeur THandle. IAutoObject<T>
est un wrapper générique permettant de libérer automatiquement les classes Delphi (c'est-à-dire tout ce qui dérive de TObject). IAutoPointer<P>
définit une interface similaire pour libérer des pointeurs alloués dynamiquement (où la taille de la région n'a pas d'importance). IMemory<P>
fournit un wrapper pour les régions de mémoire de tailles connues accessibles via un pointeur typé, telles que les enregistrements encadrés gérés et non gérés.
La recette pour utiliser cette installation est la suivante :
Définissez chaque variable qui doit conserver la propriété (potentiellement partagée) sur un objet à l'aide de l'une des interfaces :
Utilisez l'assistant Auto pour allouer/copier/capturer des objets automatiques :
Si nécessaire, utilisez le casting du côté gauche pour éviter la duplication des informations de type et raccourcir la syntaxe.
Par exemple, voici un code sécurisé pour travailler avec TStringList en utilisant l'approche classique :
var
x: TStringList;
begin
x := TStringList.Create;
try
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
finally
x.Free;
end ;
end ;
Comme vous pouvez l'imaginer, utiliser davantage d'objets dans cette fonction augmentera considérablement et de manière non linéaire sa complexité. Alternativement, voici le code équivalent qui utilise IAutoObject et évolue bien mieux :
uses
DelphiUtils.AutoObjects;
var
x: IAutoObject<TStringList>;
begin
x := Auto.From(TStringList.Create);
x.Self.Add( ' Hi there ' );
x.Self.SaveToFile( ' test.txt ' );
end ;
Le compilateur émet le code de nettoyage nécessaire dans l'épilogue de la fonction et s'assure qu'il s'exécute même si des exceptions se produisent. De plus, cette approche permet de conserver une propriété partagée sur l'objet sous-jacent, ce qui vous permet d'enregistrer une référence qui peut survivre à la fonction actuelle (en la capturant dans une fonction anonyme et en la renvoyant, par exemple). Si vous n'avez pas besoin de cette fonctionnalité et souhaitez conserver un seul propriétaire qui libère l'objet à la fin de la fonction, vous pouvez simplifier encore davantage la syntaxe :
uses
NtUtils;
var
x: TStringList;
begin
x := Auto.From(TStringList.Create).Self;
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
end ;
Ce code est toujours équivalent au code initial. En interne, il crée une variable locale cachée qui stocke l'interface et libère ultérieurement l'objet.
Lorsque vous travaillez avec des allocations de mémoire dynamiques, il peut être pratique d'utiliser le casting du côté gauche comme suit :
var
x: IMemory<PByteArray>;
begin
IMemory(x) := Auto.AllocateDynamic( 100 );
x.Data[ 15 ] := 20 ;
end ;
Vous pouvez également créer des enregistrements gérés encadrés (alloués sur le tas) qui permettent de partager des types de valeur comme s'il s'agissait de types de référence. Notez qu'ils peuvent également inclure des champs gérés comme des chaînes Delphi et des tableaux dynamiques - le compilateur émet du code pour les libérer automatiquement :
type
TMyRecord = record
MyInteger: Integer;
MyArray: TArray<Integer>;
end ;
PMyRecord = ^TMyRecord;
var
x: IMemory<PMyRecord>;
begin
IMemory(x) := Auto.Allocate<TMyRecord>;
x.Data.MyInteger := 42 ;
x.Data.MyArray := [ 1 , 2 , 3 ];
end ;
Puisque Delphi utilise le comptage de références, il est toujours possible de perdre de la mémoire si deux objets ont une dépendance circulaire. Vous pouvez empêcher que cela se produise en utilisant des références faibles . Une telle référence ne compte pas pour prolonger la durée de vie, et la variable qui les stocke devient automatiquement nulle lorsque l'objet cible est détruit. Vous devez mettre à niveau une référence faible vers une référence forte avant de pouvoir l'utiliser. Voir Weak<I> de DelphiUtils.AutoObjects pour plus de détails.
Certains alias sont disponibles pour les types de pointeurs de taille variable couramment utilisés. Voici quelques exemples :
Les handles utilisent le type IHandle (voir DelphiUtils.AutoObjects), qui suit la logique décrite ci-dessus, ils ne nécessitent donc pas de fermeture explicite. Vous pouvez également trouver des alias pour IHandle (IScmHandle, ISamHandle, ILsaHandle, etc.), qui sont disponibles uniquement pour des raisons de lisibilité du code.
Si jamais vous avez besoin de vous approprier une valeur de handle dans un IHandle, vous avez besoin d'une classe qui implémente cette interface et qui sait comment libérer la ressource sous-jacente. Par exemple, NtUtils.Objects définit une telle classe pour les objets du noyau qui nécessitent l'appel de NtClose
. Il attache également une méthode d'assistance à Auto
, permettant de capturer les descripteurs du noyau par valeur via Auto.CaptureHandle(...)
. Pour créer un IHandle non propriétaire, utilisez Auto.RefHandle(...)
.
Les noms d'enregistrements, de classes et d'énumérations commencent par T
et utilisent CamelCase (exemple : TTokenStatistics
). Les pointeurs vers des enregistrements ou d'autres types de valeur commencent par P
(exemple : PTokenStatistics
). Les noms des interfaces commencent par I
(exemple : ISid
). Les constantes utilisent ALL_CAPITALS. Toutes les définitions de la couche d'en-têtes qui ont des noms officiels connus (tels que les types définis dans le SDK Windows) sont marquées avec un attribut SDKName
spécifiant ce nom.
La plupart des fonctions utilisent la convention de nom suivante : un préfixe du sous-système avec x à la fin (Ntx, Ldrx, Lsax, Samx, Scmx, Wsx, Usrx, ...) + Action + Cible/Type d'objet/etc. Les noms de fonctions utilisent également CamelCase.
La bibliothèque cible Windows 7 ou supérieur, éditions 32 et 64 bits. Cependant, certaines fonctionnalités peuvent être disponibles uniquement sur les dernières versions 64 bits de Windows 11. Quelques exemples sont le décrochage des appels système AppContainers et ntdll. Si une fonction de bibliothèque dépend d'une API qui pourrait ne pas être présente sur Windows 7, elle utilise l'importation différée et vérifie la disponibilité au moment de l'exécution.
Delphi est livré avec un système de réflexion riche que la bibliothèque utilise dans la couche NtUiLib . La plupart des types définis dans la couche Headers sont décorés d'attributs personnalisés (voir DelphiApi.Reflection) pour y parvenir. Ces décorations émettent des métadonnées utiles qui aident la bibliothèque à représenter avec précision des types de données complexes (comme PEB, TEB, USER_SHARED_DATA) lors de l'exécution et à produire des rapports étonnants avec une seule ligne de code.
Voici un exemple de représentation de TSecurityLogonSessionData
de Ntapi.NtSecApi utilisant NtUiLib.Reflection.Types :
Voici un aperçu de l'utilité des différents modules.
Unité de soutien | Description |
---|---|
DelphiUtils.AutoObjects | Gestion automatique de la durée de vie des ressources |
DelphiUtils.AutoEvents | Événements anonymes multi-abonnés |
DelphiUtils.Arrays | Assistants TArray |
DelphiUtils.Listes | Une primitive de liste génétique à double chaînage |
DelphiUtils.Async | Définitions de prise en charge des E/S asynchrones |
DelphiUtils.ExternalImport | Aides IAT à mots clés externes Delphi |
DelphiUtils.RangeChecks | Aides à la vérification de la portée |
NtUtils | Types de bibliothèques courants |
NtUtils.SysUtils | Manipulation de chaînes |
NtUtils.Erreurs | Conversion de code d'erreur |
NtUiLib.Erreurs | Recherche du nom du code d'erreur |
NtUiLib.Exceptions | Intégration des exceptions SysUtils |
DelphiUiLib.Strings | Embellissement des cordes |
DelphiUiLib.Reflection | Prise en charge RTTI de base |
DelphiUiLib.Reflection.Numeric | Représentation RTTI des types numériques |
DelphiUiLib.Reflection.Records | Représentation RTTI des types d'enregistrement |
DelphiUiLib.Reflection.Strings | Embellissement RTTI des chaînes |
NtUiLib.Reflection.Types | Représentation RTTI pour les types courants |
NtUiLib.Console | Assistants d'E/S de console |
NtUiLib.TaskDialog | Interface graphique basée sur TaskDialog |
NtUiLib.Erreurs.Dialog | Boîte de dialogue d'erreur de l'interface graphique |
NtUiLib.Exceptions.Dialog | Boîte de dialogue d'exception de l'interface graphique |
Unité système | Description |
---|---|
NtUtils.ActCtx | Contextes d'activation |
NtUtils.AntiHooking | Décrochage et appel système direct |
NtUtils.Com | COM, IDispatch, WinRT |
NtUtils.Csr | Inscription CSRSS/SxS |
NtUtils.DbgHelp | DbgHelp et symboles de débogage |
NtUtils.Debug | Objets de débogage |
NtUtils.Dism | API DISM |
NtUtils.Environnement | Variables d'environnement |
NtUtils.Environnement.Utilisateur | Variables d'environnement utilisateur |
NtUtils.Environment.Remote | Variables d'environnement d'autres processus |
NtUtils.Fichiers | Noms de fichiers Win32/NT |
NtUtils.Files.Open | Fichier et canal ouvrir/créer |
NtUtils.Files.Opérations | Opérations sur les fichiers |
NtUtils.Files.Directories | Énumération du répertoire de fichiers |
NtUtils.Files.FltMgr | API du gestionnaire de filtres |
NtUtils.Files.Mup | Plusieurs fournisseurs UNC |
NtUtils.Files.Volumes | Opérations de volume |
NtUtils.Files.Control | Opérations FSCTL |
NtUtils.ImageHlp | Analyse PE |
NtUtils.ImageHlp.Syscalls | Récupération du numéro d'appel système |
NtUtils.ImageHlp.DbgHelp | Symboles publics sans DbgHelp |
NtUtils.Jobs | Objets de travail et silos |
NtUtils.Jobs.Remote | Requêtes d'objets de travail inter-processus |
NtUtils.Ldr | Routines et analyse LDR |
NtUtils.Lsa | Politique LSA |
NtUtils.Lsa.Audit | Politique d'audit |
NtUtils.Lsa.Sid | Recherche SID |
NtUtils.Lsa.Logon | Sessions de connexion |
NtUtils.Manifestes | Générateur de manifeste Fusion/SxS |
NtUtils.Mémoire | Opérations de mémoire |
NtUtils.MiniDumps | Analyse du format Minidump |
NtUtils.Objects | Objets et poignées du noyau |
NtUtils.Objects.Snapshots | Gérer les instantanés |
NtUtils.Objects.Namespace | Espace de noms d'objet NT |
NtUtils.Objects.Remote | Opérations de gestion inter-processus |
NtUtils.Objects.Compare | Comparaison des poignées |
NtUtils.Packages | Packages d’applications et familles de packages |
NtUtils.Packages.SRCache | Cache du référentiel d'état |
NtUtils.Packages.WinRT | Informations sur le package basé sur WinRT |
NtUtils.Power | Fonctions liées à l'alimentation |
NtUtils.Processus | Objets de processus |
NtUtils.Processes.Info | Requête de processus/informations sur l'ensemble |
NtUtils.Processes.Info.Remote | Requête/ensemble de processus via injection de code |
NtUtils.Processes.Modules | Énumération LDR inter-processus |
NtUtils.Processes.Snapshots | Énumération des processus |
NtUtils.Processes.Create | Définitions courantes de création de processus |
NtUtils.Processes.Create.Win32 | Méthodes de création de processus Win32 |
NtUtils.Processes.Create.Shell | Méthodes de création de processus Shell |
NtUtils.Processes.Create.Native | NtCreateUserProcess et co. |
NtUtils.Processes.Create.Manual | NtCreateProcessEx |
NtUtils.Processes.Create.Com | Création de processus basée sur COM |
NtUtils.Processes.Create.Csr | Création de processus via SbApiPort |
NtUtils.Processes.Create.Package | Activation Appx |
NtUtils.Processes.Create.Remote | Création de processus par injection de code |
NtUtils.Processes.Create.Clone | Clonage de processus |
NtUtils.Profiles | Profils utilisateur et AppContainer |
NtUtils.Registre | Clés de registre |
NtUtils.Registry.Offline | Manipulation de la ruche hors ligne |
NtUtils.Registry.VReg | Virtualisation de registre basée sur des silos |
NtUtils.Sam | Base de données SAM |
NtUtils.Sections | Objets de projection de section/mémoire |
NtUtils.Sécurité | Descripteurs de sécurité |
NtUtils.Security.Acl | ACL et ACE |
NtUtils.Security.Sid | SID |
NtUtils.Security.AppContainer | AppContainer et SID de fonctionnalités |
NtUtils.Shellcode | Injection de code |
NtUtils.Shellcode.Dll | Injection de DLL |
NtUtils.Shellcode.Exe | Injection EXE |
NtUtils.Svc | Services GDS |
NtUtils.Svc.SingleTaskSvc | Mise en œuvre des services |
NtUtils.Synchronisation | Primitives de synchronisation |
NtUtils.Système | Informations système |
NtUtils.TaskScheduler | Planificateur de tâches |
NtUtils.Threads | Objets de discussion |
NtUtils.Tokens.Info | Requête de fil/informations sur l'ensemble |
NtUtils.Threads.Worker | Travailleurs de thread (pools de threads) |
NtUtils.Tokens | Objets jetons |
NtUtils.Tokens.Impersonate | Usurpation d'identité de jeton |
NtUtils.Tokens.Logon | Connexion utilisateur et S4U |
NtUtils.Tokens.AppModel | Politique de jeton AppModel |
NtUtils.Transactions | Objets de transaction (TmTx) |
NtUtils.Transactions.Remote | Forcer les processus dans les transactions |
NtUtils.UserManager | API du service de gestion des utilisateurs (Umgr) |
NtUtils.Wim | API d'imagerie Windows (*.wim) |
NtUtils.WinSafer | API plus sûre |
NtUtils.WinStation | API du serveur de terminaux |
NtUtils.WinUser | API utilisateur32/interface graphique |
NtUtils.WinUser.WindowAffinity | Modification de l'affinité de la fenêtre |
NtUtils.WinUser.WinstaLock | Verrouillage et déverrouillage des postes de fenêtre |
NtUtils.XmlLite | Analyse et création XML via XmlLite |
NtUiLib.AutoCompletion | Complétion automatique pour les contrôles d'édition |
NtUiLib.AutoCompletion.Namespace | Complétion automatique de l'espace de noms d'objet NT |
NtUiLib.AutoCompletion.Sid | Complétion automatique SID |
NtUiLib.AutoCompletion.Sid.Common | Fournisseurs/reconnaissances de noms SID simples |
NtUiLib.AutoCompletion.Sid.AppContainer | AppContainer et fournisseurs/reconnaisseurs de SID de package |
NtUiLib.AutoCompletion.Sid.Capabilities | Fournisseurs/reconnaisseurs de SID de capacité |
NtUiLib.WinCred | Boîte de dialogue Informations d'identification |