Manipulating ActiveProcessLinks to Hide Processes in Userland
Last updated
Last updated
The purpose of this lab is to look into how Windows kernel rootkits hide / unlink (or used to) processes in the userland for utilities trying to list all running processes on the system such as Windows Task Manager
, tasklist
or Get-Process
cmdlet in Powershell.
This is going to be a high level overview and no kernel code will be written, instead, kernel memory structures will be manipulated manually with WinDBG.
Lab is performed on Windows 10 Professional x64, 1903.
Update 1 Some replies to my tweet to this post suggested that PatchGuard would normally kick-in and BSOD the OS, which I am sure is the case, although in my lab I experienced no BSODs even though the kernel stayed patched with an unlinked process for 12+ hours.
Update 2 I realized that my Windows VM is running in test mode with no integrity checks, possibly explaining the lack os BSODs - unconfirmed. Update 3 Thanks @FuzzySec for clarifying the BSOD/PatchGuard matter!
We need to be familiar with two kernel memory structures before we proceed.
_EPROCESS
is a kernel memory structure that describes system processes (or in other words - each process running on a system has its corresponding _EPROCESS
object somewhere in the kernel) as we know them. It contains details such as process image name, which desktop session it is running in, how many open handles to other kernel objects it has, what access token it has and much more.
Below shows a snippet of the structure and a highlighted a member that is key to this lab - ActiveProcessLinks
. It is a pointer to a structure called LIST_ENTRY
:
In programming, there is a data structure known as doubly-linked list
. It contains records (also called nodes) that are linked to each other, meaning each node in the list contains two fields (hence doubly), that reference previous and the next record of that linked list.
Simplified (head and tail omitted) graphical representation of the doubly-linked list is shown below:
LIST_ENTRY
is the doubly-linked list equivalent data structure in Windows kernel and is defined as:
...where FLINK
(forward link) and BLINK
(backward link) are the equivalents of Next
and Previous
references to the next and previous element in the list in our graphical representation of the doubly-linked list discussed above.
All Windows processes have their corresponding kernel objects in the form of an EPROCESS kernel structure. All those EPROCESS objects are stored in a doubly-linked list.
Effectively, this means that when a cmd /c tasklist
or get-process
is invoked to get a list of all running processes on the system, Windows walks through the doubly-linked list of EPROCESS nodes, utilizing the LIST_ENTRY
structures and retrieves information about all currently active processes.
Below is a simplified visualization of the above:
With all of the above information, we can now define what we're trying to do in the lab - we want to hide a process of our choice from being shown in a process list when a get-process
cmdlet or similar is issued in the userland.
Below is a simplified diagram illustrating how this will be achieved by manually manipulating kernel structures in WinDBG in order to hide the EPROCESS 2 (white):
ActiveProcessLinks.Flink
in EPROCESS 1 will be pointed to EPROCESS 3 ActiveProcessLinks.Flink
ActiveProcessLinks.Blink
in EPROCESS 3 will be pointed to EPROCESS 1 ActiveProcessLinks.Flink
Kernel memory manipulations will unlink the EPROCESS 2 from the previous node (EPROCESS 1) and the next node (EPROCESS 3) in the doubly-linked list and, effectively, render it invisible to all userland APIs that retrieve running system processes - exactly like Windows kernel rootkits do it.
Let's launch a process that we will try to hide - a notepad.exe in my case:
In kernel, we can get more information about our notepad
process like so:
Below shows that our notepad's corresponding EPROCESS
structure is located at ffffb208f8b304c0
:
Checking the EPROCESS structure of our notepad:
...we can see the ActiveProcessLinks
, the doubly-linked list, populated with two pointers (Flink and Blink):
We can also read those values with dt _list_entry ffffb208f8b304c0+2f0
or by dumping two 64-bit long values from ffffb208f8b304c0+2f0
:
Let's now figure out the previous and next EPROCESS nodes our notepad.exe is pointing to.
Below shows in two different ways (1. observing ActiveProcessLinks
from the EPROCESS structure; 2. reading two 64-bit values from the EPROCESS+0x2f0
) that our notepad's:
FLINK (green) is pointing to ffffb208`f8d1e7b0
BLINK (blue) is pointing to ffffb208`f8b89370
For curiosity, we can check the process's image name referenced by the notepad's FLINK at ffffb208`f8d1e7b0
- the next EPROCESS node to our notepad's EPROCESS:
We need to:
find the EPROCESS location by subtracting 0x2f0 from the FLINK ffffb208`f8d1e7b0
. This is because FLINK points to EPROCESS.ActiveProcessLinks
and ActiveProcessLinks
is located at offset 0x2f0 from the beginning of the EPROCESS location
add 0x450 since this is the offset of the ImageFileName
in the EPROCESS structure
Let's do the same for the process referenced by the notepad's BLINK to get the previous EPROCESS node to our notepad's EPROCESS:
Looks like our notepad EPROCESS is surrounded by two svchost EPROCESS nodes.
Continuing, we can get PIDs of those two svchost.exe processes referenced by FLINK and BLINK and they are 0x000009cc
and 0x00001464
respectively as shown below:
Below shows essentially the same as the above output with some colour-coding:
...where highlighted in green is the svchost (0x09cc) referenced by notepad's FLINK and in blue is the svchost (0x1464) referenced by notepad's BLINK.
Let's get the FLINK and BLINK for the svchost.exe (PID 0x9cc) and note that ffffb208`f8d1e7b0
is the location of EPROCESS.ActiveProcessLinks
which will be important later:
Green is FLINK and blue is BLINK:
Let's get FLINK and BLINK for the svchost.exe (PID 0x1464) and note that ffffb208`f8b89370
is the location of EPROCESS.ActiveProcessLinks
which will be important later:
Green is FLINK and blue is BLINK:
We can now summarize the FLINK and BLINK pointers we have for all the processes we are interested in:
Image | PID | EPROCESS | ActiveProcessLinks | Flink | Blink |
svchost | 0x1464 | ffffb208f8b89080 | ffffb208`f8b89370 | ffffb208`f8b307b0 | ffffb208`f96c97b0 |
notepad | 0xe14 | ffffb208f8b304c0 | ffffb208`f8b307b0 | ffffb208`f8d1e7b0 | ffffb208`f8b89370 |
svchost | 0x9cc | ffffb208f8d1e4c0 | ffffb208`f8d1e7b0 | ffffb208`f94ee7b0 | ffffb208`f8b307b0 |
Below are the two kernel modifications we need to perform in order to hide notepad.exe from process listing APIs in the userland:
Point svchost's (0x1464) FLINK at ffffb208`f8b89370
to svchost's (0x9cc) FLINK at ffffb208`f8d1e7b0
Point svchost's (0x9cc) BLINK at ffffb208`f8d1e7b0+8
(+8 because LIST_ENTRY is two fields FLINK/BLINK and are 8 bytes each on x64) to svchost's (0x1464) FLINK at ffffb208`f8b89370
Below visualizes the above outlined steps:
Let's perform the above mentioned kernel modifications:
Once the kernel memory is modified, we can run a get-process
or ps notepad
in powershell and observe that notepad.exe has been successfully hidden:
...although it can still be looked up by its PID in the kernel:
Below is another quick demo showing how notepad.exe disappears from the Windows Task Manager once the kernel memory is tampered and the debugger is resumed. Additionally, ps notepad
returns nothing, although notepad is visible in the taskbar and underneath the Windows Task Manager:
In the above demo, memory offsets of structures are different due to a system reboot since the initial write up.
In order to detect unlinked processes exhibited by malware on systems without PatchGuard, explore psscan
and psxview
from Volatility.