Skip to main content
  1. Documentation/
  2. Developer Guide/

Building Agent Modules

Table of Contents
Agent modules are compiled payloads that agents load and execute at runtime. Unlike server modules (Python, teamserver-side), agent modules run inside agents on targets.
Not to be confused with server modules. Server modules are Python plugins on the teamserver. Agent modules are compiled binaries loaded into agents. See the Extension Points for the full taxonomy.

How It Works
#

  1. You create a compiled payload (BOF, shellcode, DLL, Python script, etc.)
  2. You write a YAML manifest describing the payload’s metadata and options
  3. You place both in the agent_modules/ directory
  4. The teamserver discovers and registers the module
  5. Operators select compatible modules and load them into agents
  6. 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:

FormatExtensionDescription
bof.oBeacon Object File (COFF object)
shellcode.binPosition-independent shellcode
dll.dllWindows DLL
coff.oCOFF object file
py.pyPython script (for dev agent)
tanto.tantoTanto 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
name: screenshot
description: Capture a screenshot of the active desktop
author: Your Name
format: bof
platforms:
  - windows
architectures:
  - x64
  - x86
privilege_required: false
mitre_attack:
  - T1113
options:
  monitor:
    type: int
    description: Monitor index (0 = primary)
    required: false
    default: 0
  format:
    type: str
    description: Image format
    required: false
    default: png

Manifest Reference
#

Required Fields
#

FieldTypeDescription
namestrUnique module name
descriptionstrHuman-readable description
authorstrModule author
formatstrFormat identifier — must match an agent’s supported_module_formats()
platformslist[str]Target platforms: windows, linux, macos
architectureslist[str]Target architectures: x64, x86, arm64, any

Optional Fields
#

FieldTypeDefaultDescription
privilege_requiredboolfalseWhether elevated privileges are needed
mitre_attacklist[str][]MITRE ATT&CK technique IDs
versionstr“1.0.0”Module version
payload_filestrauto-detectedFilename of the payload relative to module directory
optionsdict{}Options schema (same structure as server module options)

3. Directory Layout
#

Place your module in agent_modules/:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
agent_modules/
  screenshot/
    manifest.yaml
    screenshot_x64.o
    screenshot_x86.o
  keylogger/
    manifest.yaml
    keylogger_x64.o
  reverse_shell/
    manifest.yaml
    reverse_shell_x64.bin

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:

  1. Format: Module’s format must appear in agent’s supported_module_formats()
  2. Platform: Module’s platforms must include the agent’s OS type
  3. Architecture: Module’s architectures must 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:

StatusMeaning
runningModule is actively loaded and executing
completedModule finished execution normally
failedModule execution failed
unloadedModule was cleanly unloaded by operator

Task Types
#

load_module
#

Sent to agent when operator loads a module:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "task_type": "load_module",
  "payload": {
    "module_name": "screenshot",
    "module_data": "<base64-encoded-payload>",
    "module_format": "bof",
    "daemonize": false,
    "options": {"monitor": 0, "format": "png"}
  }
}

unload_module
#

Sent when operator unloads a managed module:

1
2
3
4
5
6
{
  "task_type": "unload_module",
  "payload": {
    "module_name": "screenshot"
  }
}

Discovery and Refresh
#

1
2
3
4
5
6
7
# CLI
tantoc2> agent-modules list
tantoc2> agent-modules list --agent <agent-id>  # Only compatible

# API
GET /api/v1/agent-modules/compatible/<agent_id>
POST /api/v1/agent-modules/refresh

Example: Python Module for Dev Agent
#

The dev agent supports format: py. Here’s a minimal Python agent module:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# agent_modules/my_module/payload.py
import json
import os

def run(options):
    """Entry point called by the dev agent."""
    result = {
        "hostname": os.uname().nodename,
        "user": os.getenv("USER"),
        "cwd": os.getcwd(),
    }
    return json.dumps(result)

With manifest:

1
2
3
4
5
6
7
8
9
name: my_module
description: Collect basic host information
author: Your Name
format: py
platforms:
  - linux
  - windows
architectures:
  - any