Mars@Hack 2022 — Break safe code — 2 Easy Write-up
Le CTF de Mars@Hack se déroulait durant la journée du 6 mai 2022 à Mont-de-Marsan. Notre équipe a fini 1ère au classement général sur 40 équipes.
Pour ce write-up, je vais vous expliquer comment 3 paquets d’Haribo m’ont motivé à reverse un code Arduino pour ouvrir une boîte.
TL;DR
En m’aidant du fichier flowchart.png
, je pouvais déduire les fonctions dans le programme.
La fonction permettant d’initialiser un code révèle un XOR dynamique en commençant avec la clé 0xc6
.
Il m’a ensuite suffi de décoder la mémoire EEPROM via le XOR dynamique pour récupérer le code de la boîte.
Introduction
Le challenge fournit :
- un schéma électronique
- un schéma du flow d’exécution du programme
- le binaire
- un dump de la mémoire EEPROM après avoir initialisé le code
Voici le schéma électronique. Ce schéma permet d’avoir les informations sur le microcontrôleur utilisé et l’ordre de raccordement des différents boutons.
Une rapide recherche sur Google permet d’apprendre que l’ ATMega328p
est un microcontrôleur de 8bits.
Le flow d’exécution permet de comprendre comment le programme fonctionne. Je peux potentiellement en déduire qu’il y a une fonction par action effectuée.
Bypass ?
La première idée que j’ai eue est de couper l’alimentation électrique du boîtier pour rentrer mon propre code et ensuite ouvrir la boîte avec celui-ci. Cependant, l’alimentation du boîtier étant dans la boîte, il m’est impossible de réaliser ce trick. Il va donc falloir reverse.
Compréhension du programme
Lorsque j’essaye d’ouvrir le programme depuis Ghidra, celui-ci ne reconnaît pas le langage utilisé. Il faut alors lui spécifier le langage AVR8
pour avoir un code lisible (s/o @Hackdaddy).
Function where is you ?
En me basant sur le flowchart, je sais qu’une interaction avec le bouton *
est réalisée au démarrage pour initialiser le code. La fonction Reset
réalise un appelle à la fonction FUN_code_0192
qui un peu plus loin dans le code effectue une comparaison entre un registre et le caractère *
.
Il y a donc de grandes chances que cette fonction soit celle permettant d’initialiser le code au démarrage.
En rentrant dans la fonction FUN_code_008c
qui est appelée juste avant la comparaison, je m’aperçois à la fin de celle-ci des assignations de valeurs qui correspondent à nos colonnes dans le schéma électronique.
Je constate facilement que les premières valeurs retournées sont 0x31
= 1
, 0x34
= 4
, 0x37
= 7
, 0x2a
= *
. Ces valeurs correspondent bien à nos boutons dans la première colonne.
Je suis maintenant sûr que cette fonction lit l’entrée utilisateur et renvoie la valeur ASCII du bouton pressé.
En pensant qu’il n’existe qu’une seule fonction pour lire l’entrée utilisateur, je regarde les fonctions qui appellent celle-ci pour trouver celle qui attend le code rentré par l’utilisateur et ensuite le compare à la mémoire EEPROM.
Ghidra me renvoie 3 fonctions qui appellent celle-ci. Je sais déjà qu’une des fonctions et celle de laquelle je viens, donc je vais m’intéresser aux deux autres fonctions.
Décodage de l’EEPROM
En regardant de plus près la fonction FUN_code_0110
et FUN_code_0167
, je remarque que la fonction FUN_code_0167
est plus intéressante, car elle boucle tant que le bouton #
n’est pas pressé. Si je me réfère au flowchart, je sais que c’est le bouton attendu pour terminer l’initialisation du code.
Il y a donc des chances que ce soit cette fonction qui écrive dans l’EEPROM. Analysons-la pour comprendre son fonctionnement.
Tant que Ylo
ne vaut pas 0x19
(cette valeur doit être la taille maximum du code de la boîte) alors le programme lit l’entrée utilisateur, il break
ensuite la boucle s’il rencontre le caractère #
et XOR le caractère entré par l’utilisateur avec une valeur.
La clé du premier XOR est la valeur de Yhi
qui vaut 0xc6
. Ensuite Yhi
prend la valeur du registre Wlo
qui est la valeur rentrée par l’utilisateur.
Pour décoder la mémoire EEPROM et retrouver le code entier, je sais que je vais devoir XORer la première valeur avec 0xc6
puis les suivantes avec le résultat du XOR précédent.
La première valeur de la mémoire vaut 0xb
, si je réalise un XOR de cette valeur avec 0xc6
j’obtiens 0xcd
qui n’est pas un caractère de la table ASCII. Mince, j’essaye avec la deuxième valeur, 0xfe ^ 0xc6 = 56
, 56
vaut 8
en ASCII. Je tiens potentiellement mon premier numéro. Je déduis que la première valeur de la mémoire EEPROM serait la taille du code 0xb
(soit 11
caractères).
Récupération du code
Avec un rapide script python, je peux récupérer le code depuis la mémoire EEPROM.
Le code est 8*0361*1239
.
J’entre le code sur le coffre et il s’ouvre ! Voilà comment gagner trois paquets d’Haribo et maintenir le moral des troupes !
flag: fl@g{8*0361*1239}
Live
Le moment de l’ouverture du coffre peut être visionné en live (de loin) via ce lien: https://www.youtube.com/watch?v=VbB3HouG5Mo&t=16540s
Contactez-moi :
Site Web personnel
Twitter de l’équipe : LesPiresHat