A system thread created with PsCreateSystemThread can be terminated by the
thread calling PsTerminateSystemThread that takes a parameter ExitStatus.
The DDK documentation says that the ExitStatus specifies the status of the
terminating thread to the thread creator, it is undocumented how to get
the exit status but this can be done using the function
ZwQueryInformationThread.
It is common to save a pointer to the thread object using ObReferenceObject
ByHandle and later wait for termination using KeWaitForSingleObject. Since
ZwQueryInformationThread wants a handle one would in this case use ObOpen
ObjectByPointer to get a handle from the pointer when its time to query
the exit status however it is also possible to save a handle to the thread
and wait on it using ZwWaitForSingleObject where after the handle can be
used with ZwQueryInformationThread, both these methods is demonstrated
below.
If a thread returns from its main function without calling
PsTerminateSystemThread it will mean an implicit exit status of 0.
The function ZwQueryInformationThread is only exported to drivers on
Windows XP and later so on older versions of Windows must it be called
by its system service number, see below on how to do this.
Below is a simple driver to demonstrate getting the exit status of a
system thread, the debug prints looks like the following:
ThreadTest: DriverEntry
ThreadTest: threadFunc called with argument 0x1234 and terminating with
exit status 0x1235
ThreadTest: waiting for thread handle = 0xb5c and got exit status = 0x1235
ThreadTest: threadFunc called with argument 0x5678 and terminating with
exit status 0x5679
ThreadTest: waiting for thread pointer = 0x81b0eda8 and got exit status =
0x5679
ThreadTest: DriverEntry done
*/
/*
* Driver to demonstrate getting the exit status of a system thread.
*/
#include <ntddk.h>
#include <ntverp.h>
/*
* ObOpenObjectByPointer creates a handle to an object that one has a
* pointer to. Note that for files is the handle not fully usable if
* all other handles to the same file object was closed in between.
*/
NTKERNELAPI
NTSTATUS
ObOpenObjectByPointer (
IN PVOID Object,
IN ULONG HandleAttributes,
IN PACCESS_STATE PassedAccessState OPTIONAL,
IN ACCESS_MASK DesiredAccess OPTIONAL,
IN POBJECT_TYPE ObjectType OPTIONAL,
IN KPROCESSOR_MODE AccessMode,
OUT PHANDLE Handle
);
/*
* ZwWaitForSingleObject is similar to KeWaitForSingleObject but waits
* on a handle instead of a pointer. Internally it uses ObReferenceObject
* ByHandle, KeWaitForSingleObject and ObDereferenceObject.
*/
NTSYSAPI
NTSTATUS
NTAPI
ZwWaitForSingleObject (
IN HANDLE Handle,
IN BOOLEAN Alertable,
IN PLARGE_INTEGER Timeout OPTIONAL
);
/*
* ZwQueryInformationThread querys different kind of information on
* a thread, for example thread basic information that contains its
* exit status if it has terminated.
*/
#if (VER_PRODUCTBUILD >= 2600)
/*
* ZwQueryInformationThread is exported to drivers on Windows XP and later
* versions of Windows.
*/
NTSYSAPI
NTSTATUS
NTAPI
ZwQueryInformationThread (
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
#elif (VER_PRODUCTBUILD >= 2195)
/*
* On older versions of Windows can ZwQueryInformationThread be called
* if one knows its system service number, on Windows 2000 it is 87h.
* Note that it could change between service packs.
*/
__declspec(naked)
NTSTATUS
NTAPI
ZwQueryInformationThread (
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL
)
{
__asm
{
mov eax, 87h
lea edx, [esp+4]
int 2eh
ret 14h
}
}
#elif (VER_PRODUCTBUILD >= 1381)
/*
* On Windows NT 4.0 is the system service number for
* ZwQueryInformationThread 6eh.
* Note that it could change between service packs.
*/
__declspec(naked)
NTSTATUS
NTAPI
ZwQueryInformationThread (
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL
)
{
__asm
{
mov eax, 6eh
lea edx, [esp+4]
int 2eh
ret 14h
}
}
#else
NTSYSAPI
NTSTATUS
NTAPI
ZwQueryInformationThread (
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL
)
{
return STATUS_NOT_IMPLEMENTED;
}
#endif
typedef struct _THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PVOID TebBaseAddress;
ULONG UniqueProcessId;
ULONG UniqueThreadId;
KAFFINITY AffinityMask;
KPRIORITY BasePriority;
ULONG DiffProcessPriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
/*
* Creates a thread and optionally save a handle and/or a pointer to
* the thread object.
*/
NTSTATUS
createThread (
OUT HANDLE *threadHandle OPTIONAL,
OUT PVOID *threadPointer OPTIONAL,
IN PVOID threadFunction,
IN PVOID threadArgument
)
{
NTSTATUS status;
HANDLE hThread;
ASSERT(threadFunction != NULL);
status = PsCreateSystemThread(
&hThread,
(ACCESS_MASK) 0L,
NULL,
NULL,
NULL,
threadFunction,
threadArgument
);
if (!NT_SUCCESS(status))
{
return status;
}
if (threadPointer != NULL)
{
status = ObReferenceObjectByHandle(
hThread,
THREAD_ALL_ACCESS,
NULL,
KernelMode,
threadPointer,
NULL
);
if (!NT_SUCCESS(status))
{
ZwClose(hThread);
return status;
}
}
if (threadHandle != NULL)
{
*threadHandle = hThread;
}
else
{
ZwClose(hThread);
}
return STATUS_SUCCESS;
}
/*
* Wait on a thread handle for the thread to terminate and then query
* the exit status.
*/
NTSTATUS
waitForThreadHandle (
IN HANDLE threadHandle
)
{
NTSTATUS status;
THREAD_BASIC_INFORMATION threadInfo;
status = ZwWaitForSingleObject(
threadHandle,
FALSE,
NULL
);
if (!NT_SUCCESS(status))
{
return status;
}
status = ZwQueryInformationThread(
threadHandle,
ThreadBasicInformation,
&threadInfo,
sizeof(threadInfo),
NULL
);
if (NT_SUCCESS(status))
{
status = threadInfo.ExitStatus;
}
ZwClose(threadHandle);
return status;
}
/*
* Wait on a thread pointer for the thread to terminate and then query
* the exit status.
*/
NTSTATUS
waitForThreadPointer (
IN PVOID threadPointer
)
{
NTSTATUS status;
HANDLE threadHandle;
THREAD_BASIC_INFORMATION threadInfo;
ASSERT(threadPointer != NULL);
status = KeWaitForSingleObject(
threadPointer,
Executive,
KernelMode,
FALSE,
NULL
);
if (!NT_SUCCESS(status))
{
return status;
}
status = ObOpenObjectByPointer(
threadPointer,
0,
NULL,
(ACCESS_MASK) 0L,
NULL,
KernelMode,
&threadHandle
);
if (NT_SUCCESS(status))
{
status = ZwQueryInformationThread(
threadHandle,
ThreadBasicInformation,
&threadInfo,
sizeof(threadInfo),
NULL
);
if (NT_SUCCESS(status))
{
status = threadInfo.ExitStatus;
}
ZwClose(threadHandle);
}
ObDereferenceObject(threadPointer);
return status;
}
void threadFunc(int threadArg)
{
DbgPrint("ThreadTest: threadFunc called with argument %#x and
terminating with exit status %#x\n", threadArg, threadArg + 1);
PsTerminateSystemThread(threadArg + 1);
}
PDEVICE_OBJECT devObj;
VOID
DriverUnload(
IN PDRIVER_OBJECT DriverObject
)
{
DbgPrint("ThreadTest: DriverUnload\n");
IoDeleteDevice(devObj);
DbgPrint("ThreadTest: DriverUnload done\n");
}
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
UNICODE_STRING devName;
NTSTATUS status;
HANDLE threadHandle;
PVOID threadPointer;
DbgPrint("ThreadTest: DriverEntry\n");
RtlInitUnicodeString(&devName, L"\\Device\\ThreadTest");
status = IoCreateDevice(
DriverObject,
0,
&devName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&devObj
);
if (!NT_SUCCESS(status)) {
DbgPrint("ThreadTest: IoCreateDevice error: %#x\n", status);
return STATUS_UNSUCCESSFUL;
}
DriverObject->DriverUnload = DriverUnload;
createThread(&threadHandle, NULL, threadFunc, (void *) 0x1234);
createThread(NULL, &threadPointer, threadFunc, (void *) 0x5678);
status = waitForThreadHandle(threadHandle);
DbgPrint("ThreadTest: waiting for thread handle = %#x and got exit
status = %#x\n", threadHandle, status);
status = waitForThreadPointer(threadPointer);
DbgPrint("ThreadTest: waiting for thread pointer = %#x and got exit
status = %#x\n", threadPointer, status);
DbgPrint("ThreadTest: DriverEntry done\n");
return STATUS_SUCCESS;
}