From c0765482d2208d83e8304c2987bdb779f0e8b4ca Mon Sep 17 00:00:00 2001 From: n0mad1k Date: Wed, 1 Apr 2026 21:52:21 -0400 Subject: [PATCH] Fix ioc-u global declaration order and ops-logger CRLF line endings --- tools/redteam/ioc-u/ioc-u.py | 1868 ++++++++++++++++++++++++++++++++++ tools/redteam/ops-logger.sh | 1800 ++++++++++++++++++++++++++++++++ 2 files changed, 3668 insertions(+) create mode 100644 tools/redteam/ioc-u/ioc-u.py create mode 100644 tools/redteam/ops-logger.sh diff --git a/tools/redteam/ioc-u/ioc-u.py b/tools/redteam/ioc-u/ioc-u.py new file mode 100644 index 0000000..83a2273 --- /dev/null +++ b/tools/redteam/ioc-u/ioc-u.py @@ -0,0 +1,1868 @@ +#!/usr/bin/env python3 +""" +IOC-U Enhanced: Advanced Blue Team Detection & Intelligence Tool for Red Team Operations +Version 2.0 - Detecting Blue Team IR/SOC Activities with 2024-2025 capabilities +""" + +import argparse +import subprocess +import threading +import os +import json +import sys +import time +import signal +import logging + +# Ops hook (safe no-op if c2itall not available) +try: + sys.path.insert(0, os.path.expanduser("~/tools/c2itall")) + from utils.ops_hook import OpsHook as _OpsHook +except ImportError: + _OpsHook = None +import socket +import ipaddress +import re +import hashlib +import struct +import base64 +from datetime import datetime, timedelta +from scapy.all import sniff, IP, TCP, UDP, Raw, get_if_list, DNS, DNSQR, DNSRR +from collections import defaultdict, deque, Counter +from typing import Dict, List, Tuple, Optional, Set, Any + +# Try to import ML libraries, but make them optional +try: + import numpy as np + from sklearn.ensemble import IsolationForest + from sklearn.preprocessing import StandardScaler + ML_AVAILABLE = True +except ImportError: + ML_AVAILABLE = False + print("āš ļø Warning: ML libraries not available. Install with: pip install numpy scikit-learn") + +CONFIG_PATH = os.path.expanduser("~/.ioc_u_config.json") +PID_FILE = "/tmp/.ioc_u.pid" +LOG_FILE = "/tmp/.ioc_u.log" +INTELLIGENCE_LOG = os.path.expanduser("~/.ioc_u_intelligence.json") +BEHAVIORAL_MODEL = os.path.expanduser("~/.ioc_u_behavioral_model.pkl") + +# Enhanced configuration with AI/ML and modern blue team detection patterns +default_config = { + "email_enabled": False, + "alert_email": "", + "gpg_recipient": "", + "smtp_configured": False, + "interface": "auto", + "detection_threshold": 3, + "alert_cooldown": 30, + "stealth_mode": True, + "canary_ports": [2222, 8888, 5900, 9999, 1433, 3389, 445, 139], + + # Enhanced blue team detection settings + "evidence_collection_threshold": 3, + "investigation_time_window": 300, + "systematic_investigation_threshold": 5, + "behavioral_anomaly_threshold": 0.85, + + # AI/ML detection settings + "enable_ml_detection": True and ML_AVAILABLE, + "ml_model_update_interval": 3600, + "anomaly_detection_sensitivity": 0.05, + + # Modern SIEM detection patterns (Blue Team Tools) + "siem_detection_patterns": { + "splunk": { + "ports": [8089, 8000, 9997, 8088], + "patterns": ["splunkd", "search?", "servicesNS", "en-US/app", "SPL-"], + "risk_based_alerting": ["risk_score", "risk_object", "risk_notable"], + "charlotte_ai": ["charlotte_ai", "spl_generation", "automated_investigation"] + }, + "elastic": { + "ports": [9200, 5601, 9300], + "patterns": ["_search", "_msearch", ".kibana", "_ml/anomaly_detectors"], + "ai_patterns": ["attack_discovery", "security_assistant", "ml_job"] + }, + "sentinel": { + "ports": [443], + "patterns": ["SecurityInsights", "ThreatIntelligence", "incidents/", "sentinel"], + "kql_patterns": ["Kusto.Language", "summarize", "project", "where"] + }, + "chronicle": { + "ports": [443], + "patterns": ["chronicle.security", "yaral", "udm_search", "retrohunt"], + "detection_patterns": ["rule_engine", "threat_graph", "entity_graph"] + }, + "qradar": { + "ports": [443, 514, 1514], + "patterns": ["console/restapi", "ariel", "reference_data", "offense"], + "offense_patterns": ["offense_id", "rule_id", "qidmap"] + }, + "sumo_logic": { + "ports": [443], + "patterns": ["sumologic", "api/v1/search", "insight_trainer"], + "ml_patterns": ["outlier_detection", "baseline_learning"] + } + }, + + # EDR/XDR detection patterns (Blue Team Endpoint Tools) + "edr_detection_patterns": { + "crowdstrike": { + "ports": [443, 8080], + "patterns": ["falcon", "cs-falcon", "crowdstrike", "falconhoseclient"], + "behavioral_ioas": ["ProcessRollup", "DnsRequest", "NetworkConnect"], + "hardware_enhanced": ["intel_processor_trace", "rop_detection"], + "charlotte_ai": ["threat_graph", "automated_remediation"] + }, + "sentinelone": { + "ports": [443], + "patterns": ["sentinelone", "mgmt.sentinelone", "sentinelagent", "s1-"], + "ai_engine": ["static_ai", "behavioral_ai", "purple_ai"], + "dbt_tracking": ["dynamic_behavioral_tracking", "storyline"] + }, + "defender": { + "ports": [443], + "patterns": ["defender", "mdatp", "windowsdefender", "sense.exe", "mssense"], + "advanced_hunting": ["DeviceProcessEvents", "DeviceNetworkEvents", "DeviceFileEvents"], + "edr_block_mode": ["passive_mode_block", "post_breach_detection"] + }, + "cortex_xdr": { + "ports": [443], + "patterns": ["cortex", "xdr", "paloaltonetworks", "traps"], + "ml_models": ["ensemble_learning", "local_analysis", "causality_analysis"], + "behavioral_threat": ["bioc", "analytics_engine"] + }, + "carbon_black": { + "ports": [443], + "patterns": ["carbonblack", "cb.defense", "cbcloud", "confer"], + "live_response": ["lr_session", "process_search", "cblr"] + }, + "cybereason": { + "ports": [443, 8443], + "patterns": ["cybereason", "malop", "cybereason-sensor"], + "ai_detection": ["malop_detection", "root_cause_analysis"] + } + }, + + # Network monitoring and NDR patterns (Blue Team Network Tools) + "ndr_patterns": { + "darktrace": { + "ports": [443], + "patterns": ["darktrace", "immune_system", "antigena", "cyber-ai"], + "ai_patterns": ["pattern_of_life", "self_learning", "autonomous_response", "threat_visualizer"] + }, + "vectra": { + "ports": [443, 8443], + "patterns": ["vectra", "cognito", "attack_signal", "vectra-brain"], + "detection_types": ["account_takeover", "lateral_movement", "data_smuggling", "command_control"] + }, + "extrahop": { + "ports": [443], + "patterns": ["extrahop", "revealx", "extrahop-monitor"], + "protocol_analysis": ["wire_data", "l7_visibility", "ssl_decrypt"] + }, + "corelight": { + "ports": [443, 47760], + "patterns": ["corelight", "zeek", "bro-", "corelight-sensor"], + "log_types": ["conn.log", "dns.log", "http.log", "ssl.log", "files.log"] + }, + "netwitness": { + "ports": [443, 50103, 56003], + "patterns": ["netwitness", "rsa-nw", "decoder", "concentrator"], + "analysis": ["meta_extraction", "session_reconstruction"] + } + }, + + # Advanced forensic patterns (Blue Team Investigation Tools) + "forensic_patterns": { + "memory_analysis": [ + r'volatility', r'rekall', r'winpmem', r'dumpit', r'redline', + r'malfind', r'psscan', r'dlllist', r'handles', r'vadscan', + r'\.vmem$', r'\.raw$', r'\.dmp$', r'hiberfil\.sys', r'pagefile\.sys' + ], + "artifact_collection": [ + r'kape\.exe', r'ftk', r'encase', r'x-ways', r'axiom', r'autopsy', + r'MFT', r'USN.*journal', r'\.evtx', r'prefetch', r'jumplist', + r'shimcache', r'amcache', r'registry.*hive', r'ntuser\.dat' + ], + "cloud_forensics": [ + r'cloudtrail', r'azure.*activity', r'stackdriver', r'flowlogs', + r'snapshot', r'forensic.*image', r'vpc.*flow', r'cloud.*logs' + ], + "threat_hunting": [ + r'osquery', r'velociraptor', r'grr.*client', r'fleet', + r'yara', r'sigma', r'hayabusa', r'chainsaw', r'deepsearch' + ] + }, + + # Blue team PowerShell commands + "powershell_investigation": [ + "Get-WinEvent", "Get-EventLog", "Get-Process", "Get-Service", + "Get-NetTCPConnection", "Get-NetUDPEndpoint", "Get-SmbConnection", + "Get-ChildItem", "Get-Content", "Get-FileHash", "Get-ItemProperty", + "Get-LocalUser", "Get-LocalGroupMember", "Get-ScheduledTask", + "Get-CimInstance", "Invoke-Command", "Enter-PSSession" + ], + + # WMI investigation queries + "wmi_investigation": [ + "Win32_Process", "Win32_Service", "Win32_LoggedOnUser", + "Win32_NetworkConnection", "Win32_StartupCommand", + "Win32_ScheduledJob", "Win32_Share", "Win32_UserAccount" + ], + + # SOAR platform patterns (Blue Team Automation) + "soar_patterns": { + "platforms": ["phantom", "xsoar", "swimlane", "siemplify", "securonix", "resilient"], + "playbooks": ["automated_response", "enrichment", "containment", "remediation"], + "integrations": ["threat_intel", "sandbox_analysis", "reputation_check"] + }, + + # Operational filtering (Red Team Protection) + "trusted_networks": [], + "operator_ips": [], + "reverse_shell_ports": list(range(4444, 4500)) + list(range(8080, 8090)) + [1337, 31337, 9001], + "session_timeout": 3600, + + # Intelligence settings + "intelligence_mode": True, + "intelligence_retention_days": 7, + "alert_on_investigation_only": True, + + # Purple team settings + "purple_team_mode": False, + "noise_escalation_level": 1, + "detection_testing_mode": False +} + +class BlueTeamActivityDetector: + """Detects blue team IR and SOC investigation activities""" + + def __init__(self, config): + self.config = config + + # Blue team investigation patterns + self.investigation_commands = [ + # Windows commands + r'tasklist', r'netstat', r'net\s+\w+', r'wmic', r'systeminfo', + r'reg\s+query', r'dir\s*/s', r'findstr', r'type\s+\w+\.log', + + # Linux commands + r'ps\s+aux', r'netstat\s+-[tulpn]+', r'lsof', r'find\s+/', + r'grep\s+-r', r'tail\s+-f', r'journalctl', r'ausearch', + + # Investigation tools + r'nmap', r'masscan', r'zmap', r'shodan', r'censys', + r'nikto', r'dirb', r'gobuster', r'burpsuite', r'zap' + ] + + # Blue team user agents + self.blue_team_agents = [ + r'splunk', r'elastic', r'kibana', r'qradar', r'sentinel', + r'crowdstrike', r'sentinelone', r'carbon.*black', r'defender', + r'python.*requests', r'powershell', r'curl/', r'wget/' + ] + + # Evidence collection indicators + self.evidence_indicators = [ + 'collect', 'acquire', 'evidence', 'forensic', 'artifact', + 'investigation', 'incident', 'compromise', 'breach', 'malware' + ] + +class NetworkFlowAnalyzer: + """Analyzes network flows for blue team investigation patterns""" + + def __init__(self, config): + self.config = config + self.connection_tracker = defaultdict(list) + self.investigation_patterns = defaultdict(Counter) + + def track_connection(self, src_ip, dst_ip, dst_port, payload, timestamp): + """Track connections for pattern analysis""" + connection = { + 'dst_ip': dst_ip, + 'dst_port': dst_port, + 'timestamp': timestamp, + 'payload_size': len(payload) if payload else 0, + 'payload_hash': hashlib.md5(payload.encode() if payload else b'').hexdigest() + } + + self.connection_tracker[src_ip].append(connection) + + # Keep only recent connections + cutoff = timestamp - self.config['investigation_time_window'] + self.connection_tracker[src_ip] = [ + c for c in self.connection_tracker[src_ip] + if c['timestamp'] > cutoff + ] + + def detect_systematic_investigation(self, src_ip): + """Detect systematic blue team investigation patterns""" + connections = self.connection_tracker.get(src_ip, []) + + if len(connections) < self.config['systematic_investigation_threshold']: + return None + + # Pattern 1: Multiple hosts being investigated (IOC sweeping) + unique_targets = len(set(c['dst_ip'] for c in connections)) + if unique_targets >= 5: + return { + 'type': 'BLUE_TEAM_IOC_SWEEP', + 'details': f'Blue team scanning {unique_targets} hosts', + 'severity': 'HIGH' + } + + # Pattern 2: Multiple ports on same host (host investigation) + for dst_ip in set(c['dst_ip'] for c in connections): + host_connections = [c for c in connections if c['dst_ip'] == dst_ip] + unique_ports = len(set(c['dst_port'] for c in host_connections)) + + if unique_ports >= 10: + return { + 'type': 'BLUE_TEAM_HOST_INVESTIGATION', + 'details': f'Blue team investigating {dst_ip} ({unique_ports} ports)', + 'severity': 'HIGH' + } + + # Pattern 3: Repeated queries (persistence checking) + payload_counts = Counter(c['payload_hash'] for c in connections) + repeated_queries = [h for h, count in payload_counts.items() if count >= 3] + + if repeated_queries: + return { + 'type': 'BLUE_TEAM_PERSISTENCE_CHECK', + 'details': 'Blue team checking for persistence/IOCs repeatedly', + 'severity': 'MEDIUM' + } + + return None + +class BlueTeamIntelligence: + """Intelligence gathering on blue team activities""" + + def __init__(self, config): + self.config = config + self.blue_team_activities = defaultdict(list) + self.investigation_timeline = defaultdict(list) + self.detected_platforms = Counter() + self.investigation_techniques = Counter() + self.load_intelligence() + + def load_intelligence(self): + """Load existing intelligence data""" + if os.path.exists(INTELLIGENCE_LOG): + try: + with open(INTELLIGENCE_LOG, 'r') as f: + data = json.load(f) + + # Restore counters and data + if 'detected_platforms' in data: + self.detected_platforms = Counter(data['detected_platforms']) + + if 'investigation_techniques' in data: + self.investigation_techniques = Counter(data['investigation_techniques']) + + except Exception as e: + logging.error(f"Could not load intelligence: {e}") + + def save_intelligence(self): + """Save intelligence data""" + try: + data = { + 'last_updated': datetime.now().isoformat(), + 'detected_platforms': dict(self.detected_platforms), + 'investigation_techniques': dict(self.investigation_techniques), + 'timeline_entries': len(self.investigation_timeline) + } + + with open(INTELLIGENCE_LOG, 'w') as f: + json.dump(data, f, indent=2) + + except Exception as e: + logging.error(f"Could not save intelligence: {e}") + + def record_blue_team_activity(self, src_ip, activity_type, platform, details): + """Record blue team activity""" + timestamp = datetime.now() + + activity = { + 'timestamp': timestamp.isoformat(), + 'src_ip': src_ip, + 'type': activity_type, + 'platform': platform, + 'details': details + } + + self.blue_team_activities[src_ip].append(activity) + self.investigation_timeline[timestamp.date().isoformat()].append(activity) + + if platform: + self.detected_platforms[platform] += 1 + + self.investigation_techniques[activity_type] += 1 + + def get_threat_assessment(self): + """Generate blue team threat assessment""" + assessment = { + 'active_investigators': [], + 'investigation_intensity': 'LOW', + 'primary_platforms': [], + 'investigation_focus': [], + 'recommendations': [] + } + + # Analyze active investigators + current_time = datetime.now() + for src_ip, activities in self.blue_team_activities.items(): + recent_activities = [ + a for a in activities + if (current_time - datetime.fromisoformat(a['timestamp'])).seconds < 3600 + ] + + if recent_activities: + assessment['active_investigators'].append({ + 'ip': src_ip, + 'activity_count': len(recent_activities), + 'platforms': list(set(a.get('platform', 'Unknown') for a in recent_activities)) + }) + + # Determine investigation intensity + total_recent_activities = sum( + len([a for a in activities + if (current_time - datetime.fromisoformat(a['timestamp'])).seconds < 3600]) + for activities in self.blue_team_activities.values() + ) + + if total_recent_activities > 50: + assessment['investigation_intensity'] = 'CRITICAL' + elif total_recent_activities > 20: + assessment['investigation_intensity'] = 'HIGH' + elif total_recent_activities > 5: + assessment['investigation_intensity'] = 'MEDIUM' + + # Primary platforms + assessment['primary_platforms'] = [ + platform for platform, count in self.detected_platforms.most_common(5) + ] + + # Investigation focus + assessment['investigation_focus'] = [ + technique for technique, count in self.investigation_techniques.most_common(5) + ] + + # Generate recommendations + if assessment['investigation_intensity'] in ['HIGH', 'CRITICAL']: + assessment['recommendations'].append( + "CRITICAL: Active blue team investigation detected. Consider pausing operations." + ) + + if 'EDR' in str(assessment['primary_platforms']): + assessment['recommendations'].append( + "HIGH: EDR platform active. Ensure anti-EDR measures are in place." + ) + + if 'MEMORY_FORENSICS' in assessment['investigation_focus']: + assessment['recommendations'].append( + "HIGH: Memory forensics detected. Clear memory artifacts and avoid process injection." + ) + + return assessment + +if ML_AVAILABLE: + class MLBehavioralEngine: + """ML-based behavioral analysis for blue team detection""" + + def __init__(self, config): + self.config = config + self.scaler = StandardScaler() + self.anomaly_detector = IsolationForest( + contamination=config['anomaly_detection_sensitivity'], + random_state=42, + n_estimators=100 + ) + self.baselines = defaultdict(lambda: deque(maxlen=1000)) + self.is_trained = False + + def extract_features(self, connection_data): + """Extract features for ML analysis""" + features = [] + + # Connection patterns + features.append(len(connection_data)) # Connection count + features.append(len(set(c.get('dst_ip', '') for c in connection_data))) # Unique destinations + features.append(len(set(c.get('dst_port', 0) for c in connection_data))) # Unique ports + + # Temporal patterns + if len(connection_data) > 1: + timestamps = [c.get('timestamp', 0) for c in connection_data] + time_deltas = [timestamps[i+1] - timestamps[i] for i in range(len(timestamps)-1)] + features.append(np.mean(time_deltas) if time_deltas else 0) + features.append(np.std(time_deltas) if time_deltas else 0) + else: + features.extend([0, 0]) + + # Data transfer patterns + total_bytes = sum(c.get('bytes', 0) for c in connection_data) + features.append(total_bytes) + + return np.array(features).reshape(1, -1) + + def detect_anomaly(self, src_ip, connection_data): + """Detect anomalous blue team behavior""" + if len(connection_data) < 10: + return None + + features = self.extract_features(connection_data) + + # Update baseline + self.baselines[src_ip].extend(connection_data) + + # Need enough data to train + if len(self.baselines[src_ip]) < 100: + return None + + # Train or update model + if not self.is_trained or len(self.baselines[src_ip]) % 100 == 0: + all_features = [] + for baseline_data in self.baselines.values(): + if len(baseline_data) >= 50: + all_features.append(self.extract_features(list(baseline_data))) + + if len(all_features) >= 5: + X = np.vstack(all_features) + X_scaled = self.scaler.fit_transform(X) + self.anomaly_detector.fit(X_scaled) + self.is_trained = True + + if self.is_trained: + # Detect anomaly + scaled_features = self.scaler.transform(features) + anomaly_score = self.anomaly_detector.decision_function(scaled_features)[0] + is_anomaly = self.anomaly_detector.predict(scaled_features)[0] == -1 + + if is_anomaly: + return { + 'type': 'ML_BLUE_TEAM_ANOMALY', + 'details': f'Anomalous blue team behavior detected (score: {anomaly_score:.2f})', + 'severity': 'HIGH' if anomaly_score < -0.5 else 'MEDIUM' + } + + return None + +class ModernSIEMDetector: + """Detection engine for modern SIEM platforms used by blue teams""" + + def __init__(self, config): + self.config = config + self.siem_patterns = config['siem_detection_patterns'] + + def detect_siem_activity(self, src_ip, dst_ip, dst_port, payload): + """Detect SIEM platform activities from blue teams""" + if not payload: + return None + + payload_lower = payload.lower() + + # Check each SIEM platform + for platform, patterns in self.siem_patterns.items(): + # Port-based detection + if dst_port in patterns['ports']: + # Check standard patterns + for pattern in patterns['patterns']: + if pattern.lower() in payload_lower: + return { + 'type': 'BLUE_TEAM_SIEM_QUERY', + 'platform': platform.upper(), + 'details': f'{platform} SIEM query detected: {pattern}', + 'severity': 'HIGH' + } + + # Check AI-enhanced patterns + if 'charlotte_ai' in patterns: + for ai_pattern in patterns['charlotte_ai']: + if ai_pattern in payload_lower: + return { + 'type': 'BLUE_TEAM_AI_INVESTIGATION', + 'platform': platform.upper(), + 'details': f'{platform} AI-powered investigation: {ai_pattern}', + 'severity': 'CRITICAL' + } + + # Risk-based alerting (Splunk) + if platform == 'splunk' and 'risk_based_alerting' in patterns: + for rba_pattern in patterns['risk_based_alerting']: + if rba_pattern in payload_lower: + return { + 'type': 'BLUE_TEAM_RISK_INVESTIGATION', + 'platform': 'SPLUNK', + 'details': 'Splunk Risk-Based Alerting investigation', + 'severity': 'HIGH' + } + + # KQL queries (Sentinel) + if platform == 'sentinel' and 'kql_patterns' in patterns: + for kql_pattern in patterns['kql_patterns']: + if kql_pattern in payload_lower: + return { + 'type': 'BLUE_TEAM_KQL_HUNTING', + 'platform': 'SENTINEL', + 'details': 'Microsoft Sentinel KQL threat hunting', + 'severity': 'HIGH' + } + + return None + +class EDRXDRDetector: + """Detection for EDR/XDR platforms used by blue teams""" + + def __init__(self, config): + self.config = config + self.edr_patterns = config['edr_detection_patterns'] + + def detect_edr_activity(self, src_ip, dst_ip, dst_port, payload): + """Detect EDR/XDR blue team activities""" + if not payload: + return None + + payload_lower = payload.lower() + + for platform, patterns in self.edr_patterns.items(): + if dst_port in patterns['ports']: + # Standard pattern detection + for pattern in patterns['patterns']: + if pattern.lower() in payload_lower: + return { + 'type': 'BLUE_TEAM_EDR_SCAN', + 'platform': platform.upper(), + 'details': f'{platform} EDR scanning activity', + 'severity': 'HIGH' + } + + # Behavioral IOA detection + if 'behavioral_ioas' in patterns: + for ioa in patterns['behavioral_ioas']: + if ioa.lower() in payload_lower: + return { + 'type': 'BLUE_TEAM_BEHAVIORAL_DETECTION', + 'platform': platform.upper(), + 'details': f'{platform} behavioral analysis: {ioa}', + 'severity': 'CRITICAL' + } + + # Advanced hunting (Defender) + if platform == 'defender' and 'advanced_hunting' in patterns: + for hunt_query in patterns['advanced_hunting']: + if hunt_query.lower() in payload_lower: + return { + 'type': 'BLUE_TEAM_ADVANCED_HUNTING', + 'platform': 'DEFENDER', + 'details': f'Defender ATP hunting: {hunt_query}', + 'severity': 'HIGH' + } + + return None + +class NetworkMonitorDetector: + """Detection for NDR and network monitoring by blue teams""" + + def __init__(self, config): + self.config = config + self.ndr_patterns = config['ndr_patterns'] + + def detect_ndr_activity(self, src_ip, dst_ip, dst_port, payload): + """Detect network monitoring by blue teams""" + if not payload: + return None + + payload_lower = payload.lower() + + for platform, patterns in self.ndr_patterns.items(): + if dst_port in patterns.get('ports', []): + for pattern in patterns['patterns']: + if pattern.lower() in payload_lower: + # Check for AI patterns + if 'ai_patterns' in patterns: + for ai_pattern in patterns['ai_patterns']: + if ai_pattern.lower() in payload_lower: + return { + 'type': 'BLUE_TEAM_AI_NDR', + 'platform': platform.upper(), + 'details': f'{platform} AI network analysis: {ai_pattern}', + 'severity': 'CRITICAL' + } + + return { + 'type': 'BLUE_TEAM_NETWORK_MONITORING', + 'platform': platform.upper(), + 'details': f'{platform} network monitoring detected', + 'severity': 'MEDIUM' + } + + return None + +class ForensicsDetector: + """Detection for blue team forensics and threat hunting""" + + def __init__(self, config): + self.config = config + self.forensic_patterns = config['forensic_patterns'] + self.powershell_cmds = config['powershell_investigation'] + self.wmi_queries = config['wmi_investigation'] + + def detect_forensic_activity(self, payload): + """Detect forensic investigation by blue teams""" + if not payload: + return None + + payload_lower = payload.lower() + + # Memory forensics detection + for pattern in self.forensic_patterns['memory_analysis']: + if re.search(pattern, payload_lower): + return { + 'type': 'BLUE_TEAM_MEMORY_FORENSICS', + 'details': f'Memory forensics detected: {pattern}', + 'severity': 'CRITICAL' + } + + # Artifact collection + for pattern in self.forensic_patterns['artifact_collection']: + if re.search(pattern, payload_lower): + return { + 'type': 'BLUE_TEAM_ARTIFACT_COLLECTION', + 'details': f'Artifact collection detected: {pattern}', + 'severity': 'HIGH' + } + + # PowerShell investigation + for cmd in self.powershell_cmds: + if cmd.lower() in payload_lower: + return { + 'type': 'BLUE_TEAM_POWERSHELL_INVESTIGATION', + 'details': f'PowerShell investigation command: {cmd}', + 'severity': 'HIGH' + } + + # WMI queries + for query in self.wmi_queries: + if query.lower() in payload_lower: + return { + 'type': 'BLUE_TEAM_WMI_QUERY', + 'details': f'WMI investigation query: {query}', + 'severity': 'HIGH' + } + + # Threat hunting tools + for pattern in self.forensic_patterns['threat_hunting']: + if re.search(pattern, payload_lower): + return { + 'type': 'BLUE_TEAM_THREAT_HUNTING', + 'details': f'Threat hunting tool detected: {pattern}', + 'severity': 'HIGH' + } + + return None + +class EnhancedBlueTeamDetector: + """Main detection engine for blue team activities""" + + def __init__(self, config): + self.config = config + + # Initialize detectors + self.activity_detector = BlueTeamActivityDetector(config) + self.flow_analyzer = NetworkFlowAnalyzer(config) + self.intelligence = BlueTeamIntelligence(config) + self.siem_detector = ModernSIEMDetector(config) + self.edr_detector = EDRXDRDetector(config) + self.ndr_detector = NetworkMonitorDetector(config) + self.forensics_detector = ForensicsDetector(config) + + if ML_AVAILABLE and config.get('enable_ml_detection'): + self.ml_engine = MLBehavioralEngine(config) + else: + self.ml_engine = None + + # Tracking + self.activity_tracker = defaultdict(lambda: { + 'connections': deque(maxlen=1000), + 'detections': deque(maxlen=100), + 'last_seen': time.time() + }) + + # Process management + self.running = True + self.canary_threads = [] + self.local_ip = self.get_local_ip() + self.last_alert_time = defaultdict(float) + + # Setup logging + self._setup_logging() + + def _setup_logging(self): + """Setup logging configuration""" + log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + + if self.config.get('stealth_mode'): + logging.basicConfig(level=logging.WARNING, format=log_format) + else: + logging.basicConfig( + level=logging.INFO, + format=log_format, + handlers=[ + logging.FileHandler(LOG_FILE), + logging.StreamHandler() + ] + ) + + def get_local_ip(self): + """Get local IP address""" + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + local_ip = s.getsockname()[0] + s.close() + return local_ip + except: + return "127.0.0.1" + + def packet_inspector(self, pkt): + """Inspect packets for blue team activity""" + if not IP in pkt: + return + + src_ip = pkt[IP].src + dst_ip = pkt[IP].dst + + # Skip trusted traffic (red team's own traffic) + if self._is_trusted_traffic(src_ip, dst_ip): + return + + # Extract packet info + packet_info = self._extract_packet_info(pkt) + if not packet_info: + return + + packet_info['dst_ip'] = dst_ip + + # Track connections + self.flow_analyzer.track_connection( + src_ip, dst_ip, + packet_info.get('dst_port', 0), + packet_info.get('payload', ''), + packet_info['timestamp'] + ) + + # Update activity tracking + self._update_activity_tracking(src_ip, packet_info) + + # Perform detection + detections = self._perform_detection(src_ip, dst_ip, packet_info) + + # Process detections + if detections: + self._process_detections(src_ip, dst_ip, detections) + + def _extract_packet_info(self, pkt): + """Extract relevant packet information""" + info = { + 'timestamp': time.time(), + 'protocol': 'TCP' if TCP in pkt else 'UDP' if UDP in pkt else 'OTHER' + } + + if TCP in pkt: + info['src_port'] = pkt[TCP].sport + info['dst_port'] = pkt[TCP].dport + info['tcp_flags'] = str(pkt[TCP].flags) + elif UDP in pkt: + info['src_port'] = pkt[UDP].sport + info['dst_port'] = pkt[UDP].dport + else: + return None + + if Raw in pkt: + try: + info['payload'] = pkt[Raw].load.decode(errors='ignore') + info['payload_size'] = len(pkt[Raw].load) + except: + info['payload'] = None + info['payload_size'] = 0 + else: + info['payload'] = None + info['payload_size'] = 0 + + return info + + def _update_activity_tracking(self, src_ip, packet_info): + """Update activity tracking""" + tracker = self.activity_tracker[src_ip] + + conn_info = { + 'timestamp': packet_info['timestamp'], + 'dst_ip': packet_info.get('dst_ip', ''), + 'dst_port': packet_info.get('dst_port', 0), + 'protocol': packet_info['protocol'], + 'bytes': packet_info.get('payload_size', 0) + } + + tracker['connections'].append(conn_info) + tracker['last_seen'] = packet_info['timestamp'] + + def _perform_detection(self, src_ip, dst_ip, packet_info): + """Perform multi-engine detection""" + detections = [] + + # SIEM detection + siem_result = self.siem_detector.detect_siem_activity( + src_ip, dst_ip, + packet_info.get('dst_port', 0), + packet_info.get('payload', '') + ) + if siem_result: + detections.append(siem_result) + + # EDR detection + edr_result = self.edr_detector.detect_edr_activity( + src_ip, dst_ip, + packet_info.get('dst_port', 0), + packet_info.get('payload', '') + ) + if edr_result: + detections.append(edr_result) + + # NDR detection + ndr_result = self.ndr_detector.detect_ndr_activity( + src_ip, dst_ip, + packet_info.get('dst_port', 0), + packet_info.get('payload', '') + ) + if ndr_result: + detections.append(ndr_result) + + # Forensics detection + forensics_result = self.forensics_detector.detect_forensic_activity( + packet_info.get('payload', '') + ) + if forensics_result: + detections.append(forensics_result) + + # Flow analysis + flow_result = self.flow_analyzer.detect_systematic_investigation(src_ip) + if flow_result: + detections.append(flow_result) + + # ML detection if available + if self.ml_engine: + ml_result = self.ml_engine.detect_anomaly( + src_ip, + list(self.activity_tracker[src_ip]['connections']) + ) + if ml_result: + detections.append(ml_result) + + # Canary detection + if packet_info.get('dst_port', 0) in self.config['canary_ports']: + detections.append({ + 'type': 'BLUE_TEAM_CANARY_INVESTIGATION', + 'details': f'Blue team accessed canary port {packet_info["dst_port"]}', + 'severity': 'CRITICAL' + }) + + return detections + + def _process_detections(self, src_ip, dst_ip, detections): + """Process and handle detections""" + # Update tracking + tracker = self.activity_tracker[src_ip] + for detection in detections: + tracker['detections'].append({ + 'timestamp': time.time(), + 'detection': detection + }) + + # Record in intelligence + platform = detection.get('platform', 'Unknown') + self.intelligence.record_blue_team_activity( + src_ip, + detection['type'], + platform, + detection['details'] + ) + + # Determine if alert needed + highest_severity = self._get_highest_severity(detections) + + should_alert = False + if self.config.get('alert_on_investigation_only'): + # Only alert on active investigation + investigation_types = [ + 'BLUE_TEAM_CANARY_INVESTIGATION', + 'BLUE_TEAM_MEMORY_FORENSICS', + 'BLUE_TEAM_HOST_INVESTIGATION', + 'BLUE_TEAM_AI_INVESTIGATION', + 'ML_BLUE_TEAM_ANOMALY' + ] + should_alert = any(d['type'] in investigation_types for d in detections) + else: + should_alert = highest_severity in ['HIGH', 'CRITICAL'] + + # Alert if targeting our machine + if dst_ip == self.local_ip: + should_alert = True + + if should_alert and self._check_alert_cooldown(src_ip): + self._handle_alert(src_ip, detections, highest_severity) + + def _get_highest_severity(self, detections): + """Get highest severity from detections""" + severity_order = {'LOW': 0, 'MEDIUM': 1, 'HIGH': 2, 'CRITICAL': 3} + + if not detections: + return 'LOW' + + highest = max(detections, key=lambda x: severity_order.get(x.get('severity', 'LOW'), 0)) + return highest.get('severity', 'MEDIUM') + + def _check_alert_cooldown(self, src_ip): + """Check alert cooldown""" + current_time = time.time() + if current_time - self.last_alert_time[src_ip] < self.config["alert_cooldown"]: + return False + self.last_alert_time[src_ip] = current_time + return True + + def _handle_alert(self, src_ip, detections, severity): + """Handle blue team detection alerts""" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Build alert + alert_lines = [f"\n{'='*60}"] + alert_lines.append(f"🚨 BLUE TEAM DETECTION - {severity}") + alert_lines.append(f"Time: {timestamp}") + alert_lines.append(f"Source: {src_ip}") + alert_lines.append(f"{'='*60}") + + for detection in detections: + alert_lines.append(f"\nšŸ” {detection['type']}") + alert_lines.append(f" {detection['details']}") + if 'platform' in detection: + alert_lines.append(f" Platform: {detection['platform']}") + alert_lines.append(f" Severity: {detection.get('severity', 'UNKNOWN')}") + + # Add recommendations + recommendations = self._generate_recommendations(detections) + if recommendations: + alert_lines.append(f"\nšŸ’” RECOMMENDATIONS:") + for rec in recommendations: + alert_lines.append(f" • {rec}") + + alert_lines.append(f"\n{'='*60}\n") + + alert_message = '\n'.join(alert_lines) + + # Output alert + print(alert_message) + logging.warning(alert_message) + + # CLI notification + self._cli_notification(src_ip, severity, detections[0]['type']) + + # Email alert if configured + if self.config["email_enabled"]: + threading.Thread( + target=self._send_encrypted_email, + args=(f"[IOC-U] Blue Team {severity} Alert", alert_message), + daemon=True + ).start() + + def _generate_recommendations(self, detections): + """Generate recommendations based on detections""" + recommendations = [] + + detection_types = [d['type'] for d in detections] + + if 'BLUE_TEAM_MEMORY_FORENSICS' in detection_types: + recommendations.append("CRITICAL: Memory forensics detected - clear memory artifacts immediately") + recommendations.append("Avoid process injection and in-memory techniques") + + if 'BLUE_TEAM_CANARY_INVESTIGATION' in detection_types: + recommendations.append("CRITICAL: Canary triggered - blue team is actively investigating") + recommendations.append("Consider pausing all operations") + + if any('EDR' in d.get('platform', '') for d in detections): + recommendations.append("HIGH: EDR platform detected - verify anti-EDR measures") + recommendations.append("Use encrypted communications and obfuscation") + + if any('AI' in d['type'] for d in detections): + recommendations.append("HIGH: AI-powered investigation detected") + recommendations.append("Vary tactics and avoid patterns") + + if 'BLUE_TEAM_IOC_SWEEP' in detection_types: + recommendations.append("MEDIUM: IOC sweep in progress - rotate infrastructure") + + return recommendations + + def _cli_notification(self, src_ip, severity, detection_type): + """CLI notification for blue team detection""" + import shlex + timestamp = datetime.now().strftime("%H:%M:%S") + + severity_config = { + "CRITICAL": {"repeat": 10, "color": "\033[91m"}, # Red + "HIGH": {"repeat": 6, "color": "\033[93m"}, # Yellow + "MEDIUM": {"repeat": 3, "color": "\033[94m"}, # Blue + "LOW": {"repeat": 1, "color": "\033[92m"} # Green + } + + config = severity_config.get(severity, severity_config["MEDIUM"]) + + # Sanitize all external inputs to prevent shell injection + safe_src_ip = shlex.quote(str(src_ip)) + safe_severity = shlex.quote(str(severity)) + safe_detection_type = shlex.quote(str(detection_type)) + safe_timestamp = shlex.quote(timestamp) + + notification_cmd = f""" + (for i in {{1..{config['repeat']}}}; do + echo -ne "\\a\\033[?5h"; + sleep 0.2; + echo -ne "\\033[?5l"; + sleep 0.2; + done && echo -e "\\n{config['color']}[{safe_severity}] [{safe_timestamp}] BLUE TEAM: {safe_detection_type} from {safe_src_ip}\\033[0m") & + """ + + subprocess.Popen(notification_cmd, shell=True, executable='/bin/bash') + + def _send_encrypted_email(self, subject, message): + """Send encrypted email alert""" + try: + # Prepare email + email_body = f"Subject: {subject}\n" + email_body += f"From: IOC-U Blue Team Detection\n" + email_body += f"To: {self.config['alert_email']}\n\n" + email_body += message + email_body += f"\n\nTarget: {self.local_ip}" + + # Encrypt with GPG + gpg_process = subprocess.Popen( + ['gpg', '--encrypt', '--armor', '--trust-model', 'always', + '-r', self.config["gpg_recipient"]], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + encrypted_data, _ = gpg_process.communicate(email_body.encode()) + + if gpg_process.returncode != 0: + logging.error("GPG encryption failed") + return False + + # Send via ssmtp + ssmtp_process = subprocess.Popen( + ['ssmtp', self.config["alert_email"]], + stdin=subprocess.PIPE, stderr=subprocess.PIPE + ) + ssmtp_process.communicate(encrypted_data) + + return ssmtp_process.returncode == 0 + + except Exception as e: + logging.error(f"Email alert error: {e}") + return False + + def _is_trusted_traffic(self, src_ip, dst_ip): + """Check if traffic is from trusted sources (red team)""" + try: + src_ip_obj = ipaddress.ip_address(src_ip) + + # Check operator IPs + if src_ip in self.config['operator_ips']: + return True + + # Check trusted networks + for network in self.config['trusted_networks']: + if src_ip_obj in ipaddress.ip_network(network, strict=False): + return True + + # Skip localhost + if src_ip_obj.is_loopback: + return True + + except: + pass + + return False + + def deploy_canaries(self): + """Deploy canary services to detect blue team scanning""" + canary_configs = [ + (2222, "SSH", self._ssh_canary), + (8888, "HTTP", self._http_canary), + (5900, "VNC", self._vnc_canary), + (9999, "Admin", self._admin_canary), + (1433, "MSSQL", self._mssql_canary), + (3389, "RDP", self._rdp_canary), + (445, "SMB", self._smb_canary), + (139, "NetBIOS", self._netbios_canary) + ] + + print("\nšŸÆ Deploying Canary Services to Detect Blue Teams...") + print("─" * 50) + + for port, service_type, handler_func in canary_configs: + if port in self.config["canary_ports"]: + try: + thread = threading.Thread( + target=handler_func, + args=(port,), + daemon=True, + name=f"canary_{service_type}_{port}" + ) + thread.start() + self.canary_threads.append(thread) + print(f" āœ“ {service_type:<8} canary on port {port}") + except Exception as e: + print(f" āœ— {service_type:<8} failed on port {port}: {e}") + + print("─" * 50) + print("āœ… Canaries deployed - Blue team scans will trigger alerts\n") + + def _ssh_canary(self, port): + """SSH canary service""" + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('0.0.0.0', port)) + server_socket.listen(5) + + while self.running: + try: + client_socket, address = server_socket.accept() + # Send SSH banner + client_socket.send(b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3\r\n") + logging.info(f"Blue team SSH canary triggered from {address[0]}") + client_socket.close() + except socket.timeout: + continue + except: + pass + + except Exception as e: + logging.error(f"SSH canary error: {e}") + + def _http_canary(self, port): + """HTTP canary service""" + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('0.0.0.0', port)) + server_socket.listen(5) + + while self.running: + try: + client_socket, address = server_socket.accept() + client_socket.settimeout(5) + + # Receive request + request = client_socket.recv(4096) + + # Send response + response = b"HTTP/1.1 401 Unauthorized\r\n" + response += b"Server: Apache/2.4.41 (Ubuntu)\r\n" + response += b"WWW-Authenticate: Basic realm=\"Admin Portal\"\r\n" + response += b"Content-Length: 0\r\n\r\n" + + client_socket.send(response) + logging.info(f"Blue team HTTP canary triggered from {address[0]}") + client_socket.close() + + except socket.timeout: + continue + except: + pass + + except Exception as e: + logging.error(f"HTTP canary error: {e}") + + def _vnc_canary(self, port): + """VNC canary service""" + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('0.0.0.0', port)) + server_socket.listen(5) + + while self.running: + try: + client_socket, address = server_socket.accept() + client_socket.send(b"RFB 003.008\n") + logging.info(f"Blue team VNC canary triggered from {address[0]}") + client_socket.close() + except: + pass + + except Exception as e: + logging.error(f"VNC canary error: {e}") + + def _admin_canary(self, port): + """Admin portal canary""" + self._http_canary(port) + + def _mssql_canary(self, port): + """MSSQL canary service""" + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('0.0.0.0', port)) + server_socket.listen(5) + + while self.running: + try: + client_socket, address = server_socket.accept() + client_socket.send(b"\x04\x01\x00\x25\x00\x00\x00\x00") + logging.info(f"Blue team MSSQL canary triggered from {address[0]}") + client_socket.close() + except: + pass + + except Exception as e: + logging.error(f"MSSQL canary error: {e}") + + def _rdp_canary(self, port): + """RDP canary service""" + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('0.0.0.0', port)) + server_socket.listen(5) + + while self.running: + try: + client_socket, address = server_socket.accept() + client_socket.send(b"\x03\x00\x00\x13\x0e\xd0\x00\x00\x00\x00\x00") + logging.info(f"Blue team RDP canary triggered from {address[0]}") + client_socket.close() + except: + pass + + except Exception as e: + logging.error(f"RDP canary error: {e}") + + def _smb_canary(self, port): + """SMB canary service""" + self._netbios_canary(port) + + def _netbios_canary(self, port): + """NetBIOS canary service""" + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('0.0.0.0', port)) + server_socket.listen(5) + + while self.running: + try: + client_socket, address = server_socket.accept() + logging.info(f"Blue team NetBIOS canary triggered from {address[0]}") + client_socket.close() + except: + pass + + except Exception as e: + logging.error(f"NetBIOS canary error: {e}") + + def get_network_interface(self): + """Select network interface""" + if self.config["interface"] != "auto": + return self.config["interface"] + + interfaces = get_if_list() + preferred = ['eth0', 'ens33', 'ens192', 'ens160', 'enp0s3', 'wlan0'] + + for iface in preferred: + if iface in interfaces: + return iface + + for iface in interfaces: + if iface != 'lo' and not iface.startswith('docker'): + return iface + + return 'eth0' + + def generate_intelligence_report(self): + """Generate blue team intelligence report""" + assessment = self.intelligence.get_threat_assessment() + + report = { + 'timestamp': datetime.now().isoformat(), + 'blue_team_threat_level': assessment['investigation_intensity'], + 'active_blue_team_ips': assessment['active_investigators'], + 'detected_platforms': assessment['primary_platforms'], + 'investigation_techniques': assessment['investigation_focus'], + 'recommendations': assessment['recommendations'], + 'statistics': { + 'total_detections': sum(self.intelligence.investigation_techniques.values()), + 'unique_investigators': len(self.intelligence.blue_team_activities), + 'platforms_detected': len(self.intelligence.detected_platforms) + } + } + + return report + + def cleanup(self, signum=None, frame=None): + """Cleanup on shutdown""" + print("\nšŸ›‘ Shutting down Blue Team Detector...") + + self.running = False + + # Save intelligence + self.intelligence.save_intelligence() + + # Generate final report + if not self.config.get('stealth_mode'): + report = self.generate_intelligence_report() + report_file = f"blue_team_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + with open(report_file, 'w') as f: + json.dump(report, f, indent=2) + print(f"šŸ“Š Final report saved to {report_file}") + + # Remove PID file + try: + os.remove(PID_FILE) + except: + pass + + print("āœ… Shutdown complete") + sys.exit(0) + + def daemonize(self): + """Run as daemon""" + # First fork + try: + if os.fork() > 0: + sys.exit(0) + except OSError as e: + sys.stderr.write(f"Fork #1 failed: {e}\n") + sys.exit(1) + + # Decouple + os.chdir("/") + os.setsid() + os.umask(0) + + # Second fork + try: + if os.fork() > 0: + sys.exit(0) + except OSError as e: + sys.stderr.write(f"Fork #2 failed: {e}\n") + sys.exit(1) + + # Write PID + with open(PID_FILE, 'w') as f: + f.write(str(os.getpid())) + + # Redirect file descriptors + if self.config.get("stealth_mode", True): + devnull = os.open(os.devnull, os.O_RDWR) + os.dup2(devnull, sys.stdin.fileno()) + os.dup2(devnull, sys.stdout.fileno()) + os.dup2(devnull, sys.stderr.fileno()) + + def run(self): + """Main detection loop""" + # Signal handlers + signal.signal(signal.SIGTERM, self.cleanup) + signal.signal(signal.SIGINT, self.cleanup) + + # Get interface + interface = self.get_network_interface() + + # Display banner + self._display_banner(interface) + + # Start sniffing + try: + print(f"\nšŸš€ Starting blue team detection on {interface}...") + print(" Press Ctrl+C to stop\n") + + sniff( + iface=interface, + prn=self.packet_inspector, + store=False, + stop_filter=lambda x: not self.running + ) + + except PermissionError: + print("\nāŒ Error: Root privileges required") + print(" Run with: sudo python3 ioc-u-enhanced.py") + self.cleanup() + except Exception as e: + print(f"\nāŒ Fatal error: {e}") + self.cleanup() + + def _display_banner(self, interface): + """Display startup banner""" + banner = """ +╔══════════════════════════════════════════════════════════════════╗ +ā•‘ IOC-U Enhanced v2.0 - Blue Team Detector ā•‘ +ā•‘ Detecting Blue Team IR/SOC Investigation Activities ā•‘ +╠══════════════════════════════════════════════════════════════════╣ +ā•‘ Detects: ā•‘ +ā•‘ • SIEM Queries & Threat Hunting • EDR/XDR Scanning ā•‘ +ā•‘ • Memory & Disk Forensics • Network Monitoring ā•‘ +ā•‘ • PowerShell Investigation • WMI Queries ā•‘ +ā•‘ • AI-Powered Analysis • IOC Sweeps ā•‘ +ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• + """ + + print(banner) + print(f"\nšŸ“” Configuration:") + print(f" • Interface: {interface}") + print(f" • Local IP: {self.local_ip}") + print(f" • ML Detection: {'Enabled' if self.ml_engine else 'Disabled'}") + print(f" • Alert Mode: {'Investigations Only' if self.config.get('alert_on_investigation_only') else 'All Activity'}") + print(f" • Canary Ports: {len(self.config.get('canary_ports', []))}") + +# Utility functions +def load_config(): + """Load configuration""" + if os.path.exists(CONFIG_PATH): + try: + with open(CONFIG_PATH, 'r') as f: + loaded_config = json.load(f) + config = default_config.copy() + config.update(loaded_config) + return config + except: + pass + return default_config.copy() + +def save_config(config): + """Save configuration""" + try: + with open(CONFIG_PATH, 'w') as f: + json.dump(config, f, indent=2) + return True + except: + return False + +def show_intelligence_report(): + """Show blue team intelligence report""" + config = load_config() + intelligence = BlueTeamIntelligence(config) + assessment = intelligence.get_threat_assessment() + + print("\n" + "="*70) + print(" "*15 + "BLUE TEAM INTELLIGENCE REPORT") + print("="*70) + print(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print("="*70) + + # Threat level + threat_colors = { + 'CRITICAL': '\033[91m', # Red + 'HIGH': '\033[93m', # Yellow + 'MEDIUM': '\033[94m', # Blue + 'LOW': '\033[92m' # Green + } + + color = threat_colors.get(assessment['investigation_intensity'], '') + print(f"\n🚨 BLUE TEAM THREAT LEVEL: {color}{assessment['investigation_intensity']}\033[0m") + + # Active investigators + if assessment['active_investigators']: + print(f"\nšŸ‘® ACTIVE BLUE TEAM INVESTIGATORS:") + for investigator in assessment['active_investigators']: + print(f" • {investigator['ip']}") + print(f" Activity: {investigator['activity_count']} actions") + print(f" Platforms: {', '.join(investigator['platforms'])}") + else: + print(f"\nāœ… No active blue team investigators detected") + + # Detected platforms + if assessment['primary_platforms']: + print(f"\nšŸ›”ļø DETECTED SECURITY PLATFORMS:") + for platform in assessment['primary_platforms']: + print(f" • {platform}") + + # Investigation techniques + if assessment['investigation_focus']: + print(f"\nšŸ” BLUE TEAM TECHNIQUES OBSERVED:") + for technique in assessment['investigation_focus']: + print(f" • {technique}") + + # Recommendations + if assessment['recommendations']: + print(f"\nšŸ’” RECOMMENDATIONS:") + for rec in assessment['recommendations']: + print(f" • {rec}") + + print("\n" + "="*70) + +def setup_operational_config(): + """Configure operational settings""" + config = load_config() + + print("\nšŸ”§ Blue Team Detector Configuration") + print("=" * 50) + + # Trusted networks + print("\n1ļøāƒ£ TRUSTED NETWORKS (Your Red Team Networks)") + print(f"Current: {config.get('trusted_networks', [])}") + + while True: + network = input("Add trusted network (CIDR) or 'done': ").strip() + if network.lower() == 'done': + break + if network: + try: + ipaddress.ip_network(network, strict=False) + if network not in config["trusted_networks"]: + config["trusted_networks"].append(network) + print(f" āœ… Added {network}") + except: + print(f" āŒ Invalid network: {network}") + + # Operator IPs + print("\n2ļøāƒ£ OPERATOR IPs (Your Red Team IPs)") + print(f"Current: {config.get('operator_ips', [])}") + + while True: + ip = input("Add operator IP or 'done': ").strip() + if ip.lower() == 'done': + break + if ip: + try: + ipaddress.ip_address(ip) + if ip not in config["operator_ips"]: + config["operator_ips"].append(ip) + print(f" āœ… Added {ip}") + except: + print(f" āŒ Invalid IP: {ip}") + + # Alert settings + print("\n3ļøāƒ£ ALERT SETTINGS") + + alert_choice = input("Alert on active investigations only? (Y/n): ").strip() + config["alert_on_investigation_only"] = alert_choice.lower() != 'n' + + # ML detection + if ML_AVAILABLE: + print("\n4ļøāƒ£ ML DETECTION") + ml_choice = input("Enable ML-based detection? (Y/n): ").strip() + config["enable_ml_detection"] = ml_choice.lower() != 'n' + + # Canary ports + print("\n5ļøāƒ£ CANARY SERVICES") + print("Recommended: 2222,8888,5900,9999,1433,3389,445,139") + + use_default = input("Use recommended canary ports? (Y/n): ").strip() + if use_default.lower() == 'n': + custom_ports = input("Enter custom ports (comma-separated): ").strip() + if custom_ports: + try: + config["canary_ports"] = [int(p.strip()) for p in custom_ports.split(',')] + except: + print("Invalid port format, using defaults") + + save_config(config) + print("\nāœ… Configuration saved!") + +def setup_email(): + """Configure email alerts""" + config = load_config() + + print("\nšŸ“§ Email Alert Configuration") + print("=" * 40) + + config["alert_email"] = input("Alert email address: ").strip() + config["gpg_recipient"] = input("GPG recipient: ").strip() + + if config["alert_email"] and config["gpg_recipient"]: + config["email_enabled"] = True + config["smtp_configured"] = True + save_config(config) + print("āœ… Email alerts configured") + else: + print("āŒ Email configuration incomplete") + +def show_status(): + """Show detector status""" + config = load_config() + + print("\nšŸ“Š Blue Team Detector Status") + print("=" * 40) + + # Check if running + if os.path.exists(PID_FILE): + try: + with open(PID_FILE, 'r') as f: + pid = f.read().strip() + os.kill(int(pid), 0) + print(f"🟢 Status: Running (PID: {pid})") + except: + print("šŸ”“ Status: Not running") + os.remove(PID_FILE) + else: + print("šŸ”“ Status: Not running") + + # Configuration + print(f"\nāš™ļø Configuration:") + print(f" ML Detection: {'Enabled' if config.get('enable_ml_detection') and ML_AVAILABLE else 'Disabled'}") + print(f" Email Alerts: {'Enabled' if config.get('email_enabled') else 'Disabled'}") + print(f" Alert Mode: {'Investigations Only' if config.get('alert_on_investigation_only') else 'All Activity'}") + print(f" Canary Ports: {config.get('canary_ports', [])}") + +def stop_detector(): + """Stop running detector""" + if os.path.exists(PID_FILE): + try: + with open(PID_FILE, 'r') as f: + pid = int(f.read().strip()) + os.kill(pid, signal.SIGTERM) + time.sleep(2) + print("āœ… Detector stopped") + except: + print("āŒ Could not stop detector") + else: + print("āŒ Detector not running") + +def main(): + """Main entry point""" + global PID_FILE, LOG_FILE + parser = argparse.ArgumentParser( + description="IOC-U Enhanced v2.0 - Blue Team Detection Tool", + epilog="Detects blue team IR/SOC activities targeting red team operations" + ) + + # Configuration + parser.add_argument("--config-operational", action="store_true", + help="Configure operational settings") + parser.add_argument("--config-email", action="store_true", + help="Configure email alerts") + + # Control + parser.add_argument("--status", action="store_true", + help="Show detector status") + parser.add_argument("--stop", action="store_true", + help="Stop detector") + parser.add_argument("--foreground", action="store_true", + help="Run in foreground") + + # Intelligence + parser.add_argument("--intelligence", action="store_true", + help="Show blue team intelligence report") + + # Deployment + parser.add_argument("--deploy-canaries", action="store_true", + help="Deploy canary services") + parser.add_argument("--no-canaries", action="store_true", + help="Start without canaries") + + # Runtime options + parser.add_argument("--interface", help="Network interface") + parser.add_argument("--ml-disable", action="store_true", + help="Disable ML detection") + + # Sentry / daemon mode for OPSEC integration + parser.add_argument("--daemon", action="store_true", + help="Run as background sentry daemon (alerts to log)") + parser.add_argument("--alert-hook", default=None, + help="Command to run on detection (receives alert JSON via stdin)") + parser.add_argument("--sentry-pid", default="/var/run/opsec-ioc-u.pid", + help="PID file for sentry mode") + parser.add_argument("--sentry-log", default="/var/log/opsec-ioc-u.log", + help="Log file for sentry mode") + + args = parser.parse_args() + + # Ops hook + _hook = _OpsHook("ioc-u") if _OpsHook else None + if _hook: + _hook.register() + + # Handle commands + if args.config_operational: + setup_operational_config() + return + + if args.config_email: + setup_email() + return + + if args.status: + show_status() + return + + if args.intelligence: + show_intelligence_report() + return + + if args.stop: + stop_detector() + return + + # Check root + if os.geteuid() != 0: + print("āŒ Root privileges required") + print(" Run with: sudo python3 ioc-u-enhanced.py") + return + + # Check if already running + if os.path.exists(PID_FILE) and not args.foreground: + print("āš ļø Detector already running. Use --stop first.") + return + + # Load config and apply overrides + config = load_config() + + if args.interface: + config["interface"] = args.interface + + if args.ml_disable: + config["enable_ml_detection"] = False + + # Sentry daemon mode — override PID/LOG paths and suppress interactive prompts + if args.daemon: + PID_FILE = args.sentry_pid + LOG_FILE = args.sentry_log + config["alert_hook"] = args.alert_hook + # Configure logging to file for daemon mode + logging.basicConfig( + filename=args.sentry_log, + level=logging.INFO, + format="%(asctime)s %(levelname)s %(message)s", + ) + + # Start detector + print("\nšŸš€ Starting IOC-U Enhanced Blue Team Detector...") + + try: + detector = EnhancedBlueTeamDetector(config) + + # Deploy canaries + if not args.no_canaries: + detector.deploy_canaries() + if not args.foreground and not args.daemon: + input("\nPress Enter to start detection...") + + # Run + if args.daemon: + print("Starting as sentry daemon...") + detector.daemonize() + elif not args.foreground: + print("Starting in daemon mode...") + detector.daemonize() + + detector.run() + + except KeyboardInterrupt: + print("\nāš ļø Interrupted") + if 'detector' in locals(): + detector.cleanup() + except Exception as e: + print(f"\nāŒ Error: {e}") + if 'detector' in locals(): + detector.cleanup() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/redteam/ops-logger.sh b/tools/redteam/ops-logger.sh new file mode 100644 index 0000000..5906b2d --- /dev/null +++ b/tools/redteam/ops-logger.sh @@ -0,0 +1,1800 @@ +#!/usr/bin/env bash +# OpsLogger - Complete logging solution for red team operations +# Version 2.5.3 - Merged config and prompt fixes + +# Configuration variables +CONFIG_FILE="${HOME}/.ops-logger.conf" +DEFAULT_TARGET="target-$(hostname | tr '.' '-')" +DEFAULT_LOG_DIR="${HOME}/OperationLogs" +PROMPT_NEW_SHELLS=true +RECORD_INTERVAL=0.5 +DEBUG=false + +# File markers +LOG_MARKER="/tmp/ops-logger-active" +RECORDING_MARKER="/tmp/ops-logger-recording" +CONFIG_IN_PROGRESS="/tmp/ops-logger-configuring" +VERBOSE_CMD_MARKER="/tmp/ops-logger-cmd" + +# Helper functions +log_debug() { + if [[ "$DEBUG" == "true" ]]; then + local debug_dir="${LOG_DIR:-${DEFAULT_LOG_DIR}}" + mkdir -p "$debug_dir" 2>/dev/null + echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $*" >> "${debug_dir}/ops-logger-debug.log" + fi +} + +ensure_dir() { + mkdir -p "$1" 2>/dev/null || return 1 + return 0 +} + +load_config() { + if [[ -f "$CONFIG_FILE" ]]; then + source "$CONFIG_FILE" + else + TARGET_NAME="$DEFAULT_TARGET" + LOG_DIR="$DEFAULT_LOG_DIR" + fi + + TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}" + LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}" +} + +save_config() { + ensure_dir "$(dirname "$CONFIG_FILE")" + cat > "$CONFIG_FILE" << EOF +# Ops Logger Configuration +TARGET_NAME="$TARGET_NAME" +LOG_DIR="$LOG_DIR" +PROMPT_NEW_SHELLS=$PROMPT_NEW_SHELLS +RECORD_INTERVAL=$RECORD_INTERVAL +DEBUG=$DEBUG +EOF +} + +# Get public IP +get_public_ip() { + local ip + ip=$(curl -s --connect-timeout 3 ifconfig.me) || \ + ip=$(curl -s --connect-timeout 3 ipinfo.io/ip) || \ + ip=$(curl -s --connect-timeout 3 icanhazip.com) || \ + ip="unknown" + echo "$ip" +} + +# Detection functions +is_tmux() { [[ -n "$TMUX" ]]; } + +# Get normalized pane ID (session-window-pane format) +get_pane_id() { + if is_tmux; then + # If TMUX_PANE is set (from hook), use that specific pane + if [[ -n "$TMUX_PANE" ]]; then + local session=$(tmux display -t "$TMUX_PANE" -p '#{session_name}') + local window=$(tmux display -t "$TMUX_PANE" -p '#{window_index}') + local pane=$(tmux display -t "$TMUX_PANE" -p '#{pane_index}') + echo "${session}-${window}-${pane}" + else + local session=$(tmux display -p '#{session_name}') + local window=$(tmux display -p '#{window_index}') + local pane=$(tmux display -p '#{pane_index}') + echo "${session}-${window}-${pane}" + fi + else + echo "$$" + fi +} + +# Get tmux pane reference for tmux commands (% format for tmux commands) +get_tmux_pane_ref() { + if is_tmux; then + # If TMUX_PANE is set (from hook), use that specific pane + if [[ -n "$TMUX_PANE" ]]; then + echo "$TMUX_PANE" + else + tmux display -p '#{pane_id}' + fi + else + echo "" + fi +} + +# Get the actual tmux prefix key instead of hardcoding C-b +get_tmux_prefix() { + # First check if we're using oh-my-tmux which often uses C-a + if tmux show-options -g | grep -q "TMUX_CONF"; then + # Check if prefix has been overridden in .local config + local prefix=$(tmux show-options -g prefix 2>/dev/null | awk '{print $2}') + if [[ -n "$prefix" ]]; then + case "$prefix" in + "C-q") echo "C-q" ;; + "C-a") echo "C-a" ;; + "C-b") echo "C-b" ;; + *) echo "$prefix" ;; + esac + else + echo "C-a" # oh-my-tmux default + fi + else + # Try to get the actual prefix from tmux config + local prefix=$(tmux show-options -g prefix 2>/dev/null | awk '{print $2}') + if [[ -n "$prefix" ]]; then + echo "$prefix" + else + # Default to C-b if we can't determine + echo "C-b" + fi + fi +} + +is_logging_active() { [[ -f "${LOG_MARKER}-$1" ]]; } +is_recording_active() { [[ -f "${RECORDING_MARKER}-$1" ]]; } + +# ================================================================ +# DEPENDENCY CHECKING +# ================================================================ + +# Check if TPM is installed +check_tpm_installed() { + [[ -d "$HOME/.tmux/plugins/tpm" ]] +} + +# Check if tmux-logging plugin is installed +check_tmux_logging_installed() { + [[ -d "$HOME/.tmux/plugins/tmux-logging" ]] +} + +# Print installation instructions for TMux Plugin Manager (TPM) +print_tpm_install_instructions() { + echo "=========================================================================" + echo "Tmux Plugin Manager (TPM) is not installed. To install:" + echo "=========================================================================" + echo "1. Run these commands:" + echo " mkdir -p ~/.tmux/plugins" + echo " git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm" + echo "" + echo "2. Add these lines to your ~/.tmux.conf:" + echo " # List of plugins" + echo " set -g @plugin 'tmux-plugins/tpm'" + echo " set -g @plugin 'tmux-plugins/tmux-sensible'" + echo " set -g @plugin 'tmux-plugins/tmux-logging'" + echo "" + echo " # Initialize TMUX plugin manager (keep this line at the bottom)" + echo " run '~/.tmux/plugins/tpm/tpm'" + echo "" + echo "3. Reload your tmux configuration:" + echo " tmux source-file ~/.tmux.conf" + echo "" + echo "4. Install plugins by pressing:" + echo " prefix + I (capital I)" + echo "=========================================================================" +} + +# Print installation instructions for tmux-logging plugin +print_tmux_logging_install_instructions() { + echo "=========================================================================" + echo "tmux-logging plugin is not installed. To install:" + echo "=========================================================================" + echo "1. Ensure TPM is installed (see previous instructions if needed)" + echo "" + echo "2. Add this line to your ~/.tmux.conf (before the tpm init line):" + echo " set -g @plugin 'tmux-plugins/tmux-logging'" + echo "" + echo "3. Reload your tmux configuration:" + echo " tmux source-file ~/.tmux.conf" + echo "" + echo "4. Install the plugin by pressing:" + echo " prefix + I (capital I)" + echo "=========================================================================" +} + +# Check for required dependencies +check_dependencies() { + local id="$1" + local missing=false + + if ! is_tmux; then + echo "WARNING: Not running in tmux. Only basic CSV logging will be available." + echo " For full functionality, start tmux first." + return 0 + fi + + # Check TPM and tmux-logging plugin + if ! check_tpm_installed; then + missing=true + print_tpm_install_instructions + elif ! check_tmux_logging_installed; then + missing=true + print_tmux_logging_install_instructions + fi + + if $missing; then + # Don't use display-message, just echo + echo "Plugin dependencies missing, see instructions above" + return 1 + fi + + return 0 +} + +# ================================================================ +# CSV LOGGING (our custom implementation) +# ================================================================ + +# Setup CSV logging (ORIGINAL FORMAT MAINTAINED) +setup_csv_log() { + local target="$1" + local log_dir="$2" + local csv_file="${log_dir}/${target}_commands.csv" + + ensure_dir "$log_dir" + if [[ ! -f "$csv_file" ]]; then + echo '"StartTime","EndTime","SourceIP","User","Path","Command"' > "$csv_file" + fi + + echo "$csv_file" +} + +# Create command hook with better timing and error handling +create_command_hook() { + local hook_script="$1" + local csv_log="$2" + local pane_id="$3" + local verbose_log="$4" + + cat > "$hook_script" << 'EOF' +#!/usr/bin/env bash +# Command hook - FIXED version with proper function timing + +# IMMEDIATELY set all variables and disable errexit +set +e + +# Set variables first before anything else +CSV_LOG="__CSV_LOG__" +VERBOSE_LOG="__VERBOSE_LOG__" +PANE_ID="__PANE_ID__" +PUBLIC_IP=$(timeout 5 curl -s ifconfig.me 2>/dev/null || echo "unknown") +RTL_INTERNAL_LOGGING=true +RTL_CMD_START_TIME="" +VERBOSE_CMD_MARKER="__VERBOSE_CMD_MARKER__" + +# Function to check if command should be logged +should_log_command() { + local cmd="$1" + case "$cmd" in + *"RTL_"*|*"log_command"*|*"CSV_LOG"*|*"should_log_command"*) return 1 ;; + "history "*|*"PROMPT_COMMAND"*|*"source /tmp/ops"*) return 1 ;; + "") return 1 ;; + *) return 0 ;; + esac +} + +# Function to write verbose command header +write_verbose_header() { + local cmd="$1" + local start_time="$2" + local user=$(whoami) + local path=$(pwd) + + # Create a marker file with command info for the verbose processor + echo "$cmd|$start_time|$user|$path|$PUBLIC_IP|$PANE_ID" > "${VERBOSE_CMD_MARKER}-${PANE_ID}" +} + +# Function to log command to CSV +RTL_log_command() { + local cmd="$1" + local start_time="$2" + local end_time="$3" + local user=$(whoami) + local path=$(pwd) + + should_log_command "$cmd" || return 0 + + local old_flag="$RTL_INTERNAL_LOGGING" + RTL_INTERNAL_LOGGING=false + + local escaped_cmd=${cmd//\"/\"\"} + + printf '"%s","%s","%s","%s","%s","%s"\n' \ + "$start_time" "$end_time" "$PUBLIC_IP" "$user" "$path" "$escaped_cmd" >> "$CSV_LOG" 2>/dev/null + + RTL_INTERNAL_LOGGING="$old_flag" +} + +# BASH-specific setup +if [[ -n "$BASH_VERSION" ]]; then + # Save original PROMPT_COMMAND + [[ -z "$RTL_ORIG_PROMPT_COMMAND" ]] && RTL_ORIG_PROMPT_COMMAND="$PROMPT_COMMAND" + + RTL_LAST_COMMAND="" + RTL_LAST_HISTNUM="" + + # FIXED: Define preexec function BEFORE setting trap + RTL_preexec() { + local cmd="$BASH_COMMAND" + if should_log_command "$cmd" 2>/dev/null; then + RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')" + write_verbose_header "$cmd" "$RTL_CMD_START_TIME" + fi + } + + # FIXED: Define PROMPT_COMMAND function BEFORE using it + RTL_PROMPT_COMMAND() { + if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then + return 0 + fi + + local current_histnum=$(history 1 2>/dev/null | awk '{print $1}') + local current_cmd=$(history 1 2>/dev/null | sed 's/^[ ]*[0-9]*[ ]*//') + + if [[ "$current_cmd" != "$RTL_LAST_COMMAND" && "$current_histnum" != "$RTL_LAST_HISTNUM" ]]; then + if should_log_command "$current_cmd" 2>/dev/null; then + local end_time="$(date '+%Y-%m-%d %H:%M:%S')" + local start_time="${RTL_CMD_START_TIME:-$end_time}" + + RTL_log_command "$current_cmd" "$start_time" "$end_time" + + RTL_LAST_COMMAND="$current_cmd" + RTL_LAST_HISTNUM="$current_histnum" + fi + fi + + RTL_CMD_START_TIME="" + } + + # NOW set the trap (function is already defined) + trap 'RTL_preexec 2>/dev/null || true' DEBUG 2>/dev/null + + # Set PROMPT_COMMAND (function is already defined) + if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then + PROMPT_COMMAND="RTL_PROMPT_COMMAND; $RTL_ORIG_PROMPT_COMMAND" + else + PROMPT_COMMAND="RTL_PROMPT_COMMAND" + fi + +# ZSH-specific setup +elif [[ -n "$ZSH_VERSION" ]]; then + # Define functions first + RTL_zsh_preexec() { + local cmd="$1" + if should_log_command "$cmd" 2>/dev/null; then + RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')" + write_verbose_header "$cmd" "$RTL_CMD_START_TIME" + fi + } + + RTL_zsh_precmd() { + if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then + return 0 + fi + + local cmd=$(fc -ln -1 2>/dev/null) + + if should_log_command "$cmd" 2>/dev/null; then + local end_time="$(date '+%Y-%m-%d %H:%M:%S')" + local start_time="${RTL_CMD_START_TIME:-$end_time}" + + RTL_log_command "$cmd" "$start_time" "$end_time" + fi + + RTL_CMD_START_TIME="" + } + + # NOW set the hooks (functions are already defined) + if autoload -Uz add-zsh-hook 2>/dev/null; then + add-zsh-hook preexec RTL_zsh_preexec 2>/dev/null || true + add-zsh-hook precmd RTL_zsh_precmd 2>/dev/null || true + fi +fi + +# Mark as active +echo "$$" > "__LOG_MARKER__-${PANE_ID}" +echo "Ops Logger command hook installed for pane $PANE_ID" >&2 +EOF + + # Replace placeholders with actual values + sed -i "s|__CSV_LOG__|$csv_log|g" "$hook_script" + sed -i "s|__VERBOSE_LOG__|$verbose_log|g" "$hook_script" + sed -i "s|__PANE_ID__|$pane_id|g" "$hook_script" + sed -i "s|__LOG_MARKER__|$LOG_MARKER|g" "$hook_script" + sed -i "s|__VERBOSE_CMD_MARKER__|$VERBOSE_CMD_MARKER|g" "$hook_script" + + chmod +x "$hook_script" +} + +# ================================================================ +# UNIFIED VERBOSE LOGGING WITH COMMAND HEADERS +# ================================================================ + +# Create single master verbose log file for all panes +create_master_verbose_log() { + local target="$1" + local log_dir="$2" + # Remove date from filename for continuous logging + local verbose_dir="${log_dir}/verbose" + ensure_dir "$verbose_dir" + # Single log file for entire target + local log_file="${verbose_dir}/${target}_master.log" + # Create the file with header if it doesn't exist + if [[ ! -f "$log_file" ]]; then + cat > "$log_file" << EOF +================================================================================ + RED TEAM TERMINAL LOGGER + Master Verbose Log +================================================================================ +Target: $target +Date: $(date +%Y-%m-%d) +Host: $(hostname) +Public IP: $(get_public_ip) +Started: $(date '+%Y-%m-%d %H:%M:%S') +================================================================================ + +EOF + fi + + echo "$log_file" +} + +# Create debug filter that captures EVERYTHING (no filtering) +create_debug_filter_script() { + local filter_script="/tmp/ops-debug-filter-$$.sh" + + cat > "$filter_script" << 'EODEBUG' +#!/usr/bin/env bash +# DEBUG: Raw pipe-pane output capture + +LOGFILE="$1" +PANE_ID="$2" +DEBUG_RAW_FILE="${LOGFILE}.debug-raw" + +mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null + +echo "[$(date)] DEBUG FILTER STARTED for pane $PANE_ID" >> "$DEBUG_RAW_FILE" +echo "=================================================" >> "$DEBUG_RAW_FILE" + +line_count=0 +while IFS= read -r line; do + line_count=$((line_count + 1)) + + # Write EVERYTHING to debug file with line numbers + printf "[%04d][$(date +%H:%M:%S)] RAW: %s\n" "$line_count" "$line" >> "$DEBUG_RAW_FILE" + + # Also write to main log (no filtering at all) + echo "$line" >> "$LOGFILE" +done + +echo "[$(date)] DEBUG FILTER ENDED - Total lines: $line_count" >> "$DEBUG_RAW_FILE" +EODEBUG + + chmod +x "$filter_script" + echo "$filter_script" +} + +# Add debug start function +start_debug_verbose_logging() { + local id="$1" + local tmux_pane_ref="$2" + local target="$3" + local log_dir="$4" + + local debug_log="${log_dir}/debug-${target}-${id}-$(date +%H%M%S).log" + local filter_script=$(create_debug_filter_script) + + echo "Starting DEBUG verbose logging to: $debug_log" + + # Use simple pipe-pane with debug filter + tmux pipe-pane -t "$tmux_pane_ref" "bash '$filter_script' '$debug_log' '$id'" + + sleep 1 + + # Check if logging started + if tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$tmux_pane_ref.*1"; then + echo "DEBUG logging started successfully" + echo "Raw output file: ${debug_log}.debug-raw" + echo "Filtered output file: $debug_log" + + echo "$filter_script" > "/tmp/ops-debug-filter-script-${id}" + return 0 + else + echo "ERROR: DEBUG logging failed to start" + rm -f "$filter_script" + return 1 + fi +} + +# Create the MINIMAL FIX verbose filter script +# Create the FIXED verbose filter script +create_verbose_filter_script() { + local filter_script="/tmp/ops-verbose-filter-$$.sh" + + cat > "$filter_script" << 'EOFILTER' +#!/usr/bin/env bash +# FIXED: Proper command boundary detection + +LOGFILE="$1" +PANE_ID="$2" +TARGET="$3" +VERBOSE_CMD_MARKER="$4" + +mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null + +# Command tracking +CURRENT_COMMAND="" +IN_COMMAND=false +OUTPUT_LINE_COUNT=0 +MAX_LINES_PER_COMMAND=22 +LAST_MARKER_CHECK="" + +# Function to properly close a command +close_current_command() { + if [[ "$IN_COMMAND" == "true" ]]; then + echo "==============================================================================" >> "$LOGFILE" + echo "" >> "$LOGFILE" + IN_COMMAND=false + OUTPUT_LINE_COUNT=0 + CURRENT_COMMAND="" + fi +} + +# Function to check for new command markers +check_command_marker() { + local marker_file="${VERBOSE_CMD_MARKER}-${PANE_ID}" + if [[ -f "$marker_file" ]]; then + # Read the marker content + local marker_content=$(cat "$marker_file" 2>/dev/null) + + # Only process if this is a new marker (different from last check) + if [[ "$marker_content" != "$LAST_MARKER_CHECK" ]]; then + LAST_MARKER_CHECK="$marker_content" + + # FIXED: Always close previous command first + close_current_command + + # Read command info + IFS='|' read -r cmd start_time user path public_ip pane_marker <<< "$marker_content" + + # Start new command if it's for this pane + if [[ "$pane_marker" == "$PANE_ID" && -n "$cmd" ]]; then + CURRENT_COMMAND="$cmd" + IN_COMMAND=true + OUTPUT_LINE_COUNT=0 + + # Write command header + cat >> "$LOGFILE" << EOCMD +============================================================================== +COMMAND EXECUTION - $start_time +============================================================================== +Command: $cmd +User: $user +Path: $path +Start: $start_time +Pane: $PANE_ID +Public IP: $public_ip +------------------------------------------------------------------------------ +OUTPUT: +EOCMD + + # Remove the marker file after processing + rm -f "$marker_file" 2>/dev/null + fi + fi + fi +} + +# Simple ANSI stripping +strip_ansi() { + local line="$1" + # Remove bracketed paste mode + line="${line//[?2004h/}" + line="${line//[?2004l/}" + # Remove ANSI escape sequences + line=$(echo "$line" | sed -E 's/\x1b\[[0-9;]*[mGKHF]//g; s/\x1b\[[?]?[0-9]*[hlc]//g') + echo "$line" +} + +# Check if line looks like a shell prompt +is_prompt_line() { + local line="$1" + # More specific prompt detection patterns + if [[ "$line" =~ .*@.*:.*[\$#][[:space:]]*$ ]] || \ + [[ "$line" =~ ^[[:space:]]*[\$#][[:space:]]*$ ]] || \ + [[ "$line" =~ .*[\$#][[:space:]]+[a-zA-Z] ]]; then + return 0 + fi + return 1 +} + +# Main processing loop +while IFS= read -r line; do + # ALWAYS check for new commands first, before any processing + check_command_marker + + # Clean the line + clean_line=$(strip_ansi "$line") + + # Skip completely empty lines + [[ -z "$clean_line" ]] && continue + + # If we're in a command, log the output + if [[ "$IN_COMMAND" == "true" ]]; then + OUTPUT_LINE_COUNT=$((OUTPUT_LINE_COUNT + 1)) + + # Check if this looks like a prompt (indicating command end) + if is_prompt_line "$clean_line" && [[ $OUTPUT_LINE_COUNT -gt 1 ]]; then + # Don't log the prompt line, just close the command + close_current_command + continue + fi + + # Log the output line with timestamp + if [[ $OUTPUT_LINE_COUNT -le $MAX_LINES_PER_COMMAND ]]; then + echo "$(date '+%H:%M:%S') $clean_line" >> "$LOGFILE" + elif [[ $OUTPUT_LINE_COUNT -eq $(($MAX_LINES_PER_COMMAND + 1)) ]]; then + echo "... [OUTPUT TRUNCATED - showing first $MAX_LINES_PER_COMMAND lines only] ..." >> "$LOGFILE" + fi + fi +done + +# End any active command on exit +close_current_command + +EOFILTER + + chmod +x "$filter_script" + echo "$filter_script" +} + +# Enhanced command hook that integrates with verbose logging +create_enhanced_command_hook() { + local hook_script="$1" + local csv_log="$2" + local pane_id="$3" + local verbose_log="$4" + + cat > "$hook_script" << 'EOF' +#!/usr/bin/env bash +# Enhanced command hook with verbose integration + +set +e + +# Configuration +CSV_LOG="__CSV_LOG__" +VERBOSE_LOG="__VERBOSE_LOG__" +PANE_ID="__PANE_ID__" +PUBLIC_IP=$(timeout 5 curl -s ifconfig.me 2>/dev/null || echo "unknown") +RTL_INTERNAL_LOGGING=true +RTL_CMD_START_TIME="" +VERBOSE_CMD_MARKER="__VERBOSE_CMD_MARKER__" + +# Function to check if command should be logged +should_log_command() { + local cmd="$1" + case "$cmd" in + *"RTL_"*|*"log_command"*|*"CSV_LOG"*|*"should_log_command"*) return 1 ;; + "history "*|*"PROMPT_COMMAND"*|*"source /tmp/ops"*) return 1 ;; + "") return 1 ;; + *) return 0 ;; + esac +} + +# Enhanced function to write verbose command header +write_verbose_header() { + local cmd="$1" + local start_time="$2" + local user=$(whoami) + local path=$(pwd) + + # Create marker for verbose filter + echo "$cmd|$start_time|$user|$path|$PUBLIC_IP|$PANE_ID" > "${VERBOSE_CMD_MARKER}-${PANE_ID}" +} + +# Function to log command to CSV (unchanged) +RTL_log_command() { + local cmd="$1" + local start_time="$2" + local end_time="$3" + local user=$(whoami) + local path=$(pwd) + + should_log_command "$cmd" || return 0 + + local old_flag="$RTL_INTERNAL_LOGGING" + RTL_INTERNAL_LOGGING=false + + local escaped_cmd=${cmd//\"/\"\"} + + printf '"%s","%s","%s","%s","%s","%s"\n' \ + "$start_time" "$end_time" "$PUBLIC_IP" "$user" "$path" "$escaped_cmd" >> "$CSV_LOG" 2>/dev/null + + RTL_INTERNAL_LOGGING="$old_flag" +} + +# BASH-specific setup +if [[ -n "$BASH_VERSION" ]]; then + [[ -z "$RTL_ORIG_PROMPT_COMMAND" ]] && RTL_ORIG_PROMPT_COMMAND="$PROMPT_COMMAND" + + RTL_LAST_COMMAND="" + RTL_LAST_HISTNUM="" + + RTL_preexec() { + local cmd="$BASH_COMMAND" + if should_log_command "$cmd" 2>/dev/null; then + RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')" + write_verbose_header "$cmd" "$RTL_CMD_START_TIME" + fi + } + + RTL_PROMPT_COMMAND() { + if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then + return 0 + fi + + local current_histnum=$(history 1 2>/dev/null | awk '{print $1}') + local current_cmd=$(history 1 2>/dev/null | sed 's/^[ ]*[0-9]*[ ]*//') + + if [[ "$current_cmd" != "$RTL_LAST_COMMAND" && "$current_histnum" != "$RTL_LAST_HISTNUM" ]]; then + if should_log_command "$current_cmd" 2>/dev/null; then + local end_time="$(date '+%Y-%m-%d %H:%M:%S')" + local start_time="${RTL_CMD_START_TIME:-$end_time}" + + RTL_log_command "$current_cmd" "$start_time" "$end_time" + + RTL_LAST_COMMAND="$current_cmd" + RTL_LAST_HISTNUM="$current_histnum" + fi + fi + + RTL_CMD_START_TIME="" + } + + trap 'RTL_preexec 2>/dev/null || true' DEBUG 2>/dev/null + + if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then + PROMPT_COMMAND="RTL_PROMPT_COMMAND; $RTL_ORIG_PROMPT_COMMAND" + else + PROMPT_COMMAND="RTL_PROMPT_COMMAND" + fi + +# ZSH-specific setup +elif [[ -n "$ZSH_VERSION" ]]; then + RTL_zsh_preexec() { + local cmd="$1" + if should_log_command "$cmd" 2>/dev/null; then + RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')" + write_verbose_header "$cmd" "$RTL_CMD_START_TIME" + fi + } + + RTL_zsh_precmd() { + if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then + return 0 + fi + + local cmd=$(fc -ln -1 2>/dev/null) + + if should_log_command "$cmd" 2>/dev/null; then + local end_time="$(date '+%Y-%m-%d %H:%M:%S')" + local start_time="${RTL_CMD_START_TIME:-$end_time}" + + RTL_log_command "$cmd" "$start_time" "$end_time" + fi + + RTL_CMD_START_TIME="" + } + + if autoload -Uz add-zsh-hook 2>/dev/null; then + add-zsh-hook preexec RTL_zsh_preexec 2>/dev/null || true + add-zsh-hook precmd RTL_zsh_precmd 2>/dev/null || true + fi +fi + +# Mark as active +echo "$$" > "__LOG_MARKER__-${PANE_ID}" +echo "Enhanced Ops Logger with verbose integration installed for pane $PANE_ID" >&2 +EOF + + # Replace placeholders + sed -i "s|__CSV_LOG__|$csv_log|g" "$hook_script" + sed -i "s|__VERBOSE_LOG__|$verbose_log|g" "$hook_script" + sed -i "s|__PANE_ID__|$pane_id|g" "$hook_script" + sed -i "s|__LOG_MARKER__|$LOG_MARKER|g" "$hook_script" + sed -i "s|__VERBOSE_CMD_MARKER__|$VERBOSE_CMD_MARKER|g" "$hook_script" + + chmod +x "$hook_script" +} + +# Start unified verbose logging +# Start unified verbose logging (add delay for stability) +start_unified_verbose_logging() { + local id="$1" + local tmux_pane_ref="$2" + local target="$3" + local log_dir="$4" + + # Create master log file + local master_log=$(create_master_verbose_log "$target" "$log_dir") + + # Create filter script + local filter_script=$(create_verbose_filter_script) + + log_debug "Starting unified verbose logging to: $master_log" + + # Use pipe-pane with our filter script + tmux pipe-pane -t "$tmux_pane_ref" "bash '$filter_script' '$master_log' '$id' '$target' '$VERBOSE_CMD_MARKER'" + + # Give pipe-pane time to establish + sleep 0.5 + + # Check if logging started successfully + if tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$tmux_pane_ref.*1"; then + log_debug "Unified verbose logging started successfully" + + # Store filter script path for cleanup + echo "$filter_script" > "/tmp/ops-filter-script-${id}" + + return 0 + else + log_debug "ERROR: Unified verbose logging failed to start" + rm -f "$filter_script" + return 1 + fi +} + +# Stop unified verbose logging +stop_unified_verbose_logging() { + local tmux_pane_ref="$1" + local id="$2" + + # Stop pipe-pane + tmux pipe-pane -t "$tmux_pane_ref" 2>/dev/null || true + + # Clean up filter script + if [[ -f "/tmp/ops-filter-script-${id}" ]]; then + local filter_script=$(cat "/tmp/ops-filter-script-${id}") + rm -f "$filter_script" 2>/dev/null + rm -f "/tmp/ops-filter-script-${id}" + fi + + # Clean up markers + rm -f "${VERBOSE_CMD_MARKER}-${id}" 2>/dev/null + rm -f "/tmp/ops-cmd-active-${id}" 2>/dev/null + + log_debug "Stopped unified verbose logging for pane: $tmux_pane_ref" +} + +# ================================================================ +# OHMYTMUX-COMPATIBLE WINDOW NAME MANAGEMENT +# ================================================================ + +# Improved window name handling that works with ohmytmux +get_current_window_name() { + local tmux_pane_ref="$1" + local name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}') + + # Remove our indicators as well as any ohmytmux status indicators + name="${name#šŸ”“ }" + name="${name#šŸŽ„ }" + name="${name#ā— }" + name="${name#⚠ }" + name="${name#ā–¶ }" + + echo "$name" +} + +set_window_logging_indicator() { + local tmux_pane_ref="$1" + local current_name=$(get_current_window_name "$tmux_pane_ref") + + # Preserve ohmytmux automatic formats but add our indicator + if [[ "$current_name" == *Z ]]; then + # Zoomed window format in ohmytmux + tmux rename-window -t "$tmux_pane_ref" "šŸ”“ ${current_name%Z}Z" + else + tmux rename-window -t "$tmux_pane_ref" "šŸ”“ $current_name" + fi +} + +set_window_recording_indicator() { + local tmux_pane_ref="$1" + local current_name=$(get_current_window_name "$tmux_pane_ref") + + # Remove logging indicator if present and add recording + current_name="${current_name#šŸ”“ }" + + # Preserve ohmytmux automatic formats + if [[ "$current_name" == *Z ]]; then + # Zoomed window format in ohmytmux + tmux rename-window -t "$tmux_pane_ref" "šŸŽ„ ${current_name%Z}Z" + else + tmux rename-window -t "$tmux_pane_ref" "šŸŽ„ $current_name" + fi +} + +clear_window_indicators() { + local tmux_pane_ref="$1" + local current_name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}') + local clean_name=$(get_current_window_name "$tmux_pane_ref") + + # Preserve any ohmytmux indicators that might be present + if [[ "$current_name" == *Z ]]; then + # Zoomed window format in ohmytmux + tmux rename-window -t "$tmux_pane_ref" "${clean_name}Z" + else + tmux rename-window -t "$tmux_pane_ref" "$clean_name" + fi +} + +# ================================================================ +# OHMYTMUX-COMPATIBLE WINDOW NAME MANAGEMENT +# ================================================================ + +# Improved window name handling that works with ohmytmux +get_current_window_name() { + local tmux_pane_ref="$1" + local name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}') + + # Remove our indicators as well as any ohmytmux status indicators + name="${name#šŸ”“ }" + name="${name#šŸŽ„ }" + name="${name#ā— }" + name="${name#⚠ }" + name="${name#ā–¶ }" + + echo "$name" +} + +set_window_logging_indicator() { + local tmux_pane_ref="$1" + local current_name=$(get_current_window_name "$tmux_pane_ref") + + # Preserve ohmytmux automatic formats but add our indicator + if [[ "$current_name" == *Z ]]; then + # Zoomed window format in ohmytmux + tmux rename-window -t "$tmux_pane_ref" "šŸ”“ ${current_name%Z}Z" + else + tmux rename-window -t "$tmux_pane_ref" "šŸ”“ $current_name" + fi +} + +set_window_recording_indicator() { + local tmux_pane_ref="$1" + local current_name=$(get_current_window_name "$tmux_pane_ref") + + # Remove logging indicator if present and add recording + current_name="${current_name#šŸ”“ }" + + # Preserve ohmytmux automatic formats + if [[ "$current_name" == *Z ]]; then + # Zoomed window format in ohmytmux + tmux rename-window -t "$tmux_pane_ref" "šŸŽ„ ${current_name%Z}Z" + else + tmux rename-window -t "$tmux_pane_ref" "šŸŽ„ $current_name" + fi +} + +clear_window_indicators() { + local tmux_pane_ref="$1" + local current_name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}') + local clean_name=$(get_current_window_name "$tmux_pane_ref") + + # Preserve any ohmytmux indicators that might be present + if [[ "$current_name" == *Z ]]; then + # Zoomed window format in ohmytmux + tmux rename-window -t "$tmux_pane_ref" "${clean_name}Z" + else + tmux rename-window -t "$tmux_pane_ref" "$clean_name" + fi +} + +# ================================================================ +# MAIN LOGGING FUNCTIONS +# ================================================================ + +# Enhanced logging installation with unified verbose logging +install_logging() { + local id="$1" + local csv_log="$2" + local target="$3" + local log_dir="$4" + + log_debug "Installing enhanced logging for pane $id" + + if is_tmux; then + local tmux_pane_ref=$(get_tmux_pane_ref) + + # Get master verbose log path + local master_log=$(create_master_verbose_log "$target" "$log_dir") + + # Start unified verbose logging + if start_unified_verbose_logging "$id" "$tmux_pane_ref" "$target" "$log_dir"; then + log_debug "Unified verbose logging started successfully" + else + log_debug "Verbose logging failed, continuing with CSV only" + fi + + # Create enhanced command hook + local hook_script="/tmp/ops-hook-${id}.sh" + create_enhanced_command_hook "$hook_script" "$csv_log" "$id" "$master_log" + + # Install the hook + tmux send-keys -t "$tmux_pane_ref" "source '$hook_script' 2>/dev/null && echo 'Enhanced logging hooks installed successfully' || echo 'Logging may have warnings but is active'" ENTER + + sleep 2 + + # Set window indicator + set_window_logging_indicator "$tmux_pane_ref" + + # Mark as active + touch "${LOG_MARKER}-${id}" + touch "${LOG_MARKER}-${id}.success" + + else + # Direct shell logging (CSV only) + local hook_script="/tmp/ops-hook-${id}.sh" + create_enhanced_command_hook "$hook_script" "$csv_log" "$id" "" + source "$hook_script" + echo "Direct shell logging started (CSV only)" + + touch "${LOG_MARKER}-${id}" + touch "${LOG_MARKER}-${id}.success" + fi + + log_debug "Enhanced logging installation completed" +} + +# Enhanced removal function +remove_logging() { + local id="$1" + + log_debug "Removing enhanced logging for pane $id" + + if is_tmux; then + local tmux_pane_ref=$(get_tmux_pane_ref) + + # Stop unified verbose logging + stop_unified_verbose_logging "$tmux_pane_ref" "$id" + + # Clean up command hooks (same as before) + local cleanup_script="/tmp/ops-cleanup-${id}.sh" + cat > "$cleanup_script" << 'EOF' +#!/usr/bin/env bash +set +e + +if [[ -n "$BASH_VERSION" ]]; then + trap - DEBUG 2>/dev/null + if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then + PROMPT_COMMAND="$RTL_ORIG_PROMPT_COMMAND" + unset RTL_ORIG_PROMPT_COMMAND + else + unset PROMPT_COMMAND + fi + unset -f RTL_PROMPT_COMMAND RTL_log_command should_log_command RTL_preexec write_verbose_header 2>/dev/null +elif [[ -n "$ZSH_VERSION" ]]; then + add-zsh-hook -d preexec RTL_zsh_preexec 2>/dev/null + add-zsh-hook -d precmd RTL_zsh_precmd 2>/dev/null + unset -f RTL_zsh_preexec RTL_zsh_precmd RTL_log_command should_log_command write_verbose_header 2>/dev/null +fi + +unset RTL_CMD_START_TIME RTL_LAST_COMMAND RTL_LAST_HISTNUM RTL_INTERNAL_LOGGING 2>/dev/null +unset CSV_LOG VERBOSE_LOG PANE_ID PUBLIC_IP VERBOSE_CMD_MARKER 2>/dev/null + +echo "Enhanced Ops Logger hooks removed" +EOF + chmod +x "$cleanup_script" + + tmux send-keys -t "$tmux_pane_ref" "source '$cleanup_script' 2>/dev/null; rm -f '$cleanup_script'" ENTER + + # Clear window indicators + clear_window_indicators "$tmux_pane_ref" + + # Clean up temp files + rm -f "/tmp/ops-hook-${id}.sh" + + else + # Direct shell cleanup (same as before) + if [[ -n "$BASH_VERSION" ]]; then + trap - DEBUG 2>/dev/null + if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then + PROMPT_COMMAND="$RTL_ORIG_PROMPT_COMMAND" + unset RTL_ORIG_PROMPT_COMMAND + else + unset PROMPT_COMMAND + fi + unset -f RTL_PROMPT_COMMAND RTL_log_command should_log_command RTL_preexec write_verbose_header 2>/dev/null + elif [[ -n "$ZSH_VERSION" ]]; then + add-zsh-hook -d preexec RTL_zsh_preexec 2>/dev/null + add-zsh-hook -d precmd RTL_zsh_precmd 2>/dev/null + unset -f RTL_zsh_preexec RTL_zsh_precmd RTL_log_command should_log_command write_verbose_header 2>/dev/null + fi + + echo "Direct shell logging stopped" + fi + + # Remove markers + rm -f "${LOG_MARKER}-${id}" "${LOG_MARKER}-${id}.success" + rm -f "/tmp/ops-hook-${id}.sh" + + log_debug "Enhanced logging removal completed" +} + +# ================================================================ +# RECORDING FUNCTIONS +# ================================================================ + +start_recording() { + local id="$1" + local target="$2" + local log_dir="$3" + local recordings_dir="${log_dir}/recordings" + local timestamp=$(date +%Y%m%d_%H%M%S) + + ensure_dir "$recordings_dir" + + # Check for asciinema + if ! command -v asciinema >/dev/null 2>&1; then + local error_msg="ERROR: asciinema not installed" + echo "$error_msg" >&2 + echo "To install asciinema:" + echo " Ubuntu/Debian: sudo apt install asciinema" + echo " Fedora/RHEL: sudo dnf install asciinema" + echo " macOS: brew install asciinema" + echo " Pip: pip3 install asciinema" + return 1 + fi + + local cast_file="${recordings_dir}/${target}_${id}_${timestamp}.cast" + echo "asciinema:$cast_file" > "${RECORDING_MARKER}-${id}" + + if is_tmux; then + local tmux_pane_ref=$(get_tmux_pane_ref) + + # Set recording indicator (ohmytmux compatible) + set_window_recording_indicator "$tmux_pane_ref" + + # Start recording cleanly + tmux send-keys -t "$tmux_pane_ref" "asciinema rec '$cast_file'" ENTER + + else + echo "Starting asciinema recording. Use 'exit' or Ctrl+D to stop." + asciinema rec "$cast_file" + fi +} + +stop_recording() { + local id="$1" + + [[ ! -f "${RECORDING_MARKER}-${id}" ]] && { + echo "No active recording" + return 1 + } + + read -r rec_type rec_path < <(cat "${RECORDING_MARKER}-${id}" | tr ':' ' ') + + if [[ "$rec_type" == "asciinema" ]]; then + if is_tmux; then + local tmux_pane_ref=$(get_tmux_pane_ref) + + # Try sending Ctrl+D + tmux send-keys -t "$tmux_pane_ref" C-d + + sleep 1 + + # If that didn't work, try killing the asciinema process + if ps aux | grep -v grep | grep -q "asciinema rec"; then + log_debug "Ctrl+D didn't stop recording, trying to kill asciinema process" + tmux send-keys -t "$tmux_pane_ref" "pkill -f 'asciinema rec'" ENTER + + sleep 1 + + if ps aux | grep -v grep | grep -q "asciinema rec"; then + pkill -f "asciinema rec" || true + fi + fi + + # Clear recording indicator but preserve logging indicator + local current_name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}') + if [[ "$current_name" == "šŸŽ„"* ]]; then + if is_logging_active "$id"; then + local clean_name="${current_name#šŸŽ„ }" + tmux rename-window -t "$tmux_pane_ref" "šŸ”“ $clean_name" + else + clear_window_indicators "$tmux_pane_ref" + fi + fi + + else + echo "Recording stopped: $rec_path" + fi + fi + + rm -f "${RECORDING_MARKER}-${id}" +} + +# ================================================================ +# CONFIGURATION FUNCTIONS +# ================================================================ + +create_config() { + local is_first_run="${1:-false}" + + # If this is a first run from ensure_config in tmux, use the local version's approach + if [[ "$is_first_run" == "true" ]] && is_tmux; then + # Mark configuration as in progress + touch "$CONFIG_IN_PROGRESS" + + # Create a temporary script that will handle the config creation + local config_script="/tmp/ops-config-$$.sh" + cat > "$config_script" << 'EOSCRIPT' +#!/usr/bin/env bash +CONFIG_FILE="${HOME}/.ops-logger.conf" +DEFAULT_TARGET="target-$(hostname | tr '.' '-')" +DEFAULT_LOG_DIR="${HOME}/OperationLogs" + +echo "Red Team Terminal Logger - First Time Setup" +echo "===========================================" +echo "" + +read -p "Enter target name [$DEFAULT_TARGET]: " TARGET_NAME +TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}" + +read -p "Enter log directory [$DEFAULT_LOG_DIR]: " LOG_DIR +LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}" + +read -p "Prompt for logging in new shells? [Y/n]: " PROMPT_NEW_SHELLS +[[ "${PROMPT_NEW_SHELLS,,}" == "n" ]] && PROMPT_NEW_SHELLS=false || PROMPT_NEW_SHELLS=true + +read -p "Enable debug logging? [y/N]: " DEBUG +[[ "${DEBUG,,}" == "y" ]] && DEBUG=true || DEBUG=false + +# Save config +mkdir -p "$(dirname "$CONFIG_FILE")" +cat > "$CONFIG_FILE" << EOF +# Ops Logger Configuration +TARGET_NAME="$TARGET_NAME" +LOG_DIR="$LOG_DIR" +PROMPT_NEW_SHELLS=$PROMPT_NEW_SHELLS +RECORD_INTERVAL=0.5 +DEBUG=$DEBUG +EOF + +# Create directories +mkdir -p "$LOG_DIR" "$LOG_DIR/verbose" "$LOG_DIR/recordings" + +# Remove the in-progress marker +rm -f "/tmp/ops-logger-configuring" + +echo "" +echo "Configuration saved!" +echo " Target name: $TARGET_NAME" +echo " Log directory: $LOG_DIR" +echo " Prompt new shells: $PROMPT_NEW_SHELLS" +echo " Debug mode: $DEBUG" +echo "" +echo "Now run: prefix+L to start logging (or ops-logger --start)" +echo "Press ENTER to close this window..." +read +EOSCRIPT + chmod +x "$config_script" + + # Open new window for config + bash $config_script; rm -f $config_script + return 0 + fi + + # For manual --config or non-tmux environments (GitHub version approach) + echo "Red Team Terminal Logger - First Time Setup" + echo "===========================================" + echo "" + + CONFIG_FILE="${HOME}/.ops-logger.conf" + DEFAULT_TARGET="target-$(hostname | tr '.' '-')" + DEFAULT_LOG_DIR="${HOME}/OperationLogs" + + # Detect whether we're in a tty + if [[ ! -t 0 ]]; then + echo "Error: Cannot prompt user in non-interactive shell." + return 1 + fi + + read -p "Enter target name [$DEFAULT_TARGET]: " TARGET_NAME + TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}" + + read -p "Enter log directory [$DEFAULT_LOG_DIR]: " LOG_DIR + LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}" + + read -p "Prompt for logging in new shells? [Y/n]: " PROMPT_NEW_SHELLS + [[ "${PROMPT_NEW_SHELLS,,}" == "n" ]] && PROMPT_NEW_SHELLS=false || PROMPT_NEW_SHELLS=true + + read -p "Enable debug logging? [y/N]: " DEBUG + [[ "${DEBUG,,}" == "y" ]] && DEBUG=true || DEBUG=false + + # Save config + mkdir -p "$(dirname "$CONFIG_FILE")" + cat > "$CONFIG_FILE" </dev/null || echo "/dev/tty") + exec 1>$tty 2>$tty + fi + + # Auto-setup config with prompts on first logging start + ensure_config || return 1 + load_config + + local id=$(get_pane_id) + is_logging_active "$id" && { + echo "Already logging" + return 0 + } + + local csv_log=$(setup_csv_log "$TARGET_NAME" "$LOG_DIR") + + install_logging "$id" "$csv_log" "$TARGET_NAME" "$LOG_DIR" +} + +stop_logging() { + # Ensure we output to terminal if run from tmux keybinding + if is_tmux && [[ -t 1 ]]; then + local tty=$(tty 2>/dev/null || echo "/dev/tty") + exec 1>$tty 2>$tty + fi + + local id=$(get_pane_id) + is_logging_active "$id" || { + echo "Not logging" + return 0 + } + + remove_logging "$id" +} + +toggle_logging() { + # Ensure we output to terminal if run from tmux keybinding + if is_tmux && [[ -t 1 ]]; then + local tty=$(tty 2>/dev/null || echo "/dev/tty") + exec 1>$tty 2>$tty + fi + + local id=$(get_pane_id) + is_logging_active "$id" && stop_logging || start_logging +} + +toggle_recording() { + # Ensure we output to terminal if run from tmux keybinding + if is_tmux && [[ -t 1 ]]; then + local tty=$(tty 2>/dev/null || echo "/dev/tty") + exec 1>$tty 2>$tty + fi + + local id=$(get_pane_id) + # Load existing config or use defaults + [[ -f "$CONFIG_FILE" ]] && load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; } + + is_recording_active "$id" && stop_recording "$id" || start_recording "$id" "$TARGET_NAME" "$LOG_DIR" +} + +prompt_for_logging() { + local id + id=$(get_pane_id) + + # āœ… Debug logging to see what's happening + echo "[$(date)] prompt_for_logging called for pane: $id" >> /tmp/ops-logger-prompt-debug.log + echo "[$(date)] TMUX_PANE: ${TMUX_PANE:-'not set'}" >> /tmp/ops-logger-prompt-debug.log + + # āœ… Avoid recursion with a pane-specific lock - BUT with timeout + local lock_file="/tmp/ops-logger-prompted-${id}" + if [[ -f "$lock_file" ]]; then + # Check if lock file is stale (older than 30 seconds) + if [[ $(find "$lock_file" -mmin +0.5 2>/dev/null) ]]; then + echo "[$(date)] Removing stale lock file" >> /tmp/ops-logger-prompt-debug.log + rm -f "$lock_file" + else + echo "[$(date)] Recent lock file exists, returning" >> /tmp/ops-logger-prompt-debug.log + return 0 + fi + fi + touch "$lock_file" + + # āœ… Cleanup function to ensure lock file is always removed + cleanup_lock() { + rm -f "$lock_file" + echo "[$(date)] Lock file cleaned up" >> /tmp/ops-logger-prompt-debug.log + } + trap cleanup_lock EXIT + + is_logging_active "$id" && { + echo "[$(date)] Already logging, returning" >> /tmp/ops-logger-prompt-debug.log + cleanup_lock + return 0 + } + + load_config + echo "[$(date)] Config loaded - PROMPT_NEW_SHELLS: $PROMPT_NEW_SHELLS" >> /tmp/ops-logger-prompt-debug.log + + [[ "$PROMPT_NEW_SHELLS" != "true" ]] && { + echo "[$(date)] Prompting disabled, returning" >> /tmp/ops-logger-prompt-debug.log + cleanup_lock + return 0 + } + + # āœ… FIXED: Use tmux display-popup for proper interaction in tmux + if is_tmux; then + echo "[$(date)] In tmux, creating popup" >> /tmp/ops-logger-prompt-debug.log + + # Create a temporary script for the popup + local popup_script="/tmp/ops-prompt-${id}.sh" + local script_path=$(readlink -f "$0") + + cat > "$popup_script" << 'EOPOPUP' +#!/bin/bash +echo "Start logging this shell?" +echo "" +echo " [Y]es - Start logging" +echo " [N]o - Skip logging" +echo "" +read -n 1 -p "Choice [Y/n]: " response +echo "" + +if [[ -z "$response" || "${response,,}" == "y" ]]; then + echo "Starting logging..." + __SCRIPT_PATH__ --start + echo "Logging started!" + sleep 2 +else + echo "Logging skipped." + sleep 1 +fi +EOPOPUP + + # Replace placeholder with actual script path + sed -i "s|__SCRIPT_PATH__|$script_path|g" "$popup_script" + chmod +x "$popup_script" + + echo "[$(date)] Popup script created, calling display-popup" >> /tmp/ops-logger-prompt-debug.log + + # Use tmux display-popup for proper interaction + tmux display-popup -E -w 50 -h 10 -T "Ops Logger" "bash '$popup_script'; rm -f '$popup_script'" + + echo "[$(date)] display-popup completed" >> /tmp/ops-logger-prompt-debug.log + + else + echo "[$(date)] Not in tmux, using direct prompt" >> /tmp/ops-logger-prompt-debug.log + + # Direct shell - standard approach + echo "Start logging this shell?" + echo "" + echo " [Y]es - Start logging" + echo " [N]o - Skip logging" + echo "" + + if read -t 10 -n 1 -p "Choice [Y/n]: " response; then + echo "" + if [[ -z "$response" || "${response,,}" == "y" ]]; then + start_logging + echo "Logging started!" + else + echo "Logging skipped." + fi + else + echo "" + echo "Timeout - logging skipped." + fi + fi + + # Clean up will be handled by trap + echo "[$(date)] prompt_for_logging completed" >> /tmp/ops-logger-prompt-debug.log +} + +# ================================================================ +# TMUX INTEGRATION +# ================================================================ + +install_tmux_keys() { + load_config + + if is_tmux; then + local script_path + script_path=$(readlink -f "$0") + + # Bind keys + tmux bind-key L run-shell "bash -c '[ -f ~/.ops-logger.conf ] && tmux run-shell \"$script_path --toggle\" || tmux display-popup -E -w 80% -h 60% -T \"Ops Logger Config\" \"bash $script_path --toggle\"'" + tmux bind-key R run-shell "'$script_path' --toggle-recording" + + # āœ… FIXED: Create a wrapper script that exports the pane context + if [[ "$PROMPT_NEW_SHELLS" == "true" ]]; then + local hook_wrapper="/tmp/ops-logger-pane-wrapper.sh" + cat > "$hook_wrapper" << EOWRAPPER +#!/bin/bash +# Wrapper that sets pane context and calls the prompt + +# Sleep to let pane initialize +sleep 1 + +# Check config +if [ -f ~/.ops-logger.conf ]; then + source ~/.ops-logger.conf + if [ "\$PROMPT_NEW_SHELLS" = "true" ]; then + # Get pane info in tmux context + PANE_ID=\$(tmux display-message -p "#{session_name}-#{window_index}-#{pane_index}") + + # Check if already logging + if [ ! -f "/tmp/ops-logger-active-\$PANE_ID" ]; then + # Export the tmux pane reference for the script to use + export TMUX_PANE=\$(tmux display-message -p "#{pane_id}") + + # Call the script with the pane context + $script_path --prompt + fi + fi +fi +EOWRAPPER + chmod +x "$hook_wrapper" + + tmux set-hook -g after-new-window "run-shell '$hook_wrapper'" + tmux set-hook -g after-split-window "run-shell '$hook_wrapper'" + echo "Hooks installed for new windows/panes" + fi + + echo "Keys installed: $(get_tmux_prefix)+L (logging), $(get_tmux_prefix)+R (recording)" + log_debug "Tmux key bindings installed" + else + echo "Not in tmux, no keys installed" + fi +} + +uninstall_tmux_keys() { + if is_tmux; then + tmux unbind-key L 2>/dev/null + tmux unbind-key R 2>/dev/null + tmux set-hook -gu after-new-window 2>/dev/null + tmux set-hook -gu after-split-window 2>/dev/null + echo "Tmux keys removed" + log_debug "Tmux key bindings removed" + + # Remove our hook scripts + rm -f /tmp/ops-logger-hook-wrapper.sh 2>/dev/null + rm -f /tmp/ops-logger-tmux-hook.sh 2>/dev/null + rm -f /tmp/ops-logger-pane-wrapper.sh 2>/dev/null + fi + + # Also remove config file as user expected + load_config + local log_dir_for_cleanup="$LOG_DIR" + + # Remove config + [[ -f "$CONFIG_FILE" ]] && rm -f "$CONFIG_FILE" + + # Remove debug log + [[ -f "${log_dir_for_cleanup}/ops-logger-debug.log" ]] && rm -f "${log_dir_for_cleanup}/ops-logger-debug.log" + + echo "Configuration removed" +} + +uninstall_all() { + local id=$(get_pane_id) + + log_debug "Starting complete uninstall" + + # Stop active sessions + is_logging_active "$id" && stop_logging + is_recording_active "$id" && stop_recording "$id" + + # Remove tmux integration and config + is_tmux && uninstall_tmux_keys + + # Clean up temp files + rm -f /tmp/ops-* 2>/dev/null + + echo "OpsLogger uninstalled completely" +} + +show_status() { + # Ensure we output to terminal if run from tmux keybinding + if is_tmux && [[ -t 1 ]]; then + local tty=$(tty 2>/dev/null || echo "/dev/tty") + exec 1>$tty 2>$tty + fi + + load_config + local id=$(get_pane_id) + + echo "Red Team Terminal Logger Status:" + echo "===============================" + echo "Target: $TARGET_NAME" + echo "Log directory: $LOG_DIR" + echo "Debug mode: $DEBUG" + echo "Logging: $(is_logging_active "$id" && echo "ACTIVE" || echo "INACTIVE")" + echo "Recording: $(is_recording_active "$id" && echo "ACTIVE" || echo "INACTIVE")" + echo "Public IP: $(get_public_ip)" + echo "Asciinema: $(command -v asciinema >/dev/null 2>&1 && echo "INSTALLED" || echo "NOT INSTALLED")" + + if is_tmux; then + echo "Environment: tmux ($(tmux display -p '#{session_name}'))" + echo "Tmux prefix: $(get_tmux_prefix)" + echo "ohmytmux: $(tmux show-options -g | grep -q "TMUX_CONF" && echo "DETECTED" || echo "NOT DETECTED")" + echo "Normalized Pane ID: $id" + + # Check plugin installation status + echo "TPM: $(check_tpm_installed && echo "INSTALLED" || echo "NOT INSTALLED")" + echo "tmux-logging plugin: $(check_tmux_logging_installed && echo "INSTALLED" || echo "NOT INSTALLED")" + + # Check if there's active pipe-pane logging + echo "Active logging pipe: $(tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$(get_tmux_pane_ref).*1" && echo "YES" || echo "NO")" + else + echo "Environment: direct shell" + fi + + # Show current log files + local csv_log="${LOG_DIR}/${TARGET_NAME}_commands_$(date +%Y-%m-%d).csv" + + [[ -f "$csv_log" ]] && { + echo "Current CSV log: $csv_log" + echo "Commands logged: $(($(wc -l < "$csv_log") - 1))" + } + + # Show verbose logs with headers + local verbose_dir="${LOG_DIR}/verbose" + if [[ -d "$verbose_dir" ]]; then + local verbose_files=$(find "$verbose_dir" -name "*${TARGET_NAME}*.log" -type f 2>/dev/null | wc -l) + echo "Verbose log files: $verbose_files in $verbose_dir" + if [[ "$verbose_files" -gt 0 ]]; then + local latest_verbose=$(find "$verbose_dir" -name "*${TARGET_NAME}*.log" -type f -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -d' ' -f2-) + [[ -n "$latest_verbose" ]] && echo "Latest verbose log: $latest_verbose ($(du -h "$latest_verbose" | cut -f1))" + fi + fi +} + +show_help() { + echo "Red Team Terminal Logger - Professional Solution v2.5.2" + echo "========================================================" + echo "USAGE: $0 [OPTIONS]" + echo "" + echo "LOGGING CONTROLS:" + echo " --start Start command/verbose logging" + echo " --stop Stop logging" + echo " --toggle Toggle logging on/off" + echo "" + echo "RECORDING CONTROLS:" + echo " --start-recording Start terminal recording (requires asciinema)" + echo " --stop-recording Stop recording" + echo " --toggle-recording Toggle recording on/off" + echo "" + echo "SETUP & CONFIGURATION:" + echo " --prompt Show logging prompt (for new shells)" + echo " --install Install tmux keybindings" + echo " --uninstall Remove tmux keybindings and config" + echo " --uninstall-all Complete removal (same as --uninstall)" + echo " --config Configure settings" + echo " --save-config Save config (internal use)" + echo "" + echo "INFO:" + echo " --status Show current status" + echo " --help Show this help" + echo "" + echo "DEBUG:" + echo " --debug-on Enable debug logging" + echo " --debug-off Disable debug logging" + echo "" + echo "TMUX INTEGRATION:" + echo " Keys: prefix+L (toggle logging), prefix+R (recording)" + echo " Compatible with ohmytmux themes and window naming" + echo " Auto-prompts for new windows/panes (configurable)" + echo "" + echo "VERSION 2.5.2 FIXES:" + echo " - FIXED: Verbose logs now include formatted command headers" + echo " - FIXED: Configuration uses interactive tmux windows" + echo " - FIXED: Command metadata properly captured and formatted" + echo " - IMPROVED: Better separation of capture and formatting" +} + +# Main command handler with output handling +main() { + # Always ensure direct terminal output for tmux operations + if is_tmux && [[ -t 1 ]]; then + local tty=$(tty 2>/dev/null || echo "/dev/tty") + exec 1>$tty 2>$tty + fi + + case "$1" in + --start) start_logging ;; + --stop) stop_logging ;; + --toggle) toggle_logging ;; + --start-recording) [[ -f "$CONFIG_FILE" ]] && load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; }; start_recording "$(get_pane_id)" "$TARGET_NAME" "$LOG_DIR" ;; + --stop-recording) stop_recording "$(get_pane_id)" ;; + --toggle-recording) toggle_recording ;; + --prompt) prompt_for_logging ;; + --install) install_tmux_keys ;; + --uninstall) uninstall_tmux_keys ;; + --uninstall-all) uninstall_all ;; + --config) create_config ;; + --save-config) save_config_from_tmux ;; + --status) show_status ;; + --debug-pipe) + load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; } + local id=$(get_pane_id) + local tmux_pane_ref=$(get_tmux_pane_ref) + start_debug_verbose_logging "$id" "$tmux_pane_ref" "$TARGET_NAME" "$LOG_DIR" + ;; + --debug-on) [[ -f "$CONFIG_FILE" ]] && { load_config; DEBUG=true; save_config; } || echo "Run --config first to set up configuration"; echo "Debug logging enabled" ;; + --debug-off) [[ -f "$CONFIG_FILE" ]] && { load_config; DEBUG=false; save_config; } || echo "Run --config first to set up configuration"; echo "Debug logging disabled" ;; + --help|'') show_help ;; + *) echo "Unknown option: $1. Use --help" ;; + esac +} + +# ShellOpsLog compatibility layer +if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then + export REDTEAM_LOGGER_PATH="${BASH_SOURCE[0]}" + + start_operation_log() { + local auto_start=0 + local log_dir="$HOME/OperationLogs" + + while [ "$#" -gt 0 ]; do + case "$1" in + -AutoStart|-autostart) auto_start=1; shift ;; + *) log_dir="$1"; shift ;; + esac + done + + [[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE" + + LOG_DIR="$log_dir" + TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}" + save_config + + if [[ "$auto_start" -eq 1 ]]; then + "$REDTEAM_LOGGER_PATH" --start + else + "$REDTEAM_LOGGER_PATH" --prompt + fi + } + + stop_operation_log() { + "$REDTEAM_LOGGER_PATH" --stop + } + + log_debug "ShellOpsLog compatibility layer loaded" +else + # Script is being executed directly + main "$@" +fi