Broken Decryptor
hackthebox
Task: AES-CTR encryption with biased OTP (no 0x00 bytes) and broken decrypt oracle. Solution: Statistical byte elimination attack - collect ~2000 samples to identify the one missing byte value at each position, then XOR with keystream recovered from encrypting zeros.
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Broken Decryptor — HackTheBox
Description
"The decrypt function is broken and I lost my flag. Can you help me fix it?"
A Python server implements AES-CTR encryption with a twist. Players connect to a TCP service with 3 options: get encrypted flag, encrypt a message, or decrypt a message. The decrypt function is intentionally broken (calls .encode() on bytes, which crashes in Python 3).
Analysis
Key Source Code
key = os.urandom(0x10).replace(b'\x00', b'\xff') iv = os.urandom(0x10).replace(b'\x00', b'\xff') def encrypt(data): ctr = Counter.new(128, initial_value=int(iv.hex(), 16)) crypto = AES.new(key, AES.MODE_CTR, counter=ctr) if type(data) != bytes: data = data.encode() otp = os.urandom(len(data)).replace(b'\x00', b'\xff') return xor(crypto.encrypt(data), otp) def decrypt(data): ctr = Counter.new(128, initial_value=int(iv.hex(), 16)) crypto = AES.new(key, AES.MODE_CTR, counter=ctr) return crypto.decrypt(data.encode()) # BUG: .encode() on bytes crashes
Vulnerabilities Identified
-
Broken decrypt function: The
decrypt()function calls.encode()on abytesobject, which crashes in Python 3. This is the "broken" part mentioned in the challenge title. -
Biased OTP generation: The OTP is generated as
os.urandom(len(data)).replace(b'\x00', b'\xff'). This means OTP bytes range over 1-255, never 0x00. -
Fixed AES-CTR keystream: The key and IV are fixed for the session, so the AES-CTR keystream is identical for every encryption call.
Attack: Statistical Byte Elimination
Since encrypt(data) = data ⊕ keystream ⊕ OTP and OTP byte is never 0x00:
- The output byte at position j is
data[j] ⊕ KS[j] ⊕ OTP[j] - Since
OTP[j]takes all values in {1..255} but never 0, the valuedata[j] ⊕ KS[j] ⊕ 0 = data[j] ⊕ KS[j]never appears in the output - By collecting ~2000+ samples, we can identify the one missing byte value at each position
...
$ grep --similar
Similar writeups
- [crypto][free]xorxorxor— hackthebox
- [crypto][Pro]Noncense— spbctf
- [crypto][Pro]AES-CTR— spbctf
- [crypto][Pro]Поврежденная расшифровка (Corrupted Decryption)— hackerlab
- [pwn][Pro]Rusty— spbctf