Links

PatriotCTF 2023

PatriotCTF is a beginner-friendly capture-the-flag competition hosted by GMU's cybersecurity club, MasonCC. All are welcome to participate, including students and security professionals. Challenges will range from beginner to expert, so there should be something for everyone. This is a jeopardy-style CTF, meaning there will be various challenges from the different categories described below.
I participated in PatriotCTF on the Unusually Skilled Comedy Group. The team consisted of members from the SIII U.S. Cyber Combine and Squid Proxy Lovers. We were the first team to solve all challenges, however, we finished 9/697 due to some members taking hints. Overall, a great CTF and an even better team.

Crypto

Mason Competitive Cyber 2

It seems that someone managed to break the encoding on our previous message and the secret got out! We've reinforced the secret encoding of our message so there's no way anyone can break it this time.
Looking at MasonCompetitiveCyber2.txt, it appears to be a plaintext file.
M⁢M⁣aa⁢a⁣a⁣ass⁢s⁢s⁣o‍o⁢o⁣n⁣C⁢CC⁣C⁣
M⁣aaaaaa⁣aa‍a⁢a⁣s⁣on⁣C⁢C⁣C⁢C⁣CCC‍C⁢C⁢C⁣C⁢CC
M⁢MM⁣a⁣sss⁣o‍oo⁢o⁣n⁣nCC⁣C⁢C⁢C⁣CC⁢CCCCC
M‍M⁢M⁣a⁣a⁢a⁢as⁣o⁢o⁣n‍n⁢CC⁢C⁣
The first intuition was to determine the occurence of each character. Assuming each line is a character of the flag, then:
M⁢M⁣aa⁢a⁣a⁣ass⁢s⁢s⁣o‍o⁢o⁣n⁣C⁢CC⁣C⁣ => P
M⁣aaaaaa⁣aa‍a⁢a⁣s⁣on⁣C⁢C⁣C⁢C⁣CCC‍C⁢C⁢C⁣C⁢CC => C
M⁢MM⁣a⁣sss⁣o‍oo⁢o⁣n⁣nCC⁣C⁢C⁢C⁣CC⁢CCCCC => T
M‍M⁢M⁣a⁣a⁢a⁢as⁣o⁢o⁣n‍n⁢CC⁢C⁣ => F
M⁢M‍M⁢M⁣M⁣M⁢M⁣Maaaa⁢ss⁢o⁢o‍oooo⁢o⁣onC⁣C⁢CCCC⁢CC⁢CC⁢C⁣CC‍CC⁢C⁣ => {
We can write a Python script to count the occurences for us:
with open("MasonCompetitiveCyber2.txt", "r") as file:
for line in file:
line = line.strip()
m = line.count("M")
a = line.count("a")
s = line.count("s")
o = line.count("o")
n = line.count("n")
c = line.count("C")
with open("output.txt", "a") as output:
output.write(str(m) + " " + str(a) + " " + str(s) + " " + str(o) + " " + str(n) + " " + str(c) + "\n")
# 2 5 4 3 1 4
# 1 10 1 1 1 13
# 3 1 3 4 2 12
# 3 4 1 2 2 13
# 8 4 2 8 1 16
# 4 9 1 1 1 11
However, we couldn't think of a way to convert these into the necessary ASCII for PCTF{. Looking at a hexdump of the file with xxd MasonCompetitiveCyber2.txt, we see that there are some non-printable characters in the file.
hexdump
We need to figure what all these characters are.
import string
alphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits + string.punctuation + " "
with open("MasonCompetitiveCyber2.txt", "r") as f:
unique = []
for line in f:
line = line.strip()
for char in line:
if char not in alphabet:
unicode = ord(char)
unicode_hex = str(hex(unicode))
if unicode_hex not in unique:
unique.append(unicode_hex)
print(unique)
# ['0x2062', '0x2063', '0x200d']
Looks like it's some sort of zero-width steganography. There's a great tool that usually works, but for this challenge it failed. It looks like they've implemented a different method of encoding.
We'll need to write our own script to decode the zero-width characters. This took some trial and error to determine which character was 0, 1, or the separator.
with open("MasonCompetitiveCyber2.txt", "r") as f:
binary = ""
for line in f:
line = line.strip()
for char in line:
if char not in alphabet:
unicode_hex = str(hex(ord(char)))
if unicode_hex == "0x2062":
binary += "0"
if unicode_hex == "0x2063":
binary += "1"
if len(binary) == 8:
print(chr(int(binary, 2)), end="")
binary = ""
continue
# You're halfway there, now use these numbers to solve the rest: 5 3 8 4 7 1
So now we have a list of 6 numbers, and we know each line has 6 characters. We need to find a way to correlate
2 5 4 3 1 4 & 5 3 8 4 7 1 => P
1 10 1 1 1 13 & 5 3 8 4 7 1 => C
3 1 3 4 2 12 & 5 3 8 4 7 1 => T
3 4 1 2 2 13 & 5 3 8 4 7 1 => F
8 4 2 8 1 16 & 5 3 8 4 7 1 => {
I am not going to take credit for the solution, but my teammate GoldenBushRobin identified that the dot product of the two vectors corresponds to the necessary ASCII value.
import numpy as np
with open("output.txt", "r") as file:
for line in file:
b = [5, 3, 8, 4, 7, 1]
line = line.strip()
line = line.split(" ")
# convert to int
line = [int(x) for x in line]
product = np.dot(line, b)
print(chr(product), end="")
# PCTF{M0r3!_C0mpET1tIve_CyB3R_@_M4$$on}
The flag is PCTF{M0r3!_C0mpET1tIve_CyB3R_@_M4$$on}.

Forensics

Unsupported Format 2

Who doesn't love the classic Windows background!
For this challenge, we are given corrupted.jpg. Trying to open the image leads to an error. I first ran file corrupted.jpg to determine if maybe the header bytes were wrong-- this is a common CTF problem, however, the image was indeed a JPEG. I then ran xxd corrupted.jpg to see if there was any weird data in the actual bytes of the image, and saw this:
hexdump
If we look carefully, we will see things like CORRUPTEDgCORRUPTEDrCORRUPTEDK and so on. It looks like they've added "CORRUPTED" in between bytes, so we can write a Python script to remove these and recover the image.
with open("corrupted.jpg", "rb") as f:
data = f.read()
data = data.replace(b"CORRUPTED", b"")
with open("fixed.jpg", "wb") as f2:
f2.write(data)
Now we have recovered a working image, but it doesn't look like there's anything special. Running strings fixed.jpg to try and see if the flag was there reveals Monitor.jpgPK at the end of the file. PK is the magic bytes for a ZIP file, so we have a ZIP file hidden inside the image containing Monitor.jpg. We can extract it using binwalk:
binwalk -eM fixed.jpg
Now we have recovered Monitor.jpg, which states "Not a Flag". I performed some basic stego (exiftool, strings, binwalk), but didn't find anything. My typical next step for stego is aperisolve as you can upload an image and it will run a lot of stego tools for you. After uploading the image, and looking at the superimposed version
superimposed
we notice what looks like a flag on the bottom of the monitor. All we need to do is open the image and increase the exposure or brightness to see the flag:
PCTF{00ps_1_L1ed_Th3r3_1s_4_Fl4g}