Modules ======== Presentation ------------- Mirage provides a modular framework for analyzing the security of wireless communication protocols, that's why the available attacks and actions are implemented as **modules**. A *module* is a software component of Mirage, easy to run and use, allowing to perform a specific action or attack. It has multiple input parameters and generates some output parameters. Every parameter is named and allow to provide a specific information to the module in order to customize its execution. The modules can be combined thanks to a chaining operator (``|``). If you want more details about this operator, please refer to :doc:`this documentation `. Finally, some modules provide complex behaviour, such as :doc:`ble_master ` or :doc:`ble_mitm `. In order to easily customize their behaviour, Mirage allows to use :doc:`scenarios ` which are special classes allowing to modify the execution of a module without modifying its source code. List of Bluetooth Low Energy modules ------------------------------------- The following modules allows to manipulate Bluetooth Low Energy communications : .. include:: list.ble-modules.rst List of Enhanced ShockBurst modules ------------------------------------- The following modules allows to manipulate Enhanced ShockBurst communications : .. include:: list.esb-modules.rst List of Mosart modules ----------------------- The following modules allows to manipulate Mosart communications : .. include:: list.mosart-modules.rst List of WiFi modules ----------------------- The following modules allows to manipulate WiFi communications : .. include:: list.wifi-modules.rst List of Zigbee modules ----------------------- The following modules allows to manipulate Zigbee communications : .. include:: list.zigbee-modules.rst List of Infrared Radiations modules ------------------------------------ The following modules allows to manipulate Infrared Radiations signals : .. include:: list.ir-modules.rst Writing your own module ------------------------ Generating a new module ^^^^^^^^^^^^^^^^^^^^^^^^ Writing your own module is relatively simple. Indeed, Mirage allows to easily generates a basic source code using the ``create_module`` command. You can launch it from the :doc:`Command Line Interface ` : :: ~~> create_module [QUESTION] Module's name : newmod [QUESTION] Module's description : Test module [QUESTION] Module's type : test [QUESTION] Module's technology [ble] : [QUESTION] Module's dependencies (separated by commas) : ble_sniff,ble_scan [QUESTION] Input parameter #1 (name) : INTERFACE [QUESTION] Input parameter #1 (default value) : hci0 [QUESTION] Input parameter #2 (name) : PARAM1 [QUESTION] Input parameter #2 (default value) : value1 [QUESTION] Input parameter #3 (name) : [SUCCESS] Module newmod successfully generated : /home/user/.mirage/modules/newmod.py Or you can launch it from the :doc:`Direct Mode ` : :: $ mirage --create_module [QUESTION] Module's name : newmod [QUESTION] Module's description : Test module [QUESTION] Module's type : test [QUESTION] Module's technology [ble] : [QUESTION] Module's dependencies (separated by commas) : ble_sniff,ble_scan [QUESTION] Input parameter #1 (name) : INTERFACE [QUESTION] Input parameter #1 (default value) : hci0 [QUESTION] Input parameter #2 (name) : PARAM1 [QUESTION] Input parameter #2 (default value) : value1 [QUESTION] Input parameter #3 (name) : [SUCCESS] Module newmod successfully generated : /home/user/.mirage/modules/newmod.py It will generates the following code : :: from mirage.core import module from mirage.libs import utils,ble class newmod(module.WirelessModule): def init(self): self.technology = "ble" self.type = "test" self.description = "Test module" self.args = {'INTERFACE': 'hci0', 'PARAM1': 'value1'} self.dependencies = ["ble_sniff","ble_scan"] def run(self): # Enter your code here. return self.ok({}) Understanding the module environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As you can see in the generated code, every module inherits from ``WirelessModule`` (`mirage.core.module.WirelessModule `_) and implements two main methods : * ``init`` : this method allows to initialize the module. It is mainly used in order to fill the required attributes, such as ``technology`` (indicating the technology used by this module), ``type`` (indicating the type of module), ``description`` (short string describing the role of the module) ``args`` (dictionnary indicating the named input parameters and their default values) and ``dependencies`` (array containing the name of the modules needed in order to execute the module). * ``run`` : this method implements the behaviour of the module : it is executed when the module is launched. If you need to execute some actions before or after this method, you can use two optional methods named ``prerun`` (launched before ``run``) and ``postrun`` (launched after ``run``). In the ``run`` method, you have to terminate the method execution by returning a dictionary composed of two fields, **success** and **output**. Two helpers methods allows you to easily generate the right output format : * ``ok`` (`mirage.core.module.Module.ok `_) : this method returns the dictionary indicating that the module execution is successful. The dictionary of output parameters can be passed as an argument in order to output some specific values. * ``nok`` (`mirage.core.module.Module.ok `_) : this method returns the dictionary indicating that the module execution has failed. :: return self.ok({"INTERFACE":"hci0"}) # The module execution is successful, it returns an output parameter named INTERFACE # or return self.ok() # The module execution is successful, it returns no parameters # or return self.nok() # The module execution is not successful Manipulating the input parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Every module can use some input parameters. These parameters are defined in the ``args`` dictionnary, which is an attribute of the module class, and can be modified by the user. :: # The arguments are defined in the init method, here there are six arguments : # - INTERFACE : this parameter has a default value (the string "hci0") # - ARGUMENT_A : this parameter has no default value # - ARGUMENT_B : this parameter has a default value (the string "yes") # - ARGUMENT_C : this parameter has a default value (the string "0x1234") # - ARGUMENT_D : this parameter has a default value (the string "aa:bb:cc:dd:ee:ff") # - ARGUMENT_E : this parameter has a default value (the string "one,two,three") def init(self): # [...] self.args = { "INTERFACE":"hci0", "ARGUMENT_A":"", "ARGUMENT_B":"yes", "ARGUMENT_C":"0x1234", "ARGUMENT_D":"aa:bb:cc:dd:ee:ff", "ARGUMENT_E":"one,two,three" } As you can see, every input parameter is provided as a string, allowing to easily manipulate it from the command line. However, this string is generally used to transmit a "typed" value to the module (e.g. a boolean or an integer) : Mirage provides multiple functions to check and convert the value provided. These functions are defined in the ``utils`` library (`mirage.libs.utils `_). For example, the function ``booleanArg`` (`mirage.libs.utils.booleanArg `_) allows you to easily convert a specific argument to a boolean. The expected values are : * "yes","true","1" indicate **True** * "no","false","0" indicate **False** (every other string is converted to **False**) For example, if you want to convert the input parameter named *ARGUMENT_B* to a boolean, you can use : :: booleanValue = utils.booleanArg(self.args["ARGUMENT_B"]) # booleanValue contains True You can also convert a string to an integer value using the function ``integerArg`` (`mirage.libs.utils.integerArg `_). The provided value can be an hexadecimal number (e.g. "0x1a2b" or "aabb112233") or a decimal number (e.g. "42" or "-23"). The following example converts the input parameter *ARGUMENT_C* to an integer : :: integerValue = utils.integerArg(self.args["ARGUMENT_C"]) # integerValue contains 4660 .. note:: Please note that some functions of the library ``utils`` (e.g. ``isNumber`` or ``isHexadecimal``) allows to check if the provided string is a decimal or an hexadecimal number. You can refer to (`the corresponding documentation `_) for more details. It's quite common to manipulate some MAC or BD addresses during the analysis of a wireless communications, that's why Mirage provides another conversion function, named ``addressArg`` (`mirage.libs.utils.addressArg `_), in order to convert it to an address usable by Mirage. Mirage converts addresses to uppercase string in order to facilitate the comparison between them. The following example converts the input parameter *ARGUMENT_D* to an address : :: addressValue = utils.addressArg(self.args["ARGUMENT_D"]) # addressValue contains "AA:BB:CC:DD:EE:FF" Finally, some input parameters can contain a list of strings, splitted by commas (e.g. "one,two,three" <=> ["one","two","three"]). You can easily convert them to a list of string thanks to the function ``listArg`` (`mirage.libs.utils.listArg `_). The following eaxample converts the input parameter *ARGUMENT_E* to a list of string : :: listValue = utils.listArg(self.args["ARGUMENT_E"]) # listValue contains ["one","two","three"] Interacting with the user ^^^^^^^^^^^^^^^^^^^^^^^^^^ Mirage provides various functions in order to print datas to the user or asking him some informations. These functions are mainly defined in the ``io`` library (`mirage.libs.io `_). It allows to standardize the behaviour of modules in terms of user interaction. Four different functions can be used to display a message, according to the type of information you want to print. First, you can print an information message using the ``info`` function (`mirage.libs.io.info `_) : :: io.info("This is an information message.") Second, you can display a warning message thanks to the ``warning`` function (`mirage.libs.io.warning `_) : :: io.warning("This is a warning message.") Then, you can print a failure message using the ``fail`` function (`mirage.libs.io.fail `_): :: io.fail("This is a failure message.") Finally, you can print a success message using the ``success`` function (`mirage.libs.io.success `_) : :: io.success("This is a success message.") .. warning:: Please keep in mind that the user can control the verbosity level of Mirage. You can refer to `this page of the documentation `_ if you need more information about this feature. You can also display a table using the function named ``chart`` (`mirage.libs.io.chart `_) : :: io.chart(["A","B","A xor B"],[ ["False","False","False"], ["True","False","True"], ["False", "True", "True"], ["True", "True", "False"] ],title="XOR Table") The corresponding output is : :: ┌XOR Table──────┬─────────┐ │ A │ B │ A xor B │ ├───────┼───────┼─────────┤ │ False │ False │ False │ │ True │ False │ True │ │ False │ True │ True │ │ True │ True │ False │ └───────┴───────┴─────────┘ The ``progress`` function (`mirage.libs.io.progress `_) displays a progress bar (it must be called multiple times in order to display the loading process) : :: for i in range(10): io.progress(i*10,total=100,suffix="fuzzing...") utils.wait(seconds=1) This example displays something like : :: ())))))))))))))))))))))))))))))))))))))))__________________) fuzzing... Finally, you may need to ask your user to answer a question or provide an input. The ``ask`` function (`mirage.libs.io.ask `_) is dedicated to this operation : :: answer = io.ask("Enter your age", default=25,final=": ") This function returns the string entered by the user and this example displays the following line : :: [QUESTION] Enter your age [25] : 26 Launching tasks in background ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Mirage allows modules to run some tasks in background, in a daemon child process. This task may be persistent and can continue its execution after the end of the module execution (some commands allow to manipulate these background tasks in the command-line interpreter, please refer to the `corresponding documentation `_ if needed). It provides a simple API included in the ``utils`` library (`mirage.libs.utils `_). Let's imagine you have written a new method named ``helloworld`` : :: def helloworld(self): while True: print("HelloWorld") If you want to register it as a background task, you can use the function named ``addTask`` (`mirage.libs.utils.addTask `_) : :: task = utils.addTask(function=self.helloworld) This function returns the name of the task created. Using this information, you can start the task using ``startTask`` (`mirage.libs.utils.startTask `_) : :: utils.startTask(task) You can also restart it using ``restartTask`` (`mirage.libs.utils.restartTask `_) : :: utils.restartTask(task) Or stop it using ``stopTask`` (`mirage.libs.utils.stopTask `_) : :: utils.stopTask(task) Manipulating the time-related functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Mirage provides two main function included in the ``utils`` library (`mirage.libs.utils `_), allowing to easily wait for a given amount of time or get the current timestamp. For example, the function ``now``(`mirage.libs.utils.now `_) returns the current timestamp : :: currentTimestamp = utils.now() There is also a function named ``wait`` (`mirage.libs.utils.wait `_), allowing to suspend the module execution during the time specified in arguments : :: utils.wait(seconds=42,minutes=25,hours=1) This line of code pauses the module execution during 1 hour, 25min and 42 s. Manipulating a module from another one ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you want to manipulate a module from another one, you can use some helpers functions included in ``utils`` (`mirage.libs.utils `_). First of all, include this library using the following line : :: from mirage.libs import utils Then, you can use the ``loadModule`` (`mirage.libs.utils.loadModule `_) function in order to load a specific module : :: mod = utils.loadModule("ble_info") You can provide some input parameters using the following syntax : :: mod["INTERFACE"] = "hci1" Finally you can execute the module using the following method : :: output = mod.execute() The returned value is a dictionary composed of two main fields: * **success** : boolean indicating if the execution was successful * **output** : dictionary including the output parameters of the module :: if output["success"]: io.success("Successful execution !") io.info("Output parameters :") for outputParameterName,outputParameterValue in output.items(): io.info(outputParameterName + " = " + outputParameterValue) else: io.fail("An error occured during the execution !") Integrating scenarios in your module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you want to integrate scenarios in your module, please refer to `the following documentation `_.