- Aug 29, 2017
- Security
- #vmi, #introspection, #windows
Last week I was working on a VMI-based malware unpacker for Linux and Windows when I came across an interesting problem. I was trying to implement a method that would, given a virtual address and process ID, return the address range of the memory segment it belongs to using VMI.
Implementing this in Linux was no problem for me because it's the OS I'm
most familiar with. The
implementation
boils down to getting the current process' task_struct
, looking up the
pointer to it's memory mapping (task_struct->mm
), and then iterating through its
linked list of virtual memory areas (mm->mmap
) until a match is found. Pretty
straight forward.
Windows seemed a little tricker but very similar. The main difference is while
Linux uses a linked list of structures called virtual memory areas, Windows
uses structures called virtual address descriptors (VADs) linked into a
balanced binary tree. The procedure is fairly similar. Once the current executive
process (_EPROCESS
) is located in memory, read its VadRoot
pointer that, as the name implies,
points to the root of the binary tree of VADs and then check the VAD's memory range.
Lookup the left child if the range is too high, the right child if the range is too low,
and repeat until the desired VAD is located. A straightforward binary search.
So I implemented my VMI function, ran it, and to my surprise it failed. After
some debugging, I discovered that when the code read VadRoot
, the pointer
would always be 3 bytes greater than the actual base virtual address of the root VAD. Here are
some examples of addresses that my code read for 64-bit Windows 7, printed in little-endian:
1b 3c 90 02 80 fa ff ff
6b 3d 6a 02 80 fa ff ff
3b 69 8e 01 80 fa ff ff
Why are the 4 least significant bits always 0xb
and why was I only having
this problem with the VadRoot
pointer and no other pointers? Stumped, I asked
my question to the
libVMI forum
and the developer of DRAKVUF kindly pointed out the
answer:
the Windows kernel sometimes uses a special pointer called a _EX_FAST_REF
.
If you take a look at the definition for this type, you will notice something interesting:
typedef struct _EX_FAST_REF
{
union
{
PVOID Object;
ULONG RefCnt: 3;
ULONG Value;
};
} EX_FAST_REF, *PEX_FAST_REF;
As you can see, the Windows kernel uses the 3 least significant bits as a reference counter. Therefore, in order to read this pointer correctly using VMI, these bits need to be masked out after reading the pointer. Once I realized such a pointer existed, the rest of the implementation was straightforward.
So there you have it. The Windows kernel sometimes uses a special pointer that stashes a reference counter in the lower bits. Something to watch out for when you're doing virtual machine introspection. Hopefully this blog post will save others some time.