W

Wave

Guest
#1
Hello everyone!

In today’s thread, I am going to be talking about Windows Critical Processes; I will start with a part for theory and why they may be used, then I will share code examples on marking a process as critical and then I will finish the thread off with some quick sample analysis (download available within the disclaimer below).

[DISCLAIMER]
I am not responsible for how the code examples are used, the purpose of this thread is to be used as an educational reference – please do not use the code for anything where the intentions are not positive.

If you download the below samples please do not execute it on your main system for safety precautions since terminating the program at the wrong time of execution will cause a system-wide crash (Blue Screen of Death), and this can cause data corruption/loss. Please stick to using a Virtual Environment for handling anything within this thread unless you do know what you are doing (e.g. I am entirely on a Host system for this thread).

You can download the custom sample (compiled from my examples) from the bellow links:
MSIL example: Private file
[/DISCLAIMER]

Part 1 – Critical Processes (theory)
In Windows there are system processes, and then we have our standard programs running (with their own processes); a process is essentially just a container since our code does not actually execute as the “process”, but executes on a thread which are within this process – think of it like how a fruit is the protector of the seed inside, it can be viewed the same way with a process and the process’ threads, since the process is the element which is there to hold everything together (and therefore is “protecting” the threads within it). When code is executing within our process it is executing on a thread, and you can have more than one thread (therefore threads can be executing simultaneously which allows us to have multiple functions executed at once by the CPU).

System processes have a special marking in Windows and this was achieved by the usage of a Native API function which is exported by a component of the Windows Kernel, ntdll.dll (which is not “part” of the Windows Kernel itself, however it is a wrapper to allow user-mode programs to communicate with the Windows Kernel, since the wrapper will transition code execution to kernel-mode via a system call for Windows API calls – e.g. Win32 API). NTDLL.dll exports a wide-range of functions, some of which are documented by Microsoft themselves, whereas some are not – since these functions are exported by the wrapper, we can obtain the address in memory to the exported functions (as long as ntdll.dll is mapped in memory – which means it’s accessible in memory) and then use them (as long as understand how to use them which may involve reversing if the function is undocumented/unknown of).

Since the System processes which are built-in with Windows are very important and essential core components of the Windows OS, it’s necessary for them to be active for the OS to function correctly – if these processes are not running then the OS will lose functionality, and depending on the system process which has been stopped from running, it can prevent the entire OS from functioning at all. Not all system processes are required to be active for the OS to function properly, however it is essential that processes like winlogon.exe and csrss.exe are running at all times. The processes that must be running for the system to function correctly are marked as “critical processes”.

When a critical process is terminated, the system will experience a system-wide crash (which means a BSoD crash) and the error message for this crash will be CRITICAL_PROCESS_DIED. This helps ensure that when a critical process stops executing, the OS will move into its panic mode (BSoD crash), which allows the OS to collect any additional information regarding why the system crashed (if possible) which can be later accessed within a crash dump file for diagnostic/research purposes, and then afterwards the system will reboot and attempt to function correctly again.

We can mark a process as critical via the usage of an undocumented NTAPI function called RtlSetProcessIsCritical – it’s an NTAPI function (belongs to the Native Windows API) since it’s exported by ntoskrnl.exe, however we can call it through usage of the NTDLL.DLL wrapper (accessible from user-mode and the target function we want to use is exported, allowing us to dynamically obtain the address of the function in memory and call it).

The function, RtlSetProcessIsCritical, takes in three parameters: all three parameters have a data type of BOOLEAN; however, the second parameter is given in as a pointer (we can simply put NULL/0 since it’s for output and not input anyway).

The function structure would be (C++): BOOLEAN bNewValue, BOOLEAN *pbOldValue, BOOLEAN bNewVal2.

This function is most commonly found in .NET malware since many malware authors who are using .NET languages (MSIL) for malware development want to develop some sort of “self-protection” against Task Manager (since it will prevent you from terminating a “critical process” by default – forcing it with another program won’t) and therefore this is there easy way to do this since they are unaware of how to use other methods for more stable self-protection (and since they are developing malicious software, they couldn’t care less either way). This is why I have included a VB.NET example, since it will be beneficial for you to understand it to help you with understanding Part 3 to this thread – even though this method is abused by malware authors, my current judgement of sharing this thread is that there is no problem since there are some other posts out there somewhere which will have code examples on this topic, just without as much detail of course (since I am superior as we all know, and would rather make sure us good guys have better documentation ;) ).

