Emitters and Receivers ======================= Presentation ------------ One of the biggest problem in order to write offensive security tools for IoT is to deal with multiple hardware components. For example, you can communicate with a Bluetooth Low Energy device using an HCI dongle, but there are also a lot of sniffers available (such as the implementation of BTLEJack on the BBC Micro:Bit or the Ubertooth One). There is one main problem : every hardware component implements some specific features and provides a different API, leading the security analyst to learn a lot of technical informations that are not directly linked to the protocol itself. Mirage tries to solve this problem by providing an user-friendly API in order to manipulate the protocol stacks. In order to harmonize the use of multiple hardware components, it provides only two main software components allowing to easily manipulate them from the modules : the ``Emitters`` and ``Receivers``. Generally, a specific hardware component is able to send datas and/or receive datas, but it can also provide some specific behaviour, such as address or channel modification. Mirage provides a generic communication architecture based on three components : * ``Device`` (`mirage.libs.wireless_utils.device.Device `_) : this class implements the communication with the hardware component itself. It exposes four main methods : * **init** : this method allows to initialize the hardware component * **isUp** : this method returns a boolean indicating if the hardware component is up and initialized * **send** : this method sends a frame (if possible) * **recv** : this method returns a received frame (if possible) The devices manipulates only the frames formatted as needed by the hardware component (e.g. bytes array or scapy frames). If a specific hardware component provides additional features, they can be implemented easily as standard methods, and if the method's name is appended to the array ``sharedMethods`` (which is a class attribute of ``Device``), the method is available from the module environment. * ``Emitter`` (`mirage.libs.wireless.Emitter `_) : this class is linked to the protocol itself. It communicates with a specific device chosen by the user according to the selected interface. Its main role is to provides an user-friendly API dedicated to sending datas, but it can also acts as a software proxy if it has instantiated a ``Device`` exposing additional methods. It allows the modules developpers to get a standardized API to send frames, but also to call theses additional methods directly from the module environment. An ``Emitter`` doesn't use "raw" frames, but an abstract representation of a frame, which is a child class of ``Packet`` (`mirage.libs.wireless_utils.packets.Packet `_) . It allows to use an abstract representation of a frame allowing to integrate some dissectors, builders, \.\.\. and also to have one representation for one specific packet, even if the frame formats expected by the devices are not the same (for example, HCI Devices needs HCI frames while sniffers uses the Link Layer format). As a consequence, every ``Emitter`` must overload the method named ``convert``. Its role is to transform a Mirage ``Packet`` into the corresponding raw frames. * ``Receiver`` (`mirage.libs.wireless.Receiver `_) : this class is linked to the protocol itself, and similarly it communicates with the Devices according to the interface parameter. One of the most important role of a Receiver is to allow to receive datas from the devices as raw frames, to convert them into Mirage Packet, thanks to the ``convert`` method (overloaded). A Receiver provides a mechanism of callbacks allowing to receive frame asynchronously (``onEvent``), but multiple methods (such as ``receive``, ``next`` or ``skip``) allows to get the corresponding Mirage Packet synchronously. It allows to access the shared methods implemented on a specific device thanks to the implementation of the **Proxy** device pattern. .. image:: archi.png :align: center Using an existing Emitter or Receiver -------------------------------------- Mirage provides an easy way to instantiate an Emitter or a Receiver from a module environment. Two methods allow to get the right Emitter or Receiver according to the ``technology`` attribute of your module : ``getEmitter`` (`mirage.core.module.WirelessModule.getEmitter `_) and ``getReceiver`` (`mirage.core.module.WirelessModule.getReceiver `_): :: self.receiver = self.getReceiver(interface="hci0") self.emitter = self.getEmitter(interface="hci0") If you want to send a frame using an Emitter, just use the ``send`` (`mirage.libs.wireless.Emitter.send `_) or ``sendp`` (`mirage.libs.wireless.Emitter.sendp `_) method : :: packet1 = ble.BLEReadRequest(handle=0x0021) self.emitter.sendp(packet1) packet2 = ble.BLEWriteCommand(handle=0x0025,value=b"\x01\x02\x03\x04") self.emitter.sendp(packet1,packet2) Two solutions exists if you want to receive a frame from a packet : * you can easily set a callback on a specific event (e.g. the reception of a specific type of packet), using the ``onEvent`` method. This method allows to receive packets asynchronously, by linking a callback function to an *event*, which is a string indicating when should the callback be called. Three formats exists describing an event : * *\** : indicating "the callback is called every times a packet is received" * *n* : indicating "the callback is called every times n packets have been received" * *packetType* : indicating "the callback is called every times a packet of type 'packetType' is received" Some examples are represented in the following table: +----------------------+-------------------------------+ | Event | Description | +======================+===============================+ | \* | every packet | +----------------------+-------------------------------+ | 3 | every 3 packets | +----------------------+-------------------------------+ | BLEReadRequest | every BLE Read Request | +----------------------+-------------------------------+ :: def show(self,packet): packet.show() def onReadRequest(self,packet,username): io.info("Hello "+username+", I have an incoming Read Request for you : "+str(packet)) def run(self): self.receiver = self.getReceiver(interface="hci0") self.receiver.onEvent("*", callback=self.show) receiver.onEvent("BLEReadRequest",callback=self.onReadRequest, args=["Romain"]) * you can also use the synchronous methods, such as ``next`` (`mirage.libs.wireless.Receiver.next `_) or ``receive`` (`mirage.libs.wireless.Receiver.receive `_) : :: packet = self.receiver.next(timeout=1.0) if packet is not None: packet.show() else: io.info("Timeout !") for packet in self.receiver.receive(nb=5): packet.show() Finally, you can use the shared methods of the currently instantiated ``Device`` from a module environment thanks to the implementation of the Proxy design pattern. The ``hasCapabilities`` (`mirage.libs.wireless_utils.device.Device.hasCapabilities `_) method allows to easily check if the currently instantiated device is able to perform a specific action. .. image:: shared_methods.png :align: center Please refer to the :doc:`corresponding documentation ` to discover the available shared methods. :: if self.receiver.hasCapabilities("SCANNING"): self.receiver.setScan(enable=True,passive=False) if self.emitter.hasCapabilities("SCANNING"): self.emitter.setScan(enable=True,passive=True) Integrating a new protocol --------------------------- One of the main advantages of Mirage is that a new protocol can be easily added. First, the developer has to implement a new child class of ``Device`` (`mirage.libs.wireless_utils.device.Device `_), allowing interaction with a given specific hardware. This class should provide only four main methods: * **init** : initializing the hardware component * **isUp** : indicating if the hardware can be used * **recv** : allowing to receive frames * **send** : allowing to transmit frames As a result, developing a driver is straightforward. Some specific features can also be added by creating a new method and appending its name to the array ``sharedMethods`` (which is a class attribute of every child classes of ``Device``, allowing them to be called from a module environment. If you want to provide the capabilities of your device, just use the instance attribute ``capabilities`` (array of string indicating the available capabilities. At least one of the two child classes of ``Receiver`` (`mirage.libs.wireless.Receiver `_) or ``Emitter`` (`mirage.libs.wireless.Emitter `_) must be implemented. They initialize the device previously defined in their constructor and must implement the convert method, allowing to convert a binary frame into an abstraction or an abstract representation of a frame into an array of bytes. Of course, it needs you to implement the available packets as child classes of ``Packet`` (`mirage.libs.wireless_utils.packets.Packet `_). Finally, you must register your Emitter and/or your Receiver, using the ``registerEmitter`` (`mirage.core.module.WirelessModule.registerEmitter `_) and ``registerReceiver`` (`mirage.core.module.WirelessModule.registerReceiver `_) class methods. It allows to link your Emitter and Receiver to a specific technology, allowing to use the ``getEmitter`` and ``getReceiver`` methods from a module environment to get the right Emitter / Receiver instance according to the ``technology`` attribute. For example, the ``BLEEmitter`` and ``BLEReceiver`` are linked to the technology *"ble"* using the following code : :: WirelessModule.registerEmitter("ble",BLEEmitter) WirelessModule.registerReceiver("ble",BLEReceiver) As a result, the protocol is fully integrated and can be used from modules.