Iced TEA
3 minutos de lectura
Se nos proporciona el código fuente en Python que cifra la flag:
import os
from secret import FLAG
from Crypto.Util.Padding import pad
from Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b
from enum import Enum
class Mode(Enum):
ECB = 0x01
CBC = 0x02
class Cipher:
def __init__(self, key, iv=None):
self.BLOCK_SIZE = 64
self.KEY = [b2l(key[i:i+self.BLOCK_SIZE//16]) for i in range(0, len(key), self.BLOCK_SIZE//16)]
self.DELTA = 0x9e3779b9
self.IV = iv
if self.IV:
self.mode = Mode.CBC
else:
self.mode = Mode.ECB
def _xor(self, a, b):
return b''.join(bytes([_a ^ _b]) for _a, _b in zip(a, b))
def encrypt(self, msg):
msg = pad(msg, self.BLOCK_SIZE//8)
blocks = [msg[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg), self.BLOCK_SIZE//8)]
ct = b''
if self.mode == Mode.ECB:
for pt in blocks:
ct += self.encrypt_block(pt)
elif self.mode == Mode.CBC:
X = self.IV
for pt in blocks:
enc_block = self.encrypt_block(self._xor(X, pt))
ct += enc_block
X = enc_block
return ct
def encrypt_block(self, msg):
m0 = b2l(msg[:4])
m1 = b2l(msg[4:])
K = self.KEY
msk = (1 << (self.BLOCK_SIZE//2)) - 1
s = 0
for i in range(32):
s += self.DELTA
m0 += ((m1 << 4) + K[0]) ^ (m1 + s) ^ ((m1 >> 5) + K[1])
m0 &= msk
m1 += ((m0 << 4) + K[2]) ^ (m0 + s) ^ ((m0 >> 5) + K[3])
m1 &= msk
m = ((m0 << (self.BLOCK_SIZE//2)) + m1) & ((1 << self.BLOCK_SIZE) - 1) # m = m0 || m1
return l2b(m)
if __name__ == '__main__':
KEY = os.urandom(16)
cipher = Cipher(KEY)
ct = cipher.encrypt(FLAG)
with open('output.txt', 'w') as f:
f.write(f'Key : {KEY.hex()}\nCiphertext : {ct.hex()}')
También tenemos la salida del script:
Key : 850c1413787c389e0b34437a6828a1b2
Ciphertext : b36c62d96d9daaa90634242e1e6c76556d020de35f7a3b248ed71351cc3f3da97d4d8fd0ebc5c06a655eb57f2b250dcb2b39c8b2000297f635ce4a44110ec66596c50624d6ab582b2fd92228a21ad9eece4729e589aba644393f57736a0b870308ff00d778214f238056b8cf5721a843
Análisis del código fuente
El servidor usa una clase Cipher
para cifrar la flag. El servidor no cuenta explícitamente qué algoritmo de cifrado está usando, pero es bastante fácil ver que es Tiny Encryption Algorithm (TEA), que es un cifrado en bloque simétrico.
Obsérvese que se nos proporciona la clave de cifrado y el texto cifrado, por lo que solo necesitamos descifrarlo. Sin embargo, la clase Cipher
no implementa los métodos decrypt
ni decrypt_block
, entonces necesitaremos implementarlos.
Descifrado
Las funciones de descifrado son fáciles de implementar, solo necesitamos hacer las operaciones inversas de encrypt
y encrypt_block
en orden inverso. De lo contrario, podemos echar un vistazo a Wikipedia y traducir el código de referencia en C a Python.
Así, estas son las funciones necesarias:
def decrypt(self, msg_ct):
blocks = [msg_ct[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg_ct), self.BLOCK_SIZE//8)]
pt = b''
if self.mode == Mode.ECB:
for ct in blocks:
pt += self.decrypt_block(ct)
elif self.mode == Mode.CBC:
X = self.IV
for ct in blocks:
dec_block = self._xor(X, self.decrypt_block(ct))
pt += dec_block
X = ct
return unpad(pt, self.BLOCK_SIZE//8)
def decrypt_block(self, m):
m0 = b2l(m[:4])
m1 = b2l(m[4:])
K = self.KEY
msk = 0xffffffff
s = 32 * self.DELTA
for _ in range(32):
m1 -= (((m0 << 4) + K[2]) ^ (m0 + s) ^ ((m0 >> 5) + K[3]))
m1 &= msk
m0 -= (((m1 << 4) + K[0]) ^ (m1 + s) ^ ((m1 >> 5) + K[1]))
m0 &= msk
s -= self.DELTA
msg = ((m0 << 32) + m1) & 0xffffffffffffffff # m = m0 || m1
return l2b(msg)
En este punto, podemos tomar la clave de cifrado y el texto cifrado de output.txt
y descifrar la flag:
KEY = bytes.fromhex('850c1413787c389e0b34437a6828a1b2')
ct = bytes.fromhex('b36c62d96d9daaa90634242e1e6c76556d020de35f7a3b248ed71351cc3f3da97d4d8fd0ebc5c06a655eb57f2b250dcb2b39c8b2000297f635ce4a44110ec66596c50624d6ab582b2fd92228a21ad9eece4729e589aba644393f57736a0b870308ff00d778214f238056b8cf5721a843')
print(Cipher(KEY).decrypt(ct).decode())
Flag
Y aquí está la flag:
$ python3 solve.py
HTB{th1s_1s_th3_t1ny_3ncryp710n_4lg0r1thm_____y0u_m1ght_h4v3_4lr34dy_s7umbl3d_up0n_1t_1f_y0u_d0_r3v3rs1ng}