NOTE : Toutes les clés de chiffrement et mots de passe présentés ici sont fictifs et ont été inventés pour la rédaction de cet article.

Qu'est-ce qu'un tuner

Un tuner est un petit appareil destiné à être branché sur une moto. Il sert à configurer l'ordinateur de bord afin d'optimiser les ratios air/carburant et d'autres paramètres. Il est notamment utilisé pour lever les restrictions de puissance du moteur ou optimiser la consommation de carburant. Même si je suis convaincu qu'un vrai motard aurait de bien meilleurs mots pour décrire ses usages.

Tuner Dynojet Power Vision
Dynojet Power Vision pour Harley Davidson

Le modèle étudié ici est un Power Vision pour Harley Davidson, fabriqué par Dynojet. Le matériel est un Bobcat Revision D de Drew Technologies (il y a d'ailleurs une faute de frappe sur le PCB, où l'on peut lire Drew Technoligies).

PCB Drew Technologies
Bobcat Revision D par Drew Technologies

Obtenir le firmware

Analyse des outils

Le tuner est prévu pour être configuré en étant connecté à un ordinateur. Il dispose d'une entrée mini-USB à côté du CAN Bus (ce dernier devant être relié à l'ordinateur de bord de la moto). Les outils de configuration sont téléchargeables gratuitement sur le site de Dynojet.

Outils Windows Dynojet
Outils Windows du Power Vision

Les outils Windows installés contiennent les binaires suivants :

  • WinPV.exe : le logiciel principal avec l'interface graphique
  • PVUpdateClient.exe : le programme de mise à jour, chargé de télécharger et de transférer les nouveaux firmwares via le lien USB
  • RecoveryTool.exe : appelé exclusivement par le PVUpdateClient pour flasher la partie recovery du firmware
  • PVLink.dll : en charge de la communication via le port série, très important
Fichiers Power Vision
Binaires installés

Notre objectif est maintenant d'obtenir le firmware pour commencer le reverse engineering. En consultant des tutoriels YouTube et TheWaybackMachine, on constate que les firmwares étaient autrefois disponibles directement sur le site de Dynojet, dans la section firmware, qui est désormais vide. On trouve une piste intéressante en lançant PVUpdateClient.exe et Wireshark simultanément.

PVUpdateClient et Wireshark
Interception du trafic de mise à jour du firmware

La capture Wireshark montre du HTTP en clair vers dynojetpowervision.com, vérifiant les fichiers firmware disponibles. Il n'y a pas de réelle protection ici, juste un User-Agent attendu : « PVUpdateClient », sans lequel les fichiers restent masqués.

En utilisant curl, on obtient les noms de fichiers recherchés :

