def profile(self, packet):
        try:
            if self.db_object is None and packet.dev_eui and packet.data_collector_id:
                self.db_object = Device.find_with(
                    dev_eui=packet.dev_eui,
                    data_collector_id=packet.data_collector_id)

            if (packet.tmst is None) or (packet.f_count is None):
                return

            if self.last_count is None or self.last_tmst is None:
                self.last_count = packet.f_count
                self.last_tmst = packet.tmst
            else:
                tdiff, cdiff = self.calculate_differences(packet)
                if cdiff > 0:
                    new_measure = self.tdiff_profiler.profile(tdiff)
                    if new_measure and self.db_object:
                        self.db_object.activity_freq = self.tdiff_profiler.median

                    self.cdiff_profiler.profile(cdiff)

                    self.rssi_profiler[packet.gateway].profile(packet.rssi)
                    self.size_profiler.profile(packet.size)

                    self.initialized = True
                    self.last_count = packet.f_count
                    self.last_tmst = packet.tmst
                    self.last_date = packet.date

        except Exception as exc:
            log.error("Error profiling packet:\n{0}".format(exc))
Example #2
0
def process_packet(packet, policy):

    device = Device.find_with(dev_eui=packet.dev_eui,
                              data_collector_id=packet.data_collector_id)

    if device:
        parameters.update(policy.get_parameters("LAF-501"))
        jr_regularity_checker[device.id].is_anomaly(packet,
                                                    device=device,
                                                    policy=policy)
        jr_regularity_checker[device.id].profile(packet)

    if packet.m_type == "ConfirmedDataUp":
        device_session = DeviceSession.find_with(
            dev_addr=packet.dev_addr,
            data_collector_id=packet.data_collector_id)
        if device_session is None: return

        parameters.update(policy.get_parameters("LAF-503"))

        profiler = device_profilers[device_session.id]
        is_outlier, probabilities, _ = profiler.is_anomaly(packet)
        if is_outlier and policy.is_enabled("LAF-503"):
            AlertGenerator.emit_alert("LAF-503",
                                      packet,
                                      device_session=device_session)

        device_profilers[device_session.id].profile(packet)

    garbage_collection(packet.date)
 def link_device_session(self, dev_eui, dev_addr, data_collector_id):
     """
     Links the device_session and a device.
     """
     device = Device.find_with(dev_eui=dev_eui,
                               data_collector_id=data_collector_id)
     device_session = DeviceSession.find_with(
         dev_addr=dev_addr, data_collector_id=data_collector_id)
     device_session.device_id = device.id
 def unlink_device_session(self, dev_eui, dev_addr, data_collector_id):
     """
     Unlinks the device and the device_session. If the device or session are 
     not instantiated yet, raises an exception. If no session is linked with
     the device, raises an exception.
     """
     device = Device.find_with(dev_eui=dev_eui,
                               data_collector_id=data_collector_id)
     device_session = DeviceSession.find_with(device_id=device.id)
     device_session.device_id = None
 def get_deveui_from_session(self, packet):
     """
     Looks for the device_session of the packet, from this object gets the
     device_id and from this finally gets the dev_eui. If one step fails raises an exception.
     """
     device_session = DeviceSession.find_with(
         dev_addr=packet.dev_addr,
         data_collector_id=packet.data_collector_id)
     device = Device.get(device_session.device_id)
     packet.dev_eui = device.dev_eui
     return packet
