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
@ -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
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: esptool.py flashing — progress bar reaching 100%]`
|
||||

|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: serial terminal showing the encrypted flag and XOR key on boot]`
|
||||

|
||||
|
||||
### Step 3 — Identify the XOR key
|
||||
|
||||
@ -114,7 +114,7 @@ print(flag.decode())
|
||||
|
||||
Output: `ESPILON{st4rt_th3_w1r3}`
|
||||
|
||||
> 📸 `[screenshot: Python decryption script running and printing the flag]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -75,12 +75,11 @@ Open the UART console:
|
||||
screen /dev/ttyUSB0 115200
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: UART console showing jnoun-console> prompt]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Flag 1 — Console Access
|
||||

|
||||
(100 pts)
|
||||
|
||||
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.
|
||||
|
||||
> 📸 `[screenshot: successful admin_login command printing Flag 1]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Flag 2 — 802.11 TX
|
||||

|
||||
(200 pts)
|
||||
|
||||
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 (5–85 seconds)
|
||||
contains Flag 2 as cleartext in its 802.11 data payload.
|
||||
|
||||
> 📸 `[screenshot: Wireshark frame showing Flag 2 in the payload field]`
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
@ -163,7 +162,7 @@ Decoded: `target=x; flag`
|
||||
|
||||
Flag 3 prints to the UART console via `ESP_LOGE`.
|
||||
|
||||
> 📸 `[screenshot: UART console showing Flag 3 output after injection]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
@ -228,7 +227,7 @@ for block_id in range(10):
|
||||
print(flag.decode())
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python JMP client printing the final flag after block reassembly]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ nc <host> 3600
|
||||
nc <host> 3601
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: two terminal windows showing sniff output and inject prompt]`
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||
> 📸 `[screenshot: sniff output showing the 0x7E0/0x7E8 request/response pattern]`
|
||||

|
||||
|
||||
### 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`
|
||||
|
||||
> 📸 `[screenshot: seed bytes visible in the 0x7E8 response]`
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||
> 📸 `[screenshot: 0x7E8 response containing the flag bytes after successful security access]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@ arm and trigger, then read the maintenance token from the unlocked debug console
|
||||
nc <host> 3700
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: glitch lab banner and prompt]`
|
||||

|
||||
|
||||
### Step 2 — Observe the boot sequence
|
||||
|
||||
@ -68,7 +68,7 @@ LAUNCH_KERNEL [3400 – 5000]
|
||||
|
||||
The signature verification phase runs between cycles 3200 and 3400.
|
||||
|
||||
> 📸 `[screenshot: observe output showing all boot phases and cycle ranges]`
|
||||

|
||||
|
||||
### Step 3 — Configure glitch parameters
|
||||
|
||||
@ -101,7 +101,7 @@ LAUNCH_KERNEL ......... OK
|
||||
[DEBUG SHELL ACTIVATED]
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: boot log showing SIG_VERIFY SKIPPED and debug shell prompt]`
|
||||

|
||||
|
||||
### Step 5 — Read the maintenance token
|
||||
|
||||
@ -111,7 +111,7 @@ read_console
|
||||
|
||||
The debug console outputs the maintenance token containing the flag.
|
||||
|
||||
> 📸 `[screenshot: read_console output displaying the flag]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ Use the key to decrypt the EEPROM contents and recover the flag.
|
||||
nc <host> 3300
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: I2C bus interface prompt]`
|
||||

|
||||
|
||||
### Step 2 — Scan the bus
|
||||
|
||||
@ -66,7 +66,7 @@ I2C Address 0x48 [Temperature Sensor]
|
||||
I2C Address 0x60 [Crypto IC]
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: scan output listing three I2C devices]`
|
||||

|
||||
|
||||
### 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!!`
|
||||
|
||||
> 📸 `[screenshot: crypto IC returning the key after unlock]`
|
||||

