Encoders API
Encoders API
app/encoders/ is a pluggable, post-compile encoding framework. Encoders operate on the compiled shellcode bytearray and return a complete NASM program (decoder stub + encoded payload) plus metadata.
AbstractEncoder
app/encoders/base.py defines the abstract interface every encoder implements. Subclasses must provide:
name(property) — short unique identifier, e.g."xor"description(property) — one-line human descriptionencode(payload, os_, arch, **params)— returns(nasm_code, metadata)list_params()— returns a list of{name, type, required, default, description}dicts
EncoderRegistry
app/encoders/registry.py is a singleton (app/utils/singleton.py) holding all registered encoders.
| Method | Description |
|---|---|
register(cls) | Instantiate and register an encoder class; raises TypeError if it is not an AbstractEncoder subclass and ValueError if the name is already taken. |
get(name) | Return a registered encoder by name; raises KeyError if unknown. |
list_all() | Return {name, description, params} for every encoder (MCP list_encoders / CLI discovery). |
names() | Return a sorted list of registered encoder names (CLI --enc choices). |
The built-in encoders are auto-registered on import of the app.encoders package (app/encoders/__init__.py).
xor — XorEncoder
Single-byte XOR. payload[i] ^ key.
key(int, optional) — XOR key0x01–0xFF. Omitted: auto-selects the first byte not present in the payload (null-free guarantee).0x00is rejected; a key that would produce null bytes raisesValueError.- Decoder stub:
templates/decoders/{os}_{arch}/xor_decoder.asm(jmp/call/pop pattern).
from app.encoders import XorEncoder
enc = XorEncoder()
nasm, meta = enc.encode(payload, "posix", "64") # auto key
nasm, meta = enc.encode(payload, "posix", "64", key=0x41)
chunk — ChunkEncoder
Sentinel replacement. Every 0x00 byte is replaced with a sentinel byte.
sentinel(int, optional) — the replacement byte. Omitted: prefers0x90(NOP) if unused, else the first unused byte. A value already present in the payload raisesValueError;0x00is rejected.- Decoder stub:
templates/decoders/{os}_{arch}/chunk_decoder.asm.
from app.encoders import ChunkEncoder
enc = ChunkEncoder()
nasm, meta = enc.encode(payload, "win", "64") # auto sentinel
nasm, meta = enc.encode(payload, "win", "64", sentinel=0x90)
Encoder metadata
| Key | Present in | Meaning |
|---|---|---|
encoder | both | Encoder name (xor or chunk) |
original_size | both | Byte length of the input payload |
encoded_size | both | Byte length of the encoded payload |
null_free | both | True if the encoded payload contains no 0x00 bytes |
key | xor | The XOR key used (hex string) |
sentinel | chunk | The sentinel byte used (hex string) |
null_count | chunk | Number of 0x00 bytes replaced |
Writing a custom encoder
Subclass AbstractEncoder, implement the four members, then register it:
from app.encoders import EncoderRegistry
from app.encoders.base import AbstractEncoder
class Rot13Encoder(AbstractEncoder):
name = "rot13"
description = "Illustrative placeholder encoder."
def encode(self, payload, os_, arch, **params):
return "db 0x00", {"encoder": self.name}
def list_params(self):
return []
EncoderRegistry().register(Rot13Encoder)
Once registered, the encoder is available via --enc rot13 (CLI), the MCP encode_shellcode tool, and EncoderRegistry().get("rot13").