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
| Name | Type | Dir | Description |
|---|---|---|---|
| Handle | HANDLE | in | Abzufragendes Handle. Darf NULL sein bei ObjectTypesInformation (systemweite Liste). |
| ObjectInformationClass | OBJECT_INFORMATION_CLASS | in | 0=ObjectBasicInformation, 1=ObjectNameInformation, 2=ObjectTypeInformation, 3=ObjectTypesInformation. |
| ObjectInformation | PVOID | out | Vom Aufrufer bereitgestellter Puffer für die angeforderte Struktur. |
| ObjectInformationLength | ULONG | in | Größe des Ausgabepuffers in Bytes. |
| ReturnLength | PULONG | out | Empfängt die tatsächlich geschriebene (oder bei STATUS_INFO_LENGTH_MISMATCH erforderliche) Bytezahl. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x10 | win10-1507 |
| Win10 1607 | 0x10 | win10-1607 |
| Win10 1703 | 0x10 | win10-1703 |
| Win10 1709 | 0x10 | win10-1709 |
| Win10 1803 | 0x10 | win10-1803 |
| Win10 1809 | 0x10 | win10-1809 |
| Win10 1903 | 0x10 | win10-1903 |
| Win10 1909 | 0x10 | win10-1909 |
| Win10 2004 | 0x10 | win10-2004 |
| Win10 20H2 | 0x10 | win10-20h2 |
| Win10 21H1 | 0x10 | win10-21h1 |
| Win10 21H2 | 0x10 | win10-21h2 |
| Win10 22H2 | 0x10 | win10-22h2 |
| Win11 21H2 | 0x10 | win11-21h2 |
| Win11 22H2 | 0x10 | win11-22h2 |
| Win11 23H2 | 0x10 | win11-23h2 |
| Win11 24H2 | 0x10 | win11-24h2 |
| Server 2016 | 0x10 | winserver-2016 |
| Server 2019 | 0x10 | winserver-2019 |
| Server 2022 | 0x10 | winserver-2022 |
| Server 2025 | 0x10 | winserver-2025 |
Kernel-Modul
Verwandte APIs
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.
Erkennungsmö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 ENDPcDebugObject 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