Super Des

Bitskrieg CTFby smothy

Super DES - Crypto

Points: 388 | Flag: BITSCTF{5up3r_d35_1z_n07_53cur3} | Solved by: Smothy @ 0xN1umb

too easy

what we got

server running "Super DES" - dude said triple DES is deprecated so he built his own lmao. we get server.py and can connect via netcat.

we choose keys k2 and k3, then pick one of 3 modes to encrypt either the flag or our own plaintext:

python
# option 1 - standard 3DES
def triple_des(pt, k2, k3):
    cipher = DES3.new(k3 + k2 + k1, DES3.MODE_ECB)
    return cipher.encrypt(pad(pt, 8))

# option 2 - "ultra secure v1": triple encrypt
def triple_des_ultra_secure_v1(pt, k2, k3):
    return E_k1(E_k2(E_k3(pad(pt))))

# option 3 - "ultra secure v2": decrypt k1, encrypt k2 & k3
def triple_des_ultra_secure_v2(pt, k2, k3):
    return D_k1(E_k2(E_k3(pad(pt))))

k1 is a random DES key we don't know. only restriction: k2 != k3.

the solve

ngl this one clicked pretty fast. the key observation is DES semi-weak keys.

DES has pairs of keys (Ka, Kb) where Ka != Kb but E_Ka(E_Kb(x)) = x for ALL x. they literally cancel each other out. one well-known pair:

Ka = 01FE01FE01FE01FE Kb = FE01FE01FE01FE01

so if we set k2 = Ka and k3 = Kb, the inner encryption in v1 and v2 just... disappears:

  • v1 on flag: E_k1(E_Ka(E_Kb(pad(flag)))) = E_k1(pad(flag)) - reduced to single DES lol
  • v2 on our text: D_k1(E_Ka(E_Kb(pad(pt)))) = D_k1(pad(pt)) - free decryption oracle

the attack is literally 2 steps:

  1. use v1 to encrypt the flag with semi-weak keys -> get E_k1(pad(flag))
  2. split into 8-byte blocks, feed each block into v2 as "custom plaintext" -> get D_k1(block) = flag_block

when we give v2 an 8-byte block Xi, the server pads it to Xi || \x08*8 (16 bytes). after the semi-weak keys cancel, we get D_k1(Xi) || D_k1(\x08*8). first 8 bytes = our flag block. ez.

python
from pwn import *
from Crypto.Cipher import DES

Ka = bytes.fromhex('01FE01FE01FE01FE')
Kb = bytes.fromhex('FE01FE01FE01FE01')

r = remote('20.193.149.152', 1340)

# step 1: get E_k1(pad(flag)) via v1 with semi-weak keys
r.recvuntil(b'enter k2 hex bytes >')
r.sendline(Ka.hex().encode())
r.recvuntil(b'enter k3 hex bytes >')
r.sendline(Kb.hex().encode())
r.recvuntil(b'enter option >')
r.sendline(b'2')  # ultra secure v1
r.recvuntil(b'enter option >')
r.sendline(b'1')  # encrypt flag
r.recvuntil(b'ciphertext : ')
X = bytes.fromhex(r.recvline().strip().decode())

# step 2: decrypt each block using v2
blocks = [X[i:i+8] for i in range(0, len(X), 8)]
flag_blocks = []

for block in blocks:
    r.recvuntil(b'enter k2 hex bytes >')
    r.sendline(Ka.hex().encode())
    r.recvuntil(b'enter k3 hex bytes >')
    r.sendline(Kb.hex().encode())
    r.recvuntil(b'enter option >')
    r.sendline(b'3')  # ultra secure v2
    r.recvuntil(b'enter option >')
    r.sendline(b'2')  # custom plaintext
    r.recvuntil(b'enter hex bytes >')
    r.sendline(block.hex().encode())
    r.recvuntil(b'ciphertext : ')
    result = bytes.fromhex(r.recvline().strip().decode())
    flag_blocks.append(result[:8])  # first 8 bytes = decrypted block

padded_flag = b''.join(flag_blocks)
flag = padded_flag[:-padded_flag[-1]]  # remove PKCS7 padding
print(flag.decode())

output goes brrr:

[+] E_k1(pad(flag)) = 3d532debddf259d12e4cd7feaea48d91bbdfe4a327cbb9be98e8ee98e76d8da53c70926cc2821be7 [+] 5 blocks [+] Block 0: b'BITSCTF{' [+] Block 1: b'5up3r_d3' [+] Block 2: b'5_1z_n07' [+] Block 3: b'_53cur3}' [+] Block 4: b'\x08\x08\x08\x08\x08\x08\x08\x08' [*] Flag: BITSCTF{5up3r_d35_1z_n07_53cur3}

whole thing took like 5 queries total. no bruteforce, no meet-in-the-middle, just pure crypto knowledge fr.

flag

BITSCTF{5up3r_d35_1z_n07_53cur3}

the irony of "super des is not secure" being the flag when the dude literally gave us a decrypt oracle through semi-weak keys... chef's kiss


smothy out ✌️