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.

_images/archi.png

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

_images/shared_methods.png

Please refer to the 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.