Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows Kernel Driver: How to determine if thread terminated?

I have a thread that to some operations and it needs to be alive until a flag says otherwise.

I use PsCreateSystemThread to create the thread and then use ObReferenceObjectByHandle to get ETHREAD object reference to waiting for the thread to terminate before the driver unloaded using KeWaitForSingleObject.

The function that creates the thread and retrieves a reference to it:

ntStatus = PsCreateSystemThread(
    &hThread,
    (ACCESS_MASK)0, NULL,
    (HANDLE)0, NULL,
    ThreadRoutine,
    (PVOID)pThreadData
);
if (!NT_SUCCESS(ntStatus))
{
    return ntStatus;
}

ntStatus = ObReferenceObjectByHandle(
    hThread,
    THREAD_ALL_ACCESS,
    NULL,
    KernelMode,
    (PVOID*)&ptThreadObject,
    NULL
);
if (!NT_SUCCESS(ntStatus))
{
    bStopThread = TRUE;
    ptThreadObject = NULL;
    return ntStatus;
}

The thread routine:

LARGE_INTEGER liSleepTime;
while (FALSE == bThread)
{
    liSleepTime.QuadPart = 1000 * RELATIVE_MILLISECOND;
    KeDelayExecutionThread(KernelMode, FALSE, (&liSleepTime));

    ExAcquireFastMutex(&fmMutex);
    //DO SOMTHING
    ExReleaseFastMutex(&fmMutex);
}

PsTerminateSystemThread(STATUS_SUCCESS);

The unload driver function:

if (NULL != ptThreadObject)
{
    bStopThread = TRUE;

    KeWaitForSingleObject(
        (PVOID)ptThreadObject,
        Executive,
        KernelMode,
        FALSE,
        (&liTimeOut));
    ObDereferenceObject((PVOID)ptThreadObject);
    ptThreadObject= NULL;
}

I need this thread to run all the time.

Is there a way to check if the thread is terminated prematurely? (If it was done by PsTerminateSystemThread I can add a 'boolean' and set it before calling PsTerminateSystemThread to terminate the thread).

One more question:

I terminated the thread at the start of its routine and waited for 20 seconds before calling ObReferenceObjectByHandle and it didn't fail.

ntStatus = PsCreateSystemThread(
    &hThread,
    (ACCESS_MASK)0, NULL,
    (HANDLE)0, NULL,
    ThreadRoutine,
    (PVOID)pThreadData
);
if (!NT_SUCCESS(ntStatus))
{
    return ntStatus;
}
// The ThreadRoutine calling PsTerminateSystemThread first and terminate.

liSleepTime.QuadPart = 20000 * RELATIVE_MILLISECOND;
KeDelayExecutionThread(KernelMode, FALSE, (&liSleepTime));

ntStatus = ObReferenceObjectByHandle(
    hThread,
    THREAD_ALL_ACCESS,
    NULL,
    KernelMode,
    (PVOID*)&ptThreadObject,
    NULL
);
if (!NT_SUCCESS(ntStatus))
{
    bStopThread = TRUE;
    ptThreadObject = NULL;
    return ntStatus;
}

Why does ObReferenceObjectByHandle succeed and not fail? -The thread is long gone.

Thanks.

like image 645
Oriel Cochavi Avatar asked Oct 28 '22 17:10

Oriel Cochavi


1 Answers

Why does ObReferenceObjectByHandle succeed and not fail? -The thread is long gone.

but why it must fail ?? ObReferenceObjectByHandle simply return returns the corresponding pointer to the object's body. ETHREAD in your case. the state of object - not play any role here. terminated thread or not absolute unrelated. until you have handles or referenced pointers to thread body structure (ETHREAD) - the object will be not freed. so, if hThread is valid handle - ObReferenceObjectByHandle must succeed.

How to determine if thread terminated?

very easy - simply wait on it say via KeWaitForSingleObject, which you already done. because thread objects are themselves a type of dispatcher object, when thread terminated, it set to signal state and KeWaitForSingleObject return.

if (ptThreadObject)
{
    bStopThread = TRUE;

    KeWaitForSingleObject(
        ptThreadObject,
        Executive,
        KernelMode,
        FALSE,
        0);
    ObDereferenceObject(ptThreadObject);
}

note - you must set Timeout to 0 for waits indefinitely until the thread terminated (dispatcher object is set to the signaled state). also you not need cast ptThreadObject to PVOID - it is already pointer. (PVOID)ptThreadObject is not error, but superfluous and unnecessary code.

