Source code for src.detector.plugins.domainator_attributor

from src.detector.detector import DetectorBase
import numpy as np
from collections import defaultdict
import itertools
import pylcs
import Levenshtein

from src.base.log_config import get_logger
from src.detector.plugins.domainator_utils import (
    strip_domain,
    get_domainator_features,
)

module_name = "data_analysis.detector"
logger = get_logger(module_name)

LEGITIMATE_ATTRIBUTE_LABELS = {"benign", "legit", "legitimate", "legitimate_legitimate"}


[docs] class DomainatorAttributor(DetectorBase): """ Detector implementation for the attribution of a tool or malware that was used in the data exfiltration and command and control, using the subdomain level labels. This class extends the DetectorBase to provide specific functionality for identifying the tool sending the malicious queries. It analyzes subdomain similarity characteristics based on grouping of the queries in windows of fixed size, similar to the Domainator detector approach. It can be used both as a standalone detector or as a next stage in a pipeline of detectors, dependent on the provided model. The identity detector extracts various statistical similarity features from windows of subdomains to make predictions about what tool likely sent the malicious query or what job/behaviour was observed. """ def __init__( self, detector_config, consume_topic, produce_topics=None, downstream_detector_topics=None, ): """ Initialize the Domainator attributor with configuration parameters. Sets up the detector with the model base URL and passes configuration to the base class for standard detector initialization. Args: detector_config (dict): Configuration dictionary containing detector-specific parameters including base_url, model, checksum, and threshold. consume_topic (str): Kafka topic from which the detector will consume messages. """ self.model_base_url = detector_config["base_url"] self.message_queues = defaultdict(list) super().__init__( detector_config, consume_topic, produce_topics, downstream_detector_topics ) self.labels = self.model.classes_ def predict(self, messages): """ Process a window of messages and predict what tool was likely used to sent a potentially malicious exfiltration and communication. Extracts features from the subdomains in the messages and uses the loaded machine learning model to generate prediction probabilities. Args: message (list): A list containing the messages data, expected to have a "domain_name" key with the domain to analyze. Returns: np.ndarray: Prediction probabilities for each class. Typically a 2D array where the shape is (1, 2) for binary classification (benign/malicious). """ queries = [message["domain_name"] for message in messages] y_pred = self.model.predict_proba(get_domainator_features(queries)) return y_pred def detect(self): logger.info("Start detecting malicious requests.") for message in self.messages: if isinstance(message, list): # This assumes the current example for the request structure. # Both DomainatorDetector and DomainatorAttributor provide a list to the 'request' key, # due to their structure of processing a window (list) of incoming messages. # Would be better to have a key:value pair in request that defines the domain name outside of the list? message_domain = strip_domain(message[0]["domain_name"]) self.message_queues[message_domain].extend(message) else: message_domain = strip_domain(message["domain_name"]) self.message_queues[message_domain].append(message) if len(self.message_queues[message_domain]) >= 3: y_pred = self.predict(self.message_queues[message_domain]) logger.info(f"Prediction: {y_pred}") winning_index = int(np.argmax(y_pred, axis=1)[0]) winning_label = self.labels[winning_index] winning_probability = float(y_pred[0][winning_index]) y_pred_labelled = [ {"attribute": label, "probability": float(score)} for label, score in zip(self.labels, y_pred[0]) if score >= self.threshold ] logger.debug(f"Prediction with labels: {y_pred_labelled}") is_legitimate = winning_label in LEGITIMATE_ATTRIBUTE_LABELS if not is_legitimate and winning_probability >= self.threshold: logger.debug("Append malicious request domain to warning.") warning = { "request": self.message_queues[message_domain], "probability": winning_probability, "predicted_class": winning_label, "attributes": y_pred_labelled, "name": self.name, "sha256": self.checksum, } self.warnings.append(warning) if len(self.message_queues[message_domain]) >= 10: del self.message_queues[message_domain][0]