Home Debugging de la BootROM du Raspberry Pi Pico
Post
Cancel

Debugging de la BootROM du Raspberry Pi Pico

La Raspberry Pi Foundation a annoncé la mise en vente d’un nouveau produit: le Raspberry Pi Pico. C’est un microcontrôleur ARM conçu par la fondation et vendu au prix de $4. Dans ce post on va voir comment mettre en place le setup pour déboguer la BootROM avec GDB puis avec IDA PRO.

Raspberry Pi Pico

Le Raspberry Pi Pico est le microcontrôleur avec une puce ARM RP2040 “Pi Silicon” conçue par la fondation Raspberry Pi. Tout comme les cartes Arduino, l’objectif de ce produit est de rendre l’électronique et la programmation accessible au plus grand nombre.

Voici quelques caractéristiques du Raspberry Pi Pico :

  • Puce de microcontrôleur RP2040
  • Processeur Arm Cortex-M0+ dual core 133 MHz
  • 264 Ko de SRAM
  • Une mémoire flash de 2 Mo
  • 40 broches GPIO, dont 26 multifonctions
  • 3 pins dédiés au Serial Wire Debug

Plusieurs projets ont déjà été réalisés avec cette carte, notamment un mineur de bitcoins à l’aide d’une Game Boy par Thomas Roth

Pico bootrom

La bootrom (ou Boot ROM) du Raspberry Pi Pico est le bootloader embarqué dans la puce RP2040. Ce bootloader est flashé lors de la fabrication de cette même puce. C’est un petit bout de code qui est en lecture seule et il n’est pas possible de le modifier. Généralement, le code source de ce genre de composant n’est pas disponible. Cependant, la fondation l’a publié sous licence open source sur Github.

Compilation

Pour pouvoir déboguer plus simplement la ROM, il va falloir la compiler dans un premier temps pour la charger sur le Pico. Pour ça, veillez à avoir les paquets suivants :

  • git
  • cmake
  • gcc-arm-none-eabi
  • doxygen (optionnel)

Ensuite vous pouvez cloner le repo Git et récupérer le SDK du RPi Pico :

1
2
λ ~ » git clone https://github.com/raspberrypi/pico-bootrom.git && cd pico-bootrom
λ ~ » git submodule update --init

Puis on peut générer le Makefile et compiler la ROM.

1
2
3
4
5
6
7
8
9
λ ~/pico-bootrom(master) » mkdir build
λ ~/pico-bootrom(master) » cd build 
λ ~/pico-bootrom/build(master) » cmake /
λ ~/pico-bootrom/build(master*) » make
...
[ 96%] Building C object CMakeFiles/bootrom.dir/pico_sdk/src/rp2_common/hardware_claim/claim.c.obj
[100%] Linking C executable bootrom.elf
[100%] Built target bootrom

La compilation prend quelques secondes et vous devriez avoir tous les fichiers suivants :

  • bootrom.elf : l’ensemble des fichiers C compilés et linkés au format ELF.
  • bootrom.dis : dump de segments et du code désassemblé du fichier bootrom.elf.
  • bootrom.elf.map : l’ensemble des symboles du fichier bootrom.elf.
  • bootrom.bin : le fichier bootrom.elf converti au format binaire (raw binary) sans symboles.

Ces fichiers générés par le Makefile seront utiles avec IDA Pro pour déboguer la ROM.

Serial Wire Debug (SWD)

L’interface SWD, est une interface de debugging permettant déboguer des systèmes embarqués ARM. L’usage du SWD est similaire au JTAG mais nécessite uniquement 2 pins : SWDIO et SWCLK. Cette interface est généralement disponible sur tous les appareils avec un SOC ARM, mais n’est pas forcément activée.

Heureusement pour nous, l’interface SWD est accessible sur le Raspberry Pi Pico comme vous pouvez le voir ci-dessous.

Setup

Pour mettre en place le setup de debugging, il faudra utiliser un autre Raspberry Pi sur lequel sera installé OpenOCD, qui est un outil open source qui peut être utilisé pour le debugging via le protocole SWD.

La documentation fournie pour la carte nous permet de câbler le Raspberry Pi Pico au RPi 3 ou 4.

Sur l’image ci-dessus, on peut voir 3 câbles branchés près du port micro USB. Ceux-ci ne sont pas nécessaires, mais permettent d’avoir une connexion UART au Raspberry Pico. On pourra s’y connecter plus tard sur le port /dev/ttyAMA0 depuis le RPi 3 ou 4.

Ci-dessous vous pouvez voir à quoi ressemble mon setup.

Au lieu de souder directement sur les ports du Raspberry Pico, j’ai choisi d’y souder des broches sur tous les ports pour faciliter le câblage sur une breadbord.

Pour déboguer la carte, je vais me connecter en SSH sur le Raspberry Pi 3. J’ai au préalable installé Raspberry Pi OS Lite sur une carte micro SD.

OpenOCD

Maintenant que tout est câblé, il faut installer OpenOCD. La Raspberry Pi foundation a publié une version modifiée qui supporte la puce RP2040. Pour compiler OpenOCD sur le RPi 3 (host) voici les instructions :

1
2
3
4
5
6
7
$ sudo apt install automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev
$ git clone https://github.com/raspberrypi/openocd.git --recursive --branch rp2040 --depth=1
$ cd openocd
$ ./bootstrap
$ ./configure --enable-ftdi --enable-sysfsgpio --enable-bcm2835gpio
$ make -j4
$ sudo make install

S’il n’y pas eu d’erreurs lors de la compilation, vous devriez pouvoir lancer OpenOCD en spécifiant les fichiers de configuration pour la cible : sudo openocd -f interface/raspberrypi-swd.cfg -f target/rp2040.cfg

OpenOCD va créer ouvrir plusieurs ports accessibles par défaut uniquement sur la machine host. Si vous souhaitez accéder au serveur que OpenOCD créé depuis une machine distance, il faut ajouter la ligne bindto 0.0.0.0 dans le fichier tcl/target/rp2040.cfg et relancer la commande sudo make install.

Vous pouvez tester la connexion via telnet dans un autre terminal sur le port 4444.

La commande targets permets de lister les cœurs du CPU et leur état vous pouvez stopper l’exécution d’un des cœurs avec la commande halt.

Dump de la BootROM

Maintenant que le Raspberry Pi Pico est prêt à être débogué, on va pouvoir lire la mémoire et de la ROM et l’extraire. Pour ça il faut savoir ou est chargée la ROM dans la mémoire.

Le fichier bootrom.elf.map contient les zones mémoire da la ROM ainsi que la taille et les attributs de chaque zone :

1
2
3
4
5
6
Memory Configuration

Name             Origin             Length             Attributes
ROM              0x0000000000000000 0x0000000000004000 xr
SRAM             0x0000000020000000 0x0000000000042000 xrw 
USBRAM           0x0000000050100400 0x0000000000000c00 rw

On peut dumper la ROM à l’aide de la commande dump_image comme ceci : dump_image pico_bootrom.bin 0x0 0x4000.

Avec la commande strings vous pouvez verifier que vous avez bien récupéré la ROM :

1
2
3
4
λ ~ » strings pico_bootrom.bin | grep "Raspberry"
(C) 2020 Raspberry Pi Trading Ltd
Raspberry Pi
Model: Raspberry Pi RP2

Pour ma part, voici le hash SHA1 du fichier : SHA1(pico_bootrom.bin)= 4bad39460ffb7ba32f0251bfca8132b4d458b419

GDB

On a vu que OpenOCD fonctionne. On peut y attacher un débugueur et par exemple déboguer la bootrom que l’on a compilé précédemment.

Cette fois depuis ma machine Linux, je lance gdb-multiarch qui supporte les exécutables armv6 puis je me connecte au serveur GDB de OpenOCD. Puis avec la commande load je charge la bootrom.

On voit que le PC est à 0x000000ee qui est l’adresse la fonction _start, qui correspond au point d’entrée de la bootrom.

Tout comme n’importe quel programme que vous souhaitez déboguer il est possible de placer des break points, sur un fichier et une ligne, ou même une adresse. Avec GDB on peut avoir une interface textuelle (TUI) avec la commande layout. Ci-dessous on peut voir le code s’exécuter et s’arrêter à mon point arrêt.

Comme vous pouvez le voir ci-dessus, on est dans une boucle infinie. Il faudrait charger une nouvelle image pour sortir de cette boucle.

IDA PRO

