RtlGetModuleBase & RtlGetProcAddress

2 de October de 2006 - Fernando Roberto

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! 🙂

ExGetProc.zip

One Response to “RtlGetModuleBase & RtlGetProcAddress”

  1. Keyboard Filter Man says:

    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!

Deixe um comentário