예제 #1
0
 def __init__(self, rule_id, min_time_delta, max_time_delta, max_artefacts_a_for_single_b=1, artefact_match_parameters=None):
     """Create the correlation rule.
     @param artefact_match_parameters if not none, two artefacts A and B will be only treated as correlated when all the
     parsed artefact attributes identified by the list of attribute path tuples match.
     @param min_time_delta minimal delta in seconds, that artefact B may be observed after artefact A. Negative values are allowed
     as artefact B may be found before A. """
     self.rule_id = rule_id
     self.min_time_delta = min_time_delta
     self.max_time_delta = max_time_delta
     self.max_artefacts_a_for_single_b = max_artefacts_a_for_single_b
     self.artefact_match_parameters = artefact_match_parameters
     self.history_a_events = []
     self.history_b_events = []
     self.last_timestamp_seen = 0.0
     self.correlation_history = LogarithmicBackoffHistory(10)
예제 #2
0
 def __init__(self, object_history=None, debug_match_result=False, match_action=None):
     """Create a DebugHistoryMatchRule object. @param object_history use this ObjectHistory to collect the
     LogAtoms. When None, a default LogarithmicBackoffHistory for 10 items."""
     if object_history is None:
         object_history = LogarithmicBackoffHistory(10)
     elif not isinstance(object_history, ObjectHistory):
         raise Exception('objectHistory is not an instance of ObjectHistory')
     self.object_history = object_history
     self.debug_match_result = debug_match_result
     self.match_action = match_action
예제 #3
0
 def __init__(self, ruleId, minTimeDelta, maxTimeDelta, maxArtefactsAForSingleB=1,
              artefactMatchParameters=None):
   """Create the correlation rule.
   @param artefactMatchParameters if not none, two artefacts
   A and B will be only treated as correlated when all the
   parsed artefact attributes identified by the list of attribute
   path tuples match.
   @param minTimeDelta minimal delta in seconds, that artefact
   B may be observed after artefact A. Negative values are allowed
   as artefact B may be found before A.
   """
   self.ruleId = ruleId
   self.minTimeDelta = minTimeDelta
   self.maxTimeDelta = maxTimeDelta
   self.maxArtefactsAForSingleB = maxArtefactsAForSingleB
   self.artefactMatchParameters = artefactMatchParameters
   self.historyAEvents = []
   self.historyBEvents = []
   self.lastTimestampSeen = 0.0
   self.correlationHistory = LogarithmicBackoffHistory(10)
예제 #4
0
 def __init__(self,
              object_history=None,
              debug_match_result=False,
              match_action=None):
     """
     Create a DebugHistoryMatchRule object.
     @param object_history use this ObjectHistory to collect the LogAtoms. When None, a default LogarithmicBackoffHistory for 10 items.
     """
     if object_history is None:
         object_history = LogarithmicBackoffHistory(10)
     elif not isinstance(object_history, ObjectHistory):
         msg = 'object_history is not an instance of ObjectHistory'
         logging.getLogger(AMinerConfig.DEBUG_LOG_NAME).error(msg)
         raise Exception(msg)
     self.object_history = object_history
     self.debug_match_result = debug_match_result
     self.match_action = match_action
예제 #5
0
class CorrelationRule:
  """This class defines a correlation rule to match artefacts
  A and B, where a hidden event A* always triggers at least one
  artefact A and the the hidden event B*, thus triggering also
  at least one artefact B."""
  def __init__(self, ruleId, minTimeDelta, maxTimeDelta, maxArtefactsAForSingleB=1,
               artefactMatchParameters=None):
    """Create the correlation rule.
    @param artefactMatchParameters if not none, two artefacts
    A and B will be only treated as correlated when all the
    parsed artefact attributes identified by the list of attribute
    path tuples match.
    @param minTimeDelta minimal delta in seconds, that artefact
    B may be observed after artefact A. Negative values are allowed
    as artefact B may be found before A.
    """
    self.ruleId = ruleId
    self.minTimeDelta = minTimeDelta
    self.maxTimeDelta = maxTimeDelta
    self.maxArtefactsAForSingleB = maxArtefactsAForSingleB
    self.artefactMatchParameters = artefactMatchParameters
    self.historyAEvents = []
    self.historyBEvents = []
    self.lastTimestampSeen = 0.0
    self.correlationHistory = LogarithmicBackoffHistory(10)


  def updateArtefactA(self, selector, logAtom):
    """Append entry to the event history A."""
    historyEntry = self.prepareHistoryEntry(selector, logAtom)
# FIXME: Check if event A could be discarded immediately.
    self.historyAEvents.append(historyEntry)


  def updateArtefactB(self, selector, logAtom):
    """Append entry to the event history B."""
    historyEntry = self.prepareHistoryEntry(selector, logAtom)