Bash
curl -v -A PVUpdateClient http://dynojetpowervision.com/downloads/PowerVisionVersions.xml
*   Trying 52.183.62.164:80...
* TCP_NODELAY set
* Connected to dynojetpowervision.com (52.183.62.164) port 80 (#0)
> GET /downloads/PowerVisionVersions.xml HTTP/1.1
> Host: dynojetpowervision.com
> User-Agent: PVUpdateClient
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/xml
< Last-Modified: Mon, 09 Nov 2020 18:35:22 GMT
< Accept-Ranges: bytes
...
 <PVSystemUpgrade.1.1 ver="1.0.1 1.0.1" package="PV_SYSTEMUPGRADE-1.0.1-631.pvr"/>
 <PVFirmware.1.1 ver="2.0.47-1613" package="PV_FIRMWARE-2.0.47-1613.pvu" />
 <PVTuneDB.1.1 ver="0.0.10.11" package="PV_TUNEDB-0.0.10.11.pvu" />
...

On se concentre sur les fichiers FIRMWARE et SYSTEM_UPGRADE, les plus susceptibles de contenir ce qui nous intéresse. On les télécharge avec wget, et c'est là que les ennuis commencent :

Bash
mishellcode@unisec:~/powervision$ unzip PV_SYSTEMUPGRADE-1.0.1-631.pvr
Archive:  PV_SYSTEMUPGRADE-1.0.1-631.pvr
[PV_SYSTEMUPGRADE-1.0.1-631.pvr] PVRecoveryInfo.xml password:
Bash
mishellcode@unisec:~/powervision$ unzip PV_FIRMWARE-2.0.47-1613.pvu
Archive:  PV_FIRMWARE-2.0.47-1613.pvu
extracting: PVU_TYPE
extracting: PVU_CERT
inflating: PVU_FILE
mishellcode@unisec:~/powervision$ file PVU_FILE
PVU_FILE: openssl enc'd data with salted password

Le fichier SYSTEM_RECOVERY, que nous appellerons « fichier PVR », est une archive protégée par mot de passe, et le fichier FIRMWARE, nommé « PVU_FILE », est en réalité chiffré avec Openssl. De plus, le fichier PVU_CERT indique qu'un contrôle d'intégrité est probablement effectué sur le PVU_FILE.

Je passe les détails, mais puisque le fichier PVR est écrit en clair sur l'appareil, il était évident que l'un des outils (en l'occurrence RecoveryTool.exe) contenait le mot de passe quelque part. Un peu de reverse engineering plus tard, on obtient le mot de passe POWERVISION_RECOVER_3456789Z. Ce mot de passe est en réalité partiellement masqué. Il n'est pas codé en dur mais des boucles itèrent sur des valeurs entières pour générer le motif final du mot de passe, puis y concatènent une lettre majuscule. On sent clairement une intention de dissimuler cela aux utilisateurs mal intentionnés, et c'est généralement le signe qu'on est sur la bonne piste.

Contenu du fichier de recovery

Bash
nandflash_bobcat.bin: data
PVRecoveryInfo.xml:   exported SGML document, ASCII text, with CRLF line terminators
u-boot.bin:           data
uImage:               u-boot legacy uImage, Bobcat-577, Linux/ARM, OS Kernel Image
                      (Not compressed), 828996 bytes, Thu Feb 3 14:44:52 2011,
                      Load Address: 0x20008000, Entry Point: 0x20008040

Le fichier de recovery est une image u-boot avec une image noyau. L'entropie des fichiers indique que certaines parties sont chiffrées. De plus, la présence du fichier at91bootstrap indique que nous sommes en présence d'une carte SAM AT 91, capable d'utiliser le secure boot. Pas de chance. Nous pouvons néanmoins extraire une information de ces fichiers : le type de processeur est le SAM926X.

En parcourant internet, on trouve un post de forum où un employé de Drew Technoligies pose des questions sur cette même famille de processeurs (spécifiquement le SAM9260-EK) : mailing list U-Boot.

Fichier de mise à jour

Puisque le fichier de mise à jour est chiffré, on peut formuler deux hypothèses :

  • Le firmware est stocké chiffré et déchiffré à l'exécution. Ce serait lent, mais puisque la carte supporte le secure boot, c'est une hypothèse viable.
  • Le firmware est stocké en clair, seules les mises à jour sont chiffrées. Le processus de mise à jour déchiffre le PVU_FILE et remplace le firmware en cours d'exécution. Ce serait idéal, non ?

Montage physique

La méthode rapide et directe serait de dessouder la puce mémoire pour récupérer le firmware. Mais dans ce cas, c'est un peu plus compliqué. L'intégralité du PCB était moulée dans une protection plastique, probablement pour l'étanchéité contre l'humidité.

Extérieur du PCB moulé
PCB moulé en gros plan

J'ai dû le découper pour voir le PCB réel :

PCB ouvert
Le PCB après retrait du moule plastique

La puce mémoire semble être soudée de l'autre côté du PCB. Mauvaise nouvelle, car elle se trouve sous l'écran, et tenter de l'extraire physiquement détruirait probablement l'appareil. De plus, à ce stade, nous ne savons pas si récupérer la puce nous aidera. Elle pourrait simplement contenir un fichier chiffré par Openssl, déchiffré lors du processus de démarrage. Nous avons donc cherché ailleurs.

En observant attentivement, on repère 4 broches avec l'inscription DEBUG au-dessus !

Broches de debug sur le PCB
Broches DEBUG repérées sur le PCB

On s'y connecte à l'aide d'un adaptateur UART vers USB, et on lance minicom.

ROMBoot
Welcome to bobcat
bobcat login:

Problème : le shell est protégé par mot de passe, et même après des jours de bruteforce (en utilisant cet outil), aucun mot de passe n'a été trouvé. De plus, U-Boot est configuré en mode silencieux et il n'y a aucun moyen depuis ce shell d'interagir avec la séquence de démarrage.

Une piste reste inexplorée : le MODE RECOVERY.

Mode Recovery

En expérimentant avec RecoveryTool.exe, on découvre qu'il existe un mode recovery pour l'appareil. Il s'active en maintenant le bouton power enfoncé tout en branchant le lien USB.

Écran du mode recovery
Appareil en mode recovery

Ce qui est intéressant avec ce mode, c'est qu'il change le mode de communication sur le port USB Link. En fonctionnement normal, ce port utilise un protocole propriétaire qui restreint de nombreuses actions (voir la spécification Filex en Partie 2), alors qu'en mode recovery, le port expose un shell U-Boot !

U-Boot
U-Boot> printenv
bootargs=console=ttyS0,115200 ubi.mtd=linux root=31:4 lpj=598016 quiet
bootcmd=nboot kernel;bootm;
bootdelay=0
baudrate=115200
mtdids=nand0=atmel_nand
mtdparts=mtdparts=atmel_nand:128k@0x0(at91bootstrap)ro,1m(u-boot)ro,2m(kernel),-(linux)
silent=yes
ver=U-BootVersion:1.0.1
stdout=usbser
stdin=usbser
stderr=usbser

Environment size: 316/131068 bytes

On peut modifier les paramètres de démarrage afin de contourner l'authentification sur le port UART de debug interne :

U-Boot
U-Boot> setenv bootargs "console=ttyS0,115200 ubi.mtd=linux root=31:4 lpj=598016 single"
U-Boot> setenv silent no
U-Boot> setenv bootdelay 3
U-Boot> printenv
bootcmd=nboot kernel;bootm;
baudrate=115200
mtdids=nand0=atmel_nand
mtdparts=mtdparts=atmel_nand:128k@0x0(at91bootstrap)ro,1m(u-boot)ro,2m(kernel),-(linux)
ver=U-BootVersion:1.0.1
stdout=usbser
stdin=usbser
stderr=usbser
bootargs="console=ttyS0,115200 ubi.mtd=linux root=31:4 lpj=598016 single"
silent=no
bootdelay=3

Environment size: 319/131068 bytes

On remplace quiet par single pour désactiver l'authentification, on ajoute un délai pour avoir le temps d'accéder au shell UART, et on passe silent à « no » pour s'assurer d'avoir une trace de démarrage sur le shell UART.

Pour cela, il faut être connecté simultanément au lien USB où l'on configure les nouveaux paramètres, et au port UART de debug interne, où le shell doit apparaître.

Double connexion
Connexion simultanée USB Link et UART debug

Une fois tous les paramètres configurés, l'exécution de la commande boot sur le lien USB via U-Boot déclenche un démarrage en mode recovery single user :

U-Boot
U-Boot> boot
Loading from nand0, offset 0x120000
   Image Name:   Bobcat-577
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    828996 Bytes = 809.6 KiB
   Load Address: 20008000
   Entry Point:  20008040
## Booting kernel from Legacy Image at 20008000 ...
   Image Name:   Bobcat-577
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    828996 Bytes = 809.6 KiB
   Load Address: 20008000
   Entry Point:  20008040
   Verifying Checksum ... OK
   XIP Kernel Image ... OK
OK

Et pendant ce temps, sur la connexion UART de debug :

Démarrage Linux
[    0.000000] Linux version 2.6.30 (joey@superserver) (gcc version 4.3.3 (GCC) )
[    0.000000] CPU: ARM926EJ-S [41069265] revision 5 (ARMv5TEJ), cr=00053177
[    0.000000] Machine: Dynojet Power Vision
[    0.000000] Clocks: CPU 192 MHz, master 96 MHz, main 16.000 MHz
...
[    0.930000] UBI: background thread "ubi_bgt0d" started, PID 97
[    0.950000] VFS: Mounted root (squashfs filesystem) readonly on device 31:4.
[    0.960000] Freeing init memory: 64K
/
# id
uid=0(root) gid=0(root)
# ggwp

À gauche, le shell U-Boot, et à droite, le shell UART affichant la séquence de démarrage.

Shell root obtenu
Accès root obtenu sur les deux shells simultanément

Shell du mode Recovery

Le shell obtenu est configuré dans un mode spécifique où seule une partie du firmware est montée. Nous avons maintenant besoin du firmware complet. Une approche serait de trouver le mot de passe de chiffrement Openssl et de déchiffrer le PVU_FILE. Mais commençons par une autre méthode.

En Analyse des outils, nous avions suspecté que le firmware pourrait être stocké en clair, et que seuls les fichiers de mise à jour seraient chiffrés. C'est la bonne réponse :

Démarrage Linux
[    0.410000] UBI: attaching mtd3 to ubi0
[    0.410000] UBI: physical eraseblock size:   131072 bytes (128 KiB)
[    0.420000] UBI: logical eraseblock size:    129024 bytes
...
[    0.910000] UBI: number of user volumes:     2
[    0.910000] UBI: available PEBs:             0
[    0.920000] UBI: total number of reserved PEBs: 2023
[    0.930000] UBI: max/mean erase counter: 110/68

Dans la séquence de démarrage, on peut voir qu'un système de fichiers UBI est monté depuis les périphériques MTD. En utilisant le shell root que nous avons maintenant, on trouve 2 périphériques intéressants : UBI0_0 et UBI0_1.

Pour les lire directement, on utilise dd et uuencode :

Bash
dd if=/dev/ubi0X of=stdout bs=SIZE count=COUNT 2&>/dev/null | uuencode -m ubi00

Et on extrait les données encodées en base64 depuis les logs minicom. On connaît la taille du firmware grâce au PVU_FILE (environ 11 Mo), et la taille de la puce mémoire grâce aux données u-boot des fichiers de recovery (128 Mo).

Bash
$ binwalk ubi00

DECIMAL       HEXADECIMAL     DESCRIPTION
-------------------------------------------------
0             0x0             Squashfs filesystem, little endian, version 4.0,
                              compression:gzip, size: 10473396 bytes, 455 inodes,
                              blocksize: 131072 bytes, created: 2019-09-04 20:48:04

$ binwalk ubi01

DECIMAL       HEXADECIMAL     DESCRIPTION
-------------------------------------------------
0             0x0             UBIFS filesystem superblock node, CRC: 0x8254BB9D
...
5062704       0x4D4030        Zip archive data, name: PVU_TYPE
5062781       0x4D407D        Zip archive data, name: PVU_CERT
5062975       0x4D413F        Zip archive data, name: PVU_FILE
...

Et voilà, l'intégralité du firmware est là :

  • UBI0_0 : la partie en lecture seule du firmware — binaires, arborescence et tout ce qui est essentiel au fonctionnement de l'appareil
  • UBI0_1 : la partie en lecture/écriture — licences, fichiers utilisateur, nouvelles mises à jour, etc.

Bingo, le reverse engineering peut commencer !

Dans le prochain épisode

  • Reverse engineering et émulation du firmware (focus sur le protocole propriétaire USB Link)
  • Butin (mots de passe, clés de chiffrement...)
  • Buffer overflow

Lire la Partie 2 →


Au nom d'Unicorn Security, nous souhaitons à tous les passionnés de cybersécurité une bonne année ! Restez à l'écoute pour la suite.


← Retour aux articles

Besoin d'un audit de sécurité ou d'un accompagnement sur mesure ?

Découvrir nos services →