Skip to content
shellcodes

Bad-character filters: presets are a start, not the contract

How null and alphanumeric presets map to real injection channels, and when you must build a custom bad-char list.

Published on 2 min read

Presets exist because beginners drown in hex. Experts still need them as baselines, not as gospel.

shellcodes exposes presets like null avoidance and all-non-alpha constraints. They encode common pain. They do not read your exploit's soul.

What the null preset assumes

"No null bytes" targets strcpy-class delivery and some overflow chains. It does not automatically save you from:

  • newline filtering in HTTP
  • UTF-8 normalization in web apps
  • DNS-safe charset requirements (yes, that happens)

What all-non-alpha assumes

Alphanumeric-friendly shellcode helps specific exploit tricks (register constraints, printable-only channels). Tradeoff: size and decoder complexity.

If your channel allows high bytes except a handful, forcing all-non-alpha is self-harm.

Building a custom list

Start from the parser that touches your bytes:

  1. Read the vulnerable code path
  2. List every forbidden value with reason (terminator, delimiter, escape)
  3. Add toolchain constraints (encoder must not emit 0x25 if your loader uses percent decoding)

Order matters when encoders run sequentially. The union of forbidden bytes should be declared up front.

Verify after each encoder

Encoders lie by omission. They remove one bad char and introduce another. Re-scan the hex grid after every pass.

UI grid vs mental model

A clickable bad-char grid is great for teaching. In production labs, I still export the list to text next to the runbook. Screenshots of toggles do not diff well in git.

Defender angle

Bad-char knowledge explains why some exploits look malformed. Printable-only decoders stand out in memory if you know the pattern.

Hot take

Spending an hour on a perfect bad-char list beats spending six hours on an encoder stack fighting the wrong constraint.

Regression test your filter

When a client patches the vulnerable parser, bad-char rules can change without fanfare. Keep a tiny fixture: input bytes, expected rejection reason, and the final shellcode hash that passed last time. Re-run it after dependency upgrades in the lab app. Catches silent behavior drift faster than redoing a full exploit chain.

Related articles

How stacked encoders change size, decoders, and bad-char profiles, plus a sane order for lab iterations before exploit integration.
Why 0x00 breaks strcpy-style delivery, how nulls sneak into reverse TCP structs, and what to do when your encoder pass lies to you.