Skip to content

Image Techniques

hemlock provides four hiding techniques for PNG images, targeting metadata injection and steganographic embedding. Image-based attacks target multimodal RAG pipelines that ingest screenshots, diagrams, scanned documents, or other visual content alongside their metadata.

Technique Overview

Technique Stealth Description
text-chunk 55 Payload in PNG tEXt Description chunk
xmp-metadata 60 Payload in XMP dc:description via PNG iTXt chunk
multi-chunk 65 Payload distributed across Title, Author, Description, Comment chunks
steganographic 90 Payload encoded in least-significant bits of pixel data

text-chunk

How It Works

PNG files support ancillary text chunks (tEXt) that store key-value metadata. hemlock injects the payload as a Description tEXt chunk inserted between the IHDR and IDAT chunks. Most image viewers ignore these chunks, but document processing pipelines that extract metadata will read them.

The technique generates a gradient PNG carrier image, then inserts a tEXt chunk:

PNG Signature
IHDR (image header)
tEXt: Description = <payload>    ← injected
IDAT (pixel data)
IEND

Framework Survival

Image metadata extraction is pipeline-dependent. Standard text-extraction frameworks (LangChain, LlamaIndex, Unstructured, Haystack) do not natively extract PNG metadata, but multimodal pipelines and custom loaders that parse image metadata will encounter the payload.

CLI Example

hemlock craft \
  --format image \
  --technique text-chunk \
  --payload override \
  --count 1 \
  --output ./image-test

Detection

Inspect PNG chunks with tools like pngcheck or Python's PIL:

pngcheck -t poisoned-text-chunk-001.png

xmp-metadata

How It Works

XMP (Extensible Metadata Platform) is an ISO standard for embedding metadata in media files. hemlock injects the payload inside XMP XML as the dc:description field, stored in a PNG iTXt (international text) chunk with the keyword XML:com.adobe.xmp.

<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:dc="http://purl.org/dc/elements/1.1/">
      <dc:description>PAYLOAD TEXT</dc:description>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>

Framework Survival

XMP metadata is widely supported by media management tools and some document processing pipelines. Pipelines using exiftool, Adobe libraries, or XMP-aware extractors will encounter the payload.

CLI Example

hemlock craft \
  --format image \
  --technique xmp-metadata \
  --payload exfiltrate \
  --count 1 \
  --output ./xmp-test

Detection

Extract XMP with exiftool:

exiftool -XMP:Description poisoned-xmp-metadata-001.png

multi-chunk

How It Works

Instead of placing the entire payload in a single metadata field, multi-chunk distributes the payload across four PNG tEXt chunks: Title, Author, Description, and Comment. Each chunk contains a fragment of the full payload, making detection harder since no single metadata field contains the complete injection.

PNG Signature
IHDR
tEXt: Title = <fragment 1>
tEXt: Author = <fragment 2>
tEXt: Description = <fragment 3>
tEXt: Comment = <fragment 4>
IDAT
IEND

The payload is split into roughly equal parts using word boundaries.

Framework Survival

Pipelines that concatenate all metadata fields during extraction will reassemble the full payload. Pipelines that extract individual fields will see only fragments, which may still influence LLM behavior depending on the payload design.

CLI Example

hemlock craft \
  --format image \
  --technique multi-chunk \
  --payload override \
  --count 1 \
  --output ./multi-test

Detection

Inspect all text chunks:

exiftool -Title -Author -Description -Comment poisoned-multi-chunk-001.png

steganographic

How It Works

The highest-stealth image technique. The payload is encoded directly into the least-significant bits (LSB) of pixel color channels. hemlock uses 2 bits per RGB channel (6 bits per pixel), encoding a magic header (0xBEEF), a 4-byte length prefix, and the payload bytes.

The encoding is invisible to human viewers because modifying the 2 least-significant bits of each color channel produces imperceptible color shifts. However, the data can be extracted by any tool that reads pixel values.

Pixel encoding (2 bits per channel):
  R: bits[0:2] of data
  G: bits[2:4] of data
  B: bits[4:6] of data

Header: 0xBEEF (magic) + uint32(payload_length) + payload_bytes

Requires lossless format

LSB steganography requires a lossless image format like PNG. JPEG compression destroys the least-significant bit patterns. All image techniques in hemlock use PNG output.

Framework Survival

Standard metadata extractors will not find the payload because it exists only in pixel data. Detection requires steganographic analysis tools or custom extraction code that reads LSB patterns.

CLI Example

hemlock craft \
  --format image \
  --technique steganographic \
  --payload override \
  --count 1 \
  --output ./stego-test

Detection

Steganographic payloads require specialized detection:

  • Statistical analysis: Chi-squared tests on LSB distributions
  • Visual inspection: Enhanced LSB plane visualization
  • Custom extraction: Read the 2 LSBs from each RGB channel and check for the 0xBEEF magic header

Image Format Notes

All image techniques produce PNG files (.png). PNG is chosen because:

  1. Lossless compression preserves both metadata chunks and LSB steganographic data
  2. Ancillary chunk support (tEXt, iTXt) provides standardized metadata injection points
  3. Go stdlib supportimage/png in the Go standard library handles encoding/decoding without external dependencies

The carrier image is a 320x240 gradient PNG with colors derived from the cover text, providing visual variety across generated documents.


Next Steps