tunn3l v1s10n picoCTF 2021 Solution

Published: April 2, 2026

Description

We found this file. Recover the flag.

Download the file tunn3l_v1s10n.

bash
wget <url>/tunn3l_v1s10n
  1. Step 1Identify the file format
    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.
    bash
    xxd tunn3l_v1s10n | head
    bash
    cp tunn3l_v1s10n tunn3l_v1s10n.bmp
    Learn more

    Magic bytes (file signatures) are the first bytes of a file that identify its format, independent of the extension. 42 4D is ASCII "BM" (BMP). Other common signatures: FF D8 FF (JPEG), 89 50 4E 47 (PNG), 25 50 44 46 (%PDF). The file command uses a database of these signatures to identify files. See hex dumps for CTF for the broader cheat sheet.

  2. Step 2Open the BMP and notice the truncated image
    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.
  3. Step 3Fix the BMP height field in a hex editor
    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. Compute the right value from the file size and width, then patch with xxd or a hex editor.
    bash
    xxd tunn3l_v1s10n.bmp | head -2
    bash
    # Sample line at offset 0x10:
    bash
    # 00000010: 28 00 00 00 6e 04 00 00 32 01 00 00 01 00 18 00
    bash
    #                       width=0x46e   height=0x132 <-- patch this
    bash
    vim -b tunn3l_v1s10n.bmp   # then :%!xxd, edit, :%!xxd -r, :wq
    bash
    # Or use bless / hexedit for a graphical editor
    Learn more

    BMP header byte offsets (all little-endian):

    • 0x00-0x01: BM signature
    • 0x02-0x05: total file size in bytes
    • 0x0A-0x0D: pixel data offset
    • 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_offset bytes (typically file_size - 54 for a basic BMP). Each row is width * (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_bytes

    For this challenge: width 0x46e (1134), bpp 24, so row_bytes = 1134 * 3 = 3402 rounded to 3404. Divide the pixel-data size by that and write the result 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.

Flag

picoCTF{...}

BMP headers define the image dimensions. Shrinking the height field hides the bottom portion of the image that contains the real flag. Patch the height at file offset 0x16 to reveal it.

Want more picoCTF 2021 writeups?

Tools used in this challenge

Related reading

What to try next