LayerOne CTF - 2025
The Conference
The LayerOne conference has been a highlight for me each year since I started hacking and doing security things. I highly recommend it for anyone interested in security, electronics, radio, computers, or anything to do with hacking. It tends to emphasize getting hands on and learning through experience and from a phenomenal group of attendees. Check out their site at https://layerone.org and try to attend if you get a chance!
Unforunately, I wasn't able to make the conference this year but was able to mess around on the ctf a bit over the weekend. (Thanks L1 for having a public ctf page!)
The CTF
The CTF had around 80 challenges and Psychoholics won (again) with 7255 points. This write-up will cover solutions I was a part of or which were shared after the event ended. Feel free to share solutions not covered here; I will try to include files of challenges where it makes sense!
Here's the leader board from the end of the event:
There were a good variety of challenges across a wide set of categories. Use this table to navigate to the challenges you're interested in:
Misc
Read the Rules
Solution: I didn't copy it but yeah it was just in the text of the rules.
Join Discord!
Solution:
Similar thing, it is in the #ctf-announcements channel.
Follow us for a Flag!
Solution: Similar to the above 2
Survey Time
Solution: Simply fill out the survey (unlocked the last few hours of the event) and provide feedback. I like that they do this!
Help a Friend
Solution: This is my favorite! I wasn't there this year and didn't spend a lot of contiguous time on it but this is the one challenge I wish EVERY ctf did. It obviously helps people learn but it also can be a great ice breaker to meet people, join a time, and otherwise connect in an otherwise often quiet ctf room.
Leaked Coordinates
Solution: This was a fun one, we have an encrypted zip folder and an image. The image is:
This translates to German Spy Museum but, based on the description (and a quick google) we are supposed to put in berlinspymuseum
as the password to the zip.
This leads us to the next image:
Using zsteg
on the image1 led to finding an odd string - led to a base64 string: b1,rgb,lsb,xy .. text: "36:ZmxhZ3ttZXNzYWdlX2lkZW50aWZpZWR9DQo="
Base64 of this (first thing to try because it ends with =
) led to flag{message_identified}
Random Tornado?
Solution: I didn't get to this one2 so I don't have a solution.
Crypto
Password Puzzle from the Past
Solution: I didn't get to this one3 so I don't have a solution.
Domain of Lies
Solution: Here's the provided text:
During threat analysis, the following DNS TXT query was intercepted:
Command issued:
dig TXT reykjavik.is
Returned response:
"3409180c1105181a34360a1c1804460236033b011c16"
The origin IP of the DNS traffic was: 185.112.145.160
So there's a few things here.
1) Response looks like it could be some sort of dat. 2) Reykjavik is the capital of Iceland which seems relevant given the description.
The hint mentions a 'key'.
I tried a few normal things (base64 decode, generic hash lookups, search the string, etc) to no avail so then decided to look take the 'key' hint to heart. I asked chatgpt to write some python which would use reykjavik as a 'key' to XOR against the string. (I have no idea if XOR is a reasonable first choice but it seem to come up a lot in CTFs)
This gave me the following code:
#!/usr/bin/env python3
def xor_decrypt(hex_string, key):
"""XOR decrypt hex string with key"""
# Convert hex to bytes
data = bytes.fromhex(hex_string)
# XOR with repeating key
result = []
key_bytes = key.encode('utf-8')
for i, byte in enumerate(data):
result.append(byte ^ key_bytes[i % len(key_bytes)])
return bytes(result).decode('utf-8', errors='ignore')
# The intercepted DNS TXT response
hex_response = "3409180c1105181a34360a1c1804460236033b011c16"
key = "reykjavik"
# Decrypt
decrypted = xor_decrypt(hex_response, key)
print(f"Hex response: {hex_response}")
print(f"Key: {key}")
print(f"Decrypted flag: {decrypted}")
This worked but looked a bit funny: Flag{dns_Doesn't_hIde}
I decided to properly capitalize the capital and got an all lowercase version which looked correct!
Datagram
DISCORDant Glitches
Solution:
I went to the #announcements
channel and went to the date provided and found this:
There's a lot going on in that image. The RIP register hex value converted to ascii translates to L0LSECRET
which, it turns out is a troll and not actually the flag.
The next line, 67 6d 62 68 7c 7a 66 62 69 60 75 69 6a 74 60 78 62 74 60 66 62 74 7a 60 6a 60 6c 6f 70 78 7e
, translates to gmbh|zfbi`uijt`xbt`fbtz`j`lopx~
This was interesting in that it was all renderable but seemed like gibberish. I went to dcode.fr and tried a caesar cipher (ASCII Shift Cipher):
flag{yeah_this_was_easy_i_know}
is the flag!
Glitched to Death
Solution:
I didn't get around to this one.5
Long John Glitchers
Solution: I didn't get around to this one. 6
Artisanal Handcrafted Glitches
Solution: I didn't get around to this one. 7
Decoding
Barbie's Cave Adventure
Solution: This challenge gives us a picture with with people in different poses. It kinda reminded me of flag semaphore gestures but different. I did a search for 'stick figure cipher' and one of the top results was 'dancing man cipher'8 and a decoder on dcode.fr: https://www.dcode.fr/dancing-men-cipher9
Taking a minute to find all the symbols and put it into dcode.fr gives us this:
BARBIEDECODESSECRETS
is the flag.
Piercing Secrets
Solution: This challenge starts with a photo of a woman with some tattoos:
I tried a few tools on this and steghide was able to retrieve a png from inside of the image. The png was a picture of knives in a bunch of variations:
Given the { and _ characters, it seemed likely that this would be another cipher of some sort, a quick search for 'knife cipher' got me to another dcode.fr page for the 'Daggers Alphabet'. I put int the characters and got the flag: CTF{PRESS_DEEPER_THEN_TWIST}
Framed by Barbie
Solution: [Solution details]
Barbie's Mysterious Sound
Solution: [Solution details]
I wasn't able to work on and save this one.10
The Static Veil
Solution: I wasn't able to work on and save this one.11
Forensics
Out of Frame
Solution: This was a pretty simple one. We're presented with a weird looking QR code.
It's pretty obviously a QR code but in an unusual shape. Doing a quick lookup for 'thin qr code' or 'rectangle qr code' or something like that let me to this site which looked to be a good match.
I looked for an online reader and uploaded the file for the flag:
Flag Hunting
Solution: This is a relatively simple network forensics challenge. We're given a PCAP so I do what I always do for CTFs which give me PCAPs. I open wireshark, click 'export objects' then check the various types (normally HTTP has the good stuff) and sure enough it found some files.
There was both a real flag and fake flag in there. I just checked the exports and put in the flag:
Secret File 1
Solution: I didn't get around to this one.12
Locksmith
Solution: I really liked this one! It was a nice level of 'I feel like I have so little, just this weird little string' but also feeling like you have more than enough clues to figure it out.
In short, we're given a tiny file with some gibberish-looking data:
The key to figuring this one out, for me at least, ended up connecting the 'locksmith' and 'pattern' parts of the clue. It gave me the idea to look up how lock patterns on phones are stored (and also led me to resources around 'gesture.key' files - the file which stores that pattern and the file name we are given).
A quick search from there led me to this GitHub which includes a way to lookup patterns from a gesture.key file.
The lock pattern code was : 2, 1, 4, 3, 0, 7, 8, 5, 6
Fixme
Solution: This was another fun one. In this we are given a file which is named as a png but doesn't open as an image. Running various tools didn't give any additional hints so, in the absense of anything else, it made sense to see if the png needed to be fixed (given the title...).
I started by looking up the magic bytes of PNGs and using hexdump. I then just took the start and end and sent it to chatgpt to tell me if anything was wrong. It gave the following analysis:
hexdump -C ./forensics_fixup/chall.png | head -4
Expected PNG signature: 89 50 4E 47 0D 0A 1A 0A
What we found: 50 89 47 4E 0A 0D 0A 1A
PNG Signature (Bytes 0-7)
- Standard: 89 50 4E 47 0D 0A 1A 0A
- Corrupted: 50 89 47 4E 0A 0D 0A 1A
- Pattern: Each 16-bit word was byte-swapped (endianness flipped)
- 8950
→ 5089
- 4E47
→ 474E
- 0D0A
→ 0A0D
- 1A0A
→ 0A1A
IHDR Chunk Type (Bytes 12-15)
- Standard: 49 48 44 52
("IHDR")
- Corrupted: 49 44 52 48
("IDRH")
- Pattern: Bytes 2 and 3 were swapped with bytes 3 and 4
IEND Chunk (At file end) I checked the end of the file:
tail -c 20 ./forensics_fixup/chall.png | xxd
Standard: 49 45 4E 44
("IEND")
Corrupted: 49 4E 45 44
("INED")
Pattern: Bytes 2 and 3 were swapped
- IDAT Chunks Interestingly, the IDAT chunks appeared to be uncorrupted:
grep -abo "IDAT" ./forensics_fixup/chall.png
Given all of this, it looked that a few key parts of the PNG file had bytes/endianness swapped. So AI and I wrote a script to repair it and see if the file would open. This was the script we used:
#!/usr/bin/env python3
def fix_png(input_file, output_file):
with open(input_file, 'rb') as f:
data = bytearray(f.read())
# Fix PNG signature (first 8 bytes)
# Current: 50 89 47 4E 0A 0D 0A 1A
# Should be: 89 50 4E 47 0D 0A 1A 0A
if len(data) >= 8:
# Swap bytes in 16-bit words
data[0], data[1] = data[1], data[0] # 50 89 -> 89 50
data[2], data[3] = data[3], data[2] # 47 4E -> 4E 47
data[4], data[5] = data[5], data[4] # 0A 0D -> 0D 0A
data[6], data[7] = data[7], data[6] # 0A 1A -> 1A 0A
# Fix IHDR chunk type at offset 12-15
# Current: 49 44 52 48 ("IDRH")
# Should be: 49 48 44 52 ("IHDR")
if len(data) >= 16:
data[12] = 0x49 # I
data[13] = 0x48 # H
data[14] = 0x44 # D
data[15] = 0x52 # R
# Fix IEND chunk type at the end
# Current: 49 4E 45 44 ("INED")
# Should be: 49 45 4E 44 ("IEND")
# Find the IEND chunk (should be near the end)
for i in range(len(data) - 4):
if data[i:i+4] == b'INED':
data[i] = 0x49 # I
data[i+1] = 0x45 # E
data[i+2] = 0x4E # N
data[i+3] = 0x44 # D
break
# Write the fixed file
with open(output_file, 'wb') as f:
f.write(data)
print(f"Fixed PNG saved as {output_file}")
if __name__ == "__main__":
fix_png("./forensics_fixup/chall.png", "./forensics_fixup/chall_fixed.png")
The result was a nice forest with a flag in it!
However, because i'm neurotic and the flag looked weird I spent like an hour trying to figure out what it was before just submitting it and getting the points because....there was no extra steps, it was just a weird looking flag.
Barbie's Password Recovery
Solution: I didn't get around to this one.13
Secret File 2
Solution: I didn't get around to this one.14
Glitchstream
Solution: I didn't get around to this one.15
Hubris' Challenge
Asimov
The screenshot didn't fit, so here's the full description:
We hope you enjoy this challenge that rewards those that understand that in the game… subtlety is often more effective than overt force. Each layer is themed to a particular story that I enjoyed, and I hope that it will reward those that have also shared these experiences, and instill curiosity in those that have yet to discover these ideas.
What is Steganography? Steganography (STEG-ə-NOG-rə-fee) is the practice of representing information within another message or physical object, in such a manner that the presence of the information is not evident to human inspection. In computing/electronic contexts, a computer file, message, image, or video is concealed within another file, message, image, or video. The word steganography comes from Greek steganographia, which combines the words steganós, meaning "covered or concealed", and graphia meaning "writing". (via Wikipedia)
Download this file: https://ctfland-staticcontent.s3.us-west-2.amazonaws.com/layerone2025/TRANTOR.7z
Have you checked the integrity of the file? "A checksum is a small-sized block of data derived from another block of digital data for the purpose of detecting errors that may have been introduced during its transmission or storage." Here are two checksums for this challenge: SHA256sum: fffb060df7c4294823daca0bd08aa29310e94733c00e6e4219c11bdb596e2d91
SHA512sum: f5772cdc81df3ae37aa47e7f87c3614223ac8f70c50ca501b50b457bd9b14bdef30feb5df0b1bf527 d1db7fb9be52fdcdb69286f8dcf85acbcdb1c332314b61a
Learn more about verifying checksums on Linux here.
Learn more about verifying checksums on Windows here.
You'll need to do some unpacking to see the contents of this file.
Learn more about unzipping files on Linux here.
Learn more about unzipping files on Windows 10 here.
The password to open this file is also the flag to this first challenge. You will need to enter it in all CAPS.
The password is the name of the series that features the subject of this filename as a capital of sorts.
Solution: I did not get around to solving this one :(
Kyber's Challenge
Polyglots! Intro
Solution: The file looks like this:
#!/bin/bash
obf="YnlreBarPGS{o45ush1"
echo "$obf" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
exit 0
<?php
echo str_rot13("_ulc3eg3kg!!}");
__halt_compiler();
It looks like 'bash' at the start then 'php' towards the end. Running it through both gives this result:
solson@svm:~/ctf/solved/kyber_polyglot_intro$ ./polyglot
LayerOneCTF{b45hfu1
solson@svm:~/ctf/solved/kyber_polyglot_intro$ php polyglot
obf="YnlreBarPGS{o45ush1"
echo "$obf" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
exit 0
_hyp3rt3xt!!}
The flag is both outputs concatenated: LayerOneCTF{b45hfu1_hyp3rt3xt!!}
Polyglots! Two
Solution:
The file in this challenge is named as an html file but the result of 'file' show it just as data. I decided to run strings on it and got the following result:
solson@svm:~/ctf/solved/kyber_polyglot_2$ strings polyglot.html
<!DOCTYPE html>
<html>
<head><title>Nothing to see here</title></head>
<body>
<h1>Access Denied</h1>
<p>This page is restricted to authorized users only.</p>
<!-- decoderKey: d3c0mpr3s5 -->
</body>
</html>
Z^0X
flag.txtUT
5(hux
uT:v
Z^0X
flag.txtUT
5(hux
It looks like there's something after the html (turns out it's a zip).
I then ran binwalk -e polyglot.html
and found that there was a zip file there. By opening the zip with the password d3c0mpr3s5
. This results in a flag.txt
which contains the flag: LayerOneCTF{unz1pp1n6_html_15_fun}
Polyglots! Three
Solution:
This challenge starts with a polyglot.gif
file which, when we run binwalk
on we get a few files. A META-INF folder, a zip file, and a checkFlag.class
java file.
Since the java file is compiled, I used a tool (cfr
) to decompile the .class
file and then got this:
/*
* Decompiled with CFR 0.152.
*/
import java.util.Scanner;
public class CheckFlag {
public static void main(String[] stringArray) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter password: ");
String string = scanner.nextLine();
scanner.close();
if (string.equals(CheckFlag.deobf(new int[]{5, 15, 2, 4, 19, 15, 16, 8, 23, 11, 27}, 99))) {
System.out.println(CheckFlag.deobf(new int[]{102, 75, 83, 79, 88, 101, 68, 79, 105, 126, 108, 81, 77, 27, 76, 30, 88, 31, 117, 26, 88, 117, 64, 27, 76, 30, 88, 31, 21, 21, 87}, 42));
} else {
System.out.println("Wrong password.");
}
}
private static String deobf(int[] nArray, int n) {
StringBuilder stringBuilder = new StringBuilder();
for (int n2 : nArray) {
stringBuilder.append((char)(n2 ^ n));
}
return stringBuilder.toString();
}
}
It looks like it checks an input to see if it matches and then prints out a string which it deobfuscates. Instead of solving this, I posted it to chatgpt and it gave me this python to get the flag. (I acutally posted the Ghidra decompiled view of the program but I find it easier to read the java above)
# Corrected arrays based on bytecode analysis
# From the hexdump, I can see the actual array values
# First array (password key) - from bytecode around 0x350-0x390
# 10 0b bc 0a means: create array of size 11 (0x0b)
# The values are: 08, 0f, 05, 07, 13, 0f, 06, 10, 08, 17, 0b, 1b, 63
password_array = [8, 15, 5, 7, 19, 15, 6, 16, 8, 23, 11, 27, 99] # 99 = 0x63
password_key = len(password_array) # 13
print("Calculating password:")
password = ""
for i, value in enumerate(password_array):
char = chr(value ^ password_key)
password += char
print(f"[{i}] {value} XOR {password_key} = {value ^ password_key} = '{char}' (0x{value ^ password_key:02x})")
print(f"\nPassword: '{password}'")
# Second array (flag) - from bytecode around 0x3a0-0x450
# 10 1f bc 0a means: create array of size 31 (0x1f)
flag_array = [
0x66, 0x4b, 0x53, 0x4f, 0x58, 0x65, 0x44, 0x4f, 0x69, 0x7e,
0x6c, 0x51, 0x4d, 0x1b, 0x4c, 0x1e, 0x58, 0x1f, 0x75, 0x1a,
0x58, 0x75, 0x40, 0x1b, 0x4c, 0x1e, 0x58, 0x1f, 0x15, 0x15, 0x57
]
flag_key = 42 # 0x2a
print("\nCalculating flag:")
flag = ""
for i, value in enumerate(flag_array):
char = chr(value ^ flag_key)
flag += char
print(f"[{i:2}] 0x{value:02x} ({value:3}) XOR {flag_key} = {value ^ flag_key:3} = '{char}'")
print(f"\nFlag: '{flag}'")
This provided the flag: LayerOneCTF{g1f4r5_0r_j1f4r5??}
Polyglots! Four
Solution: This challenge starts with a jpg, after running file and exiftool, I ran strings which had some interesting output at the end:
Adobe Photoshop Lightroom 5.7.1 (Macintosh)
http://ns.adobe.com/xap/1.0/
<?xpacket begin='
...
<?php __HALT_COMPILER(); ?>
flag.dat
decode.php
<?php
$flag = file_get_contents('phar://polyglot.jpg/flag.dat');
$key = "secret";
$out = '';
for ($i = 0; $i < strlen($flag); $i++) {
$out .= $flag[$i] ^ $key[$i % strlen($key)];
echo $out;
I then took out the php section of the jpg and saved it as solve.php. I guess 'phar://' in the file_get_contents() function will look for an 'php archive' inside of the polyglot.jpg file.
Running solve.php returns:
solson@svm:~/ctf/solved/kyber_polyglot_4$ php solve.php
LayerOneCTF{jp3gs_4nd_phar5_4nd_crypt0_0h_my!}
Polyglots! Five
Solution: I didn't get around to solving this :(
Scan Me
Solution: I didn't get around to solving this :(
Scan Me 2
Solution: I didn't get around to solving this :(
AAAAAAA
Solution: I didn't get around to solving this :(
Q
Solution: I didn't get around to solving this :(
Mainframe
XMI
Solution:
This file gives us a .XMI file. Running file
on the archive gives us:
ARCHIVE.DATA.XMI: IBM NETDATA file
Looking up what XMI files are (apparently it's a file format for sending memos, files, notes, acknowledgements, etc. on IBM mainframe systems), led to finding a python tool called xmi-reader
which lets you read/interact with XMI files. Reading the docs on the package, you need to open, set an output location, then unload the file to that location. This output a ARCHIVE.DATA.zip
file which we can unzip and get 4 files: Dump, Flag, Memo1, and Memo2. These are .ASCII
files which are apparently encoded with EBCDIC (a character encoding). Using dd
lets us get actual readable ascii from the files:
dd if=Mainframe-XMI/final_contents/FLAG of=Mainframe-XMI/final_contents/FLAG.ASCII conv=ascii
The newly decoded FLAG file gives us: {T3RSED-c60bc1ca4469d6aba39099059afd97a7}
ASKBIT
Solution: I didn't get around to solving this :(
CGI
Solution: I didn't get around to solving this :(
COBOL
Solution: I didn't get around to solving this :(
ASM
Solution: I didn't get around to solving this :(
OSINT
Barbie's Secret Slip 1
Solution: This challenge gives us a username (chiccoder342) and notes they left some sensitive information in a coding project. I decided to check github and found they were a valid user with a few repos.
The first repo I checked didn't have anything of interest, but the second had this:
It was a test
branch and the last commit was about a .env
file (used to store secrets typically). Checking the commit history led to a prior commit with sensitive information!
The added value was the flag.
Barbie's Secret Slip 2
Solution: This one was even easier, just lookup the user on bluesky and they had a base64 encoded string as the post which when decoded gives the flag. (I think? I solved this early on and don't really remember). The base 64 on the bluesky profile decodes to:
https://sites.google.com/view/chiccodersite234/project-page
Barbie's Secret Slip 3
Solution: This very clearly ties in with the last challenge where there is a url provided. However, visiting the URL displays a 404:
Given that it mentions the information might have been stored elsewhere, I decided to check internet archive to see if the site was there. There was one snap of the site and, on the page, was the flag.
Memorial
Solution:
This is the image provided:
I asked ChatGPT where the image was from and it said it was the national gallery of modern art in New Delhi. Just north of there is the national war memorial which I can right-click and get the coordinates of. GeoWizard would be proud. 0m.
Echoes of Gunaa
Solution:
In this one, we have a nice description and then a media file. The audio file has some echoey sounds and some indian singing and stuff like that. I went to google and searched 'Echoey cave india' or something like that and found a cave called guna cave, I went there and sure enough the location worked.
Operation Silent Web
Solution:
I think this was one of my favorites! We learn about an operative named 'specter' with some message he left containing only 4 numbers (2216). There is also this image included in the challenge:
I tried exiftool, binwalk, strings, etc. on the image with no real results. I then tried a reverse image search on google but mostly got some pintrest things which looked similar. I then went to twitter, bluesky, etc and searched for specter 2216
and specter2216
and things like this. Eventually I found this instagram search result:
The latest post looked promising and was this:
It contains a somewhat hard to read pastebin URL on the top. This takes us to a password protected pastebin site. I tried the various quotes and tags on the post but had no luck. Eventually, I went through the other posts on his profile (it was not many but it took me longer than I'd like to admit).
It turns out another post had a hidden bit of text on the left side: This one was much better hidden from a cursory look and it was the password to the pastebin site!
The pastebin contained this text:
Contained:
To unlock the secrets hidden in plain sight, shift your focus 22 steps back from where 'A' begins. A simple code, but only for those who can decode the past.
dpplo://sss.hejgazej.yki/ej/olaypan-odwzkso-1833x1363?qpi_okqnya=odwna&qpi_ywilwecj=odwna_rew&qpi_ykjpajp=lnkbeha&qpi_iazeqi=wjznkez_wll
I read this to be a ~Caesar cipher with a shift of 22; this takes us to his linkedin! https://www.linkedin.com/in/specter-shadows-1833b1363/?utm_source=share&utm_campaign=share_via&utm_content=profile&utm_medium=android_app
His linkedin description contained a final puzzle which was a base64 encoded string: ZmxhZ3toaWlfaW1fc3BlY3Rlcl9uaWNlX3RvX21lZXRfeW91X2FsbH0=
This decodes to: flag{hii_im_specter_nice_to_meet_you_all}
Whispers Behind Stone Walls
Solution: When I read this one, it seemed like ChatGPT might be a good candidate so I sent it over and it solved it pretty easily, had to do a quick wiki search and tweak of the flag to get it to work though :)
https://chatgpt.com/share/68369fc5-7a38-800c-956a-32ebdb7ffaac
Reversing
Barbie World Reversing
Solution: This is a relatively straight forward binary which has a few functions in it but only one really matters:
void FUN_001013e1(char *param_1)
{
int iVar1;
size_t sVar2;
long lVar3;
ulong uVar4;
undefined8 *puVar5;
undefined8 *puVar6;
byte bVar7;
undefined8 local_308;
undefined8 local_300;
undefined8 local_2f8;
undefined8 local_2f0;
undefined8 local_2e8;
undefined3 local_2e0;
undefined5 uStack_2dd;
undefined3 uStack_2d8;
undefined8 local_2d5;
undefined8 local_2c8 [80];
undefined8 local_48;
undefined6 local_40;
undefined2 uStack_3a;
undefined6 uStack_38;
int local_2c;
int local_28;
int local_24;
int local_20;
uint local_1c;
bVar7 = 0;
local_48 = 0x49543534544e3446;
local_40 = 0x494546494c43;
uStack_3a = 0x394e;
uStack_38 = 0x43495435344c;
for (local_1c = 0; local_1c < 0x16; local_1c = local_1c + 1) {
*(byte *)((long)&local_48 + (long)(int)local_1c) =
*(byte *)((long)&local_48 + (long)(int)local_1c) ^ 0x5a;
*(byte *)((long)&local_48 + (long)(int)local_1c) =
*(char *)((long)&local_48 + (long)(int)local_1c) + 3U ^ 0x42;
}
local_20 = 0;
while( true ) {
uVar4 = (ulong)local_20;
sVar2 = strlen(param_1);
if (sVar2 <= uVar4) break;
param_1[local_20] = param_1[local_20] ^ 0x5a;
param_1[local_20] = param_1[local_20] + 3U ^ 0x42;
local_20 = local_20 + 1;
}
iVar1 = strcmp(param_1,(char *)&local_48);
if (iVar1 == 0) {
puVar5 = (undefined8 *)
".........."
;
puVar6 = local_2c8;
for (lVar3 = 0x4e; lVar3 != 0; lVar3 = lVar3 + -1) {
*puVar6 = *puVar5;
puVar5 = puVar5 + (ulong)bVar7 * -2 + 1;
puVar6 = puVar6 + (ulong)bVar7 * -2 + 1;
}
*(undefined4 *)puVar6 = *(undefined4 *)puVar5;
*(undefined2 *)((long)puVar6 + 4) = *(undefined2 *)((long)puVar5 + 4);
local_308 = 0x3d3d3d3d3d3d3d3d;
local_300 = 0x3d3d3d3d3d3d3d3d;
local_2f8 = 0x3d3d3d3d3d3d3d3d;
local_2f0 = 0x3d3d3d3d3d3d3d3d;
local_2e8 = 0x3d3d3d3d3d3d3d3d;
local_2e0 = 0x3d3d3d;
uStack_2dd = 0x3d3d3d3d3d;
uStack_2d8 = 0x3d3d3d;
local_2d5 = 0x3d3d3d3d3d3d3d;
printf("\n%s\n",&local_308);
for (local_24 = 0; *(char *)((long)local_2c8 + (long)local_24) != '\0'; local_24 = local_24 + 1)
{
putchar((int)*(char *)((long)local_2c8 + (long)local_24));
fflush(stdout);
usleep(1000);
}
putchar(10);
puts((char *)&local_308);
for (local_28 = 0; local_28 < 3; local_28 = local_28 + 1) {
putchar(0x2e);
fflush(stdout);
usleep(5000);
}
putchar(10);
local_2c = 0;
while( true ) {
uVar4 = (ulong)local_2c;
sVar2 = strlen(param_1);
if (sVar2 <= uVar4) break;
param_1[local_2c] = (param_1[local_2c] ^ 0x42U) - 3;
param_1[local_2c] = param_1[local_2c] ^ 0x5a;
local_2c = local_2c + 1;
}
printf("\nFLAG: CTF{%s}\n",param_1);
}
else {
puts("Incorrect flag. Try again!");
}
return;
}
The most important thing is these 4 lines:
local_48 = 0x49543534544e3446;
local_40 = 0x494546494c43;
uStack_3a = 0x394e;
uStack_38 = 0x43495435344c;
These 4 values store ASCII characters which get concatenated.
local_48 = 0x49543534544e3446; // "F4NT45TI"
local_40 = 0x494546494c43; // "CLIFEI"
uStack_3a = 0x394e; // "N9"
uStack_38 = 0x43495435344c; // "L45TIC"
Giving us: F4NT45TICLIFEIN9L45TIC
Binary Breach
Solution: [Solution details]
Go Break It
Solution: [Solution details]
Nexus
Solution: [Solution details]
Password Glitch
Solution: [Solution details]
Web
GlitchChat 1
Solution: [Solution details]
Examsploit
Solution: This challenge leads to a site which has a 5-question exam. No matter what options you put in, you can't get a perfect score.
After looking at the site, we find some intersting items: - main.js - glitch.js
Looking at main.js which is a few hundred lines, there is an interesting function at the end:
function initSecretShortcut() {
let konami = '';
const konamiCode = 'glitch';
document.addEventListener('keydown', function(e) {
konami += e.key.toLowerCase();
if (konami.length > konamiCode.length) {
konami = konami.substring(konami.length - konamiCode.length);
}
if (konami === konamiCode && validationId) {
// Alternative way to find the flag through keyboard shortcut
logNetworkActivity('Keyboard glitch detected! Bypassing validation...');
// Make direct API call with manipulated score
fetch('/api/submit_exam', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
validation_id: validationId,
correct_count: 5 // Perfect score!
}),
})
.then(response => response.json())
.then(data => {
if (data.flag) {
document.getElementById('flag-container').style.display = 'block';
document.getElementById('flag-container').textContent = data.flag;
document.getElementById('score').textContent = "5";
document.getElementById('pass-fail').textContent = "Passed!";
triggerGlitch();
}
});
}
});
}
This is basically a secret code so that, after the exam, you can type the word 'glitch' and get it to show you the flag!
CI/CD 1
Solution: [Solution details]
Header-Gated Internal Flag
Solution: The link in the challenge leads to a generic looking website. The hint for the challenge is:
> Not in plain sight, yet always near, I hide in paths both dark and clear.
> Behind the slash, I softly creep, Where whispers dwell and data sleeps.
> If truth you seek, don't just forget — The way is hidden… **it's simply secret**.
I went to /secret and got an additional riddle in the body of the request:
> Long ago, in the land where Snapdragons first bloomed, a secret was guarded by the keepers of silicon and code.
> Many sought the truth, searching the body and the path, but the wise knew to look above the request.
> Only those who speak the language of Qualcomm, and declare their loyalty as True, may pass the gate.
> Remember: not all treasures are found in plain sight.
This has a few hints in there. The first two things i noticed are: * 'Where snapdragons first bloomed' - Qualcomm makes the snapdragon processor. Qualcomm also puts on the ctf :) * 'the wise knew to look above the request' after a reference to 'body'. This seems like it's referencing the headers of the request.
At this point, it seemed like the /secret endpoint is looking for a specific header. This was easy enough to test with a curl command:
curl -i -H "Key: secret" http://glitch-two.be645eee0b994e65.ctf.land:9999/secret
I tried a few options like the above and 'Loyalty: True', etc.
Eventually, I tried 'Qualcomm' as one of the headers and got a different response:
</html>solson@svm:~/ctf$ curl -i -H "Qualcomm: Loyalty" http://glitch-two.be645eee0b994e65.ctf.land:9999/secret
HTTP/1.1 403 FORBIDDEN
Server: Werkzeug/2.2.3 Python/3.11.12
Date: Sun, 25 May 2025 06:22:14 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1753
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>That's a Hit!</title>
</head>
<body>
<div class="hit-flashy">
THAT'S A HIT!<br>
<span class="hint">Now ensure you have a <b>good value</b> for this header.</span>
</div>
</body>
This one seemed to indicate 'Qualcomm' was the correct header name! After a few more tries, I ended up setting the header value to true
and it worked!
Malibu Fashions 1
Solution: [Solution details]
Malibu Fashions 2
Solution: [Solution details]
Glitch Lotto
Solution: [Solution details]
CI/CD 2
Solution: [Solution details]
Foothold Factory
Solution: [Solution details]
HTTP Smuggling
Solution: [Solution details]
AI
Glitch Model Upload
Points: 150
Description: An AI model sequencer has been opened and made available for you to upload custom models. Be careful not to trigger a glitch.
You can find out more about it at: http://glitch-one.be645eee0b994e65.ctf.land:8004/help
Solution: I didn't solve this one :(
Acknowledgements
Special thanks to the following people for helping with the writeup or during the CTF:
- [Team member 1] for [contribution]
Thank you for reading and I hope this helps with future CTF challenges!
Footnotes
main-app
binary here. ↩