NtIsProcessInJob
Teste si un processus s'exécute dans un job object spécifique (ou un quelconque).
Prototype
NTSTATUS NtIsProcessInJob( HANDLE ProcessHandle, HANDLE JobHandle );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle vers le processus à tester. Requiert PROCESS_QUERY_LIMITED_INFORMATION. Souvent NtCurrentProcess() ((HANDLE)-1). |
| JobHandle | HANDLE | in | Handle optionnel vers un job spécifique. NULL demande « le processus est-il dans *un* job ? » — la forme canonique de détection de sandbox. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x4F | win10-1507 |
| Win10 1607 | 0x4F | win10-1607 |
| Win10 1703 | 0x4F | win10-1703 |
| Win10 1709 | 0x4F | win10-1709 |
| Win10 1803 | 0x4F | win10-1803 |
| Win10 1809 | 0x4F | win10-1809 |
| Win10 1903 | 0x4F | win10-1903 |
| Win10 1909 | 0x4F | win10-1909 |
| Win10 2004 | 0x4F | win10-2004 |
| Win10 20H2 | 0x4F | win10-20h2 |
| Win10 21H1 | 0x4F | win10-21h1 |
| Win10 21H2 | 0x4F | win10-21h2 |
| Win10 22H2 | 0x4F | win10-22h2 |
| Win11 21H2 | 0x4F | win11-21h2 |
| Win11 22H2 | 0x4F | win11-22h2 |
| Win11 23H2 | 0x4F | win11-23h2 |
| Win11 24H2 | 0x4F | win11-24h2 |
| Server 2016 | 0x4F | winserver-2016 |
| Server 2019 | 0x4F | winserver-2019 |
| Server 2022 | 0x4F | winserver-2022 |
| Server 2025 | 0x4F | winserver-2025 |
Module noyau
APIs liées
Stub du syscall
4C 8B D1 mov r10, rcx B8 4F 00 00 00 mov eax, 0x4F 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
NtIsProcessInJob conserve le SSN `0x4F` sur tous les builds Windows 10 / 11 / Server — une stabilité remarquable qui le rend idéal pour des stubs syscall codés en dur. Malgré le type de retour NTSTATUS, le wrapper Win32 `IsProcessInJob` expose un BOOLEAN en paramètre de sortie : le noyau retourne `STATUS_PROCESS_IN_JOB` (0x00000123) ou `STATUS_PROCESS_NOT_IN_JOB` (0x00000124), tous deux des valeurs `NT_SUCCESS`, donc un `if (NT_SUCCESS(status))` naïf n'a aucun sens. Avec `JobHandle == NULL`, le noyau parcourt le pointeur EPROCESS->Job et répond sur *n'importe quel* job englobant ; avec un handle réel, il vérifie la hiérarchie imbriquée de ce job précis. L'implémentation est `PsIsProcessInJob` dans ntoskrnl.
Usage courant par les malwares
Le check anti-sandbox archétypal. Le monitor de Cuckoo Sandbox place la cible d'analyse dans un job object pour appliquer des timeouts et nettoyer les processus enfants ; les anciennes versions de Joe Sandbox faisaient de même ; plusieurs frameworks de honeypot SOC (l'agent Drakvuf, le bridge d'ANY.RUN) laissent des artefacts d'appartenance à un job. Le malware appelle `NtIsProcessInJob(NtCurrentProcess(), NULL)` et, si le résultat est `STATUS_PROCESS_IN_JOB`, se comporte bénignement (sortie propre, bannière factice, ou bifurcation vers un chemin de code leurre). Le hic : les contextes d'exécution légitimes sont *aussi* en job — chaque renderer Chrome / Edge / Brave, chaque app UWP, chaque distro WSL2, chaque conteneur Docker-on-Windows et chaque processus PowerShell lancé depuis le Planificateur de tâches vit dans un job. Coder en dur « in-job = sandbox » produit donc beaucoup de faux positifs et est de plus en plus évité par les loaders modernes au profit d'un scoring multi-signal.
Opportunités de détection
Pris isolément, NtIsProcessInJob est bénin et extrêmement courant — Chrome seul l'appelle des milliers de fois par session. La valeur de détection vient du *contexte* : un binaire non signé, fraîchement déposé, qui appelle NtIsProcessInJob avec NULL dans la première seconde d'exécution, immédiatement suivi d'un `NtTerminateProcess(NtCurrentProcess(), 0)` ou d'un long `NtDelayExecution`, est une signature forte d'évasion de sandbox. Les EDR hookent le thunk usermode dans kernel32 (`IsProcessInJob`) mais un syscall direct le contourne. Sysmon n'a pas d'événement dédié ; la meilleure télémétrie est ETW Threat-Intelligence pour les appels `NtQueryInformation*` combinée au timing de l'arbre de processus. Le problème de détection Cuckoo / Joe Sandbox est bien étudié — les sandboxes modernes lancent l'échantillon *hors* de leur job monitor (via DETACHED_PROCESS ou reparentage par WMI) précisément pour défaire ce check.
Exemples de syscalls directs
asmx64 direct stub
; Direct syscall stub for NtIsProcessInJob (SSN 0x4F, all builds)
NtIsProcessInJob PROC
mov r10, rcx ; syscall convention
mov eax, 4Fh ; SSN
syscall
ret
NtIsProcessInJob ENDPcCuckoo / Joe Sandbox detection
// Anti-sandbox: bail out if we look like we're inside a monitor job.
// Beware: Chrome renderers, UWP apps and WSL2 all also report in-job.
#include <windows.h>
#define STATUS_PROCESS_IN_JOB ((NTSTATUS)0x00000123L)
#define STATUS_PROCESS_NOT_IN_JOB ((NTSTATUS)0x00000124L)
typedef NTSTATUS (NTAPI *pNtIsProcessInJob)(HANDLE, HANDLE);
BOOL LooksSandboxed(void) {
pNtIsProcessInJob NtIsProcessInJob = (pNtIsProcessInJob)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtIsProcessInJob");
if (!NtIsProcessInJob) return FALSE;
NTSTATUS s = NtIsProcessInJob((HANDLE)-1, NULL);
return s == STATUS_PROCESS_IN_JOB;
}
int main(void) {
if (LooksSandboxed()) { return 0; } // benign exit
// ...real payload...
return 0;
}rustdecoy branch on sandbox-positive
// Cargo: windows-sys = "0.59" (Win32_Foundation, Win32_System_Threading)
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::Threading::GetCurrentProcess;
const STATUS_PROCESS_IN_JOB: i32 = 0x00000123;
extern "system" {
fn NtIsProcessInJob(Process: HANDLE, Job: HANDLE) -> i32;
}
fn in_any_job() -> bool {
unsafe { NtIsProcessInJob(GetCurrentProcess(), std::ptr::null_mut()) == STATUS_PROCESS_IN_JOB }
}
fn main() {
if in_any_job() {
// Decoy: print something boring and exit 0.
println!("hello world");
return;
}
// real_payload();
}Mappings MITRE ATT&CK
Last verified: 2026-05-20