Skip to main content

[ECW] - RandomKey

·5 mins· 0 · 0 ·
CTF ECW Crypto
JustinType
Author
JustinType
Cybersecurity Engineer and CTF Player
Table of Contents
ECW 2023 - This article is part of a series.
Part 1: This Article

Statement #


Statement

Source Code #


We are given the C file 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;
}

And the Python file challenge.py that uses this C file:

#!/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("""An encrypted message has been sent by AI ALICE to its control center. You have managed to get your hands on some code excerpts used by ALICE to encrypt its message as well as the encrypted text. Your mission is to find the plaintext message.""")
	
	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())

As well as the encrypted message enc.txt, generated by the Python script:

21952f9ced6c9109f8ce7c41cd3e0e6981c97a84745d5fdc75b2584e9a5a05e0

Source Code Analysis #


–> The challenge.py file:

  1. Imports the necessary libraries for encryption (AES in this case) and the C file generate_key.so, as well as the flag.txt file (containing the plaintext flag).
  2. Opens the flag.txt file and stores the plaintext flag in the variable FLAG.
  3. Utilizes an encryption function named encrypt that employs the AES algorithm in CBC mode, using the provided key parameter (variable key) and the IV FEDCBA9876543210.
  4. Utilizes the C file generate_key.so to create the key by passing the string “Control_center” as an argument.
  5. Encrypts the contents of the flag.txt file with the generated key and prints the encrypted message.

–> The generate_key.c file:

  1. Initializes a variable key corresponding to an array of 32 bytes, all set to 0.
  2. Utilizes a function that encrypts the contents of the parameter in with the MD5 algorithm and writes it to the parameter out.
  3. Utilizes a function that returns a 256-bit key (32 bytes) based on the parameter recipient_name.

⇒ Let’s focus on this function.

Key Generation Analysis #


Since we know the IV (FEDCBA9876543210) and the parameter that will be used to create the key (“Control_center”), we have everything we need to decrypt the message. We just need to understand how the key is generated.

Let’s review what the programs do in order:

  1. The Python file sends the string “Control_center” to the C file, so the parameter recipient_name = “Control_center”.

  2. The C file initializes a key with 32 bytes, all of these bytes are set to 0 (variable key):

    # Initialization of the key --> 32 bytes set to 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. The C file initializes variables to 0:

    1. An integer i (which will never be used)
    2. A file f that will be used by /dev/urandom
    3. Several time_t: now1 / now2 / delta
  4. The variable now1 is set to the current time (in seconds).

  5. The C file opens the /dev/urandom file and writes 32 random bytes to the key variable:

    # Writing 32 random bytes using the urandom function
    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. The C file uses the md5 function:

    1. It encrypts the value “Control_center” with the md5 algorithm and writes it to the key variable.

    2. However, it turns out that the value of md5(“Control_center”) returns only 16 bytes so only the first 16 bytes in the key variable are overwritten.

      Hash_MD5

      # Encrypts the value of recipient_name with the md5 algorithm
      md5(recipient_name) = 13 4a bb 7b d9 d2 48 a9 8d 91 4d ae a8 1a 87 37
      
      # As this value is 16 bytes, only the first 16 bytes in the key variable are overwritten
      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. The variable now2 is set to the current time (in seconds).

  8. The variable delta is set to now2 - now1 (which is 0).

  9. The C file overwrites the bytes in the key array starting from position key[8] with the value of the variable delta.

    1. delta is a variable of type time_t, and this type of variable has a length of 8 bytes so the C file will overwrite 8 bytes starting from position key[8]:

      Key2

      (⚠️ Note: Here the values are in decimal, you will need to convert them to hexadecimal or UTF-8 to get the flag).

  10. The C file returns the key array to the Python program.

In the end, the key used by the Python program is composed of:

  • The first 8 bytes of the result of md5(recipient_name)
  • 8 bytes set to 0 corresponding to the delta variable
  • 16 bytes generated randomly by /dev/urandom

Flag #


Since the IV used by the Python program is 8 bytes (FE DC BA 98 76 54 32 10), we know that AES will only use the first 8 bytes of the key because AES requires a key and an IV of the same length:

On Cyberchef:

  • Key = First 8 bytes of the result of md5(recipient_name) ⚠️ Cyberchef only takes UTF-8 or hex values, here I chose 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

Using a Python script:

from Crypto.Cipher import AES
import hashlib

# Input in hexadecimal
flag = bytes.fromhex("21952f9ced6c9109f8ce7c41cd3e0e6981c97a84745d5fdc75b2584e9a5a05e0")
# Recipient name for generating MD5
recipient_name = b'Control_center'
# The key, which corresponds to the first 8 bytes of the result md5(recipient_name)
key = hashlib.md5(recipient_name).hexdigest()[:16].encode()  # [:16] because 2 hex letters = 1 byte
# Display the flag
print(AES.new(key, AES.MODE_CBC, b'FEDCBA9876543210').decrypt(flag))

🚩 Flag = ECW{random_key_7AgmwlBXo1tDhyqR}

ECW 2023 - This article is part of a series.
Part 1: This Article