NtQueryInformationJobObject
Liefert Accounting-, Limit- oder UI-Restriktions-Informationen eines Job-Objekts.
Prototyp
NTSTATUS NtQueryInformationJobObject( HANDLE JobHandle, JOBOBJECTINFOCLASS JobObjectInformationClass, PVOID JobObjectInformation, ULONG JobObjectInformationLength, PULONG ReturnLength );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| JobHandle | HANDLE | in | Handle auf den Job mit JOB_OBJECT_QUERY-Rechten. NULL verweist auf den Job, der den Aufruferprozess enthält. |
| JobObjectInformationClass | JOBOBJECTINFOCLASS | in | Info-Klassen-Enum. Übliche Werte: JobObjectBasicAccountingInformation = 1, JobObjectSecurityLimitInformation = 5, JobObjectExtendedLimitInformation = 9, JobObjectBasicProcessIdList = 3. |
| JobObjectInformation | PVOID | out | Vom Aufrufer bereitgestellter Puffer für die angeforderte Struktur (JOBOBJECT_BASIC_ACCOUNTING_INFORMATION, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, ...). |
| JobObjectInformationLength | ULONG | in | Größe des Puffers JobObjectInformation in Bytes. |
| ReturnLength | PULONG | out | Optional; empfängt die tatsächlich benötigte Größe (nützlich bei variabel großen Klassen wie JobObjectBasicProcessIdList). |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x131 | win10-1507 |
| Win10 1607 | 0x137 | win10-1607 |
| Win10 1703 | 0x13D | win10-1703 |
| Win10 1709 | 0x140 | win10-1709 |
| Win10 1803 | 0x142 | win10-1803 |
| Win10 1809 | 0x143 | win10-1809 |
| Win10 1903 | 0x144 | win10-1903 |
| Win10 1909 | 0x144 | win10-1909 |
| Win10 2004 | 0x14A | win10-2004 |
| Win10 20H2 | 0x14A | win10-20h2 |
| Win10 21H1 | 0x14A | win10-21h1 |
| Win10 21H2 | 0x14B | win10-21h2 |
| Win10 22H2 | 0x14B | win10-22h2 |
| Win11 21H2 | 0x151 | win11-21h2 |
| Win11 22H2 | 0x154 | win11-22h2 |
| Win11 23H2 | 0x154 | win11-23h2 |
| Win11 24H2 | 0x156 | win11-24h2 |
| Server 2016 | 0x137 | winserver-2016 |
| Server 2019 | 0x143 | winserver-2019 |
| Server 2022 | 0x150 | winserver-2022 |
| Server 2025 | 0x156 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 56 01 00 00 mov eax, 0x156 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
Undokumentierte Hinweise
Das Gegenstück zu NtSetInformationJobObject. Die SSN driftet stärker als bei NtIsProcessInJob — `0x144` auf Win10 1903 / 1909, `0x14A` auf 2004–21H1, `0x156` auf Win11 24H2 / Server 2025 — Hartkodieren über mehrere Builds ist also fragil und dynamische Auflösung im Hell's-Gate-Stil bevorzugt. Die malware-relevantesten Info-Klassen sind: `JobObjectBasicAccountingInformation` (=1) für gesamte User-/Kernel-CPU-Zeit und Prozesszählung, `JobObjectBasicProcessIdList` (=3) zur Enumeration jeder PID im Job, `JobObjectExtendedLimitInformation` (=9) für Speicher- und Aktive-Prozesse-Limits, und `JobObjectSecurityLimitInformation` (=5) für (alte, veraltete) SID-/Token-Beschränkungen.
Häufige Malware-Nutzung
Wird von sandbox-bewussten Loadern verwendet, die `NtIsProcessInJob` mit einem nachfolgenden `NtQueryInformationJobObject(JobObjectExtendedLimitInformation)` kombinieren, um die *Art* des Jobs zu fingerprinten. Verräterische Cuckoo- / Joe-Sandbox- / ANY.RUN-Signaturen sind: sehr niedriges `ActiveProcessLimit` (1–3), ein `PerJobUserTimeLimit`, das auf das Analyse-Budget gesetzt ist (typisch 60–300 Sekunden), und ein JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE-Flag — keines davon passt zum Chrome-Renderer-Job (hohes Prozesslimit, kein CPU-Zeitcap) oder zu WSL2s Job. Im Gegensatz dazu ist die Job-Prozess-Liste (`JobObjectBasicProcessIdList`) eine beliebte *Enumerations*-Primitive — sobald ein Foothold-Prozess in einem Sandbox-/Container-Job läuft, offenbart das Durchlaufen der Geschwister den Monitor, den Agent und andere zu unterdrückende Ziele.
Erkennungsmöglichkeiten
Wie NtIsProcessInJob ist dieser Aufruf alltäglich — jede Windows-Shell, jeder Browser, jede Container-Runtime liest ständig Job-Informationen. Das hochsignalbringende Muster ist die *Info-Klasse*: ein frisch abgelegter, unsignierter Prozess, der innerhalb von Sekunden nach dem Start `JobObjectExtendedLimitInformation` (=9) zu seinem enthaltenden Job abfragt und dann auf `ActiveProcessLimit` oder `PerJobUserTimeLimit` verzweigt, ist ein nahezu pathognomonischer Cuckoo-/Joe-Evasion-Fingerabdruck. ETW `Microsoft-Windows-Kernel-Process` plus User-Mode-Hooks auf `QueryInformationJobObject` in kernelbase.dll fangen den Standardweg ab; direkte Syscalls umgehen User-Mode-Hooks. Reife Sandboxes kontern, indem sie das Sample außerhalb ihres Monitor-Jobs starten und es post factum über PsSetCreateProcessNotifyRoutineEx erneut einfangen — so bleibt keine Job-Mitgliedschafts-Spur, die die Malware abfragen könnte.
Direkte Syscall-Beispiele
asmx64 stub (Win11 24H2 SSN 0x156)
; Direct syscall stub for NtQueryInformationJobObject
NtQueryInformationJobObject PROC
mov r10, rcx ; syscall convention
mov eax, 156h ; SSN (Win11 24H2 / Server 2025)
syscall
ret
NtQueryInformationJobObject ENDPcCuckoo job fingerprint
// Fingerprint the containing job: tiny ActiveProcessLimit + small wall-clock
// budget == probably a sandbox.
#include <windows.h>
#include <winnt.h>
typedef NTSTATUS (NTAPI *pNtQueryInformationJobObject)(
HANDLE, ULONG, PVOID, ULONG, PULONG);
BOOL LooksLikeCuckooJob(void) {
pNtQueryInformationJobObject NtQueryInformationJobObject =
(pNtQueryInformationJobObject)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtQueryInformationJobObject");
if (!NtQueryInformationJobObject) return FALSE;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext = {0};
NTSTATUS s = NtQueryInformationJobObject(
NULL, /* JobObjectExtendedLimitInformation */ 9,
&ext, sizeof ext, NULL);
if (s != 0) return FALSE;
BOOL tiny_active = (ext.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_ACTIVE_PROCESS)
&& ext.BasicLimitInformation.ActiveProcessLimit > 0
&& ext.BasicLimitInformation.ActiveProcessLimit < 5;
BOOL short_budget = (ext.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_JOB_TIME)
&& ext.BasicLimitInformation.PerJobUserTimeLimit.QuadPart
< 600LL * 10000000LL; // < 10 min
return tiny_active || short_budget;
}rustenumerate sibling PIDs in the same job
// Walk JobObjectBasicProcessIdList (=3) — useful for sandbox-pivot or
// container-aware enumeration.
use std::ptr;
#[repr(C)]
struct JobBasicProcessIdList {
number_of_assigned: u32,
number_of_id_in_list: u32,
process_id_list: [usize; 1], // VLA
}
extern "system" {
fn NtQueryInformationJobObject(
h: *mut core::ffi::c_void, class: u32,
info: *mut core::ffi::c_void, len: u32, ret_len: *mut u32) -> i32;
}
pub fn pids_in_my_job() -> Option<Vec<usize>> {
let mut buf = vec![0u8; 4096];
let mut needed = 0u32;
let s = unsafe { NtQueryInformationJobObject(
ptr::null_mut(), 3, buf.as_mut_ptr() as _, buf.len() as u32, &mut needed) };
if s != 0 { return None; }
let hdr = unsafe { &*(buf.as_ptr() as *const JobBasicProcessIdList) };
let n = hdr.number_of_id_in_list as usize;
let base = unsafe { (buf.as_ptr() as *const u8).add(8) as *const usize };
Some((0..n).map(|i| unsafe { *base.add(i) }).collect())
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20