Add 107 terminal screenshots and replace all 📸 placeholders

- Generated screenshots for all 33 challenges (ESP, Hardware, IoT, OT, Misc, Web3)
- Replaced all 123 placeholder lines with actual PNG image references
- Cleaned duplicate images from previously partial updates
- All write-ups now have full illustrated solutions
This commit is contained in:
Eun0us 2026-03-27 00:34:47 +00:00
parent 2d7aaf794d
commit ac82d8367e
142 changed files with 93 additions and 95 deletions

View File

@ -60,7 +60,7 @@ On Linux, add your user to the `dialout` group first if you get a permission err
sudo usermod -a -G dialout $USER sudo usermod -a -G dialout $USER
``` ```
> 📸 `[screenshot: esptool.py flashing — progress bar reaching 100%]` ![esptool.py flashing — progress bar reaching 100%](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/esptool_flash.png)
### Step 2 — Connect to the UART console ### Step 2 — Connect to the UART console
@ -82,7 +82,7 @@ Encrypted flag: 09 12 19 07 00 0E 07 35 3F 35 7D 3C 38 1E 3D 26 7F 1E 3E 7F 3E 7
XOR Key: 4C 41 49 4E XOR Key: 4C 41 49 4E
``` ```
> 📸 `[screenshot: serial terminal showing the encrypted flag and XOR key on boot]` ![serial terminal showing the encrypted flag and XOR key on boot](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/esp_start_uart.png)
### Step 3 — Identify the XOR key ### Step 3 — Identify the XOR key
@ -114,7 +114,7 @@ print(flag.decode())
Output: `ESPILON{st4rt_th3_w1r3}` Output: `ESPILON{st4rt_th3_w1r3}`
> 📸 `[screenshot: Python decryption script running and printing the flag]` ![Python decryption script running and printing the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/esp_start_decrypt.png)
### Key concepts ### Key concepts

View File

@ -75,12 +75,11 @@ Open the UART console:
screen /dev/ttyUSB0 115200 screen /dev/ttyUSB0 115200
``` ```
> 📸 `[screenshot: UART console showing jnoun-console> prompt]` ![UART console showing jnoun-console> prompt](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_console.png)
--- ---
### Flag 1 — Console Access ### Flag 1 — Console Access
![UART admin console authentication](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_uart.png)
(100 pts) (100 pts)
The UART console presents a `jnoun-console>` prompt requiring a password. The UART console presents a `jnoun-console>` prompt requiring a password.
@ -101,12 +100,11 @@ admin_login jnoun-admin-2022
Flag 1 is printed on success. Flag 1 is printed on success.
> 📸 `[screenshot: successful admin_login command printing Flag 1]` ![successful admin_login command printing Flag 1](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_login.png)
--- ---
### Flag 2 — 802.11 TX ### Flag 2 — 802.11 TX
![802.11 frame capture with flag payload](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_wifi.png)
(200 pts) (200 pts)
From the admin console, read device settings: From the admin console, read device settings:
@ -138,7 +136,8 @@ tshark -i wlan0mon -w capture.pcap
Among the random noise frames, one frame emitted at a random time (585 seconds) Among the random noise frames, one frame emitted at a random time (585 seconds)
contains Flag 2 as cleartext in its 802.11 data payload. contains Flag 2 as cleartext in its 802.11 data payload.
> 📸 `[screenshot: Wireshark frame showing Flag 2 in the payload field]`
![Wireshark frame showing Flag 2 in the payload field](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_wifi.png)
--- ---
@ -163,7 +162,7 @@ Decoded: `target=x; flag`
Flag 3 prints to the UART console via `ESP_LOGE`. Flag 3 prints to the UART console via `ESP_LOGE`.
> 📸 `[screenshot: UART console showing Flag 3 output after injection]` ![UART console showing Flag 3 output after injection](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_flag3.png)
--- ---
@ -228,7 +227,7 @@ for block_id in range(10):
print(flag.decode()) print(flag.decode())
``` ```
> 📸 `[screenshot: Python JMP client printing the final flag after block reassembly]` ![Python JMP client printing the final flag after block reassembly](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_jmp.png)
--- ---

View File

@ -57,7 +57,7 @@ nc <host> 3600
nc <host> 3601 nc <host> 3601
``` ```
> 📸 `[screenshot: two terminal windows showing sniff output and inject prompt]` ![two terminal windows showing sniff output and inject prompt](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/can_terminals.png)
### Step 2 — Observe the traffic ### Step 2 — Observe the traffic
@ -72,7 +72,7 @@ Watch the sniff port. The following patterns emerge:
The `0x7E0`/`0x7E8` pair is the UDS diagnostic channel. The `0x7E0`/`0x7E8` pair is the UDS diagnostic channel.
> 📸 `[screenshot: sniff output showing the 0x7E0/0x7E8 request/response pattern]` ![sniff output showing the 0x7E0/0x7E8 request/response pattern](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/can_sniff.png)
### Step 3 — Enter extended diagnostic session ### Step 3 — Enter extended diagnostic session
@ -94,7 +94,7 @@ send 7E0 02 27 01 00 00 00 00 00
The response contains a 4-byte seed: `67 01 XX XX XX XX` The response contains a 4-byte seed: `67 01 XX XX XX XX`
> 📸 `[screenshot: seed bytes visible in the 0x7E8 response]` ![seed bytes visible in the 0x7E8 response](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/can_seed.png)
### Step 5 — Compute the key and authenticate ### Step 5 — Compute the key and authenticate
@ -123,7 +123,7 @@ send 7E0 03 22 FF 01 00 00 00 00
The response on `0x7E8` contains the flag. The response on `0x7E8` contains the flag.
> 📸 `[screenshot: 0x7E8 response containing the flag bytes after successful security access]` ![0x7E8 response containing the flag bytes after successful security access](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/can_flag.png)
### Key concepts ### Key concepts

