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

1528 lines
60 KiB
Python

"""
AUTARCH Metasploit Module
Enhanced interface for Metasploit Framework with module browser.
Provides easy access to MSF modules, exploits, and sessions.
Uses the centralized MSF interface from core/msf_interface.py.
Integrates with msf_terms.py and msf_modules.py for descriptions.
"""
import sys
import os
import re
import json
import time
import socket
from pathlib import Path
from typing import Dict, List, Optional, Any
# Module metadata
DESCRIPTION = "Metasploit Framework interface"
AUTHOR = "darkHal"
VERSION = "2.0"
CATEGORY = "offense"
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from core.msf_interface import get_msf_interface
from core.banner import Colors, clear_screen, display_banner
from core.msf_terms import get_setting_info, get_setting_prompt, format_setting_help, validate_setting_value
from core.msf_modules import (
get_module_info as get_library_module_info,
get_module_description,
search_modules as library_search_modules,
get_modules_by_type,
format_module_help,
MSF_MODULES
)
class MSFMenu:
"""Enhanced Metasploit menu interface with module browser."""
# Module categories for browsing
MODULE_CATEGORIES = {
'scanners': {
'name': 'Scanners',
'description': 'Network and vulnerability scanners',
'types': ['auxiliary/scanner'],
'color': Colors.CYAN
},
'exploits': {
'name': 'Exploits',
'description': 'Remote and local exploits',
'types': ['exploit'],
'color': Colors.RED
},
'post': {
'name': 'Post-Exploitation',
'description': 'Post-exploitation modules',
'types': ['post'],
'color': Colors.MAGENTA
},
'payloads': {
'name': 'Payloads',
'description': 'Payload generators',
'types': ['payload'],
'color': Colors.YELLOW
},
'auxiliary': {
'name': 'Auxiliary',
'description': 'Other auxiliary modules',
'types': ['auxiliary'],
'color': Colors.GREEN
}
}
def __init__(self):
self.msf = get_msf_interface()
self.current_module = None
self.current_module_type = None
self.module_options = {}
# Global target settings - persist across module selections
self.global_settings = {
'RHOSTS': '',
'LHOST': '',
'LPORT': '4444',
}
def print_status(self, message: str, status: str = "info"):
"""Print a status message."""
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 wrap_text(self, text: str, width: int = 60, indent: str = " ") -> str:
"""Word-wrap text with indent for subsequent lines."""
words = text.split()
lines = []
current_line = ""
for word in words:
if len(current_line) + len(word) + 1 <= width:
current_line += (" " if current_line else "") + word
else:
if current_line:
lines.append(current_line)
current_line = word
if current_line:
lines.append(current_line)
return f"\n{indent}".join(lines)
def ensure_connected(self) -> bool:
"""Ensure connected to MSF RPC."""
connected, msg = self.msf.ensure_connected()
if not connected:
print(f"\n{Colors.YELLOW}{msg}{Colors.RESET}")
print(f"{Colors.DIM}Make sure msfrpcd is running: msfrpcd -P <password> -S{Colors.RESET}")
return connected
def resolve_hostname(self, hostname: str) -> Optional[str]:
"""Resolve hostname to IP address."""
try:
# Check if it's already an IP
socket.inet_aton(hostname)
return hostname
except socket.error:
pass
# Try to resolve
try:
ip = socket.gethostbyname(hostname)
return ip
except socket.gaierror:
return None
def get_local_ip(self) -> str:
"""Get local IP address for LHOST."""
try:
# Connect to external address to get local IP
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 Exception:
return "127.0.0.1"
# =========================================================================
# MAIN MENU
# =========================================================================
def show_main_menu(self):
"""Display MSF main menu."""
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} Metasploit Framework{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
# Connection status
if self.msf.is_connected:
print(f" {Colors.GREEN}Status: Connected{Colors.RESET}")
else:
print(f" {Colors.YELLOW}Status: Disconnected{Colors.RESET}")
# Show current global settings
if any(self.global_settings.values()):
print()
if self.global_settings['RHOSTS']:
print(f" {Colors.CYAN}Target:{Colors.RESET} {self.global_settings['RHOSTS']}")
if self.global_settings['LHOST']:
print(f" {Colors.CYAN}LHOST:{Colors.RESET} {self.global_settings['LHOST']}")
if self.global_settings['LPORT'] and self.global_settings['LPORT'] != '4444':
print(f" {Colors.CYAN}LPORT:{Colors.RESET} {self.global_settings['LPORT']}")
# Current module
if self.current_module:
print(f" {Colors.YELLOW}Module:{Colors.RESET} {self.current_module_type}/{self.current_module}")
print()
print(f" {Colors.RED}[1]{Colors.RESET} Set Target {Colors.DIM}- Configure target & listener settings{Colors.RESET}")
print(f" {Colors.RED}[2]{Colors.RESET} Module Browser {Colors.DIM}- Browse modules by category{Colors.RESET}")
print(f" {Colors.RED}[3]{Colors.RESET} Search Modules {Colors.DIM}- Search all modules{Colors.RESET}")
print()
print(f" {Colors.RED}[4]{Colors.RESET} Current Module {Colors.DIM}- View/configure selected module{Colors.RESET}")
print(f" {Colors.RED}[5]{Colors.RESET} Run Module {Colors.DIM}- Execute current module{Colors.RESET}")
print()
print(f" {Colors.RED}[6]{Colors.RESET} Sessions {Colors.DIM}- View and interact with sessions{Colors.RESET}")
print(f" {Colors.RED}[7]{Colors.RESET} Jobs {Colors.DIM}- View running background jobs{Colors.RESET}")
print()
print(f" {Colors.RED}[8]{Colors.RESET} MSF Console {Colors.DIM}- Direct console access{Colors.RESET}")
print(f" {Colors.RED}[9]{Colors.RESET} Quick Scan {Colors.DIM}- Common scanners{Colors.RESET}")
print(f" {Colors.RED}[E]{Colors.RESET} Exploit Suggester {Colors.DIM}- Suggest exploits from vuln data{Colors.RESET}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back to Main Menu")
print()
# =========================================================================
# GLOBAL TARGET SETTINGS
# =========================================================================
def show_target_settings(self):
"""Configure global target settings."""
while True:
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} Target Configuration{Colors.RESET}")
print(f"{Colors.DIM} Set target and listener options before selecting modules{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Display current settings with term bank descriptions
rhosts_info = get_setting_info('RHOSTS')
lhost_info = get_setting_info('LHOST')
lport_info = get_setting_info('LPORT')
rhosts_val = self.global_settings['RHOSTS'] or f"{Colors.YELLOW}(not set){Colors.RESET}"
lhost_val = self.global_settings['LHOST'] or f"{Colors.YELLOW}(not set){Colors.RESET}"
lport_val = self.global_settings['LPORT'] or '4444'
print(f" {Colors.RED}[1]{Colors.RESET} RHOSTS = {rhosts_val}")
print(f" {Colors.DIM}{self.wrap_text(rhosts_info['description'])}{Colors.RESET}")
print()
print(f" {Colors.RED}[2]{Colors.RESET} LHOST = {lhost_val}")
print(f" {Colors.DIM}{self.wrap_text(lhost_info['description'])}{Colors.RESET}")
print()
print(f" {Colors.RED}[3]{Colors.RESET} LPORT = {lport_val}")
print(f" {Colors.DIM}{self.wrap_text(lport_info['description'])}{Colors.RESET}")
print()
print(f" {Colors.GREEN}[A]{Colors.RESET} Auto-detect LHOST")
print(f" {Colors.GREEN}[R]{Colors.RESET} Resolve hostname to IP")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower()
if choice == '0' or not choice:
break
elif choice == '1':
self._set_rhosts()
elif choice == '2':
self._set_lhost()
elif choice == '3':
self._set_lport()
elif choice == 'a':
self._auto_detect_lhost()
elif choice == 'r':
self._resolve_hostname()
except (EOFError, KeyboardInterrupt):
break
def _set_rhosts(self):
"""Set RHOSTS with validation and domain resolution."""
print()
print(format_setting_help('RHOSTS'))
print()
current = self.global_settings['RHOSTS']
prompt = f"Target [{current}]: " if current else "Target: "
value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip()
if not value and current:
return # Keep current
if value:
# Check if it's a hostname that needs resolution
if not any(c.isdigit() for c in value.split('/')[0].split('-')[0]):
# Looks like a hostname
print(f"{Colors.CYAN}[*] Resolving {value}...{Colors.RESET}")
ip = self.resolve_hostname(value)
if ip:
print(f"{Colors.GREEN}[+] Resolved to {ip}{Colors.RESET}")
use_ip = input(f"{Colors.WHITE}Use resolved IP? (y/n) [{Colors.GREEN}y{Colors.WHITE}]: {Colors.RESET}").strip().lower()
if use_ip != 'n':
value = ip
else:
print(f"{Colors.YELLOW}[!] Could not resolve hostname{Colors.RESET}")
# Validate
valid, msg = validate_setting_value('RHOSTS', value)
if valid:
self.global_settings['RHOSTS'] = value
self.print_status(f"RHOSTS => {value}", "success")
else:
self.print_status(msg, "warning")
self.global_settings['RHOSTS'] = value # Set anyway, user might know better
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _set_lhost(self):
"""Set LHOST."""
print()
print(format_setting_help('LHOST'))
print()
current = self.global_settings['LHOST']
auto_ip = self.get_local_ip()
print(f" {Colors.DIM}Detected local IP: {auto_ip}{Colors.RESET}")
prompt = f"LHOST [{current or auto_ip}]: "
value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip()
if not value:
value = current or auto_ip
if value:
self.global_settings['LHOST'] = value
self.print_status(f"LHOST => {value}", "success")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _set_lport(self):
"""Set LPORT."""
print()
print(format_setting_help('LPORT'))
print()
current = self.global_settings['LPORT'] or '4444'
prompt = f"LPORT [{current}]: "
value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip()
if not value:
value = current
# Validate port
try:
port = int(value)
if 1 <= port <= 65535:
self.global_settings['LPORT'] = value
self.print_status(f"LPORT => {value}", "success")
else:
self.print_status("Port must be between 1 and 65535", "warning")
except ValueError:
self.print_status("Invalid port number", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _auto_detect_lhost(self):
"""Auto-detect LHOST."""
ip = self.get_local_ip()
self.global_settings['LHOST'] = ip
self.print_status(f"LHOST => {ip} (auto-detected)", "success")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _resolve_hostname(self):
"""Resolve a hostname to IP."""
print()
hostname = input(f"{Colors.WHITE}Hostname to resolve: {Colors.RESET}").strip()
if hostname:
print(f"{Colors.CYAN}[*] Resolving {hostname}...{Colors.RESET}")
ip = self.resolve_hostname(hostname)
if ip:
print(f"{Colors.GREEN}[+] {hostname} => {ip}{Colors.RESET}")
use_as_target = input(f"{Colors.WHITE}Use as RHOSTS? (y/n): {Colors.RESET}").strip().lower()
if use_as_target == 'y':
self.global_settings['RHOSTS'] = ip
self.print_status(f"RHOSTS => {ip}", "success")
else:
self.print_status(f"Could not resolve {hostname}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
# =========================================================================
# MODULE BROWSER
# =========================================================================
def show_module_browser(self):
"""Browse modules by category."""
while True:
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} Module Browser{Colors.RESET}")
print(f"{Colors.DIM} Browse Metasploit modules by category{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Show categories
for i, (cat_id, cat_info) in enumerate(self.MODULE_CATEGORIES.items(), 1):
color = cat_info['color']
print(f" {color}[{i}]{Colors.RESET} {cat_info['name']}")
print(f" {Colors.DIM}{cat_info['description']}{Colors.RESET}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select category: {Colors.RESET}").strip()
if choice == '0' or not choice:
break
try:
idx = int(choice) - 1
cat_ids = list(self.MODULE_CATEGORIES.keys())
if 0 <= idx < len(cat_ids):
self._browse_category(cat_ids[idx])
except ValueError:
pass
except (EOFError, KeyboardInterrupt):
break
def _browse_category(self, category: str):
"""Browse modules in a category with pagination."""
cat_info = self.MODULE_CATEGORIES.get(category)
if not cat_info:
return
# Get modules from library that match this category
modules = []
for path, info in MSF_MODULES.items():
for type_prefix in cat_info['types']:
if path.startswith(type_prefix):
modules.append({'path': path, **info})
break
# Also try to get from MSF if connected
if self.msf.is_connected:
for type_prefix in cat_info['types']:
if '/' in type_prefix:
# e.g., auxiliary/scanner
mtype = type_prefix.split('/')[0]
else:
mtype = type_prefix
msf_modules = self.msf.list_modules(mtype)
if msf_modules:
for mod_path in msf_modules[:50]: # Limit to avoid overwhelming
if mod_path not in [m['path'] for m in modules]:
# Add basic info for modules not in library
modules.append({
'path': mod_path,
'name': mod_path.split('/')[-1].replace('_', ' ').title(),
'description': 'Module from Metasploit (use "info" for details)',
'tags': []
})
if not modules:
self.print_status(f"No modules found in {cat_info['name']}", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
# Pagination
page_size = 20
page = 0
total_pages = (len(modules) + page_size - 1) // page_size
while True:
clear_screen()
display_banner()
print(f"{cat_info['color']}{Colors.BOLD} {cat_info['name']}{Colors.RESET}")
print(f"{Colors.DIM} Page {page + 1} of {total_pages} ({len(modules)} modules){Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Display modules in 2 columns
start_idx = page * page_size
end_idx = min(start_idx + page_size, len(modules))
page_modules = modules[start_idx:end_idx]
# Split into two columns
half = (len(page_modules) + 1) // 2
col1 = page_modules[:half]
col2 = page_modules[half:]
for i in range(max(len(col1), len(col2))):
line = ""
# Column 1
if i < len(col1):
num = start_idx + i + 1
mod = col1[i]
name = mod.get('name', mod['path'].split('/')[-1])
if len(name) > 22:
name = name[:19] + "..."
line += f" {cat_info['color']}[{num:2}]{Colors.RESET} {name:22}"
else:
line += " " * 30
# Column 2
if i < len(col2):
num = start_idx + half + i + 1
mod = col2[i]
name = mod.get('name', mod['path'].split('/')[-1])
if len(name) > 22:
name = name[:19] + "..."
line += f" {cat_info['color']}[{num:2}]{Colors.RESET} {name:22}"
print(line)
print()
print(f" {Colors.DIM}[N]{Colors.RESET} Next page {Colors.DIM}[P]{Colors.RESET} Previous {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select module: {Colors.RESET}").strip().lower()
if choice == '0' or not choice:
break
elif choice == 'n' and page < total_pages - 1:
page += 1
elif choice == 'p' and page > 0:
page -= 1
else:
try:
idx = int(choice) - 1
if 0 <= idx < len(modules):
self._show_module_details(modules[idx])
except ValueError:
pass
except (EOFError, KeyboardInterrupt):
break
def _show_module_details(self, module_info: Dict):
"""Show module details and offer to use it."""
clear_screen()
display_banner()
path = module_info['path']
name = module_info.get('name', path.split('/')[-1])
print(f"{Colors.RED}{Colors.BOLD} {name}{Colors.RESET}")
print(f"{Colors.DIM} {path}{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Try to get full help from library
help_text = format_module_help(path)
if help_text:
print(help_text)
else:
# Fall back to basic info
desc = module_info.get('description', 'No description available')
print(f" {Colors.CYAN}Description:{Colors.RESET}")
# Word wrap description
words = desc.split()
line = " "
for word in words:
if len(line) + len(word) > 70:
print(line)
line = " "
line += word + " "
if line.strip():
print(line)
print()
if 'author' in module_info:
authors = module_info['author']
if isinstance(authors, list):
authors = ', '.join(authors)
print(f" {Colors.CYAN}Author:{Colors.RESET} {authors}")
if 'cve' in module_info and module_info['cve']:
print(f" {Colors.CYAN}CVE:{Colors.RESET} {module_info['cve']}")
if 'reliability' in module_info:
print(f" {Colors.CYAN}Reliability:{Colors.RESET} {module_info['reliability']}")
if 'notes' in module_info:
print()
print(f" {Colors.YELLOW}Notes:{Colors.RESET} {module_info['notes']}")
print()
print(f" {Colors.GREEN}[U]{Colors.RESET} Use this module")
print(f" {Colors.CYAN}[I]{Colors.RESET} Get info from MSF")
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower()
if choice == 'u':
self._select_module(path)
elif choice == 'i':
self._show_msf_info(path)
except (EOFError, KeyboardInterrupt):
pass
def _select_module(self, module_path: str):
"""Select a module and prepare it for execution."""
if not self.ensure_connected():
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
# Parse module type and name
parts = module_path.split('/', 1)
mtype = parts[0]
mname = parts[1] if len(parts) > 1 else module_path
self.print_status(f"Loading {module_path}...", "info")
# Get module info and options from MSF
info = self.msf.get_module_info(module_path)
options = self.msf.get_module_options(module_path)
if not options:
self.print_status(f"Failed to load module: {self.msf.last_error}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
self.current_module = mname
self.current_module_type = mtype
self.module_options = {}
# Set defaults from module
for opt_name, opt_info in options.items():
if isinstance(opt_info, dict):
default = opt_info.get('default')
if default is not None and default != '':
self.module_options[opt_name] = default
# Apply global settings
if self.global_settings['RHOSTS'] and 'RHOSTS' in options:
self.module_options['RHOSTS'] = self.global_settings['RHOSTS']
if self.global_settings['RHOSTS'] and 'RHOST' in options:
self.module_options['RHOST'] = self.global_settings['RHOSTS']
if self.global_settings['LHOST'] and 'LHOST' in options:
self.module_options['LHOST'] = self.global_settings['LHOST']
if self.global_settings['LPORT'] and 'LPORT' in options:
self.module_options['LPORT'] = self.global_settings['LPORT']
self.print_status(f"Module loaded: {mtype}/{mname}", "success")
# Show what was auto-filled
auto_filled = []
if 'RHOSTS' in self.module_options or 'RHOST' in self.module_options:
target = self.module_options.get('RHOSTS') or self.module_options.get('RHOST')
if target:
auto_filled.append(f"Target: {target}")
if 'LHOST' in self.module_options and self.module_options['LHOST']:
auto_filled.append(f"LHOST: {self.module_options['LHOST']}")
if auto_filled:
print(f"{Colors.DIM} Auto-filled: {', '.join(auto_filled)}{Colors.RESET}")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _show_msf_info(self, module_path: str):
"""Get and display module info from MSF."""
if not self.ensure_connected():
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
self.print_status(f"Fetching info for {module_path}...", "info")
info = self.msf.get_module_info(module_path)
if not info:
self.print_status(f"Failed to get info: {self.msf.last_error}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} Module Info (from MSF){Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Display info fields
fields = ['name', 'fullname', 'description', 'author', 'references', 'rank', 'platform', 'arch']
for field in fields:
if field in info and info[field]:
value = info[field]
if isinstance(value, list):
if field == 'references':
print(f" {Colors.CYAN}{field}:{Colors.RESET}")
for ref in value[:5]:
if isinstance(ref, (list, tuple)) and len(ref) >= 2:
print(f" - {ref[0]}: {ref[1]}")
else:
print(f" - {ref}")
else:
value = ', '.join(str(v) for v in value[:5])
print(f" {Colors.CYAN}{field}:{Colors.RESET} {value}")
elif field == 'description':
print(f" {Colors.CYAN}{field}:{Colors.RESET}")
# Word wrap
words = str(value).split()
line = " "
for word in words:
if len(line) + len(word) > 70:
print(line)
line = " "
line += word + " "
if line.strip():
print(line)
else:
print(f" {Colors.CYAN}{field}:{Colors.RESET} {value}")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
# =========================================================================
# SEARCH MODULES
# =========================================================================
def search_modules(self):
"""Search for MSF modules."""
if not self.ensure_connected():
return
print(f"\n{Colors.BOLD}Search Metasploit Modules{Colors.RESET}")
print(f"{Colors.DIM}Examples: 'smb', 'apache', 'ssh', 'cve:2021', 'eternalblue'{Colors.RESET}\n")
query = input(f"{Colors.WHITE}Search: {Colors.RESET}").strip()
if not query:
return
self.print_status(f"Searching for '{query}'...", "info")
# Search both library and MSF
library_results = library_search_modules(query)
msf_results = self.msf.search_modules(query)
# Combine results, preferring library entries
combined = {}
for mod in library_results:
combined[mod['path']] = mod
if msf_results:
for mod in msf_results:
if isinstance(mod, dict):
fullname = mod.get('fullname', '')
if fullname and fullname not in combined:
combined[fullname] = {
'path': fullname,
'name': mod.get('name', fullname.split('/')[-1]),
'description': 'Module from Metasploit',
'rank': mod.get('rank', '')
}
elif isinstance(mod, str) and mod not in combined:
combined[mod] = {
'path': mod,
'name': mod.split('/')[-1],
'description': 'Module from Metasploit'
}
results = list(combined.values())
if not results:
self.print_status("No modules found", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
# Display with pagination
page_size = 15
page = 0
total_pages = (len(results) + page_size - 1) // page_size
while True:
clear_screen()
display_banner()
print(f"{Colors.GREEN}{Colors.BOLD} Search Results: '{query}'{Colors.RESET}")
print(f"{Colors.DIM} Page {page + 1} of {total_pages} ({len(results)} found){Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
start_idx = page * page_size
end_idx = min(start_idx + page_size, len(results))
page_results = results[start_idx:end_idx]
for i, mod in enumerate(page_results, start_idx + 1):
name = mod.get('name', mod['path'].split('/')[-1])
path = mod['path']
if len(name) > 30:
name = name[:27] + "..."
print(f" {Colors.RED}[{i:2}]{Colors.RESET} {name}")
print(f" {Colors.DIM}{path}{Colors.RESET}")
print()
print(f" {Colors.DIM}[N]{Colors.RESET} Next {Colors.DIM}[P]{Colors.RESET} Previous {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower()
if choice == '0' or not choice:
break
elif choice == 'n' and page < total_pages - 1:
page += 1
elif choice == 'p' and page > 0:
page -= 1
else:
try:
idx = int(choice) - 1
if 0 <= idx < len(results):
self._show_module_details(results[idx])
except ValueError:
pass
except (EOFError, KeyboardInterrupt):
break
# =========================================================================
# CURRENT MODULE MANAGEMENT
# =========================================================================
def show_current_module(self):
"""Show and configure current module options."""
if not self.current_module:
self.print_status("No module selected. Use Module Browser or Search first.", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
if not self.ensure_connected():
return
full_path = f"{self.current_module_type}/{self.current_module}"
options = self.msf.get_module_options(full_path)
if not options:
self.print_status(f"Failed to get options: {self.msf.last_error}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
while True:
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} {full_path}{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Separate required and optional
required = []
optional = []
for name, info in options.items():
if isinstance(info, dict):
is_required = info.get('required', False)
current_val = self.module_options.get(name, info.get('default', ''))
desc = info.get('desc', '')[:35]
entry = (name, current_val, desc, is_required)
if is_required:
required.append(entry)
else:
optional.append(entry)
# Show required first
if required:
print(f" {Colors.RED}Required Options:{Colors.RESET}")
for i, (name, val, desc, _) in enumerate(required, 1):
val_display = str(val) if val else f"{Colors.YELLOW}(not set){Colors.RESET}"
print(f" {Colors.CYAN}[{i}]{Colors.RESET} {name:18} = {val_display}")
# Get help from term bank
term_info = get_setting_info(name)
if term_info:
print(f" {Colors.DIM}{self.wrap_text(term_info['description'], width=55, indent=' ')}{Colors.RESET}")
else:
print(f" {Colors.DIM}{self.wrap_text(desc, width=55, indent=' ')}{Colors.RESET}")
print()
# Show optional (just first 8)
if optional:
print(f" {Colors.DIM}Optional (first 8):{Colors.RESET}")
for name, val, desc, _ in optional[:8]:
val_display = str(val)[:20] if val else ""
print(f" {Colors.DIM}{name:18} = {val_display}{Colors.RESET}")
print()
print(f" {Colors.GREEN}[S]{Colors.RESET} Set option")
print(f" {Colors.GREEN}[R]{Colors.RESET} Run module")
print(f" {Colors.CYAN}[A]{Colors.RESET} Show all options")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower()
if choice == '0' or not choice:
break
elif choice == 's':
self.set_option()
elif choice == 'r':
self.run_module()
break
elif choice == 'a':
self._show_all_options(options)
elif choice.isdigit():
idx = int(choice) - 1
if 0 <= idx < len(required):
self._set_specific_option(required[idx][0], options)
except (EOFError, KeyboardInterrupt):
break
def _show_all_options(self, options: Dict):
"""Show all module options."""
clear_screen()
display_banner()
full_path = f"{self.current_module_type}/{self.current_module}"
print(f"{Colors.BOLD}All Options for {full_path}{Colors.RESET}")
print(f"{Colors.DIM}{'' * 60}{Colors.RESET}\n")
for name, info in sorted(options.items()):
if isinstance(info, dict):
is_required = info.get('required', False)
current_val = self.module_options.get(name, info.get('default', ''))
desc = info.get('desc', '')
req_marker = f"{Colors.RED}*{Colors.RESET}" if is_required else " "
val_display = str(current_val)[:30] if current_val else f"{Colors.DIM}(empty){Colors.RESET}"
print(f" {req_marker} {Colors.CYAN}{name:20}{Colors.RESET} = {val_display}")
if desc:
print(f" {Colors.DIM}{self.wrap_text(desc, width=55, indent=' ')}{Colors.RESET}")
print(f"\n{Colors.DIM}* = required{Colors.RESET}")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _set_specific_option(self, opt_name: str, options: Dict):
"""Set a specific option with term bank help."""
print()
# Show help from term bank or module
term_info = get_setting_info(opt_name)
if term_info:
print(format_setting_help(opt_name))
elif opt_name in options:
opt_info = options[opt_name]
desc = opt_info.get('desc', 'No description')
print(f" {Colors.CYAN}{opt_name}:{Colors.RESET} {desc}")
print()
current = self.module_options.get(opt_name, '')
prompt = f"{opt_name} [{current}]: " if current else f"{opt_name}: "
value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip()
if value or not current:
self.module_options[opt_name] = value
self.print_status(f"{opt_name} => {value}", "success")
else:
self.print_status(f"{opt_name} unchanged", "info")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def set_option(self):
"""Set a module option."""
if not self.current_module:
self.print_status("No module selected.", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
print(f"\n{Colors.BOLD}Set Option{Colors.RESET}")
print(f"{Colors.DIM}Common: RHOSTS, RPORT, LHOST, LPORT, PAYLOAD{Colors.RESET}\n")
opt_name = input(f"{Colors.WHITE}Option name: {Colors.RESET}").strip().upper()
if not opt_name:
return
# Show help
term_info = get_setting_info(opt_name)
if term_info:
print()
print(format_setting_help(opt_name))
print()
current = self.module_options.get(opt_name, '')
prompt = f"{Colors.WHITE}Value [{current}]: {Colors.RESET}" if current else f"{Colors.WHITE}Value: {Colors.RESET}"
opt_value = input(prompt).strip()
if opt_value or not current:
self.module_options[opt_name] = opt_value
self.print_status(f"{opt_name} => {opt_value}", "success")
else:
self.print_status(f"{opt_name} unchanged", "info")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def run_module(self):
"""Execute the current module."""
if not self.current_module:
self.print_status("No module selected.", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
if not self.ensure_connected():
return
full_path = f"{self.current_module_type}/{self.current_module}"
print(f"\n{Colors.BOLD}Run Module: {full_path}{Colors.RESET}")
print(f"\n{Colors.CYAN}Options:{Colors.RESET}")
for k, v in self.module_options.items():
if v:
print(f" {k} = {v}")
confirm = input(f"\n{Colors.YELLOW}Execute? (y/n): {Colors.RESET}").strip().lower()
if confirm != 'y':
return
self.print_status("Executing module...", "info")
# Use job execution for exploits, console execution for auxiliary/scanners
if self.current_module_type in ['exploit', 'post']:
success, job_id, error = self.msf.execute_module_job(full_path, self.module_options)
if success:
self.print_status(f"Module running as Job {job_id}", "success")
else:
self.print_status(f"Execution failed: {error}", "error")
else:
result = self.msf.run_module(full_path, self.module_options, timeout=120)
self.msf.print_result(result, verbose=True)
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
# =========================================================================
# SESSIONS AND JOBS
# =========================================================================
def show_sessions(self):
"""Show active sessions."""
if not self.ensure_connected():
return
sessions = self.msf.list_sessions()
print(f"\n{Colors.BOLD}Active Sessions{Colors.RESET}")
print(f"{Colors.DIM}{'' * 60}{Colors.RESET}\n")
if not sessions:
self.print_status("No active sessions", "info")
else:
for sid, info in sessions.items():
if isinstance(info, dict):
stype = info.get('type', 'shell')
target = info.get('target_host', 'unknown')
user = info.get('username', '')
via = info.get('via_exploit', '')[:30]
print(f" {Colors.GREEN}[{sid}]{Colors.RESET} {stype} @ {target}")
if user:
print(f" {Colors.DIM}User: {user}{Colors.RESET}")
if via:
print(f" {Colors.DIM}Via: {via}{Colors.RESET}")
else:
print(f" {Colors.GREEN}[{sid}]{Colors.RESET} {info}")
print()
sid = input(f"{Colors.WHITE}Interact with session (or Enter to skip): {Colors.RESET}").strip()
if sid and sid in sessions:
self.interact_session(sid)
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def interact_session(self, session_id: str):
"""Interact with a session."""
print(f"\n{Colors.GREEN}Interacting with session {session_id}{Colors.RESET}")
print(f"{Colors.DIM}Type 'exit' to return to menu{Colors.RESET}\n")
while True:
try:
cmd = input(f"{Colors.RED}session({session_id})>{Colors.RESET} ").strip()
if cmd.lower() == 'exit':
break
if not cmd:
continue
self.msf.session_write(session_id, cmd)
time.sleep(1)
success, output = self.msf.session_read(session_id)
if success and output:
print(output)
except (EOFError, KeyboardInterrupt):
print()
break
except Exception as e:
self.print_status(f"Session error: {e}", "error")
break
def show_jobs(self):
"""Show running jobs."""
if not self.ensure_connected():
return
jobs = self.msf.list_jobs()
print(f"\n{Colors.BOLD}Running Jobs{Colors.RESET}")
print(f"{Colors.DIM}{'' * 60}{Colors.RESET}\n")
if not jobs:
self.print_status("No running jobs", "info")
else:
for jid, jname in jobs.items():
print(f" {Colors.YELLOW}[{jid}]{Colors.RESET} {jname}")
print()
jid = input(f"{Colors.WHITE}Kill job (or Enter to skip): {Colors.RESET}").strip()
if jid and jid in jobs:
if self.msf.stop_job(jid):
self.print_status(f"Job {jid} stopped", "success")
else:
self.print_status(f"Failed to stop job {jid}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
# =========================================================================
# CONSOLE AND QUICK SCAN
# =========================================================================
def console_command(self):
"""Run console commands directly."""
if not self.ensure_connected():
return
print(f"\n{Colors.BOLD}MSF Console{Colors.RESET}")
print(f"{Colors.DIM}Enter commands directly (type 'exit' to return){Colors.RESET}\n")
while True:
try:
cmd = input(f"{Colors.RED}msf>{Colors.RESET} ").strip()
if cmd.lower() == 'exit':
break
if not cmd:
continue
success, output = self.msf.run_console_command(cmd)
if output:
print(output)
except (EOFError, KeyboardInterrupt):
print()
break
def quick_scan(self):
"""Quick scanner with pre-set target."""
if not self.ensure_connected():
return
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} Quick Scan{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
# Show current target
if self.global_settings['RHOSTS']:
print(f" {Colors.GREEN}Target: {self.global_settings['RHOSTS']}{Colors.RESET}")
else:
print(f" {Colors.YELLOW}Target: Not set (will prompt){Colors.RESET}")
print()
scanners = [
("auxiliary/scanner/portscan/tcp", "TCP Port Scanner", "Scan for open TCP ports"),
("auxiliary/scanner/smb/smb_version", "SMB Version", "Identify Windows/SMB version"),
("auxiliary/scanner/smb/smb_ms17_010", "MS17-010 Check", "Check for EternalBlue vulnerability"),
("auxiliary/scanner/ssh/ssh_version", "SSH Version", "Identify SSH server version"),
("auxiliary/scanner/http/http_version", "HTTP Version", "Identify web server version"),
("auxiliary/scanner/ftp/ftp_version", "FTP Version", "Identify FTP server version"),
]
for i, (mod, name, desc) in enumerate(scanners, 1):
print(f" {Colors.RED}[{i}]{Colors.RESET} {name}")
print(f" {Colors.DIM}{desc}{Colors.RESET}")
print(f"\n {Colors.DIM}[0]{Colors.RESET} Cancel\n")
choice = input(f"{Colors.WHITE} Select scanner: {Colors.RESET}").strip()
try:
idx = int(choice) - 1
if 0 <= idx < len(scanners):
scanner_mod, scanner_name, _ = scanners[idx]
# Get target
target = self.global_settings['RHOSTS']
if not target:
target = input(f"{Colors.WHITE}Target (IP/range): {Colors.RESET}").strip()
if not target:
return
options = {'RHOSTS': target, 'THREADS': '10'}
print(f"\n{Colors.CYAN}Running {scanner_name} against {target}...{Colors.RESET}")
result = self.msf.run_scanner(scanner_mod, target, options=options, timeout=120)
self.msf.print_result(result, verbose=False)
except (ValueError, IndexError):
pass
except Exception as e:
self.print_status(f"Scanner failed: {e}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
# =========================================================================
# EXPLOIT SUGGESTER
# =========================================================================
def exploit_suggester(self):
"""Suggest exploits based on vulnerability scan results."""
clear_screen()
display_banner()
print(f"{Colors.RED}{Colors.BOLD} Exploit Suggester{Colors.RESET}")
print(f"{Colors.DIM} Suggest attack paths based on detected vulnerabilities{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
print(f" {Colors.RED}[1]{Colors.RESET} Load vuln_correlator JSON")
print(f" {Colors.RED}[2]{Colors.RESET} Run fresh scan")
print(f" {Colors.RED}[3]{Colors.RESET} Manual service list")
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
services = []
cves = []
if choice == "1":
# Load from vuln correlator JSON
results_dir = Path("results")
json_files = sorted(results_dir.glob("vuln_correlator_*.json")) if results_dir.exists() else []
if not json_files:
self.print_status("No vuln correlator results found. Run OSINT > Vulnerability Correlator first.", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
print(f"\n{Colors.CYAN}Available vuln reports:{Colors.RESET}")
for i, f in enumerate(json_files, 1):
print(f" {Colors.RED}[{i}]{Colors.RESET} {f.name}")
sel = input(f"\n{Colors.WHITE}Select: {Colors.RESET}").strip()
try:
idx = int(sel) - 1
with open(json_files[idx], 'r') as f:
data = json.load(f)
for corr in data.get('correlations', []):
svc = corr.get('service', {})
services.append(svc)
for cve in corr.get('cves', []):
cves.append(cve)
except (ValueError, IndexError, json.JSONDecodeError) as e:
self.print_status(f"Error loading file: {e}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
elif choice == "2":
target = self.global_settings.get('RHOSTS', '') or input(f"{Colors.WHITE}Target: {Colors.RESET}").strip()
if not target:
return
self.print_status(f"Running nmap -sV on {target}...", "info")
import subprocess
try:
result = subprocess.run(f"nmap -sV -T4 {target}", shell=True, capture_output=True, text=True, timeout=300)
if result.returncode == 0:
port_re = re.compile(r'(\d+)/(tcp|udp)\s+open\s+(\S+)\s*(.*)')
for line in result.stdout.split('\n'):
m = port_re.match(line.strip())
if m:
parts = m.group(4).strip().split()
services.append({
'port': int(m.group(1)),
'service': parts[0] if parts else m.group(3),
'version': parts[1] if len(parts) > 1 else '',
'host': target,
})
except Exception as e:
self.print_status(f"Scan failed: {e}", "error")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
elif choice == "3":
print(f"\n{Colors.DIM}Enter services (format: port service version), empty line to finish:{Colors.RESET}")
while True:
line = input(f"{Colors.WHITE} > {Colors.RESET}").strip()
if not line:
break
parts = line.split()
if len(parts) >= 2:
services.append({
'port': int(parts[0]) if parts[0].isdigit() else 0,
'service': parts[1],
'version': parts[2] if len(parts) > 2 else '',
})
else:
return
if not services:
self.print_status("No services to analyze", "warning")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
return
# Try LLM-based suggestion first
suggestions = []
try:
from core.llm import get_llm
llm = get_llm()
if llm.is_loaded:
self.print_status("Using LLM for exploit analysis...", "info")
prompt = self._build_exploit_prompt(services, cves)
print(f"\n{Colors.CYAN}{'' * 60}{Colors.RESET}")
print(f"{Colors.BOLD}Exploit Analysis{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 60}{Colors.RESET}\n")
# Stream response
full_response = ""
for token in llm.generate(prompt, stream=True, max_tokens=1024):
print(token, end='', flush=True)
full_response += token
print()
suggestions = self._parse_exploit_suggestions(full_response)
else:
raise Exception("LLM not loaded")
except Exception:
# Fallback: direct CVE-to-MSF mapping
self.print_status("Using direct CVE-to-MSF mapping (no LLM)...", "info")
suggestions = self._fallback_exploit_suggestions(services, cves)
# Display suggestions
if suggestions:
print(f"\n{Colors.CYAN}{'' * 60}{Colors.RESET}")
print(f"{Colors.BOLD}Suggested Exploits{Colors.RESET}")
print(f"{Colors.CYAN}{'' * 60}{Colors.RESET}\n")
for i, s in enumerate(suggestions, 1):
print(f" {Colors.RED}[{i}]{Colors.RESET} {s.get('module', 'N/A')}")
print(f" Target: {s.get('target', 'N/A')}")
if s.get('cve'):
print(f" CVE: {s['cve']}")
if s.get('reasoning'):
print(f" {Colors.DIM}{s['reasoning']}{Colors.RESET}")
print()
self._offer_autoload(suggestions)
else:
self.print_status("No matching exploits found in module library", "info")
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
def _build_exploit_prompt(self, services: list, cves: list) -> str:
"""Build LLM prompt for exploit suggestion."""
# Gather available MSF modules
relevant_modules = []
for svc in services:
svc_name = svc.get('service', '').lower()
results = library_search_modules(svc_name, max_results=5)
for mod in results:
if mod['path'] not in [m['path'] for m in relevant_modules]:
relevant_modules.append(mod)
# Also search by CVE
for cve in cves[:10]:
cve_id = cve.get('cve_id', '')
if cve_id:
results = library_search_modules(cve_id, max_results=3)
for mod in results:
if mod['path'] not in [m['path'] for m in relevant_modules]:
relevant_modules.append(mod)
prompt = "You are a penetration testing assistant. Based on the following target information, suggest the top 5 attack paths.\n\n"
prompt += "TARGET SERVICES:\n"
for svc in services:
prompt += f" - Port {svc.get('port', '?')}: {svc.get('service', '?')} {svc.get('version', '')}\n"
if cves:
prompt += "\nKNOWN VULNERABILITIES:\n"
for cve in cves[:15]:
prompt += f" - {cve.get('cve_id', '?')} ({cve.get('severity', '?')} {cve.get('cvss_score', '?')}): {(cve.get('description', '') or '')[:100]}\n"
if relevant_modules:
prompt += "\nAVAILABLE METASPLOIT MODULES:\n"
for mod in relevant_modules[:15]:
prompt += f" - {mod['path']}: {mod.get('name', '')}\n"
prompt += "\nFor each suggestion, provide: module path, target service, CVE (if applicable), and reasoning.\n"
prompt += "Format each as: RANK. MODULE_PATH | TARGET | CVE | REASONING\n"
return prompt
def _parse_exploit_suggestions(self, response: str) -> list:
"""Parse exploit suggestions from LLM response."""
suggestions = []
lines = response.split('\n')
for line in lines:
line = line.strip()
if not line:
continue
# Try to parse "N. module | target | cve | reasoning" format
if '|' in line and ('/' in line or 'exploit' in line.lower() or 'auxiliary' in line.lower()):
parts = [p.strip() for p in line.split('|')]
# Remove leading number
first = re.sub(r'^\d+\.\s*', '', parts[0])
suggestion = {
'module': first,
'target': parts[1] if len(parts) > 1 else '',
'cve': parts[2] if len(parts) > 2 else '',
'reasoning': parts[3] if len(parts) > 3 else '',
}
suggestions.append(suggestion)
return suggestions[:5]
def _fallback_exploit_suggestions(self, services: list, cves: list) -> list:
"""Fallback exploit suggestion using direct CVE-to-MSF mapping."""
suggestions = []
seen_modules = set()
# Search by CVE
for cve in cves[:20]:
cve_id = cve.get('cve_id', '')
if not cve_id:
continue
results = library_search_modules(cve_id, max_results=3)
for mod in results:
if mod['path'] not in seen_modules:
seen_modules.add(mod['path'])
suggestions.append({
'module': mod['path'],
'target': mod.get('name', ''),
'cve': cve_id,
'reasoning': f"CVSS {cve.get('cvss_score', '?')} - Direct CVE match",
})
# Search by service name
for svc in services:
svc_name = svc.get('service', '').lower()
if not svc_name:
continue
results = library_search_modules(svc_name, max_results=3)
for mod in results:
if mod['path'] not in seen_modules and mod['path'].startswith('exploit'):
seen_modules.add(mod['path'])
suggestions.append({
'module': mod['path'],
'target': f"{svc_name} on port {svc.get('port', '?')}",
'cve': ', '.join(mod.get('cve', []) or []),
'reasoning': f"Service match: {svc_name} {svc.get('version', '')}",
})
return suggestions[:5]
def _offer_autoload(self, suggestions: list):
"""Offer to auto-load a suggested module."""
choice = input(f"\n{Colors.WHITE}Load a module? (enter number or 0 to skip): {Colors.RESET}").strip()
if not choice or choice == '0':
return
try:
idx = int(choice) - 1
if 0 <= idx < len(suggestions):
module_path = suggestions[idx].get('module', '')
if module_path and '/' in module_path:
self.print_status(f"Loading {module_path}...", "info")
self._select_module(module_path)
except (ValueError, IndexError):
pass
# =========================================================================
# MAIN LOOP
# =========================================================================
def run(self):
"""Main loop."""
while True:
self.show_main_menu()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0":
break
elif choice == "1":
self.show_target_settings()
elif choice == "2":
self.show_module_browser()
elif choice == "3":
self.search_modules()
elif choice == "4":
self.show_current_module()
elif choice == "5":
self.run_module()
elif choice == "6":
self.show_sessions()
elif choice == "7":
self.show_jobs()
elif choice == "8":
self.console_command()
elif choice == "9":
self.quick_scan()
elif choice.lower() == "e":
self.exploit_suggester()
except (EOFError, KeyboardInterrupt):
print()
break
def run():
"""Module entry point."""
menu = MSFMenu()
menu.run()
if __name__ == "__main__":
run()