Mars@Hack 2022 — Break safe code — 2 Easy Write-up

Les Pires Hat
5 min readMay 8, 2022

--

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.

Schéma Électronique du système d’ouverture de la boîte

Une rapide recherche sur Google permet d’apprendre que l’ ATMega328pest 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.

Flowchart du programme d’ouverture de la boîte.

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).

Ouverture du binaire dans Ghidra.

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.

Fonction permettant d’initialiser le code de la boîte.

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.

Fonction permettant de retourner au format ASCII le bouton utilisé par l’utilisateur.

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.

Call Tree de la fonction read_input.

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.

Fonction permettant de renseigner le code.

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.

Décodage de la mémoire EEPROM pour en extraire le code.

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

--

--