Published on

Space Heroes 2022 CTF write-up

18 min read
Space Heroes 2022 CTF write-up Post Banner
Table of Contents

The Space Heroes 2022 CTF was an online CTF from April 1st (4pm UTC) to April 3rd (9pm UTC) 2022. It was hosted by FITSEC and it even was their first time organizing such an event! Lots of applause to them for their hard work 👏 As a newbie CTF player, there were lots of challenges to have fun on and to successfully solve. This of course will improve my experience with CTFs and get me even more motivated to do more. The entire CTF was space themed, which made me even more motivated since I love space :D I've solved much more challenges compared to the Insomni'hack 2022 CTF, one reason is that there was more time; and also that some challenges were much more easier. This was an overall awesome CTF, especially for me as a newbie. I've learned a lot and hope to participate in their future CTFs!

The CTF ran another competition after the CTF ended where we were able to submit our write-ups and the best seven write-ups would get an ISS sticker signed by US Astronaut Colonel Andrew Morgan. I ended up being in the winners and got my sticker after some time:

Very original type of post-CTF competition reward
Very original type of post-CTF competition reward

So now let's start the write-up - as always, all flags had the same format which was shctf{...}. Let's get right into the write-up:

k?

This was a warm up challenge. The description said:

"MEE6 was busted! Help us out and unlock the flag in #mee6..."

So here it's pretty obvious, let's head over to #mee6 on their Discord server and see what we can try out. When using the /help commands commaannd, you can see a list of custom commands made by the Discord server administrators. There was a command that looked interesting: !k (optional text) - An awesome command!. When typing !k you will get the flag sent in your private messages by the bot: k? shctf{WhY_iS_K_BaNnEd} 😭. Free points for that one!

Discord

Another challenge on Discord, this time the flag was hidden somewhere in the Discord server. I've seen lots of people trying random things in the #mee6 channel to get that flag while it said somewhere. So I've used the search function to search for some flags; nothing. I looked at every channel topic and pinned messages; nothing alarming. But when I went back on the #mee6 channel, I clicked on MEE6's profile; and there it was. MEE6 had a custom role named: shctf{4ut0b0ts_r013_0u7}.

Evil MEE6...
Evil MEE6...

Guardians of the Galaxy

We are given a netcat connection and the binary of the program (Download the file). When testing the program locally it crashes, but why? Let's investigate by opening the file in a disassembler.

The crash source
The crash source

This is the source of the crash, and it's really easy to understand. If fopen returns 0x0, then the file doesn't exist, and therefore the binary crashes. So let's create a dummy flag.txt file with the content FLAG_____FLAG. But before running the binary again, we can see that the data for the file is stored at the location rbp-0x30 with a size of 0x20.

Calling convention matters
Calling convention matters

To confirm that, we can run the file with gdb and check the content:

Confirmed
Confirmed

The binary prints exactly what we send with printf according to this assembly code:

Our text is repeated
Our text is repeated

So let's use some string formats such as %x or others. When using %p we get a nice hexadecimal representation of the address returned. So let's print lots of them.

Spam, spam, spam...
Spam, spam, spam...

When looking at the data being given back, we can see some hexadecimal values of ASCII characters. Starting at 0x6d697b6674636873 and ending at 0x55f6d2000a7d. So let's write a Python script to extract that data:

"""
Useless data:
 
0x7ffd7417b6f0
0x55f6d353b2a0
0x2570257025702570
0x2570257025702570
0x2570257025702570
0x70257025702570
---------------
 
Important data:
 
0x6d697b6674636873
0x636172747369645f
0x756f795f676e6974
0x55f6d2000a7d
"""
 
from binascii import unhexlify
 
flag = b"".join(
    [
        unhexlify("6d697b6674636873")[::-1],
        unhexlify("636172747369645f")[::-1],
        unhexlify("756f795f676e6974")[::-1],
        unhexlify("55f6d2000a7d")[::-1],
    ]
)
 
print(flag)

This gave back: b'shctf{im_distracting_you}\n\x00\xd2\xf6U', and there we have the flag, shctf{im_distracting_you}.

Space traveler

We were given a URL: https://spaceheroes-web-explore.chals.io. When going on the website we could hit the Guess The Flag button. We had to give a flag as input and it would say if it's valid or not. Looking at the network tab in the developers tool, not external requests were made. So the check is done locally. When looking at the source code there was some obfuscated source:

var _0xb645 = [
  "\x47\x75\x65\x73\x73\x20\x54\x68\x65\x20\x46\x6C\x61\x67",
  "\x73\x68\x63\x74\x66\x7B\x66\x6C\x61\x67\x7D",
  "\x59\x6F\x75\x20\x67\x75\x65\x73\x73\x65\x64\x20\x72\x69\x67\x68\x74\x2E",
  "\x73\x68\x63\x74\x66\x7B\x65\x69\x67\x68\x74\x79\x5F\x73\x65\x76\x65\x6E\x5F\x74\x68\x6F\x75\x73\x61\x6E\x64\x5F\x6D\x69\x6C\x6C\x69\x6F\x6E\x5F\x73\x75\x6E\x73\x7D",
  "\x59\x6F\x75\x20\x67\x75\x65\x73\x73\x65\x64\x20\x77\x72\x6F\x6E\x67\x2E",
  "\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C",
  "\x64\x65\x6D\x6F",
  "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64",
];
 
function myFunction() {
  let _0xb729x2;
  let _0xb729x3 = prompt(_0xb645[0], _0xb645[1]);
  switch (_0xb729x3) {
    case _0xb645[3]:
      _0xb729x2 = _0xb645[2];
      break;
    default:
      _0xb729x2 = _0xb645[4];
  }
  document[_0xb645[7]](_0xb645[6])[_0xb645[5]] = _0xb729x2;
}

When looking at it, we can see it's obfuscated, so let's deobfuscate it!

function myFunction() {
  let azante;
  let karynna = prompt("Guess The Flag", "shctf{flag}");
  switch (karynna) {
    case "shctf{eighty_seven_thousand_million_suns}":
      azante = "You guessed right.";
      break;
    default:
      azante = "You guessed wrong.";
  }
  document.getElementById("demo").innerHTML = azante;
}

Well, that's much more readable and we can see the flag in plaintext: shctf{eighty_seven_thousand_million_suns}.

Curious?

This challenge was pretty straight forward, it was an OSINT challenge. For this one is was basically "Who can search better on Google?". Well in my case, I used TinEye since we were given a picture:

Isn't Curiosity amazing?
Isn't Curiosity amazing?

When searching for that picture, there were some websites that had this picture. Here is a list of them: http://www.dailytechinfo.org/tags/%C7%E0%E4%E5%F0%E6%EA%E0/ - Russian, nothing important for the challenge https://dailytechinfo.org/space/5613-marsohod-curiosity-napolovinu-preodolel-voznikshee-pered-nim-prepyatstvie.html - Russian, nothing important for the challenge http://news.discovery.com/space/the-moment-when-curiosity-breached-a-mars-dune-140205.htm - Offline https://www.hjkc.de/_blog/2014/02/05/2391-mars-curiosity-chroniken---curiosity-news-sol-529-533/ - Could be interesting https://www.space.com/24592-mars-rover-curiosity-dune-jump.html - Could be interesting

When looking at the last result, it clearly has as title "The Moment When Curiosity Breached a Mars Dune". Considering the flag format was given, shctf{SOL_xxx}, we can see that this picture was taken at SOL 533. So pretty simple, right? shctf{SOL_533} is the flag. The other website, hjkc.de also contained the SOL 533 picture, you just have to scroll a lot.

Launched

Another OSINT challenge, this time we are given the picture of a rocket that just launched.

Just awesome to see that..
Just awesome to see that..

Considering the flag format was shctf{rocket_payload}, it's pretty easy to know we need to find the rocket and its payload name. It's also the first time I know payloads can have names 🤯 So let's get exiftool in my hands. When looking at the data we got back, we can see some interesting information:

ExifTool Version Number         : 12.40
File Name                       : launched.jpg
...
Date/Time Original              : 2019:04:11 18:36:33
Create Date                     : 2019:04:11 18:36:33
...

One of them being the exact date and time when the picture was taken. So we can see that the rocket was launched at 2019:04:11 18:36:33. Just need to find the rocket name and its payload now. A simple Google search showed that the rocket was a Falcon Heavy. Now we need the payload name; let's try Wikipedia. Yup, there we go:

Interesting that payloads have names...
Interesting that payloads have names...

So now let's put everything in the flag format, shctf{rocket_payload}, and we get shctf{falcon_heavy_arabsat-6A}.

Flag in space

A web challenge. We are greeted with a website that has a grid with empty content. We URL was http://172.105.154.14/?flag=, so let's try to put some garbage in the GET parameter. When trying some characters you can see that some grids now contain the character that was correct, so if you put the flag parameter to shctf{aaa, you get the following:

