Steganography in CTF: checklist and tools

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
548
Reaction score
951
Deposit
0$
System checklist: the order of analysis of any stego-task

Russian-language guides on steganography in the CTF usually give a list of utilities: “here is steghide, here is a binwalk, here is an exiftool.” The problem is that the beginner does not understand the order of launch and the moment of transition to the next tool. This is the order I use myself.

Step 1 is to define the real file type. Expansion .png The task does not guarantee PNG. Team file mystery.png show the real format by magictes. If file return "JPEG image data" when expanding .png – already a hint: the author of the task masks the format.

Step 2 – search for text strings. strings mystery.png | grep -iE "flag|ctf|key" – rough, but unexpectedly effective filter. Sometimes the flag lies directly in the binary data with open text. The length flag is useful for filtering debris: strings -n 10 mystery.png shows only lines from ten characters.

Step 3 is metadata. exiftool mystery.png extracts EXIF, IPTC, XMP and other metafields. In the CTF flags hide in Comment, Artist, Copyright, and GPS-coordinates give a hint for geolocation tacks.

Step 4 – search for nested files. binwalk mystery.png scans the file on the signatures of other formats: ZIP, RAR, gzip, PDF, ELF. The found attachment is extracted through binwalk -e mystery.png to a separate directory.

Step 5 – LSB analysis. For PNG and BMP – zsteg mystery.png checks the younger bits of all channels. Visual analysis of bit planes - through Stegsolve (Java application with GUI).

Step 6 – password-based steganography. steghide extract -sf mystery.jpg tries to extract data from JPEG or WAV. If there is a password, the brutforce through stegseek or stegcracker with the dictionary rockyou.txt.

Order is not accidental: each next step is more expensive in time. file and strings - seconds. exiftool - seconds. binwalk - seconds. zsteg - seconds. But the steghide broth fort with rockyou.txt is a clock. Running it first is a waste of time on CTF, where every minute counts.
Exiftool: image metadata as the first frontier of analysis

exiftool is a working horse for reading metadata from files of almost any format. In forensics CTF assignments, he does two things: finds a flag hidden in the metafields and provides clues for further analysis.

Extended Challenge exiftool -v2 target.jpg includes the output of all tags, including non-standard. In CTFs, data is hidden in fields that are not displayed in a regular viewer:

Comment / UserComment — text fields ideal for a flag string
XMP-dc:Description is a part of XMP metadata that beginners systematically miss
GPS Position – coordinates in the format 50 deg 27' 4.68" N, 30 deg 31' 24.12" E (for decimal format: exiftool -c '%.6f' target.jpg; to drive in Google Maps - get the name of the city-flag)
Thumbnail Image – the built-in thumbnail may contain a completely different picture

The nuance on which many stumble: PNG does not formally support EXIF in the classical sense (unlike JPEG), but stores metadata in text chanks - tEXt, zTXt, iTXt. exiftool reads them. If Windows through standard “Properties” shows empty fields for PNG, it does not mean that there is no metadata. On root-me.org there is a classic task of level 5 points, where GPS coordinates from PNG metadata lead to the city-flag, and Windows-properties of the file look empty.

Another diagnostic technique is the analysis of inconsistencies. If the exiftool shows Software: Adobe Photoshop for a file that according to the legend of the task "shot on the phone" is a modification marker. The abnormal size is also informative: PNG 100×100 pixels weighing 2 MB almost certainly contains additional hidden data in files. The picture 100 per 100 can not weigh so much - so, inside something extra.
Binwalk: extracting files from images and archives

binwalk was originally created for the analysis of IoT device firmware, but caught on in CTF steganography thanks to a vast database of file signatures. Its task is simple – to find inside one file the signatures of other formats.

Baseline scenario: binwalk target.png scans the file and displays the table of the found signatures with offsets. When the archive is hidden inside the picture, the output looks something like this:

DECIMAL HEXADECIMAL DESCRIPTION
0 0x0 PNG image, 800 x 600, 8-bit RGBA
1024567 0xFA037 Zip archive data, v2.0 to extract
1025891 0xFA563 End of Zip archive

The detected attachment is extracted by the team binwalk -e target.png the Directory _target.png.extracted/. For recursive extraction (archive inside the picture, inside the archive - another picture, doll) there is a flag -M: binwalk -eM target.png passes all levels of nesting.

The most frequent reception of CTF job holders is file concatenation. Takes JPEG and to it team cat image.jpg archive.rar > challenge.jpg the RAR archive is added. The viewer displays a normal picture (he reads the data before the end of JPEG marker 0xFFD9 and ignores the remainder), and the binwalk sees both signatures.

But the binwalk has a weak point: if the magic bytes of the nested file are intentionally corrupted (as in the case from the entry — replacement 0x50 on 0xFF in the first bayt of the ZIP signature), the scanner will not detect the beginning of the archive. In such cases, the binwalk will only show the “End of Zip archive” without beginning. This is a signal for manual analysis of the hex-dump - more in the section about appended data.
LSB steganography: zsteg and stegsolve for data extraction from PNG

LSB (Least Significant Bit) is the most common method of steganography in CTF assignments. Principle: in each pixel, the image of the younger bit of the color channel is replaced by a bit of a secret message. The visual difference is indistinguishable (changes in brightness by 1 of 255), but the data is extracted programmatically.

zsteg is my first choice for detecting LSB steganography in PNG and BMP. Installation through gem install zsteg, launch: zsteg target.png. The utility automatically checks all combinations of channels (R, G, B, A) and bit orders (MSB/LSB). When the data is found, the conclusion looks like this:

b1,rgb,lsb,xy .. text: "flag{hidden_in_plain_sight}"
b1,r,lsb,xy .. text: "flag{hidden_in_plain_sight}"
b2,bgr,msb,xy .. file: data

Designation b1,rgb,lsb,xy decrypted: bit 1 (younger), RGB channels, LSB order, pixel bypass on X then Y. This is the most standard configuration of LSB steganography in CTF.

If zsteg does not give results, take Stegsolve. This is a graphical Java application for manual viewing of the bit planes of each channel. Sometimes in the red channel (Red plane 0) is hidden QR code or text, which zsteg did not recognize as a string, but the eye sees instantly. Stegsolve downloads from GitHub (eugenekolo/sec-tools repository) and launched through java -jar stegsolve.jar.

Principle Limitation: zsteg only works with PNG and BMP. For JPEG LSB-steganography is used extremely rarely - compression with losses destroys the younger bits at every preservation. If the task gives JPEG and hints at LSB - check file, perhaps the real format is different.

For non-standard cases (data is written in one channel, in reverse order bit or with arbitrary displacement) a manual Python script with Pillow will be useful:

from PIL import Image
img = Image.open('target.png').convert('RGB') # convert ensures RGB mode for P/L/RGBA images
px = img.load()
bits = []
for y in range(img.height):
for x in range(img.width):
r, g, b = px[x, y]
bits.extend([r & 1, g & 1, b & 1])
bits = bits[:len(bits) - len(bits) % 8] # trim to multiple of 8
data = bytes(int(''.join(map(str, bits[i:i+8])), 2)
for i in range(0, len(bits), 8))
print(data[:200])

The script only extracts LSB from RGB channels. If the data is hidden in the alpha channel (which zsteg checks automatically), replace convert('RGB') on convert('RGBA') and add the fourth value processing. This is the starting point - further experiment with combinations of channels and the order of circumvention of pixels.
Steghide: how to use for extraction and brutforce passwords

steghide is an old good tool for steganography, working with four formats: JPEG, BMP, WAV and AU. Unlike LSB methods, steghide uses its own encryption algorithm, and without a password, the data simply does not pull out.

Basic team: steghide extract -sf target.jpg. If the data is built in without a password, it is enough to press Enter when requesting passphrase. According to hoppersropers.org, stegide in conjunction with stegsolve decides up to 90% of entry-level stego-tasks - job authors often do not set a password or use an empty line. Do not click Enter on the passphrase request is one of the most frequent mistakes of beginners. Seriously, I've seen it on every second CTF.

When the password is set, there is a broth force. Two tools to choose from:

stegcracker – screwd over steghide for vocabulary attacks. Team: stegcracker target.jpg /usr/share/wordlists/rockyou.txt. Less – speed: steghide performs a complete transcript at each attempt, so overkill rockyou.txt (about 14 million records) stretches for the clock. In write-up on TryHackMe (CTF-BasicSteganography) separately warn: "This specific action can take hours".

stegseek – a quick alternative that works on orders faster due to the direct analysis of the steghide format without a complete call of the utility for each attempt. The same rockyou.txt is processed in minutes instead of hours. The launch is similar: stegseek target.jpg rockyou.txt. On Kali is placed through apt, for other distributions - assembly from the sources on GitHub. If there is a choice between stegcracker and stegseek - take stegseek, you will not regret it.

Frequent trap: the task gives PNG, and the participant tries to run steghide. The utility does not support PNG – only JPEG, BMP, WAV and AU. PNG needs zsteg, stegsolve or openstego. This error is worth a minute of debugging on every second CTF.
Steganography in audio files: spectrograms and hidden frequencies

Audio steganography in CTF is a separate category where visual tools are more important than auditory. Two main vectors: an image encoded in a spectrogram, and LSB data in WAV samples.

Spectrogram is the most “classic” audio stego. The author of the task encodes the text or QR code in the high-frequency range of the WAV file. By ear is silence or slight noise. On the spectrogram is a readable message. It's beautiful when you see it for the first time.

Tools: Sonic Visualiser (more control over parameters – recommend for CTF) or Audacity (switch the view of the track to “Spectrogram”). Key detection settings: Increase the upper frequency limit to 20-22 kHz (flags are often hidden in the 18-20 kHz range inaudible for most people) and select a logarithmic scale. Without these settings, the message will be invisible even on the spectrogram – I myself spent half an hour myself until I guessed to steep the upper border.

LSB in WAV. WAV is an uncompressed format, each audio sample is stored as a 16-bit number. The younger bit of each sample can carry useful data. WavSteg (pip install WavSteg) extracts LSB: python3 -m WavSteg -r -i target.wav -o output.txt -n 1 Takes one junior bat from each sample.

MP3 and compressed formats. Loss compression destroys LSB data, so other methods are used for MP3: ID3 metadata (checked through exiftool target.mp3), hidden frames or steganography at the level of coding parameters. In CTF MP3-steganography is noticeably less common than WAV.

Practical prompt: if the task gives the audio file suspiciously short duration (1-3 seconds) with a large size, the data is probably hidden not in audio, but glued to the file. Launch binwalk and strings before opening the audio editor.
Appended data and magic bytes: hidden data abroad file

This technique is found on CTF regularly, and it also causes the most stupor in beginners. Browsers and image viewers read the file to the end-format marker (for JPEG – 0xFFD9, for PNG - chank IEND) and ignore everything that after. The author of the task finishes the archive, text or other image after the end marker - visually the file looks normal.

Detection: Binwalk will show the second signature. If the binwalk only sees “End of Zip archive” without starting, magic bytes are damaged. In writeup on dominicbreuker.com describes a characteristic case: ZIP glued to JPEG, the first byte PK (hex 50 4B 03 04) replaced by FF 4B 03 04. The stegdetect utility signals: appended(227)<[nonrandom][data]>, and stegoVeritas prints raw bytes of trailing data in which the line is read twice flag.txt.

Algorithm for suspected appended data with damaged signatures:

View file tail: xxd target.jpg | tail -30
Find the end marker JPEG (FF D9) and check the bytes after it
Checking bytes with known signatures – ZIP begins with 50 4B 03 04, RAR – with 52 61 72 21, gzip — with 1F 8B
Correct damaged bytes in the hex editor: hexedit target.jpg
Cut the nested file: dd if=target.jpg of=extracted.zip bs=1 skip=<offset> or automatically through foremost -i target.jpg

For PNG, there is another clever method of concealment – underreporting the height in the IHDR header. The real picture contains more pixel strings than the header is listed, and the bottom is simply not drawn. Detection: pngcheck -v target.png will show a warning about the discrepancy between the declared size and the real amount of IDAT data. Fix: increase the height in the bytes of the 4-7 block of the IHDR and recalculate the CRC block. It sounds scary, but in practice - a couple of minutes in the hex editor.
 
Top Bottom