Skip to content

Commit

Permalink
Improved plugin framework
Browse files Browse the repository at this point in the history
  • Loading branch information
x committed Aug 16, 2022
1 parent fedc188 commit e14ec0b
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 83 deletions.
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@

include /home/x/OneDrive/Projects/makefile-common/src/Makefile.twine
include /home/x/OneDrive/Projects/makefile-common/src/Makefile.python
include /home/x/OneDrive/Projects/makefile-common/src/Makefile.nethunter


$(info PROJECT_NAME: $(PROJECT_NAME))
$(info machine: $(MACHINE))
$(info INSTALL_REQUIRED_PY_PKGS: $(INSTALL_REQUIRED_PY_PKGS))


.DEFAULT_GOAL := build
PKG_NAME := $(PY_PKG_NAME)
MICROBIT_BIN = ./build/bbc-microbit-classic-gcc/src/firmware/bluescan-advsniff-combined.hex
MICROBIT_PATH = /media/${USER}/MICROBIT

Expand Down Expand Up @@ -61,3 +62,8 @@ release:
push:
$(call push-to-nethunter)
@scp dist/*.whl Raspberry-Pi-4-via-Local-Ethernet:~/Desktop/temp


.PHONY: push-to-ubuntu-server-vm
push-to-ubuntu-server-vm:
scp dist/$(PKG_NAME)-*-py3-none-any.whl Ubuntu-Server-VM:~/Downloads/
58 changes: 35 additions & 23 deletions README-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ bluescan
A Bluetooth scanner for hacking.
Author: Sourcell Xu
License: GPL-3.0
Usage:
Expand All @@ -149,33 +151,43 @@ Usage:
bluescan [-i <hci>] -m le [--ll-feature|--smp-feature] [--timeout=<sec>] --addr-type=<type> BD_ADDR
bluescan -m le --adv [--channel=<num>]
bluescan [-i <hci>] -m sdp BD_ADDR
bluescan [-i <hci>] -m gatt [--io-capability=<name>] --addr-type=<type> BD_ADDR
bluescan [-i <hci>] -m gatt [--io-capability=<name>] [--addr-type=<type>] BD_ADDR
bluescan --list-installed-plugins
bluescan --install-plugin=<path>
bluescan --uninstall-plugin=<name>
bluescan --run-plugin=<name> [--] [<plugin-opt>...]
Arguments:
BD_ADDR Target Bluetooth device address. FF:FF:FF:00:00:00 means local
device.
BD_ADDR Target Bluetooth device address. FF:FF:FF:00:00:00 means local
device.
plugin-opt Options for a plugin.
Options:
-h, --help Display this help.
-v, --version Show the version.
-i <hci> HCI device used for subsequent scans. [default: The first HCI device]
-m <mode> Scan mode, support br, le, sdp, and gatt.
--inquiry-len=<n> Inquiry_Length parameter of HCI_Inquiry command. [default: 8]
--lmp-feature Scan LMP features of the remote BR/EDR device.
--scan-type=<type> Scan type used for scanning LE devices, active or
passive. [default: active]
--timeout=<sec> Duration of the LE scanning, but may not be precise. [default: 10]
--sort=<key> Sort the discovered devices by key, only support
RSSI now. [default: rssi]
--adv Sniff advertising physical channel PDU. Need at
least one micro:bit.
--ll-feature Scan LL features of the remote LE device.
--smp-feature Detect pairing features of the remote LE device.
--channel=<num> LE advertising physical channel, 37, 38 or 39). [default: 37,38,39]
--addr-type=<type> Type of the LE address, public or random.
--io-capability=<name> Set IO capability of the agent. Available value: DisplayOnly, DisplayYesNo,
KeyboardOnly, NoInputNoOutput, KeyboardDisplay (KeyboardOnly) [default: NoInputNoOutput]
--clean Clean the cached data of a remote device.
-h, --help Display this help.
-v, --version Show the version.
-i <hci> HCI device used for subsequent scans. [default: The default HCI device]
-m <mode> Scan mode, support br, le, sdp and gatt.
--inquiry-len=<n> Inquiry_Length parameter of HCI_Inquiry command. [default: 8]
--lmp-feature Scan LMP features of the remote BR/EDR device.
--scan-type=<type> Scan type used for scanning LE devices, active or
passive. [default: active]
--timeout=<sec> Duration of the LE scanning, but may not be precise. [default: 10]
--sort=<key> Sort the discovered devices by key, only support
RSSI now. [default: rssi]
--adv Sniff advertising physical channel PDU. Need at
least one micro:bit.
--ll-feature Scan LL features of the remote LE device.
--smp-feature Detect pairing features of the remote LE device.
--channel=<num> LE advertising physical channel, 37, 38 or 39). [default: 37,38,39]
--addr-type=<type> Type of the LE address, public or random.
--io-capability=<name> Set IO capability of the agent. Available value: DisplayOnly, DisplayYesNo,
KeyboardOnly, NoInputNoOutput, KeyboardDisplay (KeyboardOnly) [default: NoInputNoOutput]
--clean Clean the cached data of a remote device.
--list-installed-plugins List plugins in local system
--install-plugin=<path> Install a plugin
--uninstall-plugin=<name> Uninstall a plugin
--run-plugin=<name> Execute plugin by name.
```

### BR 设备扫描 `-m br`
Expand Down
58 changes: 35 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ bluescan
A Bluetooth scanner for hacking.
Author: Sourcell Xu
License: GPL-3.0
Usage:
Expand All @@ -147,33 +149,43 @@ Usage:
bluescan [-i <hci>] -m le [--ll-feature|--smp-feature] [--timeout=<sec>] --addr-type=<type> BD_ADDR
bluescan -m le --adv [--channel=<num>]
bluescan [-i <hci>] -m sdp BD_ADDR
bluescan [-i <hci>] -m gatt [--io-capability=<name>] --addr-type=<type> BD_ADDR
bluescan [-i <hci>] -m gatt [--io-capability=<name>] [--addr-type=<type>] BD_ADDR
bluescan --list-installed-plugins
bluescan --install-plugin=<path>
bluescan --uninstall-plugin=<name>
bluescan --run-plugin=<name> [--] [<plugin-opt>...]
Arguments:
BD_ADDR Target Bluetooth device address. FF:FF:FF:00:00:00 means local
device.
BD_ADDR Target Bluetooth device address. FF:FF:FF:00:00:00 means local
device.
plugin-opt Options for a plugin.
Options:
-h, --help Display this help.
-v, --version Show the version.
-i <hci> HCI device used for subsequent scans. [default: The first HCI device]
-m <mode> Scan mode, support br, le, sdp and gatt.
--inquiry-len=<n> Inquiry_Length parameter of HCI_Inquiry command. [default: 8]
--lmp-feature Scan LMP features of the remote BR/EDR device.
--scan-type=<type> Scan type used for scanning LE devices, active or
passive. [default: active]
--timeout=<sec> Duration of the LE scanning, but may not be precise. [default: 10]
--sort=<key> Sort the discovered devices by key, only support
RSSI now. [default: rssi]
--adv Sniff advertising physical channel PDU. Need at
least one micro:bit.
--ll-feature Scan LL features of the remote LE device.
--smp-feature Detect pairing features of the remote LE device.
--channel=<num> LE advertising physical channel, 37, 38 or 39). [default: 37,38,39]
--addr-type=<type> Type of the LE address, public or random.
--io-capability=<name> Set IO capability of the agent. Available value: DisplayOnly, DisplayYesNo,
KeyboardOnly, NoInputNoOutput, KeyboardDisplay (KeyboardOnly) [default: NoInputNoOutput]
--clean Clean the cached data of a remote device.
-h, --help Display this help.
-v, --version Show the version.
-i <hci> HCI device used for subsequent scans. [default: The default HCI device]
-m <mode> Scan mode, support br, le, sdp and gatt.
--inquiry-len=<n> Inquiry_Length parameter of HCI_Inquiry command. [default: 8]
--lmp-feature Scan LMP features of the remote BR/EDR device.
--scan-type=<type> Scan type used for scanning LE devices, active or
passive. [default: active]
--timeout=<sec> Duration of the LE scanning, but may not be precise. [default: 10]
--sort=<key> Sort the discovered devices by key, only support
RSSI now. [default: rssi]
--adv Sniff advertising physical channel PDU. Need at
least one micro:bit.
--ll-feature Scan LL features of the remote LE device.
--smp-feature Detect pairing features of the remote LE device.
--channel=<num> LE advertising physical channel, 37, 38 or 39). [default: 37,38,39]
--addr-type=<type> Type of the LE address, public or random.
--io-capability=<name> Set IO capability of the agent. Available value: DisplayOnly, DisplayYesNo,
KeyboardOnly, NoInputNoOutput, KeyboardDisplay (KeyboardOnly) [default: NoInputNoOutput]
--clean Clean the cached data of a remote device.
--list-installed-plugins List plugins in local system
--install-plugin=<path> Install a plugin
--uninstall-plugin=<name> Uninstall a plugin
--run-plugin=<name> Execute plugin by name.
```

### Scan BR devices `-m br`
Expand Down
20 changes: 14 additions & 6 deletions src/bluescan/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
#!/usr/bin/env python3

PKG_NAME = 'bluescan'
VERSION = '0.8.3'
DEBUG_VERSION = None

from pyclui import Logger, INFO, DEBUG

LOG_LEVEL = INFO
logger = Logger(__name__, LOG_LEVEL)

if DEBUG_VERSION is not None:
LOG_LEVEL = DEBUG
logger.setLevel(LOG_LEVEL)
logger.warning("Using the debug version {} of {}".format(DEBUG_VERSION, PKG_NAME))

import io
import pkg_resources
import logging
from pathlib import Path

from pyclui import Logger
from bthci import HCI

logger = Logger(__name__, logging.INFO)

PROJECT_NAME = 'bluescan'
VERSION = '0.8.2'

PKG_ROOT = Path(__file__).parent

Expand Down
21 changes: 9 additions & 12 deletions src/bluescan/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,23 @@
from pathlib import PosixPath

from bthci import HCI
from pyclui import Logger, DEBUG, INFO, blue
from pyclui import Logger, blue
from bluepy.btle import BTLEException

from xpycommon.bluetooth import is_bluetooth_service_active

from . import BlueScanner
from .ui import parse_cmdline, INDENT
from . import BlueScanner, LOG_LEVEL
from .ui import parse_cmdline
from .helper import find_rfkill_devid, get_microbit_devpaths
from .plugin import list_plugins, install_plugin, uninstall_plugin, run_plugin
from .plugin import PluginInstallError, list_plugins, install_plugin, uninstall_plugin, run_plugin
from .br_scan import BRScanner
from .le_scan import LeScanner
from .gatt_scan import GattScanner
from .sdp_scan import SDPScanner

# from .stack_scan import StackScanner


logger = Logger(__name__, DEBUG)

logger.debug("__name__: {}".format(__name__))


PLUGIN_PATH = '/root/.bluescan/plugins'
logger = Logger(__name__, LOG_LEVEL)


def init_hci(iface: str = 'hci0'):
Expand Down Expand Up @@ -109,7 +103,8 @@ def clean(laddr: str, raddr: str):
def main():
try:
args = parse_cmdline()
logger.debug(blue("main()") + ", args: {}".format(args))
logger.debug("parse_cmdline() returned\n"
" args: {}".format(args))

if args['--list-installed-plugins']:
list_plugins()
Expand Down Expand Up @@ -193,6 +188,8 @@ def main():
exit(1)
except (BTLEException) as e:
logger.error(str(e) + ("\nNo BLE adapter or missing sudo?" if 'le on' in str(e) else ""))
except PluginInstallError as e:
logger.error("Failed to install plugin: {}".format(e))
except KeyboardInterrupt:
if args != None and args['-i'] != None:
output = subprocess.check_output(' '.join(['hciconfig', args['-i'], 'reset']),
Expand Down
64 changes: 52 additions & 12 deletions src/bluescan/plugin.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
#!/usr/bin/env python3

import subprocess
from subprocess import CalledProcessError
from typing import Union
from abc import ABCMeta, abstractmethod
from importlib.metadata import metadata, version

import pkg_resources
from pyclui import Logger, INFO, DEBUG, blue
from pyclui import Logger
from docopt import DocoptExit

from .ui import LOG_LEVEL

logger = Logger(__name__, DEBUG)

logger = Logger(__name__, LOG_LEVEL)


class PluginError(Exception):
pass

class PluginInstallError(Exception):
pass

class PluginOptionsError(PluginError):
pass

Expand Down Expand Up @@ -54,14 +60,20 @@ def run(self):
@abstractmethod
def clean(self):
pass

@abstractmethod
def print_result(self):
pass


def list_plugins():
for dist in pkg_resources.working_set:
pkg_name = dist.key
try:
if Plugin.MAGIC_CLASSIFIER in str(metadata(pkg_name)):
print(pkg_name, version(pkg_name))
logger.debug("Plugin.MAGIC_CLASSIFIER: {}".format(Plugin.MAGIC_CLASSIFIER))
logger.debug("str(metadata(pkg_name): {}".format(str(metadata(pkg_name))))
print(pkg_name.replace('-', '_'), version(pkg_name))
except:
pass

Expand All @@ -80,8 +92,24 @@ def list_plugins():


def install_plugin(whl_path: str):
subprocess.check_output('pip install {} 2>&1 | grep -v "pip as the \'root\'"'.format(whl_path), shell=True)
logger.info("Installed {}".format(whl_path))
# ps = subprocess.Popen(('pip', 'install', '--force-reinstalls', whl_path, '2>2&1'), stdout=subprocess.PIPE, shell=True)
# subprocess.check_output('grep -v "pip as the \'root\'"'.format(whl_path), shell=True)

# 下面安装 plugin 的方法无法检测出,pip 的安装错误。因为在 check_output 中携带管道时,
# 当管道成功时不会包抛出异常。
# subprocess.check_output('pip install --force-reinstall --no-deps {} 2>&1 | grep -v "pip as the \'root\'"'.format(whl_path), shell=True)

# --force-reinstall
# 该参数让 pip 在安装 package 时,无视 package 版本,强制重新安装它。这样做可以方
# 便开发者在不更改版本号的情况下,调试未发布的 plugin。
# --no-deps
# 该参数配合 --force-reinstall 使用,即在强制重装的过程中,不重新安装依赖包。这样
# 可以避免在重装过程中遇到 apt 安装的 python package 无法被 pip 卸载的问题。
try:
subprocess.check_output('pip install --force-reinstall --no-deps {}'.format(whl_path), shell=True)
logger.info("Installed {}".format(whl_path))
except CalledProcessError:
raise PluginInstallError(whl_path)


def uninstall_plugin(name: str):
Expand All @@ -90,6 +118,15 @@ def uninstall_plugin(name: str):


def run_plugin(name: str, opts: str):
"""
Parameters
name
Plugin 的名字。
opts
传给 plugin 的所有命令行选项,是一个单独的字符串。该字符串不包括 plugin 的名字本身。
就好像 plugin 单独运行时,少了 sys.argv[0] 的 sys.argv 一样。
"""
if ' ' in name or ';' in name or '(' in name or ')' in name or '{' in name or \
'}' in name or ':' in name or '\'' in name or '"' in name or '.' in name or \
len(name) > 34:
Expand All @@ -114,22 +151,25 @@ def run_plugin(name: str, opts: str):
plugin = PluginSubclass(argv=opts)

plugin.prepare()
result = plugin.run()
plugin.run()
plugin.clean()
logger.info('{}: {}'.format(plugin.__class__, result))

plugin.print_result()
except DocoptExit as e:
# 当不给 plugin 提供任何参数时,解析 plugin 参数的 docopt() 会抛出携带帮助信息的
# DocoptExit 异常。
logger.debug("{}".format(type(e)))
help_msg = str(e)
print(help_msg)
except PluginHelpException as e:
logger.debug("{}".format(type(e)))
# print help doc of the plugin
# logger.debug("PluginHelpException, {}".format(type(e)))
print(e)
except PluginOptionsError as e:
logger.error("{}: {}".format(name, e))
logger.error("PluginOptionsError, {}: {}".format(name, e))
except PluginPrepareError as e:
logger.error("{}: {}".format(name, e))
logger.error("PluginPrepareError, {}: {}".format(name, e))
except PluginRuntimeError as e:
logger.error("{}: {}".format(name, e))
logger.error("PluginRuntimeError, {}: {}".format(name, e))
except PluginCleanError as e:
logger.error("{}: {}".format(name, e))
logger.error("PluginCleanError, {}: {}".format(name, e))
Loading

0 comments on commit e14ec0b

Please sign in to comment.