cscg24-adventure

CSCG 2024 Challenge 'Choose Your Own Adventure'
git clone https://git.sinitax.com/sinitax/cscg24-adventure
Log | Files | Refs | sfeed.txt

commit a04e881dd365292f065f3ce3e8bd2d9ec5a4bbbe
parent 77a174dce674e0a9bef862db68d45e83cbb778f6
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sat, 13 Apr 2024 23:29:05 +0200

Add solution

Diffstat:
A.gitignore | 1+
Asolve/Dockerfile | 18++++++++++++++++++
Asolve/docker-compose.yml | 6++++++
Asolve/flag | 1+
Asolve/notes | 12++++++++++++
Asolve/run.sh | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asolve/solve | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 242 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/solve/Dockerfile b/solve/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:jammy-20240125@sha256:bcc511d82482900604524a8e8d64bf4c53b2461868dac55f4d04d660e61983cb + +# https://github.com/reproducible-containers/repro-sources-list.sh +# Sorry for the mess. +ADD --checksum=sha256:4e7e6536b206488b2414d1fa2272e8bbf17fbe7d11e5648eb51284c8fa96b0a9 \ + https://raw.githubusercontent.com/reproducible-containers/repro-sources-list.sh/v0.1.1/repro-sources-list.sh \ + /usr/local/bin/repro-sources-list.sh + +RUN chmod +x /usr/local/bin/repro-sources-list.sh && \ + /usr/local/bin/repro-sources-list.sh && \ + apt-get update && \ + DOCKER_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + zsh socat bsdmainutils + +COPY run.sh run.sh +RUN chmod +x run.sh +EXPOSE 1024 +CMD socat TCP-LISTEN:1024,fork,reuseaddr EXEC:"./run.sh",stderr diff --git a/solve/docker-compose.yml b/solve/docker-compose.yml @@ -0,0 +1,6 @@ +services: + service: + build: + context: . + ports: + - "1024:1024" diff --git a/solve/flag b/solve/flag @@ -0,0 +1 @@ +CSCG{zsh_subshell_random_seeding?_good_catch!} diff --git a/solve/notes b/solve/notes @@ -0,0 +1,12 @@ +fuck this challenge, no interesting concepts, only tricky details + +look into zsh source, sind out that RANDOM uses C srand / rand + +srand is called on zsh init with epoch sec+usec seed + +we can guess the seed, filter using known values and do trial decryptions +to find the right one + +tricky detail: subshell does not modify the rand state of the parent shell +so in the encoded piece generation the random value does not change.. +haha that did not cost me my sanity diff --git a/solve/run.sh b/solve/run.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env zsh + +echo "Welcome to the Mystical Coin CTF Adventure!" +echo "In your bag, you carry a special coin, known for always bring you luck. You've been throwing it repeatedly, lost in thought." +echo "After $RANDOM throws, you finally get your most precious sequence:" +sleep 1 +echo "" + +PASSKEY="" + +function mystical_coin_flip { + echo "An ancient coin lands in your hand. Will it be heads (H) or tails (T)?" + read "?Your call: " user_call + + # Validate input + if ! [[ "$user_call" =~ ^[HhTt]$ ]]; then + echo "Invalid call. Please choose heads (H) or tails (T)." + exit 1 + fi + + # Simulate a coin flip + VAL=$RANDOM + if (( $VAL % 2 )); then + coin_result="H" + else + coin_result="T" + fi + PASSKEY+=$coin_result + echo "The coin lands on $coin_result." + + if [[ ${user_call:u} == ${coin_result:u} ]]; then + echo "Fortune smiles upon you!" + echo "" + return 0 + else + echo "Oh no! The coin has other plans..." + echo "" + return 1 + fi +} + +mystical_coin_flip +mystical_coin_flip +mystical_coin_flip +mystical_coin_flip +mystical_coin_flip + +echo "Distracted by this particular sequence of flips, you suddenly find yourself at the entrance of an ancient and mystical land." +echo "Your destiny in this realm is mysteriously linked to this coin." +sleep 1 +echo "" + +echo "You stand at a mystical crossroads, with two paths forward: the Portal of Fate (P) or the Oracle's Den (O)." +read "?Choose P for the Portal of Fate or O for the Oracle's Den: " choice + + +if [[ $choice == [Pp]* ]]; then + echo "You approach the Portal of Fate, shimmering with ancient energy." + if mystical_coin_flip; then + echo "As the portal swirls open, a radiant light envelops you, transporting you to a realm beyond imagination." + echo "In this realm, you navigate a labyrinth of stars, solve riddles whispered by ancient trees, and unlock a celestial puzzle that aligns the constellations." + echo "At the heart of the realm, you find a crystal dais. Etched upon it in shimmering light is the flag part: 'PART2}'." + echo "A voice, as old as time, echoes around you, 'The journey through the stars is a reflection of the journey within. You have navigated both with wisdom.'" + echo "With a flash of light, you're back at the portal, holding a token from the starry realm as a memento of your adventure." + else + echo "The portal catapults you into space, where you become a human comet, blazing through the cosmos!" + echo "It's a breathtaking sight, but alas, a comet can't collect flag parts." + fi +else + echo "You tread cautiously towards the Oracle's Den. The coin is soaring through the air, heading back to your hand." + if mystical_coin_flip; then + echo "The Oracle guides you to a hidden library, a sanctuary of ancient secrets." + echo "Within its silent walls, you discover a curious scroll sealed with a mystic symbol." + + for ((i=1; i<=32; i++)) + do + PASSKEY=$(echo $PASSKEY$RANDOM | md5sum) + echo "$PASSKEY" + done + export PASSKEY=$PASSKEY + echo "$PASSKEY" + echo "Unfurling the scroll reveals a series of enigmatic symbols and a cryptic cipher:" + + echo "CSCG{FLAG_PART_1_" | openssl enc -aes-256-cbc -e -pass env:PASSKEY 2>/dev/null | hexdump -C + + echo "The Oracle hints that it's a hexadecimal code, part of a larger puzzle." + echo "She whispers that understanding its meaning requires knowledge found only in the distant lands of your quest." + echo "This cipher, a fragment of your flag, ignites a deeper curiosity, leading you to seek the wisdom needed to unlock its secrets." + else + echo "The Oracle laughs so heartily at your coin call that a gust of wind from her chuckle sweeps you off your feet." + echo "You find yourself blown into a painting, transforming into a permanent, smiling figure in an idyllic landscape within the frame." + echo "As you adjust to your new painted existence, surrounded by serene hills and a peaceful river, you realize that while this painted world is beautiful, it holds no flag parts for your quest." + echo "You become a cherished character in the Oracle's gallery, admired by all who pass by, but your journey for the flag ends in this artistic realm." + fi +fi diff --git a/solve/solve b/solve/solve @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +from pwn import * +from hashlib import md5 +from ctypes import CDLL + +import subprocess +import sys + +# context.log_level = "DEBUG" + +args = sys.argv[1:] +if args == []: + args = ["nc", "localhost", "1024"] + +libc = CDLL("libc.so.6") + +def connect(): + io = process(args) + io.readuntil(b"After ") + leak = int(io.readline().split()[0]) + + flips = "" + for _ in range(5): + io.sendline(b"H") + io.readuntil(b"The coin lands on ") + flips += io.readline().decode()[0] + + return io, leak, flips + +def gen_key(seed, flips): + libc.srand(seed) + libc.rand() + for _ in flips: + libc.rand() + key = flips + value = libc.rand() & 0x7fff + for _ in range(32): + concat = (key + str(value) + "\n").encode() + key = md5(concat).hexdigest() + " -" + return key + +def test_seed(seed, leak, flips): + libc.srand(seed) + value = libc.rand() & 0x7fff + if value != leak: + return False + nflips = "" + for _ in flips: + value = libc.rand() & 0x7fff + nflips += 'H' if value % 2 == 1 else 'T' + return flips == nflips + +def main(): + while True: + io, _, _ = connect() + io.sendline(b"P") + io.sendline(b"H") + io.readuntil(b"The coin lands on") + io.readline() + if b"Fortune smiles upon you" in io.readline(): + io.readuntil(b"light is the flag part:") + flag_suffix = io.readline().split(b"'")[1].decode() + io.close() + break + io.close() + + while True: + timestamp = int(time.time()) + io, leak, flips = connect() + io.sendline(b"O") + io.sendline(b"H") + io.readuntil(b"The coin lands on ") + flips += io.readline().decode()[0] + if b"Fortune smiles upon you" in io.readline(): + io.readuntil(b"cipher:\n") + flag_prefix_enc = b"" + while True: + line = io.readline() + if b" " not in line: + break + print(line) + parts = line.decode().split(" ") + hexline = (parts[1] + parts[2]).replace(" ", "") + flag_prefix_enc += bytes.fromhex(hexline) + print(flag_prefix_enc) + io.close() + break + io.close() + + flag_prefix = None + for offset in range(-2000000, 2000001): + seed = timestamp + offset + if test_seed(seed, leak, flips): + key = gen_key(seed, flips) + cmd = ["openssl", "enc", "-aes-256-cbc", + "-d", "-pass", f"pass:{key}"] + out = subprocess.run(cmd, stdout=subprocess.PIPE, + input=flag_prefix_enc).stdout + print(out) + if b"CSCG" in out: + flag_prefix = out.decode().strip() + break + assert(flag_prefix) + + print(flag_prefix + flag_suffix) + +if __name__ == "__main__": + main()