The flag is not encoded, phew
The flag is not encoded, phew

Looking at the source code it's a basic <div>s</div> for every character. We could try each character ourselves but some flags are known to have special characters or numbers so it would take ages. Therefore I made a simple script that appends every character, and if it gets the <div> element, then it gets added in a variable res. I've already put the known characters as an element in the res variable. Then we simply make a request with all the characters from res and append the currently looped character. Here is my source code, you might understand it better:

import requests
 
flag = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "}"]
res = ["s", "h", "c", "t", "f", "{"]
 
for i in range(0, len(flag)):
    for j in range (0x21, 0x7d):
        char = chr(j)
        url = "http://172.105.154.14/?flag=" + "".join(res) + char
        r = requests.get(url)
        response = "".join(r.text.split())
        previous = ""
        for k in range(0, len(res)):
            previous += "<div>" + res[k] + "</div>"
        if f"{previous}<div>{char}</div>" in response:
            res += char
            print("".join(res), end="\r")
            break
print("".join(res))

After quite some time, the script gives the following flag back: shctf{2_explor3_fronti3r}.

R2D2

Looking back at this one, it was pretty obvious... We are greeted on a website that looks like that:

I personally don't like Star Wars :D
I personally don't like Star Wars :D

Looking at the source code, nothing. Looking at the local storage, nothing. Looking at the cookies, nothing. I decided to run gobuster on the website with a wordlist. Guess what kind of file was detected... robots.txt, of course......

This shows my lack of experience
This shows my lack of experience

Getting on that file gives the flag back, shctf{th1s-aster0id-1<span>$</span>-n0t-3ntir3ly-stable}.

Starman

Another OSINT challenge. This time the description says already a lot:

"How far away from earth was the space car on January, 20 2021 at 1515 UTC? Enter distance in terms of Million Km. (Rounded to two decimals) (e.g shctf12.34)"

Searching up on Google what the space car is, I came across this website. And at the right we can give a date and time, and we get an awesome map. When looking at the map we see that it was at 56.68 million km away from earth. Therefore the flag was: shctf{56.68}.

Space Buds

"One of the puppies got into the web server. Can you help find out who it was?"

With that description there also was a picture of the Space Buds.

Cute dogs :)
Cute dogs :)

There also was a website, that pretty much contained nothing. But when inspecting the source code, there was a hidden input element.

The background is horrible
The background is horrible

So I searched up on the Internet what the names of the puppies were and put them one by one in the input field. But still nothing. However, when sending the form, there was a request made to /getcookie. So maybe there's something in my cookies?

Nothing big. Right?
Nothing big. Right?

But we can change the value of that cookie, so let's put the name of each dog in the cookie and reload the page. When typing Mudbud, there was a flag given.

Please next time put some better quality
Please next time put some better quality

After literally decrypting that flag, it results to shctf{tastes_like_raspberries}.

Cape Kennedy

This was a reversing challenge. We were given a Python file that contains a password check.

import sys
 
def main():
  if len(sys.argv) != 2:
    print("Invalid args")
    return
 
  password = sys.argv[1]
  builder = 0
 
  for c in password:
    builder += ord(c)
 
  if builder == 713 and len(password) == 8 and (ord(password[2]) == ord(password[5])):
    if (ord(password[3]) == ord(password[4])) and ((ord(password[6])) == ord(password[7])):
        print("correct")
    else:
        print("incorrect")
  else:
    print("incorrect")
 
if __name__ == "__main__":
  main()

This one was quite easy to reverse. The password must have a length of 8. The characters and index 2 and 5 must be the same, the characters at index 3 and 4 must be the same and the characters and index 6 and 7 must be the same. The sum of the hexadecimal value of the characters must be 713. Let's make a bruteforce script:

import random
from string import ascii_letters
 
pwds = []
 
def solve():
    while True:
        s = ["A"]*8
        s[0] = random.choice(ascii_letters)
        s[1] = random.choice(ascii_letters)
        s[2] = random.choice(ascii_letters)
        s[5] = s[2]
        s[3] = random.choice(ascii_letters)
        s[4] = s[3]
        s[6] = random.choice(ascii_letters)
        s[7] = s[6]
        builder = 0
 
        password = "".join(s)
 
        for c in password:
            builder += ord(c)
 
        if (builder == 713 and len(password) == 8 and (ord(password[2]) == ord(password[5]))):
            if (ord(password[3]) == ord(password[4])) and ((ord(password[6])) == ord(password[7])):
                if password not in pwds:
                    pwds.append(password)
                    with open("moon.txt", "a") as f:
                        f.write(password + "\n")
