我如何在 Windows 内核驱动程序中正确实现线程?
How do i properly implement threads in Windows Kernel Driver?
我正在尝试学习如何编写 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");
}
大部分时间都没有问题,但有时我尝试卸载驱动程序时机器会死机。当我在线程中使用某种睡眠功能时,它会更频繁地发生,所以我假设它正在崩溃,因为在驱动程序尝试卸载之前线程尚未终止。
我不太确定如何使用同步等,而且我找不到很多明确的信息。那么我如何正确实现线程并确保它们在驱动程序卸载之前终止?
线程创建后,您会得到 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 文件。此外,此代码使用信号量而不是全局标志来停止线程。
当你创建使用你的驱动程序的线程时,当然不能卸载驱动程序,直到线程不退出。为此需要在创建线程之前为您的驱动程序对象调用 ObfReferenceObject
。如果创建线程失败 - 调用 ObfDereferenceObject
。当线程退出时——需要调用 ObfDereferenceObject
。但这里有问题 - 如何/从哪里调用它?从线程例程的末尾调用 ObfDereferenceObject
没有意义 - 可以在 ObfDereferenceObject
中卸载驱动程序,我们 return 从调用到不存在的内存位置。理想情况下,如果外部代码(windows 本身)调用它,就在线程 return 之后。
寻找 IoAllocateWorkItem
的好例子。工作项 - 如线程和驱动程序不得卸载,直到 WorkerRoutine
而不是 return。这里系统关心这个 - 为此我们将 DeviceObject
传递给 IoAllocateWorkItem
: 指向调用者的驱动程序对象或调用者的设备对象之一的指针。 -系统在我们调用 IoQueueWorkItem
时引用此对象(设备或驱动程序),这是保证在 WorkerRoutine
执行期间不会卸载驱动程序。当它 return - windows 为传递的设备或驱动程序对象调用 ObfDereferenceObject
时。这里一切正常,因为在此之后我们 return 到系统内核代码(而不是驱动程序)。但不幸的是 PsCreateSystemThread
没有采用指向驱动程序对象的指针并且没有实现这样的功能。
另一个很好的例子 FreeLibraryAndExitThread
- the driver is kernel mode dll by fact, which can be loaded and unloaded. and FreeLibraryAndExitThread
完全实现了我们需要的功能,但仅适用于用户模式 dll。在内核模式下再次没有这样的 api 。
但无论如何解决方案都是可能的。可能你自己 jump (不调用)到 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 !!
}
(不要忘记 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
将在 c++ 中调用您实际的线程实现 AThread
。最后它 return 回到 _AThread
(因为你不能调用 PsTerminateSystemThread
。无论如何调用这个 api 是可选的 - 当线程例程 return 控制到系统 - 这将被自动调用)。和 _AThread
最后取消引用 g_DriverObject
和 return 到系统。
asm 文件中的主要技巧。这里有 2 个 asm for x86 和 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
我正在尝试学习如何编写 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");
}
大部分时间都没有问题,但有时我尝试卸载驱动程序时机器会死机。当我在线程中使用某种睡眠功能时,它会更频繁地发生,所以我假设它正在崩溃,因为在驱动程序尝试卸载之前线程尚未终止。
我不太确定如何使用同步等,而且我找不到很多明确的信息。那么我如何正确实现线程并确保它们在驱动程序卸载之前终止?
线程创建后,您会得到 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 文件。此外,此代码使用信号量而不是全局标志来停止线程。
当你创建使用你的驱动程序的线程时,当然不能卸载驱动程序,直到线程不退出。为此需要在创建线程之前为您的驱动程序对象调用 ObfReferenceObject
。如果创建线程失败 - 调用 ObfDereferenceObject
。当线程退出时——需要调用 ObfDereferenceObject
。但这里有问题 - 如何/从哪里调用它?从线程例程的末尾调用 ObfDereferenceObject
没有意义 - 可以在 ObfDereferenceObject
中卸载驱动程序,我们 return 从调用到不存在的内存位置。理想情况下,如果外部代码(windows 本身)调用它,就在线程 return 之后。
寻找 IoAllocateWorkItem
的好例子。工作项 - 如线程和驱动程序不得卸载,直到 WorkerRoutine
而不是 return。这里系统关心这个 - 为此我们将 DeviceObject
传递给 IoAllocateWorkItem
: 指向调用者的驱动程序对象或调用者的设备对象之一的指针。 -系统在我们调用 IoQueueWorkItem
时引用此对象(设备或驱动程序),这是保证在 WorkerRoutine
执行期间不会卸载驱动程序。当它 return - windows 为传递的设备或驱动程序对象调用 ObfDereferenceObject
时。这里一切正常,因为在此之后我们 return 到系统内核代码(而不是驱动程序)。但不幸的是 PsCreateSystemThread
没有采用指向驱动程序对象的指针并且没有实现这样的功能。
另一个很好的例子 FreeLibraryAndExitThread
- the driver is kernel mode dll by fact, which can be loaded and unloaded. and FreeLibraryAndExitThread
完全实现了我们需要的功能,但仅适用于用户模式 dll。在内核模式下再次没有这样的 api 。
但无论如何解决方案都是可能的。可能你自己 jump (不调用)到 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 !!
}
(不要忘记 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
将在 c++ 中调用您实际的线程实现 AThread
。最后它 return 回到 _AThread
(因为你不能调用 PsTerminateSystemThread
。无论如何调用这个 api 是可选的 - 当线程例程 return 控制到系统 - 这将被自动调用)。和 _AThread
最后取消引用 g_DriverObject
和 return 到系统。
asm 文件中的主要技巧。这里有 2 个 asm for x86 和 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