While analyzing a memory dump, sooner or later you'll have to convert a virtual into a physical address. This can be a challenging task when it's done for the first time. This article will guide you through the process.
For an introductory reading into the concept of virtual memory and its implementation in Microsoft Windows I recommend
- Undocumented Windows 2000, chapter 4 by Sven B. Schreiber
- Windows Internals (4th ed.), chapter 7 by Mark Russinovich and David Salomon.
You should also get the free manuals from Intel. Chapter 3.7 of Volume 3A explains the mechanics of 32-bit physical addresses. But why is it so important to understand this technology at all? Please have a look at the following excerpt taken from a debugger session:
kd> !process 0 0 **** NT ACTIVE PROCESS DUMP **** PROCESS 829516a0 SessionId: 0 Cid: 0008 Peb: 00000000 ParentCid: 0000 DirBase: 00030000 ObjectTable: 82976108 TableSize: 235. Image: System PROCESS 8246c6a0 SessionId: 0 Cid: 00a4 Peb: 7ffdf000 ParentCid: 0008 DirBase: 04e4b000 ObjectTable: 8246cd88 TableSize: 33. Image: SMSS.EXE PROCESS 82442020 SessionId: 0 Cid: 00bc Peb: 7ffdf000 ParentCid: 00a4 DirBase: 07409000 ObjectTable: 824ec9a8 TableSize: 399. Image: CSRSS.EXE PROCESS 824038c0 SessionId: 0 Cid: 00d0 Peb: 7ffdf000 ParentCid: 00a4 DirBase: 07dce000 ObjectTable: 82404ae8 TableSize: 422. Image: WINLOGON.EXE
Did you notice that with the exception of the system process all processes seem to refer to the same Process Environment Block (Peb)? The Peb is a large structure which among other things refers to the program file loaded into memory, so obviously this can't be true. The solution to this problem is strikingly simple - the Peb's address 0x7ffdf000 is a virtual address! But that doesn't help you much if all you've got is a memory dump. To read the Peb you'll have to convert the virtual address into a physical address and turn this into an offset into the dump file.
The following example will guide you through the process. Once again I will use the first image from the DFRWS Memory Analysis Challenge. First, it's free and publicly available. Second, the image was generated with dd. Therefore an offset into the image file equals an address in physical memory. Other file types would require some knowledge about the file internals and some mathematics to calculate back and forth between offsets and physical addresses.
Let's have a look at the Peb of UMGR32.EXE. As shown in the Intel manual, your starting point to convert virtual into physical addresses is the CPU's CR3 control register. Its value will be different for every process because each process has got a virtual address space of its own. The proper value for CR3 is saved in a variable named DirectoryTableBase of the EPROCESS structure. I've slightly modified PTfinder to report this value (column title is "PDB").
> ptfinder.pl --nothreads dfrws2005-physical-memory1.dmp No. Type PID TID Time created Offset PDB Remarks ---- ---- ------ ------ ------------------- ---------- ---------- ---------------- 4 Proc 668 2005-06-05 00:55:08 0x0095f020 0x075a7000 UMGR32.EXE
Let's find out the (virtual) address of the Peb. Therefore open the dump in a hex editor and seek to offset 0x0095f020. From there proceed to the Peb's offset (0x1b0 for Windows 2000). There you'll find the byte sequence 0x00 0xf0 0xfd 0x7f. So the Peb's address is 0x7ffdf000.
Write down this address as a binary number and get the most significant 10 digits. They'll give you an index into the Page Directory. In this example the index is 0x1ff.
|Page Directory Index||Remainder|
|31 - 22||21-0|
As seen above the Page Directory starts at physical address 0x075a7000. Each Page Directory Entry (PDE) is 4 Bytes in size. So the address for entry no. 0x1ff is 0x075a7000+0x1ff*4 = 0x075a77fc. The PDE reads 0x05cee067.
|Page Table Base Address||unused||G||PS||0||A||CD||WT||U/S||R/W||P|
|31 - 12||11||10||9||8||7||6||5||4||3||2||1||0|
First thing to look at is the Page Size (PS) flag. If set, the address belongs to a 4 MB page. Windows uses large pages only for the kernel's code. So not surprisingly our address belongs to a small (that is 4 kb) page. As the Present (P) flag indicates, this page is present in physical memory. So we can proceed examining the memory.
Because the address belongs to a small page the next 10 bits (21 to 12) are to be interpreted as an index into a Page Table. The remaining 12 bits are an offset into the memory page.
|Page Directory Index||Page Table Index||Offset in Page|
|31 - 22||21 - 12||11 - 0|
|0x1ff (511)||0x3df (991)||0|
The Page Table starts at address 0x5cee * 0x1000. Each Page Table Entry (PDE) is 4 bytes large. So we'll find entry no. 0x3df at physical address 0x5cee * 0x1000 + 0x3df * 4 = 0x5ceef7c. It reads 0x0532f047. Let's parse this entry, too:
|Page Base Address||unused||G||PAT||D||A||CD||WT||U/S||R/W||P|
|31 - 12||11||10||9||8||7||6||5||4||3||2||1||0|
This gives us a Page Base Address of 0x0532f * 0x1000. The offset into this page is 0. So for the virtual address 0x7ffdf000 the physical address is 0x0532f * 0x1000 + 0 = 0x532f000.
03/12/2006: Corrected a typo in an address and fixed the last table.