@botnet_hunter's blog
Using bamfdetect in your own workflow

When hunting botnets, whether they are custom developed or widely available, it can be helpful to statically extract configurations. With this ability, an automated workflow can be developed to identify and track botnets at scale. For this reason, I have developed bamfdetect.


bamfdetect is a tool which is designed to identify malware samples and statically extract their configuration information, such as the domain name of the command and control server. It is capable of taking in a single file path, or a variety of file paths. It supports searching inside of directories, archives, etc. Once it identifies files to process, it starts processing through groups of modules. The modules are as follows.

  • Preprocessors
  • Bot Modules
  • Postprocessors


Preprocessors have the ability to modify the file before being processed by the bot modules. This can be useful if files are somehow packed or obfuscated. The only currently (at the time of this blog being written) implemented preprocessor is the UPX decompressor. This identifies if the input file is UPX compressed, then uses the UPX binary installed on the system to decompress it. This functionality may be removed in the near future.

A planned preprocessor module is a reimplementation of the PHP decoder I have running here. This would deobfuscate PHP files being processed.

Bot Modules

Bot modules are the heart of bamfdetect. These modules are implemented in two portions, the identifier and the extractor. The identifiers, all of which are currently Yara signatures, identify whether a file being analyzed is a bot supported by the bot module. Since bot modules tend to focus on a single bot type or a family of very similar bots, this generally confirms that the module is likely capable of extracting the configuration.

The configuration extraction is then done with Python code. Configuration extraction can be very strict or very loose, depending on how reliably the configuration is stored in the malware. Admitedly, in some cases, the configuration extraction methods have been implemented somewhat lazily, so false positives and improperly extracted configurations are possible.


The purpose of postprocessor modules are to run after a file has been identified by at least one module. The postprocessor modules also have access to the extracted configuration information, so they can be acted upon. The only currently included postprocessing module calculates hashes on the file that was processed. I do have a few private postprocessing modules which I will mention shortly.


bamfdetect is a wonderful component of my malware tracking workflow. Clearly, I’m not about to give away all my secrets right now, but here is a sample workflow which can be done with bamfdetect.

Bot tracking workflow

Since bamfdetect has Yara signatures defined inside of it, these Yara signatures can be used in services such as VirusTotal Hunting in order to hunt for relevant samples. One can very simply download all files that trigger VirusTotal Hunting, and then feed them into bamfdetect. Files can also be fed in through a variety of other sources.

In order to make use of this, we can create a postprocessing module for bamfdetect which will upload any command and control information to our botnet tracking system. Since very few people utilize publicly available systems for this, the code below is very generic.

from BAMF_Detect.postprocessors.common import Postprocessor, Postprocessors
import httplib
import urllib
import json

def submit_c2(c2_uri, bot_type, source, api_key):
        params = urllib.urlencode({'api_key': api_key, 'uri': c2_uri, 'bot_type': bot_type, 'source': source})
        headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
        conn = httplib.HTTPSConnection("c2_tracking.example.com")
        conn.request("POST", "/upload_c2", params, headers)
        response = conn.getresponse()
        data = response.read()
        return json.loads(data)
    except KeyboardInterrupt:

class SubmitPanel(Postprocessor):
    def __init__(self):
            author="Brian Wallace (@botnet_hunter)",
            date="March 14th, 2015",
            description="Submits panel to C2 database",

    def _do_processing(self, file_data, config):
        if "information" in config:
            configuration = config["information"]
            bot_type = config["type"].lower()

            if "c2_uri" in configuration:
                # Simple bot setup
                submit_c2(configuration["c2_uri"], bot_type, "bamfdetect", "api-key")
            elif "c2s" in configuration:
                for c in configuration["c2s"]:
                    if "c2_uri" in c:
                        submit_c2(c["c2_uri"], bot_type, "bamfdetect", "api-key")

        return {}, file_data


This way, anytime we scan a file, the command and control information will be uploaded to c2_tracking.example.com to be automatically tracked. This is one of the methods I use to feed my botnet tracking platform.

Bot tracking panel

Tracking panel source

Design pdevty