$ cat writeup.md…
$ cat writeup.md…
broncoctf2026
Task: two ciphertexts (random-lowercase garbage + flag) encrypted with the SAME random key, where the flag's key is the per-byte bit-reversal of the garbage key. Solution: a two-time-pad style attack — recover each key byte from the known-lowercase garbage plaintext, bit-reverse it, XOR with the flag ciphertext, and narrow candidates with a second garbage block whose derived-key high nibble is deterministic.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
"I've finally fixed the entropy issue with the OTPs! The secret: use them twice, but only slightly. It'll make more sense once you look at the code. Anyways, I'm so confident in my new algorithm that I can send the flag (which I assure you is valid English leetspeak) with it and you will know NOTHING!"
Two files are given: challenge.py (redacted FLAG) and output.txt with two hex ciphertexts labelled "Random garbage" and "The flag". Goal: recover the flag.
challenge.py generates one random key and emits two ciphertexts:
key = random.randbytes(N) # N = len(FLAG) = 25 real_garb = "".join(random.choices(string.ascii_lowercase, k=2*N)) # guaranteed a..z garb = block_encrypt(key, real_garb) # ciphertext #1 key = scramble(key) # per-byte bit reversal flag = block_encrypt(key, FLAG) # ciphertext #2
Two crucial facts:
Same underlying key. The flag is encrypted with scramble(key), and scramble merely reverses the bit order of every key byte:
for i in range(8): num |= ((k & (1 << i)) >> i) << (7 - i)
This is fully reversible: flag_key[i] = reverse_bits(garb_key[i]). So both messages share the same pad, just bit-reversed per byte — the "use them twice, but only slightly."
...
$ grep --similar