NtQueryInformationThread
Liest eine Thread-Eigenschaft über die THREADINFOCLASS-Enumeration — TEB-Pointer, Hide-from-Debugger-Flag, Zeiten, Exit-Status.
Prototyp
NTSTATUS NtQueryInformationThread( HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadHandle | HANDLE | in | Handle auf den Ziel-Thread. NtCurrentThread() ((HANDLE)-2) liest vom Aufrufer. |
| ThreadInformationClass | THREADINFOCLASS | in | Enum, das die Eigenschaft auswählt. Üblich: ThreadBasicInformation (0) → TEB, ThreadTimes (1), ThreadIsTerminated (14), ThreadHideFromDebugger (17/0x11), ThreadBreakOnTermination (18). |
| ThreadInformation | PVOID | out | Vom Aufrufer bereitgestellter Puffer für den Wert. Struktur richtet sich nach der Klasse. |
| ThreadInformationLength | ULONG | in | Größe von ThreadInformation in Bytes. STATUS_INFO_LENGTH_MISMATCH bei zu klein. |
| ReturnLength | PULONG | out | Optional. Empfängt die Anzahl tatsächlich geschriebener Bytes. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x25 | win10-1507 |
| Win10 1607 | 0x25 | win10-1607 |
| Win10 1703 | 0x25 | win10-1703 |
| Win10 1709 | 0x25 | win10-1709 |
| Win10 1803 | 0x25 | win10-1803 |
| Win10 1809 | 0x25 | win10-1809 |
| Win10 1903 | 0x25 | win10-1903 |
| Win10 1909 | 0x25 | win10-1909 |
| Win10 2004 | 0x25 | win10-2004 |
| Win10 20H2 | 0x25 | win10-20h2 |
| Win10 21H1 | 0x25 | win10-21h1 |
| Win10 21H2 | 0x25 | win10-21h2 |
| Win10 22H2 | 0x25 | win10-22h2 |
| Win11 21H2 | 0x25 | win11-21h2 |
| Win11 22H2 | 0x25 | win11-22h2 |
| Win11 23H2 | 0x25 | win11-23h2 |
| Win11 24H2 | 0x25 | win11-24h2 |
| Server 2016 | 0x25 | winserver-2016 |
| Server 2019 | 0x25 | winserver-2019 |
| Server 2022 | 0x25 | winserver-2022 |
| Server 2025 | 0x25 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 25 00 00 00 mov eax, 0x25 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 `0x25` seit Windows 10 1507 — ein bemerkenswert stabiler Anker. Zwei Klassen dominieren den offensiven Einsatz. ThreadBasicInformation (0) liefert ein `THREAD_BASIC_INFORMATION`, dessen `TebBaseAddress` den TEB des Ziel-Threads offenlegt — der kanonische Weg, PEB / Modulliste abzuleiten, ohne GS-relative Reads zu nutzen (z. B. wenn EAF — Export Address Filter — im Ziel aktiv ist). ThreadHideFromDebugger (17) liefert ein BYTE, das anzeigt, ob das Hide-Bit gesetzt ist; diese Rückseite ist das Herz des *Anti-Anti-Debug-Checks* — ist das Hide-Flag plötzlich 0, hat ein EDR oder ein Analystentool den Set stillschweigend entfernt. ThreadIsTerminated (14) wird in Sleep-Mask-Schleifen genutzt, um auf einen Shutdown-Befehl früh aufzuwachen.
Häufige Malware-Nutzung
Fünf praktische Rezepte. (1) EAF-Bypass: TEB via ThreadBasicInformation lesen statt `gs:[0x30]`, um den Hardware-Breakpoint von Microsofts EAF-Mitigation auf den PEB-Zugriff nicht auszulösen. (2) Anti-Anti-Debug: ThreadHideFromDebugger setzen, dann sofort abfragen — kommt 0 zurück, schluckt ein instrumentiertes ntdll das Set still. (3) Sleep-Mask-Wake: ThreadIsTerminated auf einem Geschwister-Thread pollen, um Shutdown ohne Event-Objekt zu koordinieren. (4) ThreadStartAddress-Probe (Klasse 9), um andere Threads zu durchlaufen und Injection-Ziele zu wählen, die wie legitime Worker (RPC, RuntimeBroker) aussehen. (5) EPROCESS via TEB → PEB → LDR durchlaufen, um Module zu enumerieren, ohne kernel32!GetModuleHandle anzufassen.
Erkennungsmöglichkeiten
Es gibt kein Per-Call-ETW-Signal für Queries (im Unterschied zu NtSetInformationThread mit ThreadHideFromDebugger, das geloggt wird). Detection muss auf Seiteneffekte zielen: Enumerationen, die jeden Thread in Remote-Prozessen anfassen, oder Sequenzen aus NtSetInformationThread(17) gefolgt *unmittelbar* von NtQueryInformationThread(17) am selben Handle innerhalb von Mikrosekunden — typisches Anti-Anti-Debug-Muster. Defenders ASR / Network Protection decken das nicht ab; sauberstes Signal: EDR-Hook auf ntdll!NtQueryInformationThread, gefiltert auf ThreadInformationClass == 0 oder == 0x11 von Aufrufern außerhalb der System-DLLs.
Direkte Syscall-Beispiele
cAnti-anti-debug round-trip on ThreadHideFromDebugger
// Set the hide flag, then query it back. If the readback returns 0,
// an EDR or analysis tool is silently neutralizing NtSetInformationThread —
// strong signal we're being analyzed.
typedef NTSTATUS(NTAPI* fnSet)(HANDLE, ULONG, PVOID, ULONG);
typedef NTSTATUS(NTAPI* fnQuery)(HANDLE, ULONG, PVOID, ULONG, PULONG);
BOOL HideFlagSurvived(void) {
HMODULE n = GetModuleHandleA("ntdll.dll");
fnSet pSet = (fnSet)GetProcAddress(n, "NtSetInformationThread");
fnQuery pQuery = (fnQuery)GetProcAddress(n, "NtQueryInformationThread");
pSet((HANDLE)-2, 0x11 /* ThreadHideFromDebugger */, NULL, 0);
BOOLEAN hidden = FALSE;
ULONG rl = 0;
pQuery((HANDLE)-2, 0x11, &hidden, sizeof(hidden), &rl);
return hidden != 0;
}asmx64 direct stub
; SSN 0x25 across all modern builds.
NtQueryInformationThread PROC
mov r10, rcx
mov eax, 25h
syscall
ret
NtQueryInformationThread ENDPrustTEB pointer extraction for EAF-safe walking
// Cargo: windows-sys = "0.59"
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
#[repr(C)]
struct ThreadBasicInformation {
exit_status: i32,
teb_base_address: *mut u8,
client_id: [usize; 2],
affinity_mask: usize,
priority: i32,
base_priority: i32,
}
type NtQueryInformationThread = unsafe extern "system" fn(
thread: isize, class: u32, info: *mut u8, len: u32, ret_len: *mut u32,
) -> i32;
pub unsafe fn get_self_teb() -> Option<*mut u8> {
let n = GetModuleHandleA(b"ntdll.dll\0".as_ptr());
let addr = GetProcAddress(n, b"NtQueryInformationThread\0".as_ptr())?;
let f: NtQueryInformationThread = std::mem::transmute(addr);
let mut tbi = ThreadBasicInformation {
exit_status: 0, teb_base_address: core::ptr::null_mut(),
client_id: [0; 2], affinity_mask: 0, priority: 0, base_priority: 0,
};
let mut rl = 0u32;
if f(-2, 0 /* ThreadBasicInformation */, &mut tbi as *mut _ as *mut u8,
core::mem::size_of::<ThreadBasicInformation>() as u32, &mut rl) == 0 {
Some(tbi.teb_base_address)
} else { None }
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20