[ECW] - RandomKey
Sommaire
ECW 2023 - Cet article fait partie d'une série.
Enoncé #
Code source #
On nous donne le fichier C generate_key.c
:
unsigned char key[32] = { 0 };
void md5(unsigned char *in, unsigned char *out)
{
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, in, strlen(in));
MD5_Final(out, &ctx);
}
unsigned char *generate_256bits_encryption_key(unsigned char *recipient_name)
{
int i = 0;
FILE *f = NULL;
time_t now1 = 0L;
time_t now2 = 0L;
time_t delta = 0L;
now1 = time(NULL);
f = fopen("/dev/urandom", "rb");
fread(&key, 1, 32, f);
fclose(f);
md5(recipient_name, key);
now2 = time(NULL);
delta = now2 - now1;
key[8] = delta;
return key;
}
Et le fichier python challenge.py
qui va utiliser ce fichier C :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from ctypes import *
from Crypto.Cipher import AES
SO_FILE = './generate_key.so'
FLAG_FILE = '../../flag.txt'
with open(FLAG_FILE, 'r') as f:
FLAG = f.read().encode()
def encrypt(plaintext, key):
return AES.new(key, AES.MODE_CBC, b'FEDCBA9876543210').encrypt(plaintext)
if __name__ == '__main__':
print("""Un message chiffré a été envoyé par l'IA ALICE à son centre de contrôle. Vous avez réussi à mettre la main sur certains extraits de code utilisés par ALICE pour chiffrer son message ainsi que sur le texte chiffré. Votre mission est de retrouver le message en clair.""")
generate_256bits_encryption_key = CDLL(SO_FILE).generate_256bits_encryption_key
generate_256bits_encryption_key.restype = c_char_p
key = generate_256bits_encryption_key(b'Control_center').hex().encode()
enc = encrypt(FLAG, key)
print('Enc:', enc.hex())
Ainsi que le message chiffré enc.txt
, généré par le script python :
21952f9ced6c9109f8ce7c41cd3e0e6981c97a84745d5fdc75b2584e9a5a05e0
Analyse du code source #
Le fichier challenge.py
:
- Importe les bibliothèques nécessaire au chiffrement (ici AES) ainsi que le fichier C
generate_key.so
et le fichierflag.txt
(contenant le flag en clair) - Ouvre le fichier
flag.txt
et stocke le flag en clair dans la variable FLAG - Utilise une fonction de chiffrement nommée
encrypt
qui va utiliser l’algorithme AES en mode CBC, la clé fournie en paramètre (variable key) et l’IV suivant :FEDCBA9876543210
- Utilise le fichier C
generate_key.so
pour créer la clé en lui passant comme argument la chaîne : “Control_center” - Chiffre le contenu du fichier
flag.txt
avec la clé générée et affiche le message chiffré
Le fichier generate_key.c
:
- Initialise une variable key correspondant à un tableau de 32 octets, tous ces octets sont initialisés à 0
- Utilise une fonction qui chiffre avec l’algorithme md5 le contenu du paramètre in puis l’écrit dans le paramètre out
- Utilise une fonction qui retourne une clé de 256 bits (soit 32 octets) à partir du paramètre recipient_name
⇒ Intéressons-nous à cette fonction
Analyse de la génération de la clé #
Comme nous connaissons l’IV (FEDCBA9876543210
) et le paramètre qui va être utilisé pour créer la clé (“Control_center
”), nous avons tout ce qu’il nous faut pour déchiffrer le message, il suffit de comprendre comment est générée la clé.
Reprenons ce que font les programmes dans l’ordre :
Le fichier python envoi la chaîne “Control_center” au fichier C, donc le paramètre recipient_name = “Control_center”
Le fichier C initialise une clé à 32 octets, ces 32 octets valent 0 (variable key)
# Initialisation de la clé --> 32 octets à 0 key = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Le fichier C initialise des variables Ă 0 :
- un entier i (qui ne sera jamais utilisé)
- un fichier f qui sera utilisé par /dev/urandom
- plusieurs time_t : now1 / now2 / delta
La variable now1 vaut le temps actuel (en secondes)
Le fichier C ouvre le fichier /dev/urandom et écrit 32 octets aléatoires dans la variable key :
# Ecriture de 32 octets aléatoires via la fonction urandom key = [74, 109, 81, 88, 78, 209, 124, 32, 79, 109, 179, 12, 134, 105, 235, 159, 23, 198, 191, 132, 252, 69, 80, 69, 23, 78, 181, 243, 163, 140, 13, 86]
Le fichier C utilise la fonction md5
Celle-ci chiffre la valeur “Control_center” avec l’algorithme md5, puis l’écrit dans la variable key
Or il s’avère que la valeur de md5(”Control_center”) ne renvoi que 16 octets donc seuls les 16 premiers octets dans la variable key sont écrasés
# Chiffre la valeur de recipient_name avec l'algorithme md5 md5(recipient_name) = 13 4a bb 7b d9 d2 48 a9 8d 91 4d ae a8 1a 87 37 # Comme cette valeur vaut 16 octets on Ă©crase seulement les 16 premiers octets dans la variable key key = [13, 74, 187, 123, 217, 210, 72, 169, 141, 145, 77, 174, 168, 26, 135, 55, 23, 198, 191, 132, 252, 69, 80, 69, 23, 78, 181, 243, 163, 140, 13, 86]
La variable now2 vaut le temps actuel (en secondes)
La variable delta vaut now2 - now1 soit 0
Le fichier C écrase les octets présent dans le tableau key à partir de de la position key[8] par la valeur de la variable delta
delta est une variable de type time_t, or ce type de variable a une longueur de 8 octets donc le fichier C va Ă©craser 8 octets Ă partir de la position key[8] :
(⚠️Attention ici les valeurs sont en décimal, il faudra les convertir en hexadécimal ou UTF-8 pour avoir le flag)
Le fichier C renvoi le tableau key au programme python
Au final la clé utilisé par le programme python est composée de :
- Les 8 premiers octets du résultat de md5(recipient_name)
- 8 octets Ă 0 correspondant Ă la variable delta
- 16 octets générés aléatoirement par /dev/urandom
Flag #
Comme l’IV utilisé par le programme Python fait 8 octets (FE DC BA 98 76 54 32 10
) on sait que AES va seulement utiliser les 8 premiers octets de la clé car AES a besoin d’une clé et d’un IV de même longueur :
Sur Cyberchef :
- Key = 8 premiers octets du résultat md5(recipient_name) ⚠️ Cyberchef ne prends que des valeurs en UTF-8 ou hexa, ici j’ai choisi UTF-8 :
13 4a bb 7b d9 d2 48 a9
- IV =
FE DC BA 98 76 54 32 10
- Mode = CBC / No padding
Avec un script python :
from Crypto.Cipher import AES
import hashlib
# Input en hexadecimal
flag = bytes.fromhex("21952f9ced6c9109f8ce7c41cd3e0e6981c97a84745d5fdc75b2584e9a5a05e0")
# Le recipient name pour générer le MD5
recipient_name = b'Control_center'
# La clé qui correspond aux 8 premiers octets du resultat md5(recipient_name)
key = hashlib.md5(recipient_name).hexdigest()[:16].encode() # [:16] car 2 lettres hexa = 1 octet
# Affichage du flag
print(AES.new(key, AES.MODE_CBC, b'FEDCBA9876543210').decrypt(flag))
đźš© Flag = ECW{random_key_7AgmwlBXo1tDhyqR}