372 words
2 minutes
BITSCTF 2026 - Super DES - Cryptography Writeup

Category: Cryptography

Flag: BITSCTF{5up3r_d35_1z_n07_53cur3}

Challenge Description#

Given server.py. Remote: nc 20.193.149.152 1340. Description: “I heard triple des is deprecated, so I made my own.”

The server generates random k1 at startup, then lets us choose k2 and k3:

def triple_des_ultra_secure_v1(pt, k2, k3):
return E_k1(E_k2(E_k3(pad(pt))))
def triple_des_ultra_secure_v2(pt, k2, k3):
return D_k1(E_k2(E_k3(pad(pt))))

(k2 == k3 is blocked, but k2 != k3 is allowed.)

Analysis#

DES has semi-weak key pairs such that Eka(Ekb(x))=xE_{k_a}(E_{k_b}(x)) = x for specific distinct key pairs. One valid pair:

  • k2 = 01FE01FE01FE01FE

  • k3 = FE01FE01FE01FE01

So in ultra_secure_v1: Cflag=Ek1(Ek2(Ek3(pad(flag))))=Ek1(pad(flag))C_{flag} = E_{k1}(E_{k2}(E_{k3}(pad(flag)))) = E_{k1}(pad(flag))

Solution#

Use semi-weak pair to collapse encryption to Cflag=Ek1(pad(flag))C_{flag} = E_{k1}(pad(flag)). Then for arbitrary k2, k3, if we pick plaintext so that pad(pt)=Dk3(Dk2(Cflag))pad(pt) = D_{k3}(D_{k2}(C_{flag})), querying ultra_secure_v2 gives Dk1(Cflag)=pad(flag)D_{k1}(C_{flag}) = pad(flag).

The practical caveat: we must brute-force random (k2,k3) until Dk3(Dk2(Cflag))D_{k3}(D_{k2}(C_{flag})) has valid PKCS#7 structure.

File used: solve_super_des.py

# !/usr/bin/env python3
from pwn import remote
from Crypto.Cipher import DES
from Crypto.Util.Padding import unpad
from Crypto.Random import get_random_bytes
HOST, PORT = "20.193.149.152", 1340
def adjust_key(key8: bytes) -> bytes:
out = bytearray()
for b in key8:
b7 = b & 0xFE
ones = bin(b7).count("1")
out.append(b7 | (ones % 2 == 0))
return bytes(out)
def wait_k2_prompt(io):
io.recvuntil(b"enter k2 hex bytes >")
def query(io, k2: bytes, k3: bytes, option: int, mode: int, pt_hex: str | None = None) -> bytes:
wait_k2_prompt(io)
io.sendline(k2.hex().encode())
io.recvuntil(b"enter k3 hex bytes >")
io.sendline(k3.hex().encode())
io.recvuntil(b"enter option >")
io.sendline(str(option).encode())
io.recvuntil(b"enter option >")
io.sendline(str(mode).encode())
if mode == 2:
io.recvuntil(b"enter hex bytes >")
io.sendline(pt_hex.encode())
line = io.recvline_contains(b"ciphertext")
return bytes.fromhex(line.decode().split(":", 1)[1].strip())
def main():
io = remote(HOST, PORT)
# Step 1: semi-weak pair so E_k2(E_k3(x)) = x
k2w = bytes.fromhex("01FE01FE01FE01FE")
k3w = bytes.fromhex("FE01FE01FE01FE01")
# Cflag = E_k1(pad(flag))
cflag = query(io, k2w, k3w, option=2, mode=1)
print(f"[+] Cflag ({len(cflag)} bytes): {cflag.hex()}")
# Step 2: find k2,k3 where D_k3(D_k2(cflag)) is valid PKCS#7
attempts = 0
while True:
attempts += 1
k2 = adjust_key(get_random_bytes(8))
k3 = adjust_key(get_random_bytes(8))
if k2 == k3:
continue
pre = DES.new(k3, DES.MODE_ECB).decrypt(DES.new(k2, DES.MODE_ECB).decrypt(cflag))
try:
chosen_pt = unpad(pre, 8)
except ValueError:
continue
print(f"[+] Found valid candidate after {attempts} attempts")
# Step 3: v2 returns pad(flag)
out = query(io, k2, k3, option=3, mode=2, pt_hex=chosen_pt.hex())
flag = unpad(out, 8)
print(f"[+] Flag bytes: {flag}")
print(f"[+] Flag: {flag.decode()}")
break
io.close()
if __name__ == "__main__":
main()

Run:

python3 solve_super_des.py

Output:

[+] Cflag (40 bytes): 72922fe6db8bbc21825f1f3a5a9d336e82ef77555946655ed1529579670aab074df19b7d7a35e007
[+] Found valid candidate after 6 attempts
[+] Flag bytes: b'BITSCTF{5up3r_d35_1z_n07_53cur3}'
[+] Flag: BITSCTF{5up3r_d35_1z_n07_53cur3}
BITSCTF 2026 - Super DES - Cryptography Writeup
https://fuwari.vercel.app/posts/40/bitsctf-2026-super-des-cryptography-writeup/
Author
Light
Published at
2026-02-22
License
CC BY-NC-SA 4.0