https://app.hackthebox.com/challenges/704

Description

Uncertain of their safety from other potentially hostile communities, the survivors recognize the necessity of arming themselves with laser weapons and flamethrowers for self-defense. Rumor has it that an old casino on the city’s outskirts holds advanced weaponry. However, to gain access to this private area, an uninvited visitor must play a seemingly impossible game of chance and accumulate a specific amount of winnings. Is the challenge as it appears, or can you prove them wrong?

Exploitation

#!/usr/bin/env python3
from pwn import *
from Crypto.Hash import SHA256

def get_process():
    if len(sys.argv) > 1:
        ip, port = sys.argv[1].split(':')
        return remote(ip, int(port))
    print(f"Usage: {sys.argv[0]} <ip:port>")
    sys.exit(1)

def keyed_hash(key, inp):
    return SHA256.new(key + inp).digest()

def custom_hmac(key, inp):
    return keyed_hash(keyed_hash(key, b"Improving on the security of SHA is easy"), inp) + keyed_hash(key, inp)

def check_output(full_output, inp, k_prime):
    output = bytes.fromhex(full_output)
    return output[:32] == keyed_hash(k_prime, bytes.fromhex(inp))

def get_fixed_key(conn):
    msg = b"Improving on the security of SHA is easy"
    conn.recvline()
    my_balance = 100
    appeared = []
    for i in range(10):
        my_balance -= 10
        conn.recvuntil(b"Option: ")
        conn.sendline(b"2")
        conn.recvuntil(b"hex :: ")
        conn.sendline(msg.hex().encode())
        potential_hash = conn.recvline().strip().decode().split()[-1]
        appeared.append(potential_hash)
        if appeared.count(appeared[-1]) > 1:
            return bytes.fromhex(appeared[-1][64:]), my_balance
    return None, my_balance

def win_game(conn, H_k_msg, my_balance):
    while my_balance < 500:
        conn.recvuntil(b"Option: ")
        conn.sendline(b"3")
        curr_input = conn.recvline().decode().strip().split()[-1]
        curr_output = conn.recvline().decode().strip().split()[-1]
        conn.recvuntil(b" :: ")
        conn.sendline(b"0" if check_output(curr_output, curr_input, H_k_msg) else b"1")
        my_balance += 5 if "Lucky" in conn.recvline().decode() else 0

def get_flag(conn):
    conn.recvuntil(b"Option: ")
    conn.sendline(b"1")
    return conn.recvline().decode().strip()

def pwn():
    try:
        conn = get_process()
        if not conn: return
        H_k_msg, my_balance = get_fixed_key(conn)
        if not H_k_msg: return
        win_game(conn, H_k_msg, my_balance)
        print(get_flag(conn))
    except Exception as e:
        print(f"Error: {str(e)}")
    finally:
        if conn: conn.close()

if __name__ == '__main__':
    pwn()

Summary

Not that random: reconstruct the PRNG state from the leak, replay it, and recover the flag.