The Windows memory manager logic is designed around performance,
reliability, physical page re purposing, sharing, low disk writes and a
hierarchy of named objects and directories. Today we are going to talk
about paged memory, user-mode memory in particular.
In
most cases as you probably already know, unless specified otherwise, the
memory your user-mode software uses is paged. This means prior to first
access to the page, there is no associated physical page frame. This is
because the Windows memory manager wont commit a physical page until
it's absolutely needed. This is done via a page fault.
A
page fault is an interrupt and therefore takes more processing time to
dispatch the interrupt, find an unused physical page (or in the case of
an image, a shared one) and add it into the corresponding page tables
for that virtual address. Each process has a 'working set' limit and a
list which contains virtual addresses that have valid translations and should not
be paged out. This is to reduce time spent dispatching page faults
which can otherwise cause the process to take a major performance hit.
When
you allocate memory to your process from user-mode, for example
VirtualAlloc or NtMapViewOfSection, these functions do not actually set
up mappings to pfn's in the process' page tables. Instead it allocates
VAD nodes (virtual address descriptors) in the process' VAD tree. Each
process has a VAD tree, these nodes represent and describe valid virtual
addresses within the process address space. This is the area that the
VirtualQuery function gets it's data from.
Now notice I
said that a virtual address translation is not created. As said before,
the windows memory manager isn't going to commit a page frame or page
in the already paged out data until it's absolutely needed. So lets do a
basic walk through of NtAllocateVirtualMemory:
-Find an empty address range within the VAD tree
-Allocate a VAD node describing the memory
-Return
Now lets say our return virtual address value is 0x30000 and is a 4kb page.
When
we access this page for the first time, there is no valid translation
so a page fault is generated. The VAD trees are used to resolve the page
fault, a physical page is committed and we IRET right back to the
faulted instruction and is generally unbeknownst to the program or the
program's author as if the memory was always available.
Wouldn't
it be neat if there was a way to see if the page translation is valid
for an arbitrary virtual address other then just VirtualQuery telling us
it's there even though it's not really been paged in yet?
Well
of course there is! NtQueryVirtualMemory provides an infoclass of 0x4
which we can call ProcessWorkingSetInfoEx and there is even a higher
level API which will do the dirty working for us called
QueryWorkingSetEx.
This is how we can easily determine
if the page has ever been read. For instance the kernel implementation
of NtReadVirtualMemory will directly access the virtual address, if it's
not valid, it will be paged in and it's contents returned to the
caller. By examining if bit 0 is set or not in the data provided to us
by QueryWorkingSetEx we can determine if the page table entry is valid
for that virtual address, if it is, this means the memory has been
accessed.
Another way is to use NtRaiseException.
Specify the newly allocated virtual address as the instruction pointer
in the context argument, and be sure to set the contextflags
accordingly. Most debuggers will then read the instruction contents of
the instruction pointer address for dis-assembly and this will indicate
that a debugger is undoubtedly present.
Another method
not involving the use of an API would be to measure time deltas using
the kernel/user shared page or processor cycles with rdtsc between
instructions that access memory from the linear virtual address. This is
because the time to dispatch the page fault will be extremely
noticeable compared to a few cycles to access already available memory.
Use your imagination, there are many possibilities ;p
"by examining if bit 0 is set
ReplyDeleteor not in the data provided to us
by QueryWorkingSetEx we can
determine if the page table entry is
valid for that virtual address, if it
is, this means the memory has
been accessed"
can't we use this in unpacking, for example finding all the memory areas that have been recently written to by the packer?
Awesome trick
ReplyDelete