Your first ATT&CK procedure

Part two in converting an idea into a repeatable attack

If you read our Coming To Life post over at State of Security, you learned how to take an idea and convert it into a procedure. A procedure of course being the P in the Tactics, Techniques and Procedures (TTP) acronym.

A procedure is an attack with a single responsibility, bucketed under a larger operation.

Now, we take it the rest of the way: we take the built procedure from part one and import it into Operator. This way, the procedure can be stored in our database of attacks and run repeatably in our environment as part of larger security assessments.


To start, make sure you’ve read the Coming to Life post linked above. There I detail how to build a procedure from an idea (needing to gain persistence on a MacOS computer between restarts).

The attack outlined in part one and finished here will not work out-of-box against every version of MacOS. Settings and version differences can require tweaking the TTP, so test this procedure against one similar to your target.

To recap, in part one I built a 1-line (Python) procedure which drops the following plist file on a remote computer:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "<http://www.apple.com/DTDs/PropertyList-1.0.dtd>">
<plist version="1.0">
<dict>
<key>Label</key>
<string>my.boomer</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/python3</string>
<string>/Users/privateducky/Downloads/boomer.py</string>
</array>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
view raw securitystate.sh hosted with ❤ by GitHub

This will have the effect of executing the boomer.py file each time the target computer restarts, which will in turn start my agent.

Modifying this with Pneuma in mind, we come up with:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "<http://www.apple.com/DTDs/PropertyList-1.0.dtd>">
<plist version="1.0">
<dict>
<key>Label</key>
<string>my.pneuma</string>
<key>ProgramArguments</key>
<array>
<string>${agent.location}</string>
</array>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
view raw pneuma-plist.sh hosted with ❤ by GitHub

Comparing the two, you’ll notice:

  1. The name was changed from my.boomer to my.pneuma. We did this simply to be consistent with the agent name.

  2. We removed the first program argument, for Python3, as Pneuma is started as a bash/zsh executable, not Python.

  3. We added a variable in the command for #{agent.location}. When this procedure is run from Operator, the brain will replace this with the automatically generated location of the Pneuma agent on disk.

Automatic facts are environmental and are automatically applied to all agents. The Community README outlines the following automatic facts:

  • operator.session: a unique key for your Operator session. The session key regenerates on each restart of the app and is used internally to help validate external connections originated in the app.

  • operator.payloads: callback address of your HTTP payload server (defaults to operator.http)

  • operator.http: callback address of your HTTP server

  • operator.tcp: callback address of your TCP server

  • operator.udp: callback address of your UDP server

  • operator.grpc: callback address of your GRPC server

  • agent.name: current name given to the agent

  • agent.location: absolute file path of the agent file on disk

With our modified TTP in hand, we fire up Operator and head to the Editor section. There, we create a new procedure classified as Persistence (tactic) T1037 (technique) and fill in the appropriate details. Note that the 1-liner is a Python script, so we’ll need to apply the correct executor:

We updated the command to reflect the 1-liner example in the Coming to Life post and applied the changes we made around the Pneuma plist file earlier in this post.

Saving the TTP, we can test it by clicking Select Target and choosing any connected MacOS agent with a Python executor (coldduck, in my example):

Success! The status message, along with checking the local disk manually, shows that the plist file was created successfully. I can reboot my machine and see if the Pneuma agent is triggered by the new plist addition. If not, I’ll need to make adjustments to my TTP command according to my particular system requirements.

I can now add my new procedure to any new or existing adversary and run it as part of a larger operation when I want reboot persistence on MacOS computers.


We hope you enjoyed this two-part series on how to take an idea, create a single procedure and leverage the final result in your C2 environment. Keep checking back for more tutorials on all things red teaming.