DKOM: Exposing and manipulating opaque data structures

Background

The Windows kernel is home to many undocumented functions and structures, and there are also many “opaque” structures. What an opaque structure allows, is for drivers to make references to objects without exposing to them details of the structure. It is a stability measure more than a security one, as it is a strong suggestion that the objects should not be manipulated by anything but the kernel. The structures that are the focus of this post are the EPROCESS and ETHREAD data types. These are fundamental to the Windows kernel, and obviously hold information critical to the operation of processes and threads. Being able to freely interact with these opens the doors to a whole lot of interesting and uncharted stuff. Obviously we will be doing this from a kernel-mode driver.

When a driver makes a call to PsGetCurrentProcess(), the return value is a pointer to an EPROCESS object in the kernel. However, while EPROCESS has many members, the driver will find itself unable to do anything with this value other than pass it to other kernel routines. By taking a quick dip into WinDbg we can identify the members of the EPROCESS and ETHREAD data types, and in turn manipulate their values. Note that the precise layout of these types varies wildly between Windows versions and even between minor versions. I would recommend that if your goal is to recreate an accurate definition for these types that you dump the structure yourself, rather than relying wholly on online information. Geoff Chappell breaks down the history of these and many other structures in deeper detail and with more insight than I ever could.

Setting up WinDbg

See my short post here on setting up WinDbg with a VirtualBox virtual machine. Or, there are many other articles on the web to guide you. But I have trawled dead forums to find solutions to a couple of obscure problems so you don’t have to.

Dumping the data structures

This part is actually extremely easy. You will need to have nt symbols loaded, as described in the other post.
With your debugger connected, break execution with Ctrl+Break, and then enter the following command:

dt nt!_ETHREAD

This will print out the structure to the console like this:

Reconstructing a partial definition

With this information, depending what you want to do with it or how portable you need it to be, you can just pad the struct out to access just the members you are interested in. For example, maybe your reason for doing this was to find the starting address of any given thread. For that, we can find the entry displayed for StartAddress:

So we know StartAddress is located at 0x5F8 and is a Ptr64 Void (PVOID).
We can then look at the end of the list and see how large the _ETHREAD is:

So we can see that the final member is at 0x770 and is a Ptr64/PVOID (which is an 8 byte type), so the actual end of the ETHREAD will be at 0x778. With these little bits of information we can now create a usable typedef for reading start addresses. For the first pad, we have 0x5F8. For the second pad, we will take the total size, and subtract the size that is taken up by the first pad plus the StartAddress member.
0x778 – (0x5F8 + 0x008) = 0x178
Thus our basic, usable struct would look like:

typedef struct _MYETHREAD {
	char pad0[0x5F8];
	PVOID StartAddress;
	char pad1[0x178];
} MYETHREAD, *PMYETHREAD;

And so now we can cast the result of PsGetCurrentThread() to PMYETHREAD:

PMYETHREAD myThread = (PMYETHREAD)PsGetCurrentThread();

And are now able to access the StartAddress member:

printf("Start address: %p\n", myThread->StartAddress);
>> Start address: 0xFFFFF803DFD45990

As we are working with pointers, we don’t really need to pad the tail of the struct out, but if you somehow ended up with a list of MYETHREADs rather than pointers, or wanted to malloc space for one, it would fall apart without padding to the true size.

What exactly is there to manipulate?

The vast majority of the members of kernel objects like these are meaningless to those doing even advanced driver development. They may however be of interest to those developing malware or those researching how to best combat it. A quick and already well-published example is the removal of a thread from the linked list that holds it. You can see that after following these steps, a thread will no longer show up in Process Hacker (a 3rd party tool). Rogue threads can potentially still be found in other ways, such as by dissecting the VAD Tree, or walking the process’ call stack, but I won’t go into those here as I don’t know much about them myself.

Removing a thread from the list

An EPROCESS object contains the head of a circular linked list that leads to its ETHREADs.
The LIST_ENTRY type (which is not opaque) simply looks like this:

typedef struct _LIST_ENTRY {
	PLIST_ENTRY Flink; // Pointer to next entry
	PLIST_ENTRY Blink; // Pointer to previous entry
} LIST_ENTRY, *PLIST_ENTRY;

Note that the LIST_ENTRY objects are actually embedded inside the EPROCESS/ETHREAD structures that they represent. So one would traverse these LIST_ENTRYs, and then, knowing the offset of the LIST_ENTRY within that structure, subtract that offset to get a pointer to the desired object. This calculation can also be done with the CONTANING_RECORD macro.
Also note that walking or manipulating these lists can end in disaster if the kernel is modifying them at the same time.

The EPROCESS-es are linked in the same way that the ETHREADs are, but for simplicity I haven’t drawn that here. So this means you can find a pointer to an arbitrary EPROCESS, access its members like we did earlier, walk the circular process list to find the one you want, then walk that’s ETHREAD list to find the thread you want to manipulate.

So with the goal of unlinking a LIST_ENTRY, we will simply demonstrate this on the current thread which we already have a reference to, rather than walking to another. Once you are satisfied that it works, you can attempt it on a thread different to your own.

I have modified our original MYETHREAD definition to accommodate accessing the ThreadListEntry member:

typedef struct _MYETHREAD {
	char pad0[0x5F8];
	PVOID StartAddress;
	char pad1[0x88];
	LIST_ENTRY ThreadListEntry;
	char pad2[0xE0];
} MYETHREAD, *PMYETHREAD;

From there we need to obtain a reference to the list entries either side of ours, and then redirect the pointers around our list entry. Note that we must also redirect our outbound pointers back to our entry to avoid a blue screen.

LIST_ENTRY threadEntry = myThread->ThreadListEntry;

PLIST_ENTRY nextThread = threadEntry.Flink;
PLIST_ENTRY prevThread = threadEntry.Blink;

prevThread->Flink = nextThread;
nextThread->Blink = prevThread;

threadEntry.Blink = (PLIST_ENTRY)&threadEntry;
threadEntry.Flink = (PLIST_ENTRY)&threadEntry;

We now have a thread object whose LIST_ENTRY is disconnected from the rest of the process’s threads, and so it cannot be walked.

Reconstruction of full definitions

I do intend to eventually publish a tool that turns pasted WinDbg output into typedefs.
In the meantime, you can tediously reverse engineer the WinDbg output!