Part 2 – Code Examples
C++ (Empty Project -> main.cpp):

Code:
#include <Windows.h>
#include <Winternl.h>
#include <iostream>
using namespace std;

typedef NTSTATUS(NTAPI *pdef_RtlSetProcessIsCritical)(BOOLEAN bNewValue, BOOLEAN *pbOldValue, BOOLEAN bVal2);

typedef NTSTATUS(NTAPI *pdef_RtlAdjustPrivilege)(ULONG Privilege, BOOLEAN bEnable, BOOLEAN bCurrentThread, PBOOLEAN pbEnabled);

bool enable_priv(int priv_method)
{
       BOOL bResult = FALSE;
       if (priv_method == 1) // use NTAPI
       {
              BOOLEAN bRes;
              LPVOID RtlAdjustPrivilege_addr = GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlAdjustPrivilege");
              if (!RtlAdjustPrivilege_addr)
              {
                     cout << "Unable to obtain the address of ntdll!RtlAdjustPrivilege!\n";
              }
              pdef_RtlAdjustPrivilege RtlHandle = (pdef_RtlAdjustPrivilege)RtlAdjustPrivilege_addr;
              if (NT_SUCCESS(RtlHandle(20, TRUE, FALSE, &bRes)))
              {
                     cout << "Successfully enabled Debugging Rights (SeDebugPrivilege) via ntdll!RtlAdjustPrivilege!\n";
                     bResult = TRUE;
              }
              else
              {
                     cout << "Failed to enable Debugging Rights (SeDebugPrivilege) via ntdll!RtlAdjustPrivilege: " << GetLastError() << endl;
                     bResult = FALSE;
              }
       }
       else if (priv_method == 2) // use Win32 API
       {
              HANDLE TokenHandle = { 0 };
              TOKEN_PRIVILEGES TokenPriv = { 0 };
              LUID lValueId = { 0 };
              if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle))
              {
                     cout << "Failed to open the process token: " << GetLastError() << endl;
                     bResult = FALSE;
              }
              if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &lValueId))
              {
                     cout << "Failed to lookup the privilege value: " << GetLastError() << endl;
                     bResult = FALSE;
              }
              TokenPriv.PrivilegeCount = 1;
              TokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
              TokenPriv.Privileges[0].Luid = lValueId;
              if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPriv, sizeof(TokenPriv), NULL, NULL))
              {
                     cout << "Successfully adjusted the token privileges via AdjustTokenPrivileges!\n";
                     bResult = TRUE;
              }
              else
              {
                     cout << "Failed to adjust the token privileges: " << GetLastError() << endl;
                     bResult = FALSE;
              }
       }
       return bResult;
}

bool setcritical(int type_id)
{
       pdef_RtlSetProcessIsCritical RtlTramp = (pdef_RtlSetProcessIsCritical)GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlSetProcessIsCritical");
       if (type_id == 0) // enable
       {
              NTSTATUS NtRet = RtlTramp(TRUE, 0, FALSE);
              if (NT_SUCCESS(NtRet))
              {
                     return true;
              }
       }
       else // disable
       {
              NTSTATUS NtRet = RtlTramp(FALSE, 0, FALSE);
              if (NT_SUCCESS(NtRet))
              {
                     return true;
              }
       }
       return false;
}

int main()
{
       if (!enable_priv(1))
       {
              getchar();
              return 0;
       }
       BOOL setattempt = setcritical(0);
       switch (setattempt)
       {
       case true:
              cout << "This process is now a critical one... Terminate it and you'll get a BSoD crash!\n";
              break;
       case false:
              cout << "The process could not be set as a critical one... " << GetLastError() << endl;
              getchar();
              return 0;
              break;
       }
       getchar();
       setattempt = setcritical(1);
       switch (setattempt)
       {
       case true:
              cout << "You can now terminate the process\n";
              break;
       case false:
              cout << "The protection cannot be removed\n" << GetLastError() << endl;
              break;
       }
       getchar();
       return 0;
}
(I purposefully made the above code longer to demonstrate examples of a switch case since I do not believe I’ve ever used this in my previous threads containing code examples; the code can be shortened and become more efficient by changing it to a simple if statement comparison when calling the functions for enabling debugging rights and setting the process as critical – the reason we enable debugging rights is because it’s required for usage of this function.

C#.NET:
Code:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class Form1
{
                [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Auto)]
                private static extern void RtlSetProcessIsCritical(bool bValue, ref bool bVal2, bool bVal3);

                private void enablebtn_Click(object sender, EventArgs e)
                {
                                Process.EnterDebugMode();
                                RtlSetProcessIsCritical(true, ref false, false);
                }

                private void disablebtn_Click(object sender, EventArgs e)
                {
                                Process.LeaveDebugMode();
                                RtlSetProcessIsCritical(false, ref false, false);
                }
}
VB.NET:
Code:
Imports System.Runtime.InteropServices

