This page covers seven introductory forensics challenges that each need more than a
single command: a byte-level repair, several decoding layers, a carve, or reading a signal.

Each challenge has progressive hints and a hidden flag, so you can try it yourself before
reading the walkthrough.

Purpose of the wave

Wave 1 is a guided tour of the first questions a forensic analyst should ask when receiving
an unfamiliar artifact: What kind of file is it really? Does its internal structure agree
with its extension? Is the interesting data visible as text, encoded, appended after the
logical end of the file, or embedded inside a standard container?

The tasks are small, but the habits scale to real investigations. A solver who checks
signatures before opening a file, inventories an archive before extracting it, and uses
format-aware tools before guessing will move through the wave quickly and avoid damaging
the evidence.

Baseline workflow

  1. Preserve the original and calculate a hash before modifying anything.
  2. Run file, xxd, strings, and format-specific metadata tools to identify the artifact.
  3. Compare the extension with the magic bytes and expected container structure.
  4. Extract into a new directory; never overwrite the only copy while repairing headers.
  5. Record the command or byte-level change that produced the answer so the result is
    reproducible.

Toolkit

Everything below runs on a Debian/Ubuntu/Kali box or WSL. Windows alternatives are noted.

Tool Install Used for
file, xxd, strings preinstalled (apt install file xxd binutils) Identifying files and reading bytes
ImageMagick apt install imagemagick Contrast stretching (convert, or magick on v7)
binwalk apt install binwalk Finding and carving embedded files
poppler-utils apt install poppler-utils pdftotext
Audacity / Sonic Visualiser apt install audacity sonic-visualiser Viewing audio waveforms and spectrograms
CyberChef browser Chaining decoders (base64, hex, ROT13)
Python 3 + Pillow pip install pillow Byte edits and pixel manipulation on any OS

Challenge catalog

Challenge Points Files Focus
Bad Magic 500 photo.png file repair
Morse Beeps 500 transmission.wav audio
Onions 500 message.txt layered encoding
Russian Doll 500 doll.zip nested archives
Shell Script 500 Flag.pdf file types
Too Dark 500 midnight.png stego
Two Faces 500 capture.png polyglot

Bad Magic

Points: 500 points · Provided: photo.png

A recovered photo won't open — the viewer just errors out. The first few bytes took damage. Put them back.

Hint 1

Look at the first 16 bytes with xxd. What should every PNG start with?

Hint 2

The rest of the file is intact: you can still see IHDR at offset 12. Only the 8-byte signature needs rewriting.

What it targets

Skill Why it matters
File signatures (magic bytes) Every format has a fixed header; a broken one is the most common "corrupt file" cause and the easiest to repair.
Minimal, reversible repairs Rewriting 8 known bytes is safer than running a recovery tool on the only copy.

Walkthrough

1. See the damage

file photo.png says data. The first bytes in xxd photo.png | head -1 are 00 00 00 00
instead of the PNG signature, but the IHDR chunk name is still at offset 12. The chunks
(IHDR, IDAT, IEND) are intact; only the signature is damaged.

2. Restore the signature

A PNG must start with 89 50 4E 47 0D 0A 1A 0A. Work on a copy and rewrite the first 8 bytes:

cp photo.png fixed.png
printf '\211PNG\r\n\032\n' | dd of=fixed.png bs=1 count=8 conv=notrunc

(The octal escapes work in both bash and sh. On Windows, use a hex editor such as HxD, or Python:)

d = bytearray(open("photo.png", "rb").read())
d[:8] = b"\x89PNG\r\n\x1a\n"
open("fixed.png", "wb").write(d)

3. Confirm and open

$ xxd fixed.png | head -1
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
$ file fixed.png
fixed.png: PNG image data, ...

The image opens and shows the flag.

Common pitfall

Running a generic "photo recovery" tool can re-encode the image or give up entirely. Once you
can see IHDR at offset 12, you know exactly which 8 bytes to rewrite and don't need one.

