Autarch/modules/agent_hal.py
DigiJ ffe47c51b5 Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.

Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00

1454 lines
60 KiB
Python

"""
AUTARCH Agent Hal Module
AI-powered security automation for defense and penetration testing
Uses LLM integration for intelligent security operations including:
- MITM attack detection and monitoring
- Automated Metasploit module execution via natural language
- Network security analysis
"""
import os
import sys
import subprocess
import re
import json
import time
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Optional, Tuple
# Module metadata
NAME = "Agent Hal"
DESCRIPTION = "AI-powered security automation"
AUTHOR = "darkHal Security Group"
VERSION = "2.0"
CATEGORY = "core"
sys.path.insert(0, str(Path(__file__).parent.parent))
from core.banner import Colors, clear_screen, display_banner
from core.config import get_config
from core.llm import LLM, LLMError
from core.pentest_tree import PentestTree, NodeStatus, PTTNodeType
from core.pentest_pipeline import PentestPipeline, detect_source_type
from core.pentest_session import PentestSession, PentestSessionState
class AgentHal:
"""AI-powered security automation agent."""
def __init__(self):
self.config = get_config()
self.llm = None
self.msf = None
self.msf_connected = False
self.pentest_session = None
self.pentest_pipeline = None
def print_status(self, message: str, status: str = "info"):
colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED}
symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"}
print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}")
def run_cmd(self, cmd: str, timeout: int = 30) -> Tuple[bool, str]:
"""Run a shell command and return output."""
try:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
return result.returncode == 0, result.stdout.strip()
except subprocess.TimeoutExpired:
return False, "Command timed out"
except Exception as e:
return False, str(e)
def _ensure_llm_loaded(self) -> bool:
"""Ensure LLM is loaded and ready."""
if self.llm is None:
self.llm = LLM()
if not self.llm.is_loaded:
self.print_status("Loading LLM model...", "info")
try:
self.llm.load_model(verbose=True)
return True
except LLMError as e:
self.print_status(f"Failed to load LLM: {e}", "error")
return False
return True
def _ensure_msf_connected(self) -> bool:
"""Ensure MSF RPC is connected via the centralized interface."""
if self.msf is None:
try:
from core.msf_interface import get_msf_interface
self.msf = get_msf_interface()
except ImportError:
self.print_status("MSF interface not available", "error")
return False
# Use the interface's connection management
connected, msg = self.msf.ensure_connected(auto_prompt=False)
if connected:
self.msf_connected = True
self.print_status("Connected to MSF RPC", "success")
return True
else:
self.print_status(f"Failed to connect to MSF: {msg}", "error")
return False
# ==================== MITM DETECTION ====================
def mitm_detection_menu(self):
"""MITM attack detection submenu."""
while True:
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} MITM Detection{Colors.RESET}")
print(f"{Colors.DIM} Detect Man-in-the-Middle attacks on your network{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
print(f" {Colors.GREEN}[1]{Colors.RESET} Full MITM Scan (All Checks)")
print(f" {Colors.GREEN}[2]{Colors.RESET} ARP Spoofing Detection")
print(f" {Colors.GREEN}[3]{Colors.RESET} DNS Spoofing Detection")
print(f" {Colors.GREEN}[4]{Colors.RESET} SSL/TLS Stripping Detection")
print(f" {Colors.GREEN}[5]{Colors.RESET} Rogue DHCP Detection")
print(f" {Colors.GREEN}[6]{Colors.RESET} Gateway Anomaly Check")
print()
print(f" {Colors.CYAN}[7]{Colors.RESET} Continuous Monitoring Mode")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0":
break
elif choice == "1":
self.full_mitm_scan()
elif choice == "2":
self.detect_arp_spoofing()
elif choice == "3":
self.detect_dns_spoofing()
elif choice == "4":
self.detect_ssl_stripping()
elif choice == "5":
self.detect_rogue_dhcp()
elif choice == "6":
self.check_gateway_anomaly()
elif choice == "7":
self.continuous_monitoring()
if choice in ["1", "2", "3", "4", "5", "6"]:
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def full_mitm_scan(self):
"""Run all MITM detection checks."""
print(f"\n{Colors.BOLD}Full MITM Scan{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}\n")
issues = []
# ARP Spoofing
print(f"{Colors.CYAN}[1/5] Checking for ARP spoofing...{Colors.RESET}")
arp_issues = self._check_arp_spoofing()
issues.extend(arp_issues)
# DNS Spoofing
print(f"{Colors.CYAN}[2/5] Checking for DNS spoofing...{Colors.RESET}")
dns_issues = self._check_dns_spoofing()
issues.extend(dns_issues)
# SSL Stripping
print(f"{Colors.CYAN}[3/5] Checking for SSL stripping indicators...{Colors.RESET}")
ssl_issues = self._check_ssl_stripping()
issues.extend(ssl_issues)
# Rogue DHCP
print(f"{Colors.CYAN}[4/5] Checking for rogue DHCP servers...{Colors.RESET}")
dhcp_issues = self._check_rogue_dhcp()
issues.extend(dhcp_issues)
# Gateway Anomaly
print(f"{Colors.CYAN}[5/5] Checking gateway for anomalies...{Colors.RESET}")
gw_issues = self._check_gateway()
issues.extend(gw_issues)
# Results
print(f"\n{Colors.CYAN}{'' * 50}{Colors.RESET}")
print(f"{Colors.BOLD}Scan Results{Colors.RESET}\n")
if issues:
print(f"{Colors.RED}[!] Found {len(issues)} potential issue(s):{Colors.RESET}\n")
for issue in issues:
severity_color = Colors.RED if issue['severity'] == 'HIGH' else Colors.YELLOW
print(f" {severity_color}[{issue['severity']}]{Colors.RESET} {issue['type']}")
print(f" {issue['description']}")
if issue.get('details'):
print(f" {Colors.DIM}{issue['details']}{Colors.RESET}")
print()
else:
print(f"{Colors.GREEN}[+] No MITM indicators detected{Colors.RESET}")
print(f"{Colors.DIM} Network appears clean{Colors.RESET}")
def _check_arp_spoofing(self) -> List[Dict]:
"""Check for ARP spoofing indicators."""
issues = []
# Get ARP table
success, output = self.run_cmd("arp -a")
if not success:
success, output = self.run_cmd("ip neigh show")
if success and output:
# Parse ARP entries
mac_to_ips = {}
lines = output.split('\n')
for line in lines:
# Extract MAC and IP
mac_match = re.search(r'([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}', line)
ip_match = re.search(r'(\d{1,3}\.){3}\d{1,3}', line)
if mac_match and ip_match:
mac = mac_match.group().lower()
ip = ip_match.group()
if mac not in mac_to_ips:
mac_to_ips[mac] = []
mac_to_ips[mac].append(ip)
# Check for duplicate MACs (potential ARP spoofing)
for mac, ips in mac_to_ips.items():
if len(ips) > 1:
issues.append({
'type': 'ARP Spoofing Detected',
'severity': 'HIGH',
'description': f'Multiple IPs share same MAC address',
'details': f'MAC {mac} -> IPs: {", ".join(ips)}'
})
return issues
def detect_arp_spoofing(self):
"""Detailed ARP spoofing detection."""
print(f"\n{Colors.BOLD}ARP Spoofing Detection{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}\n")
# Get ARP table
print(f"{Colors.CYAN}[*] Fetching ARP table...{Colors.RESET}")
success, output = self.run_cmd("arp -a")
if not success:
success, output = self.run_cmd("ip neigh show")
if success and output:
print(f"\n{Colors.CYAN}Current ARP Table:{Colors.RESET}")
print(output)
issues = self._check_arp_spoofing()
if issues:
print(f"\n{Colors.RED}[!] ARP Spoofing Indicators Found:{Colors.RESET}")
for issue in issues:
print(f" {issue['description']}")
print(f" {Colors.DIM}{issue['details']}{Colors.RESET}")
else:
print(f"\n{Colors.GREEN}[+] No ARP spoofing detected{Colors.RESET}")
# Get gateway MAC
print(f"\n{Colors.CYAN}[*] Checking gateway MAC...{Colors.RESET}")
success, gw = self.run_cmd("ip route | grep default | awk '{print $3}'")
if success and gw:
print(f" Gateway IP: {gw}")
success, gw_mac = self.run_cmd(f"arp -n {gw} | grep -v Address | awk '{{print $3}}'")
if success and gw_mac:
print(f" Gateway MAC: {gw_mac}")
def _check_dns_spoofing(self) -> List[Dict]:
"""Check for DNS spoofing indicators."""
issues = []
# Check resolv.conf for suspicious DNS
success, output = self.run_cmd("cat /etc/resolv.conf")
if success:
dns_servers = re.findall(r'nameserver\s+(\S+)', output)
# Known safe DNS servers
safe_dns = ['8.8.8.8', '8.8.4.4', '1.1.1.1', '1.0.0.1', '9.9.9.9', '208.67.222.222']
for dns in dns_servers:
if dns.startswith('127.') or dns.startswith('192.168.') or dns.startswith('10.'):
# Local DNS - could be legitimate or malicious
pass
elif dns not in safe_dns:
issues.append({
'type': 'Suspicious DNS Server',
'severity': 'MEDIUM',
'description': f'Unknown DNS server configured',
'details': f'DNS: {dns}'
})
# Test DNS resolution consistency
test_domains = ['google.com', 'cloudflare.com']
for domain in test_domains:
success1, ip1 = self.run_cmd(f"dig +short {domain} @8.8.8.8 | head -1")
success2, ip2 = self.run_cmd(f"dig +short {domain} | head -1")
if success1 and success2 and ip1 and ip2:
if ip1 != ip2:
issues.append({
'type': 'DNS Resolution Mismatch',
'severity': 'HIGH',
'description': f'DNS returns different IP than Google DNS',
'details': f'{domain}: Local={ip2}, Google={ip1}'
})
return issues
def detect_dns_spoofing(self):
"""Detailed DNS spoofing detection."""
print(f"\n{Colors.BOLD}DNS Spoofing Detection{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}\n")
# Show current DNS config
print(f"{Colors.CYAN}[*] Current DNS Configuration:{Colors.RESET}")
success, output = self.run_cmd("cat /etc/resolv.conf | grep -v '^#'")
if success:
print(output)
# Test DNS resolution
print(f"\n{Colors.CYAN}[*] Testing DNS Resolution Consistency:{Colors.RESET}")
test_domains = ['google.com', 'cloudflare.com', 'microsoft.com']
for domain in test_domains:
success1, ip1 = self.run_cmd(f"dig +short {domain} @8.8.8.8 | head -1")
success2, ip2 = self.run_cmd(f"dig +short {domain} | head -1")
if success1 and success2:
match = "MATCH" if ip1 == ip2 else "MISMATCH"
color = Colors.GREEN if ip1 == ip2 else Colors.RED
print(f" {domain}:")
print(f" Local DNS: {ip2}")
print(f" Google DNS: {ip1}")
print(f" Status: {color}{match}{Colors.RESET}")
issues = self._check_dns_spoofing()
if issues:
print(f"\n{Colors.RED}[!] DNS Issues Found:{Colors.RESET}")
for issue in issues:
print(f" {issue['description']}: {issue['details']}")
def _check_ssl_stripping(self) -> List[Dict]:
"""Check for SSL stripping indicators."""
issues = []
# Check if HSTS is being honored
test_sites = ['https://www.google.com', 'https://www.cloudflare.com']
for site in test_sites:
success, output = self.run_cmd(f"curl -sI -m 5 {site} | head -1")
if success:
if 'HTTP/1.1 200' not in output and 'HTTP/2' not in output:
issues.append({
'type': 'HTTPS Connection Issue',
'severity': 'MEDIUM',
'description': f'Unexpected response from HTTPS site',
'details': f'{site}: {output}'
})
# Check for SSL certificate issues
success, output = self.run_cmd("curl -sI -m 5 https://www.google.com 2>&1 | grep -i 'certificate'")
if success and 'certificate' in output.lower():
issues.append({
'type': 'SSL Certificate Warning',
'severity': 'HIGH',
'description': 'SSL certificate issues detected',
'details': output
})
return issues
def detect_ssl_stripping(self):
"""Detailed SSL stripping detection."""
print(f"\n{Colors.BOLD}SSL/TLS Stripping Detection{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}\n")
test_sites = [
('https://www.google.com', 'Google'),
('https://www.cloudflare.com', 'Cloudflare'),
('https://www.github.com', 'GitHub'),
]
print(f"{Colors.CYAN}[*] Testing HTTPS Connections:{Colors.RESET}\n")
for url, name in test_sites:
print(f" Testing {name}...")
# Check HTTP redirect
http_url = url.replace('https://', 'http://')
success, output = self.run_cmd(f"curl -sI -m 5 -o /dev/null -w '%{{http_code}} %{{redirect_url}}' {http_url}")
if success:
parts = output.split()
code = parts[0] if parts else "000"
redirect = parts[1] if len(parts) > 1 else ""
if code in ['301', '302', '307', '308'] and redirect.startswith('https://'):
print(f" {Colors.GREEN}[+] HTTP->HTTPS redirect working{Colors.RESET}")
else:
print(f" {Colors.YELLOW}[!] No HTTPS redirect (Code: {code}){Colors.RESET}")
# Check HTTPS directly
success, output = self.run_cmd(f"curl -sI -m 5 {url} 2>&1 | head -1")
if success and ('200' in output or 'HTTP/2' in output):
print(f" {Colors.GREEN}[+] HTTPS connection successful{Colors.RESET}")
else:
print(f" {Colors.RED}[!] HTTPS connection failed: {output}{Colors.RESET}")
# Check certificate
domain = url.replace('https://', '').replace('/', '')
success, cert_info = self.run_cmd(f"echo | openssl s_client -connect {domain}:443 -servername {domain} 2>/dev/null | openssl x509 -noout -dates 2>/dev/null")
if success and cert_info:
print(f" {Colors.GREEN}[+] Valid SSL certificate{Colors.RESET}")
print(f" {Colors.DIM}{cert_info.replace(chr(10), ' | ')}{Colors.RESET}")
print()
def _check_rogue_dhcp(self) -> List[Dict]:
"""Check for rogue DHCP servers."""
issues = []
# Get current DHCP server
success, output = self.run_cmd("cat /var/lib/dhcp/dhclient.leases 2>/dev/null | grep 'dhcp-server-identifier' | tail -1")
if not success:
success, output = self.run_cmd("journalctl -u NetworkManager --no-pager -n 50 2>/dev/null | grep -i 'dhcp' | grep -i 'server'")
# This is a basic check - full rogue DHCP detection requires nmap or specialized tools
return issues
def detect_rogue_dhcp(self):
"""Detect rogue DHCP servers."""
print(f"\n{Colors.BOLD}Rogue DHCP Detection{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}\n")
print(f"{Colors.CYAN}[*] Current DHCP Information:{Colors.RESET}")
# Check DHCP leases
success, output = self.run_cmd("cat /var/lib/dhcp/dhclient.leases 2>/dev/null | tail -30")
if success and output:
print(output)
else:
# Try NetworkManager
success, output = self.run_cmd("nmcli device show | grep -i 'dhcp\\|gateway\\|dns'")
if success:
print(output)
print(f"\n{Colors.YELLOW}[!] Note: Full rogue DHCP detection requires nmap:{Colors.RESET}")
print(f" {Colors.DIM}nmap --script broadcast-dhcp-discover{Colors.RESET}")
# Offer to run nmap scan
run_nmap = input(f"\n{Colors.WHITE}Run nmap DHCP discovery? (y/n): {Colors.RESET}").strip().lower()
if run_nmap == 'y':
print(f"\n{Colors.CYAN}[*] Running DHCP discovery...{Colors.RESET}")
success, output = self.run_cmd("nmap --script broadcast-dhcp-discover 2>/dev/null", timeout=60)
if success:
print(output)
else:
self.print_status("nmap not available or scan failed", "warning")
def _check_gateway(self) -> List[Dict]:
"""Check gateway for anomalies."""
issues = []
# Get default gateway
success, gateway = self.run_cmd("ip route | grep default | awk '{print $3}'")
if success and gateway:
# Ping gateway
success, output = self.run_cmd(f"ping -c 1 -W 2 {gateway}")
if not success:
issues.append({
'type': 'Gateway Unreachable',
'severity': 'HIGH',
'description': 'Default gateway is not responding',
'details': f'Gateway: {gateway}'
})
# Check gateway MAC consistency
success, mac = self.run_cmd(f"arp -n {gateway} | grep -v Address | awk '{{print $3}}'")
if success and mac:
# Check if MAC is from a known vendor (basic check)
if mac.startswith('00:00:00') or mac == '(incomplete)':
issues.append({
'type': 'Suspicious Gateway MAC',
'severity': 'MEDIUM',
'description': 'Gateway MAC address appears suspicious',
'details': f'Gateway {gateway} has MAC {mac}'
})
return issues
def check_gateway_anomaly(self):
"""Check for gateway anomalies."""
print(f"\n{Colors.BOLD}Gateway Anomaly Check{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}\n")
# Get gateway info
success, gateway = self.run_cmd("ip route | grep default | awk '{print $3}'")
if success and gateway:
print(f" Default Gateway: {gateway}")
# Get MAC
success, mac = self.run_cmd(f"arp -n {gateway} | grep -v Address | awk '{{print $3}}'")
if success and mac:
print(f" Gateway MAC: {mac}")
# Ping test
success, output = self.run_cmd(f"ping -c 3 -W 2 {gateway}")
if success:
print(f" {Colors.GREEN}[+] Gateway is reachable{Colors.RESET}")
# Extract latency
latency = re.search(r'rtt min/avg/max/mdev = ([\d.]+)/([\d.]+)', output)
if latency:
print(f" Latency: {latency.group(2)}ms avg")
else:
print(f" {Colors.RED}[!] Gateway is NOT reachable{Colors.RESET}")
# Traceroute
print(f"\n{Colors.CYAN}[*] Route to Internet:{Colors.RESET}")
success, output = self.run_cmd("traceroute -m 5 8.8.8.8 2>/dev/null", timeout=30)
if success:
print(output)
else:
print(f" {Colors.RED}[!] Could not determine default gateway{Colors.RESET}")
def continuous_monitoring(self):
"""Continuous MITM monitoring mode."""
print(f"\n{Colors.BOLD}Continuous MITM Monitoring{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}")
print(f"{Colors.DIM}Press Ctrl+C to stop monitoring{Colors.RESET}\n")
# Store baseline
success, baseline_arp = self.run_cmd("arp -a")
baseline_macs = {}
if success:
for line in baseline_arp.split('\n'):
mac_match = re.search(r'([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}', line)
ip_match = re.search(r'(\d{1,3}\.){3}\d{1,3}', line)
if mac_match and ip_match:
baseline_macs[ip_match.group()] = mac_match.group().lower()
print(f"{Colors.GREEN}[+] Baseline captured: {len(baseline_macs)} hosts{Colors.RESET}\n")
try:
check_count = 0
while True:
check_count += 1
timestamp = datetime.now().strftime("%H:%M:%S")
# Get current ARP table
success, current_arp = self.run_cmd("arp -a")
if success:
current_macs = {}
for line in current_arp.split('\n'):
mac_match = re.search(r'([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}', line)
ip_match = re.search(r'(\d{1,3}\.){3}\d{1,3}', line)
if mac_match and ip_match:
current_macs[ip_match.group()] = mac_match.group().lower()
# Compare with baseline
for ip, mac in current_macs.items():
if ip in baseline_macs and baseline_macs[ip] != mac:
print(f"{Colors.RED}[{timestamp}] ALERT: MAC change detected!{Colors.RESET}")
print(f" IP: {ip}")
print(f" Old MAC: {baseline_macs[ip]}")
print(f" New MAC: {mac}")
print()
# Check for new hosts
new_hosts = set(current_macs.keys()) - set(baseline_macs.keys())
for ip in new_hosts:
print(f"{Colors.YELLOW}[{timestamp}] New host detected: {ip} ({current_macs[ip]}){Colors.RESET}")
print(f"\r{Colors.DIM}[{timestamp}] Check #{check_count} - {len(current_macs)} hosts{Colors.RESET}", end='', flush=True)
time.sleep(5)
except KeyboardInterrupt:
print(f"\n\n{Colors.CYAN}[*] Monitoring stopped{Colors.RESET}")
# ==================== MSF AUTOMATION ====================
def msf_automation_menu(self):
"""LLM-driven Metasploit automation menu."""
while True:
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} MSF Automation (AI-Powered){Colors.RESET}")
print(f"{Colors.DIM} Use natural language to run Metasploit modules{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Status
llm_status = f"{Colors.GREEN}Loaded{Colors.RESET}" if (self.llm and self.llm.is_loaded) else f"{Colors.RED}Not loaded{Colors.RESET}"
msf_status = f"{Colors.GREEN}Connected{Colors.RESET}" if self.msf_connected else f"{Colors.RED}Not connected{Colors.RESET}"
print(f" {Colors.DIM}LLM: {llm_status} | MSF: {msf_status}{Colors.RESET}")
print()
print(f" {Colors.GREEN}[1]{Colors.RESET} Describe What You Want To Do")
print(f" {Colors.GREEN}[2]{Colors.RESET} Quick Scan Target")
print(f" {Colors.GREEN}[3]{Colors.RESET} Exploit Suggester")
print(f" {Colors.GREEN}[4]{Colors.RESET} Post-Exploitation Helper")
print()
print(f" {Colors.CYAN}[C]{Colors.RESET} Connect to MSF")
print(f" {Colors.CYAN}[L]{Colors.RESET} Load LLM Model")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower()
if choice == "0":
break
elif choice == "1":
self.natural_language_msf()
elif choice == "2":
self.quick_scan_target()
elif choice == "3":
self.exploit_suggester()
elif choice == "4":
self.post_exploitation_helper()
elif choice == "c":
self._ensure_msf_connected()
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
elif choice == "l":
self._ensure_llm_loaded()
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def natural_language_msf(self):
"""Process natural language requests for MSF operations."""
print(f"\n{Colors.BOLD}Natural Language MSF Control{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}")
print(f"{Colors.DIM}Describe what you want to do in plain English.{Colors.RESET}")
print(f"{Colors.DIM}Examples:{Colors.RESET}")
print(f"{Colors.DIM} - 'Scan 192.168.1.1 for open ports'{Colors.RESET}")
print(f"{Colors.DIM} - 'Check if target is vulnerable to EternalBlue'{Colors.RESET}")
print(f"{Colors.DIM} - 'Run SMB version scanner on 10.0.0.0/24'{Colors.RESET}")
print()
# Ensure LLM is loaded
if not self._ensure_llm_loaded():
return
# Ensure MSF is connected
if not self._ensure_msf_connected():
print(f"{Colors.YELLOW}[!] MSF not connected. Will show commands but cannot execute.{Colors.RESET}")
# Get user request
request = input(f"{Colors.WHITE}What do you want to do? {Colors.RESET}").strip()
if not request:
return
print(f"\n{Colors.CYAN}[*] Analyzing request...{Colors.RESET}")
# Build prompt for LLM
system_prompt = """You are a Metasploit expert assistant. Your job is to translate user requests into specific Metasploit module recommendations.
When the user describes what they want to do, respond with:
1. The recommended MSF module path (e.g., auxiliary/scanner/smb/smb_version)
2. Required options (e.g., RHOSTS, RPORT)
3. A brief explanation of what the module does
Format your response as JSON:
{
"module_type": "auxiliary|exploit|post",
"module_path": "full/module/path",
"options": {"RHOSTS": "value", "THREADS": "10"},
"explanation": "Brief description"
}
If you cannot determine an appropriate module, respond with:
{"error": "reason"}
Common modules:
- Port scan: auxiliary/scanner/portscan/tcp
- SMB version: auxiliary/scanner/smb/smb_version
- MS17-010 check: auxiliary/scanner/smb/smb_ms17_010
- SSH version: auxiliary/scanner/ssh/ssh_version
- HTTP version: auxiliary/scanner/http/http_version
- FTP version: auxiliary/scanner/ftp/ftp_version
- Vuln scan: auxiliary/scanner/smb/smb_ms08_067
"""
try:
# Clear history for fresh context
self.llm.clear_history()
# Get LLM response
response = self.llm.chat(request, system_prompt=system_prompt)
# Try to parse JSON from response
try:
# Find JSON in response
json_match = re.search(r'\{[^{}]*\}', response, re.DOTALL)
if json_match:
module_info = json.loads(json_match.group())
else:
module_info = json.loads(response)
if 'error' in module_info:
print(f"\n{Colors.YELLOW}[!] {module_info['error']}{Colors.RESET}")
return
# Display recommendation
print(f"\n{Colors.GREEN}[+] Recommended Module:{Colors.RESET}")
print(f" Type: {module_info.get('module_type', 'unknown')}")
print(f" Path: {module_info.get('module_path', 'unknown')}")
print(f"\n{Colors.CYAN}Options:{Colors.RESET}")
for opt, val in module_info.get('options', {}).items():
print(f" {opt}: {val}")
print(f"\n{Colors.DIM}Explanation: {module_info.get('explanation', 'N/A')}{Colors.RESET}")
# Ask to execute
if self.msf and self.msf.is_connected:
execute = input(f"\n{Colors.WHITE}Execute this module? (y/n): {Colors.RESET}").strip().lower()
if execute == 'y':
self._execute_msf_module(module_info)
except json.JSONDecodeError:
# LLM didn't return valid JSON, show raw response
print(f"\n{Colors.CYAN}LLM Response:{Colors.RESET}")
print(response)
except LLMError as e:
self.print_status(f"LLM error: {e}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _execute_msf_module(self, module_info: Dict):
"""Execute an MSF module based on LLM recommendation."""
try:
module_type = module_info.get('module_type', 'auxiliary')
module_path = module_info.get('module_path', '')
options = module_info.get('options', {})
# Ensure full module path format (type/path)
if not module_path.startswith(module_type + '/'):
full_path = f"{module_type}/{module_path}"
else:
full_path = module_path
print(f"\n{Colors.CYAN}[*] Executing {full_path}...{Colors.RESET}")
# Use the interface's run_module method
result = self.msf.run_module(full_path, options)
if result.success:
print(f"{Colors.GREEN}[+] Module executed successfully{Colors.RESET}")
if result.findings:
print(f"\n{Colors.CYAN}Findings:{Colors.RESET}")
for finding in result.findings[:10]:
print(f" {finding}")
if result.info:
for info in result.info[:5]:
print(f" {Colors.DIM}{info}{Colors.RESET}")
else:
print(f"{Colors.YELLOW}[!] {result.get_summary()}{Colors.RESET}")
except Exception as e:
self.print_status(f"Execution failed: {e}", "error")
def quick_scan_target(self):
"""Quick scan a target using MSF."""
print(f"\n{Colors.BOLD}Quick Target Scan{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}\n")
target = input(f"{Colors.WHITE}Enter target (IP or range): {Colors.RESET}").strip()
if not target:
return
if not self._ensure_msf_connected():
self.print_status("Cannot scan without MSF connection", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
print(f"\n{Colors.CYAN}[*] Running quick scan on {target}...{Colors.RESET}\n")
# Run common scanners
scanners = [
('auxiliary/scanner/portscan/tcp', {'RHOSTS': target, 'PORTS': '21-25,80,443,445,3389,8080'}),
('auxiliary/scanner/smb/smb_version', {'RHOSTS': target}),
('auxiliary/scanner/ssh/ssh_version', {'RHOSTS': target}),
]
for module_path, options in scanners:
try:
print(f" Running {module_path}...")
result = self.msf.run_module(module_path, options)
if result.success:
print(f" {Colors.GREEN}Completed{Colors.RESET}")
for finding in result.findings[:3]:
print(f" {finding}")
else:
print(f" {Colors.YELLOW}{result.get_summary()}{Colors.RESET}")
except Exception as e:
print(f" {Colors.RED}Failed: {e}{Colors.RESET}")
print(f"\n{Colors.GREEN}[+] Quick scan completed.{Colors.RESET}")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def exploit_suggester(self):
"""Use LLM to suggest exploits based on target info."""
print(f"\n{Colors.BOLD}Exploit Suggester{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}")
print(f"{Colors.DIM}Describe your target and I'll suggest exploits.{Colors.RESET}\n")
if not self._ensure_llm_loaded():
return
print(f"{Colors.WHITE}Enter target information:{Colors.RESET}")
print(f"{Colors.DIM}(OS, services, versions, open ports, etc.){Colors.RESET}")
target_info = input(f"{Colors.WHITE}> {Colors.RESET}").strip()
if not target_info:
return
print(f"\n{Colors.CYAN}[*] Analyzing target...{Colors.RESET}")
system_prompt = """You are a penetration testing expert. Based on the target information provided, suggest relevant Metasploit exploits and auxiliary modules.
Consider:
1. Operating system vulnerabilities
2. Service-specific exploits
3. Common misconfigurations
4. Post-exploitation opportunities
Format your response as a prioritized list with:
- Module path
- CVE (if applicable)
- Success likelihood (High/Medium/Low)
- Brief description
Focus on practical, commonly successful exploits."""
try:
self.llm.clear_history()
response = self.llm.chat(f"Target information: {target_info}", system_prompt=system_prompt)
print(f"\n{Colors.GREEN}Exploit Suggestions:{Colors.RESET}\n")
print(response)
except LLMError as e:
self.print_status(f"LLM error: {e}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def post_exploitation_helper(self):
"""LLM-assisted post-exploitation guidance."""
print(f"\n{Colors.BOLD}Post-Exploitation Helper{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}")
print(f"{Colors.DIM}Get guidance on post-exploitation steps.{Colors.RESET}\n")
if not self._ensure_llm_loaded():
return
print(f"{Colors.WHITE}Describe your current access:{Colors.RESET}")
print(f"{Colors.DIM}(Shell type, privileges, OS, what you've found){Colors.RESET}")
access_info = input(f"{Colors.WHITE}> {Colors.RESET}").strip()
if not access_info:
return
print(f"\n{Colors.CYAN}[*] Generating post-exploitation plan...{Colors.RESET}")
system_prompt = """You are a post-exploitation expert. Based on the current access described, provide a structured post-exploitation plan.
Include:
1. Privilege escalation techniques (if not already root/SYSTEM)
2. Persistence mechanisms
3. Credential harvesting opportunities
4. Lateral movement options
5. Data exfiltration considerations
6. Relevant Metasploit post modules
Be specific with commands and module paths. Prioritize by likelihood of success."""
try:
self.llm.clear_history()
response = self.llm.chat(f"Current access: {access_info}", system_prompt=system_prompt)
print(f"\n{Colors.GREEN}Post-Exploitation Plan:{Colors.RESET}\n")
print(response)
except LLMError as e:
self.print_status(f"LLM error: {e}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
# ==================== PENTEST PIPELINE ====================
def pentest_pipeline_menu(self):
"""PentestGPT-style structured penetration testing menu."""
while True:
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} Pentest Pipeline (AI-Powered){Colors.RESET}")
print(f"{Colors.DIM} Structured penetration testing with task tree{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Status
llm_status = f"{Colors.GREEN}Ready{Colors.RESET}" if (self.llm and self.llm.is_loaded) else f"{Colors.DIM}Not loaded{Colors.RESET}"
msf_status = f"{Colors.GREEN}Connected{Colors.RESET}" if self.msf_connected else f"{Colors.DIM}Offline{Colors.RESET}"
session_status = f"{Colors.GREEN}{self.pentest_session.target}{Colors.RESET}" if self.pentest_session else f"{Colors.DIM}None{Colors.RESET}"
print(f" {Colors.DIM}LLM: {llm_status} | MSF: {msf_status}{Colors.RESET}")
print(f" {Colors.DIM}Session: {session_status}{Colors.RESET}")
print()
print(f" {Colors.GREEN}[1]{Colors.RESET} New Pentest Session")
print(f" {Colors.GREEN}[2]{Colors.RESET} Resume Saved Session")
print(f" {Colors.GREEN}[3]{Colors.RESET} List Saved Sessions")
print(f" {Colors.GREEN}[4]{Colors.RESET} Delete Session")
print()
if self.pentest_session:
print(f" {Colors.CYAN}[S]{Colors.RESET} Show Task Tree")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower()
if choice == "0":
break
elif choice == "1":
self._start_new_pentest_session()
elif choice == "2":
self._resume_pentest_session()
elif choice == "3":
self._list_pentest_sessions()
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
elif choice == "4":
self._delete_pentest_session()
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
elif choice == "s" and self.pentest_session:
print(f"\n{self.pentest_session.tree.render_text()}")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _start_new_pentest_session(self):
"""Start a new pentest session with target."""
print(f"\n{Colors.BOLD}New Pentest Session{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}\n")
target = input(f"{Colors.WHITE}Enter target (IP, hostname, or range): {Colors.RESET}").strip()
if not target:
return
notes = input(f"{Colors.WHITE}Notes (optional): {Colors.RESET}").strip()
# Ensure LLM is loaded
if not self._ensure_llm_loaded():
return
# Create session
self.pentest_session = PentestSession(target)
self.pentest_session.notes = notes
self.pentest_session.start()
# Create pipeline
self.pentest_pipeline = PentestPipeline(
self.llm, target, self.pentest_session.tree
)
self.print_status(f"Session created: {self.pentest_session.session_id}", "success")
self.print_status("Generating initial plan...", "info")
# Generate initial plan
try:
plan = self.pentest_pipeline.get_initial_plan()
print(f"\n{Colors.GREEN}Initial Plan:{Colors.RESET}")
if plan.get('first_action'):
print(f" First action: {plan['first_action']}")
if plan.get('reasoning'):
print(f" Reasoning: {plan['reasoning']}")
if plan.get('commands'):
print(f"\n{Colors.CYAN}Suggested Commands:{Colors.RESET}")
for i, cmd in enumerate(plan['commands'], 1):
print(f" {i}. {Colors.GREEN}{cmd['tool']}{Colors.RESET}: {json.dumps(cmd['args'])}")
print(f" Expect: {cmd.get('expect', 'N/A')}")
print(f"\n{Colors.DIM}Task tree initialized with {len(self.pentest_session.tree.nodes)} nodes{Colors.RESET}")
except Exception as e:
self.print_status(f"Plan generation error: {e}", "warning")
print(f"{Colors.DIM}You can still use the session manually{Colors.RESET}")
input(f"\n{Colors.WHITE}Press Enter to enter session...{Colors.RESET}")
# Enter interactive loop
self._pentest_interactive_loop()
def _resume_pentest_session(self):
"""Resume a saved session."""
sessions = PentestSession.list_sessions()
if not sessions:
self.print_status("No saved sessions found", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
print(f"\n{Colors.BOLD}Saved Sessions:{Colors.RESET}\n")
resumable = []
for i, s in enumerate(sessions, 1):
state_color = Colors.YELLOW if s['state'] == 'paused' else Colors.DIM
print(f" {Colors.GREEN}[{i}]{Colors.RESET} {s['target']} ({state_color}{s['state']}{Colors.RESET})")
stats = s.get('tree_stats', {})
print(f" Steps: {s['steps']} | Findings: {s['findings']} | "
f"Tasks: {stats.get('todo', 0)} todo, {stats.get('completed', 0)} done")
print(f" {Colors.DIM}{s['session_id']}{Colors.RESET}")
resumable.append(s)
print(f"\n {Colors.DIM}[0]{Colors.RESET} Cancel")
choice = input(f"\n{Colors.WHITE} Select session: {Colors.RESET}").strip()
if choice == "0" or not choice:
return
try:
idx = int(choice) - 1
if 0 <= idx < len(resumable):
session_id = resumable[idx]['session_id']
self.pentest_session = PentestSession.load_session(session_id)
self.pentest_session.resume()
if not self._ensure_llm_loaded():
return
self.pentest_pipeline = PentestPipeline(
self.llm, self.pentest_session.target,
self.pentest_session.tree
)
self.print_status(f"Resumed session: {self.pentest_session.target}", "success")
input(f"\n{Colors.WHITE}Press Enter to enter session...{Colors.RESET}")
self._pentest_interactive_loop()
except (ValueError, FileNotFoundError) as e:
self.print_status(f"Error: {e}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _list_pentest_sessions(self):
"""List all saved sessions."""
sessions = PentestSession.list_sessions()
if not sessions:
self.print_status("No saved sessions", "info")
return
print(f"\n{Colors.BOLD}Saved Sessions ({len(sessions)}):{Colors.RESET}\n")
for s in sessions:
state_color = {
'running': Colors.GREEN, 'paused': Colors.YELLOW,
'completed': Colors.CYAN, 'error': Colors.RED,
}.get(s['state'], Colors.DIM)
print(f" {s['target']:25} {state_color}{s['state']:10}{Colors.RESET} "
f"Steps: {s['steps']:3} Findings: {s['findings']}")
print(f" {Colors.DIM}{s['session_id']}{Colors.RESET}")
print()
def _delete_pentest_session(self):
"""Delete a saved session."""
sessions = PentestSession.list_sessions()
if not sessions:
self.print_status("No saved sessions", "info")
return
print(f"\n{Colors.BOLD}Delete Session:{Colors.RESET}\n")
for i, s in enumerate(sessions, 1):
print(f" {Colors.GREEN}[{i}]{Colors.RESET} {s['target']} ({s['state']})")
print(f"\n {Colors.DIM}[0]{Colors.RESET} Cancel")
choice = input(f"\n{Colors.WHITE} Select: {Colors.RESET}").strip()
try:
idx = int(choice) - 1
if 0 <= idx < len(sessions):
sid = sessions[idx]['session_id']
confirm = input(f"{Colors.YELLOW}Delete {sid}? (y/n): {Colors.RESET}").strip().lower()
if confirm == 'y':
session = PentestSession.load_session(sid)
session.delete()
self.print_status("Session deleted", "success")
if self.pentest_session and self.pentest_session.session_id == sid:
self.pentest_session = None
self.pentest_pipeline = None
except (ValueError, FileNotFoundError) as e:
self.print_status(f"Error: {e}", "error")
def _pentest_interactive_loop(self):
"""Interactive pentest session loop (PentestGPT-style)."""
session = self.pentest_session
pipeline = self.pentest_pipeline
settings = self.config.get_pentest_settings()
max_steps = settings['max_pipeline_steps']
while session.state == PentestSessionState.RUNNING:
clear_screen()
stats = session.tree.get_stats()
print(f"{Colors.RED}{Colors.BOLD}[Pentest Session: {session.target}]{Colors.RESET}")
print(f"{Colors.DIM}[Step {session.step_count}/{max_steps}] "
f"[Tree: {stats['total']} nodes, {stats.get('todo', 0)} todo, "
f"{stats.get('completed', 0)} done]{Colors.RESET}")
print(f"{Colors.DIM}{'' * 50}{Colors.RESET}")
print()
# Show next recommended task
next_todo = session.tree.get_next_todo()
if next_todo:
print(f" {Colors.CYAN}Next task:{Colors.RESET} {next_todo.label} (P{next_todo.priority})")
print()
print(f" {Colors.DIM}Commands:{Colors.RESET}")
print(f" {Colors.GREEN}next{Colors.RESET} - Process tool output (paste results)")
print(f" {Colors.GREEN}exec{Colors.RESET} - Auto-execute next recommended action")
print(f" {Colors.GREEN}discuss{Colors.RESET} - Ask a question (doesn't affect tree)")
print(f" {Colors.GREEN}google{Colors.RESET} - Provide external research findings")
print(f" {Colors.GREEN}tree{Colors.RESET} - Display current task tree")
print(f" {Colors.GREEN}status{Colors.RESET} - Show session status")
print(f" {Colors.GREEN}pause{Colors.RESET} - Save session and return to menu")
print(f" {Colors.GREEN}done{Colors.RESET} - Complete session and generate report")
print()
try:
cmd = input(f"{Colors.WHITE} > {Colors.RESET}").strip().lower()
except (EOFError, KeyboardInterrupt):
cmd = "pause"
if cmd == "next":
self._handle_next(pipeline, session)
elif cmd == "exec":
self._handle_exec(pipeline, session)
elif cmd == "discuss":
self._handle_discuss(pipeline)
elif cmd == "google":
self._handle_google(pipeline, session)
elif cmd == "tree":
print(f"\n{session.tree.render_text()}")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
elif cmd == "status":
self._handle_status(session)
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
elif cmd == "pause":
session.pause()
self.print_status("Session paused and saved", "success")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
break
elif cmd == "done":
self._handle_done(session)
break
# Check step limit
if session.step_count >= max_steps:
self.print_status(f"Step limit ({max_steps}) reached. Session paused.", "warning")
session.pause()
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
break
def _handle_next(self, pipeline, session):
"""Handle 'next' command - process pasted tool output."""
print(f"\n{Colors.CYAN}Paste tool output below (empty line to finish):{Colors.RESET}")
lines = []
while True:
try:
line = input()
if line == "":
if lines:
break
else:
lines.append(line)
except EOFError:
break
if not lines:
return
raw_output = '\n'.join(lines)
source_type = detect_source_type(raw_output)
self.print_status(f"Detected source: {source_type}", "info")
self.print_status("Processing through pipeline...", "info")
result = pipeline.process_output(raw_output, source_type)
# Log to session
session.log_event('tool_output', {
'source_type': source_type,
'parsed_summary': result['parsed'].get('summary', ''),
})
session.log_pipeline_result(
result['parsed'].get('summary', ''),
result['reasoning'].get('reasoning', ''),
result.get('commands', []),
)
# Add findings to session
for finding in result['parsed'].get('findings', []):
if '[VULN]' in finding or '[CRED]' in finding:
severity = 'high' if '[VULN]' in finding else 'critical'
session.add_finding(finding, finding, severity)
# Display results
print(f"\n{Colors.GREEN}--- Parsed ---{Colors.RESET}")
print(f" Summary: {result['parsed'].get('summary', 'N/A')}")
if result['parsed'].get('findings'):
print(f" Findings:")
for f in result['parsed']['findings']:
color = Colors.RED if '[VULN]' in f else Colors.YELLOW if '[CRED]' in f else Colors.WHITE
print(f" {color}- {f}{Colors.RESET}")
print(f"\n{Colors.GREEN}--- Reasoning ---{Colors.RESET}")
print(f" Next task: {result.get('next_task', 'N/A')}")
print(f" Reasoning: {result['reasoning'].get('reasoning', 'N/A')}")
if result['reasoning'].get('tree_updates'):
print(f" Tree updates: {len(result['reasoning']['tree_updates'])}")
if result.get('commands'):
print(f"\n{Colors.GREEN}--- Suggested Commands ---{Colors.RESET}")
for i, cmd in enumerate(result['commands'], 1):
print(f" {i}. {Colors.CYAN}{cmd['tool']}{Colors.RESET}: {json.dumps(cmd['args'])}")
print(f" Expect: {cmd.get('expect', 'N/A')}")
if result.get('fallback'):
print(f"\n {Colors.DIM}Fallback: {result['fallback']}{Colors.RESET}")
session.save()
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _handle_exec(self, pipeline, session):
"""Handle 'exec' command - auto-execute next recommended action."""
next_todo = session.tree.get_next_todo()
if not next_todo:
self.print_status("No pending tasks in the tree", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
self.print_status(f"Generating commands for: {next_todo.label}", "info")
# Generate commands
context = ""
if next_todo.details:
context = next_todo.details
gen_result = pipeline.generator.generate(
next_todo.label, session.target, context=context
)
if not gen_result.get('commands'):
self.print_status("No executable commands generated", "warning")
if gen_result.get('raw_response'):
print(f"\n{Colors.DIM}{gen_result['raw_response']}{Colors.RESET}")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
# Mark task as in progress
session.tree.update_node(next_todo.id, status=NodeStatus.IN_PROGRESS)
# Execute with confirmation
print(f"\n{Colors.BOLD}Commands to execute:{Colors.RESET}")
all_output = []
for i, cmd in enumerate(gen_result['commands'], 1):
print(f"\n {i}. {Colors.CYAN}{cmd['tool']}{Colors.RESET}: {json.dumps(cmd['args'])}")
print(f" Expect: {cmd.get('expect', 'N/A')}")
choice = input(f" {Colors.WHITE}Execute? (y/n/skip): {Colors.RESET}").strip().lower()
if choice == 'n':
break
elif choice == 'skip':
continue
# Execute the command
output = self._execute_pipeline_action(cmd)
if output:
all_output.append(output)
print(f"\n{Colors.DIM}Output:{Colors.RESET}")
# Show truncated output
display = output[:500]
if len(output) > 500:
display += f"\n... ({len(output)} chars total)"
print(display)
# Process collected output through pipeline
if all_output:
combined = '\n'.join(all_output)
self.print_status("Processing results through pipeline...", "info")
result = pipeline.process_output(combined)
session.log_pipeline_result(
result['parsed'].get('summary', ''),
result['reasoning'].get('reasoning', ''),
result.get('commands', []),
)
print(f"\n{Colors.GREEN}Next task: {result.get('next_task', 'N/A')}{Colors.RESET}")
print(f"Reasoning: {result['reasoning'].get('reasoning', 'N/A')}")
session.save()
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _execute_pipeline_action(self, action: dict) -> Optional[str]:
"""Execute a single pipeline action. Returns output string or None."""
tool_name = action.get('tool', '')
args = action.get('args', {})
try:
if tool_name == 'shell':
command = args.get('command', '')
timeout = args.get('timeout', 30)
success, output = self.run_cmd(command, timeout=timeout)
return output
elif tool_name.startswith('msf_'):
from core.tools import get_tool_registry
registry = get_tool_registry()
result = registry.execute(tool_name, **args)
if isinstance(result, dict):
return result.get('result', str(result))
return str(result)
else:
self.print_status(f"Unknown tool: {tool_name}", "warning")
return None
except Exception as e:
self.print_status(f"Execution error: {e}", "error")
return f"Error: {e}"
def _handle_discuss(self, pipeline):
"""Handle 'discuss' command - ad-hoc question."""
question = input(f"\n{Colors.WHITE}Question: {Colors.RESET}").strip()
if not question:
return
self.print_status("Thinking...", "info")
response = pipeline.discuss(question)
print(f"\n{Colors.GREEN}{response}{Colors.RESET}")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _handle_google(self, pipeline, session):
"""Handle 'google' command - inject external research."""
print(f"\n{Colors.CYAN}Paste research findings below (empty line to finish):{Colors.RESET}")
lines = []
while True:
try:
line = input()
if line == "" and lines:
break
lines.append(line)
except EOFError:
break
if not lines:
return
info = '\n'.join(lines)
self.print_status("Injecting information into pipeline...", "info")
result = pipeline.inject_information(info, source="research")
session.log_event('research_injected', {'info': info[:200]})
session.log_pipeline_result(
result['parsed'].get('summary', ''),
result['reasoning'].get('reasoning', ''),
result.get('commands', []),
)
print(f"\n{Colors.GREEN}Updated reasoning:{Colors.RESET}")
print(f" Next task: {result.get('next_task', 'N/A')}")
print(f" Reasoning: {result['reasoning'].get('reasoning', 'N/A')}")
session.save()
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _handle_status(self, session):
"""Display current session status."""
stats = session.tree.get_stats()
print(f"\n{Colors.BOLD}Session Status{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 50}{Colors.RESET}")
print(f" Target: {session.target}")
print(f" Session: {session.session_id}")
print(f" State: {session.state.value}")
print(f" Steps: {session.step_count}")
print(f" Findings: {len(session.findings)}")
print(f"\n Task Tree:")
print(f" Total: {stats['total']}")
print(f" Todo: {stats.get('todo', 0)}")
print(f" Active: {stats.get('in_progress', 0)}")
print(f" Done: {stats.get('completed', 0)}")
print(f" N/A: {stats.get('not_applicable', 0)}")
if session.findings:
print(f"\n {Colors.YELLOW}Key Findings:{Colors.RESET}")
for f in session.findings[-5:]:
sev = f.get('severity', 'medium').upper()
print(f" [{sev}] {f['title']}")
def _handle_done(self, session):
"""Handle 'done' command - complete session and generate report."""
summary = input(f"\n{Colors.WHITE}Session summary (optional): {Colors.RESET}").strip()
session.complete(summary)
report = session.export_report()
print(f"\n{report}")
# Save report to file
from core.paths import get_reports_dir
report_path = get_reports_dir()
report_file = report_path / f"pentest_{session.session_id}.txt"
with open(report_file, 'w') as f:
f.write(report)
self.print_status(f"Report saved: {report_file}", "success")
self.pentest_session = None
self.pentest_pipeline = None
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
# ==================== MAIN MENU ====================
def show_menu(self):
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} Agent Hal{Colors.RESET}")
print(f"{Colors.DIM} AI-powered security automation{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Status line
llm_status = f"{Colors.GREEN}Ready{Colors.RESET}" if (self.llm and self.llm.is_loaded) else f"{Colors.DIM}Not loaded{Colors.RESET}"
msf_status = f"{Colors.GREEN}Connected{Colors.RESET}" if self.msf_connected else f"{Colors.DIM}Offline{Colors.RESET}"
print(f" {Colors.DIM}LLM: {llm_status} | MSF: {msf_status}{Colors.RESET}")
print()
print(f" {Colors.RED}Defense{Colors.RESET}")
print(f" {Colors.GREEN}[1]{Colors.RESET} MITM Detection")
print()
print(f" {Colors.RED}Offense{Colors.RESET}")
print(f" {Colors.GREEN}[2]{Colors.RESET} MSF Automation (AI)")
print(f" {Colors.GREEN}[3]{Colors.RESET} Pentest Pipeline (AI)")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
def run(self):
while True:
self.show_menu()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0":
break
elif choice == "1":
self.mitm_detection_menu()
elif choice == "2":
self.msf_automation_menu()
elif choice == "3":
self.pentest_pipeline_menu()
except (EOFError, KeyboardInterrupt):
break
def run():
AgentHal().run()
if __name__ == "__main__":
run()