Guides

Bad Characters

Finding and avoiding forbidden bytes in a payload — exact offsets, all notations.

Bad Characters

A payload that must survive a filter (a network stack, a string copy, an strcpy-shaped loader) cannot contain certain bytes. ShellSNIP treats bad characters as a set and reports where they occur, rather than performing a single hard-coded null check.

The specification notation

Every bad-character argument — the CLI --bad-chars, ShellSnip.analyze() / ShellSnip.validate() / ShellSnip.pad(), and the MCP analyze_badchars / pad_shellcode bad_chars parameter — accepts the same four notations:

NotationExample
\xNN escapes\x00\x0a
0xNN0x00,0x0a
bare hex pairs00 0a
bare hex, comma-separated00,0a

analyze() on its own never aborts a run: it reports and returns. The CLI prints the report and continues, so a payload that does contain a forbidden byte is still written for inspection — the report tells you what to fix.

The report

./generate.py --os posix -o id.raw --bad-chars '\x00\x0a' exec "id"
[+] Bad characters: 2 configured, 0 occurrence(s) in 81 byte(s)
  no bad character found
[+] Shellcode does NOT contains forbidden char !!

When bytes are found, the report gives one line per forbidden byte, with every offset:

./generate.py --os posix -o id.raw --bad-chars '\x00\x48' exec "id"
[+] Bad characters: 2 configured, 14 occurrence(s) in 81 byte(s)
  0x00 : 0 occurrence(s) at []
  0x48 : 14 occurrence(s) at [0x0, 0xa, 0xf, 0x12, 0x1c, 0x21, 0x24, 0x2e, 0x33, 0x36, 0x3d, 0x40, 0x47, 0x4a]
[!] Shellcode contains 0x48 at 14 offset(s):

Offsets are absolute positions in the payload as it will be written, so with padding applied they are the offsets in the padded buffer — which is what the target actually receives.

Python

from app.shellSnip import ShellSnip

bot = ShellSnip(os_="posix", arch="64")
bot.create("exec", {"cmd": "id", "visible": False})
bot.compile()

report = bot.analyze(r"\x00\x0a")   # -> BadCharReport: offsets and counts
bot.validate(r"\x00\x0a")           # -> bool, and prints the full hex when clean

MCP

analyze_badchars(input_data="31c0b03c000f05", bad_chars=r"\x00")

Returns the same offsets/counts breakdown. 0x00 is the default whenever the argument is omitted, everywhere.

Avoiding forbidden bytes

Two mechanisms, and they compose:

  • Encoding — --enc xor or --enc chunk transforms the payload and prepends a decoder stub. The XOR encoder picks a key that does not occur in the payload unless you supply one, which guarantees a null-free result; a key that would reintroduce null byte raises ValueError listing the offending offsets. See Encoding.
  • Padding — the filler must not introduce a forbidden byte either. The inert strategy treats bad_chars as a hard constraint and refuses an unreachable size rather than emitting a filler that violates it. See Code-Cave Padding.

Validate the final artifact, after encoding and padding: that is the byte string the target executes, and it is what validate() analyses.

Copyright © 2026