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
- Preserve the original and calculate a hash before modifying anything.
- Run
file,xxd,strings, and format-specific metadata tools to identify the artifact. - Compare the extension with the magic bytes and expected container structure.
- Extract into a new directory; never overwrite the only copy while repairing headers.
- 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, ormultimon-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. Runfileon 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:
- GIMP: Colors → Levels, drag the white point all the way down.
- ImageMagick:
convert midnight.png -level 0%,1% out.png - Stegsolve: browse the bit planes.
- Python:
from PIL import Image Image.open("midnight.png").convert("L").point(lambda v: 255 if v else 0).save("out.png")
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
- File extensions are labels, while signatures and internal structures are evidence.
- Recognise an encoding from its alphabet before decoding, and peel one layer at a time.
- Trailing bytes and embedded files are part of the artifact even when a default viewer
ignores them. - Every repair should be minimal and reversible. Changing eight header bytes is better than
running an opaque recovery program against the only copy.