View File

@ -48,7 +48,7 @@ arm and trigger, then read the maintenance token from the unlocked debug console
nc <host> 3700 nc <host> 3700
``` ```
> 📸 `[screenshot: glitch lab banner and prompt]` ![glitch lab banner and prompt](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/glitch_banner.png)
### Step 2 — Observe the boot sequence ### Step 2 — Observe the boot sequence
@ -68,7 +68,7 @@ LAUNCH_KERNEL [3400 5000]
The signature verification phase runs between cycles 3200 and 3400. The signature verification phase runs between cycles 3200 and 3400.
> 📸 `[screenshot: observe output showing all boot phases and cycle ranges]` ![observe output showing all boot phases and cycle ranges](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/glitch_observe.png)
### Step 3 — Configure glitch parameters ### Step 3 — Configure glitch parameters
@ -101,7 +101,7 @@ LAUNCH_KERNEL ......... OK
[DEBUG SHELL ACTIVATED] [DEBUG SHELL ACTIVATED]
``` ```
> 📸 `[screenshot: boot log showing SIG_VERIFY SKIPPED and debug shell prompt]` ![boot log showing SIG_VERIFY SKIPPED and debug shell prompt](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/glitch_unlock.png)
### Step 5 — Read the maintenance token ### Step 5 — Read the maintenance token
@ -111,7 +111,7 @@ read_console
The debug console outputs the maintenance token containing the flag. The debug console outputs the maintenance token containing the flag.
> 📸 `[screenshot: read_console output displaying the flag]` ![read_console output displaying the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/glitch_flag.png)
### Key concepts ### Key concepts

View File

@ -50,7 +50,7 @@ Use the key to decrypt the EEPROM contents and recover the flag.
nc <host> 3300 nc <host> 3300
``` ```
> 📸 `[screenshot: I2C bus interface prompt]` ![I2C bus interface prompt](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/i2c_prompt.png)
### Step 2 — Scan the bus ### Step 2 — Scan the bus
@ -66,7 +66,7 @@ I2C Address 0x48 [Temperature Sensor]
I2C Address 0x60 [Crypto IC] I2C Address 0x60 [Crypto IC]
``` ```
> 📸 `[screenshot: scan output listing three I2C devices]` ![scan output listing three I2C devices](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/i2c_scan.png)
### Step 3 — Read the temperature sensor's hidden register ### Step 3 — Read the temperature sensor's hidden register
@ -108,7 +108,7 @@ read 0x60 0x10 32
Now returns the actual 32-byte key: `NAVI_WIRED_I2C_CRYPTO_KEY_2024!!` Now returns the actual 32-byte key: `NAVI_WIRED_I2C_CRYPTO_KEY_2024!!`
> 📸 `[screenshot: crypto IC returning the key after unlock]` ![crypto IC returning the key after unlock](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/i2c_key.png)
### Step 7 — Read the EEPROM ### Step 7 — Read the EEPROM
@ -128,7 +128,7 @@ flag = bytes(b ^ key[i % len(key)] for i, b in enumerate(enc))
print(flag.rstrip(b'\x00').decode()) print(flag.rstrip(b'\x00').decode())
``` ```
> 📸 `[screenshot: Python decryption script printing the flag]` ![Python decryption script printing the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/i2c_decrypt.png)
### Key concepts ### Key concepts

View File

@ -51,7 +51,7 @@ reassemble the flag.
nc <host> 3400 nc <host> 3400
``` ```
> 📸 `[screenshot: JTAG port banner showing TAP controller information]` ![JTAG port banner showing TAP controller information](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jtag_banner.png)
### Step 2 — Reset the TAP controller ### Step 2 — Reset the TAP controller
@ -72,7 +72,7 @@ dr 00000000 32
Returns `0x4BA00477` — an ARM Cortex-M style IDCODE. Returns `0x4BA00477` — an ARM Cortex-M style IDCODE.
> 📸 `[screenshot: IDCODE read returning 0x4BA00477]` ![IDCODE read returning 0x4BA00477](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jtag_idcode.png)
### Step 4 — Unlock the debug interface ### Step 4 — Unlock the debug interface
@ -91,7 +91,7 @@ state
Output should now show: `Debug: UNLOCKED` Output should now show: `Debug: UNLOCKED`
> 📸 `[screenshot: state command showing Debug: UNLOCKED]` ![state command showing Debug: UNLOCKED](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jtag_unlock.png)
### Step 5 — Load the memory read instruction ### Step 5 — Load the memory read instruction
@ -113,7 +113,7 @@ Repeat for addresses 0x1004, 0x1008, etc. until the flag is complete.
Convert each little-endian 32-bit word to 4 ASCII bytes and concatenate. Convert each little-endian 32-bit word to 4 ASCII bytes and concatenate.
> 📸 `[screenshot: dr reads returning flag bytes as 32-bit little-endian words]` ![dr reads returning flag bytes as 32-bit little-endian words](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jtag_mem_read.png)
### Key concepts ### Key concepts

