┌──(operator@kali)-[~/writeup]
└─$ cat flags.txt
flag captured
flag
ZeroDays{8189a1ddc6e70ef1bb2c2346b628c7d9}┌──(operator@kali)-[~/writeup]
└─$ cat 01_session.log
exploit session
┌──(myenv)─(alvan㉿vbox)-[~/Downloads/escs]
└─$ python exploit.py
[+] Opening connection to 34.244.222.63 on port 13301: Done
Soul vanquished.
Soul vanquished.
Soul vanquished.
> Soul vanquished.
> Soul vanquished.
Soul vanquished.
> Soul vanquished.
Soul vanquished.
Soul vanquished.
> Soul vanquished.
Soul vanquished.
Soul vanquished.
Soul vanquished.
Soul vanquished.
> Soul vanquished.
> Soul vanquished.
> Soul vanquished.
Soul vanquished.
> Soul vanquished.
> Soul vanquished.
> Soul vanquished.
> Soul vanquished.
Soul vanquished.
> Soul vanquished.
> Soul vanquished.
Soul vanquished.
> Soul vanquished.
Soul vanquished.
Soul vanquished.
> Soul vanquished.
> Soul vanquished.
Soul vanquished.
> Soul vanquished.
> Soul vanquished.
> Soul vanquished.
Soul vanquished.
> Soul vanquished.
Soul vanquished.
Soul vanquished.
> Soul vanquished.
Soul vanquished.
> Soul vanquished.
Soul vanquished.
Soul vanquished.
Soul vanquished.
Soul vanquished.
> Soul vanquished.
> Soul vanquished.
Soul vanquished.
Soul vanquished.
|\ | \ / \_/
| \| | / \
/ Wonderful work.
|
| I knew you could do it. Here, have this. You Father wanted you
\ to have it once you're ready.
b'ZeroDays{8189a1ddc6e70ef1bb2c2346b628c7d9}'
[*] Closed connection to 34.244.222.63 port 13301
┌──(operator@kali)-[~/writeup]
└─$ cat 02_exploit.py
exploit script
from Crypto.Cipher import AES
from os import urandom
import random
import binascii
flag = open("flag.txt", "rb").read()
class NextGenPRF:
def __init__(self, option):
self.key = urandom(16)
# 1 = pseudorandom, 0 = true random
self.option = option
def pseudorandom(self, msg: bytes): # pseudorandom function
cipher = AES.new(msg, AES.MODE_ECB)
random_string = random.randbytes(16)
ciphertext = cipher.encrypt(random_string) + cipher.decrypt(random_string)
return ciphertext
def true_random(self, msg):
random_string = random.randbytes(32)
return bytes(rs ^ m for rs, m in zip(random_string, msg))
def oracle(self, msg):
if (len(msg) != 32):
return None
if (self.option == 0):
return self.true_random(msg)
else:
return self.pseudorandom(msg)
def guess(self, mode_guess):
return (self.option == mode_guess)
def judgement(questions_left, left_soul, right_soul):
while True:
if (questions_left == 0):
break
print("State your question for the souls: ", end="")
hex_message = input()
try:
message = binascii.unhexlify(hex_message)
except binascii.Error:
continue
if (len(message) != 32):
continue
left_response = binascii.hexlify(left_soul.oracle(message)).decode("utf-8")
right_response = binascii.hexlify(right_soul.oracle(message)).decode("utf-8")
print(f"The left soul screams: {left_response}")
print(f"The right soul screams: {right_response}")
return (questions_left - 1)
BANNER = """
___ _ __ __ ___
/ __| /_\ | \/ | __|
| (_ |/ _ \| |\/| | _|
\___/_/_\_\_| |_|___|
/ _ \| __|
| (_) | _|
\___/|_| ___ ___ ___
| || | /_\ | \| __/ __|
| __ |/ _ \| |) | _|\__ \
|_||_/_/ \_\___/|___|___/
\n\n\n
"""
NYX_HEADER = """
|\ | \ / \_/
| \| | / \
"""
INTRO_TEXT = """
/ Melinoë, dear, I have heard of your issues with Chronos,
| You know your Father cannot stomach such, and your Mother, why, she
| worries about you. Your quest is mighty, but it may prove your last.
| To prepare you against Chronos' illusions, your Father devised a
| great test. Tell apart the souls, strike true, and you will be
\ ready for all that is to come.
"""
FAIL = """
...the light fades. Back to the forest you go.
"""
SUCCESS = """
Soul vanquished.
"""
WIN_TEXT = """
/ Wonderful work.
|
| I knew you could do it. Here, have this. You Father wanted you
\ to have it once you're ready.
"""
def main():
souls = 50
questions = 50
print(BANNER)
print(NYX_HEADER)
print(INTRO_TEXT)
while (souls > 0):
truth = random.getrandbits(1) # 0 is left, 1 is right
if (truth == 0):
left = NextGenPRF(0) # true random
right = NextGenPRF(1) # pseudorandom
else:
left = NextGenPRF(1) # pseudorandom
right = NextGenPRF(0) # true random
while True:
print("> ", end="")
decision = input()
match decision:
case "1": # left door
if (left.guess(0)):
print(SUCCESS)
souls -= 1
break
else:
print(FAIL)
return
case "2": # right door
if (right.guess(0)):
print(SUCCESS)
souls -= 1
break
else:
print(FAIL)
return
case "3":
if questions == 0:
print(FAIL)
return
questions = judgement(questions, left, right)
print(NYX_HEADER)
print(WIN_TEXT)
print(flag)
if (__name__ == "__main__"):
main()
┌──(operator@kali)-[~/writeup]
└─$ cat 03_exploit_alt.py
exploit script (variant)
#!/usr/bin/env python3
from pwn import remote
from Crypto.Cipher import AES
import os, binascii, sys
HOST = "34.244.222.63"
PORT = 13301
def is_pseudorandom(resp_bytes, key):
# resp_bytes length must be 32
a = resp_bytes[:16]
b = resp_bytes[16:32]
aes = AES.new(key, AES.MODE_ECB)
try:
x = aes.decrypt(a)
y = aes.encrypt(b)
return x == y
except Exception:
return False
def play_once(r):
# choose to ask a question
r.sendline(b"3")
# pick any 32-byte message (we'll use random)
msg = os.urandom(32)
hexmsg = binascii.hexlify(msg)
r.recvuntil(b"State your question for the souls:")
r.sendline(hexmsg)
# parse the two responses
r.recvuntil(b"The left soul screams: ")
left_hex = r.recvline().strip()
r.recvuntil(b"The right soul screams: ")
right_hex = r.recvline().strip()
left_bytes = binascii.unhexlify(left_hex)
right_bytes = binascii.unhexlify(right_hex)
left_is_pseudo = is_pseudorandom(left_bytes, msg)
right_is_pseudo = is_pseudorandom(right_bytes, msg)
# sanity:
if left_is_pseudo == right_is_pseudo:
# both same -> something unexpected; fail-safe: guess left false (shouldn't happen)
choice = b"1"
else:
# true random is the one that is NOT pseudorandom
if not left_is_pseudo:
choice = b"1"
else:
choice = b"2"
r.recvuntil(b"> ")
r.sendline(choice)
# Read the immediate reply (SUCCESS or FAIL)
out = r.recvline(timeout=2)
return out
def main():
r = remote(HOST, PORT)
# read banner and intro until first prompt
r.recvuntil(b"> ")
# The server gives you 50 souls to clear; loop until we get the flag or connection closes
while True:
# we will prefer to ask question each round and then choose.
try:
result = play_once(r)
except Exception as e:
print("Exception:", e)
break
if result is None:
# maybe the server printed multiple lines; try to read more
try:
data = r.recv(timeout=1)
print(data.decode(errors='ignore'))
except:
pass
# If we reduced souls to 0 the server prints flag after the loop — read until EOF/flag
# Look ahead for either fail, success, or final flag marker
data = r.recv(timeout=1)
if data:
s = data.decode(errors='ignore')
print(s, end="")
# crude check for flag (adjust if flag format differs)
if "flag" in s.lower() or "{" in s:
# print remaining
try:
rest = r.recv(timeout=2)
print(rest.decode(errors='ignore'))
except:
pass
break
r.close()
if __name__ == "__main__":
main()