Once upon a time, the most common way to dump the physical memory in Windows was through the PhysicalMemory section object. Modern versions of Windows don't allow that kind of access, and the typical method now is using a custom kernel module. Here, I describe some issues related to the old method.
\Device\PhysicalMemory is a section object which enables us to access the physical memory from ordinary user mode applications in Windows. Fortunately, from a security viewpoint, this section object can only be opened by members of the Administrators group. Unfortunately, from a forensic viewpoint, all user mode access to it was blocked starting with Windows XP 64-bit, Windows 2003 Server SP1, and Windows Vista.
We can map a part of the physical memory into our user mode application memory space with the native API call NtMapViewOfSection. The last parameter passed to NtMapViewOfSection is a ULONG called Protect. Here we have to pass a constant representing the page protection attributes we want for our mapping. The average programmer would probably pass it the constant PAGE_READONLY and think no more about it. That's not a good idea, and we will soon see why.
Windows can set the memory type in a page table entry to one of the following:
Write Back (WB)
Write Combine (WC)
Strong Uncacheable (UC)
The memory type tells the processor how to handle accesses to a page. Most pages should be marked as WB since it leads to maximum performance. Both reads from and writes to a WB page may be cached in the L1, L2, and L3 caches. The WC type can be used when multiple writes may be combined to a single one without any adverse side effects. The first write is put in a write combining buffer inside the processor. The processor associates this buffer with the address range that starts at the first byte of the first write, and ends at a distance from it equal to the size of the write combining buffer. Further writes inside that address range are also written to the buffer. When any of a few different conditions are fulfilled, the contents of the write combining buffer are written directly to the main memory. For our purposes, this fact about WC pages is the most important; that is, WC pages are never cached in the L1, L2, or L3 caches. That uncacheable pages are not meant to be cached is more obvious.
A single physical page can be mapped to more than one place in the virtual address space and can thus be associated with more than one page table entry. What if those page table entries are marked with different memory types? Imagine that the page is mapped at the virtual addresses A (a WB page) and B (a WC page). If a program writes something at address A, it will end up in one of the caches. Next, another program writes something at address B. At any time the processor might decide to do a combined write to the main memory. Now the content of the cache is stale without the processor knowing it. Sooner or later the contents of these stale cache lines will be written back to main memory. At this point, we are left with what was written at virtual address A, even though the last write was to address B. The simple rule is this: never use more than one memory type in page table entries that point to the same physical page. The scenario described is only one example of what might go wrong. In practice, what happens is dependant upon the specific processor implementation. Anything could happen - we really cannot tell without knowing exactly how the particular processor is designed.
Returning to the NtMapViewOfSection call and the Protect parameter, it is now time to look at a couple of constants that can be passed to it, namely PAGE_NOCACHE and PAGE_WRITECOMBINE. When we map a view of the physical memory into the address space of our application, we have to tell NtMapViewOfSection which memory type the page table entries describing that part of our address space should have. Somewhere else in the system some other application or kernel mode driver may have page table entries pointing to the very same part of the physical memory. The danger of the situation should be obvious. As long as we only read from memory, we could perhaps be fine, but as I said before we cannot know for sure.
Let's take a look at DD from the Forensic Acquisition Utilities. Jumping straight into the code we find a call to the Win32 API function MapViewOfFile at line 161 of the physmem.c file. The constant FILE_MAP_READ is passed as the dwDesiredAccess parameter. MapViewOfFile is exported by kernel32.dll, but I will not bore you with the reversing of it here. It contains nothing more than a call to MapViewOfFileEx with the lpBaseAddress parameter always set to NULL. The MapViewOfFileEx function contains a bit more code, but for our purposes, it does nothing more than call NtMapViewOfSection. Except for that, the only interesting part is the conversion of the value passed as the dwDesiredAccess parameter to the value passed as the Protect parameter. To make a long story short, the value 4 (FILE_MAP_READ) is always converted to the value 2 (PAGE_READONLY).
So what happens when DD runs through the whole physical memory space? If there are no WC, UC or UC- mappings all will be fine. Otherwise - who knows what will happen. Perhaps nothing special, perhaps cache incoherence, perhaps something else. Some kernel drivers allocate these kinds of memory but as far as I know Windows itself does not.
Before Windows XP there was no check in Windows for page mappings with different memory types. Windows XP does check though. A second memory type mapping attempt will fail with the STATUS_CONFLICTING_ADDRESSES (0xc0000018) error message.
To try my theories in practice, I wrote a simple kernel driver with a call to MmAllocateContiguousMemorySpecifyCache, requesting a buffer of MmNonCached memory from the nonpaged pool. With the driver loaded, I ran DD to dump the whole main memory. DD finished much too early (when hitting the buffer) and printed the warning "Attempt to access invalid address." I tried the same thing with WC memory, and the result was the same. You can think of it as a crude anti-forensics technique.
We can bypass this problem if we can pass the correct combination of constants when we do the mapping - assuming it is still valid until we are finished. However, I am not aware of any way to determine the correct memory type from user mode. Even if there is such a way, the memory type could change at any time without us knowing it. I don't think there is a general solution to the problems with using PhysicalMemory.
If we focus only on Windows XP, there's a simple solution to the blocking problem. All we have to do is try different memory types until the call succeeds. Still, it's risky since the memory type could change while we have the physical memory mapped. The method would of course not work at all on other versions of Windows since any memory type will "succeed".
After this blog post was originally published, the author of the Forensic Acquisition Tools (George M. Garner Jr.) has pointed out that the README file following his DD version warns of the side effects described in my article. He also asked me to tell a bit about what happens with other combinations of memory type parameters, and that is now merged into the text above.
Copyright © 2021 Arne Vidstrom. All Rights Reserved.