NtUtils é uma estrutura para programação de sistema Windows em Delphi que fornece um conjunto de funções com melhor tratamento de erros e integração de linguagem do que os cabeçalhos regulares do Winapi/Ntapi, combinados com trechos de código usados com frequência e tipos de dados inteligentes.
Você pode encontrar alguns exemplos de código em um repositório dedicado .
A biblioteca possui uma estrutura em camadas com três camadas no total:
Winapi.*.pas
integrados e a biblioteca Ntapi.*.pas
em seu programa; embora possa exigir a especificação explícita do prefixo do namespace em caso de nomes conflitantes.System.SysUtils
, System.Rtti
e System.Generics.Collections
.Portanto, tudo que você precisa já está incluído na versão gratuita mais recente do Delphi. Como bônus, compilar aplicativos de console sem RTTI (também conhecido como reflexão) produz executáveis extremamente pequenos. Veja exemplos para mais detalhes.
Como incluir todos os arquivos da biblioteca em seus projetos geralmente é redundante, você pode configurar o Delphi para descoberta automática de arquivos. Dessa forma, você pode especificar uma unidade na seção uses
e o Delphi irá incluí-la automaticamente e suas dependências no projeto. Para configurar as pastas onde o Delphi realiza a busca, vá em Projeto -> Opções -> Construção -> Compilador Delphi e adicione as seguintes linhas no Search Path:
.NtUtilsLibrary
.NtUtilsLibraryHeaders
.NtUtilsLibraryNtUiLib
Se os nomes ou locais das pastas forem diferentes para o seu projeto, você precisará ajustar essas linhas de forma correspondente.
A biblioteca indica falhas ao chamador, retornando valores TNTxStatus malsucedidos. TNtxStatus
(definido em NtUtils.pas) é uma estrutura que armazena um código de erro (compatível com NTSTATUS
, HRESULT
e erros Win32) além de metadados sobre a natureza da tentativa de operação, como o local da falha, um stacktrace e outros detalhes como máscara de acesso esperada/solicitada para chamadas abertas ou o valor da classe de informações para chamadas de consulta/definição. Para verificar se TNtxStatus
foi bem-sucedido, use seu método IsSuccess
. Para acessar ou definir o código de erro subjacente (dependendo do tipo e da preferência do chamador), use propriedades como Status
, HResult
, HResultAllowFalse
, Win32Error
, Win32ErrorOrSuccess
, IsHResult
, IsWin32
, etc.
Se preferir usar exceções, você sempre pode chamar RaiseOnError()
em um determinado TNtxStatus
. Observe que, a menos que você realmente queira usar exceções sem importar System.SysUtils
(o que é possível), é melhor incluir NtUiLib.Exceptions que traz uma classe de exceção ENtError
dedicada (derivada do EOSError
integrado).
NtUiLib.Errors anexa quatro métodos para representar valores TNtxStatus
como strings. Por exemplo, se o erro com valor 0xC0000061
vier de uma tentativa de alterar o ID de sessão de um token, esses métodos retornarão as seguintes informações:
Método | String retornada |
---|---|
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 |
Se você quiser ir ainda mais longe e mostrar uma caixa de mensagem bonita para o usuário, NtUiLib.Errors.Dialog oferece ShowNtxStatus()
. Além disso, incluir NtUiLib.Exceptions.Dialog trará o suporte de reflexão necessário e enriquecerá ainda mais o diálogo. Aqui está um exemplo de como pode ser:
TNtxStatus
oferece suporte à captura de rastreamentos de pilha (desativado por padrão). Para habilitá-lo, defina NtUtils.CaptureStackTraces
como True. Lembre-se de que a exibição de rastreamentos de pilha de maneira significativa requer a configuração da geração de símbolos de depuração para seu executável. Infelizmente, o Delphi só pode gerar arquivos .map
(configurados via Projeto -> Opções -> Construção -> Compilador Delphi -> Vinculação -> Arquivo de mapa), que geralmente não são suficientes. Você precisará de uma ferramenta map2dbg de terceiros para convertê-los em arquivos .dbg
, para que a API de símbolos possa entendê-los. Embora os arquivos .dbg
possam ser suficientes, é melhor processá-los ainda mais, convertendo-os para o .pdb
moderno via cv2pdb .
Para gerar símbolos de depuração automaticamente, adicione os seguintes eventos pós-compilação ao seu projeto:
map2dbg.exe $(OUTPUTPATH)
cv2pdb64.exe -n -s. -p$(OUTPUTNAME).pdb $(OUTPUTPATH)
O Delphi não inclui um coletor de lixo, portanto, apenas alguns tipos são gerenciados imediatamente: registros, strings, arrays dinâmicos e interfaces. Classes e ponteiros, por outro lado, requerem uma limpeza explícita que (em sua forma segura) requer o uso de blocos try-finally e, portanto, complica significativamente o programa. Para resolver esse problema, a biblioteca inclui recursos para gerenciamento automático do tempo de vida de memória e outros recursos, implementados em DelphiUtils.AutoObjects. Ao usar tipos deste módulo, instruímos o compilador a gerar automaticamente código seguro contra exceções para contar referências e liberar objetos automaticamente em epílogos de funções. Este módulo define diversas interfaces para vários tipos de recursos que podem exigir limpeza. Ele introduz a seguinte hierarquia:
gráfico LR;
subgrafo id1[Qualquer recurso]
IAutoReleasable
fim
subgrafo id2[Um valor Thandle]
IHandle
fim
subgrafo id3[Uma classe Delphi]
IAutoObject[IAutoObject<T>]
fim
subgrafo id4[Um ponteiro]
IAutoPointer[IAutoPointer<P>]
fim
subgrafo id5[Uma região de memória]
IMemória[IMemória<P>]
fim
IAutoReleasable --> IHandle;
IAutoReleasable --> IAutoObject;
IAutoReleasable --> IAutoPointer;
IAutoPointer --> IMemory;
IAutoReleasable
é o tipo base para todos os recursos que exigem ação na limpeza (automática). IHandle
serve como wrapper para recursos definidos por um valor THandle. IAutoObject<T>
é um wrapper genérico para liberar automaticamente classes Delphi (ou seja, qualquer coisa derivada de TObject). IAutoPointer<P>
define uma interface semelhante para liberar ponteiros alocados dinamicamente (onde o tamanho da região é irrelevante). IMemory<P>
fornece um wrapper para regiões de memória de tamanhos conhecidos que podem ser acessadas por meio de um ponteiro digitado, como registros em caixa gerenciados e não gerenciados.
A receita para usar esta facilidade é a seguinte:
Defina cada variável que precisa manter a propriedade (potencialmente compartilhada) sobre um objeto usando uma das interfaces:
Use o auxiliar Auto para alocar/copiar/capturar objetos automáticos:
Quando necessário, use a conversão do lado esquerdo que ajuda a evitar a duplicação de informações de tipo e pode encurtar a sintaxe.
Por exemplo, aqui está um código seguro para trabalhar com TStringList usando a abordagem clássica:
var
x: TStringList;
begin
x := TStringList.Create;
try
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
finally
x.Free;
end ;
end ;
Como você pode imaginar, usar mais objetos nesta função aumentará significativamente e de forma não linear sua complexidade. Alternativamente, aqui está o código equivalente que usa IAutoObject e aumenta muito melhor:
uses
DelphiUtils.AutoObjects;
var
x: IAutoObject<TStringList>;
begin
x := Auto.From(TStringList.Create);
x.Self.Add( ' Hi there ' );
x.Self.SaveToFile( ' test.txt ' );
end ;
O compilador emite o código de limpeza necessário no epílogo da função e garante que ele seja executado mesmo se ocorrerem exceções. Além disso, essa abordagem permite manter a propriedade compartilhada sobre o objeto subjacente, o que permite salvar uma referência que pode sobreviver à função atual (capturando-a em uma função anônima e retornando-a, por exemplo). Se você não precisa dessa funcionalidade e deseja manter um único proprietário que libera o objeto quando a função é encerrada, você pode simplificar ainda mais a sintaxe:
uses
NtUtils;
var
x: TStringList;
begin
x := Auto.From(TStringList.Create).Self;
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
end ;
Este código ainda é equivalente ao inicial. Internamente, ele cria uma variável local oculta que armazena a interface e posteriormente libera o objeto.
Ao trabalhar com alocações dinâmicas de memória, pode ser conveniente usar a conversão do lado esquerdo da seguinte maneira:
var
x: IMemory<PByteArray>;
begin
IMemory(x) := Auto.AllocateDynamic( 100 );
x.Data[ 15 ] := 20 ;
end ;
Você também pode criar registros gerenciados em caixa (alocados no heap) que permitem compartilhar tipos de valor como se fossem tipos de referência. Observe que eles também podem incluir campos gerenciados como strings Delphi e arrays dinâmicos - o compilador emite código para liberá-los automaticamente:
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 ;
Como o Delphi usa contagem de referências, ainda é possível vazar memória se dois objetos tiverem uma dependência circular. Você pode evitar que isso aconteça usando referências fracas . Tal referência não conta para prolongar a vida útil, e a variável que as armazena torna-se automaticamente nula quando o objeto alvo é destruído. Você precisa atualizar uma referência fraca para uma forte antes de poder usá-la. Consulte Weak<I> em DelphiUtils.AutoObjects para obter mais detalhes.
Existem alguns aliases disponíveis para tipos de ponteiros de tamanho variável comumente usados. Aqui estão alguns exemplos:
Os identificadores usam o tipo IHandle (consulte DelphiUtils.AutoObjects), que segue a lógica discutida acima, portanto, não exigem fechamento explícito. Você também pode encontrar alguns aliases para IHandle (IScmHandle, ISamHandle, ILsaHandle, etc.), que estão disponíveis apenas para facilitar a leitura do código.
Se você precisar apropriar-se de um valor de identificador em um IHandle, precisará de uma classe que implemente essa interface e saiba como liberar o recurso subjacente. Por exemplo, NtUtils.Objects define essa classe para objetos de kernel que requerem chamada NtClose
. Ele também anexa um método auxiliar a Auto
, permitindo capturar identificadores de kernel por valor via Auto.CaptureHandle(...)
. Para criar um IHandle não proprietário, use Auto.RefHandle(...)
.
Nomes de registros, classes e enumerações começam com T
e usam CamelCase (exemplo: TTokenStatistics
). Ponteiros para registros ou outros tipos de valor começam com P
(exemplo: PTokenStatistics
). Os nomes das interfaces começam com I
(exemplo: ISid
). Constantes usam ALL_CAPITALS. Todas as definições da camada de cabeçalhos que possuem nomes oficiais conhecidos (como os tipos definidos no SDK do Windows) são marcadas com um atributo SDKName
especificando esse nome.
A maioria das funções usa a seguinte convenção de nomes: um prefixo do subsistema com x no final (Ntx, Ldrx, Lsax, Samx, Scmx, Wsx, Usrx, ...) + Ação + Alvo/Tipo de objeto/etc. Os nomes das funções também usam CamelCase.
A biblioteca é voltada para Windows 7 ou superior, edições de 32 e 64 bits. Porém, algumas das funcionalidades podem estar disponíveis apenas nas versões mais recentes de 64 bits do Windows 11. Alguns exemplos são AppContainers e desengate de syscall ntdll. Se uma função de biblioteca depender de uma API que pode não estar presente no Windows 7, ela usará a importação atrasada e verificará a disponibilidade em tempo de execução.
Delphi vem com um rico sistema de reflexão que a biblioteca utiliza na camada NtUiLib . A maioria dos tipos definidos na camada Cabeçalhos são decorados com atributos personalizados (consulte DelphiApi.Reflection) para alcançá-lo. Essas decorações emitem metadados úteis que ajudam a biblioteca a representar com precisão tipos de dados complexos (como PEB, TEB, USER_SHARED_DATA) em tempo de execução e produzir relatórios surpreendentes com uma única linha de código.
Aqui está um exemplo de representação de TSecurityLogonSessionData
de Ntapi.NtSecApi usando NtUiLib.Reflection.Types:
Aqui está uma visão geral da finalidade dos diferentes módulos.
Unidade de apoio | Descrição |
---|---|
DelphiUtils.AutoObjects | Gerenciamento automático da vida útil dos recursos |
DelphiUtils.AutoEvents | Eventos anônimos de vários assinantes |
DelphiUtils.Arrays | Ajudantes TArray |
DelphiUtils.Lists | Uma primitiva de lista duplamente vinculada genética |
DelphiUtils.Async | Definições de suporte de E/S assíncrona |
DelphiUtils.ExternalImport | Ajudantes IAT de palavras-chave externas do Delphi |
DelphiUtils.RangeChecks | Ajudantes de verificação de alcance |
NtUtils | Tipos de biblioteca comuns |
NtUtils.SysUtils | Manipulação de cordas |
NtUtils.Errors | Conversão de código de erro |
NtUiLib.Errors | Pesquisa de nome de código de erro |
NtUiLib.Exceptions | Integração de exceção SysUtils |
DelphiUiLib.Strings | Embelezamento de cordas |
DelphiUiLib.Reflection | Suporte RTTI básico |
DelphiUiLib.Reflection.Numeric | Representação RTTI de tipos numéricos |
DelphiUiLib.Reflection.Records | Representação RTTI de tipos de registro |
DelphiUiLib.Reflection.Strings | Embelezamento RTTI de cordas |
NtUiLib.Reflection.Types | Representação RTTI para tipos comuns |
NtUiLib.Console | Auxiliares de E/S do console |
NtUiLib.TaskDialog | GUI baseada em TaskDialog |
NtUiLib.Errors.Dialog | Caixa de diálogo de erro da GUI |
NtUiLib.Exceptions.Dialog | Diálogo de exceção da GUI |
Unidade do sistema | Descrição |
---|---|
NtUtils.ActCtx | Contextos de ativação |
NtUtils.AntiHooking | Desenganchando e syscall direto |
NtUtils.Com | COM, IDispatch, WinRT |
NtUtils.Csr | Registro CSRSS/SxS |
NtUtils.DbgHelp | DbgHelp e símbolos de depuração |
NtUtils.Debug | Depurar objetos |
NtUtils.Dism | API DISM |
NtUtils.Environment | Variáveis de ambiente |
NtUtils.Environment.User | Variáveis de ambiente do usuário |
NtUtils.Environment.Remote | Variáveis de ambiente de outros processos |
NtUtils.Files | Nomes de arquivos Win32/NT |
NtUtils.Files.Open | Abrir/criar arquivo e pipe |
NtUtils.Files.Operations | Operações de arquivo |
NtUtils.Files.Directories | Enumeração de diretório de arquivos |
NtUtils.Files.FltMgr | API do gerenciador de filtros |
NtUtils.Files.Mup | Vários provedores UNC |
NtUtils.Files.Volumes | Operações de volume |
NtUtils.Files.Control | Operações do FSCTL |
NtUtils.ImageHlp | Análise PE |
NtUtils.ImageHlp.Syscalls | Recuperação de número Syscall |
NtUtils.ImageHlp.DbgHelp | Símbolos públicos sem DbgHelp |
NtUtils.Jobs | Objetos de trabalho e silos |
NtUtils.Jobs.Remote | Consultas de objetos de trabalho entre processos |
NtUtils.Ldr | Rotinas e análise LDR |
NtUtils.Lsa | Política LSA |
NtUtils.Lsa.Audit | Política de auditoria |
NtUtils.Lsa.Sid | Pesquisa de SID |
NtUtils.Lsa.Logon | Sessões de logon |
NtUtils.Manifestos | Construtor de manifesto Fusion/SxS |
NtUtils.Memória | Operações de memória |
NtUtils.MiniDumps | Análise de formato de minidespejo |
NtUtils.Objects | Objetos e identificadores do kernel |
NtUtils.Objects.Snapshots | Lidar com instantâneos |
NtUtils.Objects.Namespace | Espaço para nome do objeto NT |
NtUtils.Objects.Remote | Operações de manipulação entre processos |
NtUtils.Objects.Compare | Lidar com comparação |
NtUtils.Packages | Pacotes de aplicativos e famílias de pacotes |
NtUtils.Packages.SRCache | Cache do repositório de estado |
NtUtils.Packages.WinRT | Informações do pacote baseado em WinRT |
NtUtils.Power | Funções relacionadas à energia |
NtUtils.Processes | Objetos de processo |
NtUtils.Processes.Info | Consulta de processo/definir informações |
NtUtils.Processes.Info.Remote | Consulta/configuração de processo via injeção de código |
NtUtils.Processes.Modules | Enumeração LDR de processo cruzado |
NtUtils.Processes.Snapshots | Enumeração de processos |
NtUtils.Processes.Create | Definições comuns de criação de processos |
NtUtils.Processes.Create.Win32 | Métodos de criação de processos Win32 |
NtUtils.Processes.Create.Shell | Métodos de criação de processos Shell |
NtUtils.Processes.Create.Native | NtCreateUserProcess e companhia. |
NtUtils.Processes.Create.Manual | NtCreateProcessEx |
NtUtils.Processes.Create.Com | Criação de processo baseado em COM |
NtUtils.Processes.Create.Csr | Criação de processos via SbApiPort |
NtUtils.Processes.Create.Package | Ativação do aplicativo |
NtUtils.Processes.Create.Remote | Criação de processos via injeção de código |
NtUtils.Processes.Create.Clone | Clonagem de processo |
NtUtils.Profiles | Perfis de usuário e AppContainer |
NtUtils.Registry | Chaves de registro |
NtUtils.Registry.Offline | Manipulação de colmeia offline |
NtUtils.Registry.VReg | Virtualização de registro baseada em silo |
NtUtils.Sam | Banco de dados SAM |
NtUtils.Seções | Objetos de projeção de seção/memória |
NtUtils.Segurança | Descritores de segurança |
NtUtils.Security.Acl | ACLs e ACEs |
NtUtils.Security.Sid | SIDs |
NtUtils.Security.AppContainer | AppContainer e SIDs de capacidade |
NtUtils.Shellcode | Injeção de código |
NtUtils.Shellcode.Dll | Injeção de DLL |
NtUtils.Shellcode.Exe | Injeção de EXE |
NtUtils.Svc | Serviços SCM |
NtUtils.Svc.SingleTaskSvc | Implementação de serviço |
NtUtils.Synchronization | Primitivas de sincronização |
NtUtils.System | Informações do sistema |
NtUtils.TaskScheduler | Agendador de tarefas |
NtUtils.Threads | Objetos de discussão |
NtUtils.Tokens.Info | Consulta de tópico/definir informações |
NtUtils.Threads.Worker | Trabalhadores de threads (pools de threads) |
NtUtils.Tokens | Objetos de token |
NtUtils.Tokens.Impersonate | Representação de token |
NtUtils.Tokens.Logon | Login de usuário e S4U |
NtUtils.Tokens.AppModel | Política de token AppModel |
NtUtils.Transações | Objetos de transação (TmTx) |
NtUtils.Transactions.Remote | Forçando processos em transações |
NtUtils.UserManager | API do serviço Gerenciador de usuários (Umgr) |
NtUtils.Wim | API de imagens do Windows (*.wim) |
NtUtils.WinSafer | API mais segura |
NtUtils.WinStation | API do servidor de terminal |
NtUtils.WinUser | API User32/GUI |
NtUtils.WinUser.WindowAffinity | Modificação de afinidade de janela |
NtUtils.WinUser.WinstaLock | Bloquear e desbloquear estações de janela |
NtUtils.XmlLite | Análise e elaboração de XML via XmlLite |
NtUiLib.AutoCompletion | Preenchimento automático para controles de edição |
NtUiLib.AutoCompletion.Namespace | Preenchimento automático do namespace do objeto NT |
NtUiLib.AutoCompletion.Sid | Preenchimento automático de SID |
NtUiLib.AutoCompletion.Sid.Common | Provedores/reconhecedores de nomes SID simples |
NtUiLib.AutoCompletion.Sid.AppContainer | AppContainer e provedores/reconhecedores de SID de pacote |
NtUiLib.AutoCompletion.Sid.Capabilities | Provedores/reconhecedores de SID de capacidade |
NtUiLib.WinCred | Caixa de diálogo Credenciais |