Home > Writeups > DEADROP Misc 4 - SIGINT PUZZLE

DEADROP Misc 4 - SIGINT PUZZLE

Three fake declassified SIGINT documents hide base64 flag fragments in the least significant bits of the red channel. The lore tells you the order; extract, concatenate, decode.

SIGINT PUZZLE

Challenge Description

SIGINT PUZZLE PACKAGE - OPERATION NIGHTJAR. Three declassified document images are included. Each document image contains embedded key material.

The archive contains four files:

SIGINT_PUZZLE/
  freq_logs.txt - Package header and analyst notes
  SIGINT_LOG_1983_09_01.png - Frequency monitoring log [Document 1]
  CALLSIGN_REGISTRY_PARTIAL.png - Callsign registry extract [Document 2]
  ASSET_LOCATION_BRIEF_0912.png - Asset location brief [Document 3]

Reading freq_logs.txt

The package header is explicit about the structure:

ANALYST NOTES:
  Each document image contains embedded key material related to the
  encrypted burst transmissions captured on 2023-09-01. The key material
  is fragmented across documents [1], [2], and [3] in that order.

  Fragments must be extracted from the images and assembled in document
  order to recover the decryption key. Standard signal processing
  techniques apply.

And R. Okafor's note:

"I don't know how they keep getting this stuff in here. Looked at the images at every zoom level before I gave up and actually ran a script on them. Do not make my mistake."

The hint is direct: visual inspection won't work. You need a script.


Identifying the Technique: LSB Steganography

The images are PNGs of typewritten documents on aged paper, lots of nearly-uniform pixel values with small noise variations. This is classic LSB steganography territory. The noise in the background makes single-bit changes completely invisible to the eye.

LSB (Least Significant Bit) steganography hides data in the lowest bit of each pixel's color channel. A pixel with R value 11001011 becomes 11001010 or 11001011 depending on the hidden bit. The visual difference is a change of 1 in 255, imperceptible.

Tools that find this automatically:

# zsteg (ruby gem)
zsteg SIGINT_LOG_1983_09_01.png
# reports: b1,r,lsb,xy ... text: "REVBR..."

# stegsolve (Java)
# Open image → Analyse → Data Extract → R bit 0 → Extract

Or implement extraction directly.


Extraction

The embedding scheme: LSBs of the red channel, big-endian, prefixed with a 2-byte big-endian length field.

import struct
from PIL import Image

def lsb_extract(path: str) -> str:
    img = Image.open(path).convert('RGB')
    pixels = list(img.getdata())

    # Read LSB of R channel for each pixel
    bits = [(px[0] & 1) for px in pixels]

    # First 16 bits = message length in bytes
    length = 0
    for b in bits[:16]:
        length = (length << 1) | b

    # Read length * 8 bits
    msg_bits = bits[16:16 + length * 8]

    # Convert bits to bytes
    msg_bytes = bytearray()
    for i in range(0, len(msg_bits), 8):
        byte = 0
        for b in msg_bits[i:i+8]:
            byte = (byte << 1) | b
        msg_bytes.append(byte)

    return msg_bytes.decode('ascii')

Assembling the Flag

import base64

raw1 = lsb_extract('SIGINT_PUZZLE/SIGINT_LOG_1983_09_01.png')
raw2 = lsb_extract('SIGINT_PUZZLE/CALLSIGN_REGISTRY_PARTIAL.png')
raw3 = lsb_extract('SIGINT_PUZZLE/ASSET_LOCATION_BRIEF_0912.png')

print(raw1)  # k1:REVBRFJPUHtzaWdpbnRf
print(raw2)  # k2:b3NpbnRfYW5kX2FfY3Jp
print(raw3)  # k3:c2lzX29mX2ZhaXRofQ==

# Strip the k{n}: prefix before concatenating
p1 = raw1.split(':', 1)[1]
p2 = raw2.split(':', 1)[1]
p3 = raw3.split(':', 1)[1]

flag = base64.b64decode(p1 + p2 + p3).decode()
print(flag)  # DEADROP{sigint_osint_and_a_crisis_of_faith}

Document order [1], [2], [3] as stated in freq_logs.txt is the correct concatenation order.


The Lore

The three documents tell a coherent SIGINT story once you read them:

  • Document 1 captures encrypted radio bursts from NW-ARRAY-7, recovers partial coordinates (48.1N, 17.1E) and notes operator callsign WX-RAVEN
  • Document 2 shows a callsign registry: WX-RAVEN is PRIORITY/ACTIVE, identity redacted, handler is KOVACS
  • Document 3 gives full coordinates (48.1486N, 17.1077E, Bratislava), confirms NIGHTINGALE asset and 72-hour extraction window

The "key fragments" framing is in-universe justification for why each image hides part of the flag, they're supposedly distributed key material for decrypting the radio bursts.


Key Takeaways

1. LSB steganography is invisible to the eye. A 1-bit change in an 8-bit value is a 0.4% brightness difference. Against noisy paper texture it is completely imperceptible. The only way to find it is to look at the bit plane directly.

2. Read the documentation. freq_logs.txt tells you the order and through Okafor's note, the method (don't look, run a script). This is a forensics challenge that rewards careful reading as much as technical skill.

3. zsteg is your friend. For any PNG forensics challenge, zsteg is the first tool to run. It tests every combination of bit plane, channel, and byte order and reports anything that looks like text. It would have found all three fragments in under a second.

4. Length-prefixed payloads are robust. The 2-byte length prefix means the extractor knows exactly how many bits to read and doesn't have to guess at null terminators or padding. If you're implementing your own LSB scheme, prefix your payload with its length.


Flag

DEADROP{sigint_osint_and_a_crisis_of_faith}

< Back to All Writeups