$ cat writeup.md…
$ cat writeup.md…
broncoctf2026
Task: a custom PRNG is seeded with the flag bytes; an oracle discloses only guess%12, guess%7, guess%5 (month/day/region) per output. Solution: reconstruct each raw byte via CRT (lcm(12,7,5)=420>255), invert the linear recurrence (orig = 2*a[k]-a[k+1] mod 256), and brute-force the rotation/reverse schedule, validating by full re-simulation of all 101 outputs.
$ 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 made a birthday oracle recently, but I can't get it to work at all. It only gets the day of the week right 14% of the time! Here are my results from the last testing session... can you take a look at it and see what's wrong?
Two files are provided:
results.txt — a transcript of the oracle guessing month / day-of-week / region 101 times.challenge.py — the source of the "broken" PRNG-backed oracle.Goal: recover the flag, which is used directly as the PRNG entropy/seed.
The "14% right on day of week" hint is 1/7 — the RNG is not random at all, it is a fully deterministic linear recurrence whose entire internal state is the flag.
The vulnerable source:
import random class RNG: def __init__(self, entropy: bytes): self.nextrep = 0 shift = random.randint(0, len(entropy)) self.schedule = list(range(len(entropy))) self.schedule = self.schedule[shift:] + self.schedule[:shift] if random.random() > 0.5: self.schedule.reverse() self.state = list(entropy) def next(self): a = sum(self.state) % 256 self.state[self.schedule[self.nextrep % len(self.schedule)]] = a self.nextrep += 1 return a flag = open("flag.txt","rb").read() rand = RNG(flag) # loop prints "born in {month}... on a {day}, in {area}" guess = rand.next() month = months[guess % 12] day = days[guess % 7] area = areas[guess % 5]
Key observations:
The flag IS the state. state is initialized to the flag bytes. There is no external randomness in next() — only random.randint/random.random in the constructor pick a rotation shift and an optional reverse of the update schedule.
Each output leaks a full state sum. Output a[k] = sum(state) % 256 before step k. Then step k overwrites exactly one state slot p = schedule[k] with a[k].
...
$ grep --similar