1. Using nt!_MiSystemVaType to navigate dynamic kernel address space in Windows7

    32-bit Windows Vista and later use a feature known as Dynamic Kernel Address Space. To quote from a technical article - the Memory Manager dynamically manages the kernel's address space, allocating and deallocating space to various uses to meet the needs of the system. As a result, the amount of virtual memory being used for internal components, device drivers, the file system cache, kernel stacks, system PTE's, per-session code data structures as well as paged and nonpaged pool memory will grow and shrink based on system activity.

    The key to keeping track of all this dynamic memory lies in the unexported pointer nt!_MiSystemVaType, a mapped array of byte values that describes both the type of memory allocation, and by virtue of the indexed position within the array, the location and size of the memory block. Each time there is a new memory allocation, the MiSystemVaType array is updated.

    In this code project I will try to show how to use MiSystemVaType to navigate the dynamic kernel address space to get a complete mapping of the various allocation types. In addition, I'll give an example of how to use it to find and identify loaded drivers, as well as discuss how it might be used to conduct efficient memory pool searches.

    Here are a few background articles on the subject at hand:

    Understanding the kernel address space on 32-bit Windows Vista

    Inside the Windows Vista Kernel: Part 2

    Windows® Internals, Fifth Edition
    9.5.7 Dynamic System Virtual Address Space Management


    nt!_MiSystemVaType is a pointer to an array of byte values of enum type MI_SYSTEM_VA_TYPE. Each byte in the array describes a single Large Page and maps, in sequential order, the entire upper 2GB of logical address space from 0x80000000 (MmSystemRangeStart) - 0xFFFFFFFF. The size of the byte array is either 0x400 when PAE is enabled, where the default size of a Large Page is 2MB, or 0x200 in non-PAE mode, which uses a Large Page size of 4MB.

    The enum type values can be listed with WinDbg/LiveKd:

    kd> dt nt!_MI_SYSTEM_VA_TYPE
       MiVaUnused = 0n0
       MiVaSessionSpace = 0n1
       MiVaProcessSpace = 0n2
       MiVaBootLoaded = 0n3
       MiVaPfnDatabase = 0n4
       MiVaNonPagedPool = 0n5
       MiVaPagedPool = 0n6
       MiVaSpecialPoolPaged = 0n7
       MiVaSystemCache = 0n8
       MiVaSystemPtes = 0n9
       MiVaHal = 0n10
       MiVaSessionGlobalSpace = 0n11
       MiVaDriverImages = 0n12
       MiVaSpecialPoolNonPaged = 0n13
       MiVaMaximumType = 0n14

    PAE mode:

    The Physical Address Extension (PAE) processor feature enables use of 64-bit page table entries for physical addresses that are wider than 32 bits. If PAE is enabled, the size of page table entries (PTEs) are increased from 32 to 64 bits (4 to 8 bytes). Consequently, the size of a Large Page is reduced from 4MB to 2MB in PAE mode. One can determine the size of the PTE data structure, nt!_MMPTE, (and hence if PAE is enabled or not) with the command:

    kd> dt -v nt!_MMPTE
    struct _MMPTE, 1 elements, 0x8 bytes
    To determine if PAE is enabled programmatically we can read the ProcessorFeatures field of KUSER_SHARED_DATA, a shared memory structure mapped to all processes and located at 0x7FFE0000 in usermode. This is equivalent to what the IsProcessorFeaturePresent API does.

    KUSER_SHARED_DATA is duplicated at 0xFFDF0000 in kernelmode. Fortunately ntddk.h gives us a handy macro with which to work with it. The snippet below will give us (by inference) the size of nt!_MMPTE, from which we can derive the size of a large page and the size of the MiSystemVaType array.

    PHP Code:
    #define KI_USER_SHARED_DATA         0xffdf0000
    #define SharedUserData  ((KUSER_SHARED_DATA * const)   KI_USER_SHARED_DATA)

    // Determine if PAE is enabled from  KI_USER_SHARED_DATA.ProcessorFeatures

    DbgPrint ("PAE enabled\n");

    sizeof_MMPTE 8;

    } else {

    DbgPrint ("PAE not enabled\n");

    sizeof_MMPTE 4;

    In the registry the PAE status can be read from
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PhysicalAddressExtension

    Here is a summary of the differences between PAE and non-PAE mode which are relevant to our code:

    (PAGE_SIZE = 0x1000)
    PAE enabled:
        nt kernel version:
             ntkrnlpa.exe: 1 CPU, PAE
             ntkpamp.exe:  n CPU, SMP, PAE
        sizeof_MMPTE = 8
        LARGE_PAGE_SIZE = PAGE_SIZE * PAGE_SIZE / sizeof_MMPTE = 0x200000  (2MB)
        sizeof MiSystemVaType array = (0xFFFFFFFF+1 -  (ULONG)MmSystemRangeStart) / LARGE_PAGE_SIZE = 0x400
        0x400 * 0x200000 = 0x80000000 = (0x80000000 / 1024 /1024 /1024) =  2GB
    PAE disabled:
        nt kernel version:
            ntoskrnl.exe: 1 CPU
            ntkrnlmp.exe: n CPU, SMP
        sizeof_MMPTE = 4
        LARGE_PAGE_SIZE = PAGE_SIZE * PAGE_SIZE / sizeof_MMPTE = 0x400000  (4MB)
        sizeof MiSystemVaType array = (0xFFFFFFFF+1 -  (ULONG)MmSystemRangeStart) / LARGE_PAGE_SIZE = 0x200
        0x200 * 0x400000 = 0x80000000 = 2GB
    PAE is enabled by default in Windows 7, if you wish to test the included code in non-PAE mode use BCDEdit as follows:

    If DEP is enabled, PAE cannot be disabled. Use the following BCDEdit /set commands to disable both DEP and PAE:

    bcdedit /set nx AlwaysOff
    bcdedit /set pae ForceDisable

    To restore:

    bcdedit /set nx Optout (or one of [Optin |OptOut | AlwaysOn])
    bcdedit /set pae ForceEnable


    Finding the unexported pointer nt!_MiSystemVaType:

    We need to programmatically find the offset to nt!_MiSystemVaType. Since this is an unexported pointer we'll have to parse a known kernel function which makes use of the variable. Uh Oh. Production code need not apply . Oh well, this is an RCE forum, right? At least that's better than using a hard-coded value, not as good as using symbols.

    Rather than using a classic byte-pattern search that is often used to find unexported variables, I made use of a clever idea Zairon mentioned to me, that of looking for cross references between code and data sections in order to pick up instances of data variable usage. In essence, derive XREFS similar to IDA.

    I really like Zairon's idea of using XREF analysis over a byte-pattern search method because it's simple, highly adaptable, and is less susceptible to changing byte patterns between different OS kernel versions.

    The function I chose to parse for the offset of MiSystemVaType was the exported MmIsNonPagedSystemAddressValid procedure. The simple algorithm logic I used was: "Scan for the first data XREF to the section called '.data''"

    See the source code for the specific algorithm I implemented, plus a few suggestions for creating a more rigorous algorithm if desired, such as using a length disassembly engine (LDE) to avoid the possibility a false XREF could occur across instructions.

    The simple logic above should be valid for all current 32-bit nt* kernel versions in Windows 7 / Vista / Server 2008. Even better, MmIsNonPagedSystemAddressValid has been deemed to be obsolete and is exported to support existing drivers only, so it's more unlikely to change anytime soon.

    _MmIsNonPagedSystemAddressValid@4 proc
    8B FF                             mov     edi, edi
    55                                push    ebp
    8B EC                             mov     ebp, esp
    53                                push    ebx
    56                                push    esi
    8B 35 18 57 97 82                 mov     esi, ds:_MmSystemRangeStart //  xref to ALMOSTRO
    57                                push    edi
    8B 7D 08                          mov     edi, [ebp+VirtualAddress]
    BB F8 3F 00 00                    mov     ebx, 3FF8h
    3B FE                             cmp     edi, esi
    72 25                             jb      short loc_828F17A8
    8B C6                             mov     eax, esi
    C1 E8 12                          shr     eax, 12h
    8B CF                             mov     ecx, edi
    C1 E9 12                          shr     ecx, 12h
    23 C3                             and     eax, ebx
    23 CB                             and     ecx, ebx
    2B C8                             sub     ecx, eax
    C1 F9 03                          sar     ecx, 3
    8A 81 60 51 95 82                 mov     al, _MiSystemVaType[ecx]  // xref to .data

    Making sense of MiSystemVaType:

    Now that we've got the pointer to nt!_MiSystemVaType, what do we do with it? The first obvious thing is just to list everything out.

    Let's take a look at the first 0x10 bytes of the MiSystemVaType array. Each byte maps a logical address block of LARGE_PAGE_SIZE, beginning at ...
    Attached Thumbnails Attached Files