←
$ Crypto: Super DES
Key Points
- The server uses a fixed secret DES key
k1and lets us choosek2andk3. - In
ultra secure v1, encryption is:C = E_k1(E_k2(E_k3(P)))
- In
ultra secure v2, encryption is:C = D_k1(E_k2(E_k3(P)))
- If we choose a DES semi-weak pair (
k2,k3) such thatE_k2 = D_k3, then:E_k2(E_k3(X)) = X
- So with that choice:
- v1 becomes
C = E_k1(P) - v2 becomes
C = D_k1(P)
- v1 becomes
This gives a direct encryption/decryption oracle for the unknown k1.
Exploit Strategy
- Choose semi-weak DES keys:
k2 = 01fe01fe01fe01fek3 = fe01fe01fe01fe01
- Request
option=2(ultra secure v1) withoption_=1(encrypt flag):- Receive
enc_flag = E_k1(pad(flag))
- Receive
- Split
enc_flaginto 8-byte blocks. - For each block
B, calloption=3(ultra secure v2) withoption_=2(encrypt own text) andpt=B:- Output block is
D_k1(B)
- Output block is
- Reassemble decrypted blocks and remove PKCS#7 padding.
Recovered Flag
BITSCTF{5up3r_d35_1z_n07_53cur3}
Main Program (solve_super_des.py)
from pwn import remote
HOST = "20.193.149.152"
PORT = 1340
# Known DES semi-weak pair with odd parity bits.
K2 = "01fe01fe01fe01fe"
K3 = "fe01fe01fe01fe01"
def pkcs7_unpad(data: bytes, block_size: int = 8) -> bytes:
if not data or len(data) % block_size != 0:
raise ValueError("invalid padded length")
n = data[-1]
if n < 1 or n > block_size:
raise ValueError("invalid padding byte")
if data[-n:] != bytes([n]) * n:
raise ValueError("invalid padding contents")
return data[:-n]
def recv_menu(io):
io.recvuntil(b"enter k2 hex bytes >")
def do_query(io, option, option_, pt_hex=None):
recv_menu(io)
io.sendline(K2.encode())
io.recvuntil(b"enter k3 hex bytes >")
io.sendline(K3.encode())
io.recvuntil(b"enter option >")
io.sendline(str(option).encode())
io.recvuntil(b"enter option >")
io.sendline(str(option_).encode())
if option_ == 2:
io.recvuntil(b"enter hex bytes >")
io.sendline(pt_hex.encode())
io.recvuntil(b"ciphertext : ")
return bytes.fromhex(io.recvline().strip().decode())
def decrypt_block(io, block8: bytes) -> bytes:
# In option 3 with this semi-weak pair, first block equals D_k1(block8)
ct = do_query(io, option=3, option_=2, pt_hex=block8.hex())
return ct[:8]
def solve(io):
# Get E_k1(pad(flag)) via ultra secure v1
enc_flag = do_query(io, option=2, option_=1)
# Decrypt each block with v2 oracle
pt_padded = b"".join(decrypt_block(io, enc_flag[i:i+8]) for i in range(0, len(enc_flag), 8))
return pkcs7_unpad(pt_padded, 8)
if __name__ == "__main__":
io = remote(HOST, PORT)
flag = solve(io)
print(flag.decode(errors="replace"))
io.close()Run
python3 solve_super_des.py