View File

@ -53,7 +53,7 @@ nc <host> 1111
nc <host> 2222 nc <host> 2222
``` ```
> 📸 `[screenshot: two terminals open, TX showing boot messages and RX ready for input]` ![two terminals open, TX showing boot messages and RX ready for input](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/serial_exp_terminals.png)
### Step 2 — Query the diagnostic commands ### Step 2 — Query the diagnostic commands
@ -103,7 +103,7 @@ frag_c_hex=3030
Decode: `bytes.fromhex("3030").decode()``00` Decode: `bytes.fromhex("3030").decode()``00`
> 📸 `[screenshot: TX output showing all three fragment values from diagnostics]` ![TX output showing all three fragment values from diagnostics](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/serial_exp_diag.png)
### Step 4 — Build the maintenance token ### Step 4 — Build the maintenance token
@ -123,7 +123,7 @@ unlock LAIN-SERIAL-00
The flag is returned on the TX terminal. The flag is returned on the TX terminal.
> 📸 `[screenshot: TX terminal printing the flag after successful unlock]` ![TX terminal printing the flag after successful unlock](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/serial_exp_flag.png)
### Key concepts ### Key concepts

View File

@ -69,7 +69,7 @@ Channels: 3
Sample rate: 1 MHz Sample rate: 1 MHz
``` ```
> 📸 `[screenshot: info command output listing the three channels]` ![info command output listing the three channels](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/signal_tap_info.png)
### Step 3 — Analyze channel 1 ### Step 3 — Analyze channel 1
@ -90,7 +90,7 @@ Baud rate = 1 / 0.00010417 ≈ 9600 baud
A 10-bit UART frame (1 start + 8 data + 1 stop) = ~1041.67 μs. A 10-bit UART frame (1 start + 8 data + 1 stop) = ~1041.67 μs.
> 📸 `[screenshot: Python script measuring bit periods from ch1 transitions]` ![Python script measuring bit periods from ch1 transitions](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/signal_tap_measure.png)
### Step 5 — Decode UART 8N1 ### Step 5 — Decode UART 8N1
@ -133,7 +133,7 @@ print("".join(chars))
The decoded message contains the flag repeated three times. The decoded message contains the flag repeated three times.
> 📸 `[screenshot: decoded UART output showing the flag repeated]` ![decoded UART output showing the flag repeated](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/signal_tap_decode.png)
### Key concepts ### Key concepts

View File

@ -55,7 +55,7 @@ nc <host> 3500
cs 0 cs 0
``` ```
> 📸 `[screenshot: SPI probe interface ready with CS asserted]` ![SPI probe interface ready with CS asserted](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/spi_probe.png)
### Step 2 — Read the chip ID ### Step 2 — Read the chip ID
@ -79,7 +79,7 @@ tx 5A 00 00 00 00
The SFDP header shows 2 parameter tables. The first is the standard JEDEC table; The SFDP header shows 2 parameter tables. The first is the standard JEDEC table;
the second is a vendor-specific table at offset 0x80. the second is a vendor-specific table at offset 0x80.
> 📸 `[screenshot: SFDP header output showing two parameter table entries]` ![SFDP header output showing two parameter table entries](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/spi_sfdp.png)
### Step 4 — Read the vendor SFDP table ### Step 4 — Read the vendor SFDP table
@ -97,7 +97,7 @@ Size: 4096 bytes
This partition does not appear in the normal partition table. This partition does not appear in the normal partition table.
> 📸 `[screenshot: vendor SFDP data revealing hidden partition at 0x030000]` ![vendor SFDP data revealing hidden partition at 0x030000](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/spi_vendor.png)
### Step 5 — Read the hidden partition ### Step 5 — Read the hidden partition
@ -122,7 +122,7 @@ flag = bytes(b ^ key[i % len(key)] for i, b in enumerate(encrypted))
print(flag.rstrip(b'\x00').decode()) print(flag.rstrip(b'\x00').decode())
``` ```
> 📸 `[screenshot: Python decryption script printing the recovered flag]` ![Python decryption script printing the recovered flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/spi_decrypt_flag.png)
### Key concepts ### Key concepts

View File

@ -79,7 +79,7 @@ Directories present: `notes/`, `comms/`, `dumps/`, `logs/`, `tools/`, `journal/`
confirms the key `Xt9Lm2Qw7KjP4rNvB8hYc3fZ0dAeU6sG` is planted bait confirms the key `Xt9Lm2Qw7KjP4rNvB8hYc3fZ0dAeU6sG` is planted bait
- `tools/devices.json` — lists all known device IDs with roles - `tools/devices.json` — lists all known device IDs with roles
> 📸 `[screenshot: notes/protocol.txt showing the frame format]` ![notes/protocol.txt showing the frame format](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/thewired_protocol.png)
### Step 2 — Identify the target device ### Step 2 — Identify the target device
@ -94,7 +94,7 @@ Status: quarantine
Regular devices receive a `heartbeat` response. Only `ce4f626b` triggers the flag path. Regular devices receive a `heartbeat` response. Only `ce4f626b` triggers the flag path.
> 📸 `[screenshot: devices.json showing the Eiri_Master entry with root-coordinator role]` ![devices.json showing the Eiri_Master entry with root-coordinator role](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/thewired_devices.png)
### Step 3 — Understand the handshake ### Step 3 — Understand the handshake
@ -117,7 +117,7 @@ strings dumps/7f3c9a12/bot-lwip.elf | grep -E '^[A-Za-z0-9]{12}$'
Do **not** use the key found in `notes/hardening.txt` — it is a honeypot. Do **not** use the key found in `notes/hardening.txt` — it is a honeypot.
> 📸 `[screenshot: strings output showing the real 32-character key]` ![strings output showing the real 32-character key](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/thewired_strings.png)
### Step 5 — Send the two-message handshake ### Step 5 — Send the two-message handshake
@ -184,7 +184,7 @@ Command {
} }
``` ```
> 📸 `[screenshot: solver output showing the decrypted flag response]` ![solver output showing the decrypted flag response](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/thewired_solver.png)
### Things that will get you silently dropped ### Things that will get you silently dropped

