A Sample Memory Dump Utility
Now that you have worked through the complex and possibly confusing IOCTL function handler code of the memory spy device driver, you probably want to see these functions in action. Therefore, I have created a sample console-mode utility named "SBS Windows 2000 Memory Spy" that loads the spy driver and calls various IOCTL functions, depending on the parameters passed in on the command line. This application resides in the executable file w2k_mem.exe, and its source code is included on the CD accompanying this book, in the directory \src\w2k_mem.
Command Line Format
You can run the memory spy utility from the CD by invoking d:\bin\w2k_mem.exe, where d: should be replaced by the drive letter of your CD-ROM drive. If w2k_mem.exe is started without arguments, the lengthy command info screen shown in Example 4-1 is displayed. The basic command philosophy of w2k_mem is that a command consists of one or more data requests, each providing at least a linear base address where the memory dump should start. Optionally, the memory block size can be specified as wellotherwise, the default size 256 is used. The memory size must be prefixed by the "#" character. Several option switches may be added that modify the default behavior of the command. An option consists of a single-character option ID and a "+" or "" prefix that determines whether the option is switched on or off. By default, all options are turned off.
Example 4-1. Help Screen of the Memory Spy Utility
// w2k_mem.exe // SBS Windows 2000 Memory Spy V1.00 // 08-27-2000 Sven B. Schreiber // sbs@orgon.com Usage: w2k_mem { { [+option|-option] [/<path>] } [#[[0]x]<size>] [[0]x]<base> } <path> specifies a module to be loaded into memory. Use the +x/-x switch to enable/disable its startup code. If <size> is missing, the default size is 256 bytes. Display address options (mutually exclusive): +z -z zero-based display on / OFF +r -r physical RAM addresses on / OFF Display mode options (mutually exclusive): +w -w WORD data formatting on / OFF +d -d DWORD data formatting on / OFF +q -q QWORD data formatting on / OFF Addressing options (mutually exclusive): +t -t TEB-relative addressing on / OFF +f -f FS-relative addressing on / OFF +u -u user-mode FS:[<base>] on / OFF +k -k kernel-mode FS:[<base>] on / OFF +h -h handle/object resolution on / OFF +a -a add bias to last base on / OFF +s -s sub bias from last base on / OFF +p -p pointer from last block on / OFF System status options (cumulative): +o -o display OS information on / OFF +c -c display CPU information on / OFF +g -g display GDT information on / OFF +i -i display IDT information on / OFF +b -b display contiguous blocks on / OFF Other options (cumulative): +x -x execute DLL startup code on / OFF Example: The following command displays the first 64 bytes of the current Process Environment Block (PEB) in zero-based DWORD format, assuming that a pointer to the PEB is located at offset 0x30 inside the current Thread Environment Block (TEB): w2k_mem +t #0 0 +pzd #64 0x30 Note: Specifying #0 after +t causes the TEB to be addressed without displaying its contents.
A data request is executed for each command line token that cannot be identified as an option, a data block size specification, a path, or any other command modifier. Each plain number on the command line is assumed to specify a linear address and triggers a hex dump, starting at this address. Numbers are interpreted as decimal by default or hexadecimal if prefixed by "0x" or simply "x."
Complex command line option models like the one employed by w2k_mem.exe are much easier to grasp if some simple examples are provided. Here is a short compilation:
w2k_mem 0x80400000 displays the first 256 bytes of memory at linear address 0x80400000, yielding something that should look similar to Example 4-2. By the way, this is the DOS header of the ntoskrnl.exe module (note the "MZ" ID at the beginning).
w2k_mem #0x40 0x80400000 displays the same data block, but stops after 64 bytes, as demanded by the block size specification #0x40.
w2k_mem +d #0x40 0x80400000 is another variant, this time packing the bytes into 32-bit DWORD chunks because of the +d option. This option remains in effect until reset by d or overridden by a competing option such as +w or +q.
w2k_mem +wz #0x40 0x10000 +d z 0x20000 contains two data requests. First, the linear address range 0x10000 to 0x1003F is shown in 16-bit WORD format, followed by the range 0x20000 to 0x2003F in DWORD format (Example 4-3). The first request also includes the +z switch, which forces the numbers in the "Address" column to start at zero. In the second request, the zero-based display mode is turned off by adding a z switch.
w2k_mem +rd #4096 0xC0300000 displays the system's page-directory at address 0xC0300000 in DWORD format. The +r option enables the display of physical RAM addresses in the "Address" column instead of linear ones.
By now, you should have a basic understanding of how the command line format works. In the following subsections, some of the more exotic options and features are discussed in more detail. Most of them alter the interpretation of the address parameter they precede. In default mode, the specified address is a linear base address where the memory dump starts. The options +t, +f, +u, +k, +h, +a, +s, and +p change this default interpretation in various ways.
Example 4-2. A Sample Data Request
E:\>w2k_mem 0x80400000 // w2k_mem.exe // SBS Windows 2000 Memory Spy V1.00 // 08-27-2000 Sven B. Schreiber // sbs@orgon.com Loading "SBS Windows 2000 Spy Device" (w2k_spy) ... Driver: "D:\Program Files\DevStudio\MyProjects\w2k_mem\Release\w2k_spy.sys" Opening "\\.\w2k_spy" ... SBS Windows 2000 Spy Device V1.00 ready 80400000..804000FF: 256 valid bytes Address | 00 01 02 03-04 05 06 07 : 08 09 0A 0B-0C 0D 0E 0F | 0123456789ABCDEF ---------|-------------------------:-------------------------|----------------- 80400000 | 4D 5A 90 00-03 00 00 00 : 04 00 00 00-FF FF 00 00 | MZ.........ÿÿ.. 80400010 | B8 00 00 00-00 00 00 00 : 40 00 00 00-00 00 00 00 | ¸.......@....... 80400020 | 00 00 00 00-00 00 00 00 : 00 00 00 00-00 00 00 00 | ................ 80400030 | 00 00 00 00-00 00 00 00 : 00 00 00 00-C8 00 00 00 | ............È... 80400040 | 0E 1F BA 0E-00 B4 09 CD : 21 B8 01 4C-CD 21 54 68 | ..º..´.Í!¸.LÍ!Th 80400050 | 69 73 20 70-72 6F 67 72 : 61 6D 20 63-61 6E 6E 6F | is program canno 80400060 | 74 20 62 65-20 72 75 6E : 20 69 6E 20-44 4F 53 20 | t be run in DOS 80400070 | 6D 6F 64 65-2E 0D 0D 0A : 24 00 00 00-00 00 00 00 | mode....$....... 80400080 | 50 7A C4 CE-14 1B AA 9D : 14 1B AA 9D-14 1B AA 9D | PzÄÎ..ª..ª..ª 80400090 | 14 1B AB 9D-53 1B AA 9D : 18 3B A4 9D-5B 1B AA 9D | ..«S.ª.;[.ª 804000A0 | 42 13 AC 9D-15 1B AA 9D : 14 1B AA 9D-1A 19 AA 9D | B.¬..ª..ª..ª 804000B0 | 4D 38 B9 9D-12 1B AA 9D : 52 69 63 68-14 1B AA 9D | M81..ªRich..ª 804000C0 | 00 00 00 00-00 00 00 00 : 50 45 00 00-4C 01 13 00 | ........PE..L... 804000D0 | 17 9B 4D 38-00 00 00 00 : 00 00 00 00-E0 00 0E 03 | .?M8........à... 804000E0 | 0B 01 05 0C-C0 2D 14 00 : 80 D6 04 00-00 00 00 00 | ....À-..?Ö...... 804000F0 | 20 D1 00 00-C0 04 00 00 : 80 73 06 00-00 00 40 00 | Ñ..À...?s....@. 256 bytes requested 256 bytes received Closing the spy device ...
Example 4-3. Displaying Data in Special Formats
E:\>w2k_mem +wz #0x40 0x10000 +d -z 0x20000 // w2k_mem.exe // SBS Windows 2000 Memory Spy V1.00 // 08-27-2000 Sven B. Schreiber // sbs@orgon.com Loading "SBS Windows 2000 Spy Device" (w2k_spy) ...Driver: "D:\Program Files\DevStudio\MyProjects\w2k_mem\Release\ w2k_spy.sys" Opening "\\.\w2k_spy" ... SBS Windows 2000 Spy Device V1.00 ready 00010000..0001003F: 64 valid bytes Address | 0000 0002-0004 0006 : 0008 000A-000C 000E | 00 02 04 06 08 0A 0C 0E ---------|---------------------:---------------------|------------------------ 00000000 | 003D 0044-003A 003D : 0044 003A-005C 0050 | .= .D .: .= .D .: .\ .P 00000010 | 0072 006F-0067 0072 : 0061 006D-0020 0046 | .r .o .g .r .a .m . .F 00000020 | 0069 006C-0065 0073 : 005C 0044-0065 0076 | .i .l .e .s .\ .D .e .v 00000030 | 0053 0074-0075 0064 : 0069 006F-005C 004D | .S .t .u .d .i .o .\ .M 00020000..0002003F: 64 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- 00020000 | 00001000 - 00000880 : 00000001 - 00000000 | .... ...? .... .... 00020010 | 02B20001 - 00000000 : 00000003 - 00000007 | .2.. .... .... .... 00020020 | 0000000B - 0208006C : 00020290 - 00000018 | .... ...l ... .... 00020030 | 02A0029E - 00020498 : 00840082 - 00020738 | . .? ...? .?.? ...8 128 bytes requested 128 bytes received Closing the spy device ...
TEB-Relative Addressing
Each thread in a process has its own Thread Environment Block (TEB) where the system keeps frequently used thread-specific data. In user-mode, the TEB of the current thread is located in a separate 4-KB segment accessible via the processor's FS register. In kernel-mode, FS points to a different segment, as will be explained below. All TEBs of a process are stacked up in linear memory at linear address 0x7FFDE000, expanding down in 4-KB steps as needed. That is, the TEB of the second thread is found at address 0x7FFDD000, the TEB of the third thread at 0x7FFDC000, and so on. The contents of the TEBs and the Process Environment Block (PEB) address 0x7FFDF000 will be discussed in more detail in Chapter 7 (see Listings 7-18 and 7-19). Here it should suffice to take note that TEBs exist and that they are addressed by the FS register.
If the +t switch precedes an address on the command line, w2k_mem.exe adds the base address of the FS segment to it, effectively applying a bias of 0x7FFDE000 bytes. Example 4-4 shows the output of the command w2k_mem +dt #0x38 0 on my system. This time I have omitted the banner and status messages issued by w2k_mem.exe. The omissions are marked by [...].
Example 4-4. Displaying the first Thread Environment Block(TEB)
E:\>w2k_mem +dt #0x38 0 [...] 7FFDE000..7FFDE037: 56 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- 7FFDE000 | 0012FA58 - 00130000 : 0012E000 - 00000000 | ..úX .... ..à. .... 7FFDE010 | 00001E00 - 00000000 : 7FFDE000 - 00000000 | .... .... .´yà. .... 7FFDE020 | 000002C0 - 000002C8 : 00000000 - 00000000 | ...À ...È .... .... 7FFDE030 | 7FFDF000 - 00000000 : - | .´yô. .... [...]
FS-Relative Addressing
I have already mentioned that the FS refers to different segments in user- and kernel-mode. Whereas the +t switch selects the user-mode FS address as the reference point, the +f switch uses the FS base address that is in effect in kernel-mode. Of course, a Win32 application has no way to get at this value, so once again the spy device is required. w2k_mem.exe calls the IOCTL function SPY_IO_CPU_INFO, introduced in the previous section, to read CPU status information that includes the kernel-mode values of all segment registers. From there, everything goes on just the same as with the +t switch.
The kernel-mode FS points to another thread-specific structure frequently accessed by the Windows 2000 kernel, named the Kernel's Processor Control Region (KPCR). This structure has already been mentioned in the course of the discussion of the IOCTL function SPY_IO_OS_INFO and will be revisited in Chapter 7 (see Listing 7-16). Again, suffice it to note for now that this structure exists at linear address 0xFFDFF000, and that the +f switch gives easy access to it. In Example 4-5, I have issued the command w2k_mem +df #0x54 0 to demonstrate that the +f switch in fact applies a bias of 0xFFDFF000 bytes to the specified memory address.
Example 4-5. Displaying the Kernel's Processor Control Region (KPCR)
E:\>w2k_mem +df #0x54 0 [...] FFDFF000..FFDFF053: 84 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- FFDFF000 | BECD9CF0 - BECD9DF0 : BECD6000 - 00000000 | 3/4Í?Ô 3/4ÍÕ 3/4Í´. .... FFDFF010 | 00000000 - 00000000 : 7FFDE000 - FFDFF000 | .... .... .´yà. ÿßÕ. FFDFF020 | FFDFF120 - 00000000 : 00000000 - 00000000 | ÿßñ .... .... .... FFDFF030 | FFFF20C0 - 00000000 : 80036400 - 80036000 | ÿÿ À .... ?.d. ?.´. FFDFF040 | 80244000 - 00010001 : 00000001 - 000000C9 | ?$@. .... .... ...É FFDFF050 | 00000000 - : - | .... [...]
FS:[<base>] Addressing
When examining Windows 2000 kernel code, you will frequently come across instructions such as MOV EAX, and FS:[18h]. These instructions retrieve member values of the TEB, KPCR, or other structures contained in the FS segment. Many of them are pointers to other internal structures. The command line switches +u and +k allow you to follow this indirection with ease. +u retrieves a pointer from the user-mode FS segment; +k does the same in kernel-mode. For example, the command w2k_mem +du #0x1E8 0x30 (see Example 4-6) dumps 488 bytes of the memory block addressed by FS:[30h] in user-mode, which happens to be a pointer to the Process Environment Block (PEB) of w2k_mem.exe. The command w2k_mem +dk #0x1C 0x20 (see Example 4-7) displays the first 28 bytes of memory pointed to by FS:[20h] in kernel-mode, which is a pointer to the Kernel's Processor Control Block (KPRCB), briefly mentioned earlier in the discussion of the IOCTL function SPY_IO_OS_INFO and also discussed in Chapter 7 (see Listing 7-15). Don't worry if you don't know what a PEB or KPRCB isyou will know it after having read this book.
Example 4-6. Displaying the Process Environment Block (PEB)
E:\>w2k_mem +du #0x1E8 0x30 [...] 7FFDF000..7FFDF1E7: 488 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- 7FFDF000 | 00000000 - FFFFFFFF : 00400000 - 00131E90 | .... ÿÿÿÿ .@.. ... 7FFDF010 | 00020000 - 00000000 : 00130000 - 77FCD170 | .... .... .... wüÑp 7FFDF020 | 77F8AA4C - 77F8AA7D : 00000001 - 77E33E58 | wøªL wøª} .... wã>X 7FFDF030 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF040 | 77FCD1A8 - 0000007F : 00000000 - 7F6F0000 | wüѨ .... .... .o.. 7FFDF050 | 7F6F0000 - 7F6F0688 : 7FFB0000 - 7FFC1000 | .o.. .o.? .û.. .ü.. 7FFDF060 | 7FFD2000 - 00000001 : 00000000 - 00000000 | .´y . .... .... .... 7FFDF070 | 079B8000 - FFFFE86D : 00100000 - 00002000 | .??. ÿÿèm .... .. . 7FFDF080 | 00010000 - 00001000 : 00000003 - 00000010 | .... .... .... .... 7FFDF090 | 77FCE380 - 00410000 : 00000000 - 00000014 | wüã? .A.. .... .... 7FFDF0A0 | 77FCD348 - 00000005 : 00000000 - 00000893 | wüÓH .... .... ...? 7FFDF0B0 | 00000002 - 00000003 : 00000004 - 00000000 | .... .... .... .... 7FFDF0C0 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF0D0 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF0E0 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF0F0 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF100 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF110 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF120 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF130 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF140 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF150 | 77FCDCC0 - 00000000 : 00000000 - 00000000 | wüÜÀ .... .... .... 7FFDF160 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF170 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF180 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF190 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF1A0 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF1B0 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF1C0 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... 7FFDF1D0 | 00000000 - 00000000 : 00000000 - 00020000 | .... .... .... .... 7FFDF1E0 | 7F6F06C2 - 00000000 : - | .o. .... [...]
Example 4-7. Displaying the Kernel's Processor Control Block (KPRCB)
E:\>w2k_mem +dk #0x1C 0x20 [...] FFDFF120..FFDFF13B: 28 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- FFDFF120 | 00010001 - 86BBA820 : 00000000 - 8046BDF0 | .... ?»¨ .... ?F 1/2 ó FFDFF130 | 00020000 - 00000001 : 05010106 - | .... .... .... [...]
Handle/Object Resolution
Suppose you have an object HANDLE and want to see what the corresponding object looks like in memory. This is an almost trivial task if you use the +h switch, which simply calls the spy device's SPY_IO_HANDLE_INFO function (Listing 4-26) to look up the object body of the given handle. The world of Windows 2000 objects is an amazing topic that will be treated in depth in Chapter 7. So let's forget about it for now.
Relative Addressing
Sometimes it might be useful to display a series of memory blocks that are spaced out by the same number of bytes. This might be, for example, an array of structures, like the stack of TEBs in a multithreaded application. The +a and +s switches enable this kind of relative addressing by changing the interpretation of the specified address to an offset. The difference between these options is that +a ("add bias") yields a positive offset, whereas +s ("subtract bias") yields a negative one. Example 4-8 shows the output of the command w2k_mem +d #32 0xC0000000 +a 4096 4096 on my system. It samples the first 32 bytes of three consecutive 4-KB pages, starting at address 0xC0000000, where the system's page-tables are located. Note the +a switch near the end of the command. It causes the following "4096" tokens to be interpreted as offsets
Example 4-8. Sampling Page-Tables
E:\>w2k_mem +d #32 0xC0000000 +a 4096 4096 [...] C0000000..C000001F: 32 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- C0000000 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... C0000010 | 00000000 - 00000000 : 00000000 - 00000000 | .... .... .... .... C0001000..C000101F: 32 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- C0001000 | 037D1025 - 03324025 : 0329D025 - 04DDE025 | .}.% .2@% .)D% .Y´à% C0001010 | 06F17067 - 03297225 : 05115067 - 00000000 | .ñpg .)r% ..Pg .... C0002000..C000201F: 0 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- C0002000 | - : - | C0002010 | - : - | 96 bytes requested 64 bytes received [...]
to be added to the previous base address. The +a and +s switches remain in effect until switched off explicitly by specifying a or s or overridden by any of the other switches that change the interpretation of the address parameter.
Example 4-8 also shows what happens if an invalid linear address is passed in. Obviously, the first pair of page-tables referring to the 4-MB address ranges 0x00000000 to 0x003F0000 and 0x00400000 to 0x007F0000 were valid, and the third one was not. w2k_mem.exe reflects this fact by displaying an empty hex dump table. The program knows which address ranges are valid because the spy device's SPY_IO_MEMORY_DATA function puts this information into the resulting SPY_MEMORY_DATA structure (cf. Listing 4-25).
Indirect Addressing
One of my favorite command options is +p, because it saved a lot of typing while I was preparing this book. This option works similar to +u and +k, but doesn't use the FS segment as reference, but rather uses the previously displayed data block. This is a great feature if you want to chase down a linked list of objects, for example. Instead of displaying the first list member, reading out the address of the next member, typing a new command with this address, and so on, simply append +p to the command and a series of offsets that specify where the link to the next object is located in the previous hex dump panel.
In Example 4-9, I have used this option to walk down the list of active processes. First, I have asked the Kernel Debugger to give me the address of the internal variable PsActiveProcessHead, which is a LIST_ENTRY structure marking the beginning of the process list. A LIST_ENTRY consists of a Flink (forward link) member at offset 0 and a Blink (backward link) member at offset 4 (cf. Listing 2-7). The command w2k_mem #8 +d 0x8046a180 +p 0 0 0 0 first dumps the LIST_ENTRY of PsActiveProcessHead, and then it switches to indirect addressing on behalf of the +p switch. The four zeros tell w2k_mem.exe to extract the value at offset zero of the previous data block, which is, of course, the Flink member of each LIST_ENTRY. Note that the Blink members in Example 4-9, located at offset 4, do in fact point back to the previous LIST_ENTRY, as expected.
If enough zero-valued parameters would be appended to the command, the hex dump would eventually return to PsActiveProcessHead, which marks the beginning and the end of the process list. As explained in Chapter 2, the doubly-linked lists maintained by Windows 2000 are usually circular; that is, the Flink of the last list member points to the first one, and the Blink of the first list member points to the last one.
Example 4-9. Walking Down the Active-Process List
E:\>w2k_mem #8 +d 0x8046a180 +p 0 0 0 0 [...] 8046A180..8046A187: 8 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- 8046A180 | 8149D900 - 840D2BE0 : - | IÙ. ?.+à 8149D900..8149D907: 8 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- 8149D900 | 8131A4A0 - 8046A180 : - | 1 ?F¡? 8131A4A0..8131A4A7: 8 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- 8131A4A0 | 812FFDE0 - 8149D900 : - | /´yà IÙ. 812FFDE0..812FFDE7: 8 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- 812FFDE0 | 812FA460 - 8131A4A0 : - | /´ 1 812FA460..812FA467: 8 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- 812FA460 | 812E30C0 - 812FFDE0 : - | .0À /´yà [...]
Loading Modules on the Fly
Sometimes you might want to dump the memory image of a module, but the module is not mapped into the linear address space of the w2k_mem.exe process. This problem can be solved by loading the module explicitly using the /<path> and +x command options. Every command token prefixed by a slash character is interpreted as a module path, and w2k_mem.exe attempts to load this module from this path using the Win32 API function LoadLibraryEx(). By default, the load option DONT_RESOLVE_ DLL_REFERENCES is used, causing the module to be loaded without initializing it. For a DLL, this means that its DllMain() entry point is not called. Also, none of the dependent modules specified in the import section is loaded. However, if you specify the +x switch before the path, the module is loaded and fully initialized. Note that some modules might refuse initialization in the context of the w2k_mem.exe process. For example, kernel-mode device drivers should not be loaded with this option turned on.
Loading and displaying a module is typically a two-step operation, as shown in Example 4-10. First you should load the module without displaying any data, to find out the base address assigned to it by the system. Fortunately, load addresses are deterministic as long as no other modules are added to the process in the meantime, so the next attempt to load the module will yield the same base address. In Example 4-10, I have loaded the kernel-mode device driver nwrdr.sys, which is the Microsoft's NetWare redirector. I'm not using IPX/SPX on my machine, so this driver is not yet loaded. Obviously, LoadLibraryEx() succeeds, and the hex dumps of the reported load address 0x007A0000 preceding and following this API call prove that this memory region is initially unused but contains a DOS header afterward.
Example 4-10. Loading and Displaying a Module Image
E:\>w2k_mem /e:\winnt\system32\drivers\nwrdr.sys [...] You didn't request any data! LoadLibrary (e:\winnt\system32\drivers\nwrdr.sys) = 0x007A0000 [...] E:\>w2k_mem 0x007A0000 /e:\winnt\system32\drivers\nwrdr.sys 0x007A0000 [...] 007A0000..007A00FF: 0 valid bytes Address | 00 01 02 03-04 05 06 07 : 08 09 0A 0B-0C 0D 0E 0F | 0123456789ABCDEF ---------|-------------------------:-------------------------|----------------- 007A0000 | - : - | 007A0010 | - : - | 007A0020 | - : - | 007A0030 | - : - | 007A0040 | - : - | 007A0050 | - : - | 007A0060 | - : - | 007A0070 | - : - | 007A0080 | - : - | 007A0090 | - : - | 007A00A0 | - : - | 007A00B0 | - : - | 007A00C0 | - : - | 007A00D0 | - : - | 007A00E0 | - : - | 007A00F0 | - : - | LoadLibrary (e:\winnt\system32\drivers\nwrdr.sys) = 0x007A0000 007A0000..007A00FF: 256 valid bytes Address | 00 01 02 03-04 05 06 07 : 08 09 0A 0B-0C 0D 0E 0F | 0123456789ABCDEF ---------|-------------------------:-------------------------|----------------- 007A0000 | 4D 5A 90 00-03 00 00 00 : 04 00 00 00-FF FF 00 00 | MZ.........ÿÿ.. 007A0010 | B8 00 00 00-00 00 00 00 : 40 00 00 00-00 00 00 00 | ¸.......@....... 007A0020 | 00 00 00 00-00 00 00 00 : 00 00 00 00-00 00 00 00 | ................ 007A0030 | 00 00 00 00-00 00 00 00 : 00 00 00 00-D0 00 00 00 | ............D... 007A0040 | 0E 1F BA 0E-00 B4 09 CD : 21 B8 01 4C-CD 21 54 68 | ..º..´.Í!¸.LÍ!Th 007A0050 | 69 73 20 70-72 6F 67 72 : 61 6D 20 63-61 6E 6E 6F | is program canno 007A0060 | 74 20 62 65-20 72 75 6E : 20 69 6E 20-44 4F 53 20 | t be run in DOS 007A0070 | 6D 6F 64 65-2E 0D 0D 0A : 24 00 00 00-00 00 00 00 | mode....$....... 007A0080 | 61 14 4B C1-25 75 25 92 : 25 75 25 92-25 75 25 92 | a.KÁ%u%?%u%?%u%? 007A0090 | 29 55 2B 92-27 75 25 92 : 7C 56 36 92-22 75 25 92 | )U+?'u%?|V6?"u%? 007A00A0 | 25 75 24 92-BF 75 25 92 : 0F 7D 23 92-24 75 25 92 | %u$?¿u%?.}#?$u%? 007A00B0 | 25 75 25 92-14 75 25 92 : 52 69 63 68-25 75 25 92 | %u%?.u%?Rich%u%? 007A00C0 | 00 00 00 00-00 00 00 00 : 00 00 00 00-00 00 00 00 | ................ 007A00D0 | 50 45 00 00-4C 01 09 00 : 66 EC 08 38-00 00 00 00 | PE..L...fì.8.... 007A00E0 | 00 00 00 00-E0 00 0E 03 : 0B 01 05 0C-00 2D 02 00 | ....à........-.. 007A00F0 | 40 3A 00 00-00 00 00 00 : 3E 14 01 00-40 03 00 00 | @:......>...@... [...]
Oddly, you can even load the .exe file of another application into memory using the /<path> option. However, this module probably will be loaded to an unusual address, because its preferred load address is usually occupied by w2k_mem.exe. Moreover, you cannot get the loaded application to runthe +x switch applies to DLLs only and has no effect on other module types.
Demand-Paging in Action
In the discussion of the spy device function SPY_IO_MEMORY_DATA, I mentioned that this function is able to read the contents of memory pages that are flushed out to a pagefile. Now is the time to prove this claim. First, it is necessary to maneuver the system into a severe low-memory situation, forcing it to swap to the pagefiles anything that isn't urgently needed. My favorite method goes as follows:
Copy the Windows 2000 desktop to the clipboard by pressing the Print key.
Paste this bitmap into a graphics application.
Inflate the bitmap to an enormous size.
Now watch out what the command w2k_mem +d #16 0xC0280000 0xA0000000 0xA0001000 0xA0002000 0xC0280000 yields on the screen. You might wonder what this command is supposed to do. Well, it simply takes a snapshot of some PTEs before and after touching the pages they refer to. The four PTEs found at address 0xC0280000 are associated with the linear address range 0xA0000000 to 0xA0003FFF, which is part of the image of the kernel module win32k.sys. As Example 4-11 shows, this address range has been swapped out because of the bitmap operation I had performed just before. How do I know? Because the four DWORDs at address 0xC0280000 are even numbers, meaning that their least significant bitthe P bit of a PTEis zero, indicating a nonpresent page. The next three hex dump panels belong to the command parameters 0xA0000000, 0xA0001000, and 0xA0002000, requesting data from three of the four pages currently under examination. As it turns out, w2k_mem.exe has no problems accessing these pagesthe system simply swaps them in on demand. However, the final test is still to come: What do the four PTEs look like afterward? The answer is given by the last panel of Example 4-11: The first three PTEs have the P bit set, and the fourth still indicates "not present."
Example 4-11. Watching PTEs Change Their States
E:\>w2k_mem +d #16 0xC0280000 0xA0000000 0xA0001000 0xA0002000 0xC0280000 [...] C0280000..C028000F: 16 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- C0280000 | 056A14E0 - 056A14E2 : 056A14E4 - 056A14E6 | .j.à .j. .j.ä .j.æ A0000000..A000000F: 16 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- A0000000 | 00905A4D - 00000003 : 00000004 - 0000FFFF | .ZM .... .... ..ÿÿ A0001000..A000100F: 16 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- A0001000 | 000000A6 - FF0C75FF : 1738B415 - F8458BA0 | ...|| ÿ.uÿ .8´. øE? A0002000..A000200F: 16 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- A0002000 | 89A018E0 - F685D875 : 468D1A74 - 458D5020 | ? .à ö?Øu F.t EP C0280000..C028000F: 16 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- C0280000 | 0556B123 - 028C2121 : 05AD1121 - 056A14E6 | .V±# .?!! .-.! .j.æ [...]
Before stepping to the next section, please study the first hex dump panel of Example 4-11 once more. The four PTEs at address 0xC0280000 all look quite similar. In fact, they differ only in the three least-significant bits. If you examine more of these PNPES that refer to pages in the pagefiles, you find that they all have bit #10 set. That's why I assigned the name PageFile to this bit in Listing 4-3. If it is set, the remaining bitsexcept for the P flag, of courseapparently specify the location of this page in the pagefiles.
More Command Options
Some of the most interesting command options listed in Example 4-1 have not yet been explained. For example the "System status options" +o, +c, +g, +i, and +b are missing, although they sound promising. I will return to them in the last section of this chapter, where several secrets of the Windows 2000 memory system will be revealed.