Jenny From The Block
3 minutes to read
We are given this Python source code:
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()
This code runs a server that allows us to execute system commands: whoami
, ls
, cat secret.txt
or pwd
. However, the output plus an information message is encrypted with these functions, in blocks of 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()
The thing is that "Command executed: cat secret.txt"
is a 32-byte string, so we can perform a known plaintext attack:
$ 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
Moreover, the first key is password = os.urandom(32)
, and after that, the previous block is used to craft the key for the next block (using a SHA256 hash). Hence, since we know 32-bytes of plaintext, we know that the SHA256 hash of this string is the key for the next block.
Therefore, we can decrypt the next block using the SHA256 hash of the previous block, and inductively, decrypt all the blocks like this:
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()
And this is the decryption function (inverse of encryption):
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
Using this solution script: solve.py
we can obtain the 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