Bad Characters
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:
| Notation | Example |
|---|---|
\xNN escapes | \x00\x0a |
0xNN | 0x00,0x0a |
| bare hex pairs | 00 0a |
| bare hex, comma-separated | 00,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 xoror--enc chunktransforms 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 raisesValueErrorlisting the offending offsets. See Encoding. - Padding — the filler must not introduce a forbidden byte either. The
inertstrategy treatsbad_charsas 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.