Tutorial Critical threads (Native API)

D

Deleted member 65228

Guest
#1
Hello!

This thread may only be useful if you are already aware about what a critical process is and how they work/are set. Critical processes were popular once in the past amongst managed (.NET framework based) malware due to the authors not being able to develop proper self-protection mechanisms, and critical processes allowed them to have a BSOD induced when the malicious processes were terminated (and block Task Manager from allowing the termination). If you're after theory of general information on critical processes, another member seems to have documented it a bit at the following links:
Critical Processes [Theory & C++/C#/VB]
Tutorial - Check if a process is critical or not [C++]

For those of you that may be aware about critical processes (there are other threads in this programming sub-forum about what they are, how they work and how to detect/terminate them properly), you can also make an independent thread "critical". Within the Process Environment Block (PEB - which is basically a large structure containing information regarding the process) there is BreakOnTermination which holds a flag value. BreakOnTermination can be modified for the threads.

If you cannot open a handle to the process to use the Win32 API for detecting if a process is critical or not/NtQueryInformationProcess, and are trying to terminate the threads instead, it is important to check if the state of the thread is set to critical. If you terminate a thread which is critical without changing the flag so BreakOnTermination is equal to 0 (NULL), a BSOD will occur thanks to BugCheckEx.

To query if a thread is critical or change the BreakOnTermination value (enable/disable), you can use two Native API functions known as NtQueryInformationThread and NtSetInformationThread.

NtQueryInformationThread:
Code:
NTSTATUS ZwQueryInformationThread (
  _In_      HANDLE          ThreadHandle,
  _In_      THREADINFOCLASS ThreadInformationClass,
  _In_      PVOID           ThreadInformation,
  _In_      ULONG           ThreadInformationLength,
  _Out_opt_ PULONG          ReturnLength
);
NtSetInformationThread:
Code:
NTSTATUS ZwSetInformationThread(
  _In_ HANDLE          ThreadHandle,
  _In_ THREADINFOCLASS ThreadInformationClass,
  _In_ PVOID           ThreadInformation,
  _In_ ULONG           ThreadInformationLength
);
Both functions are exported by NTDLL. NTDLL is a trampoline for user-mode to kernel-mode, the functions actually exist in kernel-mode. The user-mode NTDLL stubs just move execution to the kernel automatically for us.

You can define the function prototypes by doing the following.
Code:
typedef NTSTATUS(NTAPI *pNtQueryInformationThread)(
    HANDLE ThreadHandle,
    THREADINFOCLASS ThreadInformationClass,
    PVOID ThreadInformation,
    ULONG ThreadInformationLength,
    PULONG ReturnLength OPTIONAL
    );

typedef NTSTATUS(NTAPI *pNtSetInformationThread)(
    HANDLE ThreadHandle,
    THREADINFOCLASS ThreadInformationClass,
    PVOID ThreadInformation,
    ULONG ThreadInformationLength
    );
You can use GetProcAddress to get the address for using the function prototypes.
Code:
pNtQueryInformationThread fNtQueryInformationThread = (pNtQueryInformationThread)ADDRESSHERE
pNtSetInformationThread fNtSetInformationThread = (pNtSetInformationThread)ADDRESSHERE
When performing the query or setting the new information for the thread, you'll need to pass 18 as the class for the second parameter of both functions (THREADINFOCLASS). 18 represents ThreadBreakOnTermination.

Below is an example on how you would use both functions.
Code:
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG Flag = 0;

NtStatus = NtQueryInformationThread(ThreadHandle,
        (THREADINFOCLASS)ThreadBreakOnTermination,
        &Flag,
        sizeof(ULONG),
        NULL);

if (!NT_SUCCESS(NtStatus))
{
       // error whilst trying to use NtQueryInformationThread, check the contents of NtStatus
}

if (Flag)
{
        // if you reach here, the thread IS critical
}

    // if you reach here, the thread is not critical
Code:
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG Flag = 0;

NtStatus = NtSetInformationThread(ThreadHandle,
        (THREADINFOCLASS)ThreadBreakOnTermination,
        &Flag, sizeof(ULONG));

return NtStatus; // if NtStatus is equal to STATUS_SUCCESS then the thread is no longer critical
ThreadHandle which is undefined can be acquired via OpenThread/NtOpenThread. You can enumerate the threads within a process via NtQuerySystemInformation, or by using the Win32 API for it with CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) and checking if the thread belongs to the target process.

You may need to use a type definition for an enum for THREADINFOCLASS yourself, because winternl.h may not have it/have the correct one. Below is an example.

Code:
typedef enum _THREADINFOCLASS {
    ThreadBasicInformation,
    ThreadTimes,
    ThreadPriority,
    ThreadBasePriority,
    ThreadAffinityMask,
    ThreadImpersonationToken,
    ThreadDescriptorTableEntry,
    ThreadEnableAlignmentFaultFixup,
    ThreadEventPair_Reusable,
    ThreadQuerySetWin32StartAddress,
    ThreadZeroTlsCell,
    ThreadPerformanceCount,
    ThreadAmILastThread,
    ThreadIdealProcessor,
    ThreadPriorityBoost,
    ThreadSetTlsArrayAddress,
    ThreadIsIoPending,
    ThreadHideFromDebugger,
    ThreadBreakOnTermination,
    ThreadSwitchLegacyState,
    ThreadIsTerminated,
    ThreadLastSystemCall,
    ThreadIoPriority,
    ThreadCycleTime,
    ThreadPagePriority,
    ThreadActualBasePriority,
    ThreadTebInformation,
    ThreadCSwitchMon,
    ThreadCSwitchPmu,
    ThreadWow64Context,
    ThreadGroupInformation,
    ThreadUmsInformation,
    ThreadCounterProfiling,
    ThreadIdealProcessorEx,
    MaxThreadInfoClass
} THREADINFOCLASS;
You can use NtQueryInformationProcess and NtSetInformationProcess to do the same thing if you have a HANDLE to the process (acquired via OpenProcess/NtOpenProcess for example). There is a Win32 API for checking if a process is critical via the process handle starting on Windows 8, however this would not work for Windows 7 therefore NtQueryInformationProcess and checking the flag under BreakOnTermination would allow support for previous versions of Windows if you are doing this.

Thanks for reading,
-Opcode.