CVE-2024-35250:ks.sys驱动中基于属性反序列化的提权漏洞分析
字数 5239 2025-09-23 19:27:38

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.sysksthunk.sys
  • 目标模块
    • ks.sys
    • ksthunk.sys

0x03 利用思路与代码详解

利用的核心是构造一个特殊的 IRP,通过 DeviceIoControl 发送至漏洞路径,触发任意函数调用。文档提供了两种最终提权方式,共享前期的漏洞触发过程。

阶段一:通用漏洞触发准备

  1. 获取内核对象地址(GetObjPtr

    • 目的: 泄露当前进程的 _EPROCESS 和当前线程的 _KTHREAD 内核地址,这是后续篡改 TokenPreviousMode 的基础。
    • 原理: 利用 NtQuerySystemInformation 查询 SystemHandleInformation 信息,遍历所有进程的句柄表,通过 PID 和用户态句柄值匹配到目标句柄,从而获取其对应的内核对象地址。
    • 关键: 句柄值在系统内部用 USHORT 表示,匹配时需注意转换。
  2. 分配伪造的 RTL_BITMAP 结构(AllocateBitmap

    • 目的: 在用户态申请一块可控内存(例如地址 0x10000000),并将其布置为一个伪造的 RTL_BITMAP 结构。
    • 方法: 使用 VirtualAlloc 并在指定基址(baseAddress)分配内存,设置保护属性为 PAGE_READWRITE
    • 结构布局
      • FakeBitmap->SizeOfBitMap: 设置为一个任意值(如 0x20)。
      • FakeBitmap->Buffer这是关键。将其设置为我们要操作的目标内核地址(例如 &KTHREAD->PreviousMode&EPROCESS->Token->Privileges)。
  3. 泄露内核 Gadget 地址(leak_gadget_address

    • 目的: 获取目标内核函数(如 RtlClearAllBitsRtlSetAllBits)的实际地址。
    • 原理
      • 使用 LoadLibraryExWDONT_RESOLVE_DLL_REFERENCES 方式用户态加载 ntoskrnl.exe,获取其加载基址。
      • 使用 GetProcAddress 获取目标函数在用户态模块中的偏移地址。
      • 通过其它方法(如枚举驱动模块)获取内核中 ntoskrnl.exe 的真实基址。
      • 内核函数真实地址 = 内核基址 + (用户态函数地址 - 用户态模块基址)

阶段二:选择利用路径

方法一:篡改 PreviousMode 与进程 Token
  1. 构造利用数据

    • 将伪造的 RTL_BITMAPBuffer 指向当前线程的 PreviousMode 字段地址。
    • 将漏洞最终要调用的函数指针设置为 RtlClearAllBits 的地址。该函数会将其参数(我们的伪造 RTL_BITMAP)所指向的 Buffer 内存清零。
  2. 触发漏洞

    • 通过 DeviceIoControl 发送构造好的数据,触发漏洞调用链:
      • ks!UnserializePropertySet -> ks!KsSynchronousIoControlDevice -> ksthunk!CKSThunkDevice::DispatchIoctl -> ksthunk!CKSThunkDevice::CheckIrpForStackAdjustmentNative
    • CheckIrpForStackAdjustmentNative 中,由于传入的 RequestorMode 被设置为 KernelMode,代码会执行 *(UserBuffer+0x38) 这个函数指针(即 RtlClearAllBits),并以 UserBuffer(即 0x10000000,我们的伪造 RTL_BITMAP)为参数。
  3. 效果

    • RtlClearAllBits(FakeBitmap) 执行,将 FakeBitmap->Buffer(即 KTHREAD->PreviousMode)的内存清零。
    • PreviousMode 被从 1 (UserMode) 改为 0 (KernelMode)
  4. 提权与修复

    • 现在线程的 PreviousModeKernelMode,可以绕过内核的缓冲区检查。
    • 使用诸如 WriteProcessMemoryNtWriteVirtualMemory 等函数,将当前进程的 Token 替换为 SYSTEM 进程的 Token
    • 重要: 在创建新进程前,需要将 PreviousMode 改回 1,否则会因状态不匹配导致系统不稳定或崩溃。
    • 最后,执行 system("cmd.exe") 启动一个具有 SYSTEM 权限的命令行。
方法二:开启所有权限并创建子进程
  1. 构造利用数据

    • 将伪造的 RTL_BITMAPBuffer 指向当前进程 Token 中的 Privileges 字段地址。
    • 将漏洞最终要调用的函数指针设置为 RtlSetAllBits 的地址。该函数会将其参数所指的位图全部置 1
  2. 触发漏洞

    • 同方法一,触发漏洞执行 RtlSetAllBits(FakeBitmap)
  3. 效果

    • RtlSetAllBits(FakeBitmap) 执行,将 FakeBitmap->Buffer(即 Token->Privileges)的所有位都设置为 1
    • 当前进程获得了所有可用权限
  4. 提权

    • 使用 GetPidByName 找到 winlogon.exe 等高权限系统进程的 PID。

    • 使用 OpenProcess 获取该进程的句柄。

    • 使用 CreateProcessFromHandle 函数,并通过 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 属性将新创建的进程的父进程指定为 winlogon.exe

    • 由于进程权限继承机制,这个新创建的 cmd.exe 进程将继承 SYSTEM 权限。

    • CreateProcessFromHandle 关键步骤

      • 使用 InitializeProcThreadAttributeListUpdateProcThreadAttribute 设置父进程属性。
      • 调用 CreateProcessA 时,设置 bInheritHandles=TRUE 并指定 EXTENDED_STARTUPINFO_PRESENT 标志。

0x04 调试分析关键点

  1. 调用链
    UnserializePropertySet -> KsSynchronousIoControlDevice (设置 RequestorMode=0) -> CKSThunkDevice::DispatchIoctl -> CKSThunkDevice::CheckIrpForStackAdjustmentNative -> *(UserBuffer+0x38)(UserBuffer)

  2. 关键函数 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,则安全返回
    }
    
  3. 调试断点

    • bp ks!UnserializePropertySet
    • bp ks!KsSynchronousIoControlDevice
    • bp ksthunk!CKSThunkDevice::DispatchIoctl
    • bp ksthunk!CKSThunkDevice::CheckIrpForStackAdjustmentNative
    • bp nt!RtlClearAllBits / bp nt!RtlSetAllBits
  4. 观察点

    • CheckIrpForStackAdjustmentNative 中,查看 RDX 寄存器(UserBuffer),确认其指向 0x10000000(伪造位图)。
    • RtlClearAllBits 执行前,查看 RCX 参数(FakeBitmap),以及 RCX+8FakeBitmap->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 是一个典型的由于内核驱动对用户输入验证不当导致的任意函数调用漏洞。利用过程涉及精确的内核地址泄露、可控内存布局的构造以及对内核数据结构的深刻理解。成功利用可导致本地权限提升,危害严重。修复方案需微软发布安全更新,用户应及时安装补丁。

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.sys ksthunk.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 伪代码逻辑) : 调试断点 : bp ks!UnserializePropertySet bp ks!KsSynchronousIoControlDevice bp ksthunk!CKSThunkDevice::DispatchIoctl bp ksthunk!CKSThunkDevice::CheckIrpForStackAdjustmentNative bp 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 是一个典型的由于内核驱动对用户输入验证不当导致的任意函数调用漏洞。利用过程涉及精确的内核地址泄露、可控内存布局的构造以及对内核数据结构的深刻理解。成功利用可导致本地权限提升,危害严重。修复方案需微软发布安全更新,用户应及时安装补丁。