Home > Writeups > DEADROP Forensics 5 - Field Laptop

DEADROP Forensics 5 - Field Laptop

A disk image with an encrypted hidden partition. The passphrase is GPS coordinates extracted from image EXIF data. The ChaCha20 key derivation parameters and salt are embedded in a self-describing plaintext header in the hidden region, recoverable with strings alone.

Field Laptop

Overview

A 20MB disk image. The first 10MB is a normal ext2 partition with decoy files and two geotagged photos. The second 10MB is a hidden encrypted region, no partition table entry or filesystem label. A hint in the visible partition tells you where to look. The encryption key is derived from EXIF coordinates in the photos. The encryption parameters are self-documented in the hidden region's header, no external script needed.

Step 1: Mount and explore the visible partition

sudo mount -o loop,ro,offset=0 field_laptop.img /mnt/visible
ls -la /mnt/visible/
# Documents/  Pictures/  Desktop/  .config/

Read .config/hints.txt:

Storage layout:
  visible region:    offset 0x000000  (10 MB)
  classified region: offset 0xA00000  (10 MB)

Classified region passphrase derivation:
  Read GPS from field photos in Pictures/
  Format: <lat_from_photo1>_<lon_from_photo2>

Encryption parameters are in the classified region header.
Inspect the first 128 bytes of the extracted blob.

Two jobs: get the passphrase from the photos, get the crypto params from the blob.

Step 2: Extract EXIF coordinates from the photos

exiftool /mnt/visible/Pictures/photo1.jpg | grep -i GPS
exiftool /mnt/visible/Pictures/photo2.jpg | grep -i GPS

Or with Python:

import piexif

def dms_to_decimal(dms, ref):
    d = dms[0][0]/dms[0][1]
    m = dms[1][0]/dms[1][1]
    s = dms[2][0]/dms[2][1]
    val = d + m/60 + s/3600
    return -val if ref in (b'S', b'W') else val

for fname in ['photo1.jpg', 'photo2.jpg']:
    exif = piexif.load(fname)
    gps  = exif['GPS']
    lat  = dms_to_decimal(gps[2], gps[1])
    lon  = dms_to_decimal(gps[4], gps[3])
    print(f'{fname}: lat={round(lat,4)}, lon={round(lon,4)}')

48 deg 51' 23.76" N 2 deg 21' 7.92" E

decimal = degrees + minutes/60 + seconds/3600 So: 48 + 51/60 + 23.76/3600 = 48 + 0.85 + 0.0066 = 48.8566 And: 2 + 21/60 + 7.92/3600 = 2 + 0.35 + 0.0022 = 2.3522

Results: - photo1.jpg: latitude 48.8566 (Paris) - photo2.jpg: longitude 2.3522 (Paris)

Passphrase: 48.8566_2.3522

Step 3: Extract the hidden region

dd if=field_laptop.img bs=1M skip=10 of=hidden.bin

Step 4: Read the blob header for crypto parameters

xxd hidden.bin | head -6
0000  44 45 41 44 52 4f 50 5f  48 49 44 44 45 4e 5f 56  DEADROP_HIDDEN_V
0010  44 45 41 44 52 4f 50 5f  46 49 45 4c 44 5f 4f 50  DEADROP_FIELD_OP
0020  53 5f 53 41 4c 54 5f 56  31 00 50 42 4b 44 46 32  S_SALT_V1.PBKDF2
0030  2d 53 48 41 32 35 36 20  69 74 65 72 3d 31 30 30  -SHA256 iter=100
0040  30 30 30 00 XX XX XX XX  XX XX XX XX XX XX XX XX  000.....[payload]

Or just strings hidden.bin | head:

DEADROP_HIDDEN_VDEADROP_FIELD_OPS_SALT_V1
PBKDF2-SHA256 iter=100000

Header layout:

Offset Length Content
0 16 Magic: DEADROP_HIDDEN_V
16 26 Salt (null-terminated): DEADROP_FIELD_OPS_SALT_V1
42 26 KDF string (null-terminated): PBKDF2-SHA256 iter=100000
68 4 Payload length (LE uint32)
72 16 ChaCha20 nonce
88 N Ciphertext

Step 5: Decrypt

import struct
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
from cryptography.hazmat.backends import default_backend

passphrase = b'48.8566_2.3522'
salt = b'DEADROP_FIELD_OPS_SALT_V1'   # from blob header at offset 16

data = open('hidden.bin', 'rb').read()
plen = struct.unpack_from('<I', data, 68)[0]
nonce = data[72:88]
ct = data[88:88+plen]

kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                 salt=salt, iterations=100000, backend=default_backend())
key = kdf.derive(passphrase)

cipher = Cipher(algorithms.ChaCha20(key, nonce), mode=None, backend=default_backend())
pt = cipher.decryptor().update(ct)
open('decrypted.img', 'wb').write(pt)

Step 6: Mount and read the flag

sudo mount -o loop decrypted.img /mnt/hidden
cat /mnt/hidden/flag.txt

The hidden partition also contains ops_log.txt with Nightjar field notes.

Flag: DEADROP{hidden_partition_hidden_life}

Key Takeaway

Hidden partitions don't need to appear in the partition table, they're just raw data at a known offset. The header here is self-describing by design, mimicking how real encrypted container formats (LUKS, VeraCrypt) store their parameters in-band. EXIF metadata in photos is a surprisingly common source of operational security failures in the field.

< Back to All Writeups