digital forensics

Performing Malware Persistence Investigation

Systematically investigate all persistence mechanisms on Windows and Linux systems to identify how malware survives reboots and maintains access.

autorunsforensicsincident-responsemalware-persistenceregistryrootkit-detectionscheduled-tasks
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When investigating how malware maintains presence on a compromised system after reboots
  • During incident response to identify all persistence mechanisms for complete remediation
  • For threat hunting to discover unauthorized autostart entries across endpoints
  • When analyzing malware behavior to understand its persistence strategy
  • For verifying that all persistence has been removed after incident remediation

Prerequisites

  • Forensic image or live system access with administrative privileges
  • Autoruns (Sysinternals) for Windows persistence enumeration
  • RegRipper for offline registry analysis
  • Understanding of Windows and Linux persistence mechanisms
  • YARA rules for scanning persistence locations
  • Baseline of known-good autorun entries for comparison

Workflow

Step 1: Enumerate Windows Registry Persistence

# Extract registry hives from forensic image
mount -o ro,loop,offset=$((2048*512)) /cases/case-2024-001/images/evidence.dd /mnt/evidence
 
# Key registry persistence locations
python3 << 'PYEOF'
from Registry import Registry
import json
 
results = {'registry_persistence': []}
 
# SYSTEM hive analysis
system_reg = Registry.Registry("/cases/case-2024-001/registry/SYSTEM")
select = system_reg.open("Select")
current = select.value("Current").value()
cs = f"ControlSet{current:03d}"
 
# Services (very common persistence)
services = system_reg.open(f"{cs}\\Services")
for svc in services.subkeys():
    try:
        start_type = svc.value("Start").value()
        image_path = ""
        try:
            image_path = svc.value("ImagePath").value()
        except:
            pass
        # Start types: 0=Boot, 1=System, 2=Auto, 3=Manual, 4=Disabled
        if start_type in (0, 1, 2) and image_path:
            svc_type = svc.value("Type").value() if svc.values() else 0
            results['registry_persistence'].append({
                'location': f'HKLM\\SYSTEM\\{cs}\\Services\\{svc.name()}',
                'type': 'Service',
                'value': image_path,
                'start_type': start_type,
                'timestamp': str(svc.timestamp())
            })
    except Exception:
        pass
 
# SOFTWARE hive analysis
sw_reg = Registry.Registry("/cases/case-2024-001/registry/SOFTWARE")
 
# Machine Run keys
run_keys = [
    "Microsoft\\Windows\\CurrentVersion\\Run",
    "Microsoft\\Windows\\CurrentVersion\\RunOnce",
    "Microsoft\\Windows\\CurrentVersion\\RunServices",
    "Microsoft\\Windows\\CurrentVersion\\RunServicesOnce",
    "Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run",
    "Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run",
    "Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
]
 
for key_path in run_keys:
    try:
        key = sw_reg.open(key_path)
        for value in key.values():
            results['registry_persistence'].append({
                'location': f'HKLM\\SOFTWARE\\{key_path}',
                'type': 'Run Key',
                'name': value.name(),
                'value': str(value.value()),
                'timestamp': str(key.timestamp())
            })
    except Exception:
        pass
 
# NTUSER.DAT analysis
import glob
for ntuser in glob.glob("/cases/case-2024-001/registry/NTUSER*.DAT"):
    try:
        user_reg = Registry.Registry(ntuser)
        user_run_keys = [
            "Software\\Microsoft\\Windows\\CurrentVersion\\Run",
            "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
            "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\Startup",
        ]
        for key_path in user_run_keys:
            try:
                key = user_reg.open(key_path)
                for value in key.values():
                    results['registry_persistence'].append({
                        'location': f'HKCU\\{key_path}',
                        'type': 'User Run Key',
                        'name': value.name(),
                        'value': str(value.value()),
                        'timestamp': str(key.timestamp()),
                        'hive': ntuser
                    })
            except Exception:
                pass
    except Exception:
        pass
 