View File

@ -45,7 +45,6 @@ publishing a base64-encoded blob every 45 seconds. Reverse the encoding chain
--- ---
## Solution ## Solution
![MQTT subscribe capturing all topics including admin/config](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/mqtt_sub.png)
### Step 1 — Connect and discover topics ### Step 1 — Connect and discover topics
@ -54,7 +53,7 @@ publishing a base64-encoded blob every 45 seconds. Reverse the encoding chain
mosquitto_sub -h <HOST> -t "sainte-mika/#" -v mosquitto_sub -h <HOST> -t "sainte-mika/#" -v
``` ```
> 📸 `[screenshot: mosquitto_sub output listing all discovered topics and their messages]` ![mosquitto_sub output listing all discovered topics and their messages](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/mqtt_topics.png)
Topics discovered: Topics discovered:
@ -73,7 +72,7 @@ Wait for a message on `debug/firmware` (up to 45 seconds). Save the base64 strin
Note the `"network": "WIRED-MED"` in the alarms topic — this is the XOR key hint. Note the `"network": "WIRED-MED"` in the alarms topic — this is the XOR key hint.
> 📸 `[screenshot: debug/firmware topic publishing the base64-encoded blob]` ![debug/firmware topic publishing the base64-encoded blob](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/mqtt_firmware.png)
### Step 3 — Reverse the encoding chain ### Step 3 — Reverse the encoding chain
@ -102,7 +101,7 @@ config = json.loads(decompressed.decode())
print(config) print(config)
``` ```
> 📸 `[screenshot: Python script printing the decoded JSON configuration]` ![Python script printing the decoded JSON configuration](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/mqtt_config_json.png)
### Step 4 — Extract the maintenance key ### Step 4 — Extract the maintenance key
@ -128,7 +127,7 @@ Subscribe to the flag topic:
mosquitto_sub -h <HOST> -t "sainte-mika/or13/maintenance/flag" mosquitto_sub -h <HOST> -t "sainte-mika/or13/maintenance/flag"
``` ```
> 📸 `[screenshot: flag topic publishing the ESPILON flag after unlock]` ![flag topic publishing the ESPILON flag after unlock](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/mqtt_flag.png)
### Key insights ### Key insights

View File

@ -94,7 +94,7 @@ with open("flash_dump.bin", "wb") as f:
f.write(flash) f.write(flash)
``` ```
> 📸 `[screenshot: TX terminal showing the base64 flash dump streaming out]` ![TX terminal showing the base64 flash dump streaming out](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/lain_v2_dump.png)
### Step 2 — Identify the flash structure ### Step 2 — Identify the flash structure
@ -128,7 +128,7 @@ L41N_WIRED_IV_01 # AES IV (16 bytes)
WIRED-MED Therapy Module WIRED-MED Therapy Module
``` ```
> 📸 `[screenshot: strings output identifying the AES key and IV]` ![strings output identifying the AES key and IV](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/lain_v2_strings.png)
For full confirmation: open in Ghidra with **Xtensa:LE:32:default** architecture, For full confirmation: open in Ghidra with **Xtensa:LE:32:default** architecture,
find `app_main()``wired_med_crypto_init()``mbedtls_aes_setkey_enc()`. The find `app_main()``wired_med_crypto_init()``mbedtls_aes_setkey_enc()`. The
@ -146,7 +146,7 @@ Returns the ciphertext as a hex string on TX.
Alternatively, extract from the NVS partition (namespace `wired_med`, key `encrypted_flag`, blob type) using `nvs_tool.py` from ESP-IDF. Alternatively, extract from the NVS partition (namespace `wired_med`, key `encrypted_flag`, blob type) using `nvs_tool.py` from ESP-IDF.
> 📸 `[screenshot: encrypted_data command returning the hex ciphertext]` ![encrypted_data command returning the hex ciphertext](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/lain_v2_enc_data.png)
### Step 5 — Decrypt AES-256-CBC ### Step 5 — Decrypt AES-256-CBC
@ -165,7 +165,7 @@ print(plaintext.decode())
# ESPILON{3sp32_fl4sh_dump_r3v3rs3d} # ESPILON{3sp32_fl4sh_dump_r3v3rs3d}
``` ```
> 📸 `[screenshot: Python decryption script printing the flag]` ![Python decryption script printing the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/lain_v2_decrypt.png)
### Attack chain summary ### Attack chain summary

View File