solve()

And yes, there was A LOT of valid passwords, over 3 millions. Considering it was space themed (It was mentioned in the description of the challenge again.), I searched what happened at Cape Kennedy and that was related to the moon, since the file was named moon.py. It didn't took long until I've found that Apollo 11 started from there, a historic moment in space exploration! Looking at the results in moon.txt I've found a string generated that was APOllOaa. So with the knowledge of before and that valid string, the flag is simply shctf{Apollo11}.

Star Pcap

There was no description, just a pcap file (Download the file). When opening the file with Wireshark, we can see that there is just one slight change in all those ICMP packets, which is the ICMP code. Using pyshark it was easy to put all these codes together, convert them to an decimal value and then to a character.

import pyshark
 
capture = pyshark.FileCapture("star.pcap")
data = ""
for packet in capture:
    data += chr(int(packet.layers[2]._all_fields["icmp.code"]))
 
print(data)

This resulted in the following string: c2hjdGZ7TDBnMWMtaSQtdGgzLWJlZ2lOTmluZy0wZi13aSRkb019. Typical for CTFs, the data is base64 encoded. After decoding it, we can get the flag: shctf{L0g1c-i<span>$</span>-th3-begiNNing-0f-wi<span>$</span>doM}.

Mysterious Broadcast

Another web challenge.

"There used to be 8 Models of humanoid cylon but now there are only 7. We've located one of their broadcast nodes but we can't decode it. Are you able to decipher their technologies?"

When going on the website, there is a random ID generated in the URL, it looks like this: http://173.230.134.127/seq/710a1f63-57b9-4b86-a880-f413418375d9 The website had nothing besides an ~ as response, interesting. When reloading; it turned to a 1, when reloading again; it didn't changed. But when reloading for the third time, it turned to a 0. Here is how it looked like:

You probably don't want to write one by one
You probably don't want to write one by one

Binary! But I don't want to write everything down as it might be a lot of 0's and 1's in the end, so let's make a quick Python script and save the output in a variable:

import requests
 
binary = ""
 
while True:
    r = requests.get("http://173.230.134.127/seq/710a1f63-57b9-4b86-a880-f413418375d9")
    if (r.text == "~"):
        break
    binary += r.text
    print(binary)
 
print("\n[+] Received: " + binary)
# 1100011011001011010001101010110010010001111011010011011110100011011000100111011010101100001101011111011001001010110001101100001000101011001110100011101101110110001100001010101011001110100101101000110001011011011010010110100011000111101101101001001110011000011110011101111010111101

Here we go, we got the binary and now we can just decode it.

