Catégories
ECW 2021 Reverse Write-ups

ECW 2021 – Chest

Dans cet article nous allons faire le write-up d’un challenge de reverse engineering plutôt facile et sympa que j’ai pu résoudre lors de ma qualification au CTF de l’ECW (European Cyber Week). C’était le premier challenge que je résolvais pour l’ECW et c’est celui-ci qui m’a donné envie de me lancer pleinement dans le CTF !

Énoncé du challenge

Find the key to the safe.

Prove to him that this was not a good idea.

Fichier du challenge

Recherches sur le format

Une fois la pièce jointe au challenge téléchargée, on se retrouve avec un fichier “chest.hex” qui semble alors être un fichier texte un peu spécial.

:100000000C9434000C9449000C9449000C94490061
:100010000C9449000C9449000C9449000C9449003C
:100020000C9449000C9449000C9449000C9449002C
:100030000C9449000C9449000C9449000C9449001C
:100040000C9449000C9449000C9449000C9449000C
:100050000C9449000C9449000C9449000C944900FC
:100060000C9449000C94490011241FBECFEFD8E036
:10007000DEBFCDBF11E0A0E0B1E0E8EAF1E002C0F0
:1000800005900D92A632B107D9F70E94CA000C94D0
:10009000D2000C9400001092C5008093C40088E147
:1000A0008093C10086E08093C20008959091C000C3
:1000B00095FFFCCF8093C60008950F931F93CF93B5
:1000C000DF93EC018C01060F111DC017D10721F041
:1000D00089910E945600F9CFDF91CF911F910F9126
:1000E00008958091C00087FFFCCF8091C6000895DD
:1000F0000F931F93CF93DF93EC018C01060F111D1B
:10010000C017D10721F00E9471008993F9CFDF91C8
:10011000CF911F910F910895CF93DF93CDB7DEB7A5
:100120002B970FB6F894DEBF0FBECDBF8BE0EBE18F
:10013000F1E0DE01119601900D928A95E1F76BE0F6
:10014000CE0101960E945D000E9471002B960FB6B1
:10015000F894DEBF0FBECDBFDF91CF910895FF921F
:100160000F931F93CF93DF93F82EC0E0D1E00CE103
:1001700011E0888184508F250E94560022960C172A
:100180001D07B9F78AE0DF91CF911F910F91FF9082
:100190000C94560087E60E944B000E948C000E943F
:0801A000AF00FBCFF894FFCF84
:1001A8004F5551505D62795D73546F577042435596
:1001B800717A3E3842454378773300456E746572EC
:0601C800206B65790A00BE
:00000001FF

Je décide de coller la première ligne du fichier sur Google pour débuter mes recherches sur ce fichier “.hex”. Je tombe alors sur plusieurs sources cohérentes qui parlent d’un format HEX (Intel) et de l’architecture AVR d’Atmel, comme sur ce Github : https://github.com/Hossam-Elbahrawy/16×2-LCD.

Exemple de résultat de recherche (github)

Compilation du firmware

On se rend alors compte que le programme n’est pas assemblé. Nous allons alors chercher comment assembler celui-ci pour se retrouver avec un binaire (firmware en l’occurence) que l’on pourra alors ouvrir avec IDA ou Ghidra par exemple.

Après quelques recherches, l’outil avr-objcopy permettrait de convertir notre fichier .hex en firmware au format. Celui-ci se trouve dans le package binutils-avr que l’on installe facilement.

$ sudo apt-get install binutils-avr
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances       
Lecture des informations d'état... Fait
Les paquets suivants ont été installés automatiquement et ne sont plus nécessaires :
  linux-hwe-5.4-headers-5.4.0-42 linux-hwe-5.4-headers-5.4.0-52 linux-hwe-5.4-headers-5.4.0-58 linux-hwe-5.4-headers-5.4.0-59 linux-hwe-5.4-headers-5.4.0-60
  linux-hwe-5.4-headers-5.4.0-62 linux-hwe-5.4-headers-5.4.0-65 linux-hwe-5.4-headers-5.4.0-66 linux-hwe-5.4-headers-5.4.0-67 linux-hwe-5.4-headers-5.4.0-70
  linux-hwe-5.4-headers-5.4.0-71 linux-hwe-5.4-headers-5.4.0-72 linux-hwe-5.4-headers-5.4.0-73 linux-hwe-5.4-headers-5.4.0-74 linux-hwe-5.4-headers-5.4.0-81
  linux-hwe-5.4-headers-5.4.0-84
Veuillez utiliser « sudo apt autoremove » pour les supprimer.
Les NOUVEAUX paquets suivants seront installés :
  binutils-avr
0 mis à jour, 1 nouvellement installés, 0 à enlever et 265 non mis à jour.
Il est nécessaire de prendre 1 475 ko dans les archives.
Après cette opération, 15,5 Mo d'espace disque supplémentaires seront utilisés.
Réception de :1 http://us.archive.ubuntu.com/ubuntu bionic/universe amd64 binutils-avr amd64 2.26.20160125+Atmel3.6.0-1 [1 475 kB]
1 475 ko réceptionnés en 3s (539 ko/s)        
Sélection du paquet binutils-avr précédemment désélectionné.
(Lecture de la base de données... 630001 fichiers et répertoires déjà installés.)
Préparation du dépaquetage de .../binutils-avr_2.26.20160125+Atmel3.6.0-1_amd64.deb ...
Dépaquetage de binutils-avr (2.26.20160125+Atmel3.6.0-1) ...
Paramétrage de binutils-avr (2.26.20160125+Atmel3.6.0-1) ...
Traitement des actions différées (« triggers ») pour man-db (2.8.3-2ubuntu0.1) ...

