Original link: https://windows-internals.com/lookaside-list-forensics/
Introduction
A while ago we did some research. That specific project might be published at some other time in the future and we won’t go into too much detail about it here. But as part of this project we wanted to gain access into an internal data structure used by some driver. Sadly, the driver’s global pointer to this data structure is not exported, and we couldn’t find a way to access it from outside the driver itself. It is stored in the pool, so we couldn’t even scan the driver address space for signs of this structure.
Of course, there is always the option of doing binary parsing on the driver based on a function signature that references the global, and/or using an array of known offsets for the global variable and adding the driver base to find it. But these methods require finding and using the correct RVA for every version of the driver, as well as all potential function signatures. Because this driver does not have exported functions, such signatures would be brittle and subject to change between releases. Therefore, although often used by malware authors, we find these techniques ugly and inconvenient to implement — we knew we could do better.
So, we reverse engineered the data structure itself and came up with an interesting idea that can give us easy access to this data structure and to many others. The data structure we were interested in is very large and contains, among other things, a few lookaside lists embedded in it. Lookaside lists are single linked lists containing pool allocations of a fixed size. They are used by drivers for caching memory allocations instead of always requesting them from the memory manager. Let’s see what makes these interesting.
System Lookaside Lists
Here is the wdm.h definition of a GENERAL_LOOKASIDE_LAYOUT (GENERAL_LOOKASIDE is just an aligned version of GENERAL_LOOKASIDE_LAYOUT):
//
// The goal here is to end up with two structure types that are identical except
// for the fact that one (GENERAL_LOOKASIDE) is cache aligned, and the other
// (GENERAL_LOOKASIDE_POOL) is merely naturally aligned.
//
// An anonymous structure element would do the trick except that C++ can't handle
// such complex syntax, so we're stuck with this macro technique.
//
#define GENERAL_LOOKASIDE_LAYOUT \
union { \
SLIST_HEADER ListHead; \
SINGLE_LIST_ENTRY SingleListHead; \
} DUMMYUNIONNAME; \
USHORT Depth; \
USHORT MaximumDepth; \
ULONG TotalAllocates; \
union { \
ULONG AllocateMisses; \
ULONG AllocateHits; \
} DUMMYUNIONNAME2; \
\
ULONG TotalFrees; \
union { \
ULONG FreeMisses; \
ULONG FreeHits; \
} DUMMYUNIONNAME3; \
\
POOL_TYPE Type; \
ULONG Tag; \
ULONG Size; \
union { \
PALLOCATE_FUNCTION_EX AllocateEx; \
PALLOCATE_FUNCTION Allocate; \
} DUMMYUNIONNAME4; \
\
union { \
PFREE_FUNCTION_EX FreeEx; \
PFREE_FUNCTION Free; \
} DUMMYUNIONNAME5; \
\
LIST_ENTRY ListEntry; \
ULONG LastTotalAllocates; \
union { \
ULONG LastAllocateMisses; \
ULONG LastAllocateHits; \
} DUMMYUNIONNAME6; \
ULONG Future[2];
A useful fact to notice is that this structure contains a linked list (GENERAL_LOOKASIDE.ListEntry), meaaning all lookaside lists do. Depending on whether the lookaside list was created with ExInitializeNPagedLookasideList or ExInitializePagedLookasideList (or, if ExInitializeLookasideListEx was used, the PoolType which was passed in), the data structure will be entered into one of two list heads. As such, if we follow the ListEntry of any lookaside list, we’ll eventually end up at either ExPagedLookasideListHead or ExNPagedLookasideListHead. Since we create our own lookaside list through these APIs, if we pick the same pool type as our target structure, we can therefore through all other lookasides, and eventually reach the one contained in our target structure. In this particular use case, using our own definition of the structure, the useful CONTAINING_RECORD macro, and the knowledge that the first member of the structure is a “magic” ULONG that always contains the same value, we searched all lookaside lists using this mechanism until we reached our structure.
But the possibilities don’t stop there – this method gives us access to any kernel structure, exported or not, that contains a lookaside list. So what else is there?
Pool-Based Lookaside Lists
With some WinDbg magic, we can also find out valuable information about the data – whether it’s inside a driver (and which one!) or in the kernel pool, who it belongs to, the allocation size, etc. To explore the possibilities, we wrote a simple WinDbg script that iterates through all lookaside lists and uses the extremely helpful !pool extension to dump information about them. Although we could build similar functionality in a custom C driver, there is no Windows Kernel API that can supply us with similar information about pool allocations and parsing pool pages to retrieve it is a lot of work, so we decided to avoid implementing the same functionality in C due to laziness. In fact, while we tried to implement our own C-based pool parser, we ended up realizing that nobody had described the myriad of changes in Windows 10 RS5 and above’s pool manager, so we’re busy writing a book on the topic.
Using our script, we found structures containing lookaside lists that belong to FltMgr.sys, Win32k.sys, Windows Defender drivers, various display drivers, and much more.
There are some results in which the pool tag is unknown, making the tracking of the driver they belong to difficult. A fun way to solve that is using driver verifier’s pool tracking feature. We can modify our script and replace the !pool <address> 2 command with !verifier <address> 2 and receive information about the allocating driver and the completes stack trace of the allocation. But running this command on so many addresses is extremely slow and it dumps a lot of information that is hard to sort through. So another option is going for a more manual approach – enabling driver verifier but executing the previous script as it is, and only querying specific addresses that seem interesting with verifier.
Image-Based Lookaside Lists
Initially we only searched for data in the pool because that is where the structure we were interested in was allocated. But with this trick we also get access to lookaside lists that are inside drivers, and we can use the cool new **RtlPcToFileName** function to find out what driver these structures are in. In this case we did choose to implement this in C code since it’s more straightforward and faster to execute:
_Use_decl_annotations_
NTSTATUS
DriverEntry (
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
LOOKASIDE_LIST_EX lookaside;
PLIST_ENTRY lookasideList;
PLIST_ENTRY lookasideListHead;
PGENERAL_LOOKASIDE generalLookaside;
UNICODE_STRING pcName = RTL_CONSTANT_STRING(L"RtlPcToFileName");
DECLARE_UNICODE_STRING_SIZE(driverName, 32);
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->DriverUnload = DriverUnload;
auto RtlPcToFileNamePtr = (decltype(RtlPcToFileName)*)(MmGetSystemRoutineAddress(&pcName));
NT_ASSERT(RtlPcToFileNamePtr != nullptr);
//
// Create our own lookaside list to use for finding other lookaside lists in the kernel.
//
status = ExInitializeLookasideListEx(&lookaside,
nullptr,
nullptr,
PagedPool,
0,
8,
'Fake',
0);
if (!NT_SUCCESS(status))
{
goto Exit;
}
//
// Iterate over our lookaside list to find all the other lookaside lists
// and print information about them
//
generalLookaside = nullptr;
lookasideListHead = &lookaside.L.ListEntry;
lookasideList = lookasideListHead->Flink;
do
{
generalLookaside = CONTAINING_RECORD(lookasideList,
GENERAL_LOOKASIDE,
ListEntry);
//
// Use RtlPcToFileName to find whether the lookaside list is
// inside a driver and if so, which one
//
status = RtlPcToFileNamePtr(generalLookaside, &driverName);
if (NT_SUCCESS(status))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
DPFLTR_ERROR_LEVEL,
"Lookaside list is in driver %wZ\n",
driverName);
}
else
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
DPFLTR_ERROR_LEVEL,
“Lookaside list is not inside a driver\n”);
}
lookasideList = lookasideList->Flink;
} while (lookasideList != lookasideListHead);
status = STATUS_SUCCESS;
Exit:
ExDeleteLookasideListEx(&lookaside);
return status;
}
With this code we found lookaside lists inside of Ntoskrnl.exe, Ci.dll, Ntfs.sys and more. Of course, since these are embedded inside of the driver memory, our only way to know whether these are independent lookaside lists or they are part of a larger structure is to dump the addresses and reverse engineer the drivers. But we’re all nerds who like reverse engineering, or we wouldn’t be writing/reading this blog.
We can also implement the same query in WinDbg if we choose to, using the ln command which searches for the nearest symbol to an address:
This is a pretty cool trick, which led to all sorts of cool discoveries. And we only searched for paged lookaside lists. There is a whole world of non-paged lookaside lists that we didn’t even look at yet. We ran the same WinDbg scripts as before, and just changed our starting point from nt!ExPagedLookasideListHead to nt!ExNPagedLookasideListHead to get the non-paged lookaside lists, and got some interesting results. We looked for non-paged lookaside lists in the pool:
There’s actually one more linked list of lookaside lists that we haven’t talked about yet: ExPoolLookasideListHead. Since the first versions of Windows NT, and up until Windows 10 RS5 when the pool manager was rewritten to use the Backend Heap (again, the topic of a future book!), it leveraged a per-processor array of 32 lookaside lists, one for each indexed multiple of the pool block size. On x86, this basically meant any 8-byte aligned allocation from 8 to 256 bytes, and on x64, any 16-byte aligned allocation from 16 to 512 bytes.
Since there was both a paged and nonpaged pool, each KPRCB had two such arrays — the PPNPagedLookasideList and the PPPagedLookasideList. With Windows 8 and the introduction of the non-executable nonpaged pool, a third array was created: PPNxPagedLookasideList. All of these lookaside lists are therefore inserted into the same linked list head, and on our system, you can easily see how many processors (16) are present:
Originally, this seemed exciting, as it would imply the ability to easily locate not only structures that contain a lookaside list, but in fact, any pool structure that’s a multiple of the pool block size. Unfortunately, if we take a look at these lists on modern Windows 10 systems, we find that they’re completely unused:
Indeed, looking at the code in ExAllocatePoolWithTag and friends, this logic was completely removed as part of the heap-related changes we’ll cover in a future research paper.
Executive Resources
The even cooler thing is that lookaside lists are not the only kernel structures that are linked to all other structures of the same type! Another example is theERESOURCE, a structure used to implement read/write locking for drivers. Executive resources are also contained inside of many kernel structures, and can give us access to even more internal kernel information, if we know how to find them. We changed our WinDbg scripts to iterate over the linked list found in ERESOURCE.SystemResourcesList, starting from nt!ExpSystemResourcesList.
We first searched for ERESOURCE objects in the pool:
We found some very interesting results that are probably worth further investigation, such as pool structures related to NTFS volume objects, structures inside Ci.dll, and much, much more. On our machine we found over 400 000 executive resources:
Because of the sheer number, making analysis with LINQ unwieldly, we wanted to get pool information for some of these ERESOURCE structures using C code and start analyzing them. Unfortunately, unlike lookaside lists, ERESOURCE structures don’t have their pool tag as part of the structure, so we have to write a pool parser to get the pool information for each ERESOURCE. As we’ve mentioned before, as it turns out, in RS5 and later, that is not an easy task at all, as you’ll see in our upcoming research on the new backend heap-backed kernel pool.