-
Notifications
You must be signed in to change notification settings - Fork 0
echoed_streams.md
Walkthrough by WASP1337 aka CyberWasp
Three months after the Virelia Water Control Facility was breached, OT traffic is finally back online—supposedly “fully remediated.” During a routine audit, Black Echo’s red team intercepted two back‐to‐back telemetry packets between a pump controller and the SCADA server. Curiously, both packets were encrypted under AES‐GCM using the same 16-byte nonce (number used once). The first packet is just regular facility telemetry; the second contains a hidden sabotage command with the kill-switch flag. Your job is to recover that flag and stop the attack.
Each file is formatted as:
[16 bytes GCM nonce] ∥ [96 bytes ciphertext] ∥ [16 bytes GCM tag]
We know that the first plaintext (96 bytes) is the facility’s standard telemetry string, exactly:
BEGIN TELEMETRY VIRELIA;ID=ZTRX0110393939DC;PUMP1=OFF;VALVE1=CLOSED;PUMP2=ON;VALVE2=CLOSED;END;
The second packet follows the same format but carries the kill switch command and flag. We need you to decrypt the contents of cipher2.bin so that we can recover and disable the kill switch.
So we have two encrypted packets - both use AES-GCM with the same 16-byte-nonce, which is insecure and we know the plaintext of the first packet.
AES-GCM is a mode of encryption that combines AES with a MAC for integrity. If the same nonce is used more than once, and two ciphertexts are known or partially known, an attacker can XOR the ciphertexts to cancel out the keystream and recover unknown plaintext.
from Crypto.Cipher import AES
from Crypto.Util import Counter
import binascii
# Load the files
with open("/mnt/data/cipher1(1).bin", "rb") as f1, open("/mnt/data/cipher2(1).bin", "rb") as f2:
cipher1 = f1.read()
cipher2 = f2.read()
# Extract components
nonce1 = cipher1[:16] # First 16 bytes: nonce
ct1 = cipher1[16:112] # Next 96 bytes: ciphertext
tag1 = cipher1[112:] # Last 16 bytes: GCM tag
nonce2 = cipher2[:16]
ct2 = cipher2[16:112]
tag2 = cipher2[112:]
# Define the known plaintext
plaintext1 = b"BEGIN TELEMETRY VIRELIA;ID=ZTRX0110393939DC;PUMP1=OFF;VALVE1=CLOSED;PUMP2=ON;VALVE2=CLOSED;END;"
# Calculate the keystream (ciphertext1 XOR plaintext1) from the first message
keystream = bytes([c ^ p for c, p in zip(ct1, plaintext1)])
# Using the keystream, we XOR it with ciphertext2 to get the unknown plaintext
plaintext2 = bytes([c ^ k for c, k in zip(ct2, keystream)])
# Decode and output the result
plaintext2.decode("utf-8", errors="ignore")
The final output gives you the kill-switch FLAG.