Jenny From The Block
3 minutos de lectura
Se nos proporciona este código fuente en Python:
from hashlib import sha256
from Crypto.Util.Padding import pad, unpad
import signal
import subprocess
import socketserver
import os
allowed_commands = [b'whoami', b'ls', b'cat secret.txt', b'pwd']
BLOCK_SIZE = 32
def encrypt_block(block, secret):
enc_block = b''
for i in range(BLOCK_SIZE):
val = (block[i]+secret[i]) % 256
enc_block += bytes([val])
return enc_block
def encrypt(msg, password):
h = sha256(password).digest()
if len(msg) % BLOCK_SIZE != 0:
msg = pad(msg, BLOCK_SIZE)
blocks = [msg[i:i+BLOCK_SIZE] for i in range(0, len(msg), BLOCK_SIZE)]
ct = b''
for block in blocks:
enc_block = encrypt_block(block, h)
h = sha256(enc_block + block).digest()
ct += enc_block
return ct.hex()
def run_command(cmd):
if cmd in allowed_commands:
try:
resp = subprocess.run(
cmd.decode().split(' '), capture_output=True)
output = resp.stdout
return output
except:
return b'Something went wrong!\n'
else:
return b'Invalid command!\n'
def challenge(req):
req.sendall(b'This is Jenny! I am the heart and soul of this spaceship.\n' +
b'Welcome to the debug terminal. For security purposes I will encrypt any responses.')
while True:
req.sendall(b'\n> ')
command = req.recv(4096).strip()
output = run_command(command)
response = b'Command executed: ' + command + b'\n' + output
password = os.urandom(32)
ct = encrypt(response, password)
req.sendall(ct.encode())
class incoming(socketserver.BaseRequestHandler):
def handle(self):
signal.alarm(30)
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()
Este código ejecuta un servidor que nos permite ejecutar comandos de sistema: whoami
, ls
, cat secret.txt
or pwd
. Sin embargo, la salida del comando y un mensaje de información se cifra con estas funciones, en bloques de 32 bytes:
def encrypt_block(block, secret):
enc_block = b''
for i in range(BLOCK_SIZE):
val = (block[i]+secret[i]) % 256
enc_block += bytes([val])
return enc_block
def encrypt(msg, password):
h = sha256(password).digest()
if len(msg) % BLOCK_SIZE != 0:
msg = pad(msg, BLOCK_SIZE)
blocks = [msg[i:i+BLOCK_SIZE] for i in range(0, len(msg), BLOCK_SIZE)]
ct = b''
for block in blocks:
enc_block = encrypt_block(block, h)
h = sha256(enc_block + block).digest()
ct += enc_block
return ct.hex()
La cosa es que "Command executed: cat secret.txt"
es una cadena de 32 bytes, por lo que podemos realizar un ataque de texto claro conocido:
$ nc 178.62.23.240 30410
This is Jenny! I am the heart and soul of this spaceship.
Welcome to the debug terminal. For security purposes I will encrypt any responses.
> cat secret.txt
5b091883b5808f1feb09b9016a7c7e017511fd0b2593d78bbbda209866c7c4169512e84002c2e3ac7e832ffe996c849dd9604cea72e2d356626d7035b1e17c4c2bcf2f4a5473d6fd9118285ef447e59b1bf285ea6e9abfcc346c9be2b4a2edd68945681a2f94bb15006e1b1750f94c587410321c354c79f17511afbd2a0ace5156f5a11eee7203bd2f75ab5f604a2f03ea53aa2061aa61738c6dc63d3462d448daf150c44f4ef0dad081470ce61873b64f18adb3c8925cf3d03268b03de27db53674dfe6bd2fbf39bb00a8e4f67a0e643a6d2181cedba1b2a2ee7d851b0ab6ea344221047f06fbe43f9497c1dec632ec02940a885a114ed81b6bf81d0f5ed42d
Además, la primera clave es password = os.urandom(32)
, y luego, el bloque anterior se usa para construir la clave para el siguiente bloque (usando un hash SHA256). Por tanto, como conocemos 32 bytes de texto claro, sabemos que su hash SHA256 es la clave para el siguiente bloque.
Entonces, podemos descifrar el siguiente bloque usando el hash SHA256 del bloque anterior, e inductivamente, descifrar todos los bloques así:
def main():
p = get_process()
p.sendlineafter(b'> ', b'cat secret.txt')
ct = bytes.fromhex(p.recvline().strip().decode())
block = b'Command executed: cat secret.txt'
secret = block
i = 0
while b'}' not in secret:
h = sha256(ct[32 * i : 32 * (i + 1)] + block).digest()
block = decrypt_block(ct[32 * (i + 1) : 32 * (i + 2)], h)
secret += block
i += 1
print(secret.decode())
p.close()
Y esta es la función de descifrado (función inversa del cifrado):
def decrypt_block(enc_block, plaintext):
dec_block = b''
for i in range(BLOCK_SIZE):
val = (enc_block[i] - plaintext[i]) % 256
dec_block += bytes([val])
return dec_block
Usando este script: solve.py
podemos obtener la flag:
$ python3 solve.py 178.62.23.240:30410
[+] Opening connection to 178.62.23.240 on port 30410: Done
Command executed: cat secret.txt
In case Jenny malfunctions say the following phrase: Melt My Eyez, See Your Future
The AI system will shutdown and you will gain complete control of the spaceship.
- Danbeer S.A.
HTB{th1s_b451c_b107k_c1ph3r_1s_n0t_s@fe}
[*] Closed connection to 178.62.23.240 port 30410