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
.