×

线程睡眠器:通过GMER64驱动暂停线程

hqy hqy 发表于2026-04-08 16:49:43 浏览6 评论0

抢沙发发表评论

作者: Jonny Johnson (@jsecurity101)

最近我的一个朋友,Nick Powers,发给我gmer.sys驱动程序,与Blackout活动有关,该活动暴露出可以从中等完整性级别上下文中终止任何进程的功能。这被用来对抗许多EDR供应商,包括Microsoft Defender for Endpoint,以杀死其服务进程(在MDE的情况下是MsSense.exe),该进程作为反恶意软件轻保护进程(PPL)运行。ZeroMemoryEx在推特上提到这个这里:

 

这显然不是理想的情况,像微软这样的供应商迅速工作以防止该驱动程序被写入磁盘。然而,一旦加载,就没有什么可以做了。

在查看这个驱动程序时,我发现了很多可疑的功能。其中之一是暂停你选择的任何线程,前提是你要有它的线程标识符。本文将介绍我查找替代功能的过程,以干扰与EDR驱动程序相关的PPL进程的操作,我认为这在某种程度上是有点隐蔽的。


方法论  


步骤 1:检查设备对象的安全描述符 


每个设备驱动程序都必须至少有一个设备对象。这个设备对象的目的是处理 I/O 请求。设备对象是通过 IoCreateDevice 或 IoCreateDeviceSecure 创建的。这两个 API 之间的区别是,IoCreateDeviceSecure 的第 7 个参数允许作者为设备对象指定一个 DACL,而使用 IoCreateDevice 则需要以不同的方式完成。

注意:某些驱动程序在INF文件中指定了安全描述符,但今天这不适用于我们。

在打开IDA并找到DriverEntry后,我们可以看到有一个IoCreateDevice的调用:



 


这里我们可以看到,设备对象的名称来自驱动安装时所使用的服务名称。此外,设备对象上没有设置安全描述符,因此我们知道如果在某处发现这个驱动,我们可以从非特权的角度与之交互(例如,中等完整性级别)。你也可以通过WinObjEx64轻松看到这一点:



我们可以看到,“Everyone”有充足的权限让我们通过CreateFile获取设备对象,我只使用了GenericRead/GenericWrite。在下面的POC中对此有更多介绍。

注意:如果您计划卸载此驱动程序,您需要在目标主机上具有管理员权限才能安装该服务。

然后我们看到一个符号链接被创建,允许用户模式应用程序获取设备对象的句柄。这 later我们需要发送 IOCTL 通过 DeviceIOControl。

步骤 2: 查找 DispatchDeviceControl例程 


通常,当在驱动程序中发现一个漏洞时,是因为驱动程序暴露了一些可以通过其DispatchDeviceControl例程访问的功能,当用户模式客户端使用DeviceIoControl Win32 API(还有其他可以利用的功能)传递输入/输出控制代码或IOCTL时,可以访问该例程。

当用户模式应用程序使用DeviceIoControl向设备驱动程序发送 IOCTL 时,信息会被包装成一个 I/O 请求包或 IRP。一旦驱动程序收到 IRP,它将访问I/O 堆栈位置以确定操作请求的主功能类型(在这种情况下,是IRP_MJ_DEVICE_CONTROL主功能),以及其参数。IRP 会传递给驱动程序中注册的DispatchDeviceControl。该DispatchDeviceControl常规操作将解包IRP以查找 IOCTL 和包含的缓冲区(通常包含与驱动程序功能相关的参数)。控制代码由CTL_CODE宏定义,该宏可以在ntfis.h中找到:



 


  1. 设备类型 

  2. 访问 

  3. 功能 

  4. 方法 



在反转驱动程序时,找到DispatchDeviceControl后面的函数后,IOCTLS很容易指出,因为它们通常在switch语句中。switch语句执行以找到内部函数,该函数被设置为处理具有匹配IOCTLS的请求,并传递用户传递的参数。我在我的博客文章通过命名管道文件系统驱动程序探索模拟中对此进行了更详细的介绍。


要找到特定驱动程序的DispatchDeviceControl例程,我们需要检查驱动程序是否处理IRP_MJ_DEVICE_CONTROL主要功能。如果驱动程序执行此操作,大多数驱动程序会做类似的事情:


DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = TestDeviceControl;


如果我们查看gmer64驱动程序,我们会看到:  



 


在sub_12448中,我们可以看到有一个冗余的检查来判断主功能代码是否为IRP_MJ_DEVICE_CONTROL,并将其传递给另一个内部函数,我将其重命名为DispatchDeviceControl:



 


既然我们已经找到了处理IRP_MJ_DEVICE_CONTROL请求的内部函数,我们需要看看是否有任何对我们有用的调用。  


步骤 3:映射 IOCTLs 


如果你仔细观察暴露的 IOCTLs 及其功能,你很快就会发现有一些可疑的事情可以做这个驱动。然而,我发现其中一个很有趣的是 0x9876C098:



 


0x9876C098 调用一个内部函数(我将其重命名为 SuspendThread),该函数接受两个参数:一个结构的指针和该结构的第二个成员。进入该内部函数后,会调用 ZwOpenThread,然后使用函数指针调用 ZwSuspendThread:



 