print(f"Total registry persistence entries: {len(results['registry_persistence'])}")
for entry in results['registry_persistence']:
    print(f"  [{entry['type']}] {entry.get('name', '')} -> {entry.get('value', '')[:100]}")
 
with open('/cases/case-2024-001/analysis/registry_persistence.json', 'w') as f:
    json.dump(results, f, indent=2)
PYEOF

Step 2: Check Scheduled Tasks and WMI Persistence

# Extract scheduled tasks from forensic image
mkdir -p /cases/case-2024-001/persistence/tasks/
cp -r /mnt/evidence/Windows/System32/Tasks/* /cases/case-2024-001/persistence/tasks/ 2>/dev/null
 
# Parse scheduled task XML files
python3 << 'PYEOF'
import os, xml.etree.ElementTree as ET
 
tasks_dir = '/cases/case-2024-001/persistence/tasks/'
suspicious_tasks = []
 
for root_dir, dirs, files in os.walk(tasks_dir):
    for fname in files:
        fpath = os.path.join(root_dir, fname)
        try:
            tree = ET.parse(fpath)
            root = tree.getroot()
            ns = {'t': 'http://schemas.microsoft.com/windows/2004/02/mit/task'}
 
            actions = root.findall('.//t:Exec', ns)
            for action in actions:
                command = action.find('t:Command', ns)
                args = action.find('t:Arguments', ns)
                cmd_text = command.text if command is not None else ''
                args_text = args.text if args is not None else ''
 
                # Flag suspicious commands
                suspicious_indicators = [
                    'powershell', 'cmd.exe', 'wscript', 'cscript', 'mshta',
                    'regsvr32', 'rundll32', 'certutil', 'bitsadmin',
                    '/c ', '-enc', '-e ', 'hidden', 'bypass', 'downloadstring',
                    'invoke-', 'iex', '/tmp/', 'appdata', 'programdata',
                    'temp\\', '.ps1', '.vbs', '.hta', 'base64'
                ]
 
                is_suspicious = any(s in (cmd_text + ' ' + args_text).lower() for s in suspicious_indicators)
 
                task_info = {
                    'name': fname,
                    'path': fpath.replace(tasks_dir, ''),
                    'command': cmd_text,
                    'arguments': args_text,
                    'suspicious': is_suspicious
                }
 
                if is_suspicious:
                    suspicious_tasks.append(task_info)
                    print(f"SUSPICIOUS TASK: {fname}")
                    print(f"  Command: {cmd_text}")
                    print(f"  Arguments: {args_text}")
                    print()
 
        except Exception as e:
            pass
 
print(f"\nTotal suspicious scheduled tasks: {len(suspicious_tasks)}")
PYEOF
 
# Check WMI event subscriptions (common APT persistence)
# WMI repository: C:\Windows\System32\wbem\Repository\
cp -r /mnt/evidence/Windows/System32/wbem/Repository/ /cases/case-2024-001/persistence/wmi/ 2>/dev/null
 
# Parse WMI persistence using PyWMIPersistenceFinder
python3 << 'PYEOF'
import os, re
 
# Search WMI OBJECTS.DATA for event subscriptions
wmi_db = '/cases/case-2024-001/persistence/wmi/OBJECTS.DATA'
if os.path.exists(wmi_db):
    with open(wmi_db, 'rb') as f:
        data = f.read()
 
    # Search for EventFilter strings
    filters = re.findall(b'__EventFilter.*?(?=\x00\x00)', data)
    consumers = re.findall(b'CommandLineEventConsumer.*?(?=\x00\x00)', data)
    bindings = re.findall(b'__FilterToConsumerBinding.*?(?=\x00\x00)', data)
 
    print("=== WMI PERSISTENCE ===")
    print(f"Event Filters: {len(filters)}")
    print(f"Command Consumers: {len(consumers)}")
    print(f"Filter-Consumer Bindings: {len(bindings)}")
 
    for consumer in consumers:
        decoded = consumer.decode('utf-8', errors='ignore')
        print(f"  Consumer: {decoded[:200]}")
else:
    print("WMI repository not found")
PYEOF

Step 3: Check File System and Boot Persistence

# Startup folders
echo "=== STARTUP FOLDER CONTENTS ===" > /cases/case-2024-001/analysis/startup_items.txt
 
ls -la "/mnt/evidence/ProgramData/Microsoft/Windows/Start Menu/Programs/Startup/" \
   >> /cases/case-2024-001/analysis/startup_items.txt 2>/dev/null
 
for userdir in /mnt/evidence/Users/*/; do
    username=$(basename "$userdir")
    echo "--- User: $username ---" >> /cases/case-2024-001/analysis/startup_items.txt
    ls -la "$userdir/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/" \
       >> /cases/case-2024-001/analysis/startup_items.txt 2>/dev/null
