- Published on
UIUCTF 2023 – Group project
- Authors
- Name
- Lumy
Group project
In any good project, you split the work into smaller tasks...
Table of Contents
Source code
from Crypto.Util.number import getPrime, long_to_bytes
from random import randint
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
with open("/flag", "rb") as f:
flag = f.read().strip()
def main():
print("[$] Did no one ever tell you to mind your own business??")
g, p = 2, getPrime(1024)
a = randint(2, p - 1)
A = pow(g, a, p)
print("[$] Public:")
print(f"[$] {g = }")
print(f"[$] {p = }")
print(f"[$] {A = }")
try:
k = int(input("[$] Choose k = "))
except:
print("[$] I said a number...")
if k == 1 or k == p - 1 or k == (p - 1) // 2:
print("[$] I'm not that dumb...")
Ak = pow(A, k, p)
b = randint(2, p - 1)
B = pow(g, b, p)
Bk = pow(B, k, p)
S = pow(Bk, a, p)
key = hashlib.md5(long_to_bytes(S)).digest()
cipher = AES.new(key, AES.MODE_ECB)
c = int.from_bytes(cipher.encrypt(pad(flag, 16)), "big")
print("[$] Ciphertext using shared 'secret' ;)")
print(f"[$] {c = }")
if __name__ == "__main__":
main()
Solution
The code implements a simplified version of the Diffie-Hellman key exchange protocol and uses it to encrypt a flag with AES in ECB mode. The vulnerability lies in the use of ECB mode, which is considered insecure for encrypting multiple blocks of data.
Here's the step-by-step process of exploiting the vulnerability:
The code starts by generating a random prime number p and a generator g. It generates a random secret exponent a and calculates A = g^a mod p. It prompts the user to input a value for k. It performs checks on k to ensure it is not trivial (1, p-1, or (p-1)//2). It calculates Ak = A^k mod p. It generates another random secret exponent b and calculates B = g^b mod p. It calculates Bk = B^k mod p. It calculates the shared secret S = Bk^a mod p. It derives a key by computing the MD5 hash of the shared secret. It uses AES in ECB mode to encrypt the flag, flag, after padding it to a multiple of 16 bytes. The ciphertext is printed to the console.
Exploiting the vulnerability:
Since ECB mode operates on fixed-size blocks (in this case, 16 bytes), it suffers from the vulnerability known as "block repetition." This means that if two plaintext blocks are the same, their corresponding ciphertext blocks will also be the same. In our case, the flag is encrypted using ECB mode, and the input is a single block (16 bytes) because it's padded with pad().
To exploit the vulnerability, we need to provide a carefully crafted input to make the ECB encryption reveal information about the flag.
Input: Choose k as zero (k = 0). This choice allows us to manipulate the value of B and, consequently, the shared secret S. Calculation: Since B = g^b mod p, when k = 0, B = g^b mod p = g^0 mod p = 1 mod p. Encryption: With B = 1 and k = 0, the shared secret S = Bk^a mod p = 1^a mod p = 1 mod p. Key Derivation: The MD5 hash of the shared secret S is computed to derive the key. Since S = 1, the derived key is constant, regardless of the value of the flag. Encryption: The flag is encrypted using the derived key. Since the key is constant, the same flag will always produce the same ciphertext. Output: The ciphertext is printed to the console.
By choosing k = 0, we force the shared secret S to always be 1, regardless of the random values of a and b. Consequently, the derived key will always be the same, leading to the same encryption of the flag. This allows an attacker to decrypt the flag by capturing the ciphertext and applying the reverse process (i.e., decrypting with the known key).
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Util.number import long_to_bytes
import hashlib
ciphertext = 31383420538805400549021388790532797474095834602121474716358265812491198185235485912863164473747446452579209175051706
p = 127070793439028607536342064956109854662218266157044208126980741613544121747872790109904253675858877614031259552448034420759464977644517453088828464159338660593657754776599784967334431988540554838480413041178485501565755368029445783961582081929023155317774642808414211710028521467903617094501085543117747840077
g = 2
# Compute the shared secret
S = 1
# Derive the key
key = hashlib.md5(long_to_bytes(S)).digest()
# Initialize AES cipher in ECB mode with the derived key
cipher = AES.new(key, AES.MODE_ECB)
# Decrypt the ciphertext
decrypted_bytes = cipher.decrypt(long_to_bytes(ciphertext))
plaintext = unpad(decrypted_bytes, 16).decode()
# Print the decrypted plaintext
print(plaintext)
FLAG : uiuctf{brut3f0rc3_a1n't_s0_b4d_aft3r_all!!11!!}