← ./home
root@hades:~/ecsc#
SESSION ACTIVE

ecsc ctf log

HADESECSC · pwn

EVENT ECSC CATEGORY Pwn RESULT FLAG CAPTURED
ecsc pwn exploit
▼ scroll to begin
┌──(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()