How to Write a BBOT Module
Here we'll go over a basic example of writing a custom BBOT module.
Create the python file
- Create a new
.py
file inbbot/modules
(or in a custom module directory) - At the top of the file, import
BaseModule
- Declare a class that inherits from
BaseModule
- the class must have the same name as your file (case-insensitive)
- Define in
watched_events
what type of data your module will consume - Define in
produced_events
what type of data your module will produce - Define (via
flags
) whether your module isactive
orpassive
, and whether it'ssafe
oraggressive
- Put your main logic in
.handle_event()
Here is an example of a simple module that performs whois lookups:
from bbot.modules.base import BaseModule
class whois(BaseModule):
watched_events = ["DNS_NAME"] # watch for DNS_NAME events
produced_events = ["WHOIS"] # we produce WHOIS events
flags = ["passive", "safe"]
meta = {"description": "Query WhoisXMLAPI for WHOIS data"}
options = {"api_key": ""} # module config options
options_desc = {"api_key": "WhoisXMLAPI Key"}
per_domain_only = True # only run once per domain
base_url = "https://www.whoisxmlapi.com/whoisserver/WhoisService"
# one-time setup - runs at the beginning of the scan
async def setup(self):
self.api_key = self.config.get("api_key")
if not self.api_key:
# soft-fail if no API key is set
return None, "Must set API key"
async def handle_event(self, event):
self.hugesuccess(f"Got {event} (event.data: {event.data})")
_, domain = self.helpers.split_domain(event.data)
url = f"{self.base_url}?apiKey={self.api_key}&domainName={domain}&outputFormat=JSON"
self.hugeinfo(f"Visiting {url}")
response = await self.helpers.request(url)
if response is not None:
await self.emit_event(response.json(), "WHOIS", parent=event)
Test your new module
After saving the module, you can run it with -m
:
# run a scan enabling the module in bbot/modules/mymodule.py
bbot -t evilcorp.com -m whois
Debugging Your Module
BBOT has a variety of colorful logging functions like self.hugesuccess()
that can be useful for debugging.
BBOT log levels:
critical
: bright redhugesuccess
: bright greenhugewarning
: bright orangehugeinfo
: bright blueerror
: redwarning
: orangeinfo
: blueverbose
: grey (must enable-v
to see)debug
: grey (must enable-d
to see)
For details on how tests are written, see Unit Tests.
handle_event()
and emit_event()
The handle_event()
method is the most important part of the module. By overriding this method, you control what the module does. During a scan, when an event from your watched_events
is encountered (a DNS_NAME
in this example), handle_event()
is automatically called with that event as its argument.
The emit_event()
method is how modules return data. When you call emit_event()
, it creates an event and outputs it, sending it any modules that are interested in that data type.
setup()
A module's setup()
method is used for performing one-time setup at the start of the scan, like downloading a wordlist or checking to make sure an API key is valid. It needs to return either:
True
- module setup succeededNone
- module setup soft-failed (scan will continue but module will be disabled)False
- module setup hard-failed (scan will abort)
Optionally, it can also return a reason. Here are some examples:
async def setup(self):
if not self.config.get("api_key"):
# soft-fail
return None, "No API key specified"
async def setup(self):
try:
wordlist = self.helpers.wordlist("https://raw.githubusercontent.com/user/wordlist.txt")
except WordlistError as e:
# hard-fail
return False, f"Error downloading wordlist: {e}"
async def setup(self):
self.timeout = self.config.get("timeout", 5)
# success
return True
Module Config Options
Each module can have its own set of config options. These live in the options
and options_desc
attributes on your class. Both are dictionaries; options
is for defaults and options_desc
is for descriptions. Here is a typical example:
class nmap(BaseModule):
# ...
options = {
"top_ports": 100,
"ports": "",
"timing": "T4",
"skip_host_discovery": True,
}
options_desc = {
"top_ports": "Top ports to scan (default 100) (to override, specify 'ports')",
"ports": "Ports to scan",
"timing": "-T<0-5>: Set timing template (higher is faster)",
"skip_host_discovery": "skip host discovery (-Pn)",
}
async def setup(self):
self.ports = self.config.get("ports", "")
self.timing = self.config.get("timing", "T4")
self.top_ports = self.config.get("top_ports", 100)
self.skip_host_discovery = self.config.get("skip_host_discovery", True)
return True
Once you've defined these variables, you can pass the options via -c
:
bbot -m nmap -c modules.nmap.top_ports=250
... or via the config:
modules:
nmap:
top_ports: 250
Inside the module, you access them via self.config
, e.g.:
self.config.get("top_ports")
Module Dependencies
BBOT automates module dependencies with Ansible. If your module relies on a third-party binary, OS package, or python library, you can specify them in the deps_*
attributes of your module.
class MyModule(BaseModule):
...
deps_apt = ["chromium-browser"]
deps_ansible = [
{
"name": "install dev tools",
"package": {"name": ["gcc", "git", "make"], "state": "present"},
"become": True,
"ignore_errors": True,
},
{
"name": "Download massdns source code",
"git": {
"repo": "https://github.com/blechschmidt/massdns.git",
"dest": "#{BBOT_TEMP}/massdns",
"single_branch": True,
"version": "master",
},
},
{
"name": "Build massdns",
"command": {"chdir": "#{BBOT_TEMP}/massdns", "cmd": "make", "creates": "#{BBOT_TEMP}/massdns/bin/massdns"},
},
{
"name": "Install massdns",
"copy": {"src": "#{BBOT_TEMP}/massdns/bin/massdns", "dest": "#{BBOT_TOOLS}/", "mode": "u+x,g+x,o+x"},
},
]
Load Modules from Custom Locations
If you have a custom module and you want to use it with BBOT, you can add its parent folder to module_dirs
. This saves you from having to copy it into the BBOT install location. To add a custom module directory, add it to module_dirs
in your preset:
# load BBOT modules from these additional paths
module_dirs:
- /home/user/my_modules