def emit_alert(alert_type, packet, device=None, device_session=None, gateway=None, device_auth_id=None, **custom_parameters):
    try:
        if gateway is None:
            gateway = Gateway.find_with(gw_hex_id = packet.gateway, data_collector_id = packet.data_collector_id)
        if device is None:
            device = Device.find_with(dev_eui=packet.dev_eui, data_collector_id=packet.data_collector_id)
        if device_session is None and gateway:
            device_session = DeviceSession.find_with(dev_addr=packet.dev_addr, data_collector_id=packet.data_collector_id)
        if device is None and device_session and device_session.device_id:
            device = Device.get(device_session.device_id)
    except Exception as exc:
        logging.error(f"Error guessing device/gateway/session to emit alert: {exc}")
    try:
        now = datetime.datetime.now().strftime(DATE_FORMAT)
        parameters = {}
        parameters['packet_id'] = packet.id
        parameters['packet_date'] = packet.date.strftime(DATE_FORMAT)
        parameters['packet_data'] = packet.to_json()
        parameters['created_at'] = now
        parameters['dev_eui'] = device.dev_eui if device and device.dev_eui else None
        parameters['dev_name'] = device.name if device and device.name else None
        parameters['dev_vendor'] = device.vendor if device and device.vendor else None
        parameters['dev_addr'] = device_session.dev_addr if device_session and device_session.dev_addr else None
        parameters['gateway'] = gateway.gw_hex_id if gateway and gateway.gw_hex_id else None
        parameters['gw_name'] = gateway.name if gateway and gateway.name else None
        parameters['gw_vendor'] = gateway.vendor if gateway and gateway.vendor else None

        parameters.update(custom_parameters)

        if 'prev_packet_id' in custom_parameters:
            prev_packet = Packet.find_one(custom_parameters['prev_packet_id'])
            if prev_packet:
                parameters['prev_packet_data'] = prev_packet.to_json()

        global alert_blocked_by
        blocked = False

        try:
            for blocking_issue in alert_blocked_by.get(alert_type, []):
                if Issue.has_the_issue(
                    issue_type=blocking_issue,
                    device_id=device.id if device else None,
                    gateway_id=gateway.id
                ):
                    blocked = True
                    break
        except:
            pass # We can't check if the issue exists, then, we let blocked=False
        
        alert = Alert(
            type = alert_type,
            device_id = device.id if device and device.id else None,
            device_session_id = device_session.id if device_session and device_session.id else None,
            gateway_id = gateway.id if gateway and gateway.id else None,
            device_auth_id = device_auth_id,
            data_collector_id = packet.data_collector_id,
            created_at = now,
            packet_id = packet.id,
            parameters= json.dumps(parameters),
            show = not blocked)
        alert.save()
  
        # ReportAlert.print_alert(alert)

        if not blocked:
            params = {
                'data_collector_id': packet.data_collector_id,
                'organization_id': packet.organization_id,
                'alert_id': alert.id,
                'alert_type': alert.type,
            }
            emit_alert_event('NEW', params)

        is_an_issue = any([alert_type in l for l in alert_blocked_by.values()])
        try:
            if is_an_issue: Issue.upsert(packet.date, alert)
        except:
            pass # We can't upsert the issue, then, do nothing

    except Exception as exc:
        logging.error(f"Error trying to emit alert {alert_type}: {exc}")
