From 509d8afdf536e15d106675cd7a31dafc669fc122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herm=C3=A8s=20B=C3=A9lusca-Ma=C3=AFto?= Date: Sat, 29 Nov 2025 20:38:55 +0100 Subject: [PATCH] [NTDLL_APITEST] Add some tests for NtQueryInformationThread(ThreadHideFromDebugger) (#8486) --- .../apitests/ntdll/NtQueryInformationThread.c | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/modules/rostests/apitests/ntdll/NtQueryInformationThread.c b/modules/rostests/apitests/ntdll/NtQueryInformationThread.c index 6dafdd87e67..db0ab18a170 100644 --- a/modules/rostests/apitests/ntdll/NtQueryInformationThread.c +++ b/modules/rostests/apitests/ntdll/NtQueryInformationThread.c @@ -101,6 +101,168 @@ Test_ThreadBasicInformationClass(void) HeapFree(GetProcessHeap(), 0, ThreadInfoBasic); } +static ULONG +NTAPI +DummyThread( + _In_ PVOID Parameter) +{ + HANDLE hWaitEvent = (HANDLE)Parameter; + NTSTATUS Status; + + /* Indefinitely wait for the kill signal */ + Status = NtWaitForSingleObject(hWaitEvent, FALSE, NULL); + ASSERT(NT_SUCCESS(Status)); + + NtTerminateThread(NtCurrentThread(), Status); + return Status; +} + +static +void +Test_ThreadHideFromDebuggerClass(void) +{ + NTSTATUS Status; + HANDLE hWaitEvent = NULL, hThread = NULL; + BOOLEAN IsThreadHidden; + ULONG UlongData; + BOOL IsWow64 = FALSE; + + if (GetNTVersion() < _WIN32_WINNT_VISTA) + { + skip("Skipping test as NtQueryInformationThread(ThreadHideFromDebugger) isn't supported prior to Vista\n"); + return; + } + IsWow64Process(GetCurrentProcess(), &IsWow64); + + Status = NtCreateEvent(&hWaitEvent, + EVENT_ALL_ACCESS, + NULL, + NotificationEvent, + FALSE); + ok_ntstatus(Status, STATUS_SUCCESS); + ok(hWaitEvent != NULL, "Expected not NULL, got NULL\n"); + if (!NT_SUCCESS(Status)) + { + skip("Could not create the stop event! (Status 0x%08lx)\n", Status); + goto Quit; + } + + /* Create the dummy thread and wait for it to start up */ + Status = RtlCreateUserThread(NtCurrentProcess(), + NULL, + FALSE, + 0, + 0, + 0, + DummyThread, + (PVOID)hWaitEvent, + &hThread, + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + ok(hThread != NULL, "Expected not NULL, got NULL\n"); + if (!NT_SUCCESS(Status)) + { + skip("Failed to create the dummy thread (Status 0x%08lx)\n", Status); + goto Quit; + } + + + /* Verify that the thread is debuggable by default */ + IsThreadHidden = 0xCC; + Status = NtQueryInformationThread(hThread, + ThreadHideFromDebugger, + &IsThreadHidden, + sizeof(IsThreadHidden), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + ok_bool_false(IsThreadHidden, "IsThreadHidden is"); + + /* Make the thread hidden from the debugger -- The wrong way (1) */ + IsThreadHidden = TRUE; + Status = NtSetInformationThread(hThread, + ThreadHideFromDebugger, + &IsThreadHidden, + sizeof(IsThreadHidden)); + if (IsWow64) + ok_ntstatus(Status, STATUS_DATATYPE_MISALIGNMENT); + else + ok_ntstatus(Status, STATUS_INFO_LENGTH_MISMATCH); + + /* The thread is still debuggable */ + IsThreadHidden = 0xCC; + Status = NtQueryInformationThread(hThread, + ThreadHideFromDebugger, + &IsThreadHidden, + sizeof(IsThreadHidden), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + ok_bool_false(IsThreadHidden, "IsThreadHidden is"); + + /* Make the thread hidden from the debugger -- The wrong way (2) */ + UlongData = TRUE; + Status = NtSetInformationThread(hThread, + ThreadHideFromDebugger, + &UlongData, + sizeof(UlongData)); + ok_ntstatus(Status, STATUS_INFO_LENGTH_MISMATCH); + + /* The thread is still debuggable */ + IsThreadHidden = 0xCC; + Status = NtQueryInformationThread(hThread, + ThreadHideFromDebugger, + &IsThreadHidden, + sizeof(IsThreadHidden), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + ok_bool_false(IsThreadHidden, "IsThreadHidden is"); + + /* Make the thread hidden from the debugger -- The wrong way (3) */ + UlongData = TRUE; + Status = NtSetInformationThread(hThread, + ThreadHideFromDebugger, + &UlongData, + sizeof(BOOLEAN)); + ok_ntstatus(Status, STATUS_INFO_LENGTH_MISMATCH); + + /* The thread is still debuggable */ + IsThreadHidden = 0xCC; + Status = NtQueryInformationThread(hThread, + ThreadHideFromDebugger, + &IsThreadHidden, + sizeof(IsThreadHidden), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + ok_bool_false(IsThreadHidden, "IsThreadHidden is"); + + /* Make the thread hidden from the debugger -- This way works! */ + Status = NtSetInformationThread(hThread, + ThreadHideFromDebugger, + NULL, 0); + ok_ntstatus(Status, STATUS_SUCCESS); + + /* Verify that the thread is now hidden from the debugger */ + IsThreadHidden = 0xCC; + Status = NtQueryInformationThread(hThread, + ThreadHideFromDebugger, + &IsThreadHidden, + sizeof(IsThreadHidden), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + ok_bool_true(IsThreadHidden, "IsThreadHidden is"); + + + /* Send the kill signal and wait for the thread to terminate */ + NtSetEvent(hWaitEvent, NULL); + NtWaitForSingleObject(hThread, FALSE, NULL); + /* Close the thread */ + NtTerminateThread(hThread, STATUS_SUCCESS); + NtClose(hThread); +Quit: + /* Close the event */ + if (hWaitEvent) + NtClose(hWaitEvent); +} + static void Test_ThreadQueryAlignmentProbe(void) @@ -136,5 +298,6 @@ Test_ThreadQueryAlignmentProbe(void) START_TEST(NtQueryInformationThread) { Test_ThreadBasicInformationClass(); + Test_ThreadHideFromDebuggerClass(); Test_ThreadQueryAlignmentProbe(); }