|
||||
|
||||
### 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())
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python decryption script printing the flag]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ reassemble the flag.
|
||||
nc <host> 3400
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: JTAG port banner showing TAP controller information]`
|
||||

|
||||
|
||||
### Step 2 — Reset the TAP controller
|
||||
|
||||
@ -72,7 +72,7 @@ dr 00000000 32
|
||||
|
||||
Returns `0x4BA00477` — an ARM Cortex-M style IDCODE.
|
||||
|
||||
> 📸 `[screenshot: IDCODE read returning 0x4BA00477]`
|
||||

|
||||
|
||||
### Step 4 — Unlock the debug interface
|
||||
|
||||
@ -91,7 +91,7 @@ state
|
||||
|
||||
Output should now show: `Debug: UNLOCKED`
|
||||
|
||||
> 📸 `[screenshot: state command showing Debug: UNLOCKED]`
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||
> 📸 `[screenshot: dr reads returning flag bytes as 32-bit little-endian words]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ nc <host> 1111
|
||||
nc <host> 2222
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: two terminals open, TX showing boot messages and RX ready for input]`
|
||||

|
||||
|
||||
### Step 2 — Query the diagnostic commands
|
||||
|
||||
@ -103,7 +103,7 @@ frag_c_hex=3030
|
||||
|
||||
Decode: `bytes.fromhex("3030").decode()` → `00`
|
||||
|
||||
> 📸 `[screenshot: TX output showing all three fragment values from diagnostics]`
|
||||

|
||||
|
||||
### Step 4 — Build the maintenance token
|
||||
|
||||
@ -123,7 +123,7 @@ unlock LAIN-SERIAL-00
|
||||
|
||||
The flag is returned on the TX terminal.
|
||||
|
||||
> 📸 `[screenshot: TX terminal printing the flag after successful unlock]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@ Channels: 3
|
||||
Sample rate: 1 MHz
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: info command output listing the three channels]`
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||
> 📸 `[screenshot: Python script measuring bit periods from ch1 transitions]`
|
||||

|
||||
|
||||
### Step 5 — Decode UART 8N1
|
||||
|
||||
@ -133,7 +133,7 @@ print("".join(chars))
|
||||
|
||||
The decoded message contains the flag repeated three times.
|
||||
|
||||
> 📸 `[screenshot: decoded UART output showing the flag repeated]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ nc <host> 3500
|
||||
cs 0
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: SPI probe interface ready with CS asserted]`
|
||||

|
||||
|
||||
### 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 second is a vendor-specific table at offset 0x80.
|
||||
|
||||
> 📸 `[screenshot: SFDP header output showing two parameter table entries]`
|
||||

|
||||
|
||||
### Step 4 — Read the vendor SFDP table
|
||||
|
||||
@ -97,7 +97,7 @@ Size: 4096 bytes
|
||||
|
||||
This partition does not appear in the normal partition table.
|
||||
|
||||
> 📸 `[screenshot: vendor SFDP data revealing hidden partition at 0x030000]`
|
||||

|
||||
|
||||
### 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())
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python decryption script printing the recovered flag]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ Directories present: `notes/`, `comms/`, `dumps/`, `logs/`, `tools/`, `journal/`
|
||||
confirms the key `Xt9Lm2Qw7KjP4rNvB8hYc3fZ0dAeU6sG` is planted bait
|
||||
- `tools/devices.json` — lists all known device IDs with roles
|
||||
|
||||
> 📸 `[screenshot: notes/protocol.txt showing the frame format]`
|
||||

|
||||
|
||||
### Step 2 — Identify the target device
|
||||
|
||||
@ -94,7 +94,7 @@ Status: quarantine
|
||||
|
||||
Regular devices receive a `heartbeat` response. Only `ce4f626b` triggers the flag path.
|
||||
|
||||
> 📸 `[screenshot: devices.json showing the Eiri_Master entry with root-coordinator role]`
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||
> 📸 `[screenshot: strings output showing the real 32-character key]`
|
||||

|
||||
|
||||
### Step 5 — Send the two-message handshake
|
||||
|
||||
@ -184,7 +184,7 @@ Command {
|
||||
}
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: solver output showing the decrypted flag response]`
|
||||

