SPG
3 minutos de lectura
Se nos proporciona el código fuente en Python utilizado para cifrar la flag:
from hashlib import sha256
import string, random
from secret import MASTER_KEY, FLAG
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from base64 import b64encode
ALPHABET = string.ascii_letters + string.digits + '~!@#$%^&*'
def generate_password():
master_key = int.from_bytes(MASTER_KEY, 'little')
password = ''
while master_key:
bit = master_key & 1
if bit:
password += random.choice(ALPHABET[:len(ALPHABET)//2])
else:
password += random.choice(ALPHABET[len(ALPHABET)//2:])
master_key >>= 1
return password
def main():
password = generate_password()
encryption_key = sha256(MASTER_KEY).digest()
cipher = AES.new(encryption_key, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(FLAG, 16))
with open('output.txt', 'w') as f:
f.write(f'Your Password : {password}\nEncrypted Flag : {b64encode(ciphertext).decode()}')
if __name__ == '__main__':
main()
Y también tenemos la salida del script:
Your Password : t*!zGnf#LKO~drVQc@n%oFFZyvhvGZq8zbfXKvE1#*R%uh*$M6c$zrxWedrAENFJB7xz0ps4zh94EwZOnVT9&h
Encrypted Flag : GKLlVVw9uz/QzqKiBPAvdLA+QyRqyctsPJ/tx8Ac2hIUl8/kJaEvHthHUuwFDRCs
Análisis del código fuente
Básicamente, el script toma un valor secreto MASTER_KEY y genera una contraseña basada en este. Luego, el valor de MASTER_KEY se usa para derivar una clave de AES usando SHA256 para cifrar el flag. Eso es lo que tenemos en main:
def main():
password = generate_password()
encryption_key = sha256(MASTER_KEY).digest()
cipher = AES.new(encryption_key, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(FLAG, 16))
with open('output.txt', 'w') as f:
f.write(f'Your Password : {password}\nEncrypted Flag : {b64encode(ciphertext).decode()}')
Por lo tanto, el objetivo es averiguar el valor de MASTER_KEY, para que podamos derivar la misma clave de AES y descifrar el texto cifrado.
Para lograr esto, debemos usar la contraseña generada, que proviene de generate_password:
def generate_password():
master_key = int.from_bytes(MASTER_KEY, 'little')
password = ''
while master_key:
bit = master_key & 1
if bit:
password += random.choice(ALPHABET[:len(ALPHABET)//2])
else:
password += random.choice(ALPHABET[len(ALPHABET)//2:])
master_key >>= 1
return password
Como se puede ver, se toman los bits de MASTER_KEY (interpretado como un número entero en little-endian) para elegir un carácter aleatorio de ALPHABET:
ALPHABET = string.ascii_letters + string.digits + '~!@#$%^&*'
El punto clave es que si el bit es 1, el carácter se toma al azar de la primera mitad de ALPHABET, mientras que si el bit es 0, el carácter se toma de la segunda mitad.
Solución
La idea es tomar los caracteres de la contraseña generada uno a uno y determinar si pertenece a la primera o a la segunda mitad de ALPHABET. Como resultado, sabremos si el bit correspondiente de MASTER_KEY es 1 o 0.
Podemos implementar un algoritmo simple para romper la contraseña y encontrar el valor de MASTER_KEY:
def crack_password(password):
master_key = 0
for i, p in enumerate((password)):
if p in ALPHABET[:len(ALPHABET) // 2]:
master_key |= 1 << i
return master_key.to_bytes((7 + len(password)) // 8, 'little')
Ahora podemos usar esta función main para encontrar el valor de MASTER_KEY y descifrar el texto cifrado:
def main():
password = 't*!zGnf#LKO~drVQc@n%oFFZyvhvGZq8zbfXKvE1#*R%uh*$M6c$zrxWedrAENFJB7xz0ps4zh94EwZOnVT9&h'
ciphertext = 'GKLlVVw9uz/QzqKiBPAvdLA+QyRqyctsPJ/tx8Ac2hIUl8/kJaEvHthHUuwFDRCs'
MASTER_KEY = crack_password(password)
encryption_key = sha256(MASTER_KEY).digest()
cipher = AES.new(encryption_key, AES.MODE_ECB)
print(unpad(cipher.decrypt(b64decode(ciphertext)), AES.block_size).decode())
Flag
Si ejecutamos el script, obtendremos la flag:
$ python3 solve.py
HTB{m4ll34bl3_p4ssw0rd_g3n3r4t0r!}
El script completo se puede encontrar aquí: solve.py.