Kernel AuxKlibGetImageExportDirectory and forwarders

Posted on

One of the newer additions to the DDK is the aux_klib library, which, among others, offers the routine AuxKlibGetImageExportDirectory. As its name suggests, AuxKlibGetImageExportDirectory offers a handy way to obtain a pointer to the export directory of a kernel module.

There is, however, one issue that – at least in my opinion – renders AuxKlibGetImageExportDirectory pretty much useless in most scenarios: Dealing with forwaders.

The primary motivation to call AuxKlibGetImageExportDirectory is to either enumerate the exports of a module or to find a specific export. In both cases, the code is likely to call at least one of the exported routines. To maintain binary compatibility, it would be risky for such code to rely on the fact that all exports that it aims to call are in fact ‘real’ exports and not forwarders. Rather, it is crucial to be prepared to find both types – exports and forwarders – in the export directory and handle each of them appropropriately.

So we need to tell an export from a forwarder. As it turns out, this is not quite as easy as checking some flag. Quoting the Microsoft Portable Executable and Common Object File Format Specification on the content of the export address table:

Each entry in the export address table is a field that uses one of two formats in the following table. If the address specified is not within the export section (as defined by the address and length that are indicated in the optional header), the field is an export RVA, which is an actual address in code or data. Otherwise, the field is a forwarder RVA, which names a symbol in another DLL.

And this exactly is the problem – only being provided the PIMAGE_EXPORT_DIRECTORY pointer, we do not know the start and end RVA of the export section. As a consequence, identifying forwarders is infeasible when using AuxKlibGetImageExportDirectory – which in turn makes it a pretty much useless function.

Workaround

Although AuxKlibGetImageExportDirectory is handy, the work it performs is rather trivial. Therefore, it is not hard to come up with code that, given the Load Address of a module, finds the export directory and properly checks for the existance of forwarders. The following code shows how:

    PIMAGE_DATA_DIRECTORY ExportDataDir;
    PIMAGE_EXPORT_DIRECTORY ExportDirectory;
    PIMAGE_DOS_HEADER DosHeader = ( PIMAGE_DOS_HEADER ) LoadAddress;
    PIMAGE_NT_HEADERS NtHeader; 
    
    PULONG FunctionRvaArray;
    PUSHORT OrdinalsArray;
    
    ULONG Index;
    
    //
    // Peek into PE image to obtain exports.
    //
    NtHeader = ( PIMAGE_NT_HEADERS ) 
      PtrFromRva( DosHeader, DosHeader->e_lfanew );
    if( IMAGE_NT_SIGNATURE != NtHeader->Signature )
    {
      //
      // Unrecognized image format.
      //
      return ...;
    }
    
    ExportDataDir = &NtHeader->OptionalHeader.DataDirectory
        [ IMAGE_DIRECTORY_ENTRY_EXPORT ];
    
    ExportDirectory = ( PIMAGE_EXPORT_DIRECTORY ) PtrFromRva( 
      LoadAddress, 
      ExportDataDir->VirtualAddress );
      
    
    if ( ExportDirectory->AddressOfNames == 0 ||
       ExportDirectory->AddressOfFunctions == 0 ||
       ExportDirectory->AddressOfNameOrdinals == 0 )
    {
      //
      // This module does not have any exports.
      //
      return ...;
    }
    
    FunctionRvaArray = ( PULONG ) PtrFromRva(
      LoadAddress,
      ExportDirectory->AddressOfFunctions );
    
    OrdinalsArray = ( PUSHORT ) PtrFromRva(
      LoadAddress,
      ExportDirectory->AddressOfNameOrdinals );
    
    for ( Index = 0; Index < 
          ExportDirectory->NumberOfNames; Index++ )
    {
      //
      // Get corresponding export ordinal.
      //
      USHORT Ordinal = ( USHORT ) OrdinalsArray[ Index ] 
        + ( USHORT ) ExportDirectory->Base;
    
      //
      // Get corresponding function RVA.
      //
      ULONG FuncRva = 
        FunctionRvaArray[ Ordinal - ExportDirectory->Base ];
    
      if ( FuncRva >= ExportDataDir->VirtualAddress && 
         FuncRva < ExportDataDir->VirtualAddress 
           + ExportDataDir->Size )
      {
        //
        // It is a forwarder.
        //
      }
      else 
      {
        //
        // It is an export.
        //
      }
    }
    

Any opinions expressed on this blog are Johannes' own. Refer to the respective vendor’s product documentation for authoritative information.
« Back to home