# FIXME: Check if event B could be discarded immediately.
    self.historyBEvents.append(historyEntry)


  def checkStatus(self, newestTimestamp, maxViolations=20):
    """@return None if status is OK. Returns a tuple containing
    a descriptive message and a list of violating log data lines
    on error."""

# FIXME: This part of code would be good target to be implemented
# as native library with optimized algorithm in future.
    aPos = 0
    bPosStart = 0
    for aPos in range(0, len(self.historyAEvents)):
      aEvent = self.historyAEvents[aPos]
      if aEvent is None:
        continue
      aEventTime = aEvent[0]
      if newestTimestamp-aEventTime <= self.maxTimeDelta:
# This event is so new, that timewindow for related event has
# not expired yet.
        t = datetime.fromtimestamp(time.time())
        if newestTimestamp-aEventTime == 0 and \
                aEventTime-(t-datetime.fromtimestamp(0)).total_seconds() <= self.maxTimeDelta:
          violationLine = aEvent[3].matchElement.matchString
          if isinstance(violationLine, bytes):
            violationLine = violationLine.decode("utf-8")
          violationMessage = 'FAIL: B-Event for \"%s\" (%s) not found!' % \
                  (violationLine, aEvent[2].actionId)
          violationLogs = []
          violationLogs.append(violationLine)
          del self.historyAEvents[aPos]
          return (violationMessage, violationLogs)

      for bPos in range(bPosStart, len(self.historyBEvents)):
        bEvent = self.historyBEvents[bPos]
        if bEvent is None:
          continue
        bEventTime = bEvent[0]
        delta = bEventTime-aEventTime
        if delta < self.minTimeDelta:
# See if too early, if yes go to next element. As we will not
# check again any older aEvents in this loop, skip all bEvents
# up to this position in future runs.
          bPosStart = bPos+1
          if bPosStart == len(self.historyBEvents):
            violationLine = aEvent[3].matchElement.matchString
            if isinstance(violationLine, bytes):
              violationLine = violationLine.decode("utf-8")
              violationMessage = 'FAIL: B-Event for \"%s\" (%s) was found too early!' % \
                      (violationLine, aEvent[2].actionId)
              violationLogs = []
              violationLogs.append(violationLine)
              del self.historyAEvents[aPos]
              return (violationMessage, violationLogs)
          continue
# Too late, no other bEvent may match this aEvent
        if delta > self.maxTimeDelta:
          violationLine = aEvent[3].matchElement.matchString
          if isinstance(violationLine, bytes):
            violationLine = violationLine.decode("utf-8")
            violationMessage = 'FAIL: B-Event for \"%s\" (%s) was not found in time!' % \
                    (violationLine, aEvent[2].actionId)
            violationLogs = []
            violationLogs.append(violationLine)
            del self.historyAEvents[aPos]
            return (violationMessage, violationLogs)
          break
# So time range is OK, see if match parameters are also equal.
        checkPos = 4
        for checkPos in range(4, len(aEvent)):
          if aEvent[checkPos] != bEvent[checkPos]:
            violationLine = aEvent[3].matchElement.matchString
            if isinstance(violationLine, bytes):
              violationLine = violationLine.decode("utf-8")
              violationMessage = 'FAIL: \"%s\" (%s) %s is not equal %s' % ( \
                violationLine, aEvent[2].actionId, aEvent[checkPos], bEvent[checkPos])
              violationLogs = []
              violationLogs.append(violationLine)
              del self.historyAEvents[aPos]
              return (violationMessage, violationLogs)
            break
        checkPos = checkPos+1
        if checkPos != len(aEvent):
          continue
# We found the match. Mark aEvent as done.
        self.historyAEvents[aPos] = None
# See how many eEvents this bEvent might collect. Clean it also
# when limit was reached.
        bEvent[1] += 1
        if bEvent[1] == self.maxArtefactsAForSingleB:
          self.historyBEvents[bPos] = None

# We want to keep a history of good matches to ease diagnosis
# of correlation failures. Keep information about current line
# for reference.
        self.correlationHistory.addObject((aEvent[3].matchElement.matchString, aEvent[2].actionId, \
          bEvent[3].matchElement.matchString, bEvent[2].actionId))
        aPos += 1
        break