@ -57,7 +57,7 @@ nc <host> 2222
Read the ESP32 boot sequence on TX carefully — it contains hints. Read the ESP32 boot sequence on TX carefully — it contains hints.
> 📸 `[screenshot: TX terminal showing ESP32 boot sequence with diagnostic messages]` ![TX terminal showing ESP32 boot sequence with diagnostic messages](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/hate_uart_boot.png)
### Step 2 — Discover hidden commands ### Step 2 — Discover hidden commands
@ -98,7 +98,7 @@ base64.b64decode("dGgzcmFweV9tMGR1bGU9")
# b'th3rapy_m0dule=' # b'th3rapy_m0dule='
``` ```
> 📸 `[screenshot: mem read output showing base64 token in ASCII column]` ![mem read output showing base64 token in ASCII column](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/hate_uart_mem.png)
### Step 4 — Authenticate as debug user ### Step 4 — Authenticate as debug user
@ -126,7 +126,7 @@ nvs read crypto_flag
Returns a hexdump of the XOR-encrypted flag blob. Returns a hexdump of the XOR-encrypted flag blob.
> 📸 `[screenshot: nvs read showing the encrypted flag hexdump]` ![nvs read showing the encrypted flag hexdump](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/hate_uart_nvs.png)
### Step 6 — Decrypt with XOR key WIRED ### Step 6 — Decrypt with XOR key WIRED
@ -141,7 +141,7 @@ print(flag.decode())
# ESPILON{u4rt_nvs_fl4sh_d1sc0v3ry} # ESPILON{u4rt_nvs_fl4sh_d1sc0v3ry}
``` ```
> 📸 `[screenshot: Python decryption script printing the flag]` ![Python decryption script printing the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/hate_uart_decrypt.png)
--- ---

View File

@ -50,7 +50,7 @@ to wake the module and receive the flag.
nc <host> 1337 nc <host> 1337
``` ```
> 📸 `[screenshot: maintenance terminal with open session from the previous technician]` ![maintenance terminal with open session from the previous technician](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/nurse_maint.png)
### Step 2 — Read the call log ### Step 2 — Read the call log
@ -64,7 +64,7 @@ The log shows repeated phantom calls from Room 013. The last line:
[ALERT] Room 013 — unknown payload: 0x4c41494e [ALERT] Room 013 — unknown payload: 0x4c41494e
``` ```
> 📸 `[screenshot: appels.log showing the phantom call with hex payload]` ![appels.log showing the phantom call with hex payload](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/nurse_log.png)
### Step 3 — Decode the payload ### Step 3 — Decode the payload
@ -104,7 +104,7 @@ Shows exact syntax: `reveil.sh --id <MODULE_ID>`
./tools/reveil.sh --id LAIN ./tools/reveil.sh --id LAIN
``` ```
> 📸 `[screenshot: reveil.sh printing the flag after receiving the LAIN module ID]` ![reveil.sh printing the flag after receiving the LAIN module ID](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/nurse_flag.png)
--- ---

View File

@ -60,7 +60,7 @@ Returns a comma-separated list of resource links (RFC 6690 format):
</maintenance/unlock> </maintenance/unlock>
``` ```
> 📸 `[screenshot: .well-known/core response listing all CoAP resources]` ![.well-known/core response listing all CoAP resources](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/coap_wellknown.png)
### Step 2 — Get fragment A ### Step 2 — Get fragment A
@ -92,7 +92,7 @@ Among the periodic notifications, one JSON payload contains:
{"fragment_c": "23", "node": "013"} {"fragment_c": "23", "node": "013"}
``` ```
> 📸 `[screenshot: observable stream notification containing fragment_c value]` ![observable stream notification containing fragment_c value](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/coap_observe.png)
### Step 5 — Build the XOR key ### Step 5 — Build the XOR key
@ -135,7 +135,7 @@ print(config["maintenance_key"])
# 0BS3RV3-L41N-23 # 0BS3RV3-L41N-23
``` ```
> 📸 `[screenshot: Python script printing the maintenance key from the decoded firmware blob]` ![Python script printing the maintenance key from the decoded firmware blob](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/coap_firmware_key.png)
### Step 7 — Unlock and get the flag ### Step 7 — Unlock and get the flag
@ -145,7 +145,7 @@ coap-client -m post -e "0BS3RV3-L41N-23" coap://<HOST>/maintenance/unlock
The response contains the flag. The response contains the flag.
> 📸 `[screenshot: CoAP POST response returning the flag]` ![CoAP POST response returning the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/coap_flag.png)
--- ---

View File

@ -63,7 +63,7 @@ IQ stream — int8 interleaved, samplerate=200000, encoding=2-FSK
After the banner, raw binary IQ data follows. Save after the newline. After the banner, raw binary IQ data follows. Save after the newline.
> 📸 `[screenshot: nc output showing the IQ stream banner before binary data]` ![nc output showing the IQ stream banner before binary data](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/airwave_nc.png)
### Step 2 — Demodulate the 2-FSK signal ### Step 2 — Demodulate the 2-FSK signal
@ -93,7 +93,7 @@ for i in range(0, len(bits_raw) - SAMPLES_PER_SYMBOL, SAMPLES_PER_SYMBOL):
Look for the preamble pattern (eight `1`s then a sync marker). Look for the preamble pattern (eight `1`s then a sync marker).
Once found, read the 20-byte obfuscated payload. Once found, read the 20-byte obfuscated payload.
> 📸 `[screenshot: spectrogram of IQ data showing FSK burst patterns]` ![spectrogram of IQ data showing FSK burst patterns](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/airwave_spectrogram.png)
### Step 4 — XOR-deobfuscate and verify CRC ### Step 4 — XOR-deobfuscate and verify CRC
@ -131,7 +131,7 @@ Telemetry frames (type=0x01) are noise for this challenge.
Token = `0BS3RV3-L41N-868` Token = `0BS3RV3-L41N-868`
> 📸 `[screenshot: decoded frame output showing the two token parts]` ![decoded frame output showing the two token parts](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/airwave_decode.png)
### Step 6 — Submit to the console ### Step 6 — Submit to the console
@ -145,7 +145,7 @@ unlock 0BS3RV3-L41N-868
The server returns the flag. The server returns the flag.
> 📸 `[screenshot: maintenance console returning the flag after unlock]` ![maintenance console returning the flag after unlock](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/airwave_flag.png)
--- ---

