论坛首页 漏洞分析研究区 阅读主题

[原创]Windows内核CVE-2019-1215分析与复现

211 浏览 4 回复
#1 楼主 2026-06-01 21:09:06
该漏洞源于 ws2ifsl.sys 驱动程序在处理内存对象时的缺陷,可能导致释放重引用错误。攻击者可以利用该错误,通过精心构造的输入,访问已释放的内存区域,从而执行恶意代码,提升权限。本文测试环境: 进入驱动的主函数DriverEntry中可以看到,驱动程序首先创建了一个名为\\Device\\WS2IFSL的设备供用户态访问:
驱动程序中实现了多个与用户态交互的函数,如下图所示:

而MajorFunction的下标,实际上对应了Windows内核中的宏定义,对应的字段如下:IRP_MJ_CREATE与IRP_MJ_CLOSE分别对应了我们在用户态创建文件与关闭文件句柄的请求,需要注意的是对于IRP_MJ_CLOSE来说,只有当我们传入的文件句柄对应的那个内核文件的引用计数为0时,才会真正执行。由于DispatchCreate对应我们创建文件的函数,而文件句柄这是我们与驱动交互的入口,因此我将从这里开始分析,该函数的实现如图所示:

可以看到,函数会根据SystemBuffer + 0x8的值来判断我们要创建的是ProcessFile还是SocketFile,根据微软文档的结构体定义,其中SystemBuffer实际上对应的是一片用户缓冲区。在此之后,会将FileObject, mode, SystemBuffer作为参数传递给要执行的函数,而对于参数FileObject来说,当我们用户态获得一个句柄时,内核就会创建对应的FileObject,该结构体可以在Windbg中查看到,结构如下:其中DeviceObject对应的就是驱动创建的那个设备,也就是\\Device\\WS2IFSL,而FsContext则对应了驱动程序自定义的数据,接下来首先分析CreateProcessFile函数中,主要部分如下:

函数首先根据我们传入的Handle的值,通过ObReferenceObjectByHandle获取到句柄对应的对象,该函数的原型为:而在此处,由于传入的ObjectType参数为PsThreadType,表示我们期望获取的对象为一个线程对象,当获取成功后会进行判断线程对象是否属于当前进程,如果是的话,则通过ExAllocatePoolWithQuotaTag函数分配一个大小为0x108的内存池,而其中第一个参数为POOL_TYPE,这是一个枚举类型,根据查阅微软的文档,可以知道实际上函数中的512对应的是NonPagedPoolNx,也就是非分页池,其中的Nx代表了权限位为不可执行。当内存申请成功后,会进行一系列的赋值操作,并且进入InitializeRequestQueue,由于赋的值是用户不可控制的,因此对于大部分值我们不用太过关心,只需要关注在buffer + 0x0处存储了字符串corP,buffer + 0x8存储了当前进程的PID,并且在InitializeRequestQueue的如图所示的位置:

我们可以看到驱动程序将APC对象挂在了buffer->ApcObject的位置,该结构体实际上是我自己恢复的,对应的偏移实际上为buffer+0x30处,而第二个参数则是指明了要把APC挂在哪个线程上,这里的pthread_Object实际就是根据我们前面传入的句柄所获取到的,是用户可控的。当赋值操作完成后,会将buffer挂在fileObject + 0x18的位置,根据前面给出的结构体定义,我们可以知道该偏移对应的是FsContext。而对于CreateSocketFile函数,大致与CreateProcessFile的前置逻辑相同,函数如下:

可以看到区别主要存在于赋值不同与ObReferenceObjectByHandle的不同,先看ObReferenceObjectByHandle,根据前面我们给出的函数原型可以知道,此时的ObjectType变为了IoFileObjectType,这代表文件对象类型,如用户空间打开的 文件/设备句柄,那应该传入哪个文件或设备的句柄呢?我们继续看下面的一段逻辑,首先将Object[0]给了v10,并通过IoGetRelatedDeviceObject来获取与Object[0]相关的是设备,其中DeviceObject这个全局变量对应的实际上就是驱动程序所注册的设备:\\Device\\WS2IFSL,然后取值判断,这里取得值有一点绕,我们拆开来看。而根据条件判断的内容,驱动期望该指针偏移0处的内容为corP,期望该指针偏移8处的内容为PID,再根据前面CreateProcessFile函数对于赋值的处理,我们可以知道对于ProcessFile的fileObject,偏移0x18实际上对应了FsContext,而*(Fscontext)与*(Fscontext + 0x8)正好对应了字符串corP与进程的PID,因此我们可以知道,要传入的句柄实际上就是ProcessFile对应的句柄。对于后面的赋值同样不用太过关注,只需要知道buffer + 0x0对于了字符串kcoS,而buffer + 0x10处存放了指向ProcessFile的fileObject的指针。根据MajorFunction数组的下标的宏定义可以知道,该函数对于了两种请求,分别为IRP_MJ_READ与IRP_MJ_WRITE,也就是对应了读写请求,该函数如下:

从图中我们可以看出,实际上读写请求只对应了SocketFile,而进入函数DoSocketReadWrite之后,我们可以看到:

函数首先将fileObject + 0x18处的值取出,这个偏移不论是对于SocketFile还是ProcessFile来说都是FsContext,而接下来,函数将FsContext作为参数传入了GetSocketProcessReference,该函数如下:

先是与锁相关的操作,而后将FsContext->Processfile_ptr引用计数加1,然后返回了FsContext->Processfile_ptr,而根据前面的分析,我们可以知道这里FsContext->Processfile_ptr指向的是ProcessFile的fileObject,而需要注意的是,在这里引用计数+1的操作也只针对了ProcessFile的fileObject,而并未针对FsContext指向的缓冲区。接下来在返回了fileObject后,将其作为参数传入了QueueRequest函数:

这里由于我结构体恢复的某些字段可能不太对,导致看着很奇怪,实际上我分析了一下,这里似乎是在将某个节点挂入链表的尾部。而后执行了SignalRequest:

该函数最重要的一点就是调用了KeInsertQueueApc函数,将APC挂入了我们传入的线程对应的内核线程的APC列表中,并等待执行。这里要注意的是,一旦APC被挂入了列表,那么即使我们关闭了句柄,也可以通过NtTestAlert等函数来强制执行。该函数对应的请求是IRP_MJ_CLOSE请求,也就是对应的我们在用户态关闭句柄的操作,但此操作真正的执行时机是当我们传入的句柄的引用计数为0时,该函数如下:

我们可以看见

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-288325.htm
#2 2026-06-01 21:09:06
mark
#3 2026-06-01 21:09:06
可以私聊吗
#4 2026-06-01 21:09:06
mb_yqsvfqmf


可以私聊吗

有什么问题吗
#5 2026-06-01 21:09:06
h1J4cker


有什么问题吗

私活

请登录后参与讨论

立即登录 注册账号