jagacha
Description
CTF: STACK the Flags 2022
As a wise man once said, there is no greater happiness in life than to hit a homerun with your SSS-tier gacha pulls on the first try. However, reality is not that easy. Therefore, we are providing a chance for you to pull the SSS-rarity flag if you can guess the number correctly. However, if you are still unsure of your chances of guessing it right, you’re always welcome to do a random gacha pull until you change your mind.
Note: Submit the flag in all caps.
jagacha.py
config.py
Solution
Pwned by @skytect
Looking through jagacha.py, you may realise that option 1 generates and shows you the next 64 random bits, while option 2 allows you to guess the next 64 random bits for the flag.
# jagacha.py, L75–104if option == 1:    num = rand.getrandbits(64)    gacha = GACHA_KEYS[num % len(GACHA_KEYS)]    writer.writelines(        (            f"Congrats! You have pulled a {gacha}!\n".encode(),            GACHAS[gacha],            b"Here are the stats of your character:\n",            f"STR: {num>>48 & 0xffff}\n".encode(),            f"DEX: {num>>32 & 0xffff}\n".encode(),            f"INT: {num>>16 & 0xffff}\n".encode(),            f"LUK: {num & 0xffff}\n".encode(),            b'\n',        )    )elif option == 2:    num = rand.getrandbits(64)    lucky_number = await read_number(        reader, writer, b"Enter your lucky number: "    )    if lucky_number == num:        writer.writelines((            b"Congrats! You have pulled the limited SSS-rated rarity flag-chan!\n",            FLAG,        ))        option = 3  # Quit    else:        writer.write(            b"Oops! Looks like you are not as lucky as you thought! Try again!\n\n"        )Next let’s see how the seed is generated. Unfortunately, config.py shows us that we can’t really determine the seed as it’s generated by the nanosecond.
# config.py, L6–8def get_seed():    SEED = 0xdeadc0de    return SEED + time.time_ns()However, we can still use existing knowledge to determine the state of the random generator. Python uses the Mersenne Twister algorithm to generate random numbers, which shifts its state every 624 32-bit numbers. So, we can crack it with that many bits.
I wrote a solve script below using randcrack.
import reimport timefrom pwn import *from randcrack import RandCrack
conn = remote('157.245.52.169', '31301')conn.recvuntil(b'> ')
def get_64_bits() -> int:    conn.sendline(b'1')    r = conn.recvuntil(b'> ').decode()    m = re.findall(r'Here are the stats of your character:\nSTR: (\d+)\nDEX: (\d+)\nINT: (\d+)\nLUK: (\d+)\n', r)    n = [int(s) for s in m[0]]    return n[0] << 48 | n[1] << 32 | n[2] << 16 | n[3]
def submit_flag(n: int) -> str:    conn.sendline(b'2')    r = conn.recvuntil(b': ')    conn.sendline(str(n).encode())    r = conn.recvall().decode()    return r
rc = RandCrack()for i in range(624 // 2):    b = get_64_bits()    rc.submit(b & 0xffffffff)    rc.submit((b >> 32) & 0xffffffff)
r = submit_flag(rc.predict_getrandbits(64))print(r)Running the script gives us the flag.
[x] Opening connection to 157.245.52.169 on port 31301[x] Opening connection to 157.245.52.169 on port 31301: Trying 157.245.52.169[+] Opening connection to 157.245.52.169 on port 31301: Done[x] Receiving all data[x] Receiving all data: 0B[x] Receiving all data: 86B[+] Receiving all data: Done (86B)[*] Closed connection to 157.245.52.169 port 31301Congrats! You have pulled the limited SSS-rated rarity flag-chan!STF22{W@IFU5_L@1FU5}STF22{W@IFU5_L@1FU5}