RtlGetModuleBase & RtlGetProcAddress
No post ExAllocatePool(WithoutTag) falei um pouco sobre os conflitos de utilizar APIs novas em drivers e acabar tornando-os incompatíveis com sistemas legados. Nossa proposta inicial era ter um único binário que pudesse ser executado tando em Windows NT como em sistemas mais recentes. Chegamos a uma solução que pode não ser considerada a ideal, onde utilizamos sempre as funções antigas mesmo em sistemas que suporte APIs mais novas.
Pensando em resolver este tipo de limitação, o DDK nos trouxe a MmGetSystemRoutineAddress. Analogamente à GetProcAddress exportada pela Kernel32.dll, a MmGetSystemRoutineAddress obtém o endereço de uma função dinamicamente a partir de seu nome de exportação. Mas o mundo ainda não estará a salvo enquanto o Windows NT sobreviver. Essa nova função é implementada somente a partir do Windows 2000.
Já que não temos uma alternativa para Windows NT, vamos fazer uma. Com um ou dois conceitos sobre PE, implementamos uma versão desta função. A estrutura do PE carrega alguns quilos de regras, mas para nossa necessidade podemos implementar uma versão light. Se você quer ter detalhes das regras adotadas pelo PE, você pode dar uma olhada no artigo An In-Depth Look into the Win32 Portable Executable File Format por Matt Pietrek.
Bom, agora que já lemos todo o artigo e já sabemos tudo sobre PE, segue protótipo do algoritmo básico de como caminhar pela estrutura do PE procurando por uma determinada função exportada pelo nome. Esta função não é fornecida pelo DDK do Windows NT e está definida no código fonte exemplo disponível para download.
NTSTATUS RtlGetProcAddress(IN PVOID pBaseAddress,
IN LPCSTR pszFunctionName,
IN NTPROC *pProcedure);
Repare que os parâmetros de entrada são o endereço base, o nome da função desejada e por fim o endereço onde será armazenado o endereço desta função. Mas onde é que vou conseguir esse tal de endereço base? Lembre-se de que as funções que estamos procurando são exportadas pela ntoskrnl.lib e que são implementadas no módulo ntoskrnl.exe. Para nos certificar de que a função que procuramos é realmente exportada por este módulo, utilize o "Dependecy Walker" para visualizar a tabela de exportação deste módulo. Como já comentei antes, algumas funções são implementadas como macros, desta forma, sua definição estará em um arquivo de header e não na tabela de exportação.
Para obtermos o endereço base do módulo que exporta estas funções, vamos utilizar a ZwQuerySystemInformation, que embora não seja documentada pela Microsoft, já existem várias publicações que comentam a respeito dela. Desta forma definiremos a RtlGetModuleBase que terá o comportamento similar à bem conhecida GetModuleHandle. Esta função também está definida no exemplo para download.
NTSTATUS
NTAPI
ZwQuerySystemInformation(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL);
 
Existem muitas funções e definições não documentadas que podem ser de extrema utilidade. O livro Windows NT/2000 Native Api Reference por Gary Nebbett é excelente para fazer uso destas funções. Ele traz protótipos, enums, estruturas utilizadas em chamadas, descrições do que cada função faz e de cada parâmetro. Aqui em nosso exemplo, vamos utilizar apenas as declarações abaixo para obter informações sobre os módulos carregados no address space do sistema.
typedef struct _SYSTEM_MODULE_INFORMATION // Information Class 11 { ULONG Reserved[2]; PVOID Base; ULONG Size; ULONG Flags; USHORT Index; USHORT Unknown; USHORT LoadCount; USHORT ModuleNameOffset; CHAR ImageName[256]; } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
O membro "Base" da estrutura acima nos traz o endereço base do módulo que é onde o PE está. No código de exemplo está a definição da nossa rotina RtlGetModuleBase que tem o seguinte protótipo.
NTSTATUS RtlGetModuleBase(IN LPCSTR pszModuleName,
OUT PVOID* ppBaseAddress);
 
Com a união dos seus poderes, agora poderemos saber se uma determinada API é implementada no sistema corrente e obter seu endereço. Assim é perfeitamente possível ter um único binário que possa rodar tanto em Windows NT utilizando o ExFreePool, como em sistemas posteriores utilizando o ExFreePoolWithTag. Abaixo segue um exemplo bem básico como sempre. É claro que podemos criar uma única rotina de alocação que faria todo o trabalho sujo.
#include "GetProcAddr.h" //-f--> Tipo para o ponteiro de função ExFreePoolWithTag typedef VOID (NTAPI* PF_EX_FREE_POOL_WITH_TAG) ( IN PVOID P, IN ULONG Tag ); VOID OnDriverUnload(PDRIVER_OBJECT pDriverObj) { //-f--> Esta rotina está aqui apenas para permitir // que o driver seja terminado, mesmo vazia. } /**** *** ** Era uma vez um Driver... ** */ NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pusRegistryPath) { NTSTATUS nts; PVOID pBaseAddress, pTemp; PF_EX_FREE_POOL_WITH_TAG pfExFreePoolWithTag; //-f--> Seta rotina de finalização pDriverObj->DriverUnload = OnDriverUnload; //-f--> Obtem o endereço base nts = RtlGetModuleBase("ntoskrnl.exe", &pBaseAddress); //-f--> Testar retorno não mata ninguem, mas a falta // pode matar seu sistema if (!NT_SUCCESS(nts)) return nts; //-f--> Alocando memória para teste do Free pTemp = ExAllocatePoolWithTag(NonPagedPool, 10, 'tseT'); //-f--> Obtem o endereço da API nts = RtlGetProcAddress(pBaseAddress, "ExFreePoolWithTag", (NTPROC*)&pfExFreePoolWithTag); if (NT_SUCCESS(nts)) { //-f--> Se o sistema implementa esta API, então // obteremos sucesso e poderemos chama-la pfExFreePoolWithTag(pTemp, 'tseT'); } else { //-f--> Quem não tem cão, caça com gato. ExFreePool(pTemp); } return STATUS_SUCCESS; }
Have fun! :-)
Download ExGetProc.zip



1 Comments:
Como este post ainda naum tem um comentario, eu tive q deixar o meu, muito bom cara, tava estudando PE a uma semana, isso me veio muito a calhar!
Post a Comment
<< Home