jagacha
Points: 375 [1000]
Description
JAGASTA
Solution
Looking at the attached files given, we can see that there are 2 files, jagacha.py
and config.py
.
Inside of jagacha.py
, we can see that it is a python script that uses the config.py
file to generate a random seed after a user connects to the service.
We are then prompted to choose an option.
- Option
1
would generate stats based on a random 64-bit integer. - Option
2
would allow us to predict a random 64-bit integer which will give us the flag. - Option
3
would allow us to exit the program.
async def handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
client_ip, client_port = reader._transport.get_extra_info('peername')
logging.info(f"New connection from: {client_ip}:{client_port}")
rand = random.Random(get_seed())
try:
option = None
while option != 3:
await print_menu(writer)
option = await read_number(reader, writer)
if 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"
)
elif option == 3:
writer.write(b"See you again!\n")
Looking at the get_seed
function, we can see that it is using the config.py
file to generate a seed.
def get_seed():
SEED = 0xdeadc0de
return SEED + time.time_ns()
We can see that the seed is generated by adding the current time in nanoseconds to the SEED
variable. This means we cannot use predict the seed, but as the application uses Python’s default random.Random()
, we can use a Merseene Twister program such as randcrack to predict the next random number.
HOWEVER! The randcrack
program does not allow us to feed in a 64-bit number to the library, but we can get around this limitation by splitting the 64-bit number into 2 32-bit numbers and feeding them into the library.
Since the application provides us with the STR
, DEX
, INT
, and LUK
values as 4 separate 16-bit integers, we can combine STR + DEX
and INT + LUK
to get the two 32-bit numbers to feed into randcrack
.
As randcrack
requires exactly 624 rounds of random 32-bit integers to predict the next random number, we can use the 1
option to generate 312 rounds of random numbers since there are 2 32-bit numbers generated per round.
for i in range(312):
conn.sendline(b'1')
conn.recvuntil(b"Here are the stats of your character:\n")
rand_val = 0
for j in range(2):
rand_val <<= 16
val = conn.recvuntil(b"\n")
rand_val |= int(val.decode().split(": ")[1].strip())
rand_val2 = 0
for j in range(2):
rand_val2 <<= 16
val = conn.recvuntil(b"\n")
rand_val2 |= int(val.decode().split(": ")[1].strip())
rc.submit(rand_val2)
rc.submit(rand_val)
conn.recv()
After ingesting the required 624 rounds of random numbers, we can use the 2
option to predict the next random number and use it to get the flag.
conn.sendline(b'2')
num = rc.predict_getrandbits(64)
print(conn.recv())
conn.sendline(str(num).encode())
print(conn.recv())
conn.sendline(b'3')
print(conn.recvall())
b'Enter your lucky number: '
b'Congrats! You have pulled the limited SSS-rated rarity flag-chan!\nSTF22{W@IFU5_L@1FU5}'
[+] Receiving all data: Done (0B)
[*] Closed connection to xxx port 32123
b''
Flag
STF22{W@IFU5_L@1FU5}