CVE-2024-35250 漏洞分析与利用教学文档
0x00 漏洞信息
- 漏洞编号: CVE-2024-35250
- 漏洞模块:
ks.sys(Kernel Streaming) 与ksthunk.sys - 漏洞类型: 本地权限提升 (Local Privilege Escalation)
- 漏洞机理: 该漏洞存在于
ks.sys驱动程序处理属性请求(IOCTL_KS_PROPERTY)的过程中。当请求设置了KSPROPERTY_TYPE_UNSERIALIZESET标志时,会触发一个特定的代码路径,最终在ksthunk.sys中,在没有充分验证请求来源(RequestorMode)和内容的情况下,将一个完全由用户控制的缓冲区(SystemBuffer)作为函数指针进行调用。 - 漏洞核心: 成功利用后可获得一个任意内核函数调用原语,其中第一个参数完全可控。攻击者利用此原语篡改内核关键数据结构,从而实现从普通用户权限到 SYSTEM 权限的提升。
0x01 背景知识
- ks.sys: Windows 内核流(Kernel Streaming)驱动,用于高效处理音频、视频等多媒体数据流,提供低延迟的内核级访问。
- IRP (I/O Request Packet): 内核中用于处理 I/O 操作的基本数据结构。其中
SystemBuffer是用户态传入数据的常见存储位置。 - PreviousMode: 隶属于线程(
_KTHREAD)的关键字段。它标识了 I/O 请求的发起模式。PreviousMode = 0 (KernelMode): 内核发起的请求,通常会跳过严格的用户缓冲区探针检查(ProbeForRead/Write)。PreviousMode = 1 (UserMode): 用户态发起的请求,内核会严格检查其传入的指针和缓冲区的有效性。
- Access Token: 隶属于进程(
_EPROCESS)的关键数据结构,包含了该进程的安全标识符(SID)、权限(Privileges)等信息,决定了进程的权限级别。将普通进程的 Token 替换为 SYSTEM 进程的 Token 是一种经典的提权手段。 - RTL_BITMAP: Windows 内核中用于管理位数组的数据结构,包含两个主要字段:
SizeOfBitMap: 位数组的大小(位数)。Buffer: 指向实际位数组存储内存的指针。
0x02 利用环境与工具
- 调试环境:
- 目标机: Windows 10 版本 19044.1415。需关闭 Windows Defender 的实时防护、云查杀和自动提交样本功能。
- 调试机: 配置双机内核调试(KD)。
- 分析工具:
- WinDbg Preview (x64): 用于动态调试和分析。
- IDA Pro (x64): 用于静态分析驱动文件
ks.sys和ksthunk.sys。
- 目标模块:
ks.sysksthunk.sys
0x03 利用思路与代码详解
利用的核心是构造一个特殊的 IRP,通过 DeviceIoControl 发送至漏洞路径,触发任意函数调用。文档提供了两种最终提权方式,共享前期的漏洞触发过程。
阶段一:通用漏洞触发准备
-
获取内核对象地址(
GetObjPtr):- 目的: 泄露当前进程的
_EPROCESS和当前线程的_KTHREAD内核地址,这是后续篡改Token和PreviousMode的基础。 - 原理: 利用
NtQuerySystemInformation查询SystemHandleInformation信息,遍历所有进程的句柄表,通过 PID 和用户态句柄值匹配到目标句柄,从而获取其对应的内核对象地址。 - 关键: 句柄值在系统内部用
USHORT表示,匹配时需注意转换。
- 目的: 泄露当前进程的
-
分配伪造的 RTL_BITMAP 结构(
AllocateBitmap):- 目的: 在用户态申请一块可控内存(例如地址
0x10000000),并将其布置为一个伪造的RTL_BITMAP结构。 - 方法: 使用
VirtualAlloc并在指定基址(baseAddress)分配内存,设置保护属性为PAGE_READWRITE。 - 结构布局:
FakeBitmap->SizeOfBitMap: 设置为一个任意值(如0x20)。FakeBitmap->Buffer: 这是关键。将其设置为我们要操作的目标内核地址(例如&KTHREAD->PreviousMode或&EPROCESS->Token->Privileges)。
- 目的: 在用户态申请一块可控内存(例如地址
-
泄露内核 Gadget 地址(
leak_gadget_address):- 目的: 获取目标内核函数(如
RtlClearAllBits或RtlSetAllBits)的实际地址。 - 原理:
- 使用
LoadLibraryExW以DONT_RESOLVE_DLL_REFERENCES方式用户态加载ntoskrnl.exe,获取其加载基址。 - 使用
GetProcAddress获取目标函数在用户态模块中的偏移地址。 - 通过其它方法(如枚举驱动模块)获取内核中
ntoskrnl.exe的真实基址。 - 内核函数真实地址 = 内核基址 + (用户态函数地址 - 用户态模块基址)。
- 使用
- 目的: 获取目标内核函数(如
阶段二:选择利用路径
方法一:篡改 PreviousMode 与进程 Token
-
构造利用数据:
- 将伪造的
RTL_BITMAP的Buffer指向当前线程的PreviousMode字段地址。 - 将漏洞最终要调用的函数指针设置为
RtlClearAllBits的地址。该函数会将其参数(我们的伪造RTL_BITMAP)所指向的Buffer内存清零。
- 将伪造的
-
触发漏洞:
- 通过
DeviceIoControl发送构造好的数据,触发漏洞调用链:ks!UnserializePropertySet->ks!KsSynchronousIoControlDevice->ksthunk!CKSThunkDevice::DispatchIoctl->ksthunk!CKSThunkDevice::CheckIrpForStackAdjustmentNative
- 在
CheckIrpForStackAdjustmentNative中,由于传入的RequestorMode被设置为KernelMode,代码会执行*(UserBuffer+0x38)这个函数指针(即RtlClearAllBits),并以UserBuffer(即0x10000000,我们的伪造RTL_BITMAP)为参数。
- 通过
-
效果:
RtlClearAllBits(FakeBitmap)执行,将FakeBitmap->Buffer(即KTHREAD->PreviousMode)的内存清零。PreviousMode被从1(UserMode) 改为0(KernelMode)。
-
提权与修复:
- 现在线程的
PreviousMode是KernelMode,可以绕过内核的缓冲区检查。 - 使用诸如
WriteProcessMemory或NtWriteVirtualMemory等函数,将当前进程的 Token 替换为 SYSTEM 进程的 Token。 - 重要: 在创建新进程前,需要将
PreviousMode改回1,否则会因状态不匹配导致系统不稳定或崩溃。 - 最后,执行
system("cmd.exe")启动一个具有 SYSTEM 权限的命令行。
- 现在线程的
方法二:开启所有权限并创建子进程
-
构造利用数据:
- 将伪造的
RTL_BITMAP的Buffer指向当前进程 Token 中的Privileges字段地址。 - 将漏洞最终要调用的函数指针设置为
RtlSetAllBits的地址。该函数会将其参数所指的位图全部置1。
- 将伪造的
-
触发漏洞:
- 同方法一,触发漏洞执行
RtlSetAllBits(FakeBitmap)。
- 同方法一,触发漏洞执行
-
效果:
RtlSetAllBits(FakeBitmap)执行,将FakeBitmap->Buffer(即Token->Privileges)的所有位都设置为1。- 当前进程获得了所有可用权限。
-
提权:
-
使用
GetPidByName找到winlogon.exe等高权限系统进程的 PID。 -
使用
OpenProcess获取该进程的句柄。 -
使用
CreateProcessFromHandle函数,并通过PROC_THREAD_ATTRIBUTE_PARENT_PROCESS属性将新创建的进程的父进程指定为winlogon.exe。 -
由于进程权限继承机制,这个新创建的
cmd.exe进程将继承 SYSTEM 权限。 -
CreateProcessFromHandle关键步骤:- 使用
InitializeProcThreadAttributeList和UpdateProcThreadAttribute设置父进程属性。 - 调用
CreateProcessA时,设置bInheritHandles=TRUE并指定EXTENDED_STARTUPINFO_PRESENT标志。
- 使用
-
0x04 调试分析关键点
-
调用链:
UnserializePropertySet->KsSynchronousIoControlDevice(设置RequestorMode=0) ->CKSThunkDevice::DispatchIoctl->CKSThunkDevice::CheckIrpForStackAdjustmentNative->*(UserBuffer+0x38)(UserBuffer) -
关键函数
CheckIrpForStackAdjustmentNative(IDA 伪代码逻辑):if ( Irp->RequestorMode == KernelMode ) // 漏洞关键:这里被设置为0,进入危险分支 { v5 = Irp->AssociatedIrp.SystemBuffer; // 获取用户控制的缓冲区 v6 = Irp->Tail.Overlay.CurrentStackLocation; // ... function_ptr = *(v5 + 0x38); // 从用户缓冲区偏移0x38处取函数指针 result = function_ptr(v5); // 以用户缓冲区为参数调用该函数! } else { return error_code; // 如果是UserMode,则安全返回 } -
调试断点:
bp ks!UnserializePropertySetbp ks!KsSynchronousIoControlDevicebp ksthunk!CKSThunkDevice::DispatchIoctlbp ksthunk!CKSThunkDevice::CheckIrpForStackAdjustmentNativebp nt!RtlClearAllBits/bp nt!RtlSetAllBits
-
观察点:
- 在
CheckIrpForStackAdjustmentNative中,查看RDX寄存器(UserBuffer),确认其指向0x10000000(伪造位图)。 - 在
RtlClearAllBits执行前,查看RCX参数(FakeBitmap),以及RCX+8(FakeBitmap->Buffer)指向的内存(应为PreviousMode,值为1)。 - 在
RtlClearAllBits执行后,再次观察FakeBitmap->Buffer指向的内存,确认其已被清零(PreviousMode变为0)。 - 在
NtWriteVirtualMemory前后,观察当前进程的 Token 值变化。
- 在
0x05 复现结果
- 方法一结果: 成功将当前线程的
PreviousMode改为KernelMode,并替换当前进程的 Token,最终在当前进程中启动了一个 SYSTEM 权限的cmd.exe。 - 方法二结果: 成功将当前进程 Token 的权限全部开启,并成功以
winlogon.exe为父进程创建了一个新的 SYSTEM 权限的cmd.exe。
总结: CVE-2024-35250 是一个典型的由于内核驱动对用户输入验证不当导致的任意函数调用漏洞。利用过程涉及精确的内核地址泄露、可控内存布局的构造以及对内核数据结构的深刻理解。成功利用可导致本地权限提升,危害严重。修复方案需微软发布安全更新,用户应及时安装补丁。