Add WiFi Audit, API Fuzzer, Cloud Scanner, Threat Intel, Log Correlator, Steganography, Anti-Forensics, BLE Scanner, Forensics, RFID/NFC, Malware Sandbox, Password Toolkit, Web Scanner, Report Engine, Net Mapper, and C2 Framework. Each module includes CLI interface, Flask routes, and web UI template. Also includes Go DNS server source + binary, IP Capture service, SYN Flood, Gone Fishing mail server, and hack hijack modules from v2.0 work. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1151 lines
38 KiB
Python
1151 lines
38 KiB
Python
"""
|
|
AUTARCH Metasploit Integration
|
|
Interface for Metasploit Framework via RPC
|
|
"""
|
|
|
|
import json
|
|
import http.client
|
|
import ssl
|
|
import socket
|
|
import subprocess
|
|
import time
|
|
import os
|
|
import signal
|
|
from typing import Optional, Dict, List, Any, Tuple
|
|
from dataclasses import dataclass
|
|
|
|
# msgpack is optional - MSF features disabled without it
|
|
try:
|
|
import msgpack
|
|
MSGPACK_AVAILABLE = True
|
|
except ImportError:
|
|
msgpack = None
|
|
MSGPACK_AVAILABLE = False
|
|
|
|
from .config import get_config
|
|
from .banner import Colors
|
|
|
|
|
|
class MSFError(Exception):
|
|
"""Exception raised for Metasploit-related errors."""
|
|
pass
|
|
|
|
|
|
def check_msgpack():
|
|
"""Check if msgpack is available, raise error if not."""
|
|
if not MSGPACK_AVAILABLE:
|
|
raise MSFError(
|
|
"msgpack module not installed. Install with: pip install msgpack"
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class MSFModule:
|
|
"""Information about a Metasploit module."""
|
|
type: str # exploit, auxiliary, post, payload, encoder, nop
|
|
name: str
|
|
fullname: str
|
|
description: str
|
|
rank: str = ""
|
|
author: List[str] = None
|
|
references: List[str] = None
|
|
|
|
|
|
class MetasploitRPC:
|
|
"""Client for Metasploit RPC API."""
|
|
|
|
def __init__(
|
|
self,
|
|
host: str = "127.0.0.1",
|
|
port: int = 55553,
|
|
username: str = "msf",
|
|
password: str = None,
|
|
ssl: bool = True
|
|
):
|
|
"""Initialize MSF RPC client.
|
|
|
|
Args:
|
|
host: MSF RPC host address.
|
|
port: MSF RPC port (default 55553).
|
|
username: RPC username.
|
|
password: RPC password.
|
|
ssl: Use SSL connection.
|
|
"""
|
|
self.host = host
|
|
self.port = port
|
|
self.username = username
|
|
self.password = password
|
|
self.use_ssl = ssl
|
|
self.token: Optional[str] = None
|
|
self._connected = False
|
|
|
|
@property
|
|
def is_connected(self) -> bool:
|
|
"""Check if connected to MSF RPC."""
|
|
return self._connected and self.token is not None
|
|
|
|
def _decode_bytes(self, obj):
|
|
"""Recursively decode bytes to strings in msgpack responses.
|
|
|
|
Args:
|
|
obj: Object to decode (dict, list, bytes, or other).
|
|
|
|
Returns:
|
|
Decoded object with all bytes converted to strings.
|
|
"""
|
|
if isinstance(obj, bytes):
|
|
return obj.decode('utf-8', errors='replace')
|
|
elif isinstance(obj, dict):
|
|
return {
|
|
self._decode_bytes(k): self._decode_bytes(v)
|
|
for k, v in obj.items()
|
|
}
|
|
elif isinstance(obj, list):
|
|
return [self._decode_bytes(item) for item in obj]
|
|
elif isinstance(obj, tuple):
|
|
return tuple(self._decode_bytes(item) for item in obj)
|
|
else:
|
|
return obj
|
|
|
|
def _request(self, method: str, params: List = None) -> Dict[str, Any]:
|
|
"""Make an RPC request to Metasploit.
|
|
|
|
Args:
|
|
method: RPC method name.
|
|
params: Method parameters.
|
|
|
|
Returns:
|
|
Response dictionary.
|
|
|
|
Raises:
|
|
MSFError: If request fails.
|
|
"""
|
|
check_msgpack() # Ensure msgpack is available
|
|
|
|
params = params or []
|
|
|
|
# Add token to authenticated requests
|
|
if self.token and method != "auth.login":
|
|
params = [self.token] + params
|
|
|
|
# Build request
|
|
request_data = msgpack.packb([method] + params)
|
|
|
|
try:
|
|
if self.use_ssl:
|
|
context = ssl.create_default_context()
|
|
context.check_hostname = False
|
|
context.verify_mode = ssl.CERT_NONE
|
|
conn = http.client.HTTPSConnection(
|
|
self.host, self.port, context=context, timeout=30
|
|
)
|
|
else:
|
|
conn = http.client.HTTPConnection(self.host, self.port, timeout=30)
|
|
|
|
headers = {
|
|
"Content-Type": "binary/message-pack",
|
|
"Content-Length": str(len(request_data))
|
|
}
|
|
|
|
conn.request("POST", "/api/", request_data, headers)
|
|
response = conn.getresponse()
|
|
|
|
if response.status != 200:
|
|
raise MSFError(f"HTTP error: {response.status} {response.reason}")
|
|
|
|
response_data = response.read()
|
|
result = msgpack.unpackb(response_data, raw=False, strict_map_key=False)
|
|
|
|
# Recursively normalize bytes to strings throughout the response
|
|
result = self._decode_bytes(result)
|
|
|
|
if isinstance(result, dict) and result.get("error"):
|
|
raise MSFError(f"MSF error: {result.get('error_message', 'Unknown error')}")
|
|
|
|
return result
|
|
|
|
except ConnectionRefusedError:
|
|
raise MSFError(f"Connection refused to {self.host}:{self.port}. Is msfrpcd running?")
|
|
except Exception as e:
|
|
if isinstance(e, MSFError):
|
|
raise
|
|
raise MSFError(f"RPC request failed: {e}")
|
|
finally:
|
|
try:
|
|
conn.close()
|
|
except:
|
|
pass
|
|
|
|
def connect(self, password: str = None) -> bool:
|
|
"""Connect and authenticate to MSF RPC.
|
|
|
|
Args:
|
|
password: RPC password (uses stored password if not provided).
|
|
|
|
Returns:
|
|
True if connected successfully.
|
|
|
|
Raises:
|
|
MSFError: If connection fails.
|
|
"""
|
|
password = password or self.password
|
|
if not password:
|
|
raise MSFError("No password provided for MSF RPC")
|
|
|
|
try:
|
|
result = self._request("auth.login", [self.username, password])
|
|
|
|
if result.get("result") == "success":
|
|
self.token = result.get("token")
|
|
self._connected = True
|
|
return True
|
|
else:
|
|
raise MSFError("Authentication failed")
|
|
|
|
except MSFError:
|
|
self._connected = False
|
|
self.token = None
|
|
raise
|
|
|
|
def disconnect(self):
|
|
"""Disconnect from MSF RPC."""
|
|
if self.token:
|
|
try:
|
|
self._request("auth.logout", [self.token])
|
|
except:
|
|
pass
|
|
self.token = None
|
|
self._connected = False
|
|
|
|
def get_version(self) -> Dict[str, str]:
|
|
"""Get Metasploit version info.
|
|
|
|
Returns:
|
|
Dictionary with version information.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
return self._request("core.version")
|
|
|
|
def list_modules(self, module_type: str = None) -> List[str]:
|
|
"""List available modules.
|
|
|
|
Args:
|
|
module_type: Filter by type (exploit, auxiliary, post, payload, encoder, nop).
|
|
|
|
Returns:
|
|
List of module names.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
# Map module types to their API method names
|
|
# The MSF RPC API uses module.exploits, module.auxiliary, etc.
|
|
type_to_method = {
|
|
"exploit": "module.exploits",
|
|
"auxiliary": "module.auxiliary",
|
|
"post": "module.post",
|
|
"payload": "module.payloads",
|
|
"encoder": "module.encoders",
|
|
"nop": "module.nops",
|
|
}
|
|
|
|
if module_type:
|
|
method = type_to_method.get(module_type)
|
|
if not method:
|
|
raise MSFError(f"Unknown module type: {module_type}")
|
|
result = self._request(method)
|
|
return result.get("modules", [])
|
|
else:
|
|
# Get all module types
|
|
all_modules = []
|
|
for mtype in ["exploit", "auxiliary", "post", "payload"]:
|
|
try:
|
|
method = type_to_method.get(mtype)
|
|
result = self._request(method)
|
|
modules = result.get("modules", [])
|
|
all_modules.extend([f"{mtype}/{m}" for m in modules])
|
|
except:
|
|
pass
|
|
return all_modules
|
|
|
|
def search_modules(self, query: str) -> List[Dict[str, Any]]:
|
|
"""Search for modules matching a query.
|
|
|
|
Args:
|
|
query: Search query string.
|
|
|
|
Returns:
|
|
List of matching modules.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
result = self._request("module.search", [query])
|
|
return result if isinstance(result, list) else []
|
|
|
|
def get_module_info(self, module_type: str, module_name: str) -> MSFModule:
|
|
"""Get detailed information about a module.
|
|
|
|
Args:
|
|
module_type: Module type (exploit, auxiliary, etc.).
|
|
module_name: Module name.
|
|
|
|
Returns:
|
|
MSFModule with module details.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
result = self._request("module.info", [module_type, module_name])
|
|
|
|
return MSFModule(
|
|
type=module_type,
|
|
name=result.get("name", module_name),
|
|
fullname=f"{module_type}/{module_name}",
|
|
description=result.get("description", ""),
|
|
rank=result.get("rank", ""),
|
|
author=result.get("author", []),
|
|
references=result.get("references", [])
|
|
)
|
|
|
|
def get_module_options(self, module_type: str, module_name: str) -> Dict[str, Any]:
|
|
"""Get available options for a module.
|
|
|
|
Args:
|
|
module_type: Module type.
|
|
module_name: Module name.
|
|
|
|
Returns:
|
|
Dictionary of options and their details.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
return self._request("module.options", [module_type, module_name])
|
|
|
|
def execute_module(
|
|
self,
|
|
module_type: str,
|
|
module_name: str,
|
|
options: Dict[str, Any] = None
|
|
) -> Dict[str, Any]:
|
|
"""Execute a module with given options.
|
|
|
|
Args:
|
|
module_type: Module type.
|
|
module_name: Module name.
|
|
options: Module options dictionary.
|
|
|
|
Returns:
|
|
Execution result with job_id.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
options = options or {}
|
|
return self._request("module.execute", [module_type, module_name, options])
|
|
|
|
def list_jobs(self) -> Dict[str, Any]:
|
|
"""List running jobs.
|
|
|
|
Returns:
|
|
Dictionary of job IDs and info.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
return self._request("job.list")
|
|
|
|
def get_job_info(self, job_id: str) -> Dict[str, Any]:
|
|
"""Get information about a job.
|
|
|
|
Args:
|
|
job_id: Job ID.
|
|
|
|
Returns:
|
|
Job information dictionary.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
return self._request("job.info", [job_id])
|
|
|
|
def stop_job(self, job_id: str) -> bool:
|
|
"""Stop a running job.
|
|
|
|
Args:
|
|
job_id: Job ID to stop.
|
|
|
|
Returns:
|
|
True if stopped successfully.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
result = self._request("job.stop", [job_id])
|
|
return result.get("result") == "success"
|
|
|
|
def list_sessions(self) -> Dict[str, Any]:
|
|
"""List active sessions.
|
|
|
|
Returns:
|
|
Dictionary of session IDs and info.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
return self._request("session.list")
|
|
|
|
def session_shell_read(self, session_id: str) -> str:
|
|
"""Read output from a shell session.
|
|
|
|
Args:
|
|
session_id: Session ID.
|
|
|
|
Returns:
|
|
Shell output string.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
result = self._request("session.shell_read", [session_id])
|
|
return result.get("data", "")
|
|
|
|
def session_shell_write(self, session_id: str, command: str) -> bool:
|
|
"""Write a command to a shell session.
|
|
|
|
Args:
|
|
session_id: Session ID.
|
|
command: Command to execute.
|
|
|
|
Returns:
|
|
True if written successfully.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
result = self._request("session.shell_write", [session_id, command + "\n"])
|
|
return result.get("write_count", 0) > 0
|
|
|
|
def session_stop(self, session_id: str) -> bool:
|
|
"""Stop/kill a session.
|
|
|
|
Args:
|
|
session_id: Session ID to stop.
|
|
|
|
Returns:
|
|
True if stopped successfully.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
result = self._request("session.stop", [session_id])
|
|
return result.get("result") == "success"
|
|
|
|
def run_console_command(self, command: str) -> str:
|
|
"""Run a command in MSF console.
|
|
|
|
Args:
|
|
command: Console command to run.
|
|
|
|
Returns:
|
|
Command output.
|
|
"""
|
|
if not self.is_connected:
|
|
raise MSFError("Not connected to MSF RPC")
|
|
|
|
# Create console
|
|
console = self._request("console.create")
|
|
console_id = console.get("id")
|
|
|
|
try:
|
|
# Write command
|
|
self._request("console.write", [console_id, command + "\n"])
|
|
|
|
# Read output (with retries for async commands)
|
|
import time
|
|
output = ""
|
|
for _ in range(10):
|
|
time.sleep(0.5)
|
|
result = self._request("console.read", [console_id])
|
|
output += result.get("data", "")
|
|
if not result.get("busy", False):
|
|
break
|
|
|
|
return output
|
|
|
|
finally:
|
|
# Destroy console
|
|
try:
|
|
self._request("console.destroy", [console_id])
|
|
except:
|
|
pass
|
|
|
|
|
|
class MSFManager:
|
|
"""High-level manager for Metasploit integration."""
|
|
|
|
def __init__(self):
|
|
self.config = get_config()
|
|
self.rpc: Optional[MetasploitRPC] = None
|
|
self._server_process: Optional[subprocess.Popen] = None
|
|
|
|
def _ensure_config_section(self):
|
|
"""Ensure MSF config section exists."""
|
|
if not self.config.config.has_section('msf'):
|
|
self.config.config['msf'] = {
|
|
'host': '127.0.0.1',
|
|
'port': '55553',
|
|
'username': 'msf',
|
|
'password': '',
|
|
'ssl': 'true',
|
|
'autoconnect': 'true'
|
|
}
|
|
self.config.save()
|
|
|
|
def detect_server(self) -> Tuple[bool, Optional[str]]:
|
|
"""Detect if msfrpcd is already running.
|
|
|
|
Returns:
|
|
Tuple of (is_running, pid or None)
|
|
"""
|
|
settings = self.get_settings()
|
|
host = settings['host']
|
|
port = settings['port']
|
|
|
|
# First try socket connection to check if port is open
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(2)
|
|
result = sock.connect_ex((host, port))
|
|
sock.close()
|
|
|
|
if result == 0:
|
|
# Port is open, try to find the process
|
|
pid = self._find_msfrpcd_pid()
|
|
return True, pid
|
|
except Exception:
|
|
pass
|
|
|
|
# Also check for running msfrpcd process even if port check failed
|
|
pid = self._find_msfrpcd_pid()
|
|
if pid:
|
|
return True, pid
|
|
|
|
return False, None
|
|
|
|
def _find_msfrpcd_pid(self) -> Optional[str]:
|
|
"""Find the PID of running msfrpcd process.
|
|
|
|
Works on both Linux (pgrep, /proc) and Windows (tasklist, wmic).
|
|
|
|
Returns:
|
|
PID as string, or None if not found
|
|
"""
|
|
import sys
|
|
is_win = sys.platform == 'win32'
|
|
|
|
if is_win:
|
|
# Windows: use tasklist to find ruby/msfrpcd processes
|
|
for search_term in ['msfrpcd', 'thin', 'ruby']:
|
|
try:
|
|
result = subprocess.run(
|
|
['tasklist', '/FI', f'IMAGENAME eq {search_term}*',
|
|
'/FO', 'CSV', '/NH'],
|
|
capture_output=True, text=True, timeout=5
|
|
)
|
|
if result.returncode == 0:
|
|
for line in result.stdout.strip().split('\n'):
|
|
line = line.strip().strip('"')
|
|
if line and 'INFO:' not in line:
|
|
parts = line.split('","')
|
|
if len(parts) >= 2:
|
|
return parts[1].strip('"')
|
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
pass
|
|
|
|
# Fallback: wmic for command-line matching
|
|
try:
|
|
result = subprocess.run(
|
|
['wmic', 'process', 'where',
|
|
"commandline like '%msfrpcd%' or commandline like '%thin%msf%'",
|
|
'get', 'processid'],
|
|
capture_output=True, text=True, timeout=5
|
|
)
|
|
if result.returncode == 0:
|
|
for line in result.stdout.strip().split('\n'):
|
|
line = line.strip()
|
|
if line.isdigit():
|
|
return line
|
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
pass
|
|
else:
|
|
# Linux: use pgrep
|
|
try:
|
|
result = subprocess.run(
|
|
['pgrep', '-f', 'msfrpcd'],
|
|
capture_output=True, text=True, timeout=5
|
|
)
|
|
if result.returncode == 0 and result.stdout.strip():
|
|
pids = result.stdout.strip().split('\n')
|
|
return pids[0] if pids else None
|
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
pass
|
|
|
|
# Fallback: check /proc on Linux
|
|
try:
|
|
for pid_dir in os.listdir('/proc'):
|
|
if pid_dir.isdigit():
|
|
try:
|
|
cmdline_path = f'/proc/{pid_dir}/cmdline'
|
|
with open(cmdline_path, 'r') as f:
|
|
cmdline = f.read()
|
|
if 'msfrpcd' in cmdline:
|
|
return pid_dir
|
|
except (IOError, PermissionError):
|
|
continue
|
|
except Exception:
|
|
pass
|
|
|
|
return None
|
|
|
|
def kill_server(self, use_sudo: bool = True) -> bool:
|
|
"""Kill any running msfrpcd server.
|
|
|
|
Args:
|
|
use_sudo: Use sudo for killing (needed if server was started with sudo).
|
|
Ignored on Windows.
|
|
|
|
Returns:
|
|
True if server was killed or no server was running
|
|
"""
|
|
import sys
|
|
is_win = sys.platform == 'win32'
|
|
|
|
is_running, pid = self.detect_server()
|
|
|
|
if not is_running:
|
|
return True
|
|
|
|
# Disconnect our client first if connected
|
|
if self.is_connected:
|
|
self.disconnect()
|
|
|
|
if is_win:
|
|
# Windows: use taskkill
|
|
if pid:
|
|
try:
|
|
subprocess.run(
|
|
['taskkill', '/F', '/PID', str(pid)],
|
|
capture_output=True, timeout=10
|
|
)
|
|
time.sleep(1)
|
|
return True
|
|
except Exception as e:
|
|
print(f"{Colors.RED}[X] Failed to kill msfrpcd (PID {pid}): {e}{Colors.RESET}")
|
|
|
|
# Fallback: kill by image name
|
|
for name in ['msfrpcd', 'ruby', 'thin']:
|
|
try:
|
|
subprocess.run(
|
|
['taskkill', '/F', '/IM', f'{name}.exe'],
|
|
capture_output=True, timeout=5
|
|
)
|
|
except Exception:
|
|
pass
|
|
time.sleep(1)
|
|
return True
|
|
else:
|
|
# Linux: kill by PID or pkill
|
|
if pid:
|
|
try:
|
|
os.kill(int(pid), signal.SIGTERM)
|
|
time.sleep(1)
|
|
try:
|
|
os.kill(int(pid), 0)
|
|
os.kill(int(pid), signal.SIGKILL)
|
|
time.sleep(0.5)
|
|
except ProcessLookupError:
|
|
pass
|
|
return True
|
|
except PermissionError:
|
|
if use_sudo:
|
|
try:
|
|
subprocess.run(['sudo', 'kill', '-TERM', str(pid)], timeout=5)
|
|
time.sleep(1)
|
|
try:
|
|
os.kill(int(pid), 0)
|
|
subprocess.run(['sudo', 'kill', '-KILL', str(pid)], timeout=5)
|
|
except ProcessLookupError:
|
|
pass
|
|
return True
|
|
except Exception as e:
|
|
print(f"{Colors.RED}[X] Failed to kill msfrpcd with sudo (PID {pid}): {e}{Colors.RESET}")
|
|
return False
|
|
else:
|
|
print(f"{Colors.RED}[X] Failed to kill msfrpcd (PID {pid}): Permission denied{Colors.RESET}")
|
|
return False
|
|
except ProcessLookupError:
|
|
return True
|
|
|
|
# Try pkill as fallback
|
|
try:
|
|
if use_sudo:
|
|
subprocess.run(['sudo', 'pkill', '-f', 'msfrpcd'], timeout=5)
|
|
else:
|
|
subprocess.run(['pkill', '-f', 'msfrpcd'], timeout=5)
|
|
time.sleep(1)
|
|
return True
|
|
except Exception:
|
|
pass
|
|
|
|
return False
|
|
|
|
def _find_msf_install(self) -> Optional[str]:
|
|
"""Find the Metasploit Framework installation directory.
|
|
|
|
Returns:
|
|
Path to the MSF install directory, or None if not found.
|
|
"""
|
|
import sys
|
|
is_win = sys.platform == 'win32'
|
|
|
|
if is_win:
|
|
# Common Windows Metasploit install paths
|
|
candidates = [
|
|
os.path.join(os.environ.get('ProgramFiles', r'C:\Program Files'), 'Metasploit'),
|
|
os.path.join(os.environ.get('ProgramFiles(x86)', r'C:\Program Files (x86)'), 'Metasploit'),
|
|
r'C:\metasploit-framework',
|
|
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Metasploit'),
|
|
os.path.join(os.environ.get('ProgramFiles', ''), 'Rapid7', 'Metasploit'),
|
|
]
|
|
for c in candidates:
|
|
if c and os.path.isdir(c):
|
|
return c
|
|
# Also check with -framework suffix
|
|
cf = c + '-framework' if not c.endswith('-framework') else c
|
|
if cf and os.path.isdir(cf):
|
|
return cf
|
|
else:
|
|
candidates = [
|
|
'/opt/metasploit-framework',
|
|
'/usr/share/metasploit-framework',
|
|
'/opt/metasploit',
|
|
os.path.expanduser('~/.msf4'),
|
|
]
|
|
for c in candidates:
|
|
if os.path.isdir(c):
|
|
return c
|
|
|
|
return None
|
|
|
|
def start_server(self, username: str, password: str,
|
|
host: str = "127.0.0.1", port: int = 55553,
|
|
use_ssl: bool = True, use_sudo: bool = True) -> bool:
|
|
"""Start the msfrpcd server with given credentials.
|
|
|
|
Works on both Linux and Windows.
|
|
|
|
Args:
|
|
username: RPC username
|
|
password: RPC password
|
|
host: Host to bind to
|
|
port: Port to listen on
|
|
use_ssl: Whether to use SSL
|
|
use_sudo: Run msfrpcd with sudo (Linux only; ignored on Windows)
|
|
|
|
Returns:
|
|
True if server started successfully
|
|
"""
|
|
import sys
|
|
is_win = sys.platform == 'win32'
|
|
|
|
# Find msfrpcd binary
|
|
from core.paths import find_tool
|
|
msfrpcd_bin = find_tool('msfrpcd')
|
|
|
|
if not msfrpcd_bin and is_win:
|
|
# Windows: look for msfrpcd.bat in common locations
|
|
msf_dir = self._find_msf_install()
|
|
if msf_dir:
|
|
for candidate in [
|
|
os.path.join(msf_dir, 'bin', 'msfrpcd.bat'),
|
|
os.path.join(msf_dir, 'bin', 'msfrpcd'),
|
|
os.path.join(msf_dir, 'msfrpcd.bat'),
|
|
os.path.join(msf_dir, 'embedded', 'bin', 'ruby.exe'),
|
|
]:
|
|
if os.path.isfile(candidate):
|
|
msfrpcd_bin = candidate
|
|
break
|
|
|
|
if not msfrpcd_bin:
|
|
# Try PATH with .bat extension
|
|
for ext in ['.bat', '.cmd', '.exe', '']:
|
|
for p in os.environ.get('PATH', '').split(os.pathsep):
|
|
candidate = os.path.join(p, f'msfrpcd{ext}')
|
|
if os.path.isfile(candidate):
|
|
msfrpcd_bin = candidate
|
|
break
|
|
if msfrpcd_bin:
|
|
break
|
|
|
|
if not msfrpcd_bin:
|
|
msfrpcd_bin = 'msfrpcd' # Last resort: hope it's on PATH
|
|
|
|
# Build command
|
|
cmd = [
|
|
msfrpcd_bin,
|
|
'-U', username,
|
|
'-P', password,
|
|
'-a', host,
|
|
'-p', str(port),
|
|
'-f' # Run in foreground (we'll background it ourselves)
|
|
]
|
|
|
|
if not use_ssl:
|
|
cmd.append('-S') # Disable SSL
|
|
|
|
# On Windows, if it's a .bat file, run through cmd
|
|
if is_win and msfrpcd_bin.endswith('.bat'):
|
|
cmd = ['cmd', '/c'] + cmd
|
|
|
|
# Prepend sudo on Linux if requested
|
|
if not is_win and use_sudo:
|
|
cmd = ['sudo'] + cmd
|
|
|
|
try:
|
|
# Start msfrpcd in background
|
|
popen_kwargs = {
|
|
'stdout': subprocess.DEVNULL,
|
|
'stderr': subprocess.DEVNULL,
|
|
}
|
|
if is_win:
|
|
popen_kwargs['creationflags'] = (
|
|
subprocess.CREATE_NEW_PROCESS_GROUP |
|
|
subprocess.CREATE_NO_WINDOW
|
|
)
|
|
else:
|
|
popen_kwargs['start_new_session'] = True
|
|
|
|
self._server_process = subprocess.Popen(cmd, **popen_kwargs)
|
|
|
|
# Wait for server to start (check port becomes available)
|
|
max_wait = 30
|
|
start_time = time.time()
|
|
port_open = False
|
|
|
|
while time.time() - start_time < max_wait:
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(1)
|
|
result = sock.connect_ex((host, port))
|
|
sock.close()
|
|
|
|
if result == 0:
|
|
port_open = True
|
|
break
|
|
except Exception:
|
|
pass
|
|
|
|
time.sleep(0.5)
|
|
|
|
if not port_open:
|
|
print(f"{Colors.YELLOW}[!] Server started but port not responding after {max_wait}s{Colors.RESET}")
|
|
return False
|
|
|
|
# Port is open, but server needs time to initialize RPC layer
|
|
print(f"{Colors.DIM} Waiting for RPC initialization...{Colors.RESET}")
|
|
time.sleep(5)
|
|
|
|
# Try a test connection to verify server is really ready
|
|
for attempt in range(10):
|
|
try:
|
|
test_rpc = MetasploitRPC(
|
|
host=host, port=port, username=username,
|
|
password=password, ssl=use_ssl
|
|
)
|
|
test_rpc.connect(password)
|
|
test_rpc.disconnect()
|
|
return True
|
|
except MSFError:
|
|
if attempt < 9:
|
|
time.sleep(2)
|
|
continue
|
|
except Exception:
|
|
if attempt < 9:
|
|
time.sleep(2)
|
|
continue
|
|
|
|
print(f"{Colors.YELLOW}[!] Server running but authentication not ready - try connecting manually{Colors.RESET}")
|
|
return True
|
|
|
|
except FileNotFoundError:
|
|
print(f"{Colors.RED}[X] msfrpcd not found. Is Metasploit installed?{Colors.RESET}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"{Colors.RED}[X] Failed to start msfrpcd: {e}{Colors.RESET}")
|
|
return False
|
|
|
|
def get_settings(self) -> Dict[str, Any]:
|
|
"""Get current MSF settings."""
|
|
self._ensure_config_section()
|
|
return {
|
|
'host': self.config.get('msf', 'host', '127.0.0.1'),
|
|
'port': self.config.get_int('msf', 'port', 55553),
|
|
'username': self.config.get('msf', 'username', 'msf'),
|
|
'password': self.config.get('msf', 'password', ''),
|
|
'ssl': self.config.get_bool('msf', 'ssl', True),
|
|
'autoconnect': self.config.get_bool('msf', 'autoconnect', True)
|
|
}
|
|
|
|
def save_settings(self, host: str, port: int, username: str, password: str, use_ssl: bool):
|
|
"""Save MSF settings."""
|
|
self._ensure_config_section()
|
|
self.config.set('msf', 'host', host)
|
|
self.config.set('msf', 'port', port)
|
|
self.config.set('msf', 'username', username)
|
|
self.config.set('msf', 'password', password)
|
|
self.config.set('msf', 'ssl', str(use_ssl).lower())
|
|
self.config.save()
|
|
|
|
def connect(self, password: str = None) -> MetasploitRPC:
|
|
"""Connect to Metasploit RPC.
|
|
|
|
Args:
|
|
password: RPC password (uses saved if not provided).
|
|
|
|
Returns:
|
|
Connected MetasploitRPC instance.
|
|
"""
|
|
settings = self.get_settings()
|
|
password = password or settings['password']
|
|
|
|
self.rpc = MetasploitRPC(
|
|
host=settings['host'],
|
|
port=settings['port'],
|
|
username=settings['username'],
|
|
password=password,
|
|
ssl=settings['ssl']
|
|
)
|
|
|
|
self.rpc.connect(password)
|
|
return self.rpc
|
|
|
|
def disconnect(self):
|
|
"""Disconnect from Metasploit RPC."""
|
|
if self.rpc:
|
|
self.rpc.disconnect()
|
|
self.rpc = None
|
|
|
|
@property
|
|
def is_connected(self) -> bool:
|
|
"""Check if connected to MSF."""
|
|
return self.rpc is not None and self.rpc.is_connected
|
|
|
|
def autoconnect(self) -> bool:
|
|
"""Perform automatic MSF server detection and connection on startup.
|
|
|
|
Flow:
|
|
1. Scan for existing msfrpcd server
|
|
2. If found: kill it, ask for new credentials, restart with new creds
|
|
3. If not found: ask for credentials, start server
|
|
4. Connect to the server
|
|
|
|
Returns:
|
|
True if successfully connected to MSF
|
|
"""
|
|
settings = self.get_settings()
|
|
|
|
print(f"\n{Colors.CYAN}[*] Metasploit Auto-Connect{Colors.RESET}")
|
|
print(f"{Colors.DIM} {'─' * 40}{Colors.RESET}")
|
|
|
|
# Step 1: Detect existing server
|
|
print(f"\n{Colors.WHITE} Scanning for existing MSF RPC server...{Colors.RESET}")
|
|
is_running, pid = self.detect_server()
|
|
|
|
if is_running:
|
|
print(f"{Colors.YELLOW} [!] Found existing msfrpcd server{Colors.RESET}", end="")
|
|
if pid:
|
|
print(f" (PID: {pid})")
|
|
else:
|
|
print()
|
|
|
|
# Kill existing server (use sudo in case it was started with sudo)
|
|
print(f"{Colors.WHITE} Stopping existing server...{Colors.RESET}")
|
|
if not self.kill_server(use_sudo=True):
|
|
print(f"{Colors.RED} [X] Failed to stop existing server{Colors.RESET}")
|
|
print(f"{Colors.DIM} You may need to manually run: sudo pkill -f msfrpcd{Colors.RESET}")
|
|
return False
|
|
print(f"{Colors.GREEN} [+] Server stopped{Colors.RESET}")
|
|
else:
|
|
print(f"{Colors.DIM} No existing server detected{Colors.RESET}")
|
|
|
|
# Step 2: Ask for credentials
|
|
print(f"\n{Colors.BOLD} Configure MSF RPC Credentials{Colors.RESET}")
|
|
print(f"{Colors.DIM} These credentials will be used for the new server{Colors.RESET}\n")
|
|
|
|
try:
|
|
default_user = settings.get('username', 'msf')
|
|
default_host = settings.get('host', '127.0.0.1')
|
|
default_port = settings.get('port', 55553)
|
|
|
|
username = input(f" Username [{default_user}]: ").strip()
|
|
if not username:
|
|
username = default_user
|
|
|
|
password = input(f" Password (required): ").strip()
|
|
if not password:
|
|
print(f"{Colors.RED} [X] Password is required{Colors.RESET}")
|
|
return False
|
|
|
|
host_input = input(f" Host [{default_host}]: ").strip()
|
|
host = host_input if host_input else default_host
|
|
|
|
port_input = input(f" Port [{default_port}]: ").strip()
|
|
try:
|
|
port = int(port_input) if port_input else default_port
|
|
except ValueError:
|
|
port = default_port
|
|
|
|
ssl_input = input(f" Use SSL (y/n) [y]: ").strip().lower()
|
|
use_ssl = ssl_input != 'n'
|
|
|
|
# Ask about sudo - default to yes for full module support
|
|
print(f"\n{Colors.DIM} Note: Running with sudo enables raw socket modules (SYN scan, etc.){Colors.RESET}")
|
|
sudo_input = input(f" Run with sudo (y/n) [y]: ").strip().lower()
|
|
use_sudo = sudo_input != 'n'
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
print(f"\n{Colors.YELLOW} [!] Setup cancelled{Colors.RESET}")
|
|
return False
|
|
|
|
# Save settings
|
|
self.save_settings(host, port, username, password, use_ssl)
|
|
|
|
# Step 3: Start server
|
|
if use_sudo:
|
|
print(f"\n{Colors.WHITE} Starting msfrpcd server with sudo...{Colors.RESET}")
|
|
print(f"{Colors.DIM} (You may be prompted for your password){Colors.RESET}")
|
|
else:
|
|
print(f"\n{Colors.WHITE} Starting msfrpcd server...{Colors.RESET}")
|
|
|
|
if not self.start_server(username, password, host, port, use_ssl, use_sudo):
|
|
print(f"{Colors.RED} [X] Failed to start msfrpcd server{Colors.RESET}")
|
|
return False
|
|
|
|
print(f"{Colors.GREEN} [+] Server started on {host}:{port}{Colors.RESET}")
|
|
|
|
# Step 4: Connect
|
|
print(f"{Colors.WHITE} Connecting to server...{Colors.RESET}")
|
|
try:
|
|
self.connect(password)
|
|
version = self.rpc.get_version()
|
|
print(f"{Colors.GREEN} [+] Connected to Metasploit {version.get('version', 'Unknown')}{Colors.RESET}")
|
|
return True
|
|
except MSFError as e:
|
|
print(f"{Colors.RED} [X] Connection failed: {e}{Colors.RESET}")
|
|
return False
|
|
|
|
def set_autoconnect(self, enabled: bool):
|
|
"""Enable or disable autoconnect on startup."""
|
|
self._ensure_config_section()
|
|
self.config.set('msf', 'autoconnect', str(enabled).lower())
|
|
self.config.save()
|
|
|
|
|
|
# Global MSF manager instance
|
|
_msf_manager: Optional[MSFManager] = None
|
|
|
|
|
|
def get_msf_manager() -> MSFManager:
|
|
"""Get the global MSF manager instance."""
|
|
global _msf_manager
|
|
if _msf_manager is None:
|
|
_msf_manager = MSFManager()
|
|
return _msf_manager
|
|
|
|
|
|
def msf_startup_autoconnect(skip_if_disabled: bool = True) -> bool:
|
|
"""Perform MSF autoconnect during application startup.
|
|
|
|
This is the main entry point for the autoconnect feature.
|
|
Call this during application initialization.
|
|
|
|
Args:
|
|
skip_if_disabled: If True, skip autoconnect if disabled in config
|
|
|
|
Returns:
|
|
True if connected successfully, False otherwise
|
|
"""
|
|
msf = get_msf_manager()
|
|
settings = msf.get_settings()
|
|
|
|
# Check if autoconnect is enabled
|
|
if skip_if_disabled and not settings.get('autoconnect', True):
|
|
print(f"{Colors.DIM} MSF autoconnect disabled in settings{Colors.RESET}")
|
|
return False
|
|
|
|
# Check if msgpack is available
|
|
if not MSGPACK_AVAILABLE:
|
|
print(f"{Colors.YELLOW}[!] msgpack not installed - MSF features disabled{Colors.RESET}")
|
|
print(f"{Colors.DIM} Install with: pip install msgpack{Colors.RESET}")
|
|
return False
|
|
|
|
return msf.autoconnect()
|
|
|
|
|
|
def msf_quick_connect(username: str = None, password: str = None,
|
|
host: str = "127.0.0.1", port: int = 55553,
|
|
use_ssl: bool = True, kill_existing: bool = True,
|
|
use_sudo: bool = True) -> bool:
|
|
"""Quick non-interactive MSF server setup and connection.
|
|
|
|
Useful for scripting or when credentials are already known.
|
|
|
|
Args:
|
|
username: RPC username (default: msf)
|
|
password: RPC password (required)
|
|
host: Host address
|
|
port: RPC port
|
|
use_ssl: Use SSL connection
|
|
kill_existing: Kill any existing msfrpcd server first
|
|
use_sudo: Run msfrpcd with sudo (required for raw socket modules)
|
|
|
|
Returns:
|
|
True if connected successfully
|
|
"""
|
|
if not password:
|
|
print(f"{Colors.RED}[X] Password required for msf_quick_connect{Colors.RESET}")
|
|
return False
|
|
|
|
if not MSGPACK_AVAILABLE:
|
|
print(f"{Colors.RED}[X] msgpack not installed{Colors.RESET}")
|
|
return False
|
|
|
|
username = username or "msf"
|
|
msf = get_msf_manager()
|
|
|
|
# Kill existing if requested
|
|
if kill_existing:
|
|
is_running, _ = msf.detect_server()
|
|
if is_running:
|
|
print(f"{Colors.WHITE}[*] Stopping existing msfrpcd...{Colors.RESET}")
|
|
msf.kill_server(use_sudo=use_sudo)
|
|
|
|
# Save and start
|
|
msf.save_settings(host, port, username, password, use_ssl)
|
|
|
|
print(f"{Colors.WHITE}[*] Starting msfrpcd{' with sudo' if use_sudo else ''}...{Colors.RESET}")
|
|
if not msf.start_server(username, password, host, port, use_ssl, use_sudo):
|
|
return False
|
|
|
|
print(f"{Colors.WHITE}[*] Connecting...{Colors.RESET}")
|
|
try:
|
|
msf.connect(password)
|
|
print(f"{Colors.GREEN}[+] Connected to Metasploit{Colors.RESET}")
|
|
return True
|
|
except MSFError as e:
|
|
print(f"{Colors.RED}[X] Connection failed: {e}{Colors.RESET}")
|
|
return False
|