spider

Introduction

Lately, I dusted off the marvelous ‘Practical Reverse Engineering’ book by Bruce Dang & Co. which made me realize it would be useful to structure notes along each chapter’s exercises. Could be a valuable reference for the future, especially anything related to Chapter 3 (Windows Kernel) onwards. I decided to focus on one of the most basic and still very widespread data structure in kernel land: linked lists.

The most used type in the Windows Kernel is the Circular doubly-linked list, so we are going to focus only on this kind.

As per book definition, a doubly-linked list is a:

A list whose entries are linked together with two pointers, one pointing to the next entry (Flink) and one pointing to the previous entry (Blink).

They appear in form of WDK headers, and so you’ll never find them at as pure assembly functions from a debugger point of view as they are inserted inline by the compiler. Thus, it is important to recognize their assembly patterns since they are outside any call, jmp or ret logic.

Linked lists definitions can be typically found along with many others at C:\Program Files (x86)\Windows Kits\10\Include\[SDK_version]\shared\ntdef.h

List Entry is the building block of any linked list data structure.

  typedef struct _LIST_ENTRY {
     struct _LIST_ENTRY *Flink;
     struct _LIST_ENTRY *Blink;
  } LIST_ENTRY, *PLIST_ENTRY;

Before moving to the first part of the exercises' solutions, let’s have an overview of the main routines dealing with linked-list operations.

InitializeListHead

Lists must be initialized with InitializeListHead before usage. This function simply sets the Flink and Blink fields to point to the list head.

  VOID InitializeListHead(PLIST_ENTRY ListHead) {
      ListHead->Flink = ListHead->Blink = ListHead;
      return;
}

In assembly form, this would translate to three instructions: one to retrieve ListHead and two to fill out the Flink and Blink pointers.

x86

lea eax, [esi+2Ch] ; saves ListHead pointer into eax
mov [eax+4], eax   ; makes ListHead->Blink points to ListHead
mov [eax], eax     ; makes ListHead->Flink points to ListHead

x64

lea r11, [rbx+48h] ; saves ListHead pointer into r11
mov [r11+8], r11   ; makes ListHead->Blink points to ListHead
mov [r11], r11     ; makes ListHead->Flink points to ListHead

It is worth noting that offset +0 and +4/8 from the base register are pointing to Flink and Blink respectively.

InsertHeadList

Element insertion is done via either InsertHeadList or InsertTailList, depending on where we want to insert the new entry.

VOID InsertHeadList(PLIST_ENTRY ListHead, PLIST_ENTRY Entry) {
    PLIST_ENTRY Flink;
    Flink = ListHead->Flink;
    Entry->Flink = Flink;
    Entry->Blink = ListHead;
    Flink->Blink = Entry;
    ListHead->Flink = Entry;
    return;
}

EBX/RDI point to ListHead and ECX/RAX point to Entry.

x86

mov     edx, [ebx].  ; saves old ListHead->Flink into edx (as Flink new variable)
mov     [ecx], edx   ; saves ListHead into Entry->Flink
mov     [ecx+4], ebx ; saves ListHead into Entry->Blink
mov     [edx+4], ecx ; saves Entry into Entry-Blink
mov     [ebx], ecx   ; saves Entry into ListHead->Flink

x64

mov     rcx, [rdi]   ; saves old ListHead->Flink into rcx (as Flink new variable)
mov     [rax+8], rdi ; saves ListHead into Entry->Blink
mov     [rax], rcx   ; saves ListHead->Flink into Entry->Flink
mov     [rcx+8], rax ; saves Entry into Entry-Blink
mov     [rdi], rax   ; saves Entry into ListHead->Flink

InsertTailList

VOID InsertTailList(PLIST_ENTRY ListHead, PLIST_ENTRY Entry) {
    PLIST_ENTRY Blink;
    Blink = ListHead->Blink;
    Entry->Flink = ListHead;
    Entry->Blink = Blink;
    Blink->Flink = Entry;
    ListHead->Blink = Entry;
    return;
}

Here instead, EBX/RDI point to ListHead and EAX/RAX point to Entry.

x86

