Descripción

Los extenders son el mecanismo de extensión principal de JustC2. Se compilan como bibliotecas compartidas de Go (.so) y se cargan dinámicamente al iniciar el servidor. Hay dos tipos de extenders:

  • Listener — Implementa un protocolo de comunicación con agentes
  • Agent — Implementa la generación y manejo de un tipo de agente

Estructura de un extender

Archivos requeridos

Cada extender necesita:

mi_extender/
├── Makefile          # Compilación del plugin
├── config.yaml       # Configuración del extender
├── ax_config.axs     # Script de configuración AxScript
├── plugin.go         # Código principal del plugin
└── src_*/            # (Solo agentes) Código fuente del agente

config.yaml

Para un listener

extender_type: "listener"
extender_file: "mi_listener.so"
ax_file: "ax_config.axs"

listener_name: "MiListener"
listener_type: "external"   # "external" o "internal"
protocol: "http"             # Protocolo identificador

Para un agente

extender_type: "agent"
extender_file: "mi_agente.so"
ax_file: "ax_config.axs"

agent_name: "mi_agente"
agent_watermark: "abcd1234"   # Identificador único de 8 chars hex
listeners:
  - "MiListener"              # Listeners compatibles
multi_listeners: false         # Si soporta múltiples listeners

Interfaces del módulo axc2

Los extenders implementan interfaces definidas en el módulo justc2/axc2:

PluginListener

type PluginListener interface {
    Create(name, config string, customData []byte) (
        ExtenderListener, ListenerData, []byte, error,
    )
}

ExtenderListener

type ExtenderListener interface {
    Start() error
    Edit(config string) (ListenerData, []byte, error)
    Stop() error
    GetProfile() ([]byte, error)
    InternalHandler(data []byte) (string, error)
}

PluginAgent

type PluginAgent interface {
    GenerateProfiles(profile BuildProfile) ([][]byte, error)
    BuildPayload(profile BuildProfile, agentProfiles [][]byte) ([]byte, string, error)
    CreateAgent(beat []byte) (AgentData, ExtenderAgent, error)
    GetExtender() ExtenderAgent
}

ExtenderAgent

type ExtenderAgent interface {
    CreateCommand(AgentData, map[string]any) (TaskData, ConsoleMessageData, error)
    PackTasks(AgentData, []TaskData) ([]byte, error)
    ProcessData(AgentData, []byte) error
    Encrypt([]byte, []byte) ([]byte, error)
    Decrypt([]byte, []byte) ([]byte, error)
    PivotPackData(string, []byte) (TaskData, error)
    TunnelCallbacks() TunnelCallbacks
    TerminalCallbacks() TerminalCallbacks
}

Desarrollo de un listener

Paso 1: Crear el plugin

package main

import "justc2/axc2"

type MiListenerPlugin struct{}

func (p *MiListenerPlugin) Create(name, config string, customData []byte) (
    axc2.ExtenderListener, axc2.ListenerData, []byte, error,
) {
    listener := &MiListener{name: name}

    data := axc2.ListenerData{
        Name:     name,
        Protocol: "mi_protocolo",
        Type:     "external",
        Status:   "active",
    }

    return listener, data, nil, nil
}

// Exportar el plugin
var PluginListener MiListenerPlugin

Paso 2: Implementar ExtenderListener

type MiListener struct {
    name string
}

func (l *MiListener) Start() error {
    // Iniciar el listener (abrir puerto, etc.)
    return nil
}

func (l *MiListener) Stop() error {
    // Detener el listener
    return nil
}

func (l *MiListener) Edit(config string) (axc2.ListenerData, []byte, error) {
    // Modificar configuración
    return axc2.ListenerData{}, nil, nil
}

func (l *MiListener) GetProfile() ([]byte, error) {
    // Retornar perfil de transporte
    return nil, nil
}

func (l *MiListener) InternalHandler(data []byte) (string, error) {
    // Manejar datos internos (para listeners bind)
    return "", nil
}

Paso 3: Compilar

go build -buildmode=plugin -o mi_listener.so plugin.go

Desarrollo de un agente

Los extenders de agente son más complejos ya que deben:

  1. Generar perfiles de transporte a partir de los listeners
  2. Compilar el payload del agente
  3. Procesar los datos que el agente envía
  4. Crear comandos para enviar al agente

Generación de payloads

func (p *MiAgentePlugin) BuildPayload(
    profile axc2.BuildProfile,
    agentProfiles [][]byte,
) ([]byte, string, error) {
    // Compilar el agente con los perfiles de transporte
    // Retorna: binario, nombre del archivo, error
    return binaryData, "mi_agente_linux_x64", nil
}

Procesamiento de datos

func (e *MiAgenteExtender) ProcessData(
    agent axc2.AgentData,
    data []byte,
) error {
    // Procesar datos recibidos del agente
    // Decodificar, descifrar, ejecutar callbacks
    return nil
}

Registro de endpoints

Los extenders pueden registrar endpoints HTTP adicionales a través del teamserver:

// Endpoint autenticado (requiere JWT)
ts.RegisterEndpoint("POST", "/mi/endpoint", handler)

// Endpoint público (sin autenticación)
ts.RegisterPublicEndpoint("GET", "/publico/data", handler)

Compilación

Makefile del extender

build:
    go build -buildmode=plugin -o mi_extender.so .

dist: build
    mkdir -p dist
    cp mi_extender.so dist/
    cp config.yaml dist/
    cp ax_config.axs dist/

go.mod del extender

module mi_extender

go 1.26.5

require justc2/axc2 v0.0.0

replace justc2/axc2 => ../../../axc2

Registro en el servidor

Añade el extender al profile.yaml:

Teamserver:
  extenders:
    - "extenders/mi_extender/config.yaml"