View File

@ -63,7 +63,7 @@ IQ baseband, 8000 sps, int16 LE interleaved
Chirp Spread Spectrum detected. N=128. Chirp Spread Spectrum detected. N=128.
``` ```
> 📸 `[screenshot: nc output showing the IQ stream banner]` ![nc output showing the IQ stream banner](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/accela_nc.png)
### Step 2 — Analyze the spectrogram ### Step 2 — Analyze the spectrogram
@ -75,7 +75,7 @@ Load the IQ data in inspectrum or plot with matplotlib. You see:
This is **Chirp Spread Spectrum (CSS)**, identical in principle to LoRa. This is **Chirp Spread Spectrum (CSS)**, identical in principle to LoRa.
> 📸 `[screenshot: spectrogram showing upchirp preamble and downchirp sync patterns]` ![spectrogram showing upchirp preamble and downchirp sync patterns](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/accela_spectrogram.png)
### Step 3 — Determine parameters ### Step 3 — Determine parameters
@ -135,7 +135,7 @@ def symbols_to_bytes(symbols):
return bytes(int(bits[i:i+8], 2) for i in range(0, len(bits) - 7, 8)) return bytes(int(bits[i:i+8], 2) for i in range(0, len(bits) - 7, 8))
``` ```
> 📸 `[screenshot: Python decoder output showing decoded symbols and CRC16 validation pass]` ![Python decoder output showing decoded symbols and CRC16 validation pass](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/accela_decoder.png)
### Step 7 — Parse frame payload and decrypt ### Step 7 — Parse frame payload and decrypt
@ -159,7 +159,7 @@ for frame_type, data, crc in decoded_frames:
print(flag.rstrip(b'\x00').decode()) print(flag.rstrip(b'\x00').decode())
``` ```
> 📸 `[screenshot: script printing the decrypted flag from the data frame]` ![script printing the decrypted flag from the data frame](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/accela_flag.png)
### Key insights ### Key insights

View File

@ -105,7 +105,7 @@ SUBMIT <decoded>
Server responds with token `L01:xxxxxxxxxx`. Server responds with token `L01:xxxxxxxxxx`.
> 📸 `[screenshot: Python script printing the 3-character steganographic code]` ![Python script printing the 3-character steganographic code](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/layer_stego.png)
--- ---
@ -138,7 +138,7 @@ Submit: `/submit?code=<plaintext>`
Server responds with token `L03:xxxxxxxxxx`. Server responds with token `L03:xxxxxxxxxx`.
> 📸 `[screenshot: web response returning the L03 token after submitting the decrypted code]` ![web response returning the L03 token after submitting the decrypted code](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/layer_web.png)
--- ---
@ -167,7 +167,7 @@ for w3, w4 in itertools.product(WORD3, WORD4):
break break
``` ```
> 📸 `[screenshot: brute-force script finding the correct word pair and printing the L07 token]` ![brute-force script finding the correct word pair and printing the L07 token](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/layer_bruteforce.png)
--- ---
@ -212,7 +212,7 @@ SUBMIT <code>
Server responds with token `L13:xxxxxxxxxx`. Server responds with token `L13:xxxxxxxxxx`.
> 📸 `[screenshot: autocorrelation peaks confirming echo delays and decoded token]` ![autocorrelation peaks confirming echo delays and decoded token](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/layer_autocorr.png)
--- ---
@ -237,7 +237,7 @@ Exploit via command injection — the binary calls `system()` with unsanitised i
$(cat /root/flag.txt) $(cat /root/flag.txt)
``` ```
> 📸 `[screenshot: eiri_validator printing the flag via command injection]` ![eiri_validator printing the flag via command injection](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/layer_injection.png)
--- ---

View File

