Kayaker
January 30th, 2011, 17:21
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
http://www.nynaeve.net/?p=261
Inside the Windows Vista Kernel: Part 2
http://technet.microsoft.com/en-us/magazine/2007.03.vistakernel.aspx
Windows® Internals, Fifth Edition
9.5.7 Dynamic System Virtual Address Space Management
http://www.microsoft.com/learning/en/us/book.aspx?ID=12069&locale=en-us
MiSystemVaType:
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:
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
kd> dt nt!_MI_SYSTEM_VA_TYPE
   <font color=blue>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</font>
</div></pre></div>
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:
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
kd> dt -v nt!_MMPTE
struct _MMPTE, 1 elements, 0x8 bytes
</div></pre></div>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  ("http://msdn.microsoft.com/en-us/library/ms724482%28v=vs.85%29.aspx")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.
#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
if(SharedUserData->ProcessorFeatures[PF_PAE_ENABLED])
{
    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:
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
(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
    
</div></pre></div>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
http://msdn.microsoft.com/en-us/library/aa366796(v=vs.85).aspx ("http://msdn.microsoft.com/en-us/library/aa366796%28v=vs.85%29.aspx")
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.
. 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  ("http://zairon.wordpress.com/")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  ("http://msdn.microsoft.com/en-us/library/ff554588%28v=vs.85%29.aspx")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.
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
_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 <font color="#0000ff">60 51 95 82 </font>                mov     al, <font color=blue>_MiSystemVaType</font>[ecx]  // <font color=blue>xref to .data</font>
</div></pre></div>
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.
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">kd> x nt!MiSystemVaType
82955160 nt!MiSystemVaType = <no type information>
kd> db 82955160
82955160 <font color=blue>03 03</font> 09 09 03 03 03 03-03 03 03 03 03 03 03 06   ................
82955170</div></pre></div>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:
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">### Start    End        Length (  MB) Count Type    
001 80000000 803fffff   400000 (   4)    <font color=blue>2 BootLoaded</font>
002 80400000 807fffff   400000 (   4)    2 SystemPtes
003 80800000 81dfffff  1600000 (  22)   11 BootLoaded
004 81e00000 825fffff   800000 (   8)    4 PagedPool
...</div></pre></div>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)
http://www.codemachine.com/tool_cmkd.html
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.
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">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</div></pre></div>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 ("http://msdn.microsoft.com/en-us/library/ms725506%28v=vs.85%29.aspx") (SystemInformationClass).  Any  modules not matching might be considered as hidden drivers.
For interest, here are the modules classified as MiVaBootLoaded:
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">### Base     Size     ImageName
001 80bc1000 00008000 kdcom.dll
002 82817000 00037000 halmacpi.dll
003 8284e000 00410000 ntkrnlpa.exe
</div></pre></div>
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
http://uninformed.org/?v=4&a=2&t=txt
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
http://blogs.technet.com/b/markrussinovich/archive/2009/03/26/3211216.aspx
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.
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
Windows 7:
kd> !poolfind Cbrb 1
Scanning large pool allocation table for Tag: Cbrb (b5800000 : b5c00000)
Searching Paged pool (80000000 : ffc00000) for Tag: Cbrb
</div></pre></div>In XP the same command will search between the system values of MmPagedPoolStart  (0xe1000000) and MmPagedPoolEnd (0xf0ffffff).  
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
XP:
kd> !poolfind Cbrb 1
Scanning large pool allocation table for Tag: Cbrb (823ec000 : 823f8000)
Searching Paged pool (e1000000 : f1000000) for Tag: Cbrb
</div></pre></div>
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
http://www.rootkit.com/vault/Opc0de/GetVarXP.pdf
Getting Kernel Variables from KdVersionBlock, Part 2
http://www.rootkit.com/newsread.php?newsid=153
Finding Kernel Global Variables in Windows
http://moyix.blogspot.com/2008/04/finding-kernel-global-variables-in.html
Finding Object Roots in Vista (KPCR)
http://blog.schatzforensic.com.au/2010/07/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.
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
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
</div></pre></div>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
http://www.mista.nu/research/
Parsing MM_SESSION_SPACE we see that the full kernel address space is  defined as paged session pool memory:
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
kd> !sprocess
Dumping Session 1
_MM_SESSION_SPACE 9007a000
kd> dt nt!_MM_SESSION_SPACE 9007a000
   +0x02c PagedPoolStart   : 0x80000000
   +0x030 PagedPoolEnd     : 0xffbfffff
</div></pre></div>
Conclusion:
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:
<div style="margin:20px; margin-top:5px"><div class="smallfont" style="margin-bottom:2px">Code:</div><pre class="alt2" style="margin:0px; padding:6px; border:solid 1px; width:90%; height:80px; overflow:auto"><div dir="ltr" style="text-align:left;">
### 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
</div></pre></div>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.
Kayaker
http://www.woodmann.com/forum/attachment.php?attachmentid=2416SystemVAs.zip (138.8 KB)


 Always enjoying reading your articles.
 Always enjoying reading your articles.