Friday, September 9, 2016

Leveraging INNUENDO's RPC for Fun and Profit: tagging

For the second installment of this series (see the previous here), we're going to take a look at the new tagging functionality that was added to INNUENDO 1.6.

It is now possible to tag both operations and processes, making it much more convenient to organize each in a wide variety of ways. All of this can be done from the INNUENDO Web UI, and you can see a demonstration of that in this video.

This post will demonstrate how you can use RPC to automatically add and remove tags based on the results of operations.

The first step is to set up an event stream, just as we did in the previous post.

>>> import pprint # to make it easier to look through results
>>> import rpc
>>> c = rpc.Client()
>>> for event in c.events():
...     pprint.pprint(event)
None
{'data': {'id': '...'},
 'name': 'machine_updated',
 'time': datetime.datetime(2016, 8, 26, 19, 37, 15, 890128)}
{'data': {'id': '...'},
 'name': 'node_updated',
 'time': datetime.datetime(2016, 8, 26, 19, 37, 15, 927102)}
{'data': {'id': '...'},
 'name': 'process_updated',
 'time': datetime.datetime(2016, 8, 26, 19, 37, 15, 957477)}

This is a typical example of the output from an event stream. Note that it will occasionally return None, which we can safely ignore. For our purposes, the events we are interested in are operation_updated and process_added.

We can loop through the events and process them individually in a tree of if statements as in the previous post, but let's add a layer of abstraction to make life a bit easier.

import rpc

class Monitor(rpc.Client):
    def on_some_event(self, event):
        """Called when "some_event" is emitted."""
        pass


    def monitor(self):
        """Monitors events for any existing event handlers."""
        # create an event filter based on the existing handlers
        filter = [n[3:] for n in dir(self) if n.startswith('on_')]
        print 'monitoring: {}'.format(', '.join(filter))

        for event in self.events(*filter):
            if not event: continue
            handler = getattr(self, 'on_' + event['name'])
            handler(event)

This small subclass allows us to define event handler methods for events we're interested in, making it simple to add handlers for the events we're interested in. Now we can build off of that to begin processing events.

Let's add a handler to queue some operations every time a new process is added.

class Monitor(rpc.Client):
    # ... previous code ...
    def on_process_added(self, event):
        # all process events set event['data']['id'] to the relevant
        # process ID 
        proc_id = event['data']['id']
    
        # queue some recon operations
        self.operation_execute('recon', 'assign_aliases', proc_id)
        self.operation_execute('recon', 'audio_query', proc_id)
        self.operation_execute('recon', 'camera_query', proc_id)

That's all it takes. Those operations will be queued for execution with every new process that activates with the C2. This is nice, but it would be even better if we could process the results of those operations somehow.

One way to do that is to wait for the results to come in using
Client.operation_wait or Client.operation_call. However, by taking advantage of the event stream, we can process the results of every operation that is queued (even if queued in the Web UI), not just the ones we queue ourselves in the on_process_added handler.

So, let's add another handler to process operation results. For this event handler, we'll implement functionality similar to what is done in our monitor method to make it easy to process the results of different operations by adding handler methods.

class Monitor(rpc.Client):
    # ... previous code ...
    def on_operation_updated(self, event):
        # all operation events set event['data']['id'] to the relevant
        # operation ID
        oper_id = event['data']['id']
        # using the operation ID, we can retrieve the operation metadata
        oper = self.operation_get(oper_id)

        # and we can use the metadata to filter out operations that we're not
        # interested in. In this case, operations that are not finished
        if oper['state'] != 'finished':
            return

        # get operation attributes (these are the results)
        attrs = self.operation_attributes(oper['id'])

        # handle operation (if a matching 'handle_' method exists)
        handler = getattr(self, 'handle_' + oper['name'], None)
        if handler:
            # pass in both the operation metadata and attributes
            handler(oper, attrs)

Here, we're using a different method prefix (handle_) to define the methods that will handle operation results. Now we just have to add handlers for the operations we're interested in.

class Monitor(rpc.Client):
    # ... previous code ...
    def handle_assign_aliases(self, oper, attrs):
        # assign_aliases offers us a quick way to determine the target's
        # architecture, among other useful bits of info
        arch = attrs['info']['arch']

        # let's tag it!
        self.process_tag_add('arch:{}'.format(arch), oper)

    def handle_camera_query(self, oper, attrs):
        if attrs['cameras']:
            self.process_tag_add('has:camera', oper['id'])
        else:
            # a camera could be removed, so we should be able to update
            # the tag in that case
            self.process_tag_remove('has:camera', oper['id'])

    def handle_audio_query(self, oper, attrs):
        if attrs['devices']:
            self.process_tag_add('has:audio', oper['id'])
        else:
            # audio could be removed, so we should be able to update
            # the tag in that case
            self.process_tag_remove('has:audio', oper['id'])

How you tag your processes or operations is up to you, of course. We recommend a naming scheme that includes uniquely identifiable elements so the tags can be used to search for processes/operations.

Any added/removed tag will be reflected immediately in the Web UI.

Here is the full code.

import rpc

class Monitor(rpc.Client):
    ## operation result handlers ##

    def handle_assign_aliases(self, oper, attrs):
        arch = attrs['info']['arch']
        self.process_tag_add('arch:{}'.format(arch), oper['process_id'])

    def handle_camera_query(self, oper, attrs):
        if attrs['cameras']:
            self.process_tag_add('has:camera', oper['process_id'])
        else:
            self.process_tag_remove('has:camera', oper['process_id'])

    def handle_audio_query(self, oper, attrs):
        if attrs['devices']:
            self.process_tag_add('has:audio', oper['process_id'])
        else:
            self.process_tag_remove('has:audio', oper['process_id'])

    ## event handlers ##

    def on_process_added(self, event):
        proc_id = event['data']['id']
    
        # queue some recon operations
        self.operation_execute('recon', 'assign_aliases', proc_id)
        self.operation_execute('recon', 'audio_query', proc_id)
        self.operation_execute('recon', 'camera_query', proc_id)
    
    def on_operation_updated(self, event):
        oper_id = event['data']['id']
        oper = self.operation_get(oper_id)

        # filter
        if oper['state'] != 'finished':
            return

        # get operation attributes
        attrs = self.operation_attributes(oper['id'])

        # handle operation
        handler = getattr(self, 'handle_' + oper['name'], None)
        if handler:
            print 'handling operation:', oper['name']
            handler(oper, attrs)

    ## monitor ##

    def monitor(self):
        """Monitors events for any existing event handlers."""
        # create an event filter based on the existing handlers
        filter = [n[3:] for n in dir(self) if n.startswith('on_')]
        print 'monitoring: {}'.format(', '.join(filter))

        for event in self.events(*filter):
            if not event: continue
            print 'handling event:', event['name']
            handler = getattr(self, 'on_' + event['name'])
            handler(event)

if __name__ == '__main__':
    try:
        Monitor().monitor()
    except KeyboardInterrupt:
        pass

You can watch this script in action in the video mentioned at the top of this post.