Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/HavocFramework/Havoc/llms.txt

Use this file to discover all available pages before exploring further.

The havoc-py library provides a Python interface to Havoc’s Service API, enabling custom agent development, automation, and extensibility.

Installation

git clone https://github.com/HavocFramework/havoc-py
cd havoc-py
pip install .
The havoc-py library requires Python 3.8 or higher.

Core Classes

HavocService

Manages the WebSocket connection to the Teamserver Service API.

Constructor

from havoc.service import HavocService

havoc = HavocService(
    endpoint="ws://0.0.0.0:40056/service-endpoint",
    password="service-password"
)
endpoint
string
required
WebSocket URL to the Teamserver Service endpoint
password
string
required
Service password (defined in Teamserver profile)

Methods

register_agent
method
Register a custom agent type with the Teamserver.
havoc.register_agent(agent: AgentType) -> None
Parameters:
  • agent (AgentType): Agent instance to register
Example:
from havoc.agent import AgentType

agent = MyCustomAgent()
havoc.register_agent(agent)
register_externalc2
method
Register an External C2 listener.
await havoc.register_externalc2(
    name: str,
    endpoint: str
) -> dict
Parameters:
  • name (str): Display name for the External C2
  • endpoint (str): HTTP endpoint path for agent traffic
Returns: Registration response dictionaryExample:
response = await havoc.register_externalc2(
    name="DNS-C2",
    endpoint="dns-transport"
)

AgentType

Base class for defining custom agent types.

Class Attributes

from havoc.agent import AgentType

class MyAgent(AgentType):
    Name = "MyAgent"                 # Agent name displayed in UI
    Author = "@YourHandle"           # Attribution
    Version = "1.0"                  # Version string
    Description = "Custom agent"     # Brief description
    MagicValue = 0xDEADBEEF         # Unique identifier (hex)
    
    Formats = [                      # Supported output formats
        {
            "Name": "Executable",
            "Extension": "exe"
        }
    ]
    
    SupportedOS = [                  # Operating systems
        "Windows",
        "Linux",
        "MacOS"
    ]
    
    BuildingConfig = {               # Build options (shown in UI)
        "Sleep": "5",
        "Jitter": "10",
        "Host": "0.0.0.0",
        "Port": "443"
    }
    
    Commands = []                    # List of Command instances
Name
string
required
Agent name displayed in the Havoc UI
Author
string
required
Author attribution (e.g., “@username”)
Version
string
required
Version string (e.g., “1.0.0”)
Description
string
required
Brief description of the agent’s purpose
MagicValue
integer
required
Unique identifier for the agent type (hexadecimal)
Formats
list
required
List of supported payload formats
[
    {"Name": "Executable", "Extension": "exe"},
    {"Name": "Shellcode", "Extension": "bin"}
]
SupportedOS
list
required
List of supported operating systems: "Windows", "Linux", "MacOS"
BuildingConfig
dict
required
Configuration options displayed in the payload generation UI
{
    "Sleep": "5",
    "Jitter": "10",
    "CustomOption": "value"
}
Commands
list
required
List of Command instances defining available commands

Methods

generate
method
Generate the agent payload.
def generate(self, config: dict) -> str:
    """
    Args:
        config: BuildingConfig values from UI
    
    Returns:
        Generated payload as string or bytes
    """
    pass
Example:
def generate(self, config: dict) -> str:
    with open('agent_template.py', 'r') as f:
        template = f.read()
    
    # Replace placeholders
    payload = template.replace('{{SLEEP}}', config['Sleep'])
    payload = payload.replace('{{HOST}}', config['Host'])
    
    return payload

Command

Defines a custom command for your agent.

Class Attributes

from havoc.agent import Command, CommandParam
from havoc.packer import Packer

class CommandShell(Command):
    CommandId = 0x100                      # Unique command ID
    Name = "shell"                         # Command name
    Description = "Execute shell command"  # Brief description
    Help = "shell <command>"               # Usage help text
    NeedAdmin = False                      # Requires admin privileges
    Mitr = ["T1059", "T1059.001"]         # MITRE ATT&CK techniques
    
    Params = [                             # Command parameters
        CommandParam(
            name="command",
            is_file_path=False,
            is_optional=False
        )
    ]
    
    def job_generate(self, arguments: dict) -> bytes:
        packer = Packer()
        packer.add_int(self.CommandId)
        packer.add_data(arguments['command'])
        return packer.buffer
CommandId
integer
required
Unique identifier for the command (e.g., 0x100)
Name
string
required
Command name as typed in the console
Description
string
required
Brief description shown in help text
Help
string
required
Detailed usage information
NeedAdmin
boolean
required
Whether command requires administrator/root privileges
Mitr
list
required
List of MITRE ATT&CK technique IDs (e.g., ["T1059"])
Params
list
required
List of CommandParam instances defining parameters

Methods

job_generate
method
Generate the binary command payload.
def job_generate(self, arguments: dict) -> bytes:
    """
    Args:
        arguments: Dictionary with parameter values
    
    Returns:
        Packed binary command data
    """
    pass
Example:
def job_generate(self, arguments: dict) -> bytes:
    packer = Packer()
    
    # Pack command ID
    packer.add_int(self.CommandId)
    
    # Pack parameters
    packer.add_data(arguments['command'])
    
    return packer.buffer

CommandParam

Defines a command parameter.

Constructor

from havoc.agent import CommandParam

# String parameter
param = CommandParam(
    name="message",
    is_file_path=False,
    is_optional=False
)

# File path parameter (shows file picker in UI)
param = CommandParam(
    name="local_file",
    is_file_path=True,
    is_optional=False
)

# Optional parameter
param = CommandParam(
    name="timeout",
    is_file_path=False,
    is_optional=True
)
name
string
required
Parameter name (referenced in arguments dict)
is_file_path
boolean
required
If True, UI shows file picker. If False, shows text input.
is_optional
boolean
required
Whether parameter is optional

Packer

Utility class for building binary command payloads.

Methods

add_int
method
Add a 32-bit integer (little-endian).
packer.add_int(value: int) -> None
Example:
packer.add_int(0x100)  # Command ID
packer.add_int(42)     # Numeric value
add_data
method
Add a null-terminated string.
packer.add_data(data: str) -> None
Example:
packer.add_data("whoami")
packer.add_data("/etc/passwd")
add_bytes
method
Add raw bytes.
packer.add_bytes(data: bytes) -> None
Example:
packer.add_bytes(b"\x00\x01\x02\x03")
packer.add_bytes(file_contents)
buffer
property
Get the packed binary data.
command_bytes = packer.buffer
Returns: bytes object with packed data

Complete Examples

Basic Shell Command

shell_command.py
from havoc.agent import Command, CommandParam
from havoc.packer import Packer

COMMAND_SHELL = 0x100

class CommandShell(Command):
    CommandId = COMMAND_SHELL
    Name = "shell"
    Description = "Execute shell commands using /bin/sh"
    Help = """
    Usage: shell <command>
    
    Executes the specified command using the system shell.
    
    Examples:
        shell whoami
        shell cat /etc/passwd
        shell ls -la /tmp
    """
    NeedAdmin = False
    Mitr = ["T1059", "T1059.004"]  # Command and Scripting Interpreter: Unix Shell
    
    Params = [
        CommandParam(
            name="command",
            is_file_path=False,
            is_optional=False
        )
    ]
    
    def job_generate(self, arguments: dict) -> bytes:
        packer = Packer()
        packer.add_int(self.CommandId)
        packer.add_data(arguments['command'])
        return packer.buffer

File Upload Command

upload_command.py
from havoc.agent import Command, CommandParam
from havoc.packer import Packer
import os

COMMAND_UPLOAD = 0x101

class CommandUpload(Command):
    CommandId = COMMAND_UPLOAD
    Name = "upload"
    Description = "Upload file to target"
    Help = """
    Usage: upload <local_file> <remote_path>
    
    Uploads a file from the operator's system to the target.
    
    Examples:
        upload /tmp/tool.sh /tmp/tool.sh
        upload implant.elf /var/tmp/.hidden
    """
    NeedAdmin = False
    Mitr = ["T1105"]  # Ingress Tool Transfer
    
    Params = [
        CommandParam(
            name="local_file",
            is_file_path=True,
            is_optional=False
        ),
        CommandParam(
            name="remote_path",
            is_file_path=False,
            is_optional=False
        )
    ]
    
    def job_generate(self, arguments: dict) -> bytes:
        packer = Packer()
        
        # Command ID
        packer.add_int(self.CommandId)
        
        # Remote path
        packer.add_data(arguments['remote_path'])
        
        # Read file
        with open(arguments['local_file'], 'rb') as f:
            file_data = f.read()
        
        # File size
        packer.add_int(len(file_data))
        
        # File contents
        packer.add_bytes(file_data)
        
        return packer.buffer

Complete Agent Example

from havoc.agent import AgentType
from commands import CommandShell, CommandPwd, CommandUpload, CommandDownload

class TalonAgent(AgentType):
    Name = "Talon"
    Author = "@HavocFramework"
    Version = "0.1.0"
    Description = "Python-based cross-platform agent"
    MagicValue = 0x5041594C
    
    Formats = [
        {
            "Name": "Python Script",
            "Extension": "py"
        },
        {
            "Name": "PyInstaller Executable",
            "Extension": "exe"
        }
    ]
    
    SupportedOS = [
        "Linux",
        "MacOS",
        "Windows"
    ]
    
    BuildingConfig = {
        "Sleep": "5",
        "Jitter": "20",
        "Host": "0.0.0.0",
        "Port": "443",
        "UserAgent": "Mozilla/5.0",
        "Secure": "true"
    }
    
    Commands = [
        CommandShell(),
        CommandPwd(),
        CommandUpload(),
        CommandDownload()
    ]
    
    def generate(self, config: dict) -> str:
        # Load template
        with open('templates/agent.py.template', 'r') as f:
            template = f.read()
        
        # Replace placeholders
        replacements = {
            '{{SLEEP}}': config['Sleep'],
            '{{JITTER}}': config['Jitter'],
            '{{HOST}}': config['Host'],
            '{{PORT}}': config['Port'],
            '{{USER_AGENT}}': config['UserAgent'],
            '{{SECURE}}': config['Secure']
        }
        
        payload = template
        for key, value in replacements.items():
            payload = payload.replace(key, value)
        
        return payload

Service API Protocol

Authentication

import json
import hashlib

def authenticate(websocket, password):
    # Hash password with SHA3-256
    hasher = hashlib.sha3_256()
    hasher.update(password.encode())
    hashed = hasher.hexdigest()
    
    # Send auth message
    auth_msg = {
        "Head": {"Type": "Register"},
        "Body": {"Password": password}
    }
    websocket.send(json.dumps(auth_msg))
    
    # Receive response
    response = json.loads(websocket.recv())
    return response["Body"]["Success"]

Message Types

message = {
    "Head": {
        "Type": "RegisterAgent"
    },
    "Body": {
        "Agent": {
            "Name": "MyAgent",
            "Author": "@me",
            "Version": "1.0",
            "MagicValue": "deadbeef",
            # ... other agent properties
        }
    }
}

Best Practices

  • Use unique command IDs starting from 0x100
  • Reserve 0x00-0xFF for system commands
  • Document your ID allocation scheme
  • Avoid ID conflicts between commands
def job_generate(self, arguments: dict) -> bytes:
    # Validate required parameters
    if 'command' not in arguments:
        raise ValueError("Missing required parameter: command")
    
    # Validate parameter types
    if not isinstance(arguments['command'], str):
        raise TypeError("Parameter 'command' must be string")
    
    # Sanitize inputs
    command = arguments['command'].strip()
    if not command:
        raise ValueError("Parameter 'command' cannot be empty")
    
    # Generate command
    packer = Packer()
    packer.add_int(self.CommandId)
    packer.add_data(command)
    return packer.buffer
def generate(self, config: dict) -> str:
    try:
        # Attempt payload generation
        payload = self._build_payload(config)
        return payload
    except FileNotFoundError as e:
        raise RuntimeError(f"Template not found: {e}")
    except Exception as e:
        raise RuntimeError(f"Payload generation failed: {e}")
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class MyAgent(AgentType):
    def generate(self, config: dict) -> str:
        logger.info(f"Generating payload with config: {config}")
        # ...
        logger.info("Payload generated successfully")
        return payload

Resources

havoc-py Repository

Official Python API source code

Talon Example

Reference implementation using havoc-py

Custom Agents Guide

Step-by-step agent development

External C2 Guide

Using havoc-py for External C2