The DamCTF experience

Written by on Read time: 6 min

This is written to document how a relatively new CTF player, me, approached the challenges. So this likely isn’t for you, unless you already know what the challenges were (since I didn’t bother copying them here), and you also are interested in how an inexperienced player might have approached the challenges.

Rev/Seed

This was the easiest challenge for me (after the misc/rules copy&paste one of course). Since I already knew that given the same seed, random number generators will give the same output. Think like Minecraft, where the same seed gives you the same world each time.

So my thoughts at the time were:

All I have to do is to find the right seed.

If I do that, I’ll basically have the flag.

I had output from previous seeds. I could predict the next/previous seed. So all I had to do is to try all possible seeds, find one of the rejected seeds that were before the actual one used for the flag. Because then I could just “predict” the next seed, aka the next second, which would give me the flag.

And after that you I just ran the original python code modified with that seed and it gave the flag.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python3
import sys
import time
import random
import hashlib

# These were given as wrong random outputs before the correct one
vals = [
  0.3322089622063289,
  0.10859805708337256,
  0.39751456956943265,
  0.6194981263678604,
  0.32054505821893853,
  0.2674908181379442,
  0.5379388350878211,
  0.7799698997586163,
  0.6893538761284775,
  0.7171513961367021,
  0.29362186264112344,
  0.06571100672753238,
  0.9607588522085679,
  0.33534977507836194,
  0.07384192274198853,
  0.1448081453121044
]

curr_time = round(time.time())

while True:
  curr_time = curr_time - 1;
  random.seed(curr_time, version=2)
  x = random.random()
  if (x in vals):
    print(f"Found a correct time! {curr_time}, val: {x}")

Crypto/XOR Pals

I first tired some more automated web tools like CyberChef, but I didn’t manage to get the flag with them. To be honest, before this challenge I hadn’t thought about how to do XOR with python. Luckily it was real easy after a quick lookup reminding me of the obvious way.

I guessed that the input given to us was hex strings, judging by how readable they were with the limited alphabet due to the hint given with the challenge:

Hint: Always operate on raw bytes, never on encoded strings. Flag must be submitted as UTF8 string.

Then I looked up a few things about python byte arrays, and ended up writing the following solution:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import sys

with open("flags.txt") as file:
  for line in file:
    file_bytes = bytes.fromhex(line)

    size = len(file_bytes)

    for letter_code in range(256):
      xord_byte_array = bytearray(size)

      for i in range(size):
        xord_byte_array[i] = file_bytes[i] ^ letter_code

      try:
        text = xord_byte_array.decode()
        if "dam" in text:
          print(f"\nFor letter {letter_code}:")
          print(text)
      except:
        pass

Misc/Library of Babel

First thought: It’s a minecraft save game allright. I looked up online how to figure out the game version first from the files. After finding out it was 1.17.1, I opened MultiMC and just loaded the world up to see what was up with it. There was a lot of books. With garbled text. And all the books were on page 2/10. And the name of the challenge also relates strongly to books.

So I assumed that the flag would be hidden in one of the books. First I just tried grepping all the files for the flag format. No luck though. I then tried some of the first tools I could find for reading NBT data, but no luck with them either. I ended up joining a minecraft data miners discord, and from there found mc-world-miner.

Downloaded it, setup a python virtualenv and installed the deps and ran it to extract the book data. And after a quick grep for the flag format from the newly created books.txt, I found it.

Web/Bouncy Box

I initially spent some time just poking around the frontend code to see if there was anything hidden. After that I switched my attention to the login field. One of my first tries to mess with the values took a long time to complete, it contained a ' character and got me an 500 error code, so I assumed there was an SQL injection exploit. After a few more tries I managed to log in with ' OR TRUE OR ' for the password.

But there was a second login. I tried the same on it without any success. I also looked up a few more common SQL exploit strings, but nothing got me weird results. So I quite quickly assumed that it was done correctly with a prepared statement.

I then started thinking of ways to leak the actual password with the first injection, since if I had the password I could use it just like normal on the second login. I started with figuring out how to how to query the tables in the database.

I didn’t figure out any other way to get output than the boolean of the login success though. So I just guessed that there probably exists an users table, and made the login succeed if it existed and fail if it did not exist. I did similar queries to figure out that username and password columns also existed.

I then formed an SQL subquery, where it would return true if the subquery returned results (aka it would let me login if my query returned things). First thing I did after that: Figure out the length of the password. Which was 12, so it was in the territory of being bruteforcable with leaking a character at a time. Since I wasn’t going to try to brute-force it if the password would’ve been say 80 chars (since to me that’d be like a signal saying “you’re going about this the wrong way” from the challenge creator).

I started off with just trying to guess each possible character with exact matches, but quickly switched to using regexp to make the process faster by guessing half of the alphabet, and then from the half that the users password was in guessing half again and so on, making the process along the lines of going from n*m to n*log(m).

So the password JSON string value in my curl spam was for example:

"\' or (EXISTS (SELECT * FROM users WHERE username = \'boxy_mcbounce\' AND SUBSTRING(password, 7, 1) REGEXP \'^[aeiouy]+$\')) or \'"

After iterating trough all the 12 characters of the password I logged in with it and got the flag.