Commands
This document outlines the implementation of commands in Tuoni. From a broad perspective, there are two types of commands: native commands and plugin-based commands. We will begin our explanation with native commands as they represent the simpler category.
Native commands
Native commands in Tuoni are those implemented directly within the agent. They consistently operate within the agent's process and are primarily associated with managing the agent's configuration or handling tasks that are impractical to implement as plugins. A prime example is the "die" command. This command terminates the agent process and, under no circumstances, should it be associated with any external processes or functionalities.
Plugin based commands
In Tuoni, the majority of commands are implemented as plugin-based commands, which bear a significant resemblance in structure to the listener plugin. These commands are composed of two essential parts:
-
The C2 Plugin Part: This component is responsible for integrating with the Command and Control (C2) system. It manages the configuration of the implemented command(s), parses results, and handles other related tasks.
-
ExecUnit part: This is the executable code delivered to and run by the agent. The C2 plugin generates it and the agent executes it. See ExecUnit below for details on the supported formats.
ExecUnit
An ExecUnit is the packaged executable code that a command plugin produces for the agent to run. It contains the code bytes, a declared format, an optional entrypoint, and a plugin-specific configuration blob. The agent receives the ExecUnit, loads it according to its declared format, and executes it.
For the cross-platform architecture and the distinction between commands and listeners, start with ExecUnit Execution. Command ExecUnits covers command responsibilities and lifetime, including commands packaged with listener plugins.
ExecUnit types
| Type | Description |
|---|---|
SHELLCODE_NATIVE |
Native shellcode where the agent and execution context support it. |
DOTNET_EXE |
A managed executable requiring a compatible .NET runtime. |
DOTNET_DLL |
A managed library requiring a compatible .NET runtime. |
NATIVE_LIB |
An OS-specific native library: Windows DLL, Linux/FreeBSD ELF shared object, or macOS Mach-O dynamic library. |
These are format categories, not a promise of availability on every OS. See Formats and Compatibility for the distinction between agent capabilities and available plugin artifacts.
Agent capability matching
Not every agent build supports all four ExecUnit types, and the available types may differ between self-process execution and injection into another process. The C2 resolves this automatically:
- The agent reports its supported and preferred ExecUnit types in its metadata, separately for self-process and remote-process execution.
- When a command is dispatched, the C2 intersects the command's supported types with the agent's capabilities for the chosen execution context.
- The agent's preferred ordering is respected; the first mutually supported type in that order is selected.
- If the payload configuration sets
execUnitTypeOrder, that order is reported by the agent as its self-process preference order. If a command request setsexecUnitType, the C2 validates it against both the command and the agent before accepting it. - If no compatible type exists, the command is rejected with an error.
IPC between agent and ExecUnit
Interprocess communication (IPC) lets the agent and a command ExecUnit exchange configuration, output, and outcomes. The communication mechanism is separate from the executable format and execution context:
- Pipe communication (
NAMED_PIPE): Windows uses a duplex named pipe. Linux, FreeBSD, and macOS use separate directional filesystem FIFOs. The common name does not imply an identical OS mechanism. - No IPC (
NO_IPC): This strategy provides no command back-channel. Its existence does not make it suitable for the default commands that depend on pipe communication.
See Local Communication for endpoint and stream concepts, and Agent and Command ExecUnits for the canonical message reference.
How it works in practice
- Through the C2 API, the GUI or script selects a command for the agent and configures it.
- The command's name, agent GUID, and configuration are transmitted to the C2 via the C2 API.
-
The C2 forwards the configuration to the command's plugin, which then converts it into a format understandable by the ExecUnit. The plugin returns this configuration along with the ExecUnit itself to the C2.
-
The C2 dispatches all this information to the agent through the listener plugin connection.
- The agent's core loads the received ExecUnit in itself or another process (as configured) and establishes an IPC channel to it (typically using named pipes).
- The agent's core sends the command's configuration to the ExecUnit via this channel and waits for the outcome.
- The ExecUnit executes its designated function and sends the result back to the agent's core.
- The agent's core then transmits these results back to the C2.
- The C2 utilizes the command plugin to interpret the result. The plugin provides the parsed result to the C2 in a specified format (which could be a file, a string, etc.).
- The command's results are now accessible through the C2 API.
Visual representation
Step 1
User tasks C2 to send command to the agent via C2 API. This can be done with GUI or script. User provides command configuration.

Step 2
C2 gives command configuration received via API to command plugin.

Step 3
Command plugin parses the provided configuration and gives C2 back the ExecUnit plus the configuration that should be relayed to it upon execution

Step 4
ExecUnit and configuration are sent to the agent

Step 5
Agent core parses the information sent to it and separates executable code, ExecUnit metadata, IPC configuration, and command configuration

Step 6
Agent core executes the provided ExecUnit in the configured execution context

Step 7
Agent core and executed code create a data channel (usually named pipes) and agent core relays the configuration

Step 8
Executed command code returns command results to the agent core

Step 9
Result is sent to the C2

Step 10
C2 core provides result to command plugin to parse

Step 11
Command plugin gives C2 core the parsed result

Step 12
User can now ask for command results from the C2 via API