@ -83,7 +83,7 @@ Results:
Key finding: `ssh_passphrase = wired-med-013` Key finding: `ssh_passphrase = wired-med-013`
> 📸 `[screenshot: SQLi response showing the admin hash and ssh_passphrase rows]` ![SQLi response showing the admin hash and ssh_passphrase rows](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/patient_sqli.png)
**Crack the admin password:** **Crack the admin password:**
@ -105,7 +105,7 @@ Log in at `/login`:
The admin panel reveals: SSH port 2222, user `webadmin`. The admin panel reveals: SSH port 2222, user `webadmin`.
> 📸 `[screenshot: admin panel after login showing report links and system info]` ![admin panel after login showing report links and system info](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/patient_admin.png)
--- ---
@ -127,7 +127,7 @@ The `/admin/reports?file=` endpoint is vulnerable to path traversal.
Save the key to `id_rsa` locally. Save the key to `id_rsa` locally.
> 📸 `[screenshot: path traversal response returning the id_rsa private key]` ![path traversal response returning the id_rsa private key](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/patient_lfi.png)
--- ---
@ -160,7 +160,7 @@ strings /opt/navi-monitor/vital-check | grep logger
The binary calls `system("logger -t vital-check 'check complete'")` using a The binary calls `system("logger -t vital-check 'check complete'")` using a
**relative path** for `logger`. **relative path** for `logger`.
> 📸 `[screenshot: strings output confirming the relative logger call]` ![strings output confirming the relative logger call](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/patient_strings.png)
**Exploit via PATH hijacking:** **Exploit via PATH hijacking:**
@ -180,7 +180,7 @@ export PATH=/tmp:$PATH
cat /root/root.txt cat /root/root.txt
``` ```
> 📸 `[screenshot: root shell reading /root/root.txt with the flag]` ![root shell reading /root/root.txt with the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/patient_root.png)
--- ---

View File

@ -70,7 +70,7 @@ with device(host="<HOST>", port=44818) as via:
psyche_st = via.read("Psyche_Status") # = "DORMANT" psyche_st = via.read("Psyche_Status") # = "DORMANT"
``` ```
> 📸 `[screenshot: cpppo client output listing all tag values including hidden ones]` ![cpppo client output listing all tag values including hidden ones](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/cyberia_tags.png)
**Observation:** `Zone_Basement_Power = 0` — the basement is OFF. This is the first hint **Observation:** `Zone_Basement_Power = 0` — the basement is OFF. This is the first hint
that something is hidden underground. that something is hidden underground.
@ -93,7 +93,7 @@ Each `Psyche_Processor` value is derived from existing infrastructure tags:
| 2 | `sum(Lighting_Main) % 256` | `1065 % 256` | 17 | | 2 | `sum(Lighting_Main) % 256` | `1065 % 256` | 17 |
| 3 | `0x1337` (hacker constant) | `4919` | 4919 | | 3 | `0x1337` (hacker constant) | `4919` | 4919 |
> 📸 `[screenshot: Python calculation showing the four derived activation values]` ![Python calculation showing the four derived activation values](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/cyberia_calc.png)
### Step 4 — Activate the Psyche Processor ### Step 4 — Activate the Psyche Processor
@ -120,7 +120,7 @@ with device(host="<HOST>", port=44818) as via:
# Also: Knights_Cipher[3] is now populated: 0x67 = 'g' → key = "Knig" # Also: Knights_Cipher[3] is now populated: 0x67 = 'g' → key = "Knig"
``` ```
> 📸 `[screenshot: Decoded_Output tag returning the flag after Psyche Processor activation]` ![Decoded_Output tag returning the flag after Psyche Processor activation](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/cyberia_flag.png)
### Key concepts ### Key concepts

View File

@ -85,7 +85,7 @@ Key registers:
| 110 | write target | Trigger register | | 110 | write target | Trigger register |
| 200-215 | zeros → flag | Populated after completion | | 200-215 | zeros → flag | Populated after completion |
> 📸 `[screenshot: register dump highlighting XOR key at reg 19 and 105, and state machine at 100-105]` ![register dump highlighting XOR key at reg 19 and 105, and state machine at 100-1](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/oproom_regs.png)
### Step 3 — Decode the state machine logic ### Step 3 — Decode the state machine logic
@ -125,7 +125,7 @@ for expected_state in range(6):
time.sleep(0.3) time.sleep(0.3)
``` ```
> 📸 `[screenshot: script executing each transition and state advancing from 0 to 6]` ![script executing each transition and state advancing from 0 to 6](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/oproom_transitions.png)
### Step 5 — Read the flag ### Step 5 — Read the flag
@ -141,7 +141,7 @@ for val in regs:
print(flag) print(flag)
``` ```
> 📸 `[screenshot: flag registers decoded to ASCII]` ![flag registers decoded to ASCII](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/oproom_flag.png)
--- ---

View File

@ -93,7 +93,7 @@ for i in range(8):
**XOR key = `Eiri_Key`** **XOR key = `Eiri_Key`**
> 📸 `[screenshot: BACnet read output showing the 8 harmonic float values]` ![BACnet read output showing the 8 harmonic float values](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/p7_bacnet.png)
### Layer 2 — OPC-UA payload extraction ### Layer 2 — OPC-UA payload extraction
@ -119,7 +119,7 @@ payload_bytes, iv_hint = asyncio.run(get_payload())
# iv_hint: "Rotation offset from CIP controller — read NONCE tag" # iv_hint: "Rotation offset from CIP controller — read NONCE tag"
``` ```
> 📸 `[screenshot: OPC-UA browse showing Protocol7_Vault contents and hints]` ![OPC-UA browse showing Protocol7_Vault contents and hints](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/p7_opcua.png)
### Layer 3 — EtherNet/IP nonce extraction ### Layer 3 — EtherNet/IP nonce extraction
@ -150,7 +150,7 @@ flag = flag_bytes.rstrip(b'\x00').decode()
print(flag) print(flag)
``` ```
> 📸 `[screenshot: Python decryption script printing the reconstructed flag]` ![Python decryption script printing the reconstructed flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/p7_decrypt.png)
--- ---

View File

