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))
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 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)