Steganography: Covert Payload Delivery via LSB PNG + Zero-Width Unicode
Validated LSB PNG — XOR-encrypted shellcode embedded in image, Defender CLEAN, ring-stack Phase 8 confirmed.
Validated SSH pipe delivery — extractor never touches disk, AMSI never fires on file, calc.exe spawned.
Validated Zero-width Unicode — 0/72 VirusTotal, Kaspersky Premium clean, Discord CDN exfil confirmed June 2026.
Why Transit Is the Hardest Part
A fully FUD (Fully Undetectable) payload on disk is a solved problem with enough obfuscation. The harder problem is getting the payload from attacker machine to target without it being scanned in transit.
Email gateways scan attachments. Web proxies scan downloads. Endpoint AV scans anything written to disk. The payload doesn't need to be detected at execution — if it's detected on landing, it never runs.
Steganography solves the transit problem. The file that crosses the network contains the payload hidden in a legitimate carrier — an image, a text message, a document. The scanner sees the carrier, not the payload.
Technique 1: LSB PNG Steganography
Least Significant Bit encoding
Every pixel in a PNG image has three colour channels: Red, Green, Blue. Each channel is one byte (0–255). The least significant bit of a byte contributes a value of 1. Changing a colour value from 200 to 201 — one bit difference — is invisible to the human eye. We use those throwaway bits to store payload data.
Capacity: 1 bit per channel × 3 channels = 3 bits per pixel. A 1000×1000 image = 1,000,000 pixels × 3 bits = 375,000 bytes of payload capacity. A typical shellcode is 300–800 bytes. The capacity margin is enormous.
The first 4 bytes of the encoded data (consuming 32 pixels / 96 channel LSBs) store the payload length as a 32-bit little-endian integer. The extractor reads this first, then reads exactly that many bytes — no guesswork required.
PNG only — never JPEG. JPEG recompression changes colour values during save, corrupting the LSBs. PNG is lossless. The bits survive transfer intact.
Critical: Encrypt before encoding
Never embed raw shellcode. msfvenom output contains well-known byte sequences (shellcode stubs, syscall sequences, string constants). Defender will flag the PNG during download if the embedded bytes match signatures — even without understanding the image format.
XOR-encrypt the shellcode with a key before encoding. The encrypted form has no recognisable signature. Any constant key works; the ring-stack implementation uses 0x5A. The matching key is hardcoded in the extractor.
Delivery pipeline — ring-stack Phase 8 (validated)
XOR each byte of raw shellcode with key 0x5A. Output has no AV signatures — statistical distribution looks like noise.
Python + Pillow: write 4-byte length header, then XOR-encrypted payload bytes into LSBs of carrier image pixels. Output: a valid PNG file, readable by any image viewer.
SCP over SSH (already open in kill chain). Defender scans the landing: PNG header, valid image structure, no PE magic bytes (MZ), no shellcode signatures. CLEAN.
Pipe the extractor script via SSH stdin to PowerShell. The script never writes to disk on the target — Defender's file scanner never fires. AMSI bypass (HWBP technique) runs first in the same pipe, then the extractor reads the PNG, XOR-decrypts, allocates memory, and executes in-memory. Shell comes back.
Validated result — ring-stack Phase 8, Windows 11, Defender RTP enabled
Shellcode extracted from PNG in-memory. XOR-decrypted at key 0x5A. Allocated with VirtualAlloc, executed via CreateThread. calc.exe spawned. Reverse shell came back.
Defender scan of PNG during SCP transfer: CLEAN. Defender during in-memory execution (with HWBP AMSI bypass active): CLEAN. Phase 8 marked COMPLETE.
Critical fix noted during development: WaitForSingleObject(thread, 0xFFFFFFFF) — use [uint32]::MaxValue in PowerShell, not the literal. PS's Int32 parser overflows on 0xFFFFFFFF, causing PS to exit immediately and kill the shellcode thread before the shell connects.
Technique 2: Zero-Width Unicode (ghost-encoder)
A completely different carrier: plain text. Unicode contains dozens of zero-width characters — glyphs that render as invisible in every major application (Discord, email, browsers, terminals). They take up bytes in storage and transit but display as nothing to the human eye.
The 16-character alphabet
ghost-encoder uses 16 zero-width Unicode characters as an encoding alphabet — zero-width space (U+200B), zero-width non-joiner (U+200C), zero-width joiner (U+200D), zero-width no-break space (U+FEFF), and 12 more variants. Each byte of payload maps to 2 zero-width characters (4 bits each). The encoded payload is interspersed through visible carrier text.
Result: the text looks completely normal — but between every visible character there are invisible payload bytes. An observer seeing "Hello, how are you today?" sees 24 visible characters and nothing else. The actual string contains 152 characters. 128 are zero-width payload.
Entropy is the key metric antivirus uses to decide "does this look suspicious?" High entropy = probably encrypted payload = flag it. Zero-width characters have extremely low entropy from the AV's perspective because they're a limited, predictable alphabet. The combined text file looks like normal Unicode with typical low entropy.
Delivery: Discord CDN exfil (validated)
The ghost-encoder test delivered payload via a Discord message in a private channel. Discord CDN uploaded the carrier text, attacker downloaded it via CDN URL, extracted the payload — 100% recovery. Because the carrier was posted as text content, not as a file attachment, there was no file-scanning step.
Validated result — ghost-encoder, Kaspersky Premium 21.25 active, 2026-06-29
64-byte calc.exe shellcode encoded into "Hello, how are you today?" carrier text. Entropy 0.12. Uploaded to Discord CDN. Downloaded and decoded: 64/64 bytes recovered, 100%. Kaspersky: CLEAN. VirusTotal: 0/72. calc.exe spawned after extraction and execution.
Comparison: Which Carrier for Which Situation
| Property | LSB PNG | Zero-Width Unicode |
|---|---|---|
| Payload capacity | 375KB+ (1000×1000 image) | ~1KB per visible sentence |
| Carrier suspicion | Image file — normal | Text message — normal |
| Delivery channel | File transfer (SCP, HTTP, email attachment) | Chat, email body, document, comment field |
| Extraction method | Python Pillow extractor (pipe, no disk) | Zero-width decoder (any language) |
| AV transit detection | Zero (no PE bytes, low entropy after XOR) | Zero (low entropy, no signatures) |
| Use in ring-stack | Phase 8 — large payload delivery | ghost_fud — covert exfil/delivery via messaging platforms |
Where This Fits in the Kill Chain
Steganography is a transit-layer technique. It doesn't affect runtime detection — once the payload executes, AMSI and ETW are the relevant controls (see HWBP Zero-Write). What it solves is the gap between the attacker's machine and the target's disk: getting the payload across without triggering network inspection, email gateway scanning, or endpoint AV on file-write.
In the ring-stack kill chain, steganography (Phase 8) is used specifically with SSH pipe delivery to avoid ever writing the extractor or the payload to disk on the target. The PNG lands, passes the file scan as an image, and is never decompressed by AV. The extractor runs in memory, never as a file.
Further Reading
- Ch9: The Last Mile — delivery architecture including HTML smuggling, LNK stagers, and domain fronting
- HWBP Zero-Write — runtime bypass once the payload is running
- AV Evasion Fundamentals — sandbox detection and static evasion context