如何在Windows内核驱动程序中正确实现线程?

4
我希望能学习编写Windows内核驱动程序。 在我的驱动程序中,我使用PsCreateSystemThread创建了2个线程。
我有一个名为Kill的全局变量,用于向线程发送终止信号。
VOID AThread(IN PVOID Context)
{
    for (;;)
    {
        if(Kill == True)
            break;

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


    PsTerminateSystemThread(STATUS_SUCCESS);
}

在我的卸载函数中,我设置了 Kill = TRUE
VOID f_DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    Kill = TRUE;
    IoDeleteSymbolicLink(&SymLinkName);
    IoDeleteDevice(pDeviceObject);
    DbgPrint("Driver Unloaded successfully..\r\n");
}

大部分情况下没有问题,但有时候在我试图卸载驱动程序时机器会崩溃。当线程中使用了某种睡眠函数时,这种情况发生得更加频繁,因此我认为它崩溃是因为线程在驱动程序尝试卸载之前尚未终止。
我不太确定如何使用同步等技术,也找不到很清晰的信息。那么如何正确地实现线程,并确保它们在驱动程序卸载之前被终止?
2个回答

6

创建线程后,您会得到一个 HANDLE threadHandle 结果。然后需要将此句柄转换为 PETHREAD ThreadObject;

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

关闭 threadHandle

ZwClose(threadHandle);

当您想要停止线程时,请设置标志并等待线程完成:
Kill = TRUE;

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

ObDereferenceObject(ThreadObject);

然后f_DriverUnload函数可能会退出。

你可以在这里看到所有的内容:https://github.com/Microsoft/Windows-driver-samples/tree/master/general/cancel/sys

请查看cancel.h和cancel.c文件。此外,此代码使用信号量而不是全局标志停止线程。


@MichaelStrobel - 存在更好的解决方案。 - RbMm
可以详细说明一下吗? - Michael Strobel

0

当您创建使用驱动程序的线程时,驱动程序在线程退出之前必须不被卸载。要做到这一点,需要在创建线程之前调用ObfReferenceObject以引用您的驱动程序对象。如果创建线程失败,则需要调用ObfDereferenceObject。当线程退出时,需要调用ObfDereferenceObject。但是这里有一个问题——如何/从哪里调用?从线程例程的末尾调用ObfDereferenceObject没有意义——驱动程序可能会在ObfDereferenceObject内部卸载,并且我们会从调用返回到不存在的内存位置。理想情况下,外部代码(Windows本身)会在线程返回后立即调用此函数。

寻找{{link1:IoAllocateWorkItem}}以获取良好的示例。工作项类似于线程,驱动程序必须在WorkerRoutine返回之前不被卸载。系统会关心这一点-因此我们将DeviceObject传递给IoAllocateWorkItem指向调用者的驱动程序对象或调用者设备对象之一的指针。 -当我们调用IoQueueWorkItem时,系统会引用此对象(设备或驱动程序),并保证在WorkerRoutine执行期间不会卸载驱动程序。当它返回时- Windows为传递的设备或驱动程序对象调用ObfDereferenceObject。在此之后,我们返回到系统内核代码(而不是驱动程序)。但不幸的是,PsCreateSystemThread不接受驱动程序对象的指针,并且没有实现这样的功能。

另一个很好的例子{{link1:FreeLibraryAndExitThread}} - 事实上,驱动程序是内核模式dll,可以加载和卸载。 {{link1:FreeLibraryAndExitThread}} 正好实现了我们需要的功能,但仅适用于用户模式dll。 再次强调,在内核模式中没有这样的api。

但无论如何,解决方案是可能的。 可以自己跳转(而不是调用)到线程执行结束时的ObfDereferenceObject,但需要使用汇编代码。 不可能在c / c ++中进行此技巧。

首先,让我们在全局变量中声明指向驱动程序对象的指针 - 我们在驱动程序入口点中将其初始化为有效值。

extern "C" PVOID g_DriverObject;

除了一些用于获取混淆的c++名称的宏之外,这需要在asm文件中使用:

#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

在中,我们为线程声明了2个函数:

VOID _AThread(IN PVOID Context)_ASM_FUNCTION;

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

不要忘记在AThread上使用__fastcall-对于x86来说这是必需的

现在我们使用以下代码创建线程:

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

所以你将线程入口点设置为_AThread,该入口点将在asm文件中实现。在开始时,你调用ObfReferenceObject(g_DriverObject);_AThread将调用你的实际线程实现AThread(使用c++编写)。最后它返回到_AThread(因此你不能调用PsTerminateSystemThread)。无论如何,调用这个API是可选的 - 当线程例程将控制权返回给系统时,它将自动调用此函数。最后,_AThread取消引用g_DriverObject并返回给系统。

所以主要的技巧在于asm文件。这里有两个x86和x64的asm文件:

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

看起来很混乱。你能给一个好的IoAllocateWorkItem的例子吗? - Mecanik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接