RISC CTF Writeups

eXclusive

Crypto: eXclusive

Challenge Description

One bitwise op is all it takes, fallin’ in love with me

- Halvin Carris and Lua Dipa

Attachments

chal.py

with open('flag.txt', 'r') as f:
    flag = f.read().strip()

with open('key.txt', 'r') as f:
    key = f.read().strip()

extended_key = (key * (len(flag) // len(key) + 1))[:len(flag)]

ct = bytes([ord(f)^ord(k) for f, k in zip(flag, extended_key)])

with open("output.txt", "w") as f:
    f.write(ct.hex())

output.txt

0a06016223376253077e217e3d7b21580776621639796618392d30173e2b62406932

Approach

From chal.py we can observe that the key is read from a file, repeated to match the flag’s length, and then applied byte-wise to the plaintext:

$$C = P \oplus K_{\text{ext}}.$$

Keep in mind that XOR is symmetric:

$$P \oplus K = C \Longleftrightarrow P = C \oplus K \Longleftrightarrow K = P \oplus C.$$

We also know from other challenges the flag format starts with RISC{. That gives us a crib (known plaintext) for the first five bytes. We can abuse the crib and the symmetric property of XOR to recover at least 5 bytes of the flag:

$$K_{\text{ext}}[0..4] = C[0..4] \oplus \text{“RISC\{”}.$$

Doing that yields:

C[0..4]   = 0a 06 01 62 23
"RISC{"   = 52 49 53 43 7b
xor'd     = 58 4f 52 21 58  →  "XOR!X"

Looks like we have a 4 byte key that repeats - let’s try XOR!:

ct = bytes.fromhex("0a06016223376253077e217e3d7b21580776621639796618392d30173e2b62406932")
key = b"XOR!"
pt  = bytes(c ^ key[i % len(key)] for i, c in enumerate(ct))
print(pt.decode())

solve script

RISC{x0r_1s_e4sy_907a649abb6fd0a1}