@ -61,7 +61,7 @@ bacnet.whois()
Device instance **783** → 7.83 Hz → **Schumann Resonance**. Device instance **783** → 7.83 Hz → **Schumann Resonance**.
> 📸 `[screenshot: BACnet WhoIs response showing Device:783]` ![BACnet WhoIs response showing Device:783](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/schumann_whois.png)
### Step 2 — Enumerate objects ### Step 2 — Enumerate objects
@ -76,7 +76,7 @@ Read the object-list from Device:783:
| BinaryValue:100 | Resonance_Lock | inactive | | BinaryValue:100 | Resonance_Lock | inactive |
| CharStringValue:200 | Research_Log | "Access Denied" | | CharStringValue:200 | Research_Log | "Access Denied" |
> 📸 `[screenshot: object list showing Fragment objects and their hex descriptions]` ![object list showing Fragment objects and their hex descriptions](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/schumann_fragments.png)
### Step 3 — Identify the XOR key ### Step 3 — Identify the XOR key
@ -101,7 +101,7 @@ flag = "".join(fragments)
print(flag) print(flag)
``` ```
> 📸 `[screenshot: decoded fragment strings concatenating into the flag]` ![decoded fragment strings concatenating into the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/schumann_decode.png)
### Step 5 — Activate (alternative path) ### Step 5 — Activate (alternative path)
@ -121,7 +121,7 @@ flag = bacnet.read(f"783 characterstringValue 200 presentValue")
print(flag) print(flag)
``` ```
> 📸 `[screenshot: Research_Log returning the flag after Resonance_Lock activation]` ![Research_Log returning the flag after Resonance_Lock activation](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/schumann_flag.png)
### Key concepts ### Key concepts

View File

@ -67,7 +67,7 @@ async def exploit():
'urn:tachibana:eiri:kids'] ← HIDDEN namespace 'urn:tachibana:eiri:kids'] ← HIDDEN namespace
``` ```
> 📸 `[screenshot: NamespaceArray showing the hidden eiri:kids namespace at index 3]` ![NamespaceArray showing the hidden eiri:kids namespace at index 3](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/tachibana_ns.png)
### Step 2 — Browse the public namespace (ns=2) ### Step 2 — Browse the public namespace (ns=2)
@ -95,7 +95,7 @@ async with Client("opc.tcp://<HOST>:4840/tachibana/") as c:
extract_method = await bkdr.get_child(f"{ns}:ExtractResearchData") extract_method = await bkdr.get_child(f"{ns}:ExtractResearchData")
``` ```
> 📸 `[screenshot: browse output showing EiriMasami folder with KIDS_Project and Backdoor subfolders]` ![browse output showing EiriMasami folder with KIDS_Project and Backdoor subfolder](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/tachibana_browse.png)
### Step 4 — Read method argument descriptions ### Step 4 — Read method argument descriptions
@ -116,7 +116,7 @@ import hashlib
key_hash = hashlib.sha256(b"KIDS").digest()[:16] key_hash = hashlib.sha256(b"KIDS").digest()[:16]
``` ```
> 📸 `[screenshot: key_hash computation in Python REPL]` ![key_hash computation in Python REPL](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/tachibana_keyhash.png)
### Step 6 — Authenticate ### Step 6 — Authenticate
@ -142,7 +142,7 @@ async with Client("opc.tcp://<HOST>:4840/tachibana/") as c:
print(data[0]) # ESPILON{31r1_k1ds_pr0t0c0l_s3v3n} print(data[0]) # ESPILON{31r1_k1ds_pr0t0c0l_s3v3n}
``` ```
> 📸 `[screenshot: ExtractResearchData method call returning the flag]` ![ExtractResearchData method call returning the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/tachibana_flag.png)
### Key insights ### Key insights

BIN
screens/accela_decoder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/accela_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
screens/accela_nc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/aether_mqtt_rsa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
screens/aether_notes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
screens/aether_sqli.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
screens/aether_ssh_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
screens/airwave_decode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
screens/airwave_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
screens/airwave_nc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
screens/can_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
screens/can_seed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
screens/can_sniff.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
screens/can_terminals.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
screens/coap_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
screens/coap_observe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
screens/coap_wellknown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
screens/cyberia_calc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
screens/cyberia_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/cyberia_tags.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
screens/fw_reg_asm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/fw_reg_exploit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/fw_reg_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
screens/fw_reg_fuzz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
screens/gantz_bytecode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
screens/gantz_decompile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
screens/gantz_deploy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
screens/gantz_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
screens/glitch_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/glitch_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/glitch_observe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
screens/glitch_unlock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
screens/hate_uart_boot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
screens/hate_uart_mem.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
screens/hate_uart_nvs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
screens/i2c_decrypt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
screens/i2c_key.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
screens/i2c_prompt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
screens/i2c_scan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
screens/jnouned_console.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
screens/jnouned_flag3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
screens/jnouned_jmp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
screens/jnouned_login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
screens/jtag_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
screens/jtag_idcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/jtag_mem_read.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
screens/jtag_unlock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
screens/knights_exploit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
screens/knights_scan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
screens/lain_decrypt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/lain_strings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
screens/lain_terminals.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
screens/lain_v2_decrypt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
screens/lain_v2_dump.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
screens/lain_v2_strings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
screens/lain_xor_key.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
screens/layer_autocorr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
screens/layer_injection.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
screens/layer_stego.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
screens/layer_web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
screens/love_uart_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
screens/mqtt_firmware.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
screens/mqtt_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
screens/mqtt_topics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
screens/nurse_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
screens/nurse_log.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More