371 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://blog.rei.my.id/posts/40/bitsctf-2026-super-des-cryptography-writeup/
Author
Reidho Satria
Published at
2026-02-22
License
CC BY-NC-SA 4.0