Writing Modules¶
Adding a module = drop a .py file under the right modules/<category>/ directory. The loader auto-discovers it on next launch.
Skeleton (Exploit)¶
# modules/exploits/my_exploit.py
from core.base import ExploitBase
from core.utils.printer import print_info, print_ok, print_err
class Exploit(ExploitBase):
info = {
"name": "My Exploit",
"description": "One-line description of what it does.",
"author": ["yourhandle"],
"cve": ["CVE-2025-XXXXX"],
"references": ["https://example.com/advisory"],
"platform": ["linux"], # or ["linux", "macos"]
}
options = {
"TARGET": {"value": "", "required": True, "desc": "Target MAC"},
"IFACE": {"value": "hci0","required": False, "desc": "HCI device"},
"TIMEOUT": {"value": 10, "required": False, "desc": "Seconds"},
}
def check(self):
"""Non-destructive pre-flight. Return True/False."""
target = self.options["TARGET"]["value"]
if not target:
print_err("TARGET is required")
return False
print_ok(f"Target {target} reachable")
return True
def run(self):
if not self.check():
return
target = self.options["TARGET"]["value"]
print_info(f"Exploiting {target}...")
# ... your logic ...
print_ok("Done")
Skeleton (Scanner / DoS / Recon / Auxiliary / Post)¶
Same pattern, different base class:
from core.base import ScannerBase # scanners/
from core.base import DoSBase # dos/
from core.base import ReconBase # recon/
from core.base import AuxiliaryBase # auxiliary/
from core.base import PostBase # post/
The class name should be Scanner, DoS, Recon, Auxiliary, or Post respectively.
Conventions¶
- One module per file. File name = module name (
my_exploit.py->exploits/my_exploit). - Use
print_info/print_ok/print_errfromcore.utils.printer, neverprint()directly. - Validate inputs in
check(), not deep insiderun(). - Gate platform-specific code with
import sys; if sys.platform != "linux": .... - Reuse
core/utils/bt.pyfor raw HCI / BD_ADDR / L2CAP plumbing instead of hand-rolling socket and struct code in every module. - Reuse
core/hardware.pyfor adapter access, do not open raw HCI sockets directly unless you must. - Keep module options small. If a module needs >8 options, it is probably two modules.
Writing to the engagement store¶
Every module has a lazy self.store that points at the active workspace's
SQLite-backed state. Use it instead of inventing a per-module output_file
option, so downstream modules and the hosts / creds console commands
can see what you produced.
# Recon module recording discovered devices
def run(self):
for d in discovered_devices:
self.store.add_host(
address=d.address,
name=d.name,
rssi=d.rssi,
manufacturer=d.vendor,
)
# Post-exploitation module recording a recovered credential
def run(self):
link_key = self._extract_link_key(...)
self.store.add_credential(
host=self.get_option("target"), # address or stored host id
kind="LinkKey",
value=link_key.hex(),
metadata=json.dumps({"pin_length": 4}),
)
Available helpers on self.store:
| Method | Use |
|---|---|
add_host(address, name=None, rssi=None, manufacturer=None) |
Record / update a discovered device |
add_credential(host, kind, value, metadata=None) |
Persist a key/PIN tied to a host |
add_loot(host, kind, data, source=None) |
Persist arbitrary bytes (PCAP, GATT dump) |
list_hosts() / list_credentials() / list_loot() |
Read back rows in the active workspace |
latest_credential(host, kind) |
Drives set target autofill |
Recognized credential kinds: LinkKey, LTK, IRK, CSRK, PIN,
PeripheralLTK. Other strings are accepted and stored verbatim; only
the standard kinds trigger set target autofill on a downstream module.
Testing¶
python3 bluesploit.py --list | grep my_exploit # discovery works?
# Quick syntactic check:
python3 -c "from modules.exploits.my_exploit import Exploit; e = Exploit(); print(e.info['name'])"
# Full pytest (if you wrote a test):
pytest tests/exploits/test_my_exploit.py
Submitting¶
- Branch off
main. - Add module + (optional) signature in
data/signatures/. - Update Exploits / DoS / etc. wiki page.
- Open a PR, see Contributing.