> Windows Syscalls
ntoskrnl.exeT1622T1003.001T1106

NtQueryObject

Liefert Metadaten zu einem Kernel-Objekt-Handle: Basisinfo, Name, Typ oder systemweite Typtabelle.

Prototyp

NTSTATUS NtQueryObject(
  HANDLE                   Handle,
  OBJECT_INFORMATION_CLASS ObjectInformationClass,
  PVOID                    ObjectInformation,
  ULONG                    ObjectInformationLength,
  PULONG                   ReturnLength
);

Argumente

NameTypeDirDescription
HandleHANDLEinAbzufragendes Handle. Darf NULL sein bei ObjectTypesInformation (systemweite Liste).
ObjectInformationClassOBJECT_INFORMATION_CLASSin0=ObjectBasicInformation, 1=ObjectNameInformation, 2=ObjectTypeInformation, 3=ObjectTypesInformation.
ObjectInformationPVOIDoutVom Aufrufer bereitgestellter Puffer für die angeforderte Struktur.
ObjectInformationLengthULONGinGröße des Ausgabepuffers in Bytes.
ReturnLengthPULONGoutEmpfängt die tatsächlich geschriebene (oder bei STATUS_INFO_LENGTH_MISMATCH erforderliche) Bytezahl.

Syscall-IDs pro Windows-Version

Windows-VersionSyscall-IDBuild
Win10 15070x10win10-1507
Win10 16070x10win10-1607
Win10 17030x10win10-1703
Win10 17090x10win10-1709
Win10 18030x10win10-1803
Win10 18090x10win10-1809
Win10 19030x10win10-1903
Win10 19090x10win10-1909
Win10 20040x10win10-2004
Win10 20H20x10win10-20h2
Win10 21H10x10win10-21h1
Win10 21H20x10win10-21h2
Win10 22H20x10win10-22h2
Win11 21H20x10win11-21h2
Win11 22H20x10win11-22h2
Win11 23H20x10win11-23h2
Win11 24H20x10win11-24h2
Server 20160x10winserver-2016
Server 20190x10winserver-2019
Server 20220x10winserver-2022
Server 20250x10winserver-2025

Kernel-Modul

ntoskrnl.exeNtQueryObject

Verwandte APIs

NtQueryObjectGetFileInformationByHandleExNtQuerySystemInformationNtDuplicateObject

Syscall-Stub

4C 8B D1            mov r10, rcx
B8 10 00 00 00      mov eax, 0x10
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

SSN `0x10` ist von 1507 bis 24H2 stabil — guter Hardcode-Kandidat. Die vier Information Classes haben sehr unterschiedliche Rollen: `ObjectBasicInformation` liefert OBJECT_BASIC_INFORMATION mit **HandleCount** und **PointerCount** (zur Erkennung von Debug-Objekten), `ObjectNameInformation` liefert den Device-Pfad (z. B. `\Device\HarddiskVolume3\foo`) — der kanonische Weg, ein Datei/Section/Key-Handle in einen Pfad zu konvertieren, `ObjectTypeInformation` liefert OBJECT_TYPE_INFORMATION für den Typ eines einzelnen Handles, und `ObjectTypesInformation` (Klasse 3) liefert OBJECT_TYPES_INFORMATION — ein gepacktes Array aller dem Kernel bekannten Objekttypen. Das gepackte Layout erfordert historisch `ALIGN_UP_BY(p + sizeof(OBJECT_TYPE_INFORMATION_V2) + name.MaximumLength, sizeof(PVOID))`-Walks; phnt fixiert die Offsets pro Build.

Häufige Malware-Nutzung

Zwei Vorzeige-Missbräuche. **Anti-Debugging via ObjectTypesInformation**: die Typtabelle nach Eintrag `DebugObject` durchgehen und `TotalNumberOfObjects` / `TotalNumberOfHandles` inspizieren — Werte ungleich null heißt: irgendwo im System existiert ein aktives Debug-Objekt (Debugger angehängt). OS-weit sichtbar, fängt also auch Geschwister-Prozess-Debugger ab. Billig, ohne Exception, und schwerer zu fälschen als PEB.BeingDebugged. **Handle-Enumeration für Credential Access**: post-2022 OPSEC-bewusste LSASS-Dumper (NanoDump, NanoDump-artige Forks, Dumpert-Varianten) iterieren `SystemHandleInformation` über NtQuerySystemInformation, rufen dann NtQueryObject(ObjectTypeInformation) auf, um alles auszublenden, was kein Process-Handle ist, und schließlich NtQueryObject(ObjectBasicInformation) + GrantedAccess, um ein bereits geöffnetes LSASS-Handle mit ausreichendem Zugriff zu finden — und das zu duplizieren, statt LSASS selbst zu öffnen.