Show flag

Securinets{f1x_th3_m4g1c_by73s}


Morse Beeps

Points: 500 points · Provided: transmission.wav

A recording of steady beeps. Short and long. You know this one.

Hint 1

Open the file in Audacity and zoom in on the waveform. There are only two tone lengths.

Hint 2

Short = dot, long = dash, longer silences separate letters. Morse only carries letters and digits, so the flag prefix and braces are yours to add.

What it targets

Skill Why it matters
Recognising Morse from a waveform Tone-based encodings show up in radio, telephony and malware beacons alike.
Choosing between manual and automatic decoding A 16-character message is quick to read by hand; longer ones need a decoder.

Walkthrough

1. Look at it

Open transmission.wav in Audacity. The waveform is a sequence of short and long tone bursts
with regular gaps, which is Morse code.

2. Decode

Either read it off the waveform (dot = short, dash = long, wider gap = next letter) or use an
audio Morse decoder such as the adaptive one on morsecode.world, or
multimon-ng -a MORSE_CW on raw audio. The sequence is:

-.. ----- - -.. ....- ... .... -- ----- .-. ... ...-- -.-. ----- -.. ...--
 D    0   T  D    4    S   H   M    0   R   S   3    C    0    D   3

3. Wrap it

Morse has no braces or lowercase, so wrap the decoded text in the flag format.

Common pitfall

----- is the digit zero, not the letter O (---). Count the dashes: the flag uses leetspeak digits.

Show flag

Securinets{D0TD4SHM0RS3C0D3}


Onions

Points: 500 points · Provided: message.txt

An intercepted string, wrapped three times over. Peel it.

Hint 1

A trailing = is a strong sign of base64. Decode one layer at a time and look at the character set you get back.

Hint 2

Only 0-9a-f means hex. Letters in the right places but the wrong letters means a letter rotation.

What it targets

Skill Why it matters
Recognising encodings by alphabet base64, hex and ROT13 each leave a distinctive fingerprint.
Peeling one layer at a time Saving each intermediate makes mistakes easy to isolate.

Walkthrough

1. Identify the outer layer

message.txt holds one long token ending in =, which is base64.

2. Peel the layers, outermost first

base64 -d message.txt > layer2.txt       # only 0-9a-f  -> hex
xxd -r -p layer2.txt > layer3.txt        # readable shape, wrong letters
cat layer3.txt
Frphevargf{c33y_gu3_3ap0q1at_y4l3ef}

