Initialization
4 minutos de lectura
Se nos proporciona el código fuente en Python para cifrar la flag:
#!/usr/bin/env python3
import os
from Crypto.Util import Counter
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
class AdvancedEncryption:
def __init__(self, block_size):
self.KEYS = self.generate_encryption_keys()
self.CTRs = [Counter.new(block_size) for i in range(len(MSG))] # nonce reuse : avoided!
def generate_encryption_keys(self):
keys = [[b'\x00']*16] * len(MSG)
for i in range(len(keys)):
for j in range(len(keys[i])):
keys[i][j] = os.urandom(1)
return keys
def encrypt(self, i, msg):
key = b''.join(self.KEYS[i])
ctr = self.CTRs[i]
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
return cipher.encrypt(pad(msg.encode(), 16))
def main():
AE = AdvancedEncryption(128)
with open('output.txt', 'w') as f:
for i in range(len(MSG)):
ct = AE.encrypt(i, MSG[i])
f.write(ct.hex()+'\n')
if __name__ == '__main__':
with open('messages.txt') as f:
MSG = eval(f.read())
main()
Análisis del código fuente
En primer lugar, el script toma algunos mensajes de messages.txt
y los almacena en MSG
. Luego, ejecuta la función main
:
if __name__ == '__main__':
with open('messages.txt') as f:
MSG = eval(f.read())
main()
En main
, el script define una instancia de AdvancedEncryption
, cifra cada mensaje de MSG
y pone el resultado en output.txt
:
def main():
AE = AdvancedEncryption(128)
with open('output.txt', 'w') as f:
for i in range(len(MSG)):
ct = AE.encrypt(i, MSG[i])
f.write(ct.hex()+'\n')
También disponemos de messages.txt
:
[
'This is some public information that can be read out loud.',
'No one can crack our encryption algorithm.',
'HTB{?????????????????????????????????????????????}',
'Secret information is encrypted with Advanced Encryption Standards.',
]
Y también output.txt
:
2ac199d1395745812e3e5d3c4dc995cd2f2a076426b70fd5209cdd5ddc0a0c372feb3909956a791702180f591a63af184c27a6ba2fd61c1741ea0818142d0b92
30c6d0cd775b16c23c3f103a1fd883c4632c11366fbc07d92088cc5ddc0a0c373aef3f12c7606c114f546c7f6e00c87a
36fdb2d97d0a5bcf0225586a1e8abfc62d3057273aab5ae5309d8c4ade060a236aed070d817b2c14110e590b1b27ef5d4d35ddc001b47d6c2bca00101c25039a
2dcc93d07c4a16c833375f2b00d894c62c2d442d3cf90cd43183c559c10006372cea2c1595487c0f4314091c0c268b120f3aaabe7bd31c0c05977a7f7c4f6ce6f59392e0e522e66500e153f7a6f914c7
El método de cifrado implementado en AdvancedEncryption
es AES en modo CTR:
class AdvancedEncryption:
def __init__(self, block_size):
self.KEYS = self.generate_encryption_keys()
self.CTRs = [Counter.new(block_size) for i in range(len(MSG))] # nonce reuse : avoided!
def generate_encryption_keys(self):
keys = [[b'\x00']*16] * len(MSG)
for i in range(len(keys)):
for j in range(len(keys[i])):
keys[i][j] = os.urandom(1)
return keys
def encrypt(self, i, msg):
key = b''.join(self.KEYS[i])
ctr = self.CTRs[i]
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
return cipher.encrypt(pad(msg.encode(), 16))
Este modo es solo un cifrado en flujo, por lo que el algoritmo de cifrado es una operación XOR entre el texto claro con un stream generado por AES CTR:
Como resultado, si tenemos un par de texto claro y texto cifrado conocidos, y el mismo stream se reutiliza para otro texto cifrado, podemos encontrar el stream y descifrar el resto.
El fallo de seguridad
Puede parecer que cada mensaje usa una clave de AES diferente, pero hay un error:
def generate_encryption_keys(self):
keys = [[b'\x00']*16] * len(MSG)
for i in range(len(keys)):
for j in range(len(keys[i])):
keys[i][j] = os.urandom(1)
return keys
Todos los streams serán generadas por la misma clave porque [[...]*16] * len(MSG)
genera 4 referencias a la misma clave (debido a cómo funciona Python). Podemos probarlo fácilmente:
$ python3 -q
>>> import os
>>>
>>> keys = [[b'\x00']*16] * 4
>>> keys
[[b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00'], [b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00'], [b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00'], [b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00']]
>>>
>>> for i in range(len(keys)):
... for j in range(len(keys[i])):
... keys[i][j] = os.urandom(1)
...
>>> keys
[[b'\x06', b'e', b'6', b'\xd8', b'l', b'\xa4', b'\xaa', b'[', b'\xbd', b'\xad', b'\xa0', b'w', b'\xc3', b'\xac', b'\x8d', b'\xa0'], [b'\x06', b'e', b'6', b'\xd8', b'l', b'\xa4', b'\xaa', b'[', b'\xbd', b'\xad', b'\xa0', b'w', b'\xc3', b'\xac', b'\x8d', b'\xa0'], [b'\x06', b'e', b'6', b'\xd8', b'l', b'\xa4', b'\xaa', b'[', b'\xbd', b'\xad', b'\xa0', b'w', b'\xc3', b'\xac', b'\x8d', b'\xa0'], [b'\x06', b'e', b'6', b'\xd8', b'l', b'\xa4', b'\xaa', b'[', b'\xbd', b'\xad', b'\xa0', b'w', b'\xc3', b'\xac', b'\x8d', b'\xa0']]
>>>
>>> keys[0]
[b'\x06', b'e', b'6', b'\xd8', b'l', b'\xa4', b'\xaa', b'[', b'\xbd', b'\xad', b'\xa0', b'w', b'\xc3', b'\xac', b'\x8d', b'\xa0']
>>> keys[1]
[b'\x06', b'e', b'6', b'\xd8', b'l', b'\xa4', b'\xaa', b'[', b'\xbd', b'\xad', b'\xa0', b'w', b'\xc3', b'\xac', b'\x8d', b'\xa0']
>>> keys[2]
[b'\x06', b'e', b'6', b'\xd8', b'l', b'\xa4', b'\xaa', b'[', b'\xbd', b'\xad', b'\xa0', b'w', b'\xc3', b'\xac', b'\x8d', b'\xa0']
>>> keys[3]
[b'\x06', b'e', b'6', b'\xd8', b'l', b'\xa4', b'\xaa', b'[', b'\xbd', b'\xad', b'\xa0', b'w', b'\xc3', b'\xac', b'\x8d', b'\xa0']
Como se puede ver, las 4 claves son las mismas, por lo que sabemos que:
$$ C_i = K \oplus P_i \qquad\forall i \in \{0,1,2,3\} $$
Donde $C_i$ son los textos cifrados, $P_i$ son los mensajes de texto claro y $K$ es el stream de cifrado (el mismo para cada par de texto claro y texto cifrado). Por lo tanto, a partir de un par, tenemos
$$ K = C_3 \oplus P_3 $$
Y estamos interesados en $P_2$, por lo que
$$ P_2 = C_2 \oplus K = C_2 \oplus C_3 \oplus P_3 $$
Flag
Podemos obtener la flag fácilmente con Python:
>>> from pwn import unhex, xor
>>>
>>> P = [
... 'This is some public information that can be read out loud.',
... 'No one can crack our encryption algorithm.',
... 'HTB{?????????????????????????????????????????????}',
... 'Secret information is encrypted with Advanced Encryption Standards.',
... ]
>>> C = '''2ac199d1395745812e3e5d3c4dc995cd2f2a076426b70fd5209cdd5ddc0a0c372feb3909956a791702180f591a63af184c27a6ba2fd61c1741ea0818142d0b92
... 30c6d0cd775b16c23c3f103a1fd883c4632c11366fbc07d92088cc5ddc0a0c373aef3f12c7606c114f546c7f6e00c87a
... 36fdb2d97d0a5bcf0225586a1e8abfc62d3057273aab5ae5309d8c4ade060a236aed070d817b2c14110e590b1b27ef5d4d35ddc001b47d6c2bca00101c25039a
... 2dcc93d07c4a16c833375f2b00d894c62c2d442d3cf90cd43183c559c10006372cea2c1595487c0f4314091c0c268b120f3aaabe7bd31c0c05977a7f7c4f6ce6f59392e0e522e66500e153f7a6f914c7
... '''.splitlines()
>>>
>>> xor(unhex(C[2]), unhex(C[3]), P[3].encode())
b'HTB{d4mn_th3s3_ins3cur3_bl0ckch41n_p4r4m3t3rs!!!!}\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\xa7\x1d\x0ej\xfdK\xcf\xcfv\xe4b\xf3\xde\x1c\xd9l'
>>>