Results 1 to 5 of 5

Thread: Using nt!_MiSystemVaType to navigate dynamic kernel address space in Windows7

  1. #1
    Teach, Not Flame Kayaker's Avatar
    Join Date
    Oct 2000
    Blog Entries

    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 MmSystemRangeStart.

    kd> x nt!MiSystemVaType
    82955160 nt!MiSystemVaType = <no type information>
    kd> db 82955160
    82955160 03 03 09 09 03 03 03 03-03 03 03 03 03 03 03 06   ................
    We see that the first byte is 0x03, which is the nt!_MI_SYSTEM_VA_TYPE enum type MiVaBootLoaded. It describes the logical address block from 0x80000000 - 0x801fffff (PAE enabled, Large Page size = 2MB). The second byte is also 0x03 and maps 0x80200000 - 0x803fffff. The 3rd and 4th bytes are MiVaSystemPtes, the next 11 bytes are again MiVaBootLoaded, and so forth.

    Our program output will list that as follows:

    ### Start    End        Length (  MB) Count Type    
    001 80000000 803fffff   400000 (   4)    2 BootLoaded
    002 80400000 807fffff   400000 (   4)    2 SystemPtes
    003 80800000 81dfffff  1600000 (  22)   11 BootLoaded
    004 81e00000 825fffff   800000 (   8)    4 PagedPool
    At this point I'll mention a very nice WinDbg extension cmkd!kvas which uses the known symbolic offset value of nt!_MiSystemVaType to produce the same output.

    CodeMachine Debugger Extension DLL (CMKD.dll)

    Unfortunately, there's a bug in the code and the Length and MB columns give incorrect values for every entry except the first one. It's just a small implementation bug, a counter used incorrectly. Here is the same output as above, from cmkd. It seems apparent to me that there's an extra 200000 bytes (large page size with PAE enabled) added to the Length calculation from the second entry onwards.

    kd> .load cmkd
    kd> !cmkd.kvas
    ### Start    End        Length (  MB)    Count Type
    000 80000000 803fffff   400000 (   4)        2 BootLoaded
    001 80400000 807fffff   600000 (   6)        2 SystemPtes
    002 80800000 81dfffff  1800000 (  24)       11 BootLoaded
    003 81e00000 825fffff   a00000 (  10)        4 PagedPool
    It's a nice WinDbg extension nonetheless with other useful commands, just take note of this error if using it.

    For consistency, comparison, and in recognition of the cmkd author, I have used the same logical output format and features in my code.

    Enumerating Driver Modules:

    Another feature I added to the code, just to see what else could be done, was an option to scan all memory blocks of type MiVaBootLoaded and MiVaDriverImages for MZ headers in order to identify the modules contained within them. To name the modules I matched the base address with the results from ZwQuerySystemInformation (SystemInformationClass). Any modules not matching might be considered as hidden drivers.

    For interest, here are the modules classified as MiVaBootLoaded:

    ### Base     Size     ImageName
    001 80bc1000 00008000 kdcom.dll
    002 82817000 00037000 halmacpi.dll
    003 8284e000 00410000 ntkrnlpa.exe

    Pool Searching:

    The following section is not directly related to the code and is probably of limited interest. It mainly details the differences in some kernel global pool variables between Windows 7 and XP.

    For background reference on some reasons why we'd be interested in pool searching, see

    GREPEXEC: Grepping Executive Objects from Pool Memory

    On the one hand we have what seems like a very nice mechanism in MiSystemVaType for searching through the various memory allocation types. Want to search the Paged Pool? Just parse the MiSystemVaType array for large pages presently tagged for that allocation type and search through them for valid pool headers.

    On the other hand, that's not the way Windows seems to view it.

    In the following article, Mark Russinovich describes how in 32-bit Windows Vista and later with dynamic kernel address space, the paged pool limit is simply set to 2GB, and will run out either when the system address space is full or the system commit limit is reached. Similarly, the nonpaged pool limit is set at ~75% of RAM or 2GB, whichever is smaller.

    Pushing the Limits of Windows: Paged and Nonpaged Pool

    Evidence for the above can be seen in the WinDbg !poolfind command, used to find all instances of a specific pool tag in either nonpaged or paged memory pools (as used by ExAllocatePoolWithTag).

    In Windows 7, !poolfind sets by default the pool limits for each [PoolType] flag it supports to almost the full upper 2GB address range, 80000000 - ffc00000 (the address range between 0xffc00000-0xffffffff is reserved for HAL, i.e. the last 2 bytes of the MiSystemVaType array are always enum type MiVaHal).

    Here is an example when searching the Paged Pool for the tag 'Cbrb'. This tag is used for allocations by the system callbacks PspCreateProcessNotifyRoutine, PspLoadImageNotifyRoutine, PspCreateThreadNotifyRoutine, and in XP, CmRegisterCallback.

    Windows 7:
    kd> !poolfind Cbrb 1
    Scanning large pool allocation table for Tag: Cbrb (b5800000 : b5c00000)
    Searching Paged pool (80000000 : ffc00000) for Tag: Cbrb
    In XP the same command will search between the system values of MmPagedPoolStart (0xe1000000) and MmPagedPoolEnd (0xf0ffffff).

    kd> !poolfind Cbrb 1
    Scanning large pool allocation table for Tag: Cbrb (823ec000 : 823f8000)
    Searching Paged pool (e1000000 : f1000000) for Tag: Cbrb
    In Windows 7 many of the global variables such as nt!MmPagedPoolStart, nt!MmPagedPoolEnd and related NonPagedPool variables mentioned in the GREPEXEC article are no longer valid. We can see this by parsing the (PKDDEBUGGER_DATA64)KdDebuggerDataBlock structure, which is accessible through the Kernel Processor Control Region (KPCR). See the following articles for background on this well known "KPCR trick".

    Finding some non-exported kernel variables in Windows XP

    Getting Kernel Variables from KdVersionBlock, Part 2

    Finding Kernel Global Variables in Windows

    Finding Object Roots in Vista (KPCR)

    I made up a small driver to retrieve the offset of KdDebuggerDataBlock and loaded up the driver symbols in LiveKd so the KDDEBUGGER_DATA64 structure would be defined in order to get the following output.

    You can see that several of the fields that in XP would normally be pointers to global pool variables are now zeroed out, having been made redundant in Window 7/Vista by Dynamic Kernel Address Space and the MiSystemVaType mechanism.

    kd> dt -b k_kpcr!dummy 82976be8 //  (PKDDEBUGGER_DATA64)KdDebuggerDataBlock
       +0x0a8 MmSystemCacheStart : 0
       +0x0b0 MmSystemCacheEnd : 0
       +0x0c8 MmSystemPtesStart : 0
       +0x0d0 MmSystemPtesEnd  : 0
       +0x108 MmNonPagedSystemStart : 0
       +0x110 MmNonPagedPoolStart : 0x829b612c => 0x8b971000 // not  relevant
       +0x118 MmNonPagedPoolEnd : 0
       +0x120 MmPagedPoolStart : 0
       +0x128 MmPagedPoolEnd   : 0x829b6098 => 0
       +0x278 MmSessionBase    : 0
       +0x280 MmSessionSize    : 0
    Another place we can see the use of the maximized pool limits, which again differs from XP, is in the per-session nt!_MM_SESSION_SPACE structure. Session pool memory (used by win32k) is used for session space allocations and is unique to each user session. While non-paged session memory use the global non-paged pool descriptor(s), paged session pool memory has its own pool descriptor defined in _MM SESSION SPACE.

    Kernel Pool Exploitation on Windows 7

    Parsing MM_SESSION_SPACE we see that the full kernel address space is defined as paged session pool memory:

    kd> !sprocess
    Dumping Session 1
    _MM_SESSION_SPACE 9007a000
    kd> dt nt!_MM_SESSION_SPACE 9007a000
       +0x02c PagedPoolStart   : 0x80000000
       +0x030 PagedPoolEnd     : 0xffbfffff


    So far we've seen that Windows 7 defines the same extended upper and lower pool limits for at least paged, nonpaged and session memory. WinDbg !poolfind assumes the same thing and unfortunately it significantly slows down pool-specific searches (try timing the difference between XP and Windows 7 for the same search). Chances are however that there's a very good reason for doing it that way that is not immediately apparent.

    From a reversers perspective however, we could use MiSystemVaType to narrow down the search limits rather than enumerating the entire system address space. For example, using the code from this project we can find that MiVaNonPagedPool and MiVaSessionSpace type memory is isolated within the following regions:

    ### Start    End        Length (  MB) Count Type    
    001 8b600000 8bbfffff   600000 (   6)    3 NonPagedPool
    002 8c000000 8c1fffff   200000 (   2)    1 NonPagedPool
    003 8c400000 8d9fffff  1600000 (  22)   11 NonPagedPool
    004 b5800000 b5bfffff   400000 (   4)    2 NonPagedPool
    ### Start    End        Length (  MB) Count Type    
    001 fda00000 fdbfffff   200000 (   2)    1 SessionSpace
    002 fde00000 ffbfffff  1e00000 (  30)   15 SessionSpace
    Ultimately, it seems like any algorithm one might develop for pool searching would come down to using nt!_MiSystemVaType for the efficiency of being able to identify pool-specific regions, or searching the entire system address space, a much slower proposition, for the simplicity of not having to write those extra procedures.

    A Visual Studio project with complete source is included, driver and application binaries are under /bin/i386.


  2. #2
    |< x != '+' BanMe's Avatar
    Join Date
    Oct 2008
    Farmington NH
    Blog Entries
    Very good paper..

    The part about xrefs really got my attention..Thanks for sharing. Zairon and Kayaker.
    No hate for the lost children;
    more love for the paths we walk,
    'words' shatter the truth we seek.
    from the heart and mind of Me
    me, to you.. down and across

    No more words from me, to you...
    Hate and love shatter the heart and Mind of Me.
    For the Lost Children;For the paths we walk; the real truth we seek!

  3. #3
    Nice one Kayaker Always enjoying reading your articles.

  4. #4
    Super Moderator
    Join Date
    Dec 2004
    Blog Entries
    have you ever tried tracing softice 9X using two vms or with windbg and two pc

    you might have seen my latest blog that uses 2 vms on a single physical host with vmware

    it seems installing sice 4.05 9x on one os interferes with loading of kd (Daemon tools sptd alike ?)

    sice is activated through autoexec.bat
    wdeb98 is activated by

    remember win98 ?

  5. #5
    Teach, Not Flame Kayaker's Avatar
    Join Date
    Oct 2000
    Blog Entries
    Interesting trick Blabberer. Retro
    No, I've never done anything like that.

    KD and Sice9x don't work together? Probably some other reason for it though, Daemon tools SPTD was a deliberate PspLoadImageNotifyRoutine callback which overwrote the DriverEntry of ntice.sys, giving it no chance to load.

Similar Threads

  1. Replies: 0
    Last Post: February 23rd, 2009, 14:17
  2. # dynamic TLS callbacks instead of SEH
    By nezumi-lab in forum Blogs Forum
    Replies: 1
    Last Post: January 12th, 2009, 04:39
  3. Replies: 0
    Last Post: April 23rd, 2008, 10:01
  4. Replies: 4
    Last Post: May 26th, 2006, 13:08
  5. SoftIce Server - dynamic stopping & restarting
    By Aimless in forum Tools of Our Trade (TOT) Messageboard
    Replies: 0
    Last Post: January 17th, 2003, 04:29


Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts