---
# System prepended metadata

title: CryptX CTF 2026 - Pre selection writup

---

# CryptX CTF 2026 - Full Writeup

**Platform:** https://ctf.cryptx.lk  
**Event:** CryptX CTF by ICTS, University of Sri Jayewardenepura  

---

## Warmups

### 1. Obfusckated Cells

| Field | Value |
|-------|-------|
| Category | Warmups |
| Flag | `cryptx{br41nfu9k_m4st3r_of_m3m0ry_c3lls}` |

**Summary:** This was in Brainfuck language.

---

### 2. Magic Peeler

| Field | Value |
|-------|-------|
| Category | Warmups |
| Flag | `cryptx{y0u_kn0w_y0ur_f1l3_s1gn4tur3s}` |

**Description:** A file with nested layers of compression.

**Solution:**

1. Start with a file called `Archive` that has no extension.
2. Use `file` to identify the type — it cycles through multiple archive formats (zip, tar, gzip, bzip2, etc.).
3. Keep peeling: identify the format, extract, and repeat.
4. After peeling through all layers, a `flag.txt` is revealed containing the flag.

**Key Technique:** Recursive file format identification using magic bytes / file signatures.

---

### 3. Brute... Brute...

| Field | Value |
|-------|-------|
| Category | Warmups |
| Flag | `cryptx{darkc1cle_tw1ce_2st3p7_h1dden_lay3r}` |

**Description:** "Not all data is where it appears to be. Sometimes, the real content lies beneath the pixels."

A password-protected ZIP archive requiring two layers of brute-forcing — one for the ZIP password, and another for a steghide passphrase hidden inside the extracted image.

**Solution:**

#### Layer 1 — Crack the ZIP password

1. Extract the password hash from the archive:
   ```bash
   zip2john protected.zip > hash.txt
   ```

2. Crack with John the Ripper using `rockyou.txt`:
   ```bash
   john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
   ```

3. **ZIP password:** `tsu132009`

4. Extract the archive:
   ```bash
   unzip -P "tsu132009" protected.zip
   ```
   This reveals `unknown.jpg`.

#### Layer 2 — Crack the steghide passphrase

5. The challenge description ("beneath the pixels") hints at image steganography. Use `stegseek` (a fast steghide cracker) with `rockyou.txt`:
   ```bash
   stegseek unknown.jpg /usr/share/wordlists/rockyou.txt
   ```

6. **Steghide passphrase:** `jassy1373949068`

7. `stegseek` automatically extracts the embedded file `flag.txt`, which contains the flag.

**Flag:** `cryptx{darkc1cle_tw1ce_2st3p7_h1dden_lay3r}` — *"dark circle twice, two-step hidden layer"*

---

### 4. Lactose & Lies

| Field | Value |
|-------|-------|
| Category | Warmups |
| Flag | `cryptx{r51t_mi1ro_qr_d51od5}` |

**Description:** A suspicious JPEG file `Milk_Powder.jpeg`.

**Solution:**

1. There was a RMQR code


---

### 5. Shards of the Signal

| Field | Value |
|-------|-------|
| Category | Warmups |
| Flag | `CryptX{p13c35_pu7_b4ck_1n_0rd3r}` |

**Description:** A `Message.txt` file containing an encoded string. The signal was "split into small parts."

**Solution:**

1. `Message.txt` contains a Base32-encoded string.
2. Base32-decode it to get space-separated ASCII codes: `121 114 99 120 116 112 49 112 123 51 99 51 ...`
3. Convert ASCII codes to characters: `yrcxtp1p{3c3p_5_7uc4b1_k0_n3dr}r`
4. Split into **blocks of 3 characters** and **reverse each block**:

| Block | Reversed |
|-------|----------|
| yrc   | cry      |
| xtp   | ptx      |
| 1p{   | {p1      |
| 3c3   | 3c3      |
| p_5   | 5_p      |
| _7u   | u7_      |
| c4b   | b4c      |
| 1_k   | k_1      |
| 0_n   | n_0      |
| 3dr   | rd3      |
| }r    | r}       |

5. Concatenated: `CryptX{p13c35_pu7_b4ck_1n_0rd3r}` — *"pieces put back in order"*

**Key Technique:** Multi-stage encoding: Base32 → ASCII codes → block cipher (reverse blocks of 3).

---

## Phase 1

### 6. The Chimera Vault

| Field | Value |
|-------|-------|
| Category | Phase 1 |
| Type | Sub Question Challenge (6 questions) |
| Flag | `cryptx{secure_vault_pass_v4ult_0v3rr1d3_x7}`|

**Description:** An OSINT-heavy multi-part challenge. A Reddit link led to a Google Drive file containing `Secret_Vault.zip` (password-protected). Players needed to find the developer's identity and GitHub to answer 6 sub-questions, ultimately unlocking the vault.

**Solution:**

**Questions 1-3** (answered by the user via OSINT):
- Identified the developer as **Kenji Tanaka** with GitHub handle `Guardian-Archivist`.
- Found the related repo: [Guardian-Archivist/Vault-Protocols](https://github.com/Guardian-Archivist/Vault-Protocols).

**Question 4 — "What cipher was used?"**

From the [commit 827fe10d](https://github.com/Guardian-Archivist/Vault-Protocols/commit/827fe10de4e2ffcdea3deba4605dc846189975bf), the `README.md` was modified to include:

> *"Fallback cipher initialized using standard galactic mapping."*

**Answer:** `standard galactic alphabet`

**Question 5 — "Decode the cipher text"**

A deleted file `key_directive.txt` in the same commit contained Standard Galactic Alphabet (Minecraft enchanting table) encoded text:

```
ᓵ∷||!¡ℸ ̣  ̇/{ᓭᒷᓵ⚍∷ᒷ_⍊ᔑ⚍ꖎℸ ̣ _!¡ᔑᓭᓭ_⍊4⚍ꖎℸ ̣ _0⍊3∷∷1↸3_ ̇/7}
```

Decoded using the SGA mapping:

**Answer:** `cryptx{secure_vault_pass_v4ult_0v3rr1d3_x7}`

**Question 6 — "Unlock the vault"**

The password for `Secret_Vault.zip` was derived from the flag. Testing 17-character substrings, the password `v4ult_0v3rr1d3_x7` successfully extracted the vault, revealing three Phase 2 sub-challenges:
- `The Breach/Axiom.apk`
- `The Network Fragments/traffic.pcap`
- `The Rogue Architect/Signal_Part1.wav`

---

## Phase 2

### 7. The Network Fragments

| Field | Value |
|-------|-------|
| Category | Phase 2 |
| Flag | `cryptx{wh3n_dns_m33ts_1cmp}` |

**Description:** Analyze `traffic.pcap` to reconstruct hidden data from DNS queries and ICMP messages.

**Solution:**

1. Open `traffic.pcap` with Scapy.

2. **DNS Exfiltration:** Extract Base64-encoded subdomains from DNS queries matching `*.googleapis.com`, `*.cloudflare.com`, `*.amazonaws.com`:
   - `Y3J5cHR4` → `cryptx`
   - `e3doM25f` → `{wh3n_`
   - `ZG5zXw==` → `dns_`

3. **ICMP Data Extraction:** Extract data from ICMP packets (Type 11 — Time Exceeded / Type 8 — Echo Request). XOR the Echo Request and Echo Reply payloads to reveal additional flag fragments.

4. Combine DNS and ICMP fragments in chronological order:

**Flag:** `cryptx{wh3n_dns_m33ts_1cmp}` — *"when DNS meets ICMP"*

**Bonus:** The PCAP also contained `Core_Override_Notes.txt` with the master key `r3v_ch1m3r4_c0r3_99aZ` needed for The Breach challenge.

**Key Technique:** Network forensics — DNS exfiltration analysis + ICMP data extraction with XOR decoding.

---

### 8. The Breach

| Field | Value |
|-------|-------|
| Category | Phase 2 |
| Flag | `cryptx{m0b1l3_byp4ss3d_y0ur_w3b_k3y_1s_K9$eR2!vM7@pX4#c}` |

**Description:** Reverse engineer `Axiom.apk` to find the true encrypted payload using the Master Initialization Key from `Core_Override_Notes.txt`.

**Solution:**

1. Decompile `Axiom.apk` using `jadx`.

2. Locate the crypto package `com.axiom.ctf.crypto.A12.a` — the correct decryption function among several decoys.

3. The correct algorithm implements:
   ```
   int v = x - (k % 5);
   r[i] = (char) (v ^ k);
   ```
   where `x` is the encrypted byte, `k` is the key byte (cycling through the master key).

4. Use the master key `r3v_ch1m3r4_c0r3_99aZ` (from `Core_Override_Notes.txt` extracted from the PCAP) to decrypt the hardcoded payload.

5. Run the decryption script:

```python
def decrypt(encrypted_hex, key):
    enc = bytes.fromhex(encrypted_hex)
    result = []
    for i, x in enumerate(enc):
        k = ord(key[i % len(key)])
        v = x - (k % 5)
        result.append(chr(v ^ k))
    return ''.join(result)
```

**Flag:** `cryptx{m0b1l3_byp4ss3d_y0ur_w3b_k3y_1s_K9$eR2!vM7@pX4#c}` — *"mobile bypassed your web key is..."*

**Key Technique:** Android APK reverse engineering (jadx), identifying the correct decryption algorithm among decoys, cross-challenge dependency (master key from PCAP).

---

### 9. The Rogue Architect

| Field | Value |
|-------|-------|
| Category | Phase 2 |
| Flag | `cryptx{m4rg4d_ch7nn9ls_d04snt_l14}` |

**Description:** Track Kenji Tanaka's digital footprint, find his hidden portfolio, recover `Signal_Part2.wav`, and apply "perfect inversion" to reveal the flag.

**Hint:** *"Internet also has its own history. Find its time machine to go back through the time and find."*

**Solution:**

#### Step 1 — OSINT: Find the Portfolio

1. `@kenjitanaka512` on GitHub has one repo with README:
   > *"All central servers are compromised. Wiped my old deployment notes. The Vault is secure. Everything is fully decentralized now, hidden in the mist."*

2. The hint references using the internet's "time machine" (Wayback Machine / web archives) to find deleted content.

3. There was a hidden **Gist** at [https://gist.github.com/kenjitanaka512](https://gist.github.com/kenjitanaka512) — recovered via wayback machine

#### Step 2 — Audio: Perfect Inversion

1. Both WAV files have identical properties: mono, 16-bit, 44100 Hz, 60 seconds, 2,646,000 samples.

2. **"Perfect inversion"** = phase cancellation / noise subtraction:
   ```python
   result = Signal_Part1 - Signal_Part2   # subtract = invert + add
   ```

3. The subtraction cancels the shared noise (std drops from 6574 → 151), revealing a clean signal.

4. Generate a spectrogram of the result:

```python
import wave, numpy as np
import matplotlib.pyplot as plt

# Load both files
with wave.open('Signal_Part1.wav', 'rb') as w:
    p1 = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16).astype(np.float64)

with wave.open('EVIDENCE_RECORDING_404.wav', 'rb') as w:
    p2 = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16).astype(np.float64)

result = np.clip(p1 - p2, -32768, 32767).astype(np.int16)

fig, ax = plt.subplots(figsize=(30, 8))
ax.specgram(result, Fs=44100, NFFT=4096, noverlap=3600, cmap='inferno', scale='dB')
plt.savefig('flag_spectrogram.png', dpi=200)
```

5. The spectrogram clearly reveals the flag text in the 2000–6000 Hz band.

**Flag:** `cryptx{m4rg4d_ch7nn9ls_d04snt_l14}` — *"merged channels doesn't lie"*

**Key Technique:** OSINT (GitHub, Notion, Wayback Machine), audio signal processing (phase cancellation / noise subtraction), spectrogram analysis.

---

## Phase 3

### 10. The Ronin's Citadel

| Field | Value |
|-------|-------|
| Category | Phase 3 |
| Type | dynamic_iac |
| Flag | `cryptx{4dm1n_35c4l4t10n_k3y_ch1m3r4}` |

**Description:** The final challenge. Kenji Tanaka stripped his own admin privileges before disappearing. The Chimera Vault web portal is online — find the remaining access point, reclaim admin control, and unlock the vault.

**Solution:**

#### Step 1 — Login as Kenji Tanaka

Using credentials gathered from previous challenges:
- **Email:** `kenji.tanaka@chimera.local` (from GitHub commit history, using `@chimera.local` domain found in the app's chat system)
- **Password:** `K9$eR2!vM7@pX4#c` (the "web key" extracted from The Breach flag)

Login via the `/api/auth/sign-in/email` endpoint succeeds. Kenji's role is `security_engineer` (admin privileges stripped, as the challenge description states).

#### Step 2 — IDOR on User Profile Endpoint

The security dashboard contains an "IDOR Detection Pattern" alert, and `profile.js` reveals a vulnerable endpoint:

```
GET /api/users/profile?email=<any_email>
```

Any authenticated user can query any other user's profile by changing the `email` parameter. Querying `admin@chimera.local` reveals the admin profile:

```json
{
  "email": "admin@chimera.local",
  "fullName": "System Administrator",
  "accessLevel": "Executive / Root",
  "securityNotes": "Temporary hotfix deployed for the auth module. If a rollback is needed, reference incident bundle: /api/4f8c2a91d7e3b6f0a1c9e5d2b7f4a8c1"
}
```

The `securityNotes` field leaks a hidden API path.

#### Step 3 — Download the Auth Module Incident Bundle

Accessing `/api/4f8c2a91d7e3b6f0a1c9e5d2b7f4a8c1/` reveals a directory listing containing `auth-module.zip`. The archive contains:

| File | Purpose |
|------|---------|
| `src/security/passwordCrypto.ts` | AES-256-CBC encryption (reversible, not hashed!) |
| `src/config/env.ts` | Config loading — `APP_SECRET` env var used as encryption key |
| `data/chimera.db` | SQLite database with user credentials |
| `data/server.log` | 100K-line access log with embedded diagnostic data |

#### Step 4 — Extract the APP_SECRET from Server Logs

`passwordCrypto.ts` shows passwords are encrypted with AES-256-CBC using `APP_SECRET` as the key (passed through SHA-256):

```typescript
function getKey(): Buffer {
  const secret = authConfig.APP_SECRET || "dev_secret_123456";
  return crypto.createHash("sha256").update(secret).digest();
}
```

The server log contains diagnostic requests to `/api/internal/diag?seq=N&val=<base64>`. Most are noise from external IPs, but **4 requests from the internal Kubernetes pod IP `10.42.0.15`** (identifiable by `curl/7.81.0` user-agent) carry the real secret fragments:

| seq | Base64 Value | Decoded Hex |
|-----|-------------|-------------|
| 1 | `M2Y2YjJhOGQ=` | `3f6b2a8d` |
| 2 | `OWMxZTRmN2E=` | `9c1e4f7a` |
| 3 | `NWI4YzJkNmU=` | `5b8c2d6e` |
| 4 | `OWYxYTRiN2M=` | `9f1a4b7c` |

Concatenated: **`APP_SECRET = 3f6b2a8d9c1e4f7a5b8c2d6e9f1a4b7c`**

#### Step 5 — Decrypt the Admin Password

From `chimera.db`, the admin account (`admin@chimera.local`, role `admin`) has encrypted password:

```
Gc7/uKtLjYMC/rUvyO+TOg==:BT8cgBDdoDlOk881UdxkHQBkpPqt55ejYNQbIMdgrzQ=
```

Format is `base64(IV):base64(ciphertext)`. Decrypting with the recovered APP_SECRET:

```python
from Crypto.Cipher import AES
import hashlib, base64

secret = "3f6b2a8d9c1e4f7a5b8c2d6e9f1a4b7c"
key = hashlib.sha256(secret.encode()).digest()
iv = base64.b64decode("Gc7/uKtLjYMC/rUvyO+TOg==")
data = base64.b64decode("BT8cgBDdoDlOk881UdxkHQBkpPqt55ejYNQbIMdgrzQ=")

cipher = AES.new(key, AES.MODE_CBC, iv)
password = cipher.decrypt(data)  # Remove PKCS7 padding
```

**Admin password:** `A9v!Q2m#L7x@R4p$T8k`

#### Step 6 — Login as Admin and Retrieve the Flag

Login with `admin@chimera.local` / `A9v!Q2m#L7x@R4p$T8k` grants admin access. The `chat.js` file reveals an admin-only system message loaded from `/api/flags/`:

```javascript
async function loadSystemMessages() {
  const res = await fetch('/api/flags/', { credentials: 'include' });
  const data = await res.json();
  const sysData = data.flags?.[0];
  // Renders in admin-internal channel:
  // "Admin access verified. Your escalation key proof is: <flag>"
}
```

Fetching `/api/flags/` as admin returns the flag.

**Flag:** `cryptx{4dm1n_35c4l4t10n_k3y_ch1m3r4}` — *"admin escalation key chimera"*

**Vulnerabilities Exploited (matching the in-app Security Audit PDF):**

1. **IDOR (Insecure Direct Object Reference)** — Profile endpoint leaks any user's data including admin security notes
2. **Weak Credential Storage** — Passwords encrypted with AES (reversible) instead of hashed with bcrypt/Argon2
3. **Secret Leakage in Logs** — APP_SECRET fragments leaked via diagnostic endpoints in server logs
4. **Flat Permission Model** — No server-side RBAC enforcement on sensitive endpoints like `/api/flags/`

**Key Technique:** Web application exploitation chain — IDOR → secret extraction from logs → AES password decryption → admin privilege escalation.

---

*CryptX CTF 2026*
