The Three-Eyed Oracle
3 minutos de lectura
Se nos proporciona el siguiente código en Python:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random
import signal
import subprocess
import socketserver
FLAG = b'HTB{--REDACTED--}'
prefix = random.randbytes(12)
key = random.randbytes(16)
def encrypt(key, msg):
msg = bytes.fromhex(msg)
crypto = AES.new(key, AES.MODE_ECB)
padded = pad(prefix + msg + FLAG, 16)
return crypto.encrypt(padded).hex()
def challenge(req):
req.sendall(b'Welcome to Klaus\'s crypto lab.\n' +
b'It seems like there is a prefix appended to the real firmware\n' +
b'Can you somehow extract the firmware and fix the chip?\n')
while True:
req.sendall(b'> ')
try:
msg = req.recv(4096).decode()
ct = encrypt(key, msg)
req.sendall(ct.encode() + b'\n')
except Exception as e:
print(e)
req.sendall(b'An error occurred! Please try again!')
class incoming(socketserver.BaseRequestHandler):
def handle(self):
signal.alarm(1500)
req = self.request
challenge(req)
class ReusableTCPServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
def main():
socketserver.TCPServer.allow_reuse_address = True
server = ReusableTCPServer(("0.0.0.0", 1337), incoming)
server.serve_forever()
if __name__ == "__main__":
main()
Básicamente, tenemos la oportunidad de enviar un mensaje que será cifrado usando AES ECB de una manera particular:
FLAG = b'HTB{--REDACTED--}'
prefix = random.randbytes(12)
key = random.randbytes(16)
def encrypt(key, msg):
msg = bytes.fromhex(msg)
crypto = AES.new(key, AES.MODE_ECB)
padded = pad(prefix + msg + FLAG, 16)
return crypto.encrypt(padded).hex()
A nuestro mensaje se le aádirá un prefijo aleatorio de 12 bytes (prefix
) y la flag (FLAG
) como sufijo antes de cifrar. El problema es que se utiliza AES ECB:
Por tanto, el texto claro se divide en bloques de 16 bytes y luego cada bloque se cifra de forma independiente. Para obtener la flag tenemos un oráculo. Por ejemplo, podemos introducir 15 caracteres A
, luego un carácter de prueba y otros 15 caracteres A
. Cuando la salida de los dos bloques cifrados coincida, sabremos que el carácter de prueba es el primer carácter de la flag.
Vamos a verlo gráficamente:
Our input: "BBBBAAAAAAAAAAAAAAAxAAAAAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB AAAAAAAAAAAAAAAx AAAAAAAAAAAAAAA? ????????????????
Ciphertext Blocks:
R X Y Z
Estaremos iterando sobre todos los caracteres y probandolos en la posición de la x
. Miraremos los bloques cifrados resultantes hasta que X = Y
; en este punto, sabremos que los dos bloques en texto claro son iguales, por lo que el carácterprobado es correcto.
Luego, seguimos el proceso así:
Our input: "BBBBAAAAAAAAAAAAAAHxAAAAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB AAAAAAAAAAAAAAHx AAAAAAAAAAAAAAH? ????????????????
Ciphertext Blocks:
R X Y Z
Our input: "BBBBAAAAAAAAAAAAAHTxAAAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB AAAAAAAAAAAAAHTx AAAAAAAAAAAAAHT? ????????????????
Ciphertext Blocks:
R X Y Z
Our input: "BBBBAAAAAAAAAAAAHTBxAAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB AAAAAAAAAAAAHTBx AAAAAAAAAAAAHTB? ????????????????
Ciphertext Blocks:
R X Y Z
Our input: "BBBBAAAAAAAAAAAHTB{xAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB AAAAAAAAAAAHTB{x AAAAAAAAAAAHTB{? ????????????????
Ciphertext Blocks:
R X Y Z
Habrá una situación en la que no podremos poner más caracteres A
de relleno. Vamos a asumir que la flag es HTB{f4k3_fl4g_f0r_t3st1ng}
. Este problema llegará después de esta iteración:
Our input: "BBBBHTB{f4k3_f0r_t3x"
Plaintext Blocks:
rrrrrrrrrrrrBBBB HTB{f4k3_f0r_t3x HTB{f4k3_f0r_t3? ????????????????
Ciphertext Blocks:
R X Y Z
Pero es fácil de corregir, solamente tenemos que seguir desplazando el payload:
Our input: "BBBBTB{f4k3_f0r_t3sxAAAAAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB TB{f4k3_f0r_t3sx AAAAAAAAAAAAAAAH TB{f4k3_f0r_t3s? ????????????????
Ciphertext Blocks:
R X Y Z W
Ahora estaremos mirando los bloques X
y Z
. Y procedemos así:
Our input: "BBBBB{f4k3_f0r_t3stxAAAAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB B{f4k3_f0r_t3stx AAAAAAAAAAAAAAHT B{f4k3_f0r_t3st? ????????????????
Ciphertext Blocks:
R X Y Z W
Our input: "BBBB{f4k3_f0r_t3st1xAAAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB {f4k3_f0r_t3st1x AAAAAAAAAAAAAHTB {f4k3_f0r_t3st1? ????????????????
Ciphertext Blocks:
R X Y Z W
Our input: "BBBBf4k3_f0r_t3st1nxAAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB f4k3_f0r_t3st1nx AAAAAAAAAAAAHTB{ f4k3_f0r_t3st1n? ????????????????
Ciphertext Blocks:
R X Y Z W
Our input: "BBBB4k3_f0r_t3st1ngxAAAAAAAAAAA"
Plaintext Blocks:
rrrrrrrrrrrrBBBB 4k3_f0r_t3st1ngx AAAAAAAAAAAHTB{f 4k3_f0r_t3st1ng? ????????????????
Ciphertext Blocks:
R X Y Z W
El proceso terminaría aquí, porque el carácter correcto sería }
, que marca el final de la flag. Ahora tenemos que automatizar el proceso en Python e interactuar con el servidor para obtener los caracteres correctos y construir la flag:
$ python3 solve.py 64.227.37.154:30799
[+] Opening connection to 64.227.37.154 on port 30799: Done
[◓] Flag: HTB{7h3_br0k3n_0r@c1e!!!}
[*] Closed connection to 64.227.37.154 port 30799
El script completo se puede encontrar aquí: solve.py
.