Pycobalt
Cobalt Strike Python API
Install / Use
/learn @dcsync/PycobaltREADME
PyCobalt is a Python API for Cobalt Strike.
Quick Start
Have Python3+ installed on Linux. PyCobalt probably works on macOS and Windows as well. I only test it on Linux though.
First you're going to install the PyCobalt Python library. To do that run
python3 setup.py install. If you need more installation help head over to the
Installation section.
Now you're ready to start writing PyCobalt scripts. A Python script for PyCobalt looks like this:
#!/usr/bin/env python3
import pycobalt.engine as engine
import pycobalt.aggressor as aggressor
import pycobalt.aliases as aliases
# register this function as a Beacon Console alias
@aliases.alias('example-alias')
def example_alias(bid):
aggressor.blog2(bid, 'example alias')
# read commands from cobaltstrike. must be called last
engine.loop()
You need to execute this Python script from an Aggressor script. An Aggressor script for PyCobalt looks like this:
$pycobalt_path = '/root/pycobalt/aggressor';
include($pycobalt_path . '/pycobalt.cna');
python(script_resource('example.py'));
It's necessary to set the $pycobalt_path variable so that PyCobalt can find
its dependencies.
Now load this Aggressor script into Cobalt Strike. Open up the Cobalt Strike Script Console and you'll see this:
[pycobalt] Executing script /root/pycobalt/example.py
PyCobalt comes with some Script Console commands:
aggressor> python-list
[pycobalt] Running scripts:
- /root/pycobalt/example.py
aggressor> python-stop /root/pycobalt/example.py
[pycobalt] Asking script to stop: /root/pycobalt/example.py
[pycobalt] Script process exited: /root/pycobalt/example.py
aggressor> python /root/pycobalt/example.py
[pycobalt] Executing script /root/pycobalt/example.py
aggressor> python-stop-all
[pycobalt] Asking script to stop: /root/pycobalt/example.py
[pycobalt] Script process exited: /root/pycobalt/example.py
When you reload your Aggressor script you should explicitly stop the Python scripts first. Otherwise they'll run forever doing nothing.
aggressor> python-stop-all
[pycobalt] Asking script to stop: /root/pycobalt/example.py
[pycobalt] Script process exited: /root/pycobalt/example.py
aggressor> reload example.cna
[pycobalt] Executing script /root/pycobalt/example.py
You can restart individual scripts as well:
aggressor> python /root/pycobalt/example.py
[pycobalt] /root/pycobalt/example.py is already running. Restarting.
[pycobalt] Asking script to stop: /root/pycobalt/example.py
[pycobalt] Script process exited: /root/pycobalt/example.py
[pycobalt] Executing script /root/pycobalt/example.py
For these commands to work properly you can only call PyCobalt in one Aggressor
script. Personally I have a single all.cna file with a bunch of calls to
python() and include().
PyCobalt Python Library
PyCobalt includes several Python modules. Here's the full list, with links to usage and examples:
- pycobalt.engine: Main communication code
- pycobalt.aggressor: Stubs for calling Aggressor functions
- pycobalt.aliases: Beacon Console alias registration
- pycobalt.commands: Script Console command registration
- pycobalt.events: Event handler registration
- pycobalt.console: Output modifiers and console colors
- pycobalt.gui: Context menu registration
- pycobalt.helpers: Assorted helper functions and classes to make writing scripts easier
- pycobalt.bot: Event Log bot toolkit
- pycobalt.sharpgen: Helper functions for using SharpGen
For full pydoc documentation head over to the docs/ directory.
Usage and Examples
Here are some script examples. For more complete examples see the examples directory.
Script Console Messages
To print a message on the Script Console:
import pycobalt.engine as engine
engine.message('test message')
engine.loop()
This shows up in the Script Console as:
[pycobalt example.py] test message
To print an error message on the Script Console:
import pycobalt.engine as engine
engine.error('test error')
engine.loop()
This shows up in the Script Console as:
[pycobalt example.py error] test error
To print debug messages to the Script Console:
import pycobalt.engine as engine
engine.enable_debug()
engine.debug('debug message 1')
engine.debug('debug message 2')
engine.disable_debug()
engine.debug('debug message 3')
engine.loop()
This shows up in the Script Console as:
[pycobalt example.py debug] debug message 1
[pycobalt example.py debug] debug message 2
To print raw stuff to the Script Console you can just call the Aggressor print functions:
import pycobalt.engine as engine
import pycobalt.aggressor as aggressor
aggressor.println('raw message')
engine.loop()
Aggressor
pycobalt.aggressor provides wrappers for all ~300 Aggressor functions and some Sleep functions. Here's how you call an Aggressor function:
import pycobalt.engine as engine
import pycobalt.aggressor as aggressor
for beacon in aggressor.beacons():
engine.message(beacon['user'])
engine.loop()
To call an Aggressor function with a callback:
import pycobalt.engine as engine
import pycobalt.aggressor as aggressor
def my_callback(bid, results):
aggressor.blog2(bid, 'ipconfig: ' + results)
for beacon in aggressor.beacons():
bid = beacon['bid']
aggressor.bipconfig(bid, my_callback)
engine.loop()
To call an Aggressor function without printing tasking information to the
Beacon Console (! operator, only supported by certain functions):
...
aggressor.bshell(bid, 'whoami', silent=True)
...
For information on calling Sleep or Aggressor functions that aren't in pycobalt.aggressor (including your own Aggressor functions) see the Sleep Functions section below.
For notes on using non-primitive objects such as dialog objects see the Non-Primitive Objects section.
Aliases
pycobalt.aliases provides the ability to register Beacon Console aliases.
import pycobalt.engine as engine
import pycobalt.aliases as aliases
import pycobalt.aggressor as aggressor
@aliases.alias('test_alias')
def test_alias(bid, arg1, arg2='test'):
aggressor.blog2(bid, 'test alias called with args {} {}'.format(arg1, arg2))
engine.loop()
You can register help info with an alias and it will show up when you run
Cobalt Strike's help command:
...
@aliases.alias('test_alias', short_help='Tests alias registration')
...
By default the long help will be based on the short help and Python function syntax. For example:
beacon> help test_alias
Tests alias registration
Syntax: test_alias arg1 [arg2=test]
Or you can specify the long help yourself:
...
@aliases.alias('test_alias', 'Tests alias registration', 'Test alias\n\nLong help')
...
Argument Checking
When the alias is called its arguments will be automagically checked against the arguments of the Python function. For example:
beacon> test_alias 1 2 3
[-] Syntax: test_alias arg1 [arg2=test]
To bypass this you can use python's * operator:
import pycobalt.engine as engine
import pycobalt.aliases as aliases
import pycobalt.aggressor as aggressor
@aliases.alias('test_alias', 'Tests alias registration')
def test_alias(bid, *args):
aggressor.blog2(bid, 'test alias called with args: ' + ', '.join(args))
engine.loop()
This also allows you to use Python's argparse with aliases. For more information about using argparse see the Argparse section below.
Exception Handling
If an unhandled exception occurs in your alias callback PyCobalt will catch it
and print the exception information to the Beacon Console. For example, while I
was writing the previous example I typed engine.blog2() instead of
aggressor.blog2() by accident and got this error:
beacon> test_alias
[-] Caught Python exception while executing alias 'test_alias': module 'pycobalt.engine' has no attribute 'blog2'
See Script Console for more details.
In the Script Console:
...
[pycobalt script error] exception: module 'pycobalt.engine' has no attribute 'blog2'
[pycobalt script error] traceback: Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/pycobalt-1.0.0-py3.7.egg/pycobalt/engine.py", line 122, in loop
handle_message(name, message)
File "/usr/lib/python3.7/site-packages/pycobalt-1.0.0-py3.7.egg/pycobalt/engine.py", line 89, in handle_message
callbacks.call(callback_name, callback_args)
File "/usr/lib/python3.7/site-packages/pycobalt-1.0.0-py3.7.egg/pycobalt/callbacks.py", line 42, in call
callback(*args)
File "/usr/lib/python3.7/site-packages/pycobalt-1.0.0-py3.7.egg/pycobalt/aliases.py", line 36, in alias_callback
raise e
File "/usr/lib/python3.7/site-packages/pycobalt-1.0.0-py3.7.egg/pycobalt/aliases.py", line 32, in alias_callback
callback(*args)
File "/sandboxed/tools/cobaltstrike/scripts/recon.py", line 170, in test_alias
engine.blog2(bid, 'test alias called with args: ' + ', '.join(args))
AttributeError: module 'pycobalt.engine' has no attribute 'blog2'
Double Quotes
Cobalt Strike's Beacon an
