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

问题描述 投票:1回答:2

我正在尝试学习如何编写Windows内核驱动程序。在我的驱动程序中,我有2个线程,在某些时候使用PsCreateSystemThread创建

我有一个名为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");
}

大部分时间没有问题,但有时当我尝试卸载驱动程序时机器会崩溃。当我在线程中使用某种睡眠功能时,它会更频繁地发生,因此我假设它崩溃了,因为在驱动程序尝试卸载之前线程尚未终止。

我不太确定如何使用同步等等,而且我找不到很多明确的信息。那么如何正确实现线程并确保在卸载驱动程序之前终止它们?

c windows multithreading kernel-mode
2个回答
1
投票

创建线程后,您将获得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文件。此外,此代码使用信号量而不是全局标志来停止线程。


0
投票

当你创建使用你的驱动程序的线程时,当然不能卸载驱动程序,直到线程不退出。为此,需要在创建线程之前调用ObfReferenceObject作为驱动程序对象。如果创建线程失败 - 调用ObfDereferenceObject。当线程退出时 - 需要调用ObfDereferenceObject。但这里有问题 - 如何/从哪里调用这个?从线程例程的末尾调用ObfDereferenceObject没有意义 - 驱动程序可以在ObfDereferenceObject中卸载,我们从调用返回到不存在的内存位置。理想情况下,如果外部代码(windows本身)在线程返回后调用它。

寻找IoAllocateWorkItem的好榜样。工作项 - 像线程,驱动程序不能卸载,直到WorkerRoutine不返回。在这里系统关心这个 - 为此我们将DeviceObject传递给IoAllocateWorkItem:指向调用者的驱动程序对象或调用者的一个设备对象的指针。 - 当我们调用IoQueueWorkItem时系统引用此对象(设备或驱动程序),这可以保证在WorkerRoutine执行期间不会卸载驱动程序。当它返回时 - 窗口调用ObfDereferenceObject传递设备或驱动程序对象。这一切都好,因为我们在此之后返回系统内核代码(而不是驱动程序)。但不幸的是PsCreateSystemThread没有指向驱动程序对象而没有实现这样的功能。

另一个很好的例子FreeLibraryAndExitThread - 驱动程序是事实上的内核模式dll,可以加载和卸载。和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

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

VOID _AThread(IN PVOID Context)_ASM_FUNCTION;

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

(不要忘记在__fastcall上的AThread - 对于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将在c ++中称你为实际的线程实现AThread。最后它返回_AThread(因为你不能调用PsTerminateSystemThread。无论如何称这个api是可选的 - 当线程例程将控制权返回给系统时 - 这将被自动调用)。和_AThread最后取消引用g_DriverObject并返回系统。

asm文件中的主要技巧。这里有2个asm for x86和x64:

86:

.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

64位:

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
© www.soinside.com 2019 - 2024. All rights reserved.