How It Works#
- You create a compiled payload (BOF, shellcode, DLL, Python script, etc.)
- You write a YAML manifest describing the payload’s metadata and options
- You place both in the
agent_modules/directory - The teamserver discovers and registers the module
- Operators select compatible modules and load them into agents
- The agent receives the payload and executes it
Creating a Module#
1. Write Your Payload#
Create the compiled payload in whatever format your target agent supports. Common formats:
| Format | Extension | Description |
|---|---|---|
bof | .o | Beacon Object File (COFF object) |
shellcode | .bin | Position-independent shellcode |
dll | .dll | Windows DLL |
coff | .o | COFF object file |
py | .py | Python script (for dev agent) |
tanto | .tanto | Tanto Module Spec (future) |
Any format identifier works as long as the target agent declares support for it in supported_module_formats().
2. Write the YAML Manifest#
Create manifest.yaml alongside your payload:
| |
Manifest Reference#
Required Fields#
| Field | Type | Description |
|---|---|---|
name | str | Unique module name |
description | str | Human-readable description |
author | str | Module author |
format | str | Format identifier — must match an agent’s supported_module_formats() |
platforms | list[str] | Target platforms: windows, linux, macos |
architectures | list[str] | Target architectures: x64, x86, arm64, any |
Optional Fields#
| Field | Type | Default | Description |
|---|---|---|---|
privilege_required | bool | false | Whether elevated privileges are needed |
mitre_attack | list[str] | [] | MITRE ATT&CK technique IDs |
version | str | “1.0.0” | Module version |
payload_file | str | auto-detected | Filename of the payload relative to module directory |
options | dict | {} | Options schema (same structure as server module options) |
3. Directory Layout#
Place your module in agent_modules/:
| |
If payload_file is not specified in the manifest, the registry picks the first non-YAML file in the directory.
Compatibility Filtering#
The teamserver matches modules to agents on three dimensions:
- Format: Module’s
formatmust appear in agent’ssupported_module_formats() - Platform: Module’s
platformsmust include the agent’s OS type - Architecture: Module’s
architecturesmust include the agent’s architecture
Only modules passing all three checks are offered to operators. Attempting to load an incompatible module is rejected.
Loading Modes#
Managed Mode (default)#
When loaded with daemonize=false:
- The agent allocates memory, loads the payload, and executes it
- Results flow back through the agent’s communication channel
- Long-running modules stream results over multiple check-in cycles
- The operator can cleanly unload the module
- The teamserver tracks the module: name, format, load time, status, task
Daemonized Mode#
When loaded with daemonize=true:
- The agent launches the payload independently
- If the payload is an agent, it establishes its own crypto session and registers as a new agent
- The teamserver records the parent-child relationship for topology tracking
- This is how you deploy agents through agents (e.g., loading a Shinobi shellcode through another agent)
Not all agents support daemonize — they must declare supports_daemonize=True.
Module Status Tracking#
The teamserver tracks each loaded module per agent:
| Status | Meaning |
|---|---|
running | Module is actively loaded and executing |
completed | Module finished execution normally |
failed | Module execution failed |
unloaded | Module was cleanly unloaded by operator |
Task Types#
load_module#
Sent to agent when operator loads a module:
| |
unload_module#
Sent when operator unloads a managed module:
| |
Discovery and Refresh#
| |
Example: Python Module for Dev Agent#
The dev agent supports format: py. Here’s a minimal Python agent module:
| |
With manifest:
| |