Multipage Recyclings
3 minutos de lectura
Se nos proporciona el código fuente del servidor en Python:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random, os
FLAG = b'HTB{??????????????????????}'
class CAES:
def __init__(self):
self.key = os.urandom(16)
self.cipher = AES.new(self.key, AES.MODE_ECB)
def blockify(self, message, size):
return [message[i:i + size] for i in range(0, len(message), size)]
def xor(self, a, b):
return b''.join([bytes([_a ^ _b]) for _a, _b in zip(a, b)])
def encrypt(self, message):
iv = os.urandom(16)
ciphertext = b''
plaintext = iv
blocks = self.blockify(message, 16)
for block in blocks:
ct = self.cipher.encrypt(plaintext)
encrypted_block = self.xor(block, ct)
ciphertext += encrypted_block
plaintext = encrypted_block
return ciphertext
def leak(self, blocks):
r = random.randint(0, len(blocks) - 2)
leak = [self.cipher.encrypt(blocks[i]).hex() for i in [r, r + 1]]
return r, leak
def main():
aes = CAES()
message = pad(FLAG * 4, 16)
ciphertext = aes.encrypt(message)
ciphertext_blocks = aes.blockify(ciphertext, 16)
r, leak = aes.leak(ciphertext_blocks)
with open('output.txt', 'w') as f:
f.write(f'ct = {ciphertext.hex()}\nr = {r}\nphrases = {leak}\n')
if __name__ == "__main__":
main()
Y también tenemos la salida del script en output.txt:
ct = b25bc89662197c6462188e5960eea4fbef11424b8ebdcd6b45c8f4240d64f5d1981aab0e299ff75ce9fba3d5d78926543e5e8c262b81090aef60518ee241ab131db902d2582a36618f3b9a85a35f52352d5499861b4a878fac1380f520fe13deb1ca50c64f30e98fa6fdc070d02e148f
r = 3
phrases = ['5fe633e7071e690fbe58a9dace6f3606', '501ccdc4600bc2dcf350c6b77fcf2681']
Análisis del código fuente
El servidor usa un cifrado personalizado con nombre CAES basado en AES en modo ECB:

Echemos un vistazo al método encrypt:
def encrypt(self, message):
iv = os.urandom(16)
ciphertext = b''
plaintext = iv
blocks = self.blockify(message, 16)
for block in blocks:
ct = self.cipher.encrypt(plaintext)
encrypted_block = self.xor(block, ct)
ciphertext += encrypted_block
plaintext = encrypted_block
return ciphertext
Dados algunos bloques de texto plano
- …
Filtración
Para que el cifrado sea vulnerable, se nos da la salida de leak:
def leak(self, blocks):
r = random.randint(0, len(blocks) - 2)
leak = [self.cipher.encrypt(blocks[i]).hex() for i in [r, r + 1]]
return r, leak
En resumen, se nos dan dos textos cifrados consecutivos. Como se muestra en output.txt, tenemos
Estas dos operaciones son suficientes para resolver el reto porque el texto plano es la flag repetida cuatro veces:
def main():
aes = CAES()
message = pad(FLAG * 4, 16)
ciphertext = aes.encrypt(message)
ciphertext_blocks = aes.blockify(ciphertext, 16)
r, leak = aes.leak(ciphertext_blocks)
with open('output.txt', 'w') as f:
f.write(f'ct = {ciphertext.hex()}\nr = {r}\nphrases = {leak}\n')
Solución
Podemos resolver el reto usando Python:
$ python3 -q
>>> from pwn import unhex, xor
>>>
>>> def blockify(message, size):
... return [message[i : i + size] for i in range(0, len(message), size)]
...
>>> ct = blockify(unhex('b25bc89662197c6462188e5960eea4fbef11424b8ebdcd6b45c8f4240d64f5d1981aab0e299ff75ce9fba3d5d78926543e5e8c262b81090aef60518ee241ab131db902d2582a36618f3b9a85a35f52352d5499861b4a878fac1380f520fe13deb1ca50c64f30e98fa6fdc070d02e148f'), 16)
>>> aes_c3 = unhex('5fe633e7071e690fbe58a9dace6f3606')
>>> aes_c4 = unhex('501ccdc4600bc2dcf350c6b77fcf2681')
>>>
>>> p4 = xor(ct[4], aes_c3)
>>> p5 = xor(ct[5], aes_c4)
>>> p4 + p5
b'B_15_4_n1c3_m0d3}HTB{AES_CFB_15_'
Flag
Por lo tanto, la flag es: HTB{AES_CFB_15_4_n1c3_m0d3}.