Is there a way to check if the thread is terminated prematurely?

the operation system and i not understand what you mean under prematurely. check that thread terminated we can via wait on it. but prematurely can have sense only in context of your code. say you can set different thread exit status via PsTerminateSystemThread and than (after thread terminated) get this exist status via PsGetThreadExitStatus . if thread still running the PsGetThreadExitStatus return STATUS_PENDING. this routine can be partially used to check thread state too - if it return any status different from STATUS_PENDING - thread is terminated. but if it return STATUS_PENDING - unclear - or thread still running, or thread exist via PsTerminateSystemThread(STATUS_PENDING). of course use STATUS_PENDING as exist status is bad idea and never must be used. under this condition you can determine thread state (running/terminated) with PsGetThreadExitStatus too, but this routine not wait. but your driver logic require wait when thread terminated, and only after this we can unload driver. so only KeWaitForSingleObject (or another wait function) is correct solution here. if thread can exist in different ways - use different exit status in call PsTerminateSystemThread and get it back via PsGetThreadExitStatus after thread terminated (so after KeWaitForSingleObject)

however call to PsTerminateSystemThread is optional - you can simply return from ThreadRoutine - in this case system yourself call PsTerminateSystemThread(STATUS_SUCCESS); - so in your code call PsTerminateSystemThread(STATUS_SUCCESS); also superfluous and unnecessary code. you need call PsTerminateSystemThread only in case you want return status different from STATUS_SUCCESS and check return status after thread terminated. note that windows itself not interpret and use thread exit status. it simply store it in ETHREAD object. if you not query and use this status - does not matter what its exit status.

also liSleepTime.QuadPart = 1000 * RELATIVE_MILLISECOND; line you can out from loop - set before loop, if you use constant timeout.

also we can not wait for thread termination in driver unload at all, if use special thread entry point in asm code. obviously that driver must not be unloaded, until thread is running. for this exist 2 solution - one is wait in driver unload routine, for all drivers thread termination. but exist and another - add reference to driver object, when we create thread, and de-reference driver object, when thread exit.

so define global variable:

PDRIVER_OBJECT gDriverObject;

in DriverEntry initialize it: gDriverObject = DriverObject;

and start thread in next way:

ObfReferenceObject(gDriverObject);

NTSTATUS status = PsCreateSystemThread(
    &hThread,
    0, NULL,
    0, NULL,
    ThreadRoutine,
    pThreadData
    );
if (!NT_SUCCESS(status))
{
    ObfDereferenceObject(gDriverObject);
}

and on thread exit need call ObfDereferenceObject(gDriverObject);. but after ObfDereferenceObject(gDriverObject); we already can not return to driver code - it can be already unloaded. so this call can not be done from c/c++ code. in user mode exist FreeLibraryAndExitThread, but in kernel mode no analog of this api. only solution - implement thread entry point in asm code - this entry call c/c++ thread routine and finally jmp (but not call) to ObfDereferenceObject.

define your c/c++ proc as

void NTAPI _ThreadRoutine(PVOID pv)
{
// not call PsTerminateSystemThread here !!
}

code for x64 and c (ml64 /c /Cp $(InputFileName) -> $(InputName).obj)

extern _ThreadRoutine : PROC
extern gDriverObject : QWORD
extern __imp_ObfDereferenceObject : QWORD

_TEXT segment 'CODE'

ThreadRoutine proc
    sub rsp,28h
    call _ThreadRoutine
    add rsp,28h
    mov rcx,gDriverObject
    jmp __imp_ObfDereferenceObject
ThreadRoutine endp

_TEXT ENDS

end

code for x86 and c (ml /c /Cp $(InputFileName) -> $(InputName).obj)

.686

extern _gDriverObject:DWORD
extern __imp_@ObfDereferenceObject@4:DWORD
extern __ThreadRoutine : PROC

_TEXT SEGMENT

_ThreadRoutine proc
        mov eax,[esp]
        xchg eax,[esp+4]
        mov [esp],eax
        call __ThreadRoutine
        mov ecx,_gDriverObject
        jmp __imp_@ObfDereferenceObject@4
_ThreadRoutine endp

_TEXT ENDS
END

with this you can not wait fot working thread exit - simply signal to him terminate (bStopThread = TRUE;) and return from driver unload.

like image 72
RbMm Avatar answered Nov 10 '22 21:11

RbMm