# After checking all aEvents before aPos were cleared, otherwise
# they violate a correlation rule.
    checkRange = aPos
    violationLogs = []
    violationMessage = ''
    numViolations = 0
    for aPos in range(0, checkRange):
      aEvent = self.historyAEvents[aPos]
      if aEvent is None:
        continue
      numViolations += 1
      if numViolations > maxViolations:
        continue
      violationLine = aEvent[3].matchElement.matchString
      if isinstance(violationLine, bytes):
        violationLine = violationLine.decode("utf-8")
      violationMessage += 'FAIL: \"%s\" (%s)' % (violationLine, aEvent[2].actionId)
      violationLogs.append(violationLine)
    if numViolations > maxViolations:
      violationMessage += '... (%d more)\n' % (numViolations-maxViolations)
    if numViolations != 0 and self.correlationHistory.getHistory():
      violationMessage += 'Historic examples:\n'
      for record in self.correlationHistory.getHistory():
        violationMessage += '  "%s" (%s) ==> "%s" (%s)\n' % record

# Prune out all handled event records
    self.historyAEvents = self.historyAEvents[checkRange:]
    self.historyBEvents = self.historyBEvents[bPosStart:]
    if numViolations == 0:
      return None
    return (violationMessage, violationLogs)


  def prepareHistoryEntry(self, selector, logAtom):
    """Return a history entry for a parser match."""
    parserMatch = logAtom.parserMatch
    length = 4
    if self.artefactMatchParameters is not None:
      length += len(self.artefactMatchParameters)
    result = [None]*length
    result[0] = logAtom.getTimestamp()
    result[1] = 0
    result[2] = selector
    result[3] = parserMatch

    if result[0] is None:
      result[0] = datetime.fromtimestamp(time.time())
    if isinstance(result[0], datetime):
      result[0] = (result[0]-datetime.fromtimestamp(0)).total_seconds()

    if result[0] < self.lastTimestampSeen:
      raise Exception('Unsorted!')
    self.lastTimestampSeen = result[0]

    if self.artefactMatchParameters is not None:
      pos = 4
      vDict = parserMatch.getMatchDictionary()
      for artefactMatchParameter in self.artefactMatchParameters:
        for paramPath in artefactMatchParameter:
          matchElement = vDict.get(paramPath, None)
          if matchElement is not None:
            result[pos] = matchElement.matchObject
            pos += 1
    return result
예제 #6
0
 def __init__(self, max_items):
     """Initialize the history component."""
     LogarithmicBackoffHistory.__init__(self, max_items)
     self.event_id = 0
