这是针对 Nintendo Switch 的 Fusee Gelee 漏洞(CVE-2018-6242)的一种实现,基于 Kate Temkin / ReSwitched 在 2018 年 3 月披露的漏洞。
可在处于 USB 恢复模式(RCM)的 Tegra X1 设备上启动任意载荷(例如 hekate),完全绕过 Boot ROM 的签名验证。
RCM(恢复模式)是内置于 Tegra X1 Boot ROM 的基于 USB 的恢复协议。NVIDIA 设计它是为了将小程序(“applet”)加载到设备上进行诊断或修复——例如,当 Switch 在其存储上找不到有效的引导加载程序时。
在 Nintendo Switch 上,通过在启动时短接右侧 Joy-Con 滑轨的引脚 1 和 10 进入 RCM。在正常操作中,只有 NVIDIA 可以使用 RCM——所有命令都必须使用 NVIDIA 的 RSA 私钥签名,Boot ROM 在执行任何操作之前都会验证签名。
该漏洞利用横跨两个共享 IRAM(片上 RAM)的独立协议层:
USB 层(标准,EP0): 每个 USB 设备都有一个控制端点(EP0),用于处理标准请求,如 GET_STATUS、GET_DESCRIPTOR、SET_ADDRESS。Boot ROM 按照 USB 2.0 规范的要求实现这些请求。EP0 是隐式的——它不会出现在设备的端点描述符中。
RCM 层(NVIDIA 专有,EP1): NVIDIA 定义了一个批量端点(EP1),用于传输 RCM 命令和载荷。EP1 在设备描述符中以两个方向出现:
0x01 = OUT(主机向设备发送数据)0x81 = IN(设备向主机发送数据)通过 EP1 发送的数据结构如下:
[680-byte RCM command header] [payload bytes]
这 680 字节的头部是 rcm_msg_t 结构体——一种 NVIDIA 专有结构,包含 RSA 模数/签名、ECID、操作码等字段。这个大小是通过逆向工程 Tegra X1 Boot ROM 确定的(参见 q3k 的 IDA 数据库)。NVIDIA 的开源 tegrarcm 只记录到 644 字节(Tegra124);T210 变体则大了 36 字节。
Boot ROM 的 EP0 控制请求处理程序在针对 ENDPOINT 接收方的 GET_STATUS 实现中存在一个错误。摘自 Temkin 的白皮书:
// BUG: should be size_to_tx = sizeof(status), i.e. 2 bytes
size_to_tx = length_read; // attacker-controlled via wLength, up to 65535
data_to_tx = &status; // a uint16_t on the stack
memcpy(dma_buffer, data_to_tx, size_to_tx);
memcpy 从 &status(一个恰好位于 0x40010000 下方的栈变量)读取,并写入 DMA 缓冲区(位于 0x40009000)。当长度过大时:
&status 越界读取,越过栈的其余部分,进入位于 0x40010000+ 的受攻击者控制的载荷区域(通过 EP1 批量写入放置在那里)。源数据包含一个“堆栈喷射”(重复的 0x40010000),它会被写到栈的返回地址上。当处理程序返回时,执行跳转到 0x40010000——我们在此放置了一个名为 intermezzo 的小型重定位程序。
这一切都发生在 RCM 接收循环期间(在 handle_control_requests 内部),在 Boot ROM 验证签名之前。RCM 协议是传递机制;USB 控制处理程序是触发器。
0x40005000 +------------------+
| DMA buffer LOW | USB controller writes odd packets here
0x40009000 +------------------+
| DMA buffer HIGH | USB controller writes even packets here
+------------------+
| execution stack | grows downward toward DMA buffers
0x40010000 +------------------+ <-- stack ends here / payload starts here
| intermezzo | small relocator stub (124 bytes)
0x40010E40 +------------------+
| user payload pt1 | first ~16KB of the user payload
0x40014E40 +------------------+
| stack spray | 0x40010000 repeated (8640 bytes)
0x40017000 +------------------+
| user payload pt2 | remainder of user payload
+------------------+
用户载荷以堆栈喷射为界被分成两部分,因为喷射必须放置得当,使溢出能够将它复制到栈的返回地址上。Intermezzo 将这两半重新组装成位于 0x40010000 的连续内存块,然后跳转到其中。
0x0955:0x7321)0x40010000+ 的 IRAM,放置我们的 intermezzo、用户载荷和堆栈喷射0x40009000)wLength=0x7000 的 GET_STATUS 控制请求——这会触发有漏洞的 memcpy,堆栈喷射覆盖返回地址,处理程序返回到 intermezzopip install pyusb
python launcher.py
需要一台通过 USB 连接并处于 RCM 模式的 Switch。在 macOS 上,可能需要执行 brew install libusb。
将你的载荷二进制文件放在 binaries/payload.bin。附带的 intermezzo.bin 负责载荷重定位,通常无需替换。
launcher.py — 漏洞利用脚本binaries/intermezzo.bin — 重定位桩代码(124 字节),重新组装拆分后的载荷binaries/payload.bin — 要执行的用户载荷(如 hekate)