|
||||
|
||||
### Things that will get you silently dropped
|
||||
|
||||
|
||||
@ -45,7 +45,6 @@ publishing a base64-encoded blob every 45 seconds. Reverse the encoding chain
|
||||
---
|
||||
|
||||
## Solution
|
||||

|
||||
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: mosquitto_sub output listing all discovered topics and their messages]`
|
||||

|
||||
|
||||
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.
|
||||
|
||||
> 📸 `[screenshot: debug/firmware topic publishing the base64-encoded blob]`
|
||||

|
||||
|
||||
### Step 3 — Reverse the encoding chain
|
||||
|
||||
@ -102,7 +101,7 @@ config = json.loads(decompressed.decode())
|
||||
print(config)
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python script printing the decoded JSON configuration]`
|
||||

|
||||
|
||||
### 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"
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: flag topic publishing the ESPILON flag after unlock]`
|
||||

|
||||
|
||||
### Key insights
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ with open("flash_dump.bin", "wb") as f:
|
||||
f.write(flash)
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: TX terminal showing the base64 flash dump streaming out]`
|
||||

|
||||
|
||||
### Step 2 — Identify the flash structure
|
||||
|
||||
@ -128,7 +128,7 @@ L41N_WIRED_IV_01 # AES IV (16 bytes)
|
||||
WIRED-MED Therapy Module
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: strings output identifying the AES key and IV]`
|
||||

|
||||
|
||||
For full confirmation: open in Ghidra with **Xtensa:LE:32:default** architecture,
|
||||
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.
|
||||
|
||||
> 📸 `[screenshot: encrypted_data command returning the hex ciphertext]`
|
||||

|
||||
|
||||
### Step 5 — Decrypt AES-256-CBC
|
||||
|
||||
@ -165,7 +165,7 @@ print(plaintext.decode())
|
||||
# ESPILON{3sp32_fl4sh_dump_r3v3rs3d}
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python decryption script printing the flag]`
|
||||

|
||||
|
||||
### Attack chain summary
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ nc <host> 2222
|
||||
|
||||
Read the ESP32 boot sequence on TX carefully — it contains hints.
|
||||
|
||||
> 📸 `[screenshot: TX terminal showing ESP32 boot sequence with diagnostic messages]`
|
||||

|
||||
|
||||
### Step 2 — Discover hidden commands
|
||||
|
||||
@ -98,7 +98,7 @@ base64.b64decode("dGgzcmFweV9tMGR1bGU9")
|
||||
# b'th3rapy_m0dule='
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: mem read output showing base64 token in ASCII column]`
|
||||

|
||||
|
||||
### Step 4 — Authenticate as debug user
|
||||
|
||||
@ -126,7 +126,7 @@ nvs read crypto_flag
|
||||
|
||||
Returns a hexdump of the XOR-encrypted flag blob.
|
||||
|
||||
> 📸 `[screenshot: nvs read showing the encrypted flag hexdump]`
|
||||

|
||||
|
||||
### Step 6 — Decrypt with XOR key WIRED
|
||||
|
||||
@ -141,7 +141,7 @@ print(flag.decode())
|
||||
# ESPILON{u4rt_nvs_fl4sh_d1sc0v3ry}
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python decryption script printing the flag]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ to wake the module and receive the flag.
|
||||
nc <host> 1337
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: maintenance terminal with open session from the previous technician]`
|
||||

