--- name: performing-malware-persistence-investigation description: Systematically investigate all persistence mechanisms on Windows and Linux systems to identify how malware survives reboots and maintains access. domain: cybersecurity subdomain: digital-forensics tags: - forensics - malware-persistence - autoruns - registry - scheduled-tasks - rootkit-detection - incident-response mitre_attack: - T1547.001 - T1053.005 - T1543.003 - T1546.003 - T1574 version: '1.0' author: mahipal license: Apache-2.0 nist_csf: - RS.AN-01 - RS.AN-03 - DE.AE-02 - RS.MA-01 --- # Performing Malware Persistence Investigation ## 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 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 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 ```