Blog Post

Native Functions To The Rescue - Part 1

How to make a critical process that can crash Windows if it is closed.

Native Functions To The Rescue - Part 1 - How to make a critical process that can crash Windows if it is closed.
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.
- MSDN

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:

C++[Copy]
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:

C++[Copy]
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 the SE_DEBUG_NAME privilege (or "SeDebugPrivilege") to be able to set the ProcessBreakOnTermination 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:

MakeProcCrit
MakeProcCrit tool that shows its command line options.

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
MakeProcCrit
MakeProcCrit after setting Notepad as a critical process.
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:

Task Manager warning
"Do you want to end the system process 'Notepad'?"
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:

CRITICAL PROCESS DIED
Stop code: CRITICAL PROCESS DIED

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:

C++[Copy]
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:

  1. A process needs to have the SE_DEBUG_NAME privilege even to set its own threads to the critical status.
  2. Once set, a thread can remove the critical status by calling NtSetInformationThread with the ThreadBreakOnTermination class and a 0 value.
  3. 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.
  4. A process can set (or reset) the critical status of a running thread in another process by a thread ID.
  5. 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.

Related Articles