|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: appels.log showing the phantom call with hex payload]`
|
||||

|
||||
|
||||
### Step 3 — Decode the payload
|
||||
|
||||
@ -104,7 +104,7 @@ Shows exact syntax: `reveil.sh --id <MODULE_ID>`
|
||||
./tools/reveil.sh --id LAIN
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: reveil.sh printing the flag after receiving the LAIN module ID]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ Returns a comma-separated list of resource links (RFC 6690 format):
|
||||
</maintenance/unlock>
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: .well-known/core response listing all CoAP resources]`
|
||||

|
||||
|
||||
### Step 2 — Get fragment A
|
||||
|
||||
@ -92,7 +92,7 @@ Among the periodic notifications, one JSON payload contains:
|
||||
{"fragment_c": "23", "node": "013"}
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: observable stream notification containing fragment_c value]`
|
||||

|
||||
|
||||
### Step 5 — Build the XOR key
|
||||
|
||||
@ -135,7 +135,7 @@ print(config["maintenance_key"])
|
||||
# 0BS3RV3-L41N-23
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python script printing the maintenance key from the decoded firmware blob]`
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||
> 📸 `[screenshot: CoAP POST response returning the flag]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
> 📸 `[screenshot: nc output showing the IQ stream banner before binary data]`
|
||||

|
||||
|
||||
### 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).
|
||||
Once found, read the 20-byte obfuscated payload.
|
||||
|
||||
> 📸 `[screenshot: spectrogram of IQ data showing FSK burst patterns]`
|
||||

|
||||
|
||||
### Step 4 — XOR-deobfuscate and verify CRC
|
||||
|
||||
@ -131,7 +131,7 @@ Telemetry frames (type=0x01) are noise for this challenge.
|
||||
|
||||
Token = `0BS3RV3-L41N-868`
|
||||
|
||||
> 📸 `[screenshot: decoded frame output showing the two token parts]`
|
||||

|
||||
|
||||
### Step 6 — Submit to the console
|
||||
|
||||
@ -145,7 +145,7 @@ unlock 0BS3RV3-L41N-868
|
||||
|
||||
The server returns the flag.
|
||||
|
||||
> 📸 `[screenshot: maintenance console returning the flag after unlock]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ IQ baseband, 8000 sps, int16 LE interleaved
|
||||
Chirp Spread Spectrum detected. N=128.
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: nc output showing the IQ stream banner]`
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||
> 📸 `[screenshot: spectrogram showing upchirp preamble and downchirp sync patterns]`
|
||||

|
||||
|
||||
### 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))
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python decoder output showing decoded symbols and CRC16 validation pass]`
|
||||

|
||||
|
||||
### 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())
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: script printing the decrypted flag from the data frame]`
|
||||

|
||||
|
||||
### Key insights
|
||||
|
||||
|
||||
@ -105,7 +105,7 @@ SUBMIT <decoded>
|
||||
|
||||
Server responds with token `L01:xxxxxxxxxx`.
|
||||
|
||||
> 📸 `[screenshot: Python script printing the 3-character steganographic code]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
@ -138,7 +138,7 @@ Submit: `/submit?code=<plaintext>`
|
||||
|
||||
Server responds with token `L03:xxxxxxxxxx`.
|
||||
|
||||
> 📸 `[screenshot: web response returning the L03 token after submitting the decrypted code]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
@ -167,7 +167,7 @@ for w3, w4 in itertools.product(WORD3, WORD4):
|
||||
break
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: brute-force script finding the correct word pair and printing the L07 token]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
@ -212,7 +212,7 @@ SUBMIT <code>
|
||||
|
||||
Server responds with token `L13:xxxxxxxxxx`.
|
||||
|
||||
> 📸 `[screenshot: autocorrelation peaks confirming echo delays and decoded token]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
@ -237,7 +237,7 @@ Exploit via command injection — the binary calls `system()` with unsanitised i
|
||||
$(cat /root/flag.txt)
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: eiri_validator printing the flag via command injection]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ Results:
|
||||
|
||||
Key finding: `ssh_passphrase = wired-med-013`
|
||||
|
||||
> 📸 `[screenshot: SQLi response showing the admin hash and ssh_passphrase rows]`
|
||||

