VMware Workstation PVSCSI LFH Escape (VMware-vmx on Windows 11)

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

漏洞结构:fixed-size realloc + scattered OOB writes

  • PVSCSI_FillSGI 将 guest 的 scatter/gather 条目复制到内部数组。它从一个 512 条目的静态缓冲区(0x2000)开始。在超过 512 条目时会重分配到 0x4000 字节,并且由于实现缺陷,在每次迭代都重新分配
  • 重分配大小不会增长:0x4000 / 0x10 字节条目 = 1024 可用条目。当 guest 提供 >1024 条目 时,每个新条目都会写入新分配的 0x4000 块之后的 16 字节,从而破坏相邻的 chunk header 或对象。
  • 溢出内容:VMware 存储 {u64 addr; u64 len};guest 提供 {u64 addr; u32 len; u32 flags}。32 位的 len 会被 零扩展,因此每个 16 字节 OOB 元素的最后一个 dword 总是 0x00000000

LFH 约束 & 确定性的 “Ping-Pong” 布局

  • 0x4000 分配会落在 Windows 11 LFH(每个 bucket 16 个 chunk,0x10 字节元数据并带 keyed checksum)。任何其 header checksum 被命中的 chunk 后续都会导致进程终止,因而被破坏的 header 绝不能被重用。
  • LFH 会返回一个随机的空闲 chunk,但倾向于包含最近被释放的 chunk 的 bucket。强制只留两个空位:
  1. 分配掉所有空闲的 0x4000 chunk 以对齐分配器;喷洒 32 个 SVGA shader 来填满 B1B2 buckets。
  2. 释放 B1 中除一个被固定的 shader(Hole0)之外的所有 shader,使 B1 保持活跃;在 B1 中分配 15 个 URB
  3. 在 B2 中释放一个 shader(PONG),然后立即释放 Hole0。LFH 会在两个可用槽之间交替分配,形成 PING (B1)PONG (B2)
  • 第 1025 次迭代破坏 PONG 之后的 header(该 header 不会再被触及);第 1026 次迭代击中 PING 之后的第一个 16 字节 URB(绕过安全的元数据)。用占位 shader 回收 PING/PONG 以保持布局稳定并可重复。

Reap Oracle:标记连续空洞

  • UHCI URBs 位于 FIFO 队列中,并在被完全 reaped 时释放。受限的 16 字节覆盖总是将 actual_len 置零,从而留下一个标记。
  • 按顺序 reap URB;当看到被置零的 actual_len 时,立即用一个可识别的 shader 填充被释放的槽。迭代操作可以将 Hole0–Hole3 标记为已知顺序的四个连续 chunk,以便后续依赖相邻性的原语。

将受限写转为任意覆盖(滥用 coalescing)

PVSCSI 使用 AddrA + LenA == AddrB 来合并相邻条目并将后续条目向上压缩

  • 两遍溢出: 从 PING(奇数索引)处触发并提前退出以跳过合并;然后从 PONG(偶数索引)处再次触发以填补空隙并继续写入被喷洒的 shader 中包含伪 S/G 条目的区域。
  • 清空 + 有效载荷: 将条目 [1023..2047] 设为 {addr=0,len=0},使得合并将它们折叠为一个,创建一个逻辑空洞。随后放置在 shader 中的 payload 条目会被 向上移动,落入受害 URB 内。
  • 邻接校验绕过: 通过设置 LenA=0,条件变为 AddrA==AddrB。构造对
{addr = X, len = 0}
{addr = X, len = Y}

使得合并将它们变为 {addr=X,len=Y}。偶数索引的零长度元素来自受限溢出;奇数索引的值位于 shader 中。结果:尽管强制了最后一个 dword 为零,仍能写出 任意 16 字节模式

Hybrid URB infoleak via coalescing side-effects

  • 安排连续 chunk:[Hole0 (free/PING), URB1 (target), URB2 (valid, actual_len=0), URB3 (leak target)]
  • 用连续的伪条目填充 URB1(大小为 0xFFFFFFFF),仅最小触及 URB2。合并会把它们合并为一个条目;总和 0xFFFFFFFF * 0x401 会在 URB1 的 actual_len 偏移处设置上位 dword 为 0x400
  • 压缩会把后续数据 向上复制,将 URB2 的 header 拉入 URB1。URB1 现在有了有效的 header(pipe/list 指针)、actual_len=0x400,并且 data 指针已经指向 URB2 缓冲区的末端。
  • reap URB1 会从刚好位于 URB3 之前的位置复制 0x400 字节,从而产生对 URB3 header/self-references 的 OOB read,泄露绝对的堆地址并击破 ASLR,以便后续伪造结构。

Post-leak 原语(无需重新触发漏洞)

  • 在占据 Hole0 的 shader 内伪造一个 URB 结构,然后用 coalescing 的“向上移动”将 URB1 替换为伪造数据。
  • 使该 URB 持久化:将 URB1.next = Hole0 并增加 refcount;reap URB1 会把 由 Hole0 支持的伪 URB 放到 FIFO 头。未来的原语就是对 Hole0 重新分配新的伪 URB。
  • 任意读: 构造伪 URB,设置期望的 data_ptractual_len,然后 reap 将主机内存复制到 guest。
  • 任意写(32-bit): 构造其 pipe 指向受控内存的伪 URB,并滥用 UHCI 的 TDBuffer writeback 在任意地址写入期望的 dword。
  • 任意调用: 覆盖一个 USB pipe 回调;host 会以 RCX+0x90 处的受控数据调用它。动态解析 WinExec(guest 侧读取 Kernel32)并通过一个位于 vmware-vmx 内且符合 CFG 的 gadget 把参数从 RCX+0x100 加载后再分发到 WinExec("calc.exe")

LFH 定时侧信道以确定初始 bucket 偏移

  • 确定性的 Ping-Pong 需要知道 LFH 的空闲 chunk 偏移(16 个槽中哪个会先被命中)。使用 VMware backdoor 指令(inl %%dx, %%eax)配合同步的 VMware Tools 命令 vmx.capability.unified_loop 和一个 0x4000 字节的字符串,每次调用会强制产生 两个 0x4000 分配
  • 通过 gettimeofday 对 8 次调用(16 次分配)计时;当 LFH 创建一个新 bucket 时,其中一次调用会显示出一致的时延峰值。再多做一次分配:如果峰值保持在相同索引,偏移为奇数;如果移动则为偶数;否则由于噪声需重启。
  • 注意:unified_loop 会把唯一字符串存入一个不可释放的列表,导致 O(n) 查找开销 并增加噪声,因此侧信道必须快速收敛。

References

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks