Solution de irko_okir pour ASCII Badge

intro hardware forensics

9 août 2026

Décompression du dump

gunzip badge-dump.img.gz

En lisant le tuto fournit par le challenge pour construire son badge (https://github.com/FCSC-FR/hackropole-badge/blob/61d627868d038e7277b8fd4fc4294c639a5fc889/assembly/README.fr.md), on découvre que celui-ci est ESP32-S3-Zero. Cette information me permet de savoir comment est la table de partition, pour extraire les partitions qui m’interessent.

Extraction avec dd

J’utilise dd de cette manière :

dd if=fichier_défi of=nom_fic_sortie bs=1(pour exprimer les valeurs suivantes en octets) skip=$((premier_octets_extrait)) count=$((taille_extraction))

En me renseignant sur les mémoires flash des ESP32, et sa table de partition. J’imagine trouver le flag dans le nvs ou le spiffs.

J’ai premièrement pensé que j’allais trouvé des choses intéressantes dans le NVS (Non Volatille Storage) car ici sont stockés des paramètres persistant après rédémarage.

dd if=badge-dump.img of=nvs.bin bs=1 skip=$((0x9000)) count=$((0x5000))

Je n’ai rien trouvé d’interresant.

J’essaye donc d’extraire son SPIFFS (Serial Peripheral Interface Flash File System)

dd if=badge-dump.img of=spiffs.bin bs=1 skip=$((0x310000)) count=$((0xf0000))

En faisant un strings on tombe sur des choses intéréssantes:

strings spiffs.bin 
littlefs/
presets.json
wsec.json
cfg.json7P
{"nw":{"ins":[{"psk":""}]},"ap":{"psk":"wled1234"},"if":{"mqtt":{"psk":""},"hue":{"key":"api"}},"pin":"","ota":{"pwd":"wledota","lock":false,"lock-wifi":false,"aota":true}}

On découvre que notre extraction contient un sytème de fichier LittleFS

Analyse du sytème de fichier

On va utiliser l’outil mklittlefs pour analyser notre système de fichier :

On télécharge et on compile :

git clone https://github.com/earlephilhower/mklittlefs.git
cd mklittlefs
make

On créer un répertoire pour extraire :

mkdir extracted

Puis on extrait :

./mklittlefs \
  -b 4096 \
  -p 256 \
  -s 0xf0000 \
  -u extracted \
  ../spiffs.bin
littlefs/lfs.c:4546:debug: Found older minor version v2.0 < v2.1
littlefs/lfs.c:4623:debug: Found pending gstate 0x000002000000000000000000
/cfg.json	 > ./extracted/cfg.json	size: 2836 Bytes
/presets.json	 > ./extracted/presets.json	size: 46549 Bytes
/wsec.json	 > ./extracted/wsec.json	size: 172 Bytes

On va pouvoir analyser les 3 fichiers qui nous interessent !

On se place dans extracted :

cd extrated

En faisant un strings presets.json on remarque à la fin :

"17":{"playlist":{"ps":[1,2,3,2,4,5,6,7,8,9,10,6,9,11,12,9,12,13,6,9,3,12,7,14,15,16],"dur":[10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000],"transition":[7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7],"repeat":0,"end":0,"r":0},"on":true,"n":"flag"}}

On remarque que le preset 17 s’appelle “flag”.

Analyse de presets.json

Dans WLED les presets sont des configurations enregistrés

Je vais utiliser jq pour y voir plus claire dans les affichages.

J’aimerais maitenant connaitre les noms des autres presets :

jq '.[] | {name:.n}'

Je remarque que les autres presets s’apellent “char0” à “char15” (donc 16 caractères) et comme par hasard dans le preset 17 il y a un playlist avec avec 16 numéros. Je vais donc devoir décrypter chaque caractère pour reconstituer la playlist qui me donnera le flag.

Décryptage des char

On va analyser les 16 char :

jq '.[] | select(.id <= 16)' presets.json

On remarque que chaque char possède 8 id, ce qui est cohérent avec les 8 leds sur la photo du défi. De plus un paramètre m’interpelle car il diffère entre les leds des différents char :

bri (la brightness)

Celle-ci est toujours fixé à 1 ou 255 ce qui représente si la led est éteinte ou allumée

un ASCII tient sur 8 bits, et si chaque led allumée représentait un 1 et celles éteintes un 0 ?

On va faire une commande un peu élaborée qui va nous permettre de récupérer le binaire de chaque char :

jq -r '
to_entries[]
| select((.value.n // "") | startswith("char"))
| [
    range(1;9) as $i
    | if ([.value.seg[] | select(.id == $i and (.bri // 0) > 1)] | length) > 0
      then "1"
      else "0"
      end
  ]
| join("")
' presets.json > resultat.txt

On vérifie que ca marche bien en convertissant le premier binaire de notre fichier en ASCII et ca marche pas, c’est un cararctère ilisible !

Mais ! Nous savons que nous somme dans un défi et que le flag doit probablement commencer par FCSC. On compare le binaire de F et notre caractère

? : 10001101

F : 1000110

Il suffit d’enlever les 1 à la fin de chacun de nos caractères.

Je vous laisse le plaisir de convertir tout vos binaires par des ASCII et de les écrire dans l’ordre de la playlist du flag (rappel [1,2,3,2,4,5,6,7,8,9,10,6,9,11,12,9,12,13,6,9,3,12,7,14,15,16]).