Aller au contenu

[ECW] - RandomKey

·5 mins· 0 · 0 ·
CTF ECW Crypto
JustinType
Auteur
JustinType
Ingénieur Cybersécurité et Joueur de CTF
Sommaire
ECW 2023 - Cet article fait partie d'une série.
Partie 1: Cet article

Enoncé #


Enonce

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:

  1. Importe les bibliothèques nécessaire au chiffrement (ici AES) ainsi que le fichier C generate_key.so et le fichier flag.txt (contenant le flag en clair)
  2. Ouvre le fichier flag.txt et stocke le flag en clair dans la variable FLAG
  3. 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
  4. Utilise le fichier C generate_key.so pour créer la clé en lui passant comme argument la chaîne : “Control_center”
  5. Chiffre le contenu du fichier flag.txt avec la clé générée et affiche le message chiffré

Le fichier generate_key.c :

  1. Initialise une variable key correspondant à un tableau de 32 octets, tous ces octets sont initialisés à 0
  2. Utilise une fonction qui chiffre avec l’algorithme md5 le contenu du paramètre in puis l’écrit dans le paramètre out
  3. 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 :

  1. Le fichier python envoi la chaîne “Control_center” au fichier C, donc le paramètre recipient_name = “Control_center”

  2. 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]
    
  3. Le fichier C initialise des variables Ă  0 :

    1. un entier i (qui ne sera jamais utilisé)
    2. un fichier f qui sera utilisé par /dev/urandom
    3. plusieurs time_t : now1 / now2 / delta
  4. La variable now1 vaut le temps actuel (en secondes)

  5. 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]
    
  6. Le fichier C utilise la fonction md5

    1. Celle-ci chiffre la valeur “Control_center” avec l’algorithme md5, puis l’écrit dans la variable key

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

      Hash_MD5

      # 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]
      

      Key1

  7. La variable now2 vaut le temps actuel (en secondes)

  8. La variable delta vaut now2 - now1 soit 0

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

    1. 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] :

      Key2

      (⚠️Attention ici les valeurs sont en décimal, il faudra les convertir en hexadécimal ou UTF-8 pour avoir le flag)

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

Cyberchef_flag

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}

ECW 2023 - Cet article fait partie d'une série.
Partie 1: Cet article