Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Windows event logs messages attributes #2910

Merged
merged 38 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
65e6522
Initial commit
roshanmaskey Sep 26, 2023
c28af94
Added security event in unit test.
roshanmaskey Sep 27, 2023
d753fff
Merge branch 'master' into add_feature_extraction_winevt
jkppr Sep 29, 2023
2d472c7
Updated the code as per feedback
roshanmaskey Oct 9, 2023
3dfd964
Fixed unit test
roshanmaskey Oct 9, 2023
282c329
Merge branch 'master' into add_feature_extraction_winevt
roshanmaskey Oct 9, 2023
40f119b
Merge branch 'master' into add_feature_extraction_winevt
jkppr Oct 19, 2023
77a7ab2
Update timesketch/lib/analyzers/feature.py
roshanmaskey Oct 26, 2023
822899d
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
2774108
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
b61afba
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
6985566
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
cdaf9f6
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
0c9d4b7
Update data/winevt_features.yaml
roshanmaskey Oct 26, 2023
16820c3
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
4223298
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
8409b15
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
ad9643a
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
4097ef5
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
f120d43
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
daa65ae
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
d04e4b5
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
b50cca3
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
467284f
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 26, 2023
24b822b
Update timesketch/lib/analyzers/feature_plugins/winevt_features.py
roshanmaskey Oct 27, 2023
91b0215
Merge branch 'google:master' into add_feature_extraction_winevt
roshanmaskey Oct 27, 2023
59af04f
Updated as per feedback2
roshanmaskey Oct 28, 2023
42add61
fixed linting
roshanmaskey Oct 28, 2023
9130894
fixed formatting issue
roshanmaskey Oct 28, 2023
a092c9a
Adding original feature_extractions analyzer as regex_features plugin
roshanmaskey Oct 30, 2023
d76da25
Adding unit tests for regex_features
roshanmaskey Oct 30, 2023
9351e54
Merge branch 'google:master' into add_feature_extraction_winevt
roshanmaskey Oct 30, 2023
c4f5de2
Merge branch 'master' into add_feature_extraction_winevt
jkppr Oct 30, 2023
69f2042
clean-up & renaming
jkppr Nov 2, 2023
0927308
linter
jkppr Nov 2, 2023
251b1ba
adjusting naming schema & logger
jkppr Nov 2, 2023
7fada3d
linter fix
jkppr Nov 2, 2023
5d56128
fix unittest
jkppr Nov 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
556 changes: 556 additions & 0 deletions data/winevt.yaml
jkppr marked this conversation as resolved.
Show resolved Hide resolved
roshanmaskey marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions timesketch/lib/analyzers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from timesketch.lib.analyzers import chain
from timesketch.lib.analyzers import domain
from timesketch.lib.analyzers import expert_sessionizers
from timesketch.lib.analyzers import feature
from timesketch.lib.analyzers import feature_extraction
from timesketch.lib.analyzers import gcp_logging
from timesketch.lib.analyzers import geoip
Expand Down
58 changes: 58 additions & 0 deletions timesketch/lib/analyzers/feature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Main sketch analyzer for feature extraction."""

import logging

from timesketch.lib.analyzers import interface
from timesketch.lib.analyzers import manager
from timesketch.lib.analyzers.feature_plugins import manager as feature_manager

logger = logging.getLogger("timesketch.analyzers.feature")


class FeatureSketchPlugin(interface.BaseAnalyzer):
"""Main sketch analyzer for feature extraction."""

NAME = "feature_extraction_main"
DISPLAY_NAME = "Feature Extraction Sketch Analyzer"
DESCRIPTION = "This analyzer runs all the feature extractions plugins in the index."
roshanmaskey marked this conversation as resolved.
Show resolved Hide resolved

DEPENDENCIES = frozenset()

def __init__(
self, index_name: str, sketch_id: int, timeline_id: int = None
) -> None:
"""Initializes the sketch analyzer.

Args:
index_name (str): OpenSearch index name.
sketch_id (int): TimeSketch's sketch ID.
timeline_id (int): The ID of the timeline.
"""

super().__init__(
index_name=index_name, sketch_id=sketch_id, timeline_id=timeline_id
)

self._feature_plugins = (
feature_manager.FeatureExtractionPluginManager.get_plugins(self)
)

def run(self) -> str:
"""Entry point for the sketch analyzer.

Returns:
str: A summary of sketch analyzer result.
"""

results = []

for feature_plugin in self._feature_plugins:
result = feature_plugin.run_plugin()
if not result:
logger.debug("No plugin result for %s", feature_plugin.NAME)
results.append(result)

return "\n".join(results)
jkppr marked this conversation as resolved.
Show resolved Hide resolved


manager.AnalysisManager.register_analyzer(FeatureSketchPlugin)
3 changes: 3 additions & 0 deletions timesketch/lib/analyzers/feature_plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Imports for the feature extraction plugins."""

from timesketch.lib.analyzers.feature_plugins import winevt
29 changes: 29 additions & 0 deletions timesketch/lib/analyzers/feature_plugins/interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""This file contains an interface to feature extraction plugins."""

import abc


class BaseFeatureExtractionPlugin(object):
"""A base plugin for the feature extraction.

This is an interface for the feature extraction plugins.
"""

NAME = "base_feature_extraction"
DISPLAY_NAME = "Base Feature Extraction"
DESCRIPTION = ""

def __init__(self, analyzer_object) -> None:
"""Initializes the plugin.

Args:
analyzer_object (FeatureSketchPlugin): An object of class
FeatureSketchPlugin.
"""

super().__init__()
self.analyzer_object = analyzer_object

@abc.abstractmethod
def run_plugin(self):
"""Main entry point to feature extraction plugins"""
74 changes: 74 additions & 0 deletions timesketch/lib/analyzers/feature_plugins/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""The feature extraction plugins manager object."""

from typing import List


class FeatureExtractionPluginManager(object):
""" "A class that implements the plugins manager."""

_plugin_classes = {}

@classmethod
def register_plugin(cls, plugin_class) -> None:
"""Registers a plugin class.

Args:
plugin_class (type): A class object of a plugin.

Raises:
KeyError: If the `plugin_class` is already registered.
"""

plugin_name = plugin_class.NAME.lower()
if plugin_name in cls._plugin_classes:
raise KeyError(f"Plugin class {plugin_class.NAME} is already registered.")

cls._plugin_classes[plugin_name] = plugin_class

@classmethod
def register_plugins(cls, plugin_classes) -> None:
"""Registers multiple plugin classes.

Args:
plugin_classes (List[type]): A list of plugin class objects.

Raises:
KeyError: If plugin classes are already registered.
"""

for plugin_class in plugin_classes:
cls.register_plugin(plugin_class=plugin_class)

@classmethod
def deregister_plugin(cls, plugin_class) -> None:
"""Deregisters a plugin class.

Args:
plugin_class (type): A plugin class to be deregistered.

Raises:
KeyError: If the plugin class is not registered.
"""

plugin_name = plugin_class.NAME.lower()
if plugin_name not in cls._plugin_classes:
raise KeyError(f"Plugin class {plugin_class.NAME} is not registered.")

del cls._plugin_classes[plugin_name]

@classmethod
def get_plugins(cls, analyzer_object) -> List:
"""Retrieves plugins classes.

Args:
analayzer_object (FeatureExtractionSketchPlugin): An instance of
FeatureSketchPlugin.

Returns:
List[type]: A list of plugin class.
"""

return [
plugin_class(analyzer_object)
for plugin_class in iter(cls._plugin_classes.values())
]
172 changes: 172 additions & 0 deletions timesketch/lib/analyzers/feature_plugins/winevt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
"""This file contains the plugin for Windows event logs feature extraction."""

from typing import List

import logging

from timesketch.lib.analyzers import interface as base_interface
from timesketch.lib.analyzers.feature_plugins import interface
from timesketch.lib.analyzers.feature_plugins import manager

logger = logging.getLogger("timesketch.analyzers.feature")


class WindowsEventFeatureExtractionPlugin(interface.BaseFeatureExtractionPlugin):
"""A plugin for Windows event log feature extraction."""

NAME = "winevt_feature_extraction"
DISPLAY_NAME = "Windows Event Log Feature Extraction"
DESCRIPTION = (
"This plugin extracts Windows event logs attributes from plaso output"
" attribute `strings`"
)

EVENT_FIELDS = ["strings"]

def run_plugin(self) -> str:
"""Initializes plugin class object.

Returns:
str: Summary of plugin output.
"""

# Stores extraction summary for each feature.
results = []

feature_kwargs = self.load_feature_config()
if not feature_kwargs:
return f"No feature configuration data for {self.NAME}"

for feature in feature_kwargs:
# NOTE: feature_name and feature_config value checking is done at the sink
# function `extract_features`.
feature_name = feature.get("feature", None)
feature_config = feature.get("feature_config", None)

result = self.extract_features(feature_name, feature_config)
if not result:
logger.debug("No result for plugin run for %s", feature_name)
results.append(result)

return "\n".join(results)
jkppr marked this conversation as resolved.
Show resolved Hide resolved

def extract_features(self, name: str, config: dict) -> str:
"""Extracts features from events.

Args:
name (str): Features extraction name.
config (dict): A dict that contains the configuration fo the
feature extraction.

Returns:
str: Returns summary of the feature extraction for `feature_name`.
"""

if not name:
raise ValueError("Feature name is empty")

if not config:
raise ValueError("Feature configuration value is empty")

source_name = config.get("source_name", None)
if not source_name:
raise ValueError("source_name is empyt")
jkppr marked this conversation as resolved.
Show resolved Hide resolved

if not isinstance(source_name, list):
raise TypeError(
f"Expecting source_name as a list in {name} and got {type(source_name)}"
)

source_name = source_name[0]

# Using -1 as a default value for event_identifier and event_version as
# 0 is considered as None.
event_identifier = int(config.get("event_identifier", -1))
event_version = int(config.get("event_version", -1))
jkppr marked this conversation as resolved.
Show resolved Hide resolved

if not source_name:
raise ValueError("source_name is empty")
jkppr marked this conversation as resolved.
Show resolved Hide resolved

if event_identifier < 0:
raise ValueError(
f"event_identifier is {event_identifier}. Expecting the value >= 0"
)
jkppr marked this conversation as resolved.
Show resolved Hide resolved

if event_version < 0:
raise ValueError(f"event_version is {event_version}. Expecting value >= 0")

mappings = config.get("mapping", None)
if not mappings:
raise ValueError(f"mapping value for {name} is empty")

query = (
f"source_name: {source_name} AND event_identifier: {event_identifier}"
f" AND event_version: {event_version}"
)
jkppr marked this conversation as resolved.
Show resolved Hide resolved
jkppr marked this conversation as resolved.
Show resolved Hide resolved

events = self.analyzer_object.event_stream(
query_string=query, return_fields=self.EVENT_FIELDS
)
event_counter = 0

for event in events:
attributes = {}

strings = event.source.get("strings", None)
if not strings:
logger.debug("No strings attribute in event ID: %s", event.event_id)
continue

for mapping in mappings:
attribute_name = mapping.get("name", None)
string_index = int(mapping.get("string_index", -1))
jkppr marked this conversation as resolved.
Show resolved Hide resolved
attribute_aliases = mapping.get("aliases", None)

attribute_value = ""
try:
attribute_value = strings[string_index]
except IndexError:
logger.warning(
"The index %d does not exist in strings", string_index
)
continue

attributes[attribute_name] = attribute_value
if attribute_aliases:
for alias in attribute_aliases:
attributes[alias] = attribute_value

event.add_attributes(attributes)
event.commit()

event_counter += 1

return f"{event_counter} events updated by {name}."
jkppr marked this conversation as resolved.
Show resolved Hide resolved

def load_feature_config(self) -> List[dict]:
"""Load feature configuration from a file.

Returns:
List[dict]: A list of dictionary containing feature name and config.
None: Returns None if configuration file is empty.
"""

# config_file winevt.yaml is located within the timesketch/data directory.
config_file = "winevt.yaml"

features_config = base_interface.get_yaml_config(config_file)
if not features_config:
logger.debug("No feature configuration data in %s", config_file)
return None

features_kwargs = [
{"feature": feature, "feature_config": config}
for feature, config in features_config.items()
]

return features_kwargs


manager.FeatureExtractionPluginManager.register_plugin(
WindowsEventFeatureExtractionPlugin
)
Loading