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 whatOPTIONSprovides.def __init__(self, arg1: type1, arg2: type2, ...): first step of attack launch; ingest the parameters you specified withinOPTIONShere.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.PwnObdExceptionwith 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:
intfloatstr- 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
Scannerdoes not return instantiableDevicess; it may instead return anything.Scanners are not explicitly called when runningrecon scan, and instead are used as a base to implement other scanners. For example,pwnobd.modules.bluetooth.BluetoothScannerperforms a Bluetooth scan using bleak‘sBleakScannerand returns its results as-is. -
A
LeafScannerreturns 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 Nis executed). They often rely on upstreamScanners whose results they filter in order to find devices compatible with a given device driver; this is done as follows:[pastacode lang=”markup” manual=”class%20BluetoothThingymabobScanResult(ScannedDevice)%3A%20%23%20TODO%20implement%20%23%20name(self)%20-%3E%20str%20%23%20device_type(self)%20-%3E%20str%20%23%20create_device(self)%20-%3E%20Device%20pass%20%40scanner(%22thingymabob%22)%20class%20BluetoothThingymabobScanner(LeafScanner)%3A%20async%20def%20scan(self%2C%20ctx%3A%20ScanContext)%3A%20%23%20Retrieve%20%60BluetoothScanner%60%20and%20ask%20it%20to%20scan%20for%20Bluetooth%20devices.%20devices%20%3D%20await%20ctx.get_scanner(BluetoothScanner).scan(ctx)%20%23%20…%20do%20something%20with%20the%20returned%20devices…%20return%20%5B%20BluetoothThingymabobScanResult(…)%2C%20BluetoothThingymabobScanResult(…)%2C%20%23%20…%20%5D” message=”” highlight=”” provider=”manual”/]
Results from upstream scanners are cached within a given
recon scanrun; 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").
Install
Support Our Threat Intelligence
If you find our technology report and cybersecurity news helpful, consider supporting our work.