|
||||
|
||||
**Crack the admin password:**
|
||||
|
||||
@ -105,7 +105,7 @@ Log in at `/login`:
|
||||
|
||||
The admin panel reveals: SSH port 2222, user `webadmin`.
|
||||
|
||||
> 📸 `[screenshot: admin panel after login showing report links and system info]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
@ -127,7 +127,7 @@ The `/admin/reports?file=` endpoint is vulnerable to path traversal.
|
||||
|
||||
Save the key to `id_rsa` locally.
|
||||
|
||||
> 📸 `[screenshot: path traversal response returning the id_rsa private key]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
@ -160,7 +160,7 @@ strings /opt/navi-monitor/vital-check | grep logger
|
||||
The binary calls `system("logger -t vital-check 'check complete'")` using a
|
||||
**relative path** for `logger`.
|
||||
|
||||
> 📸 `[screenshot: strings output confirming the relative logger call]`
|
||||

|
||||
|
||||
**Exploit via PATH hijacking:**
|
||||
|
||||
@ -180,7 +180,7 @@ export PATH=/tmp:$PATH
|
||||
cat /root/root.txt
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: root shell reading /root/root.txt with the flag]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ with device(host="<HOST>", port=44818) as via:
|
||||
psyche_st = via.read("Psyche_Status") # = "DORMANT"
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: cpppo client output listing all tag values including hidden ones]`
|
||||

|
||||
|
||||
**Observation:** `Zone_Basement_Power = 0` — the basement is OFF. This is the first hint
|
||||
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 |
|
||||
| 3 | `0x1337` (hacker constant) | `4919` | 4919 |
|
||||
|
||||
> 📸 `[screenshot: Python calculation showing the four derived activation values]`
|
||||

|
||||
|
||||
### 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"
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Decoded_Output tag returning the flag after Psyche Processor activation]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ Key registers:
|
||||
| 110 | write target | Trigger register |
|
||||
| 200-215 | zeros → flag | Populated after completion |
|
||||
|
||||
> 📸 `[screenshot: register dump highlighting XOR key at reg 19 and 105, and state machine at 100-105]`
|
||||

|
||||
|
||||
### Step 3 — Decode the state machine logic
|
||||
|
||||
@ -125,7 +125,7 @@ for expected_state in range(6):
|
||||
time.sleep(0.3)
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: script executing each transition and state advancing from 0 to 6]`
|
||||

|
||||
|
||||
### Step 5 — Read the flag
|
||||
|
||||
@ -141,7 +141,7 @@ for val in regs:
|
||||
print(flag)
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: flag registers decoded to ASCII]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -93,7 +93,7 @@ for i in range(8):
|
||||
|
||||
**XOR key = `Eiri_Key`**
|
||||
|
||||
> 📸 `[screenshot: BACnet read output showing the 8 harmonic float values]`
|
||||

|
||||
|
||||
### 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"
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: OPC-UA browse showing Protocol7_Vault contents and hints]`
|
||||

|
||||
|
||||
### Layer 3 — EtherNet/IP nonce extraction
|
||||
|
||||
@ -150,7 +150,7 @@ flag = flag_bytes.rstrip(b'\x00').decode()
|
||||
print(flag)
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Python decryption script printing the reconstructed flag]`
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -61,7 +61,7 @@ bacnet.whois()
|
||||
|
||||
Device instance **783** → 7.83 Hz → **Schumann Resonance**.
|
||||
|
||||
> 📸 `[screenshot: BACnet WhoIs response showing Device:783]`
|
||||

|
||||
|
||||
### Step 2 — Enumerate objects
|
||||
|
||||
@ -76,7 +76,7 @@ Read the object-list from Device:783:
|
||||
| BinaryValue:100 | Resonance_Lock | inactive |
|
||||
| CharStringValue:200 | Research_Log | "Access Denied" |
|
||||
|
||||
> 📸 `[screenshot: object list showing Fragment objects and their hex descriptions]`
|
||||

