This challenge prints one number for each character of the flag:
BITS = 56
A = int.from_bytes(os.urandom(BITS//8), "little")
B = int.from_bytes(os.urandom(BITS//8), "little")
SEED = int.from_bytes(os.urandom(BITS//8), "little")
def rng(x, size):
return (x*A+B) & ((2**size)-1)
def gen_random(seed, bits, mask):
state = seed
while True:
state = rng(state, bits)
yield state & mask
def main():
print("Here are some random numbers, now guess the flag")
rng = gen_random(SEED, BITS, 0xFF)
for i in range(len(FLAG)):
print(next(rng) ^ ord(FLAG[i]))
The generator is a linear congruential generator, but the output only uses the lowest byte of the state. Because the update is linear, the lowest byte also follows an LCG modulo
state[i + 1] = state[i] * a + b mod 256
We know that the flag starts with
outputs = [
212,222,52,234,44,156,96,149,122,84,164,111,148,46,218,43,
165,239,14,239,31,175,5,149,122,68,177,123,162,44,224,55,
225,238,26,157,48,155,90,129,125,105,176,20,174,4,242,43,
228,215,26,232,48,155,112,171,98,87,197,21,176,31,133,84,
228,215,30,239,20,174,90,171,37,111,142,111,144,47,132,5,
167,238,26,148,103,132,113,167,97,111,160,123,161,4,241,39,
225,239,32,251,33,132,113,187,98,108,160,119,161,46,218,93,
240,216,26,148,35,151,96,213,112,95,160,99,184,47,206,47,
196,239,69,156,59,175,78,172,42,112,
]
known_flag = "CSCG{"
states = [outputs[i] ^ ord(known_flag[i]) for i in range(len(known_flag))]
for a in range(256):
b = (states[1] - states[0] * a) % 256
if (states[2] - states[1] * a) % 256 == b:
print(f"Found LCG params modulo 256: a={a}, b={b}")
break
recovered_flag = list(known_flag)
state = states[-1]
for i in range(len(known_flag), len(outputs)):
state = (state * a + b) % 256
recovered_flag.append(chr(state ^ outputs[i]))
print("".join(recovered_flag))
Running this gives us the flag:
CSCG{QWxmYSBCcmF2byBHb2xmIFVuaWZvcm0gVmljdG9yIEFsZmEgVGFuZ28gR29sZiBCcmF2byBGb3h0cm90IFJvbWVvIFJvbWVvIFVuaWZvcm0gUm9tZW8gRWNobyBSb21lbyA=}
The flag body is base64. Decoding it gives this extra message:
Alfa Bravo Golf Uniform Victor Alfa Tango Golf Bravo Foxtrot Romeo Romeo Uniform Romeo Echo Romeo
Taking the first letter of each word gives
NOTHINGTOSEEHERE