On se retrouve alors avec toute une toolchain associée :

$ avr-
avr-addr2line  avr-as         avr-elfedit    avr-ld         avr-nm         avr-objdump    avr-readelf    avr-strings    
avr-ar         avr-c++filt    avr-gprof      avr-ld.bfd     avr-objcopy    avr-ranlib     avr-size       avr-strip 

Comme convenu on va utiliser avr-objcopy pour transformer notre “chest.hex” en un firmware du nom de “chest.bin”.

$ avr-objcopy -I ihex chest.hex -O binary chest.bin
$ file chest.bin
chest.bin: data
$ binwalk -A chest.bin # -A pour scanner des opcodes

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------

fqjb3323@EB-L-OR6221619:~/ECW/chest$ binwalk -A chest.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
189           0xBD            AVR8 instructions, function prologue
243           0xF3            AVR8 instructions, function prologue
355           0x163           AVR8 instructions, function prologue

On a bien assemblé/compilé notre binaire en AVR8 (la documentation est d’ailleurs trouvable ici) comme on peut le voir avec la sortie du binwalk, on va pouvoir passer à la phase de reverse engineering !

Reverse engineering

Avant tout, je commence par faire un strings sur le binaire pour voir si des chaines de caractères intéressantes s’y trouvent.

$ strings chest.bin 
OUQP]by]sToWpBCUqz>8BECxw3
Enter key

Seulement 2 chaines de caractères ressortent : le message “Enter key” ainsi que ce qui ressemble à un flag chiffré… Allons voir ça de plus près avec un désassembleur !

Pour avoir testé ce challenge sur IDA et Ghidra, je dois pour une fois admettre préférer Ghidra (assez rare pour être souligné 🙃)…

Pour le choix du langage, je renseigne bien AVR8 en processeur et prends la configuration de base en version gcc (probablement compilé avec avr-gcc de la toolchain binutils-avr).

Choix du langage dans Ghidra

Je me mets ensuite à la recherche de la chaine de caractères identifiée plus tôt (celle semblable à un flag chiffré).

Flag chiffré en raw data

Je vais ensuite définir cette data comme une chaine de caractère pour que Ghidra la gère mieux (clic droit > data > TerminatedCString) et je renomme le label en “encrypted_flag”.

Vient ensuite la recherche de références à ce label : une seule apparaît.

Références au label “encrypted_flag”

Ni une ni deux, je regarde ce que fait la fonction qui utilise ce flag chiffré.

Fonction qui utilise notre label “encrypted_flag”

Ici cela saute rapidement au yeux : le programme va boucler pour copier notre “encrypted_flag” en “DAT_mem_0100” (que je renommerai par la suite “copy_enc_flag”).

Pour mieux comprendre, voici quelques explications :
– X, Y et Z sont des stack pointers, R[0-26] sont des registres généraux
– L’instruction lpm (load program memory) en code:0040 va charger l’octet contenu à Z=>encrypted_flag dans R0 et incrémenter Z
– L’instruction st (store) en code:0041 va stocker R0 dans X=>copy_enc_flag et incrémenter X
– La boucle sera répétée 0x26 fois (strlen(encrypted_flag + “Enter key\n”) + 1 pour le null byte)

Il va donc ensuite être intéressant de trouver où cette copie de notre chaine de caractère va être utilisée !

Références à “copy_enc_flag”

On voit alors une référence en lecture à cette copie du flag chiffré, la première référence étant le code que l’on vient d’examiner (d’où le contexte d’écriture). Allons regarder ça.

Code de la fonction utilisant “copy_enc_flag”

Cette fonction est encore une fois très simple. Elle va, pour chaque caractère de notre flag chiffré, soustraire 4 puis le XOR avec R15.

En effet à chaque tour le programme va :
– Mettre le caractère pointé par Y=>copy_enc_flag dans Wlo
– Soustraire 4 à Wlo
– XOR Wlo par R15
– Appeler une fonction dont on ne se préoccupera pas
– Ajouter 2 au pointeur Y (on va alors sauter un caractère à chaque tour de boucle)

Cela sera répété sur 0x1a soit la longueur du flag chiffré.

Flag

Il ne nous manque plus qu’une information : la valeur avec laquelle chaque caractère sera XORé. En effet, nous avons vu que chaque caractère va être XORé avec R15. En début de fonction, R15 est initialisé à Wlo : il nous faut remonter à l’appel de la fonction pour déterminer la valeur a Wlo lors de l’appel de celle-ci… Cette manière étant un peu fastidieuse j’ai plutôt opté pour un brute-force de la clé étant donné que celle-ci ne fait qu’un octet 🙂.

J’ai alors sortit mon ami Cyberchef : je commence par soustraire 4 à chaque caractère, fais un BF de toutes les clés possibles sur un octet et je recherche un flag qui commence par “E” (le format de flag étant “ECW{“). Bingo !

Résultat de cyberchef

Je me retrouve alors avec le flag suivant :

E_CBWP{Wa^e]b01_cx4:0O1z}!

A ce moment je me souviens que Y est incrémenté de 2 dans la fonction et qu’il faut donc que je prenne un caractère sur 2 :

ECW{aeb1c401}

C’est ainsi ça que se terminait alors mon premier challenge de reverse engineering à l’ECW 🙂 !