Description
We found this file. Recover the flag.
Setup
Download the file tunn3l_v1s10n.
wget <url>/tunn3l_v1s10nSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Identify the file format
ObservationThe file has no extension, so the name says nothing about its type. Inspect the raw bytes to establish the real format before anything else.xxd shows the first bytes start with 42 4d (ASCII 'BM'). That's the BMP signature. Rename to .bmp so image viewers will open it.bashxxd tunn3l_v1s10n | headbashcp tunn3l_v1s10n tunn3l_v1s10n.bmpExpected output
00000000: 424d 4e26 0f00 0000 0000 bad0 0000 bad0 BMN&............ 00000010: 0000 6e04 0000 3201 0000 0100 1800 0000 ..n...2.........
What didn't work first
Tried: Run 'file tunn3l_v1s10n' instead of xxd to identify the format.
The file command does correctly report a PC bitmap, but it gives you the type and nothing else, not the corrupted field values at 0x0A, 0x0E, and 0x16 that need patching. xxd shows the exact bytes and offsets the fix is computed from, so skipping it means skipping the information the repair depends on.
Tried: Run 'strings tunn3l_v1s10n' hoping to extract the flag text directly.
The flag is drawn as pixel art in the image data, not stored as an ASCII string. strings prints a few text fragments from the header or the decoy region and never the flag, because the flag exists only as coloured pixels that appear once the height field is corrected and the image renders.
Learn more
Magic bytes (file signatures) are the first bytes of a file that identify its format, independent of the extension.
42 4Dis ASCII "BM" (BMP). Other common signatures:FF D8 FF(JPEG),89 50 4E 47(PNG),25 50 44 46(%PDF). Thefilecommand uses a database of these signatures to identify files. See hex dumps for CTF for the broader cheat sheet.Step 2Open the BMP and notice the truncated image
ObservationThe BMP magic bytes check out, and the recorded file size is far larger than a small decoy image would need. So there is pixel data beyond the region the header tells viewers to render.Open tunn3l_v1s10n.bmp in an image viewer. It shows only a small portion (a decoy message) at the top. The real flag is in the lower portion of the pixel data, hidden because the BMP header lies about the height.Step 3Fix the BMP height field in a hex editor
ObservationThe header bytes at 0x0A and 0x0E both read 'ba d0', a repeating pattern that looks deliberately corrupted, and the height at 0x16 is far smaller than the file size implies. All three fields were falsified to hide the flag in the lower pixel rows.BMP stores width at file offset 0x12 and height at 0x16, both 4-byte little-endian signed integers. The current value at 0x16 (32 01 00 00 = 306) is too small. Two additional fields are corrupted in this file: the pixel data offset at 0x0A (ba d0 00 00, should be 36 00 00 00 = 54) and the DIB header size at 0x0E (ba d0 00 00, should be 28 00 00 00 = 40). Patch all three fields, then compute the correct height from the file size and width.bashxxd tunn3l_v1s10n.bmp | head -2bash# Sample corrupted header (offsets 0x00-0x1F):bash# 00000000: 42 4d 4e 26 0f 00 00 00 00 00 ba d0 00 00 ba d0bash# ^0x0A corrupt ^0x0E corruptbash# 00000010: 00 00 6e 04 00 00 32 01 00 00 01 00 18 00 ...bash# ^0x0E cont. width=0x46e height=0x132 <-- also patch 0x16bash# Fix 0x0A: ba d0 00 00 -> 36 00 00 00 (pixel data offset = 54)bash# Fix 0x0E: ba d0 00 00 -> 28 00 00 00 (DIB header size = 40)bash# Fix 0x16: 32 01 00 00 -> 52 03 00 00 (height = 850)bashvim -b tunn3l_v1s10n.bmp # then :%!xxd, edit, :%!xxd -r, :wqbash# Or use bless / hexedit for a graphical editorWhat didn't work first
Tried: Only patch the height field at 0x16 and leave 0x0A and 0x0E as-is.
With the pixel data offset left at 0xBAD0, the parser hunts for pixel rows starting at byte 47824 rather than byte 54, which is past the end of the file. Most viewers then show a blank image or refuse to open it. Correct both 0x0A and 0x0E, to 0x36 and 0x28, before fixing the height has any visible effect.
Tried: Use a steganography tool like steghide or zsteg to extract the hidden content instead of editing the header.
Nothing here is hidden by LSB steganography or a steghide passphrase. The concealment is entirely the falsified height, which makes decoders stop rendering before they reach the flag rows. steghide reports no extractable data and zsteg finds nothing meaningful, because there is no embedded payload: the pixel art is already in the raw data and only needs the decoder told the correct dimensions.
Learn more
BMP header byte offsets (all little-endian):
- 0x00-0x01:
BMsignature - 0x02-0x05: total file size in bytes
- 0x0A-0x0D: pixel data offset (start of pixel array; should be 54 = 0x36 for a basic BMP)
- 0x0E-0x11: DIB header size (should be 40 = 0x28 for BITMAPINFOHEADER)
- 0x12-0x15: image width
- 0x16-0x19: image height
- 0x1C-0x1D: bits per pixel
Computing the corrected height. The pixel data occupies
file_size - pixel_data_offsetbytes (typicallyfile_size - 54for a basic BMP). Each row iswidth * (bits_per_pixel / 8)bytes, padded up to a 4-byte boundary. So:pixel_data_size = file_size - pixel_data_offset row_bytes = ((width * bpp + 31) // 32) * 4 # 4-byte aligned height = pixel_data_size // row_bytesFor this challenge: width 0x46e (1134), bpp 24, so
row_bytes = 1134 * 3 = 3402rounded-lg to 3404. Note that the size field at 0x02-0x05 is also corrupted in this file and does not reflect the real file size; use the actual on-disk size fromwc -c tunn3l_v1s10n.bmp(approximately 2,893,454 bytes) rather than the header value. Divide the pixel-data size by row_bytes and write the result (850) at offset 0x16 in little-endian.Verification. Save and reopen the image. The top should still show the decoy line, but now the bottom rows render and the flag appears as drawn text in the previously hidden region.
- 0x00-0x01:
Interactive tools
- File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.
Flag
Reveal flag
picoCTF{qu1t3_a_v13w_2020}
Three BMP header fields are corrupted: pixel data offset at 0x0A (ba d0 -> 36 00), DIB header size at 0x0E (ba d0 -> 28 00), and image height at 0x16 (32 01 -> 52 03 = 850). Patching all three reveals the flag hidden in the lower portion of the image.