pwnobd: Offensive cybersecurity toolkit for vulnerability analysis and penetration testing of OBD-II devices
pwnobd
Offensive cybersecurity toolkit for vulnerability analysis and penetration testing of OBD-II devices.
Adding new functionality
Most functionality is dynamically registered onto pwnobd through the use of decorators.
Attacks
Located in src/pwnobd/modules/attacks/
. See the example to understand how to develop an attack and allow the user to parametrize it.
The lifecycle of an attack is as follows:
def precheck(**kwargs)
: called every time an option is set for this attack. Allows to implement custom parameter validation beyond whatOPTIONS
provides.def __init__(self, arg1: type1, arg2: type2, ...)
: first step of attack launch; ingest the parameters you specified withinOPTIONS
here.async def setup(self)
: second step of attack launch, perform initialization here. Input is blocked and the attack task is not created until this coroutine finishes, so you may take advantage of interactivity here. Raisepwnobd.exceptions.PwnObdException
with a custom message if something goes wrong in order to abort the attack’s launch.async def run(self, ctx: WorkTaskContext, devices: dict[int, Device])
: actual attack implementation here. Runs within a task. Devices are provided to you pre-locked and ready to use as part ofdevices
. Note: if you launch tasks which use a device provided here, ensure they finish by the time this coroutine returns; otherwise undefined behavior may happen.
Once your Attack
subclass is ready, annotate it with @attack
; as soon as the corresponding Python file is loaded by pwnobd
, the attack will be automatically registered on startup.
The following parameter types are supported:
int
float
str
- If a parameter is supposed to be a path, you can add
"hint": "path"
along with"type": "str"
; this will enable auto-completion of paths within the UI.
- If a parameter is supposed to be a path, you can add
- An array of any of the above.
The class constant DEVICE_REQUIREMENTS
contains the minimum set of classes the device driver must subclass in order for the attack to run. This is validated at runtime by the core logic before the attack class is instanced.
Device drivers and scanners
Located in src/pwnobd/modules/devices/
.
A device driver is a class that at least subclasses pwnobd.core.Device
, plus any common interfaces it may implement (SendCan
, Reset
, Interactive
, etc.).
The lifecycle of a device is as follows.
- The device is instantiated using
__init__
, options as required. Provide a docstring here with the initialization parameters you want to accept; the method’s signature and docstring will automatically be parsed upon registration and exposed accordingly to the user. async def connect(self)
is then called; the connection with the device is set up and the device is initialized here.- Once the connection is ready,
async def handle(self)
is ran as a task. You may handle communications with the device here. Once the task returns, the connection is marked as destroyed and can no longer be used.
An scanner is a class which subclasses Scanner
or LeafScanner
.
-
A
Scanner
does not return instantiableDevices
s; it may instead return anything.Scanner
s are not explicitly called when runningrecon scan
, and instead are used as a base to implement other scanners. For example,pwnobd.modules.bluetooth.BluetoothScanner
performs a Bluetooth scan using bleak‘sBleakScanner
and returns its results as-is. -
A
LeafScanner
returns a list of objects that subclassScannedDevice
. These objects can in turn be used to directly instantiate a device driver of a given type with the required parameters already filled in (which is what happens whenconnect --scanned N
is executed). They often rely on upstreamScanner
s whose results they filter in order to find devices compatible with a given device driver; this is done as follows:Results from upstream scanners are cached within a given
recon scan
run; each scanner thus runs only once.
Once your Device
subclass is ready, annotate it with @device("device_name")
; as soon as the corresponding Python file is loaded by pwnobd
, the attack will be automatically registered on startup. Same goes for scanners, use @scanner("scanner_name")
.