예제 #7
0
class CorrelationRule:
    """This class defines a correlation rule to match artefacts A and B, where a hidden event A* always triggers at least one
    artefact A and the the hidden event B*, thus triggering also at least one artefact B."""

    def __init__(self, rule_id, min_time_delta, max_time_delta, max_artefacts_a_for_single_b=1, artefact_match_parameters=None):
        """Create the correlation rule.
        @param artefact_match_parameters if not none, two artefacts A and B will be only treated as correlated when all the
        parsed artefact attributes identified by the list of attribute path tuples match.
        @param min_time_delta minimal delta in seconds, that artefact B may be observed after artefact A. Negative values are allowed
        as artefact B may be found before A. """
        self.rule_id = rule_id
        self.min_time_delta = min_time_delta
        self.max_time_delta = max_time_delta
        self.max_artefacts_a_for_single_b = max_artefacts_a_for_single_b
        self.artefact_match_parameters = artefact_match_parameters
        self.history_a_events = []
        self.history_b_events = []
        self.last_timestamp_seen = 0.0
        self.correlation_history = LogarithmicBackoffHistory(10)

    def update_artefact_a(self, selector, log_atom):
        """Append entry to the event history A."""
        history_entry = self.prepare_history_entry(selector, log_atom)
        # Check if event A could be discarded immediately.
        self.history_a_events.append(history_entry)

    def update_artefact_b(self, selector, log_atom):
        """Append entry to the event history B."""
        history_entry = self.prepare_history_entry(selector, log_atom)
        # Check if event B could be discarded immediately.
        self.history_b_events.append(history_entry)

    def check_status(self, newest_timestamp, max_violations=20):
        """@return None if status is OK. Returns a tuple containing a descriptive message and a list of violating log data lines
        on error."""

        # This part of code would be good target to be implemented as native library with optimized algorithm in future.
        a_pos = 0
        check_range = len(self.history_a_events)
        violation_logs = []
        violation_message = ''
        num_violations = 0
        while a_pos < check_range:
            deleted = False
            check_range = len(self.history_a_events)
            a_event = self.history_a_events[a_pos]
            if a_event is None:
                continue
            a_event_time = a_event[0]
            b_pos = 0
            while b_pos < len(self.history_b_events):
                b_event = self.history_b_events[b_pos]
                if b_event is None:
                    continue
                b_event_time = b_event[0]
                delta = b_event_time - a_event_time
                if delta < self.min_time_delta:
                    # See if too early, if yes go to next element. As we will not check again any older aEvents in this loop, skip
                    # all bEvents up to this position in future runs.
                    if b_pos < len(self.history_b_events):
                        violation_line = a_event[3].match_element.match_string
                        if isinstance(violation_line, bytes):
                            violation_line = violation_line.decode()
                            if num_violations <= max_violations:
                                violation_message += 'FAIL: B-Event for \"%s\" (%s) was found too early!\n' % (
                                                     violation_line, a_event[2].action_id)
                            violation_logs.append(violation_line)
                            del self.history_a_events[a_pos]
                            del self.history_b_events[b_pos]
                            deleted = True
                            check_range = check_range - 1
                            num_violations = num_violations + 1
                            break
                    continue
                # Too late, no other b_event may match this a_event
                if delta > self.max_time_delta:
                    violation_line = a_event[3].match_element.match_string
                    if isinstance(violation_line, bytes):
                        violation_line = violation_line.decode()
                        if num_violations <= max_violations:
                            violation_message += 'FAIL: B-Event for \"%s\" (%s) was not found in time!\n' % (
                                                 violation_line, a_event[2].action_id)
                        violation_logs.append(violation_line)
                        del self.history_a_events[a_pos]
                        del self.history_b_events[b_pos]
                        deleted = True
                        check_range = check_range - 1
                        num_violations = num_violations + 1
                    break
                # So time range is OK, see if match parameters are also equal.
                violation_found = False
                for check_pos in range(4, len(a_event)):
                    if a_event[check_pos] != b_event[check_pos]:
                        violation_line = a_event[3].match_element.match_string
                        if isinstance(violation_line, bytes):
                            violation_line = violation_line.decode()
                            if num_violations <= max_violations:
                                violation_message += 'FAIL: \"%s\" (%s) %s is not equal %s\n' % (
                                    violation_line, a_event[2].action_id, a_event[check_pos], b_event[check_pos])
                            violation_logs.append(violation_line)
                            del self.history_a_events[a_pos]
                            del self.history_b_events[b_pos]
                            deleted = True
                            check_range = check_range - 1
                            num_violations = num_violations + 1
                            violation_found = True
                        break
                check_pos = check_pos + 1
                if violation_found:
                    continue

                # We want to keep a history of good matches to ease diagnosis of correlation failures. Keep information about current line
                # for reference.
                self.correlation_history.add_object((a_event[3].match_element.match_string, a_event[2].action_id,
                                                     b_event[3].match_element.match_string, b_event[2].action_id))
                del self.history_a_events[a_pos]
                del self.history_b_events[b_pos]
                deleted = True
                check_range = check_range - 1
                b_pos = b_pos + 1
            if deleted is False:
                a_pos = a_pos + 1
        # After checking all aEvents before a_pos were cleared, otherwise they violate a correlation rule.
        for a_pos in range(0, check_range):
            a_event = self.history_a_events[a_pos]
            if a_event is None:
                continue
            delta = newest_timestamp - a_event[0]
            if delta > self.max_time_delta:
                violation_line = a_event[3].match_element.match_string
                if isinstance(violation_line, bytes):
                    violation_line = violation_line.decode()
                    if num_violations <= max_violations:
                        violation_message += 'FAIL: B-Event for \"%s\" (%s) was not found in time!\n' % (
                                             violation_line, a_event[2].action_id)
                    violation_logs.append(violation_line)
                    del self.history_a_events[a_pos]
                    deleted = True
                    check_range = check_range - 1
                    num_violations = num_violations + 1
                break

        if num_violations > max_violations:
            violation_message += '... (%d more)\n' % (num_violations - max_violations)
        if num_violations != 0 and len(self.correlation_history.get_history()) > 0:
            violation_message += 'Historic examples:\n'
            for record in self.correlation_history.get_history():
                violation_message += '  "%s" (%s) ==> "%s" (%s)\n' % (record[0].decode(), record[1], record[2].decode(), record[3])

        if num_violations == 0:
            return None
        return violation_message, violation_logs

    def prepare_history_entry(self, selector, log_atom):
        """Return a history entry for a parser match."""
        parser_match = log_atom.parser_match
        timestamp = log_atom.get_timestamp()
        if timestamp is None:
            timestamp = time.time()
        length = 4
        if self.artefact_match_parameters is not None:
            length += len(self.artefact_match_parameters)
        result = [None] * length
        result[0] = timestamp
        result[1] = 0
        result[2] = selector
        result[3] = parser_match

        if result[0] < self.last_timestamp_seen:
            raise Exception('Unsorted!')
        self.last_timestamp_seen = result[0]

        if self.artefact_match_parameters is not None:
            pos = 4
            v_dict = parser_match.get_match_dictionary()
            for artefact_match_parameter in self.artefact_match_parameters:
                for param_path in artefact_match_parameter:
                    match_element = v_dict.get(param_path, None)
                    if match_element is not None:
                        result[pos] = match_element.match_object
                        pos += 1
        return result