Pour la suite je vais utiliser IDA PRO comme désassembleur pour faire du debugging avancé, toujours à l’aide d’OpenOCD. Cette fois-ci je vais charger dans IDA le fichier bootrom.bin que l’on a précédemment extrait avec OpenOCD.

Setup

Lorsque vous charger la bootrom dans IDA Pro, vous pouvez spécifier les informations concernant l’organisation de la mémoire du binaire. Vous pouvez utiliser les mêmes zone mémoire qui ont été utilisées pour dumper la ROM.

La bootrom contient du code thumb2, donc il faudra le spécifier à IDA : alt+G, on choisit T et on met la valeur à 0x1. Ensuite vous pouvez tout sélectionner et générer l’assembleur avec la touche C. Vous devriez avoir la même vue ci-dessous.

Comme vu plus haut, on a chargé un fichier binaire brut. IDA n’a pas pu identifier de fonction. On pourrait nommer les fonctions d’après le fichier bootrom.elf.map. Mais la bootrom n’était pas exactement la même que le code source. Il nous faut donc un autre outil pour récupérer le nom des fonctions.

Diaphora

Diaphora est un plugin de binary diffing pour IDA PRO qui permet de comparer 2 fichiers binaires. On peut utiliser Diaphora pour différencier bootrom.elf qui contient tous les symboles, avec la bootrom que l’on a récupéré.

Pour cela il faut récupérer le code et charger le script diaphora.py dans IDA pour le fichier ELF, ensuite on peut générer la base de données pour le fichier binaire, mais cette fois-ci on va spécifier la base du premier fichier.

On peut lancer la comparaison. Le fichier étant assez léger, cela prend quelques secondes. Le plugin va ainsi nous créer de nouvelles fenêtres sur les différents matches qu’il a pu déterminer. On peut sélectionner toutes les fonctions et les importer. Les fonctions devraient s’afficher sur la gauche.

Diaphora n’as pas pu déterminer certaines fonctions, notamment main et async_task_worker donc les voici :

  • async_task_worker @ 0x000020E8
  • main @ 0x000024E8

La mise en place de Diaphora et l’import des fonctions n’est pas forcement nécessaire pour faire du debugging, mais cela facilite la tâche.

Debugging

Maintenant qu’on a notre IDB fonctionnelle, on peut se connecter au serveur GDB d’OpenOCD. Pour cela il faut spécifier l’adresse IP et le port du Raspberry Pi sur lequel est lance OpenOCD.

Pour ça on se rend dans debugger -> process options et vous choisissez l’IP et le port sur lequel écoute le serveur GDB.

Il ne vous reste plus qu’à appuyer sur le bouton play et la vue d’IDA passe sur le débugueur. Tout comme avec GDB, on peut placer des points d’arrêt, visualiser les registres et exécuter chaque instruction une par une.

Contrairement à GDB ou j’ai utilisé un fichier ELF avec symboles, le code source n’est pas disponible, donc il n’y a pas la pas la possibilité d’avoir une vue en C ou du pseudocode généré par le decompiler.

Utiliser IDA PRO pour ce genre de programme peut être un peu overkill, mais je pense que cela peut servir d’exemple pour des binaires plus complexes sur des systèmes embarqués.

De plus IDA Pro n’est pas forcement accessible (merci à Jiang Ying de me prêter sa license), heureusement Ghidra, devrait proposer un débugueur lors de sa prochaine release.

J’aurais bien aimé faire une démo avec Binary Ninja mais le débugueur ne supporte pas le serveur GDB d’OpenOCD


Pour résumer, on a setup le Raspberry Pi Pico pour faire du Serial Wire Debugging, on a dumpé la ROM, et on a configuré GDB et IDA Pro pour déboguer la ROM de deux façons différentes.

Ce genre de méthode peut s’appliquer à d’autres systèmes embarqués que le RPi Pico, mais celui-ci était le parfait exemple. Les ports permettant d’accéder au SWD sur les objets connectés (routeurs, caméras IP, etc.) ne sont pas forcément présents sur tous les objets connectés ou le protocole peut être désactivé par défaut.

Pour finir j’espère que ce post vous a plu, n’hésitez pas à me contacter sur Twitter ou même par mail si vous avez des questions


Sources :

This post is licensed under CC BY 4.0 by the author.