Erkennungs­möglichkeiten

NtQueryObject(ObjectNameInformation) auf Datei-Handles ist in legitimer Software üblich (Process Explorer, AV). Starkes Signal: **Klasse 3 (ObjectTypesInformation) von einem Nicht-System- und Nicht-Debugger-Prozess aufgerufen** — es gibt fast keinen legitimen Grund, dass unprivilegierter Code die globale Typtabelle enumeriert. EDRs achten auf diese Class-Nummer im gehookten Stub. Direkter Syscall entfernt die User-Mode-Spur, hinterlässt aber ETW Microsoft-Windows-Kernel-Audit-API-Calls; Hersteller korrelieren außerdem `NtQuerySystemInformation(SystemHandleInformation)` unmittelbar gefolgt von einer Salve `NtQueryObject`-Aufrufen — der kanonische Fingerabdruck eines Handle-Table-Walks.

Direkte Syscall-Beispiele

asmx64 direct stub

; Direct syscall stub for NtQueryObject (SSN 0x10, all builds)
NtQueryObject PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 10h          ; SSN
    syscall
    ret
NtQueryObject ENDP

cDebugObject anti-debug via ObjectTypesInformation

// Walk the global object-type table looking for the "DebugObject" entry
// and report its TotalNumberOfObjects. Non-zero = a debugger exists somewhere.
#include <windows.h>
#include <winternl.h>

#define ObjectTypesInformation 3

typedef struct _OBJECT_TYPE_INFORMATION_V2 {
    UNICODE_STRING TypeName;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG TotalPagedPoolUsage;
    ULONG TotalNonPagedPoolUsage;
    ULONG TotalNamePoolUsage;
    ULONG TotalHandleTableUsage;
    ULONG HighWaterNumberOfObjects;
    // ... truncated for brevity
} OBJECT_TYPE_INFORMATION_V2;

typedef struct _OBJECT_TYPES_INFORMATION {
    ULONG NumberOfTypes;
} OBJECT_TYPES_INFORMATION;

typedef NTSTATUS (NTAPI *pNtQueryObject)(
    HANDLE, ULONG, PVOID, ULONG, PULONG);

BOOL HasDebugObjects(void) {
    pNtQueryObject NtQueryObject = (pNtQueryObject)GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtQueryObject");
    ULONG need = 0;
    NtQueryObject(NULL, ObjectTypesInformation, NULL, 0, &need);
    PBYTE buf = (PBYTE)LocalAlloc(LPTR, need + 0x1000);
    if (!NT_SUCCESS(NtQueryObject(NULL, ObjectTypesInformation,
                                  buf, need + 0x1000, &need))) return FALSE;
    // Caller responsibility: walk the packed array with proper alignment.
    // Search for TypeName == L"DebugObject" and read TotalNumberOfObjects.
    LocalFree(buf);
    return TRUE;
}

rustNameInformation: handle -> NT path

// Cargo: windows-sys = "0.59", ntapi = "0.4"
use ntapi::ntobapi::{NtQueryObject, ObjectNameInformation};
use windows_sys::Win32::Foundation::HANDLE;

pub unsafe fn handle_to_nt_path(h: HANDLE) -> Option<String> {
    let mut need: u32 = 0;
    let _ = NtQueryObject(h as _, ObjectNameInformation,
                          std::ptr::null_mut(), 0, &mut need);
    let mut buf = vec![0u8; need as usize + 0x100];
    let st = NtQueryObject(h as _, ObjectNameInformation,
                           buf.as_mut_ptr() as _, buf.len() as u32, &mut need);
    if st < 0 { return None; }
    // OBJECT_NAME_INFORMATION: UNICODE_STRING followed by buffer
    let name = &*(buf.as_ptr() as *const ntapi::ntobapi::OBJECT_NAME_INFORMATION);
    let slice = std::slice::from_raw_parts(
        name.Name.Buffer, (name.Name.Length / 2) as usize);
    Some(String::from_utf16_lossy(slice))
}

MITRE ATT&CK-Mappings

Last verified: 2026-05-20