我正在尝试学习如何对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");
}
大多数时候没有问题,但是有时当我尝试卸载驱动程序时,机器将崩溃。当我在线程中使用某种睡眠函数时,它会更频繁地发生,因此我假设它崩溃了,因为在驱动程序尝试卸载之前线程尚未终止。
我不太确定如何使用同步等等,而且我找不到很多清晰的信息。那么,如何正确实现线程并确保在卸载驱动程序之前将其终止?
答案 0 :(得分: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文件。此外,此代码使用信号量而不是全局标志来停止线程。
答案 1 :(得分:0)
当创建使用驱动程序的线程时,当然必须卸载驱动程序,直到线程不退出。为此,需要在创建线程之前为驱动程序对象调用ObfReferenceObject
。如果创建线程失败-调用ObfDereferenceObject
。当线程退出时-需要调用ObfDereferenceObject
。但这是问题-如何/从何处调用此方法?从线程例程末尾调用ObfDereferenceObject
毫无意义-可以在ObfDereferenceObject
内卸载驱动程序,然后我们从调用返回到不存在的内存位置。理想情况是在线程返回之后,如果外部代码(Windows本身)调用此函数。
寻找IoAllocateWorkItem
就是一个很好的例子。工作项-如线程,并且驱动程序不得卸载,直到WorkerRoutine
不返回为止。而系统对此很在意-为此,我们将DeviceObject
传递给IoAllocateWorkItem
:指向调用者的驱动程序对象或调用者的设备对象之一的指针。-系统引用了此当我们调用IoQueueWorkItem
时对象(设备或驱动程序),这可以确保在执行WorkerRoutine
时不会卸载驱动程序。返回时-Windows为传递的设备或驱动程序对象调用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文件中的主要技巧。这里是x86和x64的2个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