This article contains functions and features that are not documented by the original manufacturer. By following advice in this article, you're doing so at your own risk. The methods presented in this article may rely on internal implementation and may not work in the future.
Intro
Do you know what makes csrss.exe
, smss.exe
or wininit.exe
special in the system? Besides anything else, if you terminate any of those processes or somehow close them, it will take down the entire operating system.
But why?
Because those are processes that are critical to the operation of Windows and without them many things won't work. So the only logical way is to cause the system to blue-screen if any of those critical processes are no longer running.
But how does the operating system know if a process is critical or not?
We'll review it in this blog post.
Critical Process From The Kernel Side
If you look at the EPROCESS
kernel structure, that holds information about a running process, its Flags
member contains a union of flags. One of them is BreakOnTermination
, that can be accessed via the AND
operator with 0x00002000
or with the PS_PROCESS_FLAGS_BREAK_ON_TERMINATION
constant in C.
Then if you check the PspTerminateProcess
function, that is called every time a process is terminated, you'll see that the EPROCESS::Flags
is checked and if the BreakOnTermination
bit is set in it, this causes the kernel logic to execute a KeBugCheckEx
to BSOD the system with the CRITICAL_PROCESS_DIED
bug-check code.
This tells us that each process in the system can have a special flag that will make it critical.
Critical Process From The User-Mode
Many member of the EPROCESS
structure are accessible from the user-mode via the GetProcessInformation
/SetProcessInformation
pair of documented functions. The latter one seems like what we need.
If you look into the documentation we can find a brief mentioning of the ProcessBreakOnTermination
class for the NtQueryInformationProcess
function. It says:
ProcessBreakOnTermination
: Retrieves a ULONG value indicating whether the process is considered critical.
So if we can retrieve the process' critical status, then we should be able to set it.
Unfortunately, when I tried to do the following using the documented SetProcessInformation
function, I was always getting the error: 0xC000000D
, or STATUS_INVALID_PARAMETER
:
ULONG bBreakOnTermination = 1;
//DOES NOT WORK! Do not use!
if(SetProcessInformation(hProc,
ProcessBreakOnTermination, //29
&bBreakOnTermination,
sizeof(bBreakOnTermination)))
{
//Success!
}
else
{
//Failed
nError = GetLastError();
}
What was the issue? The documentation did not give me any clues.
One way to find out was to dive into the internals of the SetProcessInformation
function with the disassembler and see it for myself.
And that's when I learned that the SetProcessInformation
function (that resides in the Kernel32.dll
) actually sanitizes its input, and namely its second parameter ProcessInformationClass
, before it passes control to the undocumented NtSetInformationProcess
function. (The latter one is a native function that sits on a lower level than SetProcessInformation
in the ntdll.dll
.)
And passing the value 29
(for ProcessBreakOnTermination
) to SetProcessInformation
made it shunt out such a call with the STATUS_INVALID_PARAMETER
error.
But when I tried to bypass SetProcessInformation
, the same code with the NtSetInformationProcess
function worked:
ULONG bBreakOnTermination = 1;
if(NT_SUCCESS(NtSetInformationProcess(hProc,
ProcessBreakOnTermination, //29
&bBreakOnTermination,
sizeof(bBreakOnTermination))))
{
//Success!
}
Yay!
One caveat that I need to mention is that a process needs to have theSE_DEBUG_NAME
privilege (or "SeDebugPrivilege
") to be able to set theProcessBreakOnTermination
status.
The same works in reverse, by setting my bBreakOnTermination
variable in the code above to 0, one can remove the critical status from a process.
Proof Of Concept
One final step was to write a POC application that can let me set and reset the critical status on any arbitrary process. I called it MakeProcCrit
for the lack of a better name.
You can find my MakeProcCrit
project on GitHub.
It is a command line tool, that has to run as administrator to be able to set the SE_DEBUG_NAME
privilege for itself:
Then you can use it to set any running process as critical. For instance, let's run a Notepad and make it a critical process by its name:
MakeProcCrit.exe 1 notepad
I'm specifying the Notepad by its image name, which will technically make all running instances of the Notepad as critical. If that is not the requirement, you can use a process ID instead.
Then if the tool reports success, and I try to terminate that instance of Notepad with the Task Manager, I will get the following warning:
Do you want to end the system process 'Notepad'?
Ending this process will cause Windows to become unstable or shut down, causing you to lose any data hasn't been saved. Are you sure you want to continue?
Aside from poor grammar, the warning kinda lets you know what will happen if you close a critical process.
Obviously Microsoft was not intending this warning for a Notepad.
Alternatively, if you simply close that instance of Notepad, the operating system will BSOD:
So to make sure that you don't crash your OS, let's remove the critical status from Notepad with the following command line call:
MakeProcCrit.exe 0 notepad
If you see a successful confirmation, you can now close that instance of the Notepad.
One interesting aside. If you try to remove the critical process status from one of the critical system processes that I named above:MakeProcCrit.exe 0 smss
You would get an access-denied error and the removal will fail.
My guess is that there's a check somewhere in the kernel to prevent that from happening. (And this could be a homework assignment for the readers to find out where and how this is done. If you find it, please leave a comment below.)
How About a Critical Thread?
What seems to be less handy (and even less documented) is that one can set a thread to a critical status as well. Pretty much the same logic applies:
ULONG bBreakOnTermination = 1;
if(NT_SUCCESS(NtSetInformationThread(hThread,
ThreadBreakOnTermination, //18
&bBreakOnTermination,
sizeof(bBreakOnTermination))))
{
//Success!
}
The thread handle in question must be opened with the THREAD_SET_INFORMATION
access right, and the current process that calls NtSetInformationThread
with the ThreadBreakOnTermination
class must have the SE_DEBUG_NAME
privilege.
The full C++ code for it can be found in my CMain::makeThreadCriticalByThreadID
function on GitHub.
There are some interesting aspects of setting a thread as critical:
- A process needs to have the
SE_DEBUG_NAME
privilege even to set its own threads to the critical status. - Once set, a thread can remove the critical status by calling
NtSetInformationThread
with theThreadBreakOnTermination
class and a 0 value. - If a thread that is created with a call to
CreateThread
, or similar APIs, is set as critical and later exits, this will also cause a BSOD since that thread will be terminated internally. Thus, if you set a critical status on a thread that may exit, make sure to remove that status first. - A process can set (or reset) the critical status of a running thread in another process by a thread ID.
- If a process itself is terminated, while one of its threads had the critical status, such will not cause the BSOD.
Conclusion
The features to make a process (or a thread) "critical" are quite powerful, and I'm genuinely surprised that Microsoft is allowing any process or a thread to take that status. From your end though, try not to overuse this feature as it is a sure way to make your users very unhappy.
And lastly, this is a good example when giving up after having tried a documented API may not provide you with the most satisfying outcome. And thus digging a little bit deeper, into the world of the native APIs, may give you the desired result. Sure, relying on undocumented functions is bad in the production environment. But there's nothing bad about it in your research.