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.
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).
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.
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
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.
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 :
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 :
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:
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
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é.
J'ai dû le découper pour voir le PCB réel :
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 !
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.
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> 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> 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.
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> 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 :
[ 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 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 :
[ 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 :
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).
$ 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
Au nom d'Unicorn Security, nous souhaitons à tous les passionnés de cybersécurité une bonne année ! Restez à l'écoute pour la suite.
Besoin d'un audit de sécurité ou d'un accompagnement sur mesure ?
Découvrir nos services →