In this post, I delve into the interseting aspects of the Windows kernel, specifically focusing on the concept of creating unkillable processes. This is not intended as a tutorial or a definitive guide; rather, it serves as a personal reference and insights that I have gathered during my exploration. I acknowledge that there may be inaccuracies, but I believe that learning from mistakes is a crucial part of the development process.
Note: The experiments described herein were conducted on Windows 10 x64, Version 1709, Build 16299.125.
Understanding the PsTerminateProcess Function
The journey begins with the PsTerminateProcess function, which is a critical component in the Windows kernel responsible for terminating processes.
This function subsequently invokes PspTerminateProcess.
The PspTerminateProcess function then calls PspTerminateThreads, which iterates through all threads associated with the process and invokes PspTerminateThreadByPointer for each thread.
The Role of KeRequestTerminationThread
The PspTerminateThreadByPointer function subsequently calls KeRequestTerminationThread. This function checks the 15th bit of the 0x74th field of the _KTHREAD structure (*(v2+116) & 0x4000). If this bit is set, it inserts a kernel-mode Asynchronous Procedure Call (APC) into the APC queue of the thread, effectively marking it for termination.
It appears that if a thread is not APC queueable (i.e., the 15th bit of the 0x74 field is not set), it becomes impossible to terminate that thread using this method.
Manipulating the _KTHREAD Structure
The _KTHREAD structure’s 0x74 field is a union, where the 15th bit represents the ApcQueueable flag. This raises an interesting question: what if we were to manipulate this bit and set it to 0?
To achieve this, we can utilize tools like WinDbg or develop a custom driver. The driver code is relatively straightforward; it receives thread IDs from userland and disables the APCQueueable flag.