done
 
# Check DLL search order hijacking locations
echo "=== DLL HIJACKING CHECK ===" > /cases/case-2024-001/analysis/dll_hijack.txt
# Check for DLLs in application directories that should only be in System32
find /mnt/evidence/Program\ Files/ /mnt/evidence/Program\ Files\ \(x86\)/ \
   -name "*.dll" -newer /mnt/evidence/Windows/System32/ntdll.dll 2>/dev/null \
   >> /cases/case-2024-001/analysis/dll_hijack.txt
 
# Check for COM object hijacking
python3 << 'PYEOF'
from Registry import Registry
 
reg = Registry.Registry("/cases/case-2024-001/registry/SOFTWARE")
 
# Check for suspicious CLSID entries
try:
    clsid = reg.open("Classes\\CLSID")
    for key in clsid.subkeys():
        try:
            server = key.subkey("InprocServer32")
            dll_path = server.value("(default)").value()
            if any(s in dll_path.lower() for s in ['temp', 'appdata', 'programdata', 'downloads', 'tmp']):
                print(f"SUSPICIOUS COM: {key.name()} -> {dll_path}")
        except:
            pass
except:
    pass
PYEOF
 
# Check boot configuration for bootkits
# BCD (Boot Configuration Data)
ls -la /mnt/evidence/Boot/BCD 2>/dev/null
# Check for modified bootmgr or winload.exe
sha256sum /mnt/evidence/Windows/System32/winload.exe 2>/dev/null

Step 4: Check Linux Persistence Mechanisms

# If analyzing a Linux system
LINUX_ROOT="/mnt/evidence"
 
echo "=== LINUX PERSISTENCE CHECK ===" > /cases/case-2024-001/analysis/linux_persistence.txt
 
