diff --git a/beagle/transformers/__init__.py b/beagle/transformers/__init__.py index 7b9d87a3..da4755f7 100644 --- a/beagle/transformers/__init__.py +++ b/beagle/transformers/__init__.py @@ -8,7 +8,7 @@ from .procmon_transformer import ProcmonTransformer from .sysmon_transformer import SysmonTransformer from .darpa_tc_transformer import DRAPATCTransformer - +from .ossem_transformer import OSSEMTransformer __all__ = [ "Transformer", @@ -19,4 +19,5 @@ "ProcmonTransformer", "SysmonTransformer", "DRAPATCTransformer", + "OSSEMTransformer", ] diff --git a/beagle/transformers/ossem_transformer.py b/beagle/transformers/ossem_transformer.py new file mode 100644 index 00000000..5f77e97d --- /dev/null +++ b/beagle/transformers/ossem_transformer.py @@ -0,0 +1,92 @@ +from typing import List, Optional, Tuple + +from beagle.common import logger, split_path +from beagle.nodes import File, Process +from beagle.transformers.base_transformer import Transformer + + +class OSSEMTransformer(Transformer): + """Transformer based on the fields defined here: + https://github.com/Cyb3rWard0g/OSSEM/tree/master/common_information_model + + And the relationships defined here: + https://docs.google.com/spreadsheets/d/1ow7YRDEDJs67kcKMZZ66_5z1ipJry9QrsDQkjQvizJM/edit#gid=0 + + Parameters + ---------- + Transformer : [type] + [description] + """ + + name = "OSSEM" + + def __init__(self, *args, **kwargs) -> None: + + super().__init__(*args, **kwargs) + + logger.info("Created OSSEM Transformer.") + + def transform(self, event: dict) -> Optional[Tuple]: + + relationship = event["event_type"] + + if relationship == "launched": + return self.created(event) + elif relationship in ["loaded", "created", "modified", "downloaded"]: + return self.file_ops(event) + + return tuple() + + def created(self, event: dict) -> Tuple[Process, File, Process, File]: + + proc_name, proc_path = split_path(event["process_path"]) + + proc = Process( + process_id=event.get("process_id"), + process_image=proc_name, + process_image_path=proc_path, + command_line=event.get("process_command_line"), + ) + + proc_name, proc_path = split_path(event["process_parent_path"]) + + parent = Process( + process_id=event.get("process_parent_id"), + process_image=proc_name, + process_image_path=proc_path, + command_line=event.get("process_parent_command_line"), + ) + + parent_file = parent.get_file_node() + proc_file = proc.get_file_node() + + parent_file.file_of[parent] + proc_file.file_of[proc] + + parent.launched[proc].append(timestamp=event["event_creation_time"]) + + return (proc, proc_file, parent, parent_file) + + def file_ops(self, event: dict) -> Tuple[Process, File, File]: + proc_name, proc_path = split_path(event["process_path"]) + + proc = Process( + process_id=event.get("process_id"), + process_image=proc_name, + process_image_path=proc_path, + command_line=event.get("process_command_line"), + ) + proc_file = proc.get_file_node() + + proc_file.file_of[proc] + + name, path = split_path(event["file_path"]) + + dest_file = File(file_name=name, file_path=path, extension=event.get("file_extension")) + + if event["event_type"] == "loaded": + proc.loaded[dest_file].append(timestamp=event["event_creation_time"]) + elif event["event_type"] in ["created", "modified", "downloaded"]: + proc.wrote[dest_file].append(timestamp=event["event_creation_time"]) + + return (proc, proc_file, dest_file) diff --git a/tests/transformers/test_ossem_transformer.py b/tests/transformers/test_ossem_transformer.py new file mode 100644 index 00000000..3fbf45aa --- /dev/null +++ b/tests/transformers/test_ossem_transformer.py @@ -0,0 +1,89 @@ +import pytest + +from beagle.nodes import Process, File +from beagle.transformers import OSSEMTransformer + + +@pytest.fixture +def transformer() -> OSSEMTransformer: + return OSSEMTransformer(datasource=None) # type: ignore + + +def test_unknown_event(transformer): + assert transformer.transform({"event_type": "foooo"}) == () + + +def test_make_process(transformer): + test_event = { + "event_creation_time": "1", + "event_type": "launched", + "process_id": "4756", + "process_name": "conhost.exe", + "process_path": "C:\\Windows\\System32\\conhost.exe", + "process_command_line": "??\\C:\\WINDOWS\\system32\\conhost.exe 0xffffffff -ForceV1", + "process_integrity_level": "Medium", + "process_parent_guid": "A98268C1-9C2E-5ACD-0000-00100266AB00}", + "process_parent_id": "240", + "process_parent_name": "cmd.exe", + "process_parent_path": "C:\\Windows\\System32\\cmd.exe", + "process_parent_command_line": "C:\\WINDOWS\\system32\\cmd.exe", + } + + nodes = transformer.transform(test_event) + + assert len(nodes) == 4 + + proc: Process = nodes[0] + proc_file: File = nodes[1] + parent_proc: Process = nodes[2] + parent_proc_file: File = nodes[3] + + assert {"timestamp": "1"} in parent_proc.launched[proc] + assert proc in proc_file.file_of + assert parent_proc in parent_proc_file.file_of + + assert proc.process_id == "4756" + assert proc.process_image == "conhost.exe" + assert proc.process_image_path == "C:\\Windows\\System32" + assert proc.command_line == "??\\C:\\WINDOWS\\system32\\conhost.exe 0xffffffff -ForceV1" + + assert parent_proc.process_id == "240" + assert parent_proc.process_image == "cmd.exe" + assert parent_proc.process_image_path == "C:\\Windows\\System32" + assert parent_proc.command_line == "C:\\WINDOWS\\system32\\cmd.exe" + + +@pytest.mark.parametrize( + "eventtype,attribute", + [("loaded", "loaded"), ("created", "wrote"), ("modified", "wrote"), ("downloaded", "wrote")], +) +def test_file_operations(transformer, eventtype, attribute): + test_event = { + "event_creation_time": "1", + "event_type": eventtype, + "process_id": "4756", + "process_name": "conhost.exe", + "process_path": "C:\\Windows\\System32\\conhost.exe", + "process_command_line": "??\\C:\\WINDOWS\\system32\\conhost.exe 0xffffffff -ForceV1", + "file_name": "cmd.exe", + "file_path": "C:\\Windows\\System32\\cmd.exe", + } + nodes = transformer.transform(test_event) + + assert len(nodes) == 3 + + proc: Process = nodes[0] + proc_file: File = nodes[1] + dest_file: File = nodes[2] + + assert proc in proc_file.file_of + + assert proc.process_id == "4756" + assert proc.process_image == "conhost.exe" + assert proc.process_image_path == "C:\\Windows\\System32" + assert proc.command_line == "??\\C:\\WINDOWS\\system32\\conhost.exe 0xffffffff -ForceV1" + + assert dest_file.file_name == "cmd.exe" + assert dest_file.file_path == "C:\\Windows\\System32" + + assert {"timestamp": "1"} in getattr(proc, attribute)[dest_file]