前面的例子由ZeroMemoryEx演示,使用了IOCTL 0x9876C094,调用了内部函数,传入一个进程ID,然后通过ZwTerminateProcess终止了进程。这让我思考 - 如果我能暂停任何我想暂停的进程,并且在不显示它已被终止的情况下使一个进程(例如PPL进程)无用,那会怎么样。让我们创建一个POC。


步骤 4:创建 PoC 


注意:为了使 IOCTL 正常工作,必须在通过 IOCTL 0x9876C004 发送的驱动程序中进行初始化,这可以在 Blackout POC 中看到。 


我需要做的第一件事是创建一个可以传递的自定义结构,因为SuspendThread内部函数接受两个参数:一个结构的指针和该结构的第一个成员。在研究了该内部函数后,我意识到所涉及的结构很可能是CLIENT_ID,因为用户提供的结构的成员被传递到了一个新的CLIENT_ID中


驱动代码:  



 


POC 代码:  


struct TargetProcess { 
    DWORD ProcessId; 
    DWORD ThreadId; };


我确实想指出,ProcessId 成员实际上并没有使用,因此从用户模式方面来看,你只需要获取线程 ID,这可以作为中等 IL 获取。我一开始对此感到困惑,并担心我忽略了什么,所以非常感谢 Matt Hand 进行了合理性检查 —— 这让我们都很奇怪。

接下来,我们需要通过符号链接获取设备对象的句柄,这可以通过CreateFile来实现:


hDevice = CreateFileW(L"\\\\.\\gmer64", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);


如果你不熟悉,CreateFile 不仅可以用来创建文件,还可以用来获取现有对象的句柄。你还会注意到设备对象的名称是 gmer64。这是因为我在我的机器上将服务创建为 gmer64。  


之后我创建了一个我结构的实例,并适当地传递了参数,其中参数1是进程ID(再次未使用)和参数2是线程ID:  


TargetProcess data; 
    data.ProcessId = atoi(argv[1]); 
    data.ThreadId = atoi(argv[2]);


然后我们需要通过DeviceIoControl发送IOCTL。在我们能够发送IOCTL以暂停线程之前,必须先对驱动程序进行一些初始化。初始化很简单,因为我们只需要发送IOCTL 0x9876C004。这将把全局变量设置为1,如果我们不这样做且全局变量没有被设置为1,稍后的DeviceIoControl调用将失败。一旦设置了这一点,你可以发送任意多的DeviceIoControl请求。


 BOOL deviceControl = DeviceIoControl(hDevice, INITIALIZE_IOCTL_CODE, &data, sizeof(data), output, outputSize, &bytes, NULL);


在使用之前的请求初始化后,我们可以调用DeviceIoControl来执行我们想要的IOCTL,并暂停我们想要的线程ID。


deviceControl = DeviceIoControl(hDevice, SUSPEND_THREAD_IOCTL_CODE, &data, sizeof(data), &output, outputSize, &bytes, NULL);


以下是显示进程中的所有线程都被暂停的输出:  



 


代码可以在这里找到:https://github.com/jsecurity101/RandomPOCs/tree/main/SuspendThreadDriver  


影响 


对于那些不熟悉的人来说,许多端点保护产品如果他们的主要进程被终止,会重新启动他们的主要进程。这现在是一种相对常见的做法;然而,他们没有检查那个主要进程中的线程是否已暂停。他们通常只是检查进程是否存活,这在技术上仍然使用了这种技术。如果这个驱动程序已经在系统上运行,你可以从中等IL获取与传感器进程相关的所有线程ID,并使用这个驱动程序暂停它们。这就是我在中提到的:这条推文



 


为了证明在此之后没有发生回调,我在此设备上进行了检查,该设备在执行上述操作后3天安装了MDE:



 


建议 


从防御的角度来看,我的建议主要是对传感器供应商: 

  • 从威胁情报ETW提供者中收集暂停线程事件。有来自用户模式和内核模式的调用事件。

  • 从威胁情报ETW提供者中收集Driver/DeviceLoad事件,你将能够看到该驱动程序何时被加载。

  • 将gmer64驱动程序添加到您的禁止驱动程序列表中




结论 



尽管这已经被标记为一个易受攻击的驱动程序,但这是一个有趣的项目,因为它突显出,如果仔细观察,发现驱动程序存在漏洞,通常会嵌入对攻击者有用的其他漏洞或功能。

最后,易受攻击的驱动程序今年确实有所增加,我想指出的是,除了WDAC之外,还有防御能力。Microsoft的团队通过威胁情报ETW提供者努力暴露了在这种情况下可以使用的事情。



资源:

再次感谢 ZeroMemoryEx 对BlackOut的出色工作,发现了终止进程的漏洞,以及他们的POC,这让我稍微轻松了一些。



一般参考文献: 





 










其他好的关于反向驾驶/脆弱驾驶人的博客:  





 




打赏

本文链接:https://kinber.cn/post/6393.html 转载需授权!

分享到:


推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

 您阅读本篇文章共花了: 

群贤毕至

访客