mov     ecx, [ebx+4] ; saves old ListHead->Blink into ecx
mov     [eax], ebx   ; saves ListHead into Entry->Flink
mov     [eax+4], ecx ; load old ListHead->Blink into Entry->Blink
mov     [ecx], eax   ; saves Entry into ListHead->Flink
mov     [ebx+4], eax ; saves Entry into ListHead->Blink

x64

mov     rcx, [rdi+8] ; saves old ListHead->Blink into rcx
mov     [rax], rdi   ; saves ListHead into Entry->Flink
mov     [rax+8], rcx ; load old ListHead->Blink into Entry->Blink
mov     [rcx], rax   ; saves Entry into ListHead->Flink
mov     [rdi+8], rax ; saves Entry into ListHead->Blink

IsListEmpty

Before deleting any entry via either RemoveHeadList, RemoveTailList, or RemoveEntryList a check is done through isListEmpty to verify if the list is not already empty.

BOOLEAN IsListEmpty(PLIST_ENTRY ListHead) {
    return (BOOLEAN)(ListHead->Flink == ListHead);

x86

mov eax, [esi]       ; loads ListHead’s Flink into eax
cmp eax, esi         ; compare it with the ListHead itself

x64

mov rax, [rbx]       ; loads ListHead’s Flink into rax
cmp rax, rbx         ; compare it with the ListHead itself

RemoveHeadList

PLIST_ENTRY RemoveHeadList(PLIST_ENTRY ListHead) {
    PLIST_ENTRY Flink;
    PLIST_ENTRY Entry;
    Entry = ListHead->Flink;
    Flink = Entry->Flink;
    ListHead->Flink = Flink;
    Flink->Blink = ListHead;
    return Entry;
}

x86

mov eax, [esi]       ; saves ListHead->Flink into eax
mov ecx, [eax]       ; saves Entry->Flink into ecx
mov [esi], ecx       ; makes ListHead->Flink point to NextEntry->Flink
mov [ecx+4], esi     ; makes NextEntry->Blink ListHead point to ListHead

x64

mov rax, [rbx]       ; saves ListHead->Flink into rax
mov rcx, [rax]       ; saves Entry->Flink into rcx
mov [rbx], rcx       ; makes ListHead->Flink point to NextEntry->Flink
mov [rcx+8], rbx     ; makes NextEntry->Blink ListHead point to ListHead

RemoveTailList

PLIST_ENTRY RemoveTailList(PLIST_ENTRY ListHead) {
    PLIST_ENTRY Blink;
    PLIST_ENTRY Entry;
    Entry = ListHead->Blink;
    Blink = Entry->Blink;
    ListHead->Blink = Blink;
    Blink->Flink = ListHead;
    return Entry;

x86

mov  ebx, [edi+4]    ; saves ListHead->Blink into ebx (Entry)
mov  eax, [ebx+4]    ; saves Entry->Blink into eax (Blink)
mov  [edi+4], eax    ; makes ListHead->Blink point to NextEntry->Blink
mov  [eax], edi      ; makes NextEntry->Flink ListHead point to ListHead

x64

mov  rsi, [rdi+8]    ; saves ListHead->Blink into ebx (Entry)
mov  rax, [rsi+8]    ; saves Entry->Blink into eax (Blink)
mov  [rdi+8], rax    ; makes ListHead->Blink point to NextEntry->Blink
mov  [rax], rdi      ; makes NextEntry->Flink ListHead point to ListHead

RemoveEntryList

BOOLEAN RemoveEntryList(PLIST_ENTRY Entry){
    PLIST_ENTRY Blink;
    PLIST_ENTRY Flink;
    Flink = Entry->Flink;
    Blink = Entry->Blink;
    Blink->Flink = Flink;
    Flink->Blink = Blink;
    return (BOOLEAN)(Flink == Blink);

x86

mov  edx, [ecx]      ; saves Entry->Flink
mov  eax, [ecx+4]    ; saves Entry->Blink
mov  [eax], edx      ; makes previous entry point to next one
mov  [edx+4], eax    ; makes next entry point to previous one

x64

mov  rdx, [rcx]      ; same as above
mov  rax, [rcx+8]
mov  [rax], rdx
mov  [rdx+8], rax

Finally, the following macro is used to obtain the actual value of the content of a list entry. As per MSDN

The CONTAINING_RECORD macro returns the base address of an instance of a structure given the type of the structure and the address of a field within the containing structure.

#define CONTAINING_RECORD(address, type, field) ((type *)( \
                                                  (PCHAR)(address) - \
                                                  (ULONG_PTR)(&((type *)0)->field)))

Solutions Chapter 3 - page 123

Problem:

On Windows 8 x64, the following kernel functions have InitalizeListHead inlined at least once:

  • CcAllocateInitializeMbcb
  • CmpInitCallbacks
  • ExCreateCallback
  • ExpInitSystemPhase0
  • ExpInitSystemPhase1
  • ExpTimerInitialization
  • InitBootProcessor
  • IoCreateDevice
  • IoInitializeIrp
  • KeInitThread
  • KeInitializeMutex
  • KeInitializeProcess
  • KeInitializeTimerEx
  • KeInitializeTimerTable
  • KiInitializeProcessor
  • KiInitializeThread
  • MiInitializeLoadedModuleList
  • MiInitializePrefetchHead
  • PspAllocateProcess
  • PspAllocateThread

Instead of Windows 8 x64 we are going to inspect these functions in the latest Win10 build 12004 - 19041.685.

CcAllocateInitializeMbcb

This function comes from the Cache Manager (Cc) component.

mov     eax,2FBh
mov     word ptr [rbx],ax
lea     rcx,[rbx+10h]
lea     rax,[rbx+30h]
mov     qword ptr [rax],rcx
mov     qword ptr [rax+8],rcx
mov     qword ptr [rcx],rax
mov     qword ptr [rcx+8],rax

Before initializing the linked list, the functions first call ExAllocatePoolWithTag and the allocated memory is saved in rbx.

fffff801`2570ed2e e8fd226a00      call    nt!ExAllocatePoolWithTag (fffff801`25db1030)
fffff801`2570ed33 488bd8          mov     rbx,rax

It then sets NodeTypeCode field in the MBCB to 2FB which is actually telling the cache manager that we are dealing with a CACHE_NTC_MBCB type.

0: kd> dt nt!_MBCB
   +0x000 NodeTypeCode     : Int2B
   +0x002 NodeIsInZone     : Int2B
   +0x004 PagesToWrite     : Uint4B
   +0x008 DirtyPages       : Uint4B
   +0x00c Reserved         : Uint4B
   +0x010 BitmapRanges     : _LIST_ENTRY
   +0x020 ResumeWritePage  : Int8B
   +0x028 MostRecentlyDirtiedPage : Int8B
   +0x030 BitmapRange1     : _BITMAP_RANGE
   +0x060 BitmapRange2     : _BITMAP_RANGE
   +0x090 BitmapRange3     : _BITMAP_RANGE

Offsets 0x10 and 0x30 tell that we are dealing with the first of BitmapRanges entry.

CmpInitCallbacks

nt!CmpInitCallbacks:
sub     rsp,28h ; negligible
xor     ecx,ecx ; negligible
lea     rax,[nt!CallbackListHead (fffff801`260482e0)]
mov     dword ptr [nt!CmpCallBackCount (fffff801`260508f0)],ecx
lea     rdx,[nt!`string' (fffff801`2540e9e8)]
mov     qword ptr [nt!CmpCallbackListLock (fffff801`260482d8)],rcx
mov     qword ptr [nt!CmpContextListLock (fffff801`260482f0)],rcx
mov     qword ptr [nt!CallbackListDeleteEvent (fffff801`260482f8)],rcx

The list initialization is done via lea rax,[nt!CallbackListHead].

4: kd> x nt!CallBackListHead
fffff801`260482e0 nt!CallbackListHead = <no type information>

We can validate that is in fact a LIST_ENTRY by double checking against the ERESOURCE structure.

4: kd> dt nt!_ERESOURCE  fffff801`260482e0
   +0x000 SystemResourcesList : _LIST_ENTRY [ 0xffff8b09`84c05960 - 0xffff8b09`8bbd1e70 ]
   +0x010 OwnerTable       : (null) 
   +0x018 ActiveCount      : 0n0
   +0x01a Flag             : 0
   +0x01a ReservedLowFlags : 0 ''
   +0x01b WaiterPriority   : 0 ''
   +0x020 SharedWaiters    : 0x01d6d4a5`ceb5a831 Void
   +0x028 ExclusiveWaiters : (null) 
   +0x030 OwnerEntry       : _OWNER_ENTRY
   +0x040 ActiveEntries    : 0
   +0x044 ContentionCount  : 0
   +0x048 NumberOfSharedWaiters : 0
   +0x04c NumberOfExclusiveWaiters : 0
   +0x050 Reserved2        : (null) 
   +0x058 Address          : (null) 
   +0x058 CreatorBackTraceIndex : 0
   +0x060 SpinLock         : 0

ExCreateCallback

Here’s the inlined InitializeListHead

mov  qword ptr [rax+8],rbx
mov  qword ptr [rax+18h],rsi
mov  qword ptr [rax+20h],rdi

ExpInitSystemPhase0

This function has been fully restructured on this Windows10 version and has no InitializeListHead inlined

ExpInitSystemPhase1

This function has been fully restructured on this Windows10 version and has no InitializeListHead inlined

ExpTimerInitialization

This function has been fully restructured on this Windows10 version and has no InitializeListHead inlined

InitBootProcessor

This function has been fully restructured on this Windows10 version and has no InitializeListHead inlined

IoCreateDevice

Here is the inlined InitializeListHead

 lea     rax,[rbx+50h]
 mov     qword ptr [rax+8],rax
 mov     qword ptr [rax],rax

at rbx we have a pointer to DeviceObject whose +50h offset is a Queue of KDEVICE_QUEUE type. At 0x8 from where we have indeed a LIST_ENTRY .

nt!IoCreateDevice+0x258:
fffff804`75702698 48894008        mov     qword ptr [rax+8],rax

3: kd> dt nt!_KDEVICE_QUEUE @rax
   +0x000 Type             : 0n0
   +0x002 Size             : 0n0
   +0x008 DeviceListHead   : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x018 Lock             : 0
   +0x020 Busy             : 0 ''
   +0x020 Reserved         : 0y00000000 (0)
   +0x020 Hint             : 0y00000000000000000000000000000000000000000000000000000000 (0)

IoInitializeIrp

Here the inlined routine:

lea     rax,[rbx+20h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

at 0x20 of the IRP we have a ThreadListEntry struct.

1: kd> dt _IRP @rbx
nt!_IRP
   +0x000 Type             : 0n6
   +0x002 Size             : 0x6b8
   +0x004 AllocationProcessorNumber : 0
   +0x006 Reserved         : 0
   +0x008 MdlAddress       : (null) 
   +0x010 Flags            : 0
   +0x018 AssociatedIrp    : <anonymous-tag>
   +0x020 ThreadListEntry  : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x030 IoStatus         : _IO_STATUS_BLOCK
   +0x040 RequestorMode    : 0 ''
   +0x041 PendingReturned  : 0 ''
   +0x042 StackCount       : 21 ''
   +0x043 CurrentLocation  : 22 ''
   +0x044 Cancel           : 0 ''
   +0x045 CancelIrql       : 0 ''
   +0x046 ApcEnvironment   : 0 ''
   +0x047 AllocationFlags  : 0 ''
   +0x048 UserIosb         : (null) 
   +0x050 UserEvent        : (null) 
   +0x058 Overlay          : <anonymous-tag>
   +0x068 CancelRoutine    : (null) 
   +0x070 UserBuffer       : (null) 
   +0x078 Tail             : <anonymous-tag>

which is naturally of LIST_ENTRY type.

1: kd> dx -id 0,0,ffff89884d7e32c0 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xffff89884cadc8c0))
(*((ntkrnlmp!_LIST_ENTRY *)0xffff89884cadc8c0))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0x0 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x0 [Type: _LIST_ENTRY *]

KeInitThread

This routine is responsible for the KTHREAD initialization, so it’s wise to start inspecting this structure in the first place and highlight the LIST_ENTRY sub-members.

0: kd> dt _KTHREAD -b
nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
    [...]
      +0x008 WaitListHead     : _LIST_ENTRY
         +0x000 Flink            : Ptr64 
         +0x008 Blink            : Ptr64 
    [...]
   +0x098 ApcState         : _KAPC_STATE
      +0x000 ApcListHead      : _LIST_ENTRY
         +0x000 Flink            : Ptr64 
         +0x008 Blink            : Ptr64 
    [...]
   +0x0d8 WaitListEntry    : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 
    [...]
         +0x008 WaitListHead     : _LIST_ENTRY
            +0x000 Flink            : Ptr64 
            +0x008 Blink            : Ptr64 
    [...]      
      +0x020 TimerListEntry   : _LIST_ENTRY
         +0x000 Flink            : Ptr64 
         +0x008 Blink            : Ptr64 
    [...]
   +0x140 WaitBlock        : _KWAIT_BLOCK
      +0x000 WaitListEntry    : _LIST_ENTRY
         +0x000 Flink            : Ptr64 
         +0x008 Blink            : Ptr64 
    [...]
   +0x208 QueueListEntry   : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 
    [...]
   +0x258 SavedApcState    : _KAPC_STATE
      +0x000 ApcListHead      : _LIST_ENTRY
         +0x000 Flink            : Ptr64 
         +0x008 Blink            : Ptr64 
    [...]
   +0x288 SchedulerApc     : _KAPC
      +0x000 Type             : UChar
      +0x001 SpareByte0       : UChar
      +0x002 Size             : UChar
      +0x003 SpareByte1       : UChar
      +0x004 SpareLong0       : Uint4B
      +0x008 Thread           : Ptr64 
      +0x010 ApcListEntry     : _LIST_ENTRY
         +0x000 Flink            : Ptr64 
         +0x008 Blink            : Ptr64 
    [...]
   +0x2dc UserTime         : Uint4B
   +0x2e0 SuspendEvent     : _KEVENT
         +0x008 WaitListHead     : _LIST_ENTRY
            +0x000 Flink            : Ptr64 
            +0x008 Blink            : Ptr64 
    [...]
   +0x2f8 ThreadListEntry  : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 
    [...]
   +0x308 MutantListHead   : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 
    [...]
   +0x370 GlobalForegroundListEntry : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 
    [...]
   +0x3f0 GlobalUpdateVpThreadPriorityListEntry : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 

The first occurrence of InitializeListHead is:

lea     rax,[rcx+8]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

rcx points to KTHREAD, so this is initializing a WaitListHead LIST_ENTRY.

The second one is setting up MutantListHead:

lea     rax,[rcx+308h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

The third is related to ApcState

lea     rax,[rdi+98h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

and the last one is initializing the struct at offset 0x10 (0xA8) from ApcState which is the second entry of the list.

lea     rax,[rdi+0A8h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

Let’s verify it: rdi holds a pointer to the ApcState structure and at offset 0 we have the ApcListHead

kd> dt nt!_KAPC_STATE @rdi
   +0x000 ApcListHead      : [2] _LIST_ENTRY [ 0x00000000`00200000 - 0xffff8988`4c6e4088 ]
   +0x020 Process          : (null) 
   +0x028 InProgressFlags  : 0 ''
   +0x028 KernelApcInProgress : 0y0
   +0x028 SpecialApcInProgress : 0y0
   +0x029 KernelApcPending : 0 ''
   +0x02a UserApcPendingAll : 0 ''
   +0x02a SpecialUserApcPending : 0y0
   +0x02a UserApcPending   : 0y0

kd> dx -id 0,0,ffff89884d071080 -r1 (*((ntkrnlmp!_LIST_ENTRY (*)[2])0xffff89884c6e4080))
(*((ntkrnlmp!_LIST_ENTRY (*)[2])0xffff89884c6e4080))                 [Type: _LIST_ENTRY [2]]
    [0]              [Type: _LIST_ENTRY]
    [1]              [Type: _LIST_ENTRY]

kd> dx -id 0,0,ffff89884d071080 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xffff89884c6e4090))
(*((ntkrnlmp!_LIST_ENTRY *)0xffff89884c6e4090))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0xffff89884c6e4088 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x0 [Type: _LIST_ENTRY *]

The second entry 0xffff89884c6e4090 is exactly at 0x10 from the ListHead 0xffff89884c6e4080.

KeInitializeMutex

This routine is just a wrapper to KiInitializeMutant

0: kd> uf KeInitializeMutex
nt!KeInitializeMutex:
sub     rsp,28h
mov     r8b,1
xor     edx,edx
call    nt!KiInitializeMutant (fffff804`752debe8)
add     rsp,28h
ret

So if we examine the latter we can just spot a single inlined InitializeListHead.

lea     rax,[rbx+8]
mov     rsi,qword ptr [rsp+58h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

Which is referencing an offset +8 to a KMUTANT struct. If we place a breakpoint and analyze the structure against rbx we can notice:

kd> dt nt!_KMUTANT @rbx
   +0x000 Header           : _DISPATCHER_HEADER
   +0x018 MutantListEntry  : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x028 OwnerThread      : (null) 
   +0x030 MutantFlags      : 0 ''
   +0x030 Abandoned        : 0y0
   +0x030 Spare1           : 0y0000000 (0)
   +0x030 Abandoned2       : 0y0
   +0x030 AbEnabled        : 0y0
   +0x030 Spare2           : 0y000000 (0)
   +0x031 ApcDisable       : 0 ''


kd> dx -id 0,0,ffff89884bebd080 -r1 (*((ntkrnlmp!_DISPATCHER_HEADER *)0xffff89884c4d2c40))
(*((ntkrnlmp!_DISPATCHER_HEADER *)0xffff89884c4d2c40))                 [Type: _DISPATCHER_HEADER]
[...]
    [+0x008] WaitListHead     [Type: _LIST_ENTRY]

kd> dx -id 0,0,ffff89884bebd080 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xffff89884c4d2c48))

(*((ntkrnlmp!_LIST_ENTRY *)0xffff89884c4d2c48))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0xffff89884c4d2c48 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0xffff89884c4d2c48 [Type: _LIST_ENTRY *]

Which belongs to a WaitListHead LIST_ENTRY structure.

KeInitializeProcess

The first initialization can be found here:

lea     rax,[rcx+8]
[...]
mov     qword ptr [rax+8],rax
[...]
mov     qword ptr [rax],rax

rcx points to KPROCESS structure as it’s the first argument.

KeInitializeProcess(
  IN  PRKPROCESS Process,
  IN  KPRIORITY BasePriority,
  IN  KAFFINITY Affinity,
  IN  ULONG DirectoryTableBase[2],
  IN  BOOLEAN Enable
  );

And at offset +8 we can see WaitListHead as part of the header of type DISPATCHER HEADER

nt!_KPROCESS
   +0x000 Header           : _DISPATCHER_HEADER
[...]
      +0x008 WaitListHead     : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
         +0x000 Flink            : (null) 
         +0x008 Blink            : (null) 

2: kd> dx -id 0,0,ffff89884b6db080 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xffff8988521a6200))
(*((ntkrnlmp!_LIST_ENTRY *)0xffff8988521a6200))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0x0 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x0 [Type: _LIST_ENTRY *]

Next we can see three initialization in one go:

lea     rax,[rbx+18h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax
lea     rax,[rbx+158h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax
lea     rax,[rbx+30h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

rbx is still pointing to the same KPROCESS as it has been moved from rcx earlier in the routine.

The first item accessed is a ProfileListHead, then ReadyListHead and lastly ThreadListHead

2: kd> dt _KPROCESS -b
nt!_KPROCESS
   +0x000 Header           : _DISPATCHER_HEADER
   [...]
   +0x018 ProfileListHead  : _LIST_ENTRY
   [...]
   +0x030 ThreadListHead   : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 
    [...]
   +0x158 ReadyListHead    : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 

KeInitializeTimerEx

This is a brief one and has only one inlined list initialization.

lea     rax,[rcx+8]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

The first parameter is referencing a KTIMER struct which has a WaitListHead at offset +8

0: kd> dt _KTIMER -b
nt!_KTIMER
   +0x000 Header           : _DISPATCHER_HEADER
[...]
   +0x008 WaitListHead     : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 

KeInitializeTimerTable

Here’s the only inlined entry:

nt!KeInitializeTimerTable+0x9c:
lea     rax,[rbx+3B48h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

This function looks like unused as the symbol for KiTimerTableListHead is missing.

KiInitializeProcessor

This function has been fully restructured on this Windows10 version and has no InitializeListHead inlined

KiInitializeThread

This function has been fully restructured on this Windows10 version and has no InitializeListHead inlined It might have been replaced by KiInitializeIdleThread and KiInitializeContextThread

KiInitializeIdleThread (extra)

We can see that we have two inlined in a row.

lea     rax,[rbx+560h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax
lea     rax,[rbx+570h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

This one is not documented so, given the context, we have to guess that rbx is pointing to a ETHREAD structure/ If so, offset 560 and 570 point to BoostList and DeboostList respectively.

+0x560 BoostList        : _LIST_ENTRY
   +0x000 Flink            : Ptr64 
   +0x008 Blink            : Ptr64 
+0x570 DeboostList      : _LIST_ENTRY
   +0x000 Flink            : Ptr64 
   +0x008 Blink            : Ptr64

KiInitializeContextThread (extra)

mov     rax,qword ptr [rsp+30h]
mov     qword ptr [rbx],rax
mov     rax,qword ptr [rsp+38h]
mov     qword ptr [rbx+8],rax

rsp+38 points to a KSTART_ROUTINE, while rsp+30 to a StartContext.

MiInitializeLoadedModuleList

This function has been fully restructured on this Windows10 version and has no InitializeListHead inlined

MiInitializePrefetchHead

This function is missing on this Windows10 version

PspAllocateProcess

This one has three inlined at once. Also undocumented, but since we are dealing with processes, we can affirm with almost certainty that the one we are facing it’s a EPROCESS structure.

lea     rax,[r15+5E0h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax
lea     rax,[r15+8A0h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax
lea     rax,[r15+990h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

And indeed these offsets are matching types of LIST ENTRY

0: kd> dt _EPROCESS -b
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
    [...]
   +0x5e0 ThreadListHead   : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 
    [...]
   +0x8a0 SvmProcessDeviceListHead : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 
    [...]
   +0x990 VirtualTimerListHead : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64 

PspAllocateThread

We have six inlined initialization in a row.

lea     rax,[rsi+438h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

lea     rax,[rsi+560h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

lea     rax,[rsi+570h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

lea     rax,[rsi+5C8h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

lea     rax,[rsi+4B0h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

lea     rax,[rsi+468h]
mov     qword ptr [rax+8],rax
mov     qword ptr [rax],rax

Again, given the context, we can infer it’s an ETHREAD structure

+0x438 KeyedWaitChain   : _LIST_ENTRY
   +0x000 Flink            : Ptr64 
   +0x008 Blink            : Ptr64 
[...] 
+0x468 ActiveTimerListHead : _LIST_ENTRY
   +0x000 Flink            : Ptr64 
   +0x008 Blink            : Ptr64 
[...]
+0x4b0 IrpList          : _LIST_ENTRY
   +0x000 Flink            : Ptr64 
   +0x008 Blink            : Ptr64 
[...]
+0x560 BoostList        : _LIST_ENTRY
   +0x000 Flink            : Ptr64 
   +0x008 Blink            : Ptr64 
[...]
+0x570 DeboostList      : _LIST_ENTRY
   +0x000 Flink            : Ptr64 
   +0x008 Blink            : Ptr64 
[...]
+0x5c8 PropertySet      : _PS_PROPERTY_SET
   +0x000 ListHead         : _LIST_ENTRY
      +0x000 Flink            : Ptr64 
      +0x008 Blink            : Ptr64