|
||||
|
||||
### Step 3 — Identify the XOR key
|
||||
|
||||
@ -101,7 +101,7 @@ flag = "".join(fragments)
|
||||
print(flag)
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: decoded fragment strings concatenating into the flag]`
|
||||

|
||||
|
||||
### Step 5 — Activate (alternative path)
|
||||
|
||||
@ -121,7 +121,7 @@ flag = bacnet.read(f"783 characterstringValue 200 presentValue")
|
||||
print(flag)
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: Research_Log returning the flag after Resonance_Lock activation]`
|
||||

|
||||
|
||||
### Key concepts
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ async def exploit():
|
||||
'urn:tachibana:eiri:kids'] ← HIDDEN namespace
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: NamespaceArray showing the hidden eiri:kids namespace at index 3]`
|
||||

|
||||
|
||||
### 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")
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: browse output showing EiriMasami folder with KIDS_Project and Backdoor subfolders]`
|
||||

|
||||
|
||||
### Step 4 — Read method argument descriptions
|
||||
|
||||
@ -116,7 +116,7 @@ import hashlib
|
||||
key_hash = hashlib.sha256(b"KIDS").digest()[:16]
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: key_hash computation in Python REPL]`
|
||||

|
||||
|
||||
### 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}
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: ExtractResearchData method call returning the flag]`
|
||||

|
||||
|
||||
### Key insights
|
||||
|
||||
|
||||
BIN
screens/accela_decoder.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/accela_flag.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
screens/accela_nc.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/accela_spectrogram.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
screens/aether_cube_root.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/aether_mqtt_rsa.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
screens/aether_notes.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
screens/aether_sqli.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
screens/aether_ssh_flag.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
screens/airwave_decode.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
screens/airwave_flag.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
screens/airwave_nc.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
screens/airwave_spectrogram.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
screens/can_flag.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
screens/can_seed.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/can_sniff.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
screens/can_terminals.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
screens/coap_firmware_key.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
screens/coap_flag.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
screens/coap_observe.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
screens/coap_wellknown.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/cyberia_calc.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/cyberia_flag.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/cyberia_tags.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
screens/fw_reg_asm.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/fw_reg_exploit.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/fw_reg_flag.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
screens/fw_reg_fuzz.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
screens/gantz_bytecode.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/gantz_decompile.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
screens/gantz_deploy.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/gantz_flag.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
screens/glitch_banner.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/glitch_flag.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/glitch_observe.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
screens/glitch_unlock.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
screens/hate_uart_boot.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/hate_uart_decrypt.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
screens/hate_uart_mem.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
screens/hate_uart_nvs.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
screens/i2c_decrypt.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
screens/i2c_key.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/i2c_prompt.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
screens/i2c_scan.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
screens/jnouned_console.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
screens/jnouned_flag3.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
screens/jnouned_jmp.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
screens/jnouned_login.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
screens/jtag_banner.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/jtag_idcode.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/jtag_mem_read.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/jtag_unlock.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/knights_exploit.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/knights_fragments.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/knights_scan.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
screens/lain_decrypt.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/lain_strings.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
screens/lain_terminals.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
screens/lain_v2_decrypt.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/lain_v2_dump.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/lain_v2_enc_data.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
screens/lain_v2_strings.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
screens/lain_xor_key.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/layer_autocorr.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/layer_bruteforce.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
screens/layer_injection.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/layer_stego.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
screens/layer_web.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
screens/love_uart_flag.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
screens/love_uart_terminals.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
screens/mqtt_config_json.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
screens/mqtt_firmware.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
screens/mqtt_flag.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
screens/mqtt_topics.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
screens/nurse_flag.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
screens/nurse_log.png
Normal file
|
After Width: | Height: | Size: 18 KiB |