Frphevargf{ has the same shape as Securinets{, so this is ROT13. Undo it:

tr 'A-Za-z' 'N-ZA-Mn-za-m' < layer3.txt

3. Or use CyberChef

Paste the string and use Magic, or chain From Base64 → From Hex → ROT13 by hand.

Common pitfall

Decoding in the wrong order (for example ROT13 first) produces garbage that looks like a
failed decode. Always identify the outermost layer from its alphabet before applying anything.

Show flag

Securinets{p33l_th3_3nc0d1ng_l4y3rs}


Russian Doll

Points: 500 points · Provided: doll.zip

An archive inside an archive inside an archive... keep going until you hit the bottom.

Hint 1

unzip -l doll.zip shows exactly one member. What is it?

Hint 2

There are seven layers. Doing it by hand works, but a three-line loop is faster and teaches you more.

What it targets

Skill Why it matters
Recursive unpacking Droppers and packed samples often nest containers several levels deep.
Scripting a repetitive task Checking the real file type at each step is what makes the loop safe.

Walkthrough

1. Inventory before extracting

unzip -l doll.zip lists a single inner zip. Each layer contains only the next layer.

2. Unwrap every layer with a loop

The loop asks file for the real type, reads the member name from the archive itself, and
stops as soon as the result isn't a zip:

mkdir work && cp doll.zip work/ && cd work
f=doll.zip
while [ "$(file -b --mime-type "$f")" = application/zip ]; do
  next=$(unzip -Z1 "$f")      # the single member's name
  unzip -oq "$f"
  f=$next
done
cat "$f"

3. Or let binwalk do it

binwalk -Me doll.zip extracts all layers recursively (-M = matryoshka mode).

Common pitfall

Selecting "the newest file" with ls -t looks natural but fails: unzip restores the
timestamps stored in the archive, so the file you just extracted isn't necessarily the newest.
Read the member name from the archive instead.

Show flag

Securinets{unz1p_4ll_th3_w4y_d0wn}


Shell Script

Points: 500 points · Provided: Flag.pdf

It's called Flag.pdf. It is not a PDF. Run file on everything.

Hint 1

Do what the description says: file Flag.pdf. Then read the file as text.

Hint 2

A shell archive writes its payload to disk. Run file again on whatever it produces.

What it targets

Skill Why it matters
Checking every layer with file Each output can be a different format than its name suggests.
Reading a script before running it Self-extracting archives are code; auditing them first is a basic safety habit.

Walkthrough

1. Identify

$ file Flag.pdf
Flag.pdf: shell archive text

It's a self-extracting /bin/sh script (a shar), not a PDF.

2. Read it, then unpack it

head -40 Flag.pdf shows it only base64-decodes an embedded blob into secret.gz. Once
you've confirmed that, run it in a scratch directory:

mkdir shar && cp Flag.pdf shar/ && cd shar && sh Flag.pdf

(Or skip execution entirely: copy the base64 block out of the script and decode it yourself.)

3. Decompress

file secret.gz          # gzip compressed data
gunzip secret.gz && cat secret

Common pitfall

Trying to open it in a PDF reader, or running pdftotext, just fails. The extension is irrelevant; the magic bytes and content decide.

Show flag

Securinets{sh4r_1s_4_sh3ll_4rch1v3}


Too Dark

Points: 500 points · Provided: midnight.png

An all-black image. Or is it?

Hint 1

"Black" to your eyes isn't necessarily #000000 to the computer.

Hint 2

Stretch the brightness levels, or map every non-zero pixel to white.

What it targets

Skill Why it matters
Low-contrast stego Data one shade away from the background is invisible to the eye but trivial for software.
Level/contrast manipulation The same technique reveals faint text in scans and screenshots.

Walkthrough

1. It's not empty

The flag is drawn in #010101 on a #000000 background, one shade apart.

2. Stretch the contrast

Any of these works:

3. Read the flag

The text appears white on black.

Common pitfall

strings and grep find nothing: the flag is pixels, not text, so text-searching tools won't help.

Show flag

Securinets{bl4ck_0n_bl4ck_r3v34l3d}


Two Faces

Points: 500 points · Provided: capture.png

One file, two faces. It's an image. It's also a document. Each holds half the flag.

Hint 1

The image only shows half the flag. Where could a second file hide inside a PNG without breaking it?

Hint 2

binwalk capture.png, or search the bytes for %PDF.

What it targets

Skill Why it matters
File-in-file / polyglot thinking One file can carry a second complete file; viewers only show the first.
Manual carving Knowing start/end markers lets you carve without special tools.

Walkthrough

1. Open as an image

capture.png is a valid PNG and shows the first half: Securinets{0n3_f1l3_.

2. Find the second face

binwalk capture.png reports a PDF after the image data. Carve it with binwalk, or by hand
from the %PDF marker onwards:

binwalk -e capture.png
d = open("capture.png", "rb").read()
open("face2.pdf", "wb").write(d[d.index(b"%PDF"):])

3. Read the PDF

$ pdftotext face2.pdf -
second half: tw0_f0rm4ts}

Concatenate the two halves.

Common pitfall

Stopping at the image. The first half ends in _, so it's clearly incomplete, and the description says each face holds half.

Show flag

Securinets{0n3_f1l3_tw0_f0rm4ts}


Wave 1 takeaways