Repeating-key XOR known-plaintext attack and key brute
Foundations crypto engagement: TCP service on 1337 returned hex XOR of a THM{} flag under a 5-character alphanumeric key; recovered four key bytes from known plaintext THM{, brute-forced the fifth, decrypted flag 1, and submitted the key for flag 2.
- Case files
- Repeating-key XOR known-plaintext attack and key brute
Reviewed the supplied Python server: random 5-byte key, cyclic XOR, hex encode; mounted a known-plaintext attack on THM{ and brute-forced the remaining key character.
nc to :1337; derived key prefix from THM{; brute-forced last byte; submitted key for second artifact.
Engagement summary
Reviewed the supplied Python server: random 5-byte key, cyclic XOR, hex encode; mounted a known-plaintext attack on THM{ and brute-forced the remaining key character.
W1SEGUY exposed a TCP listener on 1337 that advertised a hex-encoded XOR ciphertext and challenged the operator for the encryption key. Source review showed setup() XORing each flag byte with key[i % 5], then hex-encoding the result. The key was five characters from ascii_letters + digits. Because engagement flags begin with THM{, ciphertext[0:4] XOR b'THM{' yields the first four key bytes. The fifth character is a 62-candidate brute: decrypt, require startswith THM{ and endswith }. Recovered plaintext for flag 1 was THM{p1alntExtAtt4ckcAnr3alLyhUrty0urxOr}. Submitting the full key to the service returned flag 2: THM{BrUt3_ForC1nG_XOR_cAn_B3_FuN_nO?}. Classic repeating-key XOR failure under known plaintext.
Business impact
Homegrown XOR with short cyclic keys is not encryption. Known format prefixes (API tokens, flag wrappers, magic headers) collapse the keyspace. Use authenticated encryption (AES-GCM, ChaCha20-Poly1305) with proper key management — never roll cyclic XOR for confidentiality.
Known-plaintext recovery and key submission
nc to :1337; derived key prefix from THM{; brute-forced last byte; submitted key for second artifact.
SERVER LOGIC (SUPPLIED)
setup_xor.py
def setup(flag, key):
xored = "".join(
chr(ord(flag[i]) ^ ord(key[i % len(key)]))
for i in range(len(flag))
)
return xored.encode().hex()
# key = 5 chars from ascii_letters + digitsCRACK
w1seguy_crack.py
import binascii, string
def crack(hex_ct, known=b"THM{"):
ct = binascii.unhexlify(hex_ct)
prefix = "".join(chr(ct[i] ^ known[i]) for i in range(4))
for c in string.ascii_letters + string.digits:
key = prefix + c
pt = bytes(ct[i] ^ ord(key[i % 5]) for i in range(len(ct)))
if pt.startswith(b"THM{") and pt.endswith(b"}"):
return key, pt.decode()
return None, NoneOPERATOR · CRYPTO
savvy@lab:~$ nc 10.10.10.10 1337
This XOR encoded text has flag 1: <hex>…
savvy@lab:~$ python3 w1seguy_crack.py
THM{p1alntExtAtt4ckcAnr3alLyhUrty0urxOr}
What is the encryption key? <recovered-5-char-key>
flag 2: THM{BrUt3_ForC1nG_XOR_cAn_B3_FuN_nO?}
Remediation
Ban custom XOR/stream toys in production crypto reviews. Prefer libsodium/OpenSSL AEAD APIs. If a challenge or canary format is public, assume attackers will use it as known plaintext against any weak cipher.