前段时间我某次逆向一个程序,发现有一个处于无敌的进程组件用火绒剑都杀不死,进程一直悬挂在进程列表,这意味着内核ZwTerminateProcess都无法杀死这个进程,当时立刻吓我一跳,第一时间我判断能做到这种事情的只有rootkit和bookit技术,难道我这种老司机还能翻车被恶意软件感染了,当时立刻重装系统,并且启用了secureboot,不过现在我找到了答案,让我们一探究竟。
复现场景
notmyfault64.exe可以非常适合复现这个场景,读者可以这里下载, 运行即可获得一个无法杀死的进程:

点击huang,然后尝试使用火绒剑杀死这个进程:

这确实很反常识,内核居然有杀不死的进程:

我快速逆向发现这个工具的驱动使用了KeWaitForSingleObject,这意味着线程一直在内核模式等待一个无法发生的事件,非常有意思。
进一步测试
这玩意有没有潜力用于恶意软件开发呢?我编写了如下驱动代码测试:
#include <ntddk.h>
//#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)
#define IOCTL_Device_Usermode CTL_CODE(FILE_DEVICE_UNKNOWN, 0x008, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_Device_Usermode_APC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x009, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_Device_Kernelmode CTL_CODE(FILE_DEVICE_UNKNOWN, 0x010, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_Device_Kernelmode_APC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x020, METHOD_BUFFERED, FILE_ANY_ACCESS)
void unloadriver(PDRIVER_OBJECT driver_object) {
UNREFERENCED_PARAMETER(driver_object);
}
NTSTATUS dispatch_function(
_In_ struct _DEVICE_OBJECT* DeviceObject,
_Inout_ struct _IRP* Irp
) {
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
DbgPrint("Good to in the dispatch ruteam");
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
KEVENT event = { 0 };
switch (stack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_Device_Usermode:
KeInitializeEvent(&event, SynchronizationEvent, 0);
KeWaitForSingleObject(&event, UserRequest, UserMode, 0, nullptr);
break;
case IOCTL_Device_Usermode_APC:
KeInitializeEvent(&event, SynchronizationEvent, 0);
KeWaitForSingleObject(&event, UserRequest, UserMode, 1, nullptr);
break;
case IOCTL_Device_Kernelmode:
KeInitializeEvent(&event, SynchronizationEvent, 0);
KeWaitForSingleObject(&event, UserRequest, KernelMode, 0, nullptr);
break;
case IOCTL_Device_Kernelmode_APC:
KeInitializeEvent(&event, SynchronizationEvent, 0);
KeWaitForSingleObject(&event, UserRequest, KernelMode, 1, nullptr);
break;
default:
DbgPrint("no macth any ioctl");
return -1;
}
IofCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS default_dispatch(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
extern "C"
NTSTATUS DriverEntry(PDRIVER_OBJECT object , PUNICODE_STRING Regstring) {
UNREFERENCED_PARAMETER(Regstring);
UNICODE_STRING devname;
RtlInitUnicodeString(&devname, L"\\Device\\killmyifyoucan");
PDEVICE_OBJECT deviceobject;
IoCreateDevice(object, 0, &devname, FILE_DEVICE_UNKNOWN, 0, 1, &deviceobject);
UNICODE_STRING symlink;
RtlInitUnicodeString(&symlink,L"\\DosDevices\\killmeifyoucan");
IoCreateSymbolicLink(&symlink,&devname);
object->DriverUnload = unloadriver;
object->MajorFunction[IRP_MJ_CREATE] = default_dispatch;
object->MajorFunction[IRP_MJ_CLOSE] = default_dispatch;
object->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dispatch_function;
return 0;
}
客户端:
#include <Windows.h>
#include <stdio.h>
#define IOCTL_Device_Usermode CTL_CODE(FILE_DEVICE_UNKNOWN, 0x008, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_Device_Usermode_APC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x009, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_Device_Kernelmode CTL_CODE(FILE_DEVICE_UNKNOWN, 0x010, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_Device_Kernelmode_APC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x020, METHOD_BUFFERED, FILE_ANY_ACCESS)
int main()
{
HANDLE hDevice = CreateFileW(
L"\\\\.\\killmeifyoucan",
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("CreateFile failed: %lu\n", GetLastError());
getchar();
return 1;
}
printf("Device opened.\n");
DWORD bytesReturned = 0;
BOOL ret = DeviceIoControl(
hDevice,
IOCTL_Device_Kernelmode_APC,
nullptr,
0,
nullptr,
0,
&bytesReturned,
nullptr);
printf("DeviceIoControl ret=%d gle=%lu\n",ret,GetLastError());
return 0;
}
很遗憾,并没有,实际上被ZwTerminateProcess选中的进程,除了这个挂起的线程以外全被清空了,我们再也无法创建新的线程在这个进程里面。

如果设置可以被打断那么这个线程立刻就会被kill掉,实际上毫无用处,不过有意思的是无论设置的KernelMode和usermode,都无法被终结,实际上我们对这个线程毫无办法,只能重启系统来恢复
底层原理
只有被驱动设置了取消历程的IRP可以被取消,进程管理器会等待与线程有关的I/O全部处理取消,才会删除这个线程。系统即使是在内核,系统无法强制取消IRP,IRP随时可能返回,可能损坏系统内存,系统如果直接把线程删除,IRP返回的数据将放到一个随机的地址,这将瞬间崩溃windows
非常有意思的情况,虽然对我毫无用处,但加深了我对线程、进程和I/O的理解:
- 线程和I / O是强相关的,基本上直接绑定
- 进程只是一个容器,被kill实际上系统需要先把线程全部释放掉才能清除进程
- 线程有内核模式和用户模式,线程作为载体只是平等的执行用户态和内核态的代码,任何线程都能执行驱动的代码,驱动的代码有机会发生在任意上下文,任意线程任意进程。
结论
如果下次再遇到无法结束的进程,很可能只是驱动BUG导致的,我们大可放心,毕竟如果真的感染高级的恶意代码,很可能压根无法察觉;而且PG经过几轮迭代在现代windows 11已经非常激进,对抗PG的成本远远超过找一个固件漏洞载入bootkit,更别说还有hypergraud这种硬件监控pg的完整性。
让GPT给我生成的window 11娘的告警图,都2026年了,Ai抽卡生成的图片总是三四个手,笑:
