> Windows Syscalls
ntoskrnl.exeT1497.001T1497T1106

NtQueryInformationJobObject

Retrieves accounting, limits or UI-restriction information about a job object.

Prototype

NTSTATUS NtQueryInformationJobObject(
  HANDLE             JobHandle,
  JOBOBJECTINFOCLASS JobObjectInformationClass,
  PVOID              JobObjectInformation,
  ULONG              JobObjectInformationLength,
  PULONG             ReturnLength
);

Arguments

NameTypeDirDescription
JobHandleHANDLEinHandle to the job, with JOB_OBJECT_QUERY rights. NULL refers to the job that contains the calling process.
JobObjectInformationClassJOBOBJECTINFOCLASSinInfo class enum. Common values: JobObjectBasicAccountingInformation = 1, JobObjectSecurityLimitInformation = 5, JobObjectExtendedLimitInformation = 9, JobObjectBasicProcessIdList = 3.
JobObjectInformationPVOIDoutCaller-supplied buffer receiving the requested structure (JOBOBJECT_BASIC_ACCOUNTING_INFORMATION, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, ...).
JobObjectInformationLengthULONGinSize in bytes of the JobObjectInformation buffer.
ReturnLengthPULONGoutOptional; receives the size actually required (useful to size variable-length classes like JobObjectBasicProcessIdList).

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x131win10-1507
Win10 16070x137win10-1607
Win10 17030x13Dwin10-1703
Win10 17090x140win10-1709
Win10 18030x142win10-1803
Win10 18090x143win10-1809
Win10 19030x144win10-1903
Win10 19090x144win10-1909
Win10 20040x14Awin10-2004
Win10 20H20x14Awin10-20h2
Win10 21H10x14Awin10-21h1
Win10 21H20x14Bwin10-21h2
Win10 22H20x14Bwin10-22h2
Win11 21H20x151win11-21h2
Win11 22H20x154win11-22h2
Win11 23H20x154win11-23h2
Win11 24H20x156win11-24h2
Server 20160x137winserver-2016
Server 20190x143winserver-2019
Server 20220x150winserver-2022
Server 20250x156winserver-2025

Kernel module

ntoskrnl.exeNtQueryInformationJobObject

Related APIs

QueryInformationJobObjectNtSetInformationJobObjectNtIsProcessInJobNtAssignProcessToJobObjectNtCreateJobObject

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

Undocumented notes

The companion to NtSetInformationJobObject. The SSN drifts more than NtIsProcessInJob — `0x144` on Win10 1903 / 1909, `0x14A` on 2004–21H1, `0x156` on Win11 24H2 / Server 2025 — so hardcoding it across multiple builds is fragile and Hell's-Gate-style dynamic resolution is preferred. The most malware-relevant info classes are: `JobObjectBasicAccountingInformation` (=1) for total user/kernel CPU time and process counts, `JobObjectBasicProcessIdList` (=3) to enumerate every PID in the job, `JobObjectExtendedLimitInformation` (=9) for memory and active-process limits, and `JobObjectSecurityLimitInformation` (=5) for the (legacy, deprecated) SID/token restrictions.

Common malware usage

Used by sandbox-aware loaders that combine `NtIsProcessInJob` with a follow-up `NtQueryInformationJobObject(JobObjectExtendedLimitInformation)` to fingerprint the *kind* of job they're in. Telltale Cuckoo / Joe Sandbox / ANY.RUN signatures include: very low `ActiveProcessLimit` (1–3), a `PerJobUserTimeLimit` set to the analysis budget (typically 60–300 seconds), and a JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag — none of which match Chrome's renderer job (high process limit, no CPU-time cap) or WSL2's job. By contrast, the per-job process list query (`JobObjectBasicProcessIdList`) is a popular *enumeration* primitive — once a foothold process is in a sandbox / container job, walking siblings reveals the monitor, the agent, and other targets to suppress.

Detection opportunities

Like NtIsProcessInJob, this call is mainstream — every Windows shell, every browser, every container runtime reads job info constantly. The high-signal pattern is the *info class*: a fresh, unsigned process asking for `JobObjectExtendedLimitInformation` (=9) on its containing job within seconds of launch, then branching on `ActiveProcessLimit` or `PerJobUserTimeLimit`, is a near-pathognomonic Cuckoo / Joe-evasion fingerprint. ETW `Microsoft-Windows-Kernel-Process` plus user-mode hooks on `QueryInformationJobObject` in kernelbase.dll catch the standard path; direct syscalls bypass userland hooks. Mature sandboxes counter this by spawning the sample outside their monitor job and re-acquiring it post-fact via PsSetCreateProcessNotifyRoutineEx, leaving no job-membership trail for the malware to query.

Direct syscall examples

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 ENDP

cCuckoo 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