# Cron jobs
echo "--- Cron Jobs ---" >> /cases/case-2024-001/analysis/linux_persistence.txt
cat $LINUX_ROOT/etc/crontab >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
ls -la $LINUX_ROOT/etc/cron.d/ >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
cat $LINUX_ROOT/etc/cron.d/* >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
cat $LINUX_ROOT/var/spool/cron/crontabs/* >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
 
# Systemd services
echo "--- Custom Systemd Services ---" >> /cases/case-2024-001/analysis/linux_persistence.txt
find $LINUX_ROOT/etc/systemd/system/ -name "*.service" -not -type l \
   >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
 
# SSH authorized keys
echo "--- SSH Authorized Keys ---" >> /cases/case-2024-001/analysis/linux_persistence.txt
find $LINUX_ROOT/home/ $LINUX_ROOT/root/ -name "authorized_keys" -exec cat {} \; \
   >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
 
# Init scripts and rc.local
echo "--- RC Scripts ---" >> /cases/case-2024-001/analysis/linux_persistence.txt
cat $LINUX_ROOT/etc/rc.local >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
 
# Shell profile scripts
echo "--- Profile Scripts ---" >> /cases/case-2024-001/analysis/linux_persistence.txt
cat $LINUX_ROOT/etc/profile.d/*.sh >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
 
# LD_PRELOAD
echo "--- LD_PRELOAD ---" >> /cases/case-2024-001/analysis/linux_persistence.txt
cat $LINUX_ROOT/etc/ld.so.preload >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
grep -r "LD_PRELOAD" $LINUX_ROOT/etc/ >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
 
# Kernel modules
echo "--- Loaded Kernel Modules ---" >> /cases/case-2024-001/analysis/linux_persistence.txt
cat $LINUX_ROOT/etc/modules-load.d/*.conf >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null
 
# PAM backdoors
echo "--- PAM Configuration ---" >> /cases/case-2024-001/analysis/linux_persistence.txt
find $LINUX_ROOT/etc/pam.d/ -exec grep -l "pam_exec\|pam_script" {} \; \
   >> /cases/case-2024-001/analysis/linux_persistence.txt 2>/dev/null

Step 5: Compile Persistence Report

# Generate comprehensive persistence report
python3 << 'PYEOF'
import json
 
with open('/cases/case-2024-001/analysis/registry_persistence.json') as f:
    reg_data = json.load(f)
 
report = """
MALWARE PERSISTENCE INVESTIGATION REPORT
==========================================
 
PERSISTENCE MECHANISMS FOUND:
 
1. REGISTRY RUN KEYS:
"""
 
run_keys = [e for e in reg_data['registry_persistence'] if 'Run' in e.get('type', '')]
for entry in run_keys:
    report += f"   [{entry['timestamp']}] {entry.get('name', 'N/A')} -> {entry.get('value', '')[:100]}\n"
 
services = [e for e in reg_data['registry_persistence'] if e.get('type') == 'Service']
report += f"\n2. SERVICES ({len(services)} auto-start services):\n"
for entry in services[:20]:
    report += f"   {entry['location'].split('\\')[-1]}: {entry['value'][:100]}\n"
 
report += """
3. SCHEDULED TASKS: [See scheduled_tasks analysis]
4. WMI SUBSCRIPTIONS: [See WMI analysis]
5. STARTUP FOLDER: [See startup_items.txt]
6. COM HIJACKING: [See COM analysis]
 
SUSPICIOUS ENTRIES REQUIRING INVESTIGATION:
"""
 
# Flag suspicious entries
for entry in reg_data['registry_persistence']:
    value = str(entry.get('value', '')).lower()
    suspicious_indicators = ['powershell', 'cmd /c', 'wscript', 'certutil',
                             'programdata', 'appdata\\local\\temp', 'base64',
                             '.ps1', '.vbs', '.hta', '/tmp/', 'hidden']
    if any(s in value for s in suspicious_indicators):
        report += f"   SUSPICIOUS: {entry.get('name', 'N/A')} -> {entry.get('value', '')[:100]}\n"
 
with open('/cases/case-2024-001/analysis/persistence_report.txt', 'w') as f:
    f.write(report)
 
print(report)
PYEOF

Key Concepts

Concept Description
Run keys Registry keys executing programs at user logon (HKLM and HKCU)
Scheduled tasks Windows Task Scheduler entries that execute on triggers (time, event, logon)
WMI event subscriptions Persistent WMI queries that trigger actions (stealthy persistence)
COM hijacking Redirecting COM object loading to execute malicious DLLs
DLL search order hijacking Placing malicious DLLs in directories searched before System32
Service persistence Installing Windows services that auto-start with the system
Boot-level persistence Modifying boot configuration or MBR/VBR for pre-OS execution
Living-off-the-land Using legitimate system tools (PowerShell, WMI, certutil) for persistence

Tools & Systems

Tool Purpose
Autoruns Sysinternals comprehensive autostart enumeration tool
RegRipper Automated registry persistence artifact extraction
KAPE Automated persistence artifact collection and analysis
Velociraptor Endpoint agent with persistence hunting artifacts
OSQuery SQL-based system querying for persistence enumeration
PersistenceSniper PowerShell tool for Windows persistence detection
RECmd Eric Zimmerman registry command-line analysis tool
Volatility Memory forensics for in-memory only persistence

Common Scenarios

Scenario 1: APT Persistence After Initial Compromise Check all registry Run keys, enumerate scheduled tasks for encoded PowerShell commands, examine WMI event subscriptions for event-triggered execution, check COM object registrations for hijacked CLSIDs, review services for recently installed entries with suspicious image paths.

Scenario 2: Ransomware Pre-Encryption Persistence Identify how the ransomware maintains access for re-encryption or monitoring, check for scheduled tasks that would re-launch encryption, examine services installed by the ransomware operator, verify no additional backdoor persistence exists before declaring remediation complete.

Scenario 3: Fileless Malware Persistence Focus on registry-based persistence storing payload in registry values, check WMI subscriptions executing PowerShell from event triggers, examine scheduled tasks using encoded command arguments, check for mshta/rundll32 based persistence loading remote content.

Scenario 4: Post-Remediation Verification Run Autoruns comparison against known-good baseline, verify all identified persistence mechanisms have been removed, check for additional persistence that may have been missed, confirm services, tasks, and registry entries are clean, monitor for re-infection indicators.

Output Format

Persistence Investigation Summary:
  System: DESKTOP-ABC123 (Windows 10 Pro)
  Analysis Date: 2024-01-20
 
  Persistence Mechanisms Found:
    Registry Run Keys (HKLM):    5 entries (1 SUSPICIOUS)
    Registry Run Keys (HKCU):    3 entries (1 SUSPICIOUS)
    Services (Auto-Start):       142 entries (2 SUSPICIOUS)
    Scheduled Tasks:             67 entries (3 SUSPICIOUS)
    WMI Subscriptions:           1 entry (SUSPICIOUS)
    Startup Folder:              4 items (1 SUSPICIOUS)
    COM Objects:                 0 hijacked entries
    DLL Hijacking:               0 detected
 
  Suspicious Entries:
    1. HKCU\Run\WindowsUpdate -> powershell -ep bypass -e <base64>
       Timestamp: 2024-01-15 14:35:00
       Action: Encoded PowerShell download cradle
 
    2. Service: WinDefenderUpdate -> C:\ProgramData\svc\update.exe
       Timestamp: 2024-01-15 14:40:00
       Action: Unknown executable in ProgramData
 
    3. Task: \Microsoft\Windows\Maintenance\SecurityUpdate
       Command: cmd.exe /c powershell -w hidden -e <base64>
       Trigger: On system startup
 
    4. WMI: __EventFilter "ProcessStart" -> CommandLineEventConsumer
       Action: Execute C:\Windows\Temp\svc.exe on WMI event
 
  Remediation Required: 4 persistence mechanisms to remove
  Report: /cases/case-2024-001/analysis/persistence_report.txt
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.9 KB

API Reference: Malware Persistence Investigation

python-registry Library

from Registry import Registry
reg = Registry.Registry("SOFTWARE")
key = reg.open("Microsoft\\Windows\\CurrentVersion\\Run")
for value in key.values():
    print(f"{value.name()} -> {value.value()}")

Key Windows Persistence Locations

Location Type Registry Path / Filesystem Path
Run Keys (HKLM) Registry SOFTWARE\Microsoft\Windows\CurrentVersion\Run
Run Keys (HKCU) Registry NTUSER.DAT\Software\Microsoft\Windows\CurrentVersion\Run
Services Registry SYSTEM\ControlSetXXX\Services
Scheduled Tasks Filesystem C:\Windows\System32\Tasks\
WMI Subscriptions WMI DB C:\Windows\System32\wbem\Repository\OBJECTS.DATA
Startup Folder Filesystem %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
COM Hijacking Registry SOFTWARE\Classes\CLSID\{...}\InprocServer32

Linux Persistence Locations

Location Mechanism
/etc/crontab, /etc/cron.d/ Cron jobs
/etc/systemd/system/*.service Systemd services
~/.ssh/authorized_keys SSH key persistence
/etc/rc.local Boot scripts
/etc/ld.so.preload Shared library injection
/etc/pam.d/ PAM backdoors

Python Libraries

Library Version Purpose
python-registry >=1.4 Offline Windows registry hive parsing
xml.etree.ElementTree stdlib Scheduled task XML parsing
pathlib stdlib Filesystem traversal

References

Scripts 1

agent.py9.3 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for performing malware persistence investigation.

Enumerates Windows registry Run keys, services, scheduled tasks,
WMI subscriptions, and Linux cron/systemd persistence mechanisms.
"""

import json
import sys
import xml.etree.ElementTree as ET
from pathlib import Path


SUSPICIOUS_INDICATORS = [
    "powershell", "cmd /c", "wscript", "cscript", "mshta",
    "regsvr32", "rundll32", "certutil", "bitsadmin",
    "downloadstring", "invoke-", "iex", "hidden",
    "bypass", "base64", "-enc", "-e ", ".ps1", ".vbs", ".hta",
    "programdata", "appdata\\local\\temp", "/tmp/",
]


class PersistenceInvestigator:
    """Investigates malware persistence mechanisms on forensic images."""

    def __init__(self, evidence_root, output_dir):
        self.evidence_root = Path(evidence_root)
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.findings = []

    def _is_suspicious(self, value):
        """Check if a value matches suspicious indicators."""
        value_lower = str(value).lower()
        return any(ind in value_lower for ind in SUSPICIOUS_INDICATORS)

    def check_registry_run_keys(self):
        """Check registry Run keys using python-registry."""
        try:
            from Registry import Registry
        except ImportError:
            return [{"error": "Install python-registry: pip install python-registry"}]

        entries = []
        sw_path = self.evidence_root / "Windows/System32/config/SOFTWARE"
        if not sw_path.exists():
            return entries

        reg = Registry.Registry(str(sw_path))
        run_paths = [
            "Microsoft\\Windows\\CurrentVersion\\Run",
            "Microsoft\\Windows\\CurrentVersion\\RunOnce",
            "Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run",
        ]
        for key_path in run_paths:
            try:
                key = reg.open(key_path)
                for value in key.values():
                    entry = {
                        "location": f"HKLM\\SOFTWARE\\{key_path}",
                        "name": value.name(),
                        "value": str(value.value()),
                        "timestamp": str(key.timestamp()),
                        "suspicious": self._is_suspicious(str(value.value())),
                    }
                    entries.append(entry)
                    if entry["suspicious"]:
                        self.findings.append(entry)
            except Exception:
                continue
        return entries

    def check_services(self):
        """Check Windows services for suspicious auto-start entries."""
        try:
            from Registry import Registry
        except ImportError:
            return []

        entries = []
        system_path = self.evidence_root / "Windows/System32/config/SYSTEM"
        if not system_path.exists():
            return entries

        reg = Registry.Registry(str(system_path))
        try:
            select = reg.open("Select")
            current = select.value("Current").value()
            cs = f"ControlSet{current:03d}"
            services = reg.open(f"{cs}\\Services")

            for svc in services.subkeys():
                try:
                    start = svc.value("Start").value()
                    if start not in (0, 1, 2):
                        continue
                    image_path = ""
                    try:
                        image_path = svc.value("ImagePath").value()
                    except Exception:
                        continue
                    entry = {
                        "service_name": svc.name(),
                        "image_path": image_path,
                        "start_type": start,
                        "timestamp": str(svc.timestamp()),
                        "suspicious": self._is_suspicious(image_path),
                    }
                    entries.append(entry)
                    if entry["suspicious"]:
                        self.findings.append(entry)
                except Exception:
                    continue
        except Exception:
            pass
        return entries

    def check_scheduled_tasks(self):
        """Parse Windows scheduled task XML files."""
        tasks_dir = self.evidence_root / "Windows/System32/Tasks"
        if not tasks_dir.exists():
            return []

        entries = []
        ns = {"t": "http://schemas.microsoft.com/windows/2004/02/mit/task"}

        for task_file in tasks_dir.rglob("*"):
            if not task_file.is_file():
                continue
            try:
                tree = ET.parse(str(task_file))
                root = tree.getroot()
                for action in root.findall(".//t:Exec", ns):
                    cmd = action.find("t:Command", ns)
                    args = action.find("t:Arguments", ns)
                    cmd_text = cmd.text if cmd is not None else ""
                    args_text = args.text if args is not None else ""
                    combined = f"{cmd_text} {args_text}"
                    entry = {
                        "task_name": task_file.name,
                        "command": cmd_text,
                        "arguments": args_text,
                        "suspicious": self._is_suspicious(combined),
                    }
                    entries.append(entry)
                    if entry["suspicious"]:
                        self.findings.append(entry)
            except Exception:
                continue
        return entries

    def check_startup_folder(self):
        """Check Windows startup folder contents."""
        entries = []
        startup_paths = [
            "ProgramData/Microsoft/Windows/Start Menu/Programs/Startup",
        ]
        for user_dir in (self.evidence_root / "Users").iterdir() if (self.evidence_root / "Users").exists() else []:
            if user_dir.is_dir():
                startup_paths.append(
                    f"Users/{user_dir.name}/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup"
                )

        for spath in startup_paths:
            target = self.evidence_root / spath
            if target.exists() and target.is_dir():
                for item in target.iterdir():
                    if item.is_file():
                        entries.append({
                            "location": str(spath),
                            "filename": item.name,
                            "size": item.stat().st_size,
                            "suspicious": self._is_suspicious(item.name),
                        })
        return entries

    def check_linux_persistence(self):
        """Check Linux persistence mechanisms."""
        entries = []
        checks = {
            "crontab": "etc/crontab",
            "rc_local": "etc/rc.local",
            "ld_preload": "etc/ld.so.preload",
        }
        for name, path in checks.items():
            target = self.evidence_root / path
            if target.exists() and target.is_file():
                content = target.read_text(errors="ignore")
                entries.append({
                    "mechanism": name,
                    "file": path,
                    "content_preview": content[:500],
                    "suspicious": self._is_suspicious(content),
                })

        cron_dirs = ["etc/cron.d", "etc/cron.daily", "var/spool/cron/crontabs"]
        for cron_dir in cron_dirs:
            target = self.evidence_root / cron_dir
            if target.exists() and target.is_dir():
                for f in target.iterdir():
                    if f.is_file():
                        content = f.read_text(errors="ignore")
                        entries.append({
                            "mechanism": "cron_job",
                            "file": str(f.relative_to(self.evidence_root)),
                            "content_preview": content[:300],
                        })

        auth_keys_pattern = "home/*/. ssh/authorized_keys".replace(". ", ".")
        for ak in self.evidence_root.glob("home/*/.ssh/authorized_keys"):
            entries.append({
                "mechanism": "ssh_authorized_keys",
                "file": str(ak.relative_to(self.evidence_root)),
                "content_preview": ak.read_text(errors="ignore")[:300],
            })
        return entries

    def generate_report(self):
        """Run all persistence checks and generate report."""
        report = {
            "case_output": str(self.output_dir),
            "evidence_root": str(self.evidence_root),
            "registry_run_keys": self.check_registry_run_keys(),
            "services": self.check_services()[:30],
            "scheduled_tasks": self.check_scheduled_tasks(),
            "startup_folder": self.check_startup_folder(),
            "linux_persistence": self.check_linux_persistence(),
            "suspicious_findings": self.findings,
            "total_suspicious": len(self.findings),
        }

        report_path = self.output_dir / "persistence_report.json"
        with open(report_path, "w") as f:
            json.dump(report, f, indent=2)
        print(json.dumps(report, indent=2))
        return report


def main():
    if len(sys.argv) < 3:
        print("Usage: agent.py <evidence_root> <output_dir>")
        sys.exit(1)
    investigator = PersistenceInvestigator(sys.argv[1], sys.argv[2])
    investigator.generate_report()


if __name__ == "__main__":
    main()
Keep exploring