← Back to Writeups
HTBEasyCryptography

Six Seven

D3v0o0Nu119/2/20264 min read
#cryptography#htb#easy#exhaustive_seed_brute_force#known_plaintext_validation#deterministic_keystream_recove

Six Seven

Platform: Metactf | Category: Cryptography | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | 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{.

Recon

Port scan

bash
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: metactf | ID: 20260410_metactf_six_seven
  • Tags: brute_force, known_plaintext, stream_cipher, low_entropy_seed, prng_weakness, urandom_misuse
  • Indicators: The keystream is generated by a deterministic recurrence key[i] = gen(key[i-1]), The seed is a single byte from os.urandom(1) — only 256 possible keystreams, Known plaintext prefix: flag starts with DawgCTF{, Challenge description hints at streaming + brainrot 6-7 meme (referring to 6*7 in the gen function)
  • Source: 20260410_metactf_six_seven.md

Foothold

Vulnerability / Misconfiguration

  1. exhaustive_seed_brute_force
  2. known_plaintext_validation
  3. deterministic_keystream_recovery
bash
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

bash
sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
bash
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • exhaustive_seed_brute_force
  • known_plaintext_validation
  • deterministic_keystream_recovery
  • Tags: brute_force, known_plaintext, stream_cipher, low_entropy_seed, prng_weakness, urandom_misuse

Original Writeup

<details><summary>Click to expand original content</summary>

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 script
  • output.txt — ciphertext as hex

Source: https://github.com/UMBCCyberDawgs/dawgctf-sp26/tree/main/Six%20Seven

Challenge Source

python
from 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 gen is the brainrot joke — it packs "six" and "seven" (6, 7, 42) into an arithmetic expression and reduces mod 255. It is deterministic.
  • encrypt seeds the key with one random byte and then chains gen to 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:

  1. Low-entropy seed. Only one byte of randomness is used. Brute force is trivial — 256 candidate decryptions.
  2. 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
#!/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}
</details>

signed by XESXOR