Six Seven
Six Seven
Platform: metactf | Category: Crypto | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-04-10 | Status: Solved Techniques: exhaustive_seed_brute_force, known_plaintext_validation, deterministic_keystream_recovery
Summary
Task: a custom stream cipher XORs the flag against a deterministic keystream whose only random input is a single byte from os.urandom(1). Solution: brute-force all 256 possible seeds and select the candidate whose decryption starts with the known prefix DawgCTF{.
Six Seven — metactf (DawgCTF SP26)
Description
I heard something about streaming before in my crypto book, idk what twitch has to do with crypto, but I decided to add my favorite brainrot to it.
Files provided:
chall.py— encryption scriptoutput.txt— ciphertext as hex
Source: https://github.com/UMBCCyberDawgs/dawgctf-sp26/tree/main/Six%20Seven
Challenge Source
python read-only • copy allowedfrom Crypto.Util.strxor import strxor from secrets import flag import os assert flag[:8] == b"DawgCTF{" def gen(start): return (((6 * 7) * (start - 6) * 7) + ((start * 6) - 7) * (start ^ 6)) % 255 def encrypt(message): start = os.urandom(1); key = start; for i in range(1, len(message)): key += gen(key[i-1]).to_bytes(1, "big") return strxor(key, message) ct = encrypt(flag) print("ct =", ct.hex())
Ciphertext (67 bytes):
9f2eadbd998e9ca1aab6bfbba9bf85afa9bf85a9bfb9a8bfaea985b3b485a3b5afa885a9aea8bfbbb785b9b3aab2bfa8a985ece3b8bcbfebe3eebbbceee9bceab9bea7
Summary
The custom stream cipher draws exactly one byte of entropy from os.urandom(1) as the seed. Every subsequent keystream byte is fully determined by the recurrence key[i] = gen(key[i-1]). Because the seed has only 256 possible values, the entire keystream has only 256 possible instantiations. We brute-force all of them and use the known plaintext prefix DawgCTF{ to recognise the correct one.
Recon
Reading chall.py:
- The function
genis the brainrot joke — it packs "six" and "seven" (6, 7, 42) into an arithmetic expression and reduces mod 255. It is deterministic. encryptseeds the key with one random byte and then chainsgento produce as many keystream bytes as the plaintext has.- The plaintext is XORed against that keystream with
strxor.
The cryptographic weakness is immediate: the random seed space is 2^8 = 256, not 2^128 or similar. No matter how complex gen looks, the total entropy of the keystream cannot exceed that of the seed.
A quick sanity check on plaintext length: the ciphertext is 67 bytes, which matches a typical DawgCTF{...} flag length with a plausible body.
Vulnerability
Two independent design flaws collapse to the same break:
- Low-entropy seed. Only one byte of randomness is used. Brute force is trivial — 256 candidate decryptions.
- Known plaintext prefix.
assert flag[:8] == b"DawgCTF{"guarantees eight known plaintext bytes at offset 0. These let us reject 255 of the 256 candidates without any human inspection.
Combining the two, the attack is purely offline and runs in microseconds.
As an aside, the plaintext itself turns out to be the lesson: use secrets (or at least enough entropy) for cryptographic seeds, never a single os.urandom(1) byte.
Exploitation
Full solver:
python read-only • copy allowed#!/usr/bin/env python3 """ Six Seven - DawgCTF SP26 (crypto) The stream cipher uses a single random byte `start` as the seed. All subsequent keystream bytes are deterministic: key[i] = gen(key[i-1]) Since `start` is only 1 byte, there are only 256 possible keystreams. Brute force all 256 and check for known plaintext prefix "DawgCTF{". """ from Crypto.Util.strxor import strxor def gen(start): return (((6 * 7) * (start - 6) * 7) + ((start * 6) - 7) * (start ^ 6)) % 255 def main(): ct = bytes.fromhex( "9f2eadbd998e9ca1aab6bfbba9bf85afa9bf85a9bfb9a8bfaea985b3b485a3b5afa88" "5a9aea8bfbbb785b9b3aab2bfa8a985ece3b8bcbfebe3eebbbceee9bceab9bea7" ) for start in range(256): key = bytes([start]) for i in range(1, len(ct)): key += gen(key[i - 1]).to_bytes(1, "big") pt = strxor(key, ct) if pt[:8] == b"DawgCTF{": print(f"[+] start byte = {start}") print(f"[+] flag = {pt.decode()}") return if __name__ == "__main__": main()
Output:
[+] start byte = 219
[+] flag = DawgCTF{REDACTED}