Trying to read version resources from the loaded kernel modules in my WinDbg extension

I'm writing a WinDbg extension and I'm trying to get the list of loaded kernel modules, each with a version number and description. As far as I know, both are available in the PE file's resources.

So at first I tried the following, using IDebugSymbols3 interface:

//I'm not checking return error codes for brevity

// pDebugSymb is a pointer to IDebugSymbols3 that you can get by 
// doing QueryInterface on IDebugClient

ULONG ncntLoaded, ncntUnloaded;
hr = pDebugSymb->GetNumberModules(&ncntLoaded, &ncntUnloaded);

for(ULONG m = 0; m < ncntLoaded; m++)
{
    ULONG64 uiBase;
    hr = pDebugSymb->GetModuleByIndex(m, &uiBase);

    ULONG uichSz = 0;
    hr = pDebugSymb->GetModuleVersionInformationWide(m, uiBase, 
        L"\\VarFileInfo\\Translation",
        NULL, 0, &uichSz);
    if(SUCCEEDED(hr))
    {
        //Continue on ...

    }
    else
    {
        Log_Error(L"hr=0x%x", hr); 
    }
}

This is just the part that fails.

What happens is that when I run it on a live x64 Windows 10 kernel target, GetModuleVersionInformationWide returns either 0xD0000147 or 0x80070715 error for almost all modules.

I tried doing this first:

//Reload all modules
hr = pDebugSymb->ReloadWide(L"/s");   // same as .reload /s

but that didn't help either.


Then I decided to get to the resource section via the loaded PE files manually:

// pDataSpace = pointer to IDebugDataSpaces

LIST_ENTRY* psLoadedModuleList = NULL;

HRESULT hr = pDataSpace->ReadDebuggerData(DEBUG_DATA_PsLoadedModuleListAddr, 
	&psLoadedModuleList, sizeof(psLoadedModuleList), NULL);

LIST_ENTRY* pEntry = psLoadedModuleList;

for(;pEntry;)
{
    LIST_ENTRY* pFlink;
    ULONG uicbRead;
    hr = pDataSpace->ReadVirtual((ULONG64)pEntry, &pFlink, sizeof(pFlink), &uicbRead);
    assert(SUCCEEDED(hr));

    pEntry = pFlink;
    if(pEntry == psLoadedModuleList)
    {
        //Done
        break;
    }
                    
    KLDR_DATA_TABLE_ENTRY dte;

    hr = pDataSpace->ReadVirtual((ULONG64)CONTAINING_RECORD(pEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks),
        &dte, sizeof(dte), &uicbRead);
    assert(SUCCEEDED(hr));

    void* pBaseAddr = dte.DllBase;
    DWORD uicbSz = dte.SizeOfImage;

    BYTE* pMem = new BYTE[uicbSz];

    uicbRead = 0;
    hr = pDataSpace->ReadVirtual((ULONG64)pBaseAddr, pMem, uicbSz, &uicbRead);
    assert(SUCCEEDED(hr));

    Log(L"mod_sz=0x%X, mod_read=0x%X", uicbSz, uicbRead);

    delete[] pMem;
}

where:

typedef struct _KLDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;
    PVOID ExceptionTable;
    ULONG ExceptionTableSize;
    PVOID GpValue;
    PNON_PAGED_DEBUG_INFO NonPagedDebugInfo;
    PVOID DllBase;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    USHORT LoadCount;
    USHORT __Unused5;
    PVOID SectionPointer;
    ULONG CheckSum;
    PVOID LoadedImports;
    PVOID PatchInformation;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

but when I run my code above, again on the live x64 kernel target (Win10), I'm seeing that my mod_read in the log is several KB less than mod_sz for almost all modules.

Upon further research, it seems like the PE file sections that are marked with IMAGE_SCN_MEM_DISCARDABLE are not present in memory. I'm still not sure why ReadVirtual doesn't load them upon request though.

So any idea how to get to the version resources in those loaded modules?
(Apart from reading them from the actual files.)

Pretty sure the only way to get the resource is going to be from the local files.

Discardable PE sections are deleted from memory and their corresponding PTEs are marked as entirely invalid (and not just paged out). So, ReadVirtual doesn't work because it doesn't know where to get the data from:

0: kd> !dh fastfat

...

SECTION HEADER #A
   .rsrc name
    1DC0 virtual size
   68000 virtual address
    2000 size of raw data
   67000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable
         (no align specified)
         Read Only
0: kd> !pte fastfat+68000 
                                           VA fffff800202d8000
PXE at FFFFF0783C1E0F80    PPE at FFFFF0783C1F0000    PDE at FFFFF0783E000808    PTE at FFFFF07C001016C0
contains 000000109FA0B063  contains 000000109FA0C063  contains 0A00001095702863  contains 0000000000000000
pfn 109fa0b   ---DA--KWEV  pfn 109fa0c   ---DA--KWEV  pfn 1095702   ---DA--KWEV  not valid

Given the local image and debugger info it could certainly figure it out, but it doesn't so you're going to have to instead.

Semi-related: Note that the executable files themselves are also available on the Symbol Server. So if you set your Executable Image Search Path to srv* you can automatically download the images as well:

0: kd> .exepath src*
0: kd> .reload /f fastfat.sys
0: kd> lmv mfastfat
Browse full module list
start             end                 module name
fffff800`20270000 fffff800`202de000   fastfat  # (pdb symbols)          c:\symbols\fastfat.pdb\AF58FA0BC4EF013ABB75DDDC1A264C4A1\fastfat.pdb
    Loaded symbol image file: fastfat.SYS
    Mapped memory image file: c:\symbols\fastfat.SYS\D5EBE9E96e000\fastfat.SYS

That means you don't have to go to the target to get the image files to do the resource lookup.

2 Likes

Oh, that's a good idea, Scott. Thanks for that suggestion!