def process_packet(packet, policy):
    result = ""
    key_tested = False
    global device_auth_obj
    global dontGenerateKeys
    global keys
    global hours_betweeen_bruteforce_trials
    device_auth_obj = None

    if packet.m_type not in ("JoinRequest", "JoinAccept"): return
    if not policy.is_enabled("LAF-009"): return
    if packet.dev_eui is None: return

    if packet.m_type == "JoinRequest":

        # device_obj = ObjectInstantiator.get_or_error_device(packet)
        device_obj = Device.find_with(
            dev_eui=packet.dev_eui, data_collector_id=packet.data_collector_id)
        if not device_obj: return

        # Before cracking with many different keys, try with a PotentialAppKey previously found. In case this key is valid, we are pretty sure that is the correct AppKey
        device_auth_obj = DeviceAuthData.find_one_by_device_id(device_obj.id)
        if device_auth_obj and extractMIC(
                device_auth_obj.join_request) != packet.mic:
            pot_app_keys = [
                pk.app_key_hex for pk in
                PotentialAppKey.find_all_by_device_auth_id(device_auth_obj.id)
            ]

            if len(pot_app_keys) > 0:
                correct_app_keys = LorawanWrapper.testAppKeysWithJoinRequest(
                    keys=[bytes(pk, encoding='utf-8') for pk in pot_app_keys],
                    data=packet.data,
                    dontGenerateKeys=True).split()
                correct_app_keys = [
                    ak.upper().rstrip() for ak in correct_app_keys
                ]
                key_tested = True

                pk_to_remove = [
                    pk for pk in pot_app_keys if pk not in correct_app_keys
                ]
                PotentialAppKey.delete_keys(
                    device_auth_data_id=device_auth_obj.id, keys=pk_to_remove)

                if len(correct_app_keys) > 1:
                    logging.warning(
                        f"Found more than one possible keys for the device {packet.dev_eui}."
                        +
                        f" One of them should be the correct. Check it manually. Keys: {correct_app_keys}"
                    )
                elif len(correct_app_keys) == 1:
                    # AppKey found!!
                    device_auth_obj.second_join_request_packet_id = packet.id
                    device_auth_obj.second_join_request = packet.data
                    device_auth_obj.app_key_hex = correct_app_keys[0]

                    emit_alert(
                        "LAF-009",
                        packet,
                        device=device_obj,
                        device_auth_id=device_auth_obj.id,
                        app_key=correct_app_keys[0],
                        packet_id_1=device_auth_obj.join_request_packet_id,
                        packet_type_1="JoinRequest",
                        packet_type_2="JoinRequest")
                    return

        # Check if the DeviceAuthData wasn't already generated
        never_bruteforced = False

        if device_auth_obj is None:
            never_bruteforced = True
            try:
                device_auth_obj = DeviceAuthData(
                    device_id=device_obj.id,
                    data_collector_id=packet.data_collector_id,
                    organization_id=packet.organization_id,
                    join_request=packet.data,
                    created_at=packet.date,
                    join_request_packet_id=packet.id)
                device_auth_obj.save()
            except Exception as exc:
                logging.error(
                    "Error trying to save DeviceAuthData at JoinRequest: {0}".
                    format(exc))

        # Check when was the last time it was bruteforced and
        # Try checking with the keys dictionary, the keys generated on the fly
        # and the keys uploaded by the corresponding organization
        elapsed = packet.date - device_auth_obj.created_at  # Time in seconds

        if elapsed > datetime.timedelta(hours=hours_betweeen_bruteforce_trials
                                        ) or never_bruteforced or True:

            result = LorawanWrapper.testAppKeysWithJoinRequest(
                keys, packet.data, dontGenerateKeys)
            organization_keys = [
                bytes(app_key.key.upper(), encoding='utf-8')
                for app_key in AppKey.get_with(
                    organization_id=packet.organization_id)
            ]
            result_org_keys = LorawanWrapper.testAppKeysWithJoinRequest(
                organization_keys, packet.data, dontGenerateKeys=True)
            if result_org_keys != "":
                result += " " + result_org_keys

            key_tested = True

            # Update the last time it was bruteforced
            device_auth_obj.created_at = packet.date

            # If potential keys found...
            if result != "":

                device_auth_obj.join_request_packet_id = packet.id
                device_auth_obj.join_request = packet.data

                # Split string possibly containing keys separated by spaces
                candidate_keys_array = set(result.split())

                for hex_key in candidate_keys_array:
                    try:
                        # Save the potential app key if it does not exists already in the DB
                        potential_key_obj = PotentialAppKey.get_by_device_auth_data_and_hex_app_key(
                            device_auth_data_id=device_auth_obj.id,
                            app_key_hex=hex_key.upper())
                        if not potential_key_obj:
                            potential_key_obj = PotentialAppKey(
                                app_key_hex=hex_key.upper(),
                                organization_id=packet.organization_id,
                                last_seen=packet.date,
                                packet_id=packet.id,
                                device_auth_data_id=device_auth_obj.id)
                            potential_key_obj.save()
                    except Exception as exc:
                        logging.error(
                            "Error trying to save PotentialAppKey at JoinRequest: {0}"
                            .format(exc))

    elif packet.m_type == "JoinAccept" and packet.data is not None:

        last_seconds_date = packet.date - datetime.timedelta(seconds=5)

        try:
            organization_keys = PotentialAppKey.find_all_by_organization_id_after_datetime(
                packet.organization_id, last_seconds_date)

            # Return if no JR keys were found
            if len(organization_keys) != 0:
                keys_array = list()
                for pk in organization_keys:
                    # Fetch keys in byte format. Needed by ctypes
                    keys_array.append(
                        bytes(pk.app_key_hex.rstrip().upper(),
                              encoding='utf-8'))

                # Remove possible duplicates in array
                keys_array = list(dict.fromkeys(keys_array))

                result = LorawanWrapper.testAppKeysWithJoinAccept(
                    keys_array, packet.data, True)
                key_tested = True
        except Exception as es:
            logging.error(f"Error trying to bforce JA: {es}")

        if result != "":

            # Clean the key string
            result = result.rstrip().upper()

            for potential_key_obj in organization_keys:
                if potential_key_obj.app_key_hex.upper() == result:
                    device_auth_obj = DeviceAuthData.find_one_by_id(
                        potential_key_obj.device_auth_data_id)
                    break

            if device_auth_obj:
                #Add missing data
                device_auth_obj.join_accept = packet.data
                device_auth_obj.join_accept_packet_id = packet.id
                device_auth_obj.app_key_hex = result

                # Add session keys
                device_auth_obj = deriveSessionKeys(device_auth_obj, result)

                # Get the device to get dev_eui
                device_obj = Device.get(device_auth_obj.device_id)

                # Get DevAddr from JA packet
                dev_addr = LorawanWrapper.getDevAddr(result, packet.data)

                emit_alert("LAF-009",
                           packet,
                           device=device_obj,
                           device_auth_id=device_auth_obj.id,
                           app_key=result,
                           dev_addr=dev_addr,
                           packet_id_1=device_auth_obj.join_request_packet_id,
                           packet_type_1="JoinRequest",
                           packet_type_2="JoinAccept")
            else:
                logging.error(
                    "Cracked a JoinAccept but no device_auth object found")

    if key_tested and len(result) == 0:
        res_comment = "The AppKey was modified"
        issue_solved = Issue.solve(
            resolution_reason=res_comment,
            date=packet.date,
            issue_type="LAF-009",
            device_id=device_obj.id,
        )
        if issue_solved:
            emit_alert("LAF-600",
                       packet,
                       device=device_obj,
                       alert_solved_type="LAF-009",
                       alert_solved=AlertType.find_one_by_code("LAF-009").name,
                       resolution_reason=res_comment)