ÆËF¬‘í7£bv¬5öJÆÂ+:;v0ªÎ–Œ[ihǶ“˜yÞ½ ehhh, I don't think that's correct 🤔 Let's look at the description again - There used to be *8* Models of humanoid cylon but now there are only *7*. [...]. Remember, when representing a character, for example A, in binary there are 8 1's or 0's such as 01000001. And according to the description, there are now only 7. So let's put a space every 7th character and decode that. Most of the decoders give the same output, as they don't take in consideration the space. But this one did take in consideration the space. It resulted in c2hjdGZ7QXNjaWlJc0E3Qml0U3RhbmRhcmR9Cg==. Again, the == is typical for base64 encoding. So let's decode it: shctf{AsciiIsA7BitStandard}.

Space Captain Garfield

This one was more about OSINT at the beginning. We have the following picture:

What on Earth is that...
What on Earth is that...

There was just the number 2254 not encrypted. So by searching garfield dreaming 2254 on Google I've found this picture:

The fun is coming...
The fun is coming...

So yes, what had to be done was to map each character to its sign and then reconstruct the flag in the last picture. I started and got shctf{lasa..alo.er}, after some guessing for the last ones it was shctf{lasagnalover}.

Netflix and CTF

This one was very similar to the Star Pcap challenge above. We are given a pcap file (Download the file) and we have to analyze it. When looking at it, there is always request made to http://10.10.100.124:8060/keypress/Lit_X where X always varies. Sometimes there is a request made to /browse, this puts a line between the keypresses, and show names. So let's make a Python script using tshark again and save the output of each show in a list:

import pyshark
import urllib.parse
 
capture = pyshark.FileCapture("netflix-and-ctf.pcap")
data = []
 
show_data = ""
for packet in capture:
    try:
        if "browse" in packet.layers[3]._all_fields["http.response_for.uri"]:
            data.append(show_data)
            show_data = ""
            continue
        show_data += urllib.parse.unquote(packet.layers[3]._all_fields["http.response_for.uri"].replace("http://10.10.100.124:8060/keypress/Lit_", ""))
    except:
        pass
 
for show in data:
    if "shctf" in show:
        print(f"Special show found: {show}")
        break

The special show found was: shctf{T1m3-is-th3-ultimat3-curr3Ncy}.

Strange Traffic

My favorite forensics challenge. We were given again a pcap file (Download the file). There also was a free hint, so let's take it:

"Hint: alt,esc,1,2,3,4,5,6,7,8,9,0,-,=,backspace,tab,q,w,..."

All right, now let's investigate the pcap file.

Only UDP packets, this one will be easy.
Only UDP packets, this one will be easy.

The number encircled in purple is the only number that changes and appears in all packets. So we need to get this value for each packet. For this example, the 35 is formed thanks to the 33 and 35 which if decoded to in the ASCII table, they are 3 and 5. Now with 35 what can we do? Let's look at the hint again. It's clearly a keyboard layout. Now if we look at the query keyboard layout that was sent on Discord:

I've never used such a keyboard
I've never used such a keyboard

If we start counting each key and count up to 35, we get the key s, which could fit for the s in the shctf{...} format. After checking with the other packets, this theory is right. Now let's code a script for that:

import pyshark, binascii
 
capture = pyshark.FileCapture("strangetraffic.pcap")
 
# Map the entire keyboard
map = {
    "1": "`",
    "2": "1",
    "3": "2",
    "4": "3",
    "5": "4",
    "6": "5",
    "7": "6",
    "8": "7",
    "9": "8",
    "10": "9",
    "11": "0",
    "12": "-",
    "13": "=",
    "14": "<-",
    "15": "tab",
    "16": "q",
    "17": "w",
    "18": "e",
    "19": "r",
    "20": "t",
    "21": "y",
    "22": "u",
    "23": "i",
    "24": "o",
    "25": "p",
    "26": "[",
    "27": "]",
    "28": "enter",
    "29": "caps",
    "30": "a",
    "31": "s",
    "32": "d",
    "33": "f",
    "34": "g",
    "35": "h",
    "36": "j",
    "37": "k",
    "38": "l",
    "39": ";",
    "40": "'",
    "41": "#",
    "42": "shift",
    "43": "\\",
    "44": "z",
    "45": "x",
    "46": "c",
    "47": "v",
    "48": "b",
    "49": "n",
    "50": "m",
    "51": ",",
    "52": ".",
    "53": "/",
    "54": "ctrl",
    "55": "win",
    "56": "alt",
    "57": "space",
    "58": "alt",
    "59": "win",
    "60": "menu",
    "61": "ctrl",
}
 
flag = []
 
for packet in capture:
    payload = packet.layers[2]._all_fields["udp.payload"][75:].split(":") # Ignore the fist 75 characters from the payload
    for i in range(0, len(payload)):
        payload[i] = str(binascii.unhexlify(payload[i]), "ascii") # Coinvert to ASCII representation
    flag.append("".join(payload))
 
res = ""
 
for f in flag:
    res += map[str(f)]
print(res.replace("shift[", "{").replace("shift]", "}").replace("enter", "").replace("space", "_")) # Description said we can swap spaces with underscores

In the end we get the following flag: shctf{thanks_f0r_th3_t4nk._he_n3ver_get5_me_anyth1ng}.

Future Stego

For this challenge there were two pictures, one to download which was:

The quality is insane
The quality is insane

There also was another picture in the description, as a hint:

I wasn't born when this was published x)
I wasn't born when this was published x)

After trying lots of steganography techniques, I couldn't find any that lead me to the flag. One of the last was to use stegcracker. I tried to bruteforce the password with the rockyou wordlist, but stopped at around 300'000 words tried. Nothing. But the picture wasn't here for nothing.. Let's try some passwords that are in the news paper picture and use steghide --extract -sf shuttle.jpg. After playing around and trying some combinations such as spacewoman, newsweek or sally k. ride, I tried the file name: sallyride. This was the password and extracted a text file which contained the flag: shctf{weightlessness_is_a_great_equalizer}. It also would've worked with stegcracker and the right password in a text file.