NtUtils es un marco para la programación del sistema Windows en Delphi que proporciona un conjunto de funciones con mejor manejo de errores e integración del lenguaje que los encabezados Winapi/Ntapi normales, combinados con fragmentos de código de uso frecuente y tipos de datos inteligentes.
Puede encontrar algún código de ejemplo en un repositorio dedicado .
La biblioteca tiene una estructura en capas con tres capas en total:
Winapi.*.pas
y la biblioteca Ntapi.*.pas
en su programa; aunque es posible que sea necesario especificar explícitamente el prefijo del espacio de nombres en caso de nombres en conflicto.System.SysUtils
, System.Rtti
y System.Generics.Collections
.Por lo tanto, todo lo que necesita ya está incluido en la última versión gratuita de Delphi. Como beneficio adicional, compilar aplicaciones de consola sin RTTI (también conocido como reflexión) produce ejecutables extremadamente pequeños. Vea ejemplos para más detalles.
Dado que incluir todos los archivos de la biblioteca en sus proyectos suele ser redundante, puede configurar Delphi para el descubrimiento automático de archivos. De esta manera, puede especificar una unidad en la sección uses
y Delphi la incluirá automáticamente junto con sus dependencias en el proyecto. Para configurar las carpetas donde Delphi realiza la búsqueda, vaya a Proyecto -> Opciones -> Construcción -> Compilador Delphi y agregue las siguientes líneas en la Ruta de búsqueda:
.NtUtilsLibrary
.NtUtilsLibraryHeaders
.NtUtilsLibraryNtUiLib
Si los nombres o ubicaciones de las carpetas son diferentes para su proyecto, debe ajustar estas líneas en consecuencia.
La biblioteca indica errores al autor de la llamada devolviendo valores TNtxStatus incorrectos. TNtxStatus
(definido en NtUtils.pas) es una estructura que almacena un código de error (compatible con NTSTATUS
, HRESULT
y Win32 Errors) además de metadatos sobre la naturaleza de la operación intentada, como la ubicación del error, un seguimiento de pila y otros detalles como la máscara de acceso esperada/solicitada para llamadas abiertas o el valor de clase de información para llamadas de consulta/establecimiento. Para comprobar si TNtxStatus
tiene éxito, utilice su método IsSuccess
. Para acceder o configurar el código de error subyacente (según su tipo y la preferencia de la persona que llama), utilice propiedades como Status
, HResult
, HResultAllowFalse
, Win32Error
, Win32ErrorOrSuccess
, IsHResult
, IsWin32
, etc.
Si prefiere usar excepciones, siempre puede llamar RaiseOnError()
en un TNtxStatus
determinado. Tenga en cuenta que, a menos que realmente desee utilizar excepciones sin importar System.SysUtils
(lo cual es posible), es mejor incluir NtUiLib.Exceptions que trae una clase de excepción ENtError
dedicada (derivada del EOSError
incorporado).
NtUiLib.Errors adjunta cuatro métodos para representar valores TNtxStatus
como cadenas. Por ejemplo, si el error con el valor 0xC0000061
proviene de un intento de cambiar el ID de sesión de un token, estos métodos devolverán la siguiente información:
Método | cadena devuelta |
---|---|
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 desea ir aún más lejos y mostrar un bonito cuadro de mensaje al usuario, NtUiLib.Errors.Dialog ofrece ShowNtxStatus()
. Además, incluir NtUiLib.Exceptions.Dialog brindará el soporte de reflexión necesario y enriquecerá aún más el diálogo. A continuación se muestra un ejemplo de cómo podría verse:
TNtxStatus
admite la captura de seguimientos de pila (deshabilitado de forma predeterminada). Para habilitarlo, configure NtUtils.CaptureStackTraces
en True. Tenga en cuenta que mostrar seguimientos de pila de manera significativa requiere configurar la generación de símbolos de depuración para su ejecutable. Desafortunadamente, Delphi solo puede generar archivos .map
(configurados a través de Proyecto -> Opciones -> Construcción -> Compilador de Delphi -> Vinculación -> Archivo de mapa) que generalmente no son suficientes. Necesitará una herramienta map2dbg de terceros para convertirlos en archivos .dbg
, de modo que la API de símbolos pueda entenderlos. Si bien los archivos .dbg
pueden ser suficientes, es mejor procesarlos aún más convirtiéndolos al .pdb
moderno a través de cv2pdb .
Para generar símbolos de depuración automáticamente, agregue los siguientes eventos posteriores a la compilación en su proyecto:
map2dbg.exe $(OUTPUTPATH)
cv2pdb64.exe -n -s. -p$(OUTPUTNAME).pdb $(OUTPUTPATH)
Delphi no incluye un recolector de basura, por lo que solo se administran unos pocos tipos de forma inmediata: registros, cadenas, matrices dinámicas e interfaces. Las clases y los punteros, por otro lado, requieren una limpieza explícita que (en su forma segura) requiere el uso de bloques try-finally y, por lo tanto, complica significativamente el programa. Para abordar este problema, la biblioteca incluye funciones para la gestión automática de la vida útil de la memoria y otros recursos, implementadas en DelphiUtils.AutoObjects. Al utilizar tipos de este módulo, le indicamos al compilador que genere automáticamente código seguro para excepciones para contar referencias y liberar objetos automáticamente en epílogos de funciones. Este módulo define varias interfaces para varios tipos de recursos que pueden requerir limpieza. Introduce la siguiente jerarquía:
gráfico LR;
subgrafo id1[Cualquier recurso]
IAutoReleasable
fin
subgrafo id2[Un valor THandle]
Yo manejar
fin
subgrafo id3[Una clase Delphi]
IAutoObjeto[IAutoObjeto<T>]
fin
subgrafo id4[Un puntero]
IAutoPointer[IAutoPointer<P>]
fin
subgrafo id5[Una región de memoria]
IMemoria[IMemoria<P>]
fin
IAutoReleasable --> IHandle;
IAutoReleasable --> IAutoObject;
IAutoReleasable --> IAutoPointer;
IAutoPointer --> IMemoria;
IAutoReleasable
es el tipo base para todos los recursos que requieren realizar acciones de limpieza (automática). IHandle
sirve como contenedor para recursos definidos por un valor THandle. IAutoObject<T>
es un contenedor genérico para liberar automáticamente clases Delphi (es decir, cualquier cosa derivada de TObject). IAutoPointer<P>
define una interfaz similar para liberar punteros asignados dinámicamente (donde el tamaño de la región es irrelevante). IMemory<P>
proporciona un contenedor para regiones de memoria de tamaños conocidos a las que se puede acceder mediante un puntero escrito, como registros en cuadros administrados y no administrados.
La receta para utilizar esta instalación es la siguiente:
Defina cada variable que necesita mantener la propiedad (potencialmente compartida) sobre un objeto usando una de las interfaces:
Utilice el asistente automático para asignar/copiar/capturar objetos automáticos:
Cuando sea necesario, utilice la conversión del lado izquierdo, lo que ayuda a evitar la duplicación de información de tipo y puede acortar la sintaxis.
Por ejemplo, aquí hay un código seguro para trabajar con TStringList usando el enfoque clásico:
var
x: TStringList;
begin
x := TStringList.Create;
try
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
finally
x.Free;
end ;
end ;
Como puede imaginar, el uso de más objetos en esta función aumentará significativamente y de forma no lineal su complejidad. Alternativamente, aquí está el código equivalente que usa IAutoObject y se escala mucho mejor:
uses
DelphiUtils.AutoObjects;
var
x: IAutoObject<TStringList>;
begin
x := Auto.From(TStringList.Create);
x.Self.Add( ' Hi there ' );
x.Self.SaveToFile( ' test.txt ' );
end ;
El compilador emite el código de limpieza necesario en el epílogo de la función y se asegura de que se ejecute incluso si se producen excepciones. Además, este enfoque permite mantener la propiedad compartida sobre el objeto subyacente, lo que le permite guardar una referencia que puede sobrevivir a la función actual (capturándola en una función anónima y devolviéndola, por ejemplo). Si no necesita esta funcionalidad y desea mantener un único propietario que libere el objeto cuando la función finaliza, puede simplificar aún más la sintaxis:
uses
NtUtils;
var
x: TStringList;
begin
x := Auto.From(TStringList.Create).Self;
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
end ;
Este código sigue siendo equivalente al inicial. Internamente, crea una variable local oculta que almacena la interfaz y luego libera el objeto.
Cuando se trabaja con asignaciones de memoria dinámica, puede resultar conveniente utilizar la conversión del lado izquierdo de la siguiente manera:
var
x: IMemory<PByteArray>;
begin
IMemory(x) := Auto.AllocateDynamic( 100 );
x.Data[ 15 ] := 20 ;
end ;
También puede crear registros administrados encuadrados (asignados en el montón) que permitan compartir tipos de valores como si fueran tipos de referencia. Tenga en cuenta que también pueden incluir campos administrados como cadenas de Delphi y matrices dinámicas; el compilador emite código para liberarlos automáticamente:
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 ;
Dado que Delphi utiliza el recuento de referencias, aún es posible perder memoria si dos objetos tienen una dependencia circular. Puedes evitar que esto suceda utilizando referencias débiles . Dicha referencia no cuenta para prolongar la vida útil, y la variable que las almacena se vuelve automáticamente nula cuando se destruye el objeto de destino. Debe actualizar una referencia débil a una fuerte antes de poder usarla. Consulte Débil<I> de DelphiUtils.AutoObjects para obtener más detalles.
Hay algunos alias disponibles para tipos de punteros de tamaño variable utilizados habitualmente. A continuación se muestran algunos ejemplos:
Los identificadores utilizan el tipo IHandle (consulte DelphiUtils.AutoObjects), que sigue la lógica analizada anteriormente, por lo que no requieren un cierre explícito. También puede encontrar algunos alias para IHandle (IScmHandle, ISamHandle, ILsaHandle, etc.), que están disponibles simplemente para facilitar la lectura del código.
Si alguna vez necesita tomar posesión de un valor de identificador en un IHandle, necesita una clase que implemente esta interfaz y que sepa cómo liberar el recurso subyacente. Por ejemplo, NtUtils.Objects define dicha clase para objetos del kernel que requieren llamar NtClose
. También adjunta un método auxiliar a Auto
, permitiendo capturar identificadores del kernel por valor a través de Auto.CaptureHandle(...)
. Para crear un IHandle que no sea propietario, use Auto.RefHandle(...)
.
Los nombres de registros, clases y enumeraciones comienzan con T
y usan CamelCase (ejemplo: TTokenStatistics
). Los punteros a registros u otros tipos de valores comienzan con P
(ejemplo: PTokenStatistics
). Los nombres de las interfaces comienzan con I
(ejemplo: ISid
). Las constantes usan ALL_CAPITALS. Todas las definiciones de la capa de encabezados que tienen nombres oficiales conocidos (como los tipos definidos en el SDK de Windows) están marcadas con un atributo SDKName
que especifica este nombre.
La mayoría de las funciones utilizan la siguiente convención de nombres: un prefijo del subsistema con x al final (Ntx, Ldrx, Lsax, Samx, Scmx, Wsx, Usrx, ...) + Acción + Destino/Tipo de objeto/etc. Los nombres de funciones también usan CamelCase.
La biblioteca está dirigida a Windows 7 o superior, tanto en ediciones de 32 como de 64 bits. Sin embargo, es posible que algunas de las funciones solo estén disponibles en las últimas versiones de 64 bits de Windows 11. Algunos ejemplos son AppContainers y la desconexión de llamadas al sistema ntdll. Si una función de biblioteca depende de una API que podría no estar presente en Windows 7, utiliza la importación retrasada y verifica la disponibilidad en tiempo de ejecución.
Delphi viene con un rico sistema de reflexión que la biblioteca utiliza dentro de la capa NtUiLib . La mayoría de los tipos definidos en la capa Encabezados están decorados con atributos personalizados (ver DelphiApi.Reflection) para lograrlo. Estas decoraciones emiten metadatos útiles que ayudan a la biblioteca a representar con precisión tipos de datos complejos (como PEB, TEB, USER_SHARED_DATA) en tiempo de ejecución y producir informes sorprendentes con una sola línea de código.
Aquí hay una representación de ejemplo de TSecurityLogonSessionData
de Ntapi.NtSecApi usando NtUiLib.Reflection.Types:
Aquí se ofrece una descripción general del propósito de los diferentes módulos.
unidad de apoyo | Descripción |
---|---|
DelphiUtils.AutoObjetos | Gestión automática de la vida útil de los recursos |
DelphiUtils.AutoEvents | Eventos anónimos de múltiples suscriptores |
DelphiUtils.Arrays | Ayudantes de matriz |
DelphiUtils.Listas | Una primitiva de lista genética doblemente enlazada. |
DelphiUtils.Async | Definiciones de soporte de E/S asíncronas |
DelphiUtils.Importación externa | Ayudantes IAT de palabras clave externas de Delphi |
DelphiUtils.RangeChecks | Ayudantes de control de alcance |
NtUtils | Tipos de biblioteca comunes |
NtUtils.SysUtils | manipulación de cuerdas |
Errores NtUtils | Conversión de código de error |
Errores de NtUiLib. | Búsqueda de nombre de código de error |
NtUiLib.Excepciones | Integración de excepciones de SysUtils |
DelphiUiLib.Strings | Embellecimiento de cuerdas |
DelphiUiLib.Reflexión | Soporte básico RTTI |
DelphiUiLib.Reflection.Numeric | Representación RTTI de tipos numéricos. |
DelphiUiLib.Reflection.Records | Representación RTTI de tipos de registros |
DelphiUiLib.Reflection.Strings | RTTI embellecimiento de cuerdas |
NtUiLib.Reflexión.Tipos | Representación RTTI para tipos comunes |
NtUiLib.Consola | Ayudantes de E/S de consola |
NtUiLib.TaskDialog | GUI basada en TaskDialog |
NtUiLib.Errores.Diálogo | Cuadro de diálogo de error de la GUI |
NtUiLib.Excepciones.Diálogo | Diálogo de excepción de GUI |
Unidad del sistema | Descripción |
---|---|
NtUtils.ActCtx | Contextos de activación |
NtUtils.AntiHooking | Desenganche y llamada directa al sistema |
NtUtils.Com | COM, IDispatch, WinRT |
NtUtils.Csr | Registro CSRSS/SxS |
NtUtils.DbgAyuda | DbgHelp y símbolos de depuración |
NtUtils.Depuración | Objetos de depuración |
NtUtils.Dismo | API DISM |
NtUtils.Entorno | Variables ambientales |
NtUtils.Environment.Usuario | Variables de entorno de usuario |
NtUtils.Environment.Remote | Variables de entorno de otros procesos. |
Archivos NtUtils | Nombres de archivos Win32/NT |
NtUtils.Files.Open | Abrir/crear archivo y canalización |
NtUtils.Files.Operaciones | Operaciones de archivos |
NtUtils.Archivos.Directorios | Enumeración del directorio de archivos |
NtUtils.Files.FltMgr | API del administrador de filtros |
NtUtils.Files.Mup | Proveedor UNC múltiple |
NtUtils.Files.Volumes | Operaciones de volumen |
NtUtils.Files.Control | Operaciones FSCTL |
NtUtils.ImageHlp | análisis de PE |
NtUtils.ImageHlp.Syscalls | Recuperación de número de llamada al sistema |
NtUtils.ImageHlp.DbgAyuda | Símbolos públicos sin DbgHelp |
NtUtils.Trabajos | Objetos de trabajo y silos |
NtUtils.Jobs.Remoto | Consultas de objetos de trabajo entre procesos |
NtUtils.Ldr | Rutinas LDR y análisis |
NtUtils.Lsa | política de LSA |
NtUtils.Lsa.Auditoría | Política de auditoría |
NtUtils.Lsa.Sid | búsqueda de SID |
NtUtils.Lsa.Inicio de sesión | Sesiones de inicio de sesión |
NtUtils.Manifiestos | Generador de manifiestos Fusion/SxS |
NtUtils.Memoria | Operaciones de memoria |
NtUtils.MiniDumps | Análisis de formato de minivolcado |
NtUtils.Objetos | Objetos y identificadores del kernel |
NtUtils.Objects.Instantáneas | Manejar instantáneas |
NtUtils.Objects.Espacio de nombres | Espacio de nombres de objetos NT |
NtUtils.Objetos.Remoto | Operaciones de manejo entre procesos |
NtUtils.Objects.Comparar | Comparación de mangos |
NtUtils.Paquetes | Paquetes de aplicaciones y familias de paquetes |
NtUtils.Packages.SRCache | Caché del repositorio estatal |
NtUtils.Packages.WinRT | Información del paquete basado en WinRT |
NtUtils.Power | Funciones relacionadas con la energía |
NtUtils.Procesos | Objetos de proceso |
NtUtils.Procesos.Info | Consulta de proceso/establecer información |
NtUtils.Procesos.Info.Remoto | Consulta de proceso/establecido mediante inyección de código |
NtUtils.Procesos.Módulos | Enumeración LDR entre procesos |
NtUtils.Procesos.Instantáneas | Enumeración de procesos |
NtUtils.Procesos.Crear | Definiciones comunes de creación de procesos |
NtUtils.Procesos.Crear.Win32 | Métodos de creación de procesos Win32 |
NtUtils.Procesos.Crear.Shell | Métodos de creación de procesos de Shell |
NtUtils.Procesos.Crear.Nativo | NtCreateUserProcess y compañía. |
NtUtils.Procesos.Crear.Manual | NtCreateProcessEx |
NtUtils.Processes.Create.Com | Creación de procesos basados en COM |
NtUtils.Procesos.Crear.Csr | Creación de procesos a través de SbApiPort |
NtUtils.Procesos.Crear.Paquete | Activación de Appx |
NtUtils.Procesos.Crear.Remoto | Creación de procesos mediante inyección de código. |
NtUtils.Procesos.Crear.Clonar | Clonación de procesos |
NtUtils.Perfiles | Perfiles de usuario y contenedor de aplicaciones |
NtUtils.Registro | Claves de registro |
NtUtils.Registry.Sin conexión | Manipulación de colmena sin conexión |
NtUtils.Registry.VReg | Virtualización de registros basada en silos |
NtUtils.Sam | base de datos SAM |
NtUtils.Secciones | Objetos de proyección de sección/memoria |
NtUtils.Seguridad | Descriptores de seguridad |
NtUtils.Security.Acl | ACL y ACE |
NtUtils.Security.Sid | SID |
NtUtils.Security.AppContainer | AppContainer y SID de capacidad |
NtUtils.Shellcode | inyección de código |
NtUtils.Shellcode.Dll | inyección de DLL |
NtUtils.Shellcode.Exe | inyección EXE |
NtUtils.Svc | servicios SCM |
NtUtils.Svc.SingleTaskSvc | Implementación del servicio |
NtUtils.Sincronización | Primitivas de sincronización |
NtUtils.Sistema | Información del sistema |
NtUtils.TaskScheduler | Programador de tareas |
NtUtils.Threads | Objetos de hilo |
NtUtils.Tokens.Info | Consulta de hilo/establecer información |
NtUtils.Threads.Trabajador | Trabajadores de subprocesos (grupos de subprocesos) |
NtUtils.Tokens | Objetos simbólicos |
NtUtils.Tokens.Impersonate | Suplantación de tokens |
NtUtils.Tokens.Logon | Inicio de sesión de usuario y S4U |
NtUtils.Tokens.AppModel | Política de modelo de aplicación de token |
NtUtils.Transacciones | Objetos de transacción (TmTx) |
NtUtils.Transacciones.Remoto | Forzar procesos a realizar transacciones |
NtUtils.UserManager | API del servicio Administrador de usuarios (Umgr) |
NtUtils.Wim | API de imágenes de Windows (*.wim) |
NtUtils.WinSafer | API más segura |
NtUtils.WinStation | API del servidor terminal |
NtUtils.WinUser | Usuario32/API GUI |
NtUtils.WinUser.WindowAffinity | Modificación de afinidad de ventana |
NtUtils.WinUser.WinstaLock | Bloqueo y desbloqueo de estaciones de ventana |
NtUtils.XmlLite | Análisis y elaboración de XML a través de XmlLite |
NtUiLib.AutoCompleción | Autocompletar para controles de edición |
NtUiLib.AutoCompletion.Espacio de nombres | Autocompletado del espacio de nombres de objetos NT |
NtUiLib.AutoCompletion.Sid | Autocompletado de SID |
NtUiLib.AutoCompletion.Sid.Común | Proveedores/reconocedores de nombres SID simples |
NtUiLib.AutoCompletion.Sid.AppContainer | Proveedores/reconocedores de SID de AppContainer y paquetes |
NtUiLib.AutoCompletion.Sid.Capabilities | Capacidad de proveedores/reconocedores de SID |
NtUiLib.WinCred | Diálogo de credenciales |