Public Class Form1

    <DllImport("ntdll.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Private Shared Sub RtlSetProcessIsCritical(ByVal bValue As Boolean, ByRef bVal2 As Boolean, ByVal bVal3 As Boolean)
    End Sub

    Private Sub enablebtn_Click(sender As Object, e As EventArgs) Handles enablebtn.Click
        Process.EnterDebugMode()
        RtlSetProcessIsCritical(True, False, False)
    End Sub

    Private Sub disablebtn_Click(sender As Object, e As EventArgs) Handles disablebtn.Click
        Process.LeaveDebugMode()
        RtlSetProcessIsCritical(False, False, False)
    End Sub
End Class
In the above code examples (C#.NET and VB.NET) you may have noticed that I enter debug mode (and also leave it); this is the equivalent of what I do in the C++ code example, where I enable debugging rights, since it’s required to use the function.

There is another function which can be used for the same thing called NtSetInformationProcess; I will save this for a future thread.

Part 3 – Sample Analysis
The purpose of this part is not to be detailed malware analysis since: we do not have any real malware with a lot of functionality for the purpose of this thread and we already know what to expect; I will go through a quick demonstration of MSIL decompilation, it’s so quick that this part is most likely pointless.

To start off you will need software which is capable of decompiling MSIL assemblies; a free choice would be ILSPY and a paid alternate would be .NET Reflector (from SmartAssembly).

Once you have your selected software, open up the MSIL assembly within it:
Snaggy - easy screenshots

After you’ve opened your chosen assembly (e.g. the MSIL test sample) then go through the Form1.vb to get an estimation of the code (generated pseudo-code back from the byte-code, it’s fairly accurate and good enough to get an understanding on the sample).

I have pointed out our code in the below screenshot (found successfully):
Snaggy - easy screenshots
--------------------------------------------------------------------------------------

I apologize for this thread being short and lack of detail again, soon I hope to make a really detailed thread for you lot! :)

Stay safe,
Wave. ;)
 
D

Deleted member 65228

Guest
#5
We can mark a process as critical via the usage of an undocumented NTAPI function called RtlSetProcessIsCritical
This function will internally call NtSetInformationProcess. The PROCESS_INFORMATION_CLASS is 29 and the call will change the value for BreakOnTermination flag. If BreakOnTermination is 0 then the process is not critical, whereas if the flag is 1 then it is critical.
 
Joined
Mar 13, 2017
Messages
29
Operating System
Windows 10
Antivirus
ESET
#6
Is `critical process` same as `protected process`?
 
Last edited:
D

Deleted member 65228

Guest
#7
Is `critical process` same as `protected process`?
"Critical" processes are not the same as "protected" processes.

Critical processes have a flag set within their Process Environment Block (PEB) for BreakOnTermination. When the process is terminated, a kernel-mode routine within the Windows Kernel named KeBugCheckEx will identify that the process had been set with the critical status prior to termination, and this will cause the OS to throw an manually-induced BSOD.

Protected processes can also have a flag set within their Process Environment Block (PEB). Since Windows Vista, there has been ability to do this... It was enforced in an non-exported kernel-mode routine called PspOpenProcess (determine whether the process had the correct flag/s for a protected process before granting the handle open request). Microsoft provide special signatures to software developers of security software (as an example) which is required to have your process set as protected like this.

Protected processes are not always enforced by the OS. There is a kernel-mode callback under the title ObRegisterCallbacks which can be used to receive notifications about handle operations for processes and threads (open/duplicate requests). Through usage of this kernel-mode callback, a caller can be denied permission to open/duplicate a handle with X access rights. By "X" I am referring to any being discussed (e.g. PROCESS_VM_OPERATION, PROCESS_THREAD_CREATE, etc.).

I wrote a post on another thread which I do actually own (I am not the author of this one, please forgive me for stepping in) which briefly mentioned built-in Windows process protection. If you're interested, you can find it here: Malware Analysis - Code injection identification [Malware Analysis]