Sthack 2023 / Harduino
May 12, 2023Harduino writeup
Préambule
Arduino est la marque d’une plateforme de prototypage open-source qui permet aux utilisateurs de créer des objets électroniques interactifs à partir de cartes électroniques matériellement libres sur lesquelles se trouve un microcontrôleur. Wikipédia
Le nom du challenge nous donne une indication sur certains éléments du challenge. Nous allons sûrement a un certain point avoir à faire à une carte Arduino.
Analyse du Pcapng
 Le challenge commence avec un fichier pcapng que nous ouvrons avec Wireshark.
Nous observons une requête HTTP vers un Github ainsi que différents échanges TCP.
La page Github est la suivante :
https://github.com/crazyhardwaredev/harduino_loader/releases/download/ClosedSource/harduino_OTA
Page Github
Nous allons donc Ă©tudier le repo github :
Nous observons 4 commits et une release. La release a pour titre “Closed source” cela signifie que les sources ne sont pas fournies et que seul un binaire est présent. Nous analysons donc l’historique des commits.
Le premier commit contient du code en Rust qui a ensuite été supprimé lors du troisième commit.
Le premier commit contient donc un code effectuant une connexion TCP locale et envoyant un fichier binaire (ici /bin/id
) après l’avoir chiffré en AES 128. Des valeurs de clé et d’IV sont également fournies. Ces valeurs sont nécessaires au déchiffrement.
Nous récupérons également le fichier de release :
Le fichier de release est appelé harduino_OTA. Dans le monde de l’électronique embarquée OTA signifie Over The Air, dans ce cas on parle d’OTA update ou mise à jour à distance. Le projet étant également nommé harduino_loader et un loader étant un système de chargement de programme pour microcontrôleur nous pouvons déduire que le code Rust vu précédemment est celui qui a généré ce binaire.
Nous vérifions le fichier récupéré :
$ file harduino_OTA
harduino_OTA: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b37e83597ddd7e5c382d3fe3c847cb5dbc1e289f, for GNU/Linux 4.4.0, stripped
Le fichier est bien un binaire Linux ELF x86. La valeur stripped à la fin indique que les symboles ne sont pas présents dans le fichier et qu’il a donc été compilé en mode release.
Analyse du fichier binaire
Nous analysons le fichier binaire dans binary-ninja.
La fonction main est très courte mais contient un appel vers la fonction sub_9c20 qui elle-même contient la logique du programme.
Nous retrouvons la logique de l’application avec le chargement du firmwarre harduino.
Pour mieux comprendre le code nous décidons de compiler le code trouvé dans l’historique des commits du répo github.
Nous retrouvons donc que la fonction appelée avant le chargement du fichier est celle d’initialisation du chiffrement et que la clé et l’IV sont concaténés.
Nous regardons donc les valeurs des champs data_45055 et data_45065:
Nous retrouvons donc les 32 bytes de données formant la clé et le vecteur d’initialisation du chiffrement.
Nous allons donc récupérer le firmware uploadé.
Récupération et déchiffrement du firmware
De retour dans Wireshark nous cherchons la communication TCP locale sur le port 1337 comme indiqué dans le code trouvé. Une fois cet échange trouvé, nous récupérons le fichier en effectuant un clic droit > Follow > TCP stream
que nous affichons en raw.
Nous copions le contenu ainsi récupéré dans un fichier et à l’aide d’un code Python et des clé et IV récupérés nous déchiffrons le binaire.
from Crypto.Cipher import AES
import binascii
my_key = binascii.unhexlify("87df86f0ba4e95dae94be985609f31a7")
iv = binascii.unhexlify("6e3329c538cfca39ec3a0742273dad68")
encryptor = AES.new(my_key, AES.MODE_CBC, IV=iv)
file_content = open("dump.raw",).read()
decrypted = encryptor.decrypt(binascii.unhexlify(file_content))
open("firmware.bin", "wb").write(decrypted)
Nous retrouvons bien un exécutable ARM dans le fichier firmware.bin
$ file firmware.bin
firmware.bin: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
Nous analysons alors le fichier récupéré.
Analyse du firmware
Le firmware n’est pas strippé donc les méthodes sont présentes :
Cela nous permet de nous rendre facilement dans la fonction setup qui initialise le microcontrĂ´leur.
Nous retrouvons donc la fonction beginAP indiquant le SSID MyAccessPoint ainsi qu’un mot de passe. Cependant r1_8, la variable contenant le mot de passe au moment de la création de l’AP, a subi des modifications par rapport à la valeur vu dans le memcpy.
Nous récupérons donc le code C du dé-compilateur et remplaçons les méthodes Arduino par des méthodes C standard pour récupérer la clé une fois ces modifications effectuées.
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv){
int var_50 = 0x2a; // 42
char* cle_wifi = "Z7cX_Ud-3A5E'1m)=?cJU664";
int len_cle = strlen(cle_wifi);
void* r1_8 = (&var_50 - (((len_cle + 7) >> 3) << 3));
int r2 = (len_cle - 1);
*(char*)(((char*)r1_8 + 8) + len_cle) = 0;
while (r2 != 0xffffffff)
{
int r3_1 = ((len_cle - r2) - 1);
char r1_11 = *(char*)(cle_wifi + r2);
r2 = (r2 - 1);
*(char*)(((char*)r1_8 + 8) + r3_1) = r1_11;
}
for (int r1_12 = 0; len_cle != r1_12; r1_12 = (r1_12 + 1))
{
unsigned int r3_8 = ((unsigned int)(*(char*)(cle_wifi + r1_12) ^ r1_12));
char r3_9;
if (r3_8 <= 0x20)
{
r3_9 = (r3_8 + 0x1f);
}
if ((r3_8 > 0x20 && r3_8 > 0x7d))
{
r3_9 = (r3_8 - 0x7d);
}
if ((r3_8 <= 0x20 || (r3_8 > 0x20 && r3_8 > 0x7d)))
{
r3_8 = ((unsigned int)r3_9);
}
*(char*)((((char*)r1_8 - (((len_cle + 7) >> 3) << 3)) + 8) + r1_12) = ((char)r3_8);
}
*(char*)((((char*)r1_8 - (((len_cle + 7) >> 3) << 3)) + 8) + len_cle) = 0;
printf("%s",((char*)r1_8 - (((len_cle + 7) >> 3) << 3)) + 8);
}
Une commande gcc plus tard nous récupérons la clé Wi-Fi.
$ ./decode_cle
Z6a[[Pb*;H?N+<c&-.qYA#?#
Récupération du flag
Alors nous ouvrons le gestionnaire de Wi-Fi mais sans trouver le SSID MyAccessPoint. Est-il caché ? Eh bien non, il suffisait juste de se balader dans la mairie pour le trouver. Une fois a porté nous pouvons nous connecter. Reste-t-il à savoir que faire une fois connecté.
En analysant la suite du code on peut retrouver une réponse HTTP:
Il nous faut donc trouver l’adresse IP de l’Arduino sur le réseau. Pour cela on peut revenir dans la fonction setup et chercher la méthode associée.
arduino::IPAddress::IPAddress(&var_34, 0xc0, 0xa8, 4, 0x2a);
On y retrouve l’adresse suivante une fois décodée : 192.168.4.42
Un curl et le flag est Ă nous
$ curl 192.168.4.42
Welcome to the flag provider
The flag is: STHACK{H@rdU1n0_107_MuCh_FuN}
Bingo :sparkles: