Skip to main content

Enhance! picoCTF 2022 Solution

Examine an SVG image file to uncover a flag hidden in plain sight.

Published: July 20, 2023Updated: August 25, 2026

Description

Despite the hint to "enhance," there's no need to zoom the image; just read the text stored in the SVG file.

Run strings or open the SVG in a text editor.

Grep for lines containing > / < and strip the XML tags to reveal the embedded characters.

bash
strings drawing.flag.svg | grep ">" | cut -d '>' -f2 | cut -d '<' -f1 | tr -d '\n '

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Treat the SVG as text
    Observation
    The file is an SVG rather than a raster image like PNG or JPEG. Its content is XML markup, readable directly as text with no image processing at all.
    SVG files are XML, so you can view them as text. The flag's characters are split, space-separated, across a handful of <tspan> elements inside a single <text> element, drawn at a font-size of 0.0035px so nothing shows up visually.
    Learn more

    SVG (Scalable Vector Graphics) is an XML-based image format that describes graphics through markup rather than pixels. Unlike raster formats (PNG, JPEG), SVG files are plain text that a browser or renderer interprets - you can open one in any text editor and read the underlying structure directly.

    Because SVG is XML, it embeds arbitrary text inside <text> elements. That XML stays in the file even when the renderer hides it. Authors can shrink the font (font-size="0.1pt"), position the text outside the canvas (x="-9999"), set visibility="hidden", or use color matching the background. None of those affect the underlying XML; strings reads them verbatim.

    The challenge title references the TV trope of endlessly enhancing low-resolution images to reveal hidden detail. The joke is that the real "enhancement" is recognizing that SVG is text, not a bitmap - no image processing needed.

  2. Step 2Remove the markup
    Observation
    The flag characters are scattered across many individual XML text nodes rather than sitting on one line. cut and tr can strip the surrounding tags and join the pieces into a single readable string.
    Strip the tags, drop whitespace, and concatenate. Build it up step by step so you can sanity-check each stage.
    bash
    strings drawing.flag.svg | grep '>'
    bash
    strings drawing.flag.svg | grep '>' | cut -d '>' -f2
    bash
    strings drawing.flag.svg | grep '>' | cut -d '>' -f2 | cut -d '<' -f1
    bash
    strings drawing.flag.svg | grep '>' | cut -d '>' -f2 | cut -d '<' -f1 | tr -d '\n '
    bash
    xmlstarlet sel -t -v "//text()" drawing.flag.svg | tr -d '\n '

    Expected output

    image/svg+xmlpicoCTF{3nh4nc3d_24374675}
    What didn't work first

    Tried: Open the SVG in a browser and use the browser's built-in zoom to magnify the tiny text until it is readable.

    The text nodes carry a font size so small they render as invisible or single-pixel dots even at full zoom. The browser draws the visual output rather than the raw XML, so zooming only enlarges blank space. Reading the file as text, with strings or an editor, skips the renderer and shows the characters directly.

    Tried: Run grep 'picoCTF' drawing.flag.svg to find the flag in one shot instead of building the cut pipeline.

    The characters are spread across several XML text elements on separate lines, so no single line holds the whole flag. grep matches whole lines and returns nothing useful. The cut and tr pipeline extracts the per-character text nodes and concatenates them into one continuous string.

    Learn more

    Walk the pipeline left to right. strings ... | grep ">" filters to lines that contain at least one tag terminator - i.e. lines with text content sitting next to markup. cut -d '>' -f2 keeps everything to the right of the first >, dropping the opening tag. cut -d '<' -f1 keeps everything to the left of the next <, dropping the closing tag. What survives is the inner text. tr -d '\n ' collapses the per-character lines into one continuous string - that's your flag.

    The pipeline is fragile (multiple tags per line, attributes containing >, mixed content all break it) but fast enough for well-formed simple SVG. For anything bigger, use a real XML parser: xmlstarlet sel -t -v "//text()" drawing.flag.svg uses XPath to extract all text nodes regardless of layout. Python's xml.etree.ElementTree works too:

    import xml.etree.ElementTree as ET
    tree = ET.parse('drawing.flag.svg')
    print(''.join(t.strip() for t in tree.getroot().itertext()))
Interactive tools
  • StegallDrop any file and Stegall runs every applicable steg technique in parallel: LSB sweeps, bit planes, spectrograms, polyglot carving, metadata, whitespace decode, and a 6-layer base/ROT/XOR/zlib cascade. Recursively unpacks results and surfaces flag matches.
  • Image Metadata ViewerRead EXIF, XMP, JPEG comments, and PNG tEXt / iTXt / zTXt chunks from images entirely in the browser. Highlights flag-like values.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.

Flag

Reveal flag

picoCTF{3nh4nc3d_24374675}

Sometimes "enhancing" means simply inspecting the source.

Key takeaway

SVG is an XML vector format, so the file is plain text a renderer interprets rather than a grid of pixels. Data hidden in its text elements stays readable as markup whatever the visual trick: zero font size, off-canvas positioning, colour matched to the background. The same holds for any format that is really XML or JSON, OOXML, ODT, SVG, RSS, all of which expose their full structure to a text editor. Renderer-level obfuscation does nothing against someone reading the source.

Related reading

Useful tools for Forensics

Where to go next