Files & Archives

Before touching pixels or samples, treat the file as a structure. A huge fraction of "stego" challenges are really file-format tricks: something appended after the logical end, a second format hiding inside the first, or an archive masquerading as an image. None of these need pixel analysis — they need you to read the bytes.

Magic bytes and appended data

Every format starts with a magic signature (list of file signatures). file reads it to identify the type — but it only looks at the start, so anything appended after the logical end is invisible to it.

$ file target
$ tail -c 200 target | xxd      # inspect the trailer
$ binwalk target                # list embedded signatures
$ binwalk -e target             # auto-extract them

The classic: a ZIP concatenated after an image. Because unzip reads the central directory at the end of the file, an appended ZIP often just opens:

$ unzip target.png
$ 7z l target.png

Worked example: a ZIP appended to a PNG

cat.png is 4 MB but shows a small image — a size mismatch. binwalk cat.png lists a Zip archive data signature at a nonzero offset. Because the ZIP's central directory sits at the end of the file, unzip cat.png opens it directly and extracts flag.txt — no carving needed. If the ZIP is password-protected, fcrackzip -u -D -p rockyou.txt cat.png cracks weak ZipCrypto; for AES, guess the password from the challenge instead.

If binwalk misses it, carve by header/footer with foremost, or manually with dd once you know the offset:

$ dd if=target of=payload.bin bs=1 skip=$OFFSET

Polyglot files

A polyglot is valid under more than one format at once — an image you can view that is also a runnable JAR, or a PDF that is also a ZIP. file reports only one type and will not flag the trick; look for multiple magic numbers and trailing data. The common kinds:

  • Simple — a plain concatenation of two files.
  • Parasitic — one file fully contained inside another's structure.
  • Mille-feuille — layers alternated by controlling the internal structure.
  • Chimera — one body, several heads: several formats share the same data block (e.g. Zlib Deflate pixels) behind different headers, so one file renders as JPEG and PNG.
  • Schizophrenic — a single format interpreted differently by different readers (a PDF whose JavaScript some viewers run, or the Gamma image trick).
  • Angecryption — encrypting or decrypting the file yields another valid file (same or different format).

Angecryption

Corkami's file-format posters are the reference for how these are built. To generate or dissect one: mitra (python3 mitra.py a.png b.zip emits every viable polyglot of a pair) and truepolyglot (truepolyglot pdfzip --input1 payload.zip --input2 doc.pdf out.pdf).

One file, or one file per parser?

The polyglot test is simple: open the same bytes with a different tool — unzip a PNG, render a ZIP as a PDF. If a second tool succeeds, there is a second payload.

Archives (ZIP and friends)

$ zipdetails -v target.zip       # full structure
$ zipinfo target.zip
$ 7z l target.zip
  • Weak crypto: ZipCrypto leaks filenames and sizes and is plaintext- attackable; AES-256 resists. Crack short ZipCrypto passwords with fcrackzip -u -D -p rockyou.txt target.zip.
  • Known-plaintext attack (the bigger win): if you know ~12 contiguous bytes of any one entry — trivial when it is a PNG/PDF with a fixed header — bkcrack recovers the internal keys without the password and decrypts the whole archive:
$ bkcrack -C secret.zip -c inner.png -p known_prefix.bin      # recover 3 keys
$ bkcrack -C secret.zip -k <k0> <k1> <k2> -D unlocked.zip     # decrypt everything

Match the compression of your known plaintext

bkcrack attacks the compressed byte stream, not the raw file. If the target entry is Deflate-compressed, your known-plaintext bytes must be Deflate-compressed at the same level before you feed them with -p, or no keys are found (you need ≥12 known bytes, ≥8 contiguous). It recovers the keystream, not the password — but you can read the original password back from the keys with bkcrack -k <k0> <k1> <k2> -r 10 ?p.

  • Repair a broken archive with zip -FF broken.zip --out fixed.zip.
  • Evasion tricks (seen in modern challenges): concatenated central directories (7-Zip reads the first, WinRAR the last), overlapping entries, and local-header vs central-directory mismatches. Detect duplicate end-of- central-directory markers with binwalk -R "PK\x05\x06".

Documents

Office (OOXML).docx, .pptx, .xlsx are ZIPs of XML parts:

$ 7z x report.docx -o report/

Inspect word/media/ (embedded images), word/_rels/ (relationships, external resource pointers) and any custom XML parts. The same applies to .jar, .apk, .odf — they are all valid archives, so check whether the file opens with a tool other than the one its extension implies. For a macro-enabled document (.docm/.xlsm), extract the VBA without opening Office:

$ olevba --decode target.docm       # VBA source + de-obfuscation + IOCs

VBA stomping hides the real code as p-code

In a stomped document, olevba shows benign or empty source because the genuine logic exists only as compiled p-code that Office actually runs. Dump and decompile it with pcodedmp target.doc / pcode2code target.doc.

PDF — an object/stream container:

$ pdfinfo file.pdf
$ pdfdetach -list file.pdf && pdfdetach -saveall file.pdf   # embedded attachments
$ qpdf --qdf --object-streams=disable file.pdf out.pdf      # decompress for grep

Three PDF-specific hiding spots the basics miss:

  • Retained earlier revisions. More than one %%EOF marker means the PDF was incrementally updated and old (e.g. "redacted") versions are still inside. pdfresurrect recovers them: pdfresurrect -q file.pdf (count), then pdfresurrect -w file.pdf.
  • Compressed object streams hide content from grep/strings. Expand them with Didier Stevens' pdf-parser.py -O -a file.pdf, and hunt actions with pdf-parser.py --search /JS / --search /OpenAction / --search /EmbeddedFile.
  • Optional Content Groups (/OCG) are toggleable layers — one may be hidden. pdf-parser.py --search /OCG file.pdf, then enable it in a full viewer.

peepdf is a good interactive alternative.

Other containers and hiding spots

  • SVG is XML — it renders fine while hiding <script>, <!-- comments -->, <metadata>, or elements drawn outside the viewBox. Pretty-print and read it: xmllint --format file.svg | less, plus exiftool/strings; in Inkscape, Ungroup (Ctrl+Shift+G) and open the XML editor.
  • ICO / WebP / TIFF — ICO can bundle several images or embed a full PNG (icotool -x file.ico); WebP is a RIFF container (webpinfo, appended data after the RIFF size is invisible); TIFF can carry extra IFDs/tags (exiftool -a -u -g1 file.tif, tiffinfo). binwalk catches appended data in all three.
  • NTFS Alternate Data Streams — a file's content can hide in a named stream. Windows: dir /r, Get-Content file -Stream secret. From an image: Sleuth Kit icat.
  • .git directories — the flag is often a deleted commit or a dangling blob: git log --all, git fsck --lost-found then git cat-file -p <sha>, and git reflog for rewound heads.
  • QR / barcodes in a recovered file → zbarimg --raw file.png (upscale tiny codes first: convert file.png -resize 400% big.png).

Executables and binaries

An ELF / PE / Mach-O is also a carrier. Beyond the obvious (data appended after the last section, odd strings, a payload in .comment / .rodatareadelf -p .comment file, objdump -s -j .rodata file), data can hide in semantic-dual instructions: swapping an instruction for an equivalent of the same length encodes bits without changing behavior or file size. This is the Hydan idea, revived by steg86:

$ steg86 profile binary          # how many bits this binary can carry
$ steg86 extract binary.steg     # recover the hidden message

The distribution shift it leaves is invisible to strings, binwalk and file, so recognizing that an executable is the carrier is the whole trick.

Network captures (pcap)

Forensics challenges frequently ship a .pcap; the hidden file or message is in the traffic:

  • Files over HTTP/SMB/FTPtshark -nr cap.pcap --export-objects http,out/ (or smb, tftp), or Wireshark File → Export Objects. When that fails, tcpflow -r cap.pcap splits each stream, then binwalk/foremost each.
  • USB HID keyboardtshark -r usb.pcap -Y 'usb.capdata && usb.data_len==8' -T fields -e usb.capdata piped into ctf-usb-keyboard-parser. If that field comes back empty, the capture may name it usbhid.data instead — try both. Keyboard reports are always 8 bytes (modifier + reserved + up to 6 keycodes), so filter on usb.data_len==8 to drop noise.
  • DNS / ICMP exfiltration → pull the payload fields (tshark -r cap.pcap -Y dns.qry.name -T fields -e dns.qry.name) and decode the subdomain labels — usually Base32 (DNS labels are case-insensitive, so Base64 fails), sometimes hex. Dedupe first (request and response repeat each name): … | awk '!seen[$0]++'. ICMP tunnels carry bytes in data.data. Repair a truncated capture with pcapfix cap.pcap before anything else.

File carving

When a disk image or blob contains many embedded files, carve them by signature:

Tool Notes
binwalk Signature scan + -e / -Me recursive extract
foremost Header/footer carving (/etc/foremost.conf)
scalpel Config-driven carving
photorec File-type-selective recovery
bulk_extractor Carve URLs, emails, and embedded artifacts