4

I am trying to learn how to code windows kernel drivers. In my driver i have 2 threads which are created at some point with PsCreateSystemThread

I have a global variable called Kill which signals the threads to terminate like this.

VOID AThread(IN PVOID Context)
{
    for (;;)
    {
        if(Kill == True)
            break;

        KmWriteProcessMemory(rProcess, &NewValue, dwAAddr, sizeof(NewValue));
    }


    PsTerminateSystemThread(STATUS_SUCCESS);
}

In my unload function i am setting Kill = TRUE

VOID f_DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    Kill = TRUE;
    IoDeleteSymbolicLink(&SymLinkName);
    IoDeleteDevice(pDeviceObject);
    DbgPrint("Driver Unloaded successfully..\r\n");
}

Most of the time there's no problem, but sometimes the machine will crash when i try to unload the driver. It happens more frequently when i have some kind of sleep function being used in the threads, so i'm assuming it's crashing because the threads have not yet terminated before the driver tries to unload.

I'm not too sure how to use synchronisation and such, and there's not a lot of clear information out there that i can find. So how do i properly implement threads and ensure they're terminated before the driver is unloaded?

Michael Strobel
  • 367
  • 5
  • 16

2 Answers2

6

Once the thread is created, you have HANDLE threadHandle result. Then you need to convert this handle to PETHREAD ThreadObject; :

ObReferenceObjectByHandle(threadHandle,
                          THREAD_ALL_ACCESS,
                          NULL,
                          KernelMode,
                          &ThreadObject,
                          NULL );

and close threadHandle:

ZwClose(threadHandle);

When you want to stop the thread, set the flag and wait for thread completion:

Kill = TRUE;

KeWaitForSingleObject(ThreadObject,
                    Executive,
                    KernelMode,
                    FALSE,
                    NULL );

ObDereferenceObject(ThreadObject);

Then f_DriverUnload function may exit.

You can see all this stuff here: https://github.com/Microsoft/Windows-driver-samples/tree/master/general/cancel/sys

See cancel.h and cancel.c files. Additionally, this code uses semaphore instead of global flag to stop the thread.

Alex F
  • 42,307
  • 41
  • 144
  • 212
0

when you create thread which used your driver, the driver of course must not be unloaded, until thread not exit. for do this need call ObfReferenceObject for your driver object, before create thread. if create thread fail - call ObfDereferenceObject. and when thread exit - need call ObfDereferenceObject. but here is problem - how / from where call this ? call ObfDereferenceObject from the end of thread routine no sense - the driver can be unloaded inside ObfDereferenceObject and we return from call to not existing memory place. ideally will be if external code (windows itself) call this, just after thread return.

look for IoAllocateWorkItem for good example. work item - like thread, and driver must not be unloaded, until WorkerRoutine not return. and here system care about this - for this we pass DeviceObject to IoAllocateWorkItem: Pointer to the caller's driver object or to one of the caller's device objects. - the system reference this object (device or driver) when we call IoQueueWorkItem and this is guarantee that driver will be not unloaded during WorkerRoutine execution. when it return - windows call ObfDereferenceObject for passed device or driver object. and here all ok, because we return to system kernel code (not to driver) after this. but unfortunately PsCreateSystemThread not take pointer to driver object and not implement such functional.

another good example FreeLibraryAndExitThread - the driver is kernel mode dll by fact, which can be loaded and unloaded. and FreeLibraryAndExitThread exactly implement functional which we need, but for user mode dlls only. again no such api in kernel mode.

but anyway solution is possible. possible yourself jump (not call) to ObfDereferenceObject at the end of thread execution, but for this need use assembler code. not possible do this trick in c/c++.

first of all let declare pointer to driver object in global variable - we initialize it to valid value in driver entry point.

extern "C" PVOID g_DriverObject;

than some macros for get mangled c++ names, this need for use it in asm file:

#if 0
#define __ASM_FUNCTION __pragma(message(__FUNCDNAME__" proc\r\n" __FUNCDNAME__ " endp"))
#define _ASM_FUNCTION {__ASM_FUNCTION;}
#define ASM_FUNCTION {__ASM_FUNCTION;return 0;}
#define CPP_FUNCTION __pragma(message("extern " __FUNCDNAME__ " : PROC ; "  __FUNCSIG__))
#else
#define _ASM_FUNCTION
#define ASM_FUNCTION
#define CPP_FUNCTION
#endif

in c++ we declare 2 functions for thread:

VOID _AThread(IN PVOID Context)_ASM_FUNCTION;

VOID __fastcall AThread(IN PVOID Context)
{
    CPP_FUNCTION;
    // some code here
    // but not call PsTerminateSystemThread !!
}

(don't forget __fastcall on AThread - for x86 this need)

now we create thread with next code:

    ObfReferenceObject(g_DriverObject);
    HANDLE hThread;
    if (0 > PsCreateSystemThread(&hThread, 0, 0, 0, 0, _AThread, ctx)) 
    {
        ObfDereferenceObject(g_DriverObject);
    }
    else
    {
        NtClose(hThread);
    }

so you set thread entry point to _AThread which will be implemented in asm file. at begin you call ObfReferenceObject(g_DriverObject);. the _AThread will call you actual thread implementation AThread in c++. finally it return back to _AThread (because this you must not call PsTerminateSystemThread. anyway call this api is optional at all - when thread routine return control to system - this will be auto called). and _AThread at the end de-reference g_DriverObject and return to system.

so main trick in asm files. here 2 asm for x86 and x64:

x86:

.686p

extern _g_DriverObject:DWORD
extern __imp_@ObfDereferenceObject@4:DWORD
extern ?AThread@@YIXPAX@Z : PROC ; void __fastcall AThread(void *)

_TEXT segment

?_AThread@@YGXPAX@Z proc
        pop ecx
        xchg ecx,[esp]
        call ?AThread@@YIXPAX@Z
        mov ecx,_g_DriverObject
        jmp __imp_@ObfDereferenceObject@4
?_AThread@@YGXPAX@Z endp

_TEXT ends

END

x64:

extern g_DriverObject:QWORD
extern __imp_ObfDereferenceObject:QWORD
extern ?AThread@@YAXPEAX@Z : PROC ; void __cdecl AThread(void *)

_TEXT segment 'CODE'

?_AThread@@YAXPEAX@Z proc
    sub rsp,28h
    call ?AThread@@YAXPEAX@Z
    add rsp,28h
    mov rcx,g_DriverObject
    jmp __imp_ObfDereferenceObject
?_AThread@@YAXPEAX@Z endp

_TEXT ENDS

END
RbMm
  • 31,280
  • 3
  • 35
  • 56