PDA

View Full Version : Using nt!_MiSystemVaType to navigate dynamic kernel address space in Windows7


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&gt; 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&gt; 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  (&quot;http://msdn.microsoft.com/en-us/library/ms724482%28v=vs.85%29.aspx&quot;)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-&gt;ProcessorFeatures[PF_PAE_ENABLED])

{

    DbgPrint (&quot;PAE enabled\n&quot;



    sizeof_MMPTE = 8;



} else {



    DbgPrint (&quot;PAE not enabled\n&quot;



    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 (&quot;http://msdn.microsoft.com/en-us/library/aa366796%28v=vs.85%29.aspx&quot;)





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  (&quot;http://zairon.wordpress.com/&quot;)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  (&quot;http://msdn.microsoft.com/en-us/library/ff554588%28v=vs.85%29.aspx&quot;)procedure. The simple algorithm  logic I used was: &quot;Scan for the first data XREF to the section called  '.data''&quot;



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=&quot;#0000ff&quot;>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&gt; x nt!MiSystemVaType



82955160 nt!MiSystemVaType = &lt;no type information&gt;



kd&gt; 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&gt; .load cmkd

kd&gt; !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 (&quot;http://msdn.microsoft.com/en-us/library/ms725506%28v=vs.85%29.aspx&quot;) (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&amp;a=2&amp;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,  PspLoadImageNotifyRoutinePspCreateThreadNotifyRoutine,  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&gt; !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&gt; !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 &quot;KPCR trick&quot;.



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&gt; 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 =&gt; 0x8b971000 // not  relevant

   +0x118 MmNonPagedPoolEnd : 0

   +0x120 MmPagedPoolStart : 0

   +0x128 MmPagedPoolEnd   : 0x829b6098 =&gt; 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&gt; !sprocess

Dumping Session 1



_MM_SESSION_SPACE 9007a000



kd&gt; 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)


BanMe
January 30th, 2011, 21:15
Very good paper..

The part about xrefs really got my attention..Thanks for sharing. Zairon and Kayaker.

deroko
January 31st, 2011, 07:16
Nice one Kayaker Always enjoying reading your articles.

blabberer
December 1st, 2011, 16:07
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 system.in

remember win98 ?

Kayaker
December 1st, 2011, 21:38
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.