programming/adaptixc2-dev/SKILL.md
Develop, extend, and maintain AdaptixC2 extenders (agents, listeners, services) and their template generators. Use when creating new plugins, adding commands, building beacon/listener/service implementations, writing AxScript UI, designing wire protocols, or using the Template-Generators scaffold system. Covers the full plugin lifecycle: Go plugin API (axc2 v1.2.0), AxScript forms/commands/events/menus, config.yaml wiring, Teamserver interface, protocol overlays, multi-language implant builds (Go/C++/Rust), evasion gates, and validation workflows.
npx skillsauth add aeondave/malskill adaptixc2-devInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Workflows and patterns for building extenders (agents, listeners, services) for the AdaptixC2 framework and maintaining the Template-Generators scaffold system.
AdaptixC2 has three extension points — all are Go plugins (.so, -buildmode=plugin):
| Type | Purpose | InitPlugin signature |
|------|---------|---------------------|
| Agent | Implant builder + session handler | InitPlugin(ts any, moduleDir string, watermark string) adaptix.PluginAgent |
| Listener | Network transport + agent traffic handler | InitPlugin(ts any, moduleDir string, listenerDir string) adaptix.PluginListener |
| Service | Auxiliary pipeline (wrapper, hook, tool) | InitPlugin(ts any, moduleDir string, serviceConfig string) adaptix.PluginService |
The Teamserver loads plugins via plugin.Open(), calls InitPlugin, registers commands from ax_config.axs, and stores instances in safe maps. The axc2 v1.2.0 module defines all interfaces.
<name>_agent/
├── config.yaml # extender_type: "agent"
├── ax_config.axs # AxScript UI + command definitions
├── go.mod # requires axc2 v1.2.0
├── Makefile # go build -buildmode=plugin
├── pl_main.go # InitPlugin, PluginAgent, ExtenderAgent
├── pl_build.go # GenerateProfiles, BuildPayload (one per language)
├── pl_utils.go # Wire types, crypto, helpers
├── srdi.go # (Rust only) DLL→shellcode sRDI converter
└── src_<name>/ # Implant source tree
// PluginAgent — returned by InitPlugin
type PluginAgent interface {
GenerateProfiles(profile adaptix.BuildProfile) ([][]byte, error)
BuildPayload(profile adaptix.BuildProfile, agentProfiles [][]byte) ([]byte, string, error)
CreateAgent(beat []byte) (adaptix.AgentData, adaptix.ExtenderAgent, error)
GetExtender() adaptix.ExtenderAgent
}
// ExtenderAgent — per-session handler
type ExtenderAgent interface {
CreateCommand(agentData adaptix.AgentData, args map[string]any) (adaptix.TaskData, adaptix.ConsoleMessageData, error)
ProcessData(agentData adaptix.AgentData, decryptedData []byte) error
Encrypt(data []byte, key []byte) ([]byte, error)
Decrypt(data []byte, key []byte) ([]byte, error)
PackTasks(agentData adaptix.AgentData, tasks []adaptix.TaskData) ([]byte, error)
TunnelCallbacks() adaptix.TunnelCallbacks
TerminalCallbacks() adaptix.TerminalCallbacks
PivotPackData(pivotId string, data []byte) (adaptix.TaskData, error)
}
.\agent\generator.ps1 -Name <name> -Watermark <hex8> -Protocol <proto> -Language <lang> -Toolchain <tc>CreateCommand switch cases — one case per command registered in ax_config.axsProcessData response handler — one case per response code in protocol constantsGenerateProfiles — serialize listener profiles into agent-readable config blobsBuildPayload — invoke build toolchain, return compiled binarysrc_<name>/go mod tidy && go vet ./...Select-String -Path *.go -Pattern '__[A-Z_]+__'Every command in ax_config.axs needs a matching case in CreateCommand:
func (ext *Extender) CreateCommand(agentData adaptix.AgentData, args map[string]any) (adaptix.TaskData, adaptix.ConsoleMessageData, error) {
command, _ := args["command"].(string)
subcommand, _ := args["subcommand"].(string)
switch command {
case "shell":
cmdStr, _ := args["command_line"].(string)
// Marshal into wire format, return TaskData with .Data = serialized bytes
case "download":
path, _ := args["remote_path"].(string)
// ...
}
return taskData, messageData, nil
}
func (ext *Extender) ProcessData(agentData adaptix.AgentData, data []byte) error {
var msg Message
Unmarshal(data, &msg)
for _, raw := range msg.Object {
var cmd Command
Unmarshal(raw, &cmd)
switch cmd.Code {
case COMMAND_SHELL:
Ts.TsTaskUpdate(agentData.Id, taskData)
case COMMAND_DOWNLOAD:
Ts.TsDownloadUpdate(agentData.Id, downloadData)
}
}
return nil
}
extender_type: "agent"
extender_file: "agent_<name>.so"
ax_file: "ax_config.axs"
agent_name: "<name>"
agent_watermark: "<hex8>"
listeners:
- "<NameCap><ProtoCap>" # e.g. "BeaconSpectre"
multi_listeners: false
<name>_listener/
├── config.yaml # extender_type: "listener"
├── ax_config.axs # UI form for listener creation
├── go.mod, Makefile
├── pl_main.go # InitPlugin, PluginListener
├── pl_transport.go # Network transport (HTTP, TCP, SMB, DNS, etc.)
├── pl_utils.go # Wire utilities
├── pl_crypto.go # Encrypt/Decrypt functions
├── pl_internal.go # Internal listener registration (optional)
└── map.go # Concurrent connection maps
// PluginListener — returned by InitPlugin
type PluginListener interface {
Create(name string, config string, customData []byte) (adaptix.ExtenderListener, adaptix.ListenerData, []byte, error)
}
// ExtenderListener — live listener instance
type ExtenderListener interface {
Start() error
Stop() error
Edit(config string) (adaptix.ListenerData, []byte, error)
GetProfile() ([]byte, error)
InternalHandler(data []byte) (string, error) // internal listeners only
}
.\listener\generator.ps1 -Name <name> -Protocol <proto> -ListenerType externalCreate() — parse JSON config, validate, build transportStart() — bind network, serve HTTP/TCP/DNS/etc.Ts.TsAgentCreate() → return sessionsTs.TsAgentProcessData() → get tasks → Ts.TsAgentGetHostedAll() → encrypt → respondStop() — graceful shutdownGetProfile() — serialize crypto keys + config for agent embeddinggo mod tidy && go vet ./...extender_type: "listener"
extender_file: "listener_<name>.so"
ax_file: "ax_config.axs"
listener_name: "<NameCap><ProtoCap>"
listener_type: "external" # or "internal"
protocol: "http" # transport protocol identifier
InternalHandler() processes data relayed by other agents.<name>_service/
├── config.yaml # extender_type: "service"
├── ax_config.axs # Optional UI + service commands
├── go.mod, Makefile
└── pl_main.go # InitPlugin, PluginService
type PluginService interface {
Call(operator string, function string, args string)
}
.\service\generator.ps1 -Name <name> (add -Wrapper for post-build pipeline)Call() — dispatch by function name, parse args JSONTsEventHookRegister()agent.generate to intercept and transform payloadsextender_type: "service"
extender_file: "service_<name>.so"
ax_file: "ax_config.axs"
service_name: "<ServiceName>"
service_config: |
custom_key: value
The name in ax.service_command(...) (axscript) and TsServiceSendDataClient/TsServiceSendDataAll (plugin) must match config.yaml → service_name exactly. Mismatches cause silent routing failures.
AxScript is JavaScript (Goja engine) with bridge APIs for UI, commands, menus, and events. Files are loaded from ax_file in config.yaml. Each plugin type has a different required lifecycle.
Agent .axs files must define two functions and register menus/events at top level:
// REQUIRED: Define CLI commands. Called by framework with the listener type string.
function RegisterCommands(listenerType) {
let cmd_shell = ax.create_command("shell", "Run shell cmd", "shell whoami", "Task: shell");
cmd_shell.addArgString("cmd_params", true, "Command");
let cmd_sleep = ax.create_command("sleep", "Set sleep", "sleep 5s 20");
cmd_sleep.addArgString("interval", true, "Duration");
cmd_sleep.addArgInt("jitter", false, "Jitter %");
let win = ax.create_commands_group("<agent_name>", [cmd_shell, cmd_sleep]);
let unix = ax.create_commands_group("<agent_name>", [cmd_sleep]);
// MUST return this structure:
return {
commands_windows: win,
commands_linux: unix,
commands_macos: unix
};
}
// REQUIRED: Build the "Generate Agent" dialog form.
// Receives array of listener type strings (e.g. ["BeaconHTTP", "BeaconSMB"]).
function GenerateUI(listeners_type) {
let container = form.create_container();
let layout = form.create_gridlayout();
// ... build form ...
let panel = form.create_panel();
panel.setLayout(layout);
// MUST return this structure:
return {
ui_panel: panel, // the root panel widget
ui_container: container, // framework calls container.toJson() on OK
ui_height: 400, // dialog height
ui_width: 600 // dialog width
};
}
// TOP-LEVEL: Register menus and events (imperative, outside functions)
let action_fb = menu.create_action("Download", function(files) { ... });
menu.add_filebrowser(action_fb, ["<agent_name>"]);
event.on_filebrowser_list(function(id, path) { ax.execute_browser(id, "ls " + path); }, ["<agent_name>"]);
event.on_filebrowser_disks(function(id) { ax.execute_browser(id, "disks"); }, ["<agent_name>"]);
event.on_processbrowser_list(function(id) { ax.execute_browser(id, "ps list"); }, ["<agent_name>"]);
Listener .axs files must define one function:
// REQUIRED: Build the listener configuration form.
// mode_create: true = new listener, false = viewing/editing existing.
function ListenerUI(mode_create) {
let container = form.create_container();
let layout = form.create_gridlayout();
let txtHost = form.create_textline("0.0.0.0");
let spinPort = form.create_spin();
spinPort.setRange(1, 65535);
spinPort.setValue(443);
layout.addWidget(form.create_label("Bind Host:"), 0, 0, 1, 1);
layout.addWidget(txtHost, 0, 1, 1, 1);
layout.addWidget(form.create_label("Bind Port:"), 1, 0, 1, 1);
layout.addWidget(spinPort, 1, 1, 1, 1);
container.put("host", txtHost);
container.put("port", spinPort);
let panel = form.create_panel();
panel.setLayout(layout);
// MUST return this structure:
return {
ui_panel: panel,
ui_container: container,
ui_height: 300,
ui_width: 500
};
}
Service .axs files must define three functions and end with the boot call:
var serviceName = "<ServiceNameV2>"; // must match config.yaml → service_name
var g_output_widget = null;
// REQUIRED: Called once when plugin loads.
function InitService() {
ax.log("Service loaded.");
// Load persisted settings from Go side:
ax.service_command(serviceName, "load_settings", null);
// Register menu entries:
let action = menu.create_action("Open Tool", function() { buildMainWindow(); });
menu.add_main_axscript(action);
}
// REQUIRED: Called after InitService. Usually minimal.
function ServiceUI() {
ax.log("Service UI ready.");
}
// REQUIRED: Receives async responses from Go plugin.
// Go calls TsServiceSendDataClient(serviceName, client, function, jsonArgs)
// → arrives here as a JSON string.
function data_handler(data) {
let response = JSON.parse(data);
switch (response.action) {
case "load_settings_result":
// restore settings
break;
case "compile_log":
if (g_output_widget !== null) {
g_output_widget.appendText(response.output);
}
break;
case "compile_done":
if (response.success && response.file_content) {
let path = ax.prompt_save_file(response.file_name || "output.exe");
if (path && path !== "") ax.file_write_binary(path, response.file_content);
}
break;
}
}
// ... buildMainWindow() and other functions ...
// REQUIRED: Boot statement — must be last line in the file.
ServiceUI();
Key service data flow: ax.service_command() is fire-and-forget. The Go plugin's Call() method processes the request, then pushes results back via TsServiceSendDataClient(), which arrives at data_handler(data) as JSON. There is NO request-response mechanism — all communication is asynchronous. Dispatch responses using an action field in the JSON.
Every form section uses this pattern — always groupbox.setPanel(panel), never setLayout directly on groupbox:
let grid = form.create_gridlayout();
grid.addWidget(form.create_label("Name:"), 0, 0, 1, 1);
grid.addWidget(txtName, 0, 1, 1, 1);
let panel = form.create_panel();
panel.setLayout(grid);
let grp = form.create_groupbox("Section Title", false); // false = not checkable
grp.setPanel(panel);
Pass true as second arg to make it checkable — acts like a collapsible toggle:
let grp = form.create_groupbox("Use Proxy", true); // true = checkable
grp.setPanel(panel);
grp.setChecked(false); // initially collapsed/disabled
// Toggle child enable state:
form.connect(grp, "clicked", function(checked) {
panel.setEnabled(checked);
});
// In a container, serializes as boolean:
container.put("use_proxy", grp); // → {"use_proxy": true/false}
Wrap content in a scrollarea when the form exceeds dialog height:
let mainLayout = form.create_vlayout();
mainLayout.addWidget(grp1);
mainLayout.addWidget(grp2);
mainLayout.addWidget(grp3);
let innerPanel = form.create_panel();
innerPanel.setLayout(mainLayout);
let scroll = form.create_scrollarea();
scroll.setPanel(innerPanel); // NOTE: setPanel(), not setWidget()
let outerLayout = form.create_vlayout();
outerLayout.addWidget(scroll);
// For agent/listener, return outerPanel:
let outerPanel = form.create_panel();
outerPanel.setLayout(outerLayout);
// For services, use as dialog layout directly
create_segcontrolis NOT in official docs — discovered in source code. Use with caution.
let controller = form.create_segcontrol();
controller.addItems(["Main", "Headers", "Advanced"]);
let stack = form.create_stack();
stack.addPage(panel1);
stack.addPage(panel2);
stack.addPage(panel3);
stack.setCurrentIndex(0);
form.connect(controller, "currentIndexChanged", function() {
stack.setCurrentIndex(controller.currentIndex());
});
// Standard modal dialog (blocks until closed)
let dialog = form.create_dialog("Title");
dialog.setSize(600, 400);
dialog.setLayout(layout);
dialog.setButtonsText("Save", "Cancel"); // customize button labels
let accepted = dialog.exec(); // returns true if OK/Save clicked
if (accepted) { /* persist settings */ }
// Extended dialog (for service/tool windows)
let ext = form.create_ext_dialog("Title");
ext.setSize(600, 400);
ext.setButtonsText("Close", ""); // "" hides the cancel button
ext.exec();
// Button click
form.connect(btn, "clicked", function() { /* ... */ });
// Checkbox toggle → show/hide dependent fields
form.connect(chk, "stateChanged", function() {
let checked = chk.isChecked();
lbl.setVisible(checked);
txt.setVisible(checked);
});
// Combo selection change (receives new text)
form.connect(combo, "currentTextChanged", function(text) {
chk.setEnabled(text.toLowerCase() !== "bin");
});
// Checkable groupbox toggle
form.connect(grp, "clicked", function(checked) {
panel.setEnabled(checked);
});
// Segmented control tab switch
form.connect(segctrl, "currentIndexChanged", function() {
stack.setCurrentIndex(segctrl.currentIndex());
});
// Mutual exclusion between checkboxes
form.connect(chkA, "stateChanged", function() {
if (chkA.isChecked()) { chkB.setChecked(false); chkB.setEnabled(false); }
else { chkB.setEnabled(true); }
});
// File selector gives base64 content when read through container
let fileSelector = form.create_selector_file();
fileSelector.setPlaceholder("/path/to/file.dll");
let container = form.create_container();
container.put("dll_content", fileSelector);
// Check if file was selected:
if (!container.get("dll_content")) {
ax.show_message("Error", "File is required.");
return;
}
// Extract base64 content:
let json = JSON.parse(container.toJson());
let base64Data = json.dll_content;
let cmd = ax.create_command("name", "description", "example", "task message");
// Argument types:
cmd.addArgString("name", true, "help"); // positional, required
cmd.addArgString("path", false, "help"); // positional, optional
cmd.addArgString("path", ".", "help"); // optional with default "."
cmd.addArgInt("count", true, "help"); // integer
cmd.addArgInt("count", "help", 10); // optional with default value
cmd.addArgBool("-v", "verbose"); // flag (true if present)
cmd.addArgBool("-v", "verbose", true); // flag with default bool value
cmd.addArgFlagInt("-n", "num", false, "help"); // -n <int>
cmd.addArgFile("payload", true, "help"); // file → base64
cmd.addArgFlagString("-o", "output", false, "help"); // -o <string>
cmd.addArgFlagFile("-f", "file", true, "help"); // -f <file>
cmd.addSubCommands([sub1, sub2]); // subcommand tree
// PreHook — rewrite command before execution (e.g., alias)
cmd.setPreHook(function(id, cmdline, parsed_json, ...parsed_lines) {
let real_cmd = "ps run -o C:\\Windows\\System32\\cmd.exe /c " + parsed_json["cmd_params"];
ax.execute_alias(id, cmdline, real_cmd, "Running shell via ps");
});
// PostHook — process result after agent returns (hooktask struct)
cmd.setPostHook(function(hooktask) {
// hooktask: { agent, type, message, text, completed, index }
// Modify and return; requires originating client to stay connected
return hooktask;
});
// Register per-OS command groups
let win = ax.create_commands_group("<agent>", [cmd1, cmd2, cmd_win_only]);
let unix = ax.create_commands_group("<agent>", [cmd1, cmd2, cmd_unix_only]);
ax.register_commands_group(win, ["<agent>"], ["windows"], []);
ax.register_commands_group(unix, ["<agent>"], ["linux", "macos"], []);
// Or return from RegisterCommands: { commands_windows: win, commands_linux: unix }
See references/axscript-api.md for the complete API. Key patterns:
// Context menus
menu.add_filebrowser(action, ["<agent>"]);
menu.add_session_agent(action, ["<agent>"]);
menu.add_processbrowser(action, ["<agent>"], ["windows"]);
menu.add_downloads_running(action, ["<agent>"]);
menu.add_tasks_job(action, ["<agent>"]);
// Events
event.on_filebrowser_list(handler, ["<agent>"]);
event.on_new_agent(handler, ["<agent>"]);
event.on_ready(handler);
event.on_interval(handler, seconds);
ax.execute_command(id, cmdline) — issue command to agentax.execute_command_hook(id, cmdline, hook) — execute with PostHook callback (hooktask → returns modified hooktask)ax.execute_command_handler(id, cmdline, handler) — execute with Handler callback (handlertask → void)ax.execute_alias(id, displayCmdline, actualCmd, message) — show one command, run another (4 params)ax.execute_alias_hook(id, displayCmdline, actualCmd, message, hook) — alias with PostHookax.execute_alias_handler(id, displayCmdline, actualCmd, message, handler) — alias with Handlerax.execute_browser(id, cmd) — browser commandax.service_command(svcName, function, data) — send command to Go service pluginax.agents() / ax.ids() / ax.agent_info(id, prop) — session dataax.credentials() / ax.credentials_add(user, pass, realm, type, tag, storage, host) — credential managementax.targets() / ax.targets_add(computer, domain, address, os, osDesc, tag, info, alive) — target managementax.credentials_add_list(arr) / ax.targets_add_list(arr) — bulk addax.bof_pack(types, args) — BOF argument packing (b=bytes, h=short, i=int, z=cstr, Z=wstr, B=binary)ax.console_message(id, msg, type, text) — output to consoleax.open_browser_files(id) / ax.open_browser_process(id) / ax.open_remote_shell(id) / ax.open_remote_terminal(id) — UI actionsax.show_message(title, msg) / ax.prompt_save_file(name) / ax.prompt_confirm(title, msg) — dialogsax.file_read(path) / ax.file_write_text(path, text) / ax.file_write_binary(path, b64) — file I/Oax.random_string(len, set) / ax.hash(algo, len, data) — utilitiesax.encode_data(algo, data, key) / ax.decode_data(algo, b64, key) — codecax.convert_to_code(lang, b64data, varName) — shellcode formatterax.validate_command(id, cmd) — returns {valid, message, is_pre_hook, has_output, has_post_hook, parsed}ax.agent_set_impersonate(id, impersonate, elevated) / ax.copy_to_clipboard(text) — session helpersax.log(msg) / ax.log_error(msg) — loggingax.get_project() / ax.ticks() — project info + timing| Gotcha | Detail |
|--------|--------|
| setPanel() not setLayout() | GroupBox and ScrollArea use .setPanel(panel). Never call .setLayout() on them directly. |
| getEnabled() not isEnabled() | To read enabled state, use widget.getEnabled(). This is asymmetric with widget.setEnabled(bool). |
| setItems() vs addItems() | combo.setItems([]) clears then sets. combo.addItems([]) appends. Use setItems() inside signal handlers to replace options. |
| fromJson() exists but rare | container.fromJson(jsonStr) is officially documented and recovers widget values from JSON. Manual restoration with .setText() etc. is more common in practice. |
| File selector in container | container.put("key", fileSelector) serializes file content as base64. Must JSON.parse(container.toJson()) to extract it. |
| Checkable groupbox as boolean | container.put("key", checkableGroupbox) serializes as true/false. |
| addArg* default overloads | addArgString(name, true, desc) = required. addArgString(name, false, desc) = optional. addArgString(name, ".", desc) = optional with default. Same for addArgInt(name, desc, value) and addArgBool(flag, desc, value). |
| ServiceUI() boot call | Must be the last line of a service .axs file. Without it, the service won't initialize. |
| data_handler is async-only | ax.service_command() is fire-and-forget. No return value. Go side sends results back via TsServiceSendDataClient() → data_handler(data). |
| create_dialog vs create_ext_dialog | create_dialog is in official docs. create_ext_dialog is NOT in official docs — discovered in source code. May have different blocking semantics. |
| create_segcontrol | NOT in official docs — discovered in source code. Use with caution; may be internal/undocumented. |
| Separator / spacer names | Official API: form.create_hline() (horizontal line), form.create_vline() (vertical line), form.create_vspacer() (vertical spacer), form.create_hspacer() (horizontal spacer). |
| Splitter creation | Official docs use form.create_vsplitter() / form.create_hsplitter(), not form.create_splitter(orientation). |
| PostHook requires connection | PostHook callbacks need the originating client to stay connected. Disconnection loses responses. |
The Teamserver interface (type-asserted from ts any in InitPlugin) provides all server-side operations. See references/teamserver-api.md for complete method signatures and data types.
// Agent lifecycle
Ts.TsAgentCreate(agentCrc, agentId string, beat []byte, listenerName, externalIP string, async bool) (adaptix.AgentData, error)
Ts.TsAgentProcessData(agentId string, bodyData []byte) error
Ts.TsAgentUpdateData(newAgentData adaptix.AgentData) error
Ts.TsAgentSetTick(agentId, listenerName string) error
Ts.TsAgentGetHostedAll(agentId string, maxDataSize int) ([]byte, error)
Ts.TsAgentConsoleOutput(agentId string, result adaptix.ConsoleMessageData) error
// Tasks
Ts.TsTaskCreate(agentId, cmdline, client string, data adaptix.TaskData)
Ts.TsTaskUpdate(agentId string, data adaptix.TaskData)
// Downloads
Ts.TsDownloadAdd(agentId, fileId, fileName string, totalSize int) error
Ts.TsDownloadUpdate(agentId, fileId string, data []byte) error
Ts.TsDownloadClose(agentId, fileId string) error
// Build
Ts.TsAgentBuildExecute(builderId, workingDir, program string, args ...string) error
Ts.TsAgentBuildLog(builderId string, status int, message string) error
// Services (send data back to AxScript data_handler)
Ts.TsServiceSendDataClient(serviceName, client, function, args string) error
Ts.TsServiceSendDataAll(serviceName, function, args string) error
// Events
Ts.TsEventHookRegister(event string, phase int, priority int, handler func(...)) (string, error)
AgentData.Sleep is uint (seconds) — convert from string with time.ParseDuration() then castAgentData.Pid is string — convert from int with fmt.Sprintf("%d", pid)AgentData.Os uses adaptix.OS_WINDOWS=1, OS_LINUX=2, OS_MAC=3 — never OS_MACOSTaskData.Data is []byte — serialized wire-format command payloadBuildProfile.AgentConfig is JSON string from container.toJson() in the GenerateUI formThe scaffold system at AdaptixC2-Template-Generators/ generates plugin + implant boilerplate.
# Agent (all flags required for non-interactive)
.\agent\generator.ps1 -Name <name> -Watermark a1b2c3d4 -Protocol <proto> -Language <lang> -Toolchain <tc>
# With evasion gate
.\agent\generator.ps1 -Name <name> -Watermark a1b2c3d4 -Protocol <proto> -Language <lang> -Toolchain <tc> -Evasion
# Listener
.\listener\generator.ps1 -Name <name> -Protocol <proto> -ListenerType external
# Service
.\service\generator.ps1 -Name <name>
# Service with wrapper pipeline
.\service\generator.ps1 -Name <name> -Wrapper
# Protocol scaffold
.\generator.ps1 -Mode protocol
# Crypto swap
.\generator.ps1 -Mode crypto
All must be substituted — zero survivors in output:
| Placeholder | Value | Context |
|-------------|-------|---------|
| __NAME__ | lowercase name | All files |
| __NAME_CAP__ | Capitalized | Class names |
| __WATERMARK__ | 8-char hex | Agent config |
| __PACKAGE__ | main/crypto/protocol | Go package name |
| __BUILD_TOOL__ | From toolchain YAML | Build command |
| __PROTOCOL__ | Protocol name | Wire format |
| __PROTOCOL_CAP__ | Capitalized protocol | Listener names |
| __LISTENER_TYPE__ | external/internal | Listener behavior |
Protocols live in protocols/<name>/ and can override plugin templates:
types.go.tmpl + constants.go.tmpl → merged into pl_utils.go (repackaged as main)crypto.go.tmpl → injected into src_<name>/crypto/crypto.go (repackaged as crypto)pl_main.go.tmpl → replaces base pl_main.go entirelypl_transport.go.tmpl → replaces listener transportpl_internal.go.tmpl → replaces internal listener handlerpl_build_<lang>.go.tmpl → replaces build handler for that languageimplant/ overlays → merged into implant source treeDefault safe toolchains: Go → go-standard, C++ → mingw, Rust → cargo.
Toolchain YAML format:
name: go-standard
language: go
compiler:
binary: go
build:
command: "go build"
env: { CGO_ENABLED: "0" }
flags: ["-trimpath"]
ldflags: "-s -w"
targets:
- { goos: linux, goarch: amd64, suffix: "_linux_amd64" }
- { goos: windows, goarch: amd64, suffix: "_windows_amd64.exe" }
Generated with -Evasion flag. Scaffolds evasion/ directory with a Gate interface (5 methods). Markers in templates:
// __EVASION_IMPORT__, // __EVASION_FIELD__, // __EVASION_INIT__// __EVASION_INCLUDE__, // __EVASION_MEMBER__, // __EVASION_CTOR__// __EVASION_MOD__, # __EVASION_FEATURES__ (must stay inside [features] in Cargo.toml)Without -Evasion: all markers are stripped, no evasion/ directory.
wsl bash -lc 'cd /mnt/d/Sources/AdaptixC2-Template-Generators/output/<dir> && /usr/local/go/bin/go mod tidy && /usr/local/go/bin/go vet ./...'
wsl bash -lc 'cd /mnt/d/Sources/AdaptixC2-Template-Generators/output/<dir>/src_<name> && cargo check'
Select-String -Path output\<dir>\*.go -Pattern '__[A-Z_]+__'
# Zero matches expected
create_command() in ax_config.axs → matching CreateCommand case in pl_main.goCreateCommand case → matching ProcessData handler for the responseParamsExecBof, Job, BOF_PACK, CALLBACK_AX_*) must survive protocol overlay into pl_utils.gopl_main.go.tmpl overrides must replace entirely and still pass go vet| Forbidden | Correct |
|-----------|---------|
| Edit output/ by hand in regeneration workflow | Fix template, re-generate |
| adaptix.OS_MACOS | adaptix.OS_MAC |
| SessionInfo.Sleep (string) → AgentData.Sleep (uint) | time.ParseDuration(si.Sleep) then cast |
| ProcessId (int) → AgentData.Pid (string) | fmt.Sprintf("%d", params.ProcessId) |
| # __EVASION_FEATURES__ outside [features] | Keep marker inside [features] TOML section |
| Adding command to ax_config.axs without handler | Add CreateCommand + ProcessData simultaneously |
| Module ref without implementation file | Create implementation file simultaneously |
| Stubs that compile but do nothing at runtime | Implement fully or remove entirely |
let cmd = ax.create_command("mycmd", "desc", "mycmd arg1") + add to groupCOMMAND_MYCMD constant + request/response structscase "mycmd": — marshal args into wire structcase COMMAND_MYCMD: — unmarshal response, call Ts methodstasks.go / tasks.cpp / tasks.rsgo vet, check placeholder leaks.\generator.ps1 -Mode protocol → creates protocols/<name>/crypto.go.tmpl (encrypt/decrypt), constants.go.tmpl, types.go.tmplmeta.yamlpl_main.go.tmpl / pl_transport.go.tmpl overrides if neededimplant/ if needed (Go root, C++ in cpp/, Rust in rust/)-Protocol <name>, validate with go vet.\service\generator.ps1 -Name <name> -Wrapperpl_wrapper.goagent.generate event to intercept payload after buildpl_build.go switches on Language field:
go build with cross-compilation env varsmake with MinGW/Clang toolchaincargo build with target flags, optional LLVM obfuscation via linker-plugin-ltoRust LLVM obfuscation requires: -C link-arg=-fuse-ld=lld and -C link-arg=-Wl,-mllvm,<flag> routing.
These are verified issues from real development:
void* is a GCC extension. Use __builtin_return_address(0). Use __builtin_offsetof instead of offsetof() in namespaces.-std=c++17 for C files. Extract C_SOURCES, compile separately with -std=c11.lld (not ld.bfd). Route -mllvm flags as -Wl,-mllvm,<arg>.# __EVASION_FEATURES__ must be inside the existing [features] section to avoid duplicate sections..rsrc as import padding section name — conflicts with resource injection.ax.service_command(...) name must match config.yaml → service_name exactly.github.com/Adaptix-Framework/axc2 v1.2.0data-ai
Scoped routing: Linux operator; hosts, sessions, users, services, packages, logs, containers, SSH, network paths, privilege evidence.
development
Offensive methodology for ICS/OT/SCADA environments in authorized industrial penetration testing and red team operations. Use when assessing PLCs, RTUs, HMIs, engineering workstations, historians, or field devices running Modbus, DNP3, EtherNet/IP, S7comm/S7+, Profinet, IEC 60870-5-104, BACnet, or OPC-UA. Covers passive OT network enumeration, protocol-level device interrogation, PLC coil/register read-write attacks, HMI session exploitation, historian and engineering workstation compromise, and safe escalation rules for critical infrastructure scope. Does not cover: general IT network exploitation (network-technique), physical hardware interfaces UART/JTAG/SPI (hardware-technique), wireless sensor network attacks (wireless-technique), RF/SDR signal analysis (hardware-ctf or wireless-technique), or CTF-framed ICS lab tasks (ics-ctf).
tools
Offensive methodology for authorized game security assessments, game client security research, and game-adjacent penetration testing in real-world engagements. Use when assessing game clients for cheating vulnerabilities, testing anti-cheat effectiveness, auditing game server protocols for score manipulation or economic fraud, reverse engineering game DRM or license validation, analyzing game save file protection, or assessing game mod/plugin security. Covers: process memory scanning and manipulation (Cheat Engine methodology), game binary reversing for license and DRM bypass, game network protocol analysis and packet replay, anti-cheat mechanism analysis, save file format reversing and tampering, speed hack and value injection techniques. Does NOT cover: CTF game challenges (game-ctf), game engine source code auditing (web-exploit-technique or vuln-search-technique for the backend), or general binary exploitation (pwn-ctf or reversing-technique).
development
Auth assessment: hardware/embedded methodology; UART/JTAG/SWD/SPI/I2C, firmware extraction, boot/debug paths, embedded OS evidence.