1d95a868a0
- deploy_phishing.py: undefined extra_vars on orchestration playbook - freebird/main_menu.py: set_variable() called as function, is a method; fix to config.prompt_set_variable() - freebird/tool_manager.py: shell=True with f-string paths; replace with direct pip binary call - certipy-enum.py: password exposed in process list and logs; pass via env var - cleanup_engine.py: load_vars_file() called with path string, expects provider name - ioc-u.py: scapy import not guarded; tool crashed if scapy missing - um_ops.py + um-vault.py: SQL table names f-stringed directly; quote identifiers - ops-logger.sh: 63-line duplicate function block; removed second copy; quote config_script var - recon_tools.py: ir-sandbox + link-sandbox not in TOOLS dict; forensics menu option 5 hidden from user - um-crack.py: missing --scope flag inconsistent with all other Umbra tools - heartbeat_ingest.py: openssl subprocess not catching FileNotFoundError; wrap with clear error
873 lines
32 KiB
Python
873 lines
32 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Recon & Red Team Tools — c2itall integration module
|
|
Provides local launch + remote deployment for Umbra suite and custom Red Team tools.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import glob
|
|
from datetime import datetime
|
|
|
|
# Add parent paths for imports
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
from utils.common import COLORS, clear_screen, print_banner, wait_for_input
|
|
|
|
# ─── Tool Path Registry ─────────────────────────────────────────────────────
|
|
C2ITALL_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
UMBRA_PATH = os.path.join(C2ITALL_ROOT, "tools", "umbra")
|
|
REDTEAM_PATH = os.path.join(C2ITALL_ROOT, "tools", "redteam")
|
|
|
|
TOOLS = {
|
|
# Umbra tools
|
|
"umbra-launcher": {
|
|
"path": os.path.join(UMBRA_PATH, "umbra.py"),
|
|
"type": "python",
|
|
"desc": "Unified Umbra Launcher",
|
|
"deps": ["rich", "click", "aiohttp", "aiohttp-socks", "pillow", "beautifulsoup4"],
|
|
},
|
|
"um-scan": {
|
|
"path": os.path.join(UMBRA_PATH, "um-scan.py"),
|
|
"type": "python",
|
|
"desc": "TCP Port Scanner",
|
|
"deps": ["rich", "click", "aiohttp"],
|
|
},
|
|
"um-api": {
|
|
"path": os.path.join(UMBRA_PATH, "um-api.py"),
|
|
"type": "python",
|
|
"desc": "API Endpoint Discovery",
|
|
"deps": ["rich", "click", "aiohttp", "aiohttp-socks", "beautifulsoup4"],
|
|
},
|
|
"um-intel": {
|
|
"path": os.path.join(UMBRA_PATH, "um-intel.py"),
|
|
"type": "python",
|
|
"desc": "Passive OSINT Aggregator",
|
|
"deps": ["rich", "click", "aiohttp", "aiohttp-socks"],
|
|
},
|
|
"um-enum": {
|
|
"path": os.path.join(UMBRA_PATH, "um-enum.py"),
|
|
"type": "python",
|
|
"desc": "Predictive URL Enumeration",
|
|
"deps": ["rich", "click", "aiohttp", "aiohttp-socks", "beautifulsoup4"],
|
|
},
|
|
"um-fuzz": {
|
|
"path": os.path.join(UMBRA_PATH, "um-fuzz.py"),
|
|
"type": "python",
|
|
"desc": "Web Path Discovery",
|
|
"deps": ["rich", "click", "aiohttp", "aiohttp-socks"],
|
|
},
|
|
"um-hash": {
|
|
"path": os.path.join(UMBRA_PATH, "um-hash.py"),
|
|
"type": "python",
|
|
"desc": "Gravatar Hash Extraction",
|
|
"deps": ["rich", "click", "aiohttp", "aiohttp-socks", "beautifulsoup4"],
|
|
},
|
|
"um-wp": {
|
|
"path": os.path.join(UMBRA_PATH, "um-wp.py"),
|
|
"type": "python",
|
|
"desc": "WordPress Detection",
|
|
"deps": ["rich", "click", "aiohttp", "aiohttp-socks"],
|
|
},
|
|
"um-crack": {
|
|
"path": os.path.join(UMBRA_PATH, "um-crack.py"),
|
|
"type": "python",
|
|
"desc": "Hash Cracker",
|
|
"deps": ["rich", "click"],
|
|
},
|
|
"um-exif": {
|
|
"path": os.path.join(UMBRA_PATH, "um-exif.py"),
|
|
"type": "python",
|
|
"desc": "EXIF Metadata Extraction",
|
|
"deps": ["rich", "click", "aiohttp", "aiohttp-socks", "pillow"],
|
|
},
|
|
"um-vault": {
|
|
"path": os.path.join(UMBRA_PATH, "um-vault.py"),
|
|
"type": "python",
|
|
"desc": "Master Database Aggregator",
|
|
"deps": ["rich", "click"],
|
|
},
|
|
# Red Team tools
|
|
"trashpanda": {
|
|
"path": os.path.join(REDTEAM_PATH, "trashpanda", "trashpanda.py"),
|
|
"type": "python",
|
|
"desc": "Network Enumeration",
|
|
"deps": [],
|
|
},
|
|
"ioc-u": {
|
|
"path": os.path.join(REDTEAM_PATH, "ioc-u", "ioc-u.py"),
|
|
"type": "python",
|
|
"desc": "Blue Team Detection & Intel",
|
|
"deps": ["scapy", "numpy", "scikit-learn"],
|
|
},
|
|
"lions-share": {
|
|
"path": os.path.join(REDTEAM_PATH, "lions-share", "lions-share.py"),
|
|
"type": "python",
|
|
"desc": "SSHFS Mount & Backup",
|
|
"deps": ["click", "python-crontab"],
|
|
"sys_deps": ["sshfs", "rsync"],
|
|
},
|
|
"micro-scope": {
|
|
"path": os.path.join(REDTEAM_PATH, "micro-scope", "micro-scope.py"),
|
|
"type": "python",
|
|
"desc": "Scope Verification",
|
|
"deps": [],
|
|
},
|
|
"linkedout": {
|
|
"path": os.path.join(REDTEAM_PATH, "linkedout", "linkedout.py"),
|
|
"type": "python",
|
|
"desc": "LinkedIn OSINT",
|
|
"deps": ["selenium", "webdriver-manager", "beautifulsoup4"],
|
|
},
|
|
"ops-logger": {
|
|
"path": os.path.join(REDTEAM_PATH, "ops-logger.sh"),
|
|
"type": "bash",
|
|
"desc": "Terminal Session Logging",
|
|
"deps": [],
|
|
},
|
|
"freebird": {
|
|
"path": os.path.join(REDTEAM_PATH, "freebird", "main.py"),
|
|
"type": "python",
|
|
"desc": "MITRE ATT&CK Framework",
|
|
"deps": ["InquirerPy", "requests", "click"],
|
|
},
|
|
"regex-search": {
|
|
"path": os.path.join(REDTEAM_PATH, "regex-search", "regex-search-tool.py"),
|
|
"type": "python",
|
|
"desc": "Sensitive Data Search",
|
|
"deps": [],
|
|
},
|
|
"certipy-enum": {
|
|
"path": os.path.join(REDTEAM_PATH, "certipy-enum.py"),
|
|
"type": "python",
|
|
"desc": "ADCS Enumeration",
|
|
"deps": [],
|
|
},
|
|
"ping-sweep": {
|
|
"path": os.path.join(REDTEAM_PATH, "ping-sweep.py"),
|
|
"type": "python",
|
|
"desc": "Network Discovery",
|
|
"deps": [],
|
|
"sys_deps": ["nmap"],
|
|
},
|
|
# Forensics / sandbox tools
|
|
"ir-sandbox": {
|
|
"path": os.path.expanduser("~/tools/ir-sandbox/ir-sandbox.py"),
|
|
"type": "python",
|
|
"desc": "Malware Analysis & IR Platform",
|
|
"deps": [],
|
|
},
|
|
"link-sandbox": {
|
|
"path": os.path.expanduser("~/tools/link-sandbox/analyze.py"),
|
|
"type": "python",
|
|
"desc": "Secure URL Analysis",
|
|
"deps": [],
|
|
},
|
|
# Ops tools
|
|
"ops-dashboard": {
|
|
"path": os.path.join(C2ITALL_ROOT, "ops_dashboard.py"),
|
|
"type": "python",
|
|
"desc": "Real-Time Engagement Monitor",
|
|
"deps": ["rich"],
|
|
},
|
|
"ops-bridge": {
|
|
"path": os.path.join(C2ITALL_ROOT, "ops_bridge.py"),
|
|
"type": "python",
|
|
"desc": "Remote State Sync (rsync)",
|
|
"deps": [],
|
|
"sys_deps": ["rsync"],
|
|
},
|
|
"heartbeat-ingest": {
|
|
"path": os.path.join(C2ITALL_ROOT, "heartbeat", "heartbeat_ingest.py"),
|
|
"type": "python",
|
|
"desc": "Heartbeat Receiver Server",
|
|
"deps": [],
|
|
},
|
|
}
|
|
|
|
# Tool groups for menu organization
|
|
UMBRA_TOOLS = [
|
|
"umbra-launcher", "um-scan", "um-api", "um-intel", "um-enum",
|
|
"um-fuzz", "um-hash", "um-wp", "um-crack", "um-exif", "um-vault",
|
|
]
|
|
REDTEAM_TOOLS = [
|
|
"trashpanda", "ioc-u", "lions-share", "micro-scope", "linkedout",
|
|
"ops-logger", "freebird", "regex-search", "certipy-enum", "ping-sweep",
|
|
]
|
|
OPS_TOOLS = ["ops-dashboard", "ops-bridge", "heartbeat-ingest"]
|
|
|
|
# Deployment tracking file
|
|
DEPLOY_LOG = os.path.expanduser("~/.c2itall_tool_deployments.log")
|
|
|
|
|
|
# ─── Utility Functions ──────────────────────────────────────────────────────
|
|
|
|
def check_tool_status(tool_name):
|
|
"""Check if a tool exists locally."""
|
|
info = TOOLS.get(tool_name)
|
|
if not info:
|
|
return False
|
|
return os.path.exists(info["path"])
|
|
|
|
|
|
def launch_local_tool(tool_name):
|
|
"""Launch a tool locally."""
|
|
info = TOOLS.get(tool_name)
|
|
if not info:
|
|
print(f"{COLORS['RED']}Unknown tool: {tool_name}{COLORS['RESET']}")
|
|
return
|
|
|
|
if not os.path.exists(info["path"]):
|
|
print(f"{COLORS['RED']}Tool not found: {info['path']}{COLORS['RESET']}")
|
|
wait_for_input()
|
|
return
|
|
|
|
tool_dir = os.path.dirname(info["path"])
|
|
|
|
if info["type"] == "python":
|
|
cmd = [sys.executable, info["path"]]
|
|
elif info["type"] == "bash":
|
|
cmd = ["bash", info["path"]]
|
|
else:
|
|
print(f"{COLORS['RED']}Unknown tool type: {info['type']}{COLORS['RESET']}")
|
|
return
|
|
|
|
print(f"\n{COLORS['CYAN']}Launching {tool_name}...{COLORS['RESET']}\n")
|
|
try:
|
|
subprocess.run(cmd, cwd=tool_dir)
|
|
except KeyboardInterrupt:
|
|
print(f"\n{COLORS['YELLOW']}Tool interrupted{COLORS['RESET']}")
|
|
except Exception as e:
|
|
print(f"{COLORS['RED']}Error launching {tool_name}: {e}{COLORS['RESET']}")
|
|
wait_for_input()
|
|
|
|
|
|
def log_deployment(host, tools_deployed, ssh_user, ssh_key):
|
|
"""Log a tool deployment for future SSH & Run."""
|
|
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
with open(DEPLOY_LOG, "a") as f:
|
|
tools_str = ",".join(tools_deployed)
|
|
f.write(f"{ts}|{host}|{ssh_user}|{ssh_key}|{tools_str}\n")
|
|
|
|
|
|
def get_deployments():
|
|
"""Read deployment log and return list of deployments."""
|
|
if not os.path.exists(DEPLOY_LOG):
|
|
return []
|
|
deployments = []
|
|
with open(DEPLOY_LOG) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
parts = line.split("|")
|
|
if len(parts) >= 5:
|
|
deployments.append({
|
|
"timestamp": parts[0],
|
|
"host": parts[1],
|
|
"ssh_user": parts[2],
|
|
"ssh_key": parts[3],
|
|
"tools": parts[4].split(","),
|
|
})
|
|
return deployments
|
|
|
|
|
|
def get_active_attack_boxes():
|
|
"""Get active attack box deployments from c2itall logs."""
|
|
hosts = []
|
|
log_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'logs')
|
|
info_files = glob.glob(os.path.join(log_dir, "deployment_info_*.txt"))
|
|
|
|
for info_file in info_files:
|
|
try:
|
|
with open(info_file) as f:
|
|
content = f.read()
|
|
# Extract IP and SSH info
|
|
ip = None
|
|
ssh_user = None
|
|
ssh_key = None
|
|
for line in content.splitlines():
|
|
line = line.strip()
|
|
if "IP:" in line or "attack_box_ip:" in line:
|
|
ip = line.split(":")[-1].strip().strip('"')
|
|
elif "SSH User:" in line:
|
|
ssh_user = line.split(":")[-1].strip()
|
|
elif "SSH Key:" in line:
|
|
ssh_key = line.split(":")[-1].strip()
|
|
if ip:
|
|
hosts.append({
|
|
"ip": ip,
|
|
"ssh_user": ssh_user or "root",
|
|
"ssh_key": ssh_key,
|
|
"source": os.path.basename(info_file),
|
|
})
|
|
except Exception:
|
|
pass
|
|
return hosts
|
|
|
|
|
|
# ─── Deployment Functions ───────────────────────────────────────────────────
|
|
|
|
def select_target_host():
|
|
"""Select a target host from active deployments or manual entry."""
|
|
print(f"\n{COLORS['CYAN']}Select target machine:{COLORS['RESET']}")
|
|
|
|
# Check c2itall active deployments
|
|
attack_boxes = get_active_attack_boxes()
|
|
if attack_boxes:
|
|
print(f"\n {COLORS['GREEN']}Active c2itall deployments:{COLORS['RESET']}")
|
|
for i, box in enumerate(attack_boxes, 1):
|
|
print(f" {i}) {box['ip']} ({box['ssh_user']}@) — {box['source']}")
|
|
print(f" {len(attack_boxes) + 1}) Manual entry")
|
|
|
|
choice = input(f"\n Select: ").strip()
|
|
try:
|
|
idx = int(choice) - 1
|
|
if 0 <= idx < len(attack_boxes):
|
|
box = attack_boxes[idx]
|
|
return box["ip"], box["ssh_user"], box.get("ssh_key")
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
print(f" {COLORS['YELLOW']}No active c2itall deployments found{COLORS['RESET']}")
|
|
|
|
# Manual entry
|
|
host = input(f"\n Enter target (user@host or host): ").strip()
|
|
if not host:
|
|
return None, None, None
|
|
|
|
if "@" in host:
|
|
ssh_user, host = host.split("@", 1)
|
|
else:
|
|
ssh_user = input(f" SSH user [{COLORS['CYAN']}root{COLORS['RESET']}]: ").strip() or "root"
|
|
|
|
ssh_key = input(f" SSH key path (blank for default): ").strip() or None
|
|
return host, ssh_user, ssh_key
|
|
|
|
|
|
def _build_ssh_cmd(host, ssh_user, ssh_key, command=None, interactive=False):
|
|
"""Build an SSH command list."""
|
|
cmd = ["ssh"]
|
|
if ssh_key:
|
|
cmd.extend(["-i", ssh_key])
|
|
# Use accept-new: trust on first connect, reject if key changes
|
|
known_hosts = os.path.expanduser(f"~/.ssh/c2deploy_recon_known_hosts")
|
|
cmd.extend([
|
|
"-o", "StrictHostKeyChecking=accept-new",
|
|
"-o", f"UserKnownHostsFile={known_hosts}",
|
|
"-o", "IdentitiesOnly=yes",
|
|
])
|
|
if interactive:
|
|
cmd.append("-t")
|
|
cmd.append(f"{ssh_user}@{host}")
|
|
if command:
|
|
cmd.append(command)
|
|
return cmd
|
|
|
|
|
|
def _build_scp_cmd(ssh_key=None):
|
|
"""Build base SCP command with consistent SSH options."""
|
|
known_hosts = os.path.expanduser(f"~/.ssh/c2deploy_recon_known_hosts")
|
|
cmd = ["scp", "-o", "StrictHostKeyChecking=accept-new", "-o", f"UserKnownHostsFile={known_hosts}"]
|
|
if ssh_key:
|
|
cmd.extend(["-i", ssh_key])
|
|
return cmd
|
|
|
|
|
|
def deploy_umbra_remote(host, ssh_user, ssh_key):
|
|
"""Deploy Umbra suite to remote machine."""
|
|
print(f"\n{COLORS['CYAN']}Deploying Umbra to {ssh_user}@{host}...{COLORS['RESET']}")
|
|
|
|
# Create remote directory
|
|
ssh_cmd = _build_ssh_cmd(host, ssh_user, ssh_key, "mkdir -p /opt/umbra")
|
|
result = subprocess.run(ssh_cmd, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
print(f"{COLORS['RED']}Failed to create remote directory: {result.stderr}{COLORS['RESET']}")
|
|
return False
|
|
|
|
# SCP Umbra files
|
|
scp_cmd = _build_scp_cmd(ssh_key)
|
|
|
|
# Copy all Python files and wordlists
|
|
py_files = glob.glob(os.path.join(UMBRA_PATH, "*.py"))
|
|
if not py_files:
|
|
print(f"{COLORS['RED']}No Umbra Python files found at {UMBRA_PATH}{COLORS['RESET']}")
|
|
return False
|
|
|
|
scp_cmd.extend(py_files)
|
|
scp_cmd.append(f"{ssh_user}@{host}:/opt/umbra/")
|
|
|
|
print(f" Copying {len(py_files)} Python files...")
|
|
result = subprocess.run(scp_cmd, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
print(f"{COLORS['RED']}SCP failed: {result.stderr}{COLORS['RESET']}")
|
|
return False
|
|
|
|
# Copy wordlists if they exist
|
|
wordlist_dir = os.path.join(UMBRA_PATH, "wordlists")
|
|
if os.path.isdir(wordlist_dir):
|
|
ssh_cmd = _build_ssh_cmd(host, ssh_user, ssh_key, "mkdir -p /opt/umbra/wordlists")
|
|
subprocess.run(ssh_cmd, capture_output=True)
|
|
|
|
scp_wl = _build_scp_cmd(ssh_key)
|
|
scp_wl.extend(["-r", wordlist_dir + "/"])
|
|
scp_wl.append(f"{ssh_user}@{host}:/opt/umbra/wordlists/")
|
|
print(f" Copying wordlists...")
|
|
subprocess.run(scp_wl, capture_output=True)
|
|
|
|
# Install pip dependencies
|
|
all_deps = set()
|
|
for tool in UMBRA_TOOLS:
|
|
info = TOOLS.get(tool, {})
|
|
all_deps.update(info.get("deps", []))
|
|
|
|
if all_deps:
|
|
deps_str = " ".join(sorted(all_deps))
|
|
print(f" Installing dependencies: {deps_str}")
|
|
install_cmd = _build_ssh_cmd(host, ssh_user, ssh_key,
|
|
f"pip3 install {deps_str} 2>/dev/null || pip install {deps_str}")
|
|
result = subprocess.run(install_cmd, capture_output=True, text=True, timeout=120)
|
|
if result.returncode != 0:
|
|
print(f"{COLORS['YELLOW']}Warning: Some deps may have failed: {result.stderr[:200]}{COLORS['RESET']}")
|
|
|
|
# Verify
|
|
print(f" Verifying installation...")
|
|
verify_cmd = _build_ssh_cmd(host, ssh_user, ssh_key,
|
|
"python3 -m py_compile /opt/umbra/um_tui.py && echo 'VERIFY_OK'")
|
|
result = subprocess.run(verify_cmd, capture_output=True, text=True)
|
|
if "VERIFY_OK" in result.stdout:
|
|
print(f"{COLORS['GREEN']}Umbra deployed successfully to {host}:/opt/umbra/{COLORS['RESET']}")
|
|
log_deployment(host, UMBRA_TOOLS, ssh_user, ssh_key or "default")
|
|
return True
|
|
else:
|
|
print(f"{COLORS['RED']}Verification failed{COLORS['RESET']}")
|
|
return False
|
|
|
|
|
|
def deploy_redteam_remote(host, ssh_user, ssh_key):
|
|
"""Deploy Red Team tools to remote machine."""
|
|
print(f"\n{COLORS['CYAN']}Deploying Red Team tools to {ssh_user}@{host}...{COLORS['RESET']}")
|
|
|
|
# Create remote directories
|
|
ssh_cmd = _build_ssh_cmd(host, ssh_user, ssh_key,
|
|
"mkdir -p /opt/redteam/{trashpanda,ioc-u,lions-share,micro-scope,linkedout,freebird,regex-search}")
|
|
subprocess.run(ssh_cmd, capture_output=True)
|
|
|
|
deployed = []
|
|
for tool_name in REDTEAM_TOOLS:
|
|
info = TOOLS.get(tool_name)
|
|
if not info:
|
|
continue
|
|
|
|
src_path = info["path"]
|
|
if not os.path.exists(src_path):
|
|
print(f" {COLORS['YELLOW']}Skipping {tool_name}: not found locally{COLORS['RESET']}")
|
|
continue
|
|
|
|
# Determine remote path
|
|
remote_dir = f"/opt/redteam/{tool_name}"
|
|
src_dir = os.path.dirname(src_path)
|
|
|
|
scp_cmd = _build_scp_cmd(ssh_key)
|
|
|
|
# If it's a single file, just copy the file
|
|
if os.path.isfile(src_path) and src_dir == os.path.dirname(src_path):
|
|
ssh_mkdir = _build_ssh_cmd(host, ssh_user, ssh_key, f"mkdir -p {remote_dir}")
|
|
subprocess.run(ssh_mkdir, capture_output=True)
|
|
scp_cmd.extend([src_path, f"{ssh_user}@{host}:{remote_dir}/"])
|
|
else:
|
|
scp_cmd.extend(["-r", src_dir + "/"])
|
|
scp_cmd.append(f"{ssh_user}@{host}:{remote_dir}/")
|
|
|
|
result = subprocess.run(scp_cmd, capture_output=True, text=True)
|
|
if result.returncode == 0:
|
|
print(f" {COLORS['GREEN']}Deployed: {tool_name}{COLORS['RESET']}")
|
|
deployed.append(tool_name)
|
|
else:
|
|
print(f" {COLORS['RED']}Failed: {tool_name} — {result.stderr[:100]}{COLORS['RESET']}")
|
|
|
|
# Install deps for deployed tools
|
|
all_deps = set()
|
|
for tool_name in deployed:
|
|
info = TOOLS.get(tool_name, {})
|
|
all_deps.update(info.get("deps", []))
|
|
|
|
if all_deps:
|
|
deps_str = " ".join(sorted(all_deps))
|
|
print(f"\n Installing dependencies: {deps_str}")
|
|
install_cmd = _build_ssh_cmd(host, ssh_user, ssh_key,
|
|
f"pip3 install {deps_str} 2>/dev/null || pip install {deps_str}")
|
|
subprocess.run(install_cmd, capture_output=True, text=True, timeout=120)
|
|
|
|
if deployed:
|
|
print(f"\n{COLORS['GREEN']}Deployed {len(deployed)} Red Team tools to {host}{COLORS['RESET']}")
|
|
log_deployment(host, deployed, ssh_user, ssh_key or "default")
|
|
return len(deployed) > 0
|
|
|
|
|
|
def ssh_run_tool(host, ssh_user, ssh_key, tool_path, interactive=True):
|
|
"""SSH into a remote machine and run a tool."""
|
|
if interactive:
|
|
cmd = _build_ssh_cmd(host, ssh_user, ssh_key, f"cd {os.path.dirname(tool_path)} && python3 {tool_path}",
|
|
interactive=True)
|
|
else:
|
|
cmd = _build_ssh_cmd(host, ssh_user, ssh_key, f"python3 {tool_path}")
|
|
|
|
print(f"\n{COLORS['CYAN']}Connecting to {ssh_user}@{host}...{COLORS['RESET']}")
|
|
try:
|
|
subprocess.run(cmd)
|
|
except KeyboardInterrupt:
|
|
print(f"\n{COLORS['YELLOW']}Session ended{COLORS['RESET']}")
|
|
|
|
|
|
def check_remote_tools(host, ssh_user, ssh_key):
|
|
"""Check which tools are installed on a remote machine."""
|
|
print(f"\n{COLORS['CYAN']}Checking tools on {ssh_user}@{host}...{COLORS['RESET']}\n")
|
|
|
|
# Check Umbra
|
|
check_cmd = _build_ssh_cmd(host, ssh_user, ssh_key,
|
|
"ls /opt/umbra/*.py 2>/dev/null && echo '---DIVIDER---' && ls /opt/redteam/*/ 2>/dev/null")
|
|
result = subprocess.run(check_cmd, capture_output=True, text=True)
|
|
|
|
if result.returncode != 0:
|
|
print(f"{COLORS['YELLOW']}Could not connect or no tools found{COLORS['RESET']}")
|
|
return
|
|
|
|
output = result.stdout
|
|
if "---DIVIDER---" in output:
|
|
umbra_part, redteam_part = output.split("---DIVIDER---", 1)
|
|
else:
|
|
umbra_part = output
|
|
redteam_part = ""
|
|
|
|
print(f" {COLORS['WHITE']}Umbra Suite:{COLORS['RESET']}")
|
|
if umbra_part.strip():
|
|
for line in umbra_part.strip().splitlines():
|
|
fname = os.path.basename(line.strip())
|
|
print(f" {COLORS['GREEN']}FOUND{COLORS['RESET']} {fname}")
|
|
else:
|
|
print(f" {COLORS['RED']}Not installed{COLORS['RESET']}")
|
|
|
|
print(f"\n {COLORS['WHITE']}Red Team Tools:{COLORS['RESET']}")
|
|
if redteam_part.strip():
|
|
for line in redteam_part.strip().splitlines():
|
|
print(f" {COLORS['GREEN']}FOUND{COLORS['RESET']} {line.strip()}")
|
|
else:
|
|
print(f" {COLORS['RED']}Not installed{COLORS['RESET']}")
|
|
|
|
|
|
# ─── Menu Functions ─────────────────────────────────────────────────────────
|
|
|
|
def umbra_submenu():
|
|
"""Submenu for Umbra Reconnaissance Suite."""
|
|
while True:
|
|
clear_screen()
|
|
print_banner()
|
|
print(f"{COLORS['WHITE']}UMBRA RECONNAISSANCE SUITE{COLORS['RESET']}")
|
|
print(f"{COLORS['WHITE']}=========================={COLORS['RESET']}")
|
|
print(f"1) um-scan — TCP Port Scanner")
|
|
print(f"2) um-api — API Endpoint Discovery")
|
|
print(f"3) um-intel — OSINT Aggregator")
|
|
print(f"4) um-enum — URL Enumeration")
|
|
print(f"5) um-fuzz — Directory Fuzzer")
|
|
print(f"6) um-hash — Hash Extraction")
|
|
print(f"7) um-wp — WordPress Scanner")
|
|
print(f"8) um-crack — Password Cracker")
|
|
print(f"9) um-exif — EXIF Metadata")
|
|
print(f"10) um-vault — Database Manager")
|
|
print(f"99) Back")
|
|
|
|
choice = input(f"\nSelect: ").strip()
|
|
|
|
tool_map = {
|
|
"1": "um-scan", "2": "um-api", "3": "um-intel",
|
|
"4": "um-enum", "5": "um-fuzz", "6": "um-hash",
|
|
"7": "um-wp", "8": "um-crack", "9": "um-exif",
|
|
"10": "um-vault",
|
|
}
|
|
|
|
if choice == "99":
|
|
return
|
|
elif choice in tool_map:
|
|
launch_local_tool(tool_map[choice])
|
|
wait_for_input()
|
|
else:
|
|
print(f"\n{COLORS['RED']}Invalid option{COLORS['RESET']}")
|
|
wait_for_input()
|
|
|
|
|
|
def redteam_submenu():
|
|
"""Submenu for Red Team Operations Tools."""
|
|
while True:
|
|
clear_screen()
|
|
print_banner()
|
|
print(f"{COLORS['WHITE']}RED TEAM OPERATIONS TOOLS{COLORS['RESET']}")
|
|
print(f"{COLORS['WHITE']}========================={COLORS['RESET']}")
|
|
print(f"1) TrashPanda — Network Enumeration")
|
|
print(f"2) IOC-U — Blue Team Detection & Intel")
|
|
print(f"3) Lions-Share — SSHFS Mount & Backup")
|
|
print(f"4) micro-scope — Scope Verification")
|
|
print(f"5) linkedout — LinkedIn OSINT")
|
|
print(f"6) ops-logger — Terminal Session Logging")
|
|
print(f"7) freebird — MITRE ATT&CK Framework")
|
|
print(f"8) regex-search — Sensitive Data Search")
|
|
print(f"9) certipy-enum — ADCS Enumeration")
|
|
print(f"10) ping-sweep — Network Discovery")
|
|
print(f"99) Back")
|
|
|
|
choice = input(f"\nSelect: ").strip()
|
|
|
|
tool_map = {
|
|
"1": "trashpanda", "2": "ioc-u", "3": "lions-share",
|
|
"4": "micro-scope", "5": "linkedout", "6": "ops-logger",
|
|
"7": "freebird", "8": "regex-search", "9": "certipy-enum",
|
|
"10": "ping-sweep",
|
|
}
|
|
|
|
if choice == "99":
|
|
return
|
|
elif choice in tool_map:
|
|
launch_local_tool(tool_map[choice])
|
|
wait_for_input()
|
|
else:
|
|
print(f"\n{COLORS['RED']}Invalid option{COLORS['RESET']}")
|
|
wait_for_input()
|
|
|
|
|
|
def remote_submenu():
|
|
"""Submenu for Remote Deployment."""
|
|
while True:
|
|
clear_screen()
|
|
print_banner()
|
|
print(f"{COLORS['WHITE']}REMOTE DEPLOYMENT{COLORS['RESET']}")
|
|
print(f"{COLORS['WHITE']}================={COLORS['RESET']}")
|
|
print(f"1) Deploy Umbra Suite to Remote Machine")
|
|
print(f"2) Deploy Red Team Tools to Remote Machine")
|
|
print(f"3) Deploy All Tools to Remote Machine")
|
|
print(f"4) SSH into Remote & Run Umbra")
|
|
print(f"5) SSH into Remote & Run Tool")
|
|
print(f"6) Check Remote Tool Status")
|
|
print(f"99) Back")
|
|
|
|
choice = input(f"\nSelect: ").strip()
|
|
|
|
if choice == "99":
|
|
return
|
|
|
|
elif choice == "1":
|
|
host, ssh_user, ssh_key = select_target_host()
|
|
if host:
|
|
deploy_umbra_remote(host, ssh_user, ssh_key)
|
|
wait_for_input()
|
|
|
|
elif choice == "2":
|
|
host, ssh_user, ssh_key = select_target_host()
|
|
if host:
|
|
deploy_redteam_remote(host, ssh_user, ssh_key)
|
|
wait_for_input()
|
|
|
|
elif choice == "3":
|
|
host, ssh_user, ssh_key = select_target_host()
|
|
if host:
|
|
deploy_umbra_remote(host, ssh_user, ssh_key)
|
|
deploy_redteam_remote(host, ssh_user, ssh_key)
|
|
wait_for_input()
|
|
|
|
elif choice == "4":
|
|
# SSH into remote and run Umbra launcher
|
|
deployments = get_deployments()
|
|
if not deployments:
|
|
host, ssh_user, ssh_key = select_target_host()
|
|
else:
|
|
print(f"\n{COLORS['CYAN']}Known deployments:{COLORS['RESET']}")
|
|
for i, d in enumerate(deployments, 1):
|
|
print(f" {i}) {d['host']} ({d['ssh_user']}@) — deployed {d['timestamp']}")
|
|
print(f" {len(deployments) + 1}) Other host")
|
|
|
|
sel = input(f"\n Select: ").strip()
|
|
try:
|
|
idx = int(sel) - 1
|
|
if 0 <= idx < len(deployments):
|
|
d = deployments[idx]
|
|
host = d["host"]
|
|
ssh_user = d["ssh_user"]
|
|
ssh_key = d["ssh_key"] if d["ssh_key"] != "default" else None
|
|
else:
|
|
host, ssh_user, ssh_key = select_target_host()
|
|
except ValueError:
|
|
host, ssh_user, ssh_key = select_target_host()
|
|
|
|
if host:
|
|
ssh_run_tool(host, ssh_user, ssh_key, "/opt/umbra/umbra.py")
|
|
wait_for_input()
|
|
|
|
elif choice == "5":
|
|
# SSH into remote and run a specific tool
|
|
host, ssh_user, ssh_key = select_target_host()
|
|
if not host:
|
|
wait_for_input()
|
|
continue
|
|
|
|
print(f"\n{COLORS['CYAN']}Select tool to run:{COLORS['RESET']}")
|
|
all_tools = list(TOOLS.keys())
|
|
for i, name in enumerate(all_tools, 1):
|
|
info = TOOLS[name]
|
|
status = f"{COLORS['GREEN']}LOCAL{COLORS['RESET']}" if os.path.exists(info["path"]) else f"{COLORS['RED']}MISSING{COLORS['RESET']}"
|
|
print(f" {i:2d}) {name:16s} — {info['desc']} [{status}]")
|
|
|
|
sel = input(f"\n Select tool #: ").strip()
|
|
try:
|
|
idx = int(sel) - 1
|
|
if 0 <= idx < len(all_tools):
|
|
tool_name = all_tools[idx]
|
|
info = TOOLS[tool_name]
|
|
# Determine remote path
|
|
if tool_name in UMBRA_TOOLS:
|
|
remote_path = f"/opt/umbra/{os.path.basename(info['path'])}"
|
|
else:
|
|
remote_path = f"/opt/redteam/{tool_name}/{os.path.basename(info['path'])}"
|
|
ssh_run_tool(host, ssh_user, ssh_key, remote_path)
|
|
except (ValueError, IndexError):
|
|
print(f"{COLORS['RED']}Invalid selection{COLORS['RESET']}")
|
|
wait_for_input()
|
|
|
|
elif choice == "6":
|
|
host, ssh_user, ssh_key = select_target_host()
|
|
if host:
|
|
check_remote_tools(host, ssh_user, ssh_key)
|
|
wait_for_input()
|
|
|
|
else:
|
|
print(f"\n{COLORS['RED']}Invalid option{COLORS['RESET']}")
|
|
wait_for_input()
|
|
|
|
|
|
def ops_submenu():
|
|
"""Submenu for Ops Dashboard & Engagement Management."""
|
|
while True:
|
|
clear_screen()
|
|
print_banner()
|
|
print(f"{COLORS['WHITE']}OPS & ENGAGEMENT MANAGEMENT{COLORS['RESET']}")
|
|
print(f"{COLORS['WHITE']}============================{COLORS['RESET']}")
|
|
print(f"1) Ops Dashboard — Real-Time Engagement Monitor")
|
|
print(f"2) Ops Bridge — Sync State from Remote Hosts")
|
|
print(f"3) Heartbeat Ingest — Receive Host Check-Ins")
|
|
print(f"4) Umbra tmux — Launch tmux Workspace")
|
|
print(f"99) Back")
|
|
|
|
choice = input(f"\nSelect: ").strip()
|
|
|
|
if choice == "99":
|
|
return
|
|
elif choice == "1":
|
|
launch_local_tool("ops-dashboard")
|
|
wait_for_input()
|
|
elif choice == "2":
|
|
launch_local_tool("ops-bridge")
|
|
wait_for_input()
|
|
elif choice == "3":
|
|
launch_local_tool("heartbeat-ingest")
|
|
wait_for_input()
|
|
elif choice == "4":
|
|
_launch_umbra_tmux()
|
|
wait_for_input()
|
|
else:
|
|
print(f"\n{COLORS['RED']}Invalid option{COLORS['RESET']}")
|
|
wait_for_input()
|
|
|
|
|
|
def _launch_umbra_tmux():
|
|
"""Prompt for engagement and launch Umbra in tmux mode."""
|
|
engagement = input(f"\n Engagement name: ").strip()
|
|
if not engagement:
|
|
print(f"{COLORS['YELLOW']}No engagement specified.{COLORS['RESET']}")
|
|
return
|
|
umbra_script = os.path.join(UMBRA_PATH, "umbra.py")
|
|
if not os.path.exists(umbra_script):
|
|
print(f"{COLORS['RED']}Umbra launcher not found at {umbra_script}{COLORS['RESET']}")
|
|
return
|
|
try:
|
|
subprocess.run([sys.executable, umbra_script, "-E", engagement, "--tmux", "--dashboard"])
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
|
|
def _forensics_submenu():
|
|
"""Submenu for Forensics & Malware Analysis tools."""
|
|
# Delegate to the forensics module for full functionality
|
|
forensics_module_path = os.path.join(
|
|
os.path.dirname(__file__), '..', 'forensics', 'deploy_forensics.py'
|
|
)
|
|
if os.path.exists(forensics_module_path):
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location('deploy_forensics', forensics_module_path)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
mod.forensics_menu()
|
|
else:
|
|
# Fallback: launch tools directly
|
|
while True:
|
|
clear_screen()
|
|
print_banner()
|
|
print(f"{COLORS['WHITE']}FORENSICS & MALWARE ANALYSIS{COLORS['RESET']}")
|
|
print(f"{COLORS['WHITE']}============================={COLORS['RESET']}")
|
|
print(f"1) IR-Sandbox (Interactive Menu)")
|
|
print(f"2) Link-Sandbox (URL Analysis)")
|
|
print(f"99) Back")
|
|
|
|
choice = input(f"\nSelect: ").strip()
|
|
if choice == "99":
|
|
return
|
|
elif choice == "1":
|
|
launch_local_tool("ir-sandbox")
|
|
wait_for_input()
|
|
elif choice == "2":
|
|
launch_local_tool("link-sandbox")
|
|
wait_for_input()
|
|
else:
|
|
print(f"\n{COLORS['RED']}Invalid option{COLORS['RESET']}")
|
|
wait_for_input()
|
|
|
|
|
|
def recon_tools_menu():
|
|
"""Main entry point — called from c2itall deploy.py tools_menu()."""
|
|
while True:
|
|
clear_screen()
|
|
print_banner()
|
|
print(f"{COLORS['WHITE']}RECON & RED TEAM TOOLS{COLORS['RESET']}")
|
|
print(f"{COLORS['WHITE']}======================{COLORS['RESET']}")
|
|
|
|
# Show local tool status summary
|
|
umbra_found = sum(1 for t in UMBRA_TOOLS if check_tool_status(t))
|
|
redteam_found = sum(1 for t in REDTEAM_TOOLS if check_tool_status(t))
|
|
ops_found = sum(1 for t in OPS_TOOLS if check_tool_status(t))
|
|
print(f" Local: Umbra {COLORS['GREEN']}{umbra_found}/{len(UMBRA_TOOLS)}{COLORS['RESET']} | "
|
|
f"Red Team {COLORS['GREEN']}{redteam_found}/{len(REDTEAM_TOOLS)}{COLORS['RESET']} | "
|
|
f"Ops {COLORS['CYAN']}{ops_found}/{len(OPS_TOOLS)}{COLORS['RESET']}")
|
|
print()
|
|
|
|
print(f"1) Umbra Reconnaissance Suite {COLORS['GREEN']}({umbra_found} tools){COLORS['RESET']}")
|
|
print(f"2) Red Team Operations Tools {COLORS['GREEN']}({redteam_found} tools){COLORS['RESET']}")
|
|
print(f"3) Remote Deployment & SSH")
|
|
print(f"4) Ops & Engagement Management {COLORS['CYAN']}(Dashboard, Bridge, Heartbeat){COLORS['RESET']}")
|
|
print(f"5) Forensics & Sandbox {COLORS['YELLOW']}(IR-Sandbox, Link-Sandbox){COLORS['RESET']}")
|
|
print(f"99) Return to Tools Menu")
|
|
|
|
choice = input(f"\nSelect: ").strip()
|
|
|
|
if choice == "1":
|
|
umbra_submenu()
|
|
elif choice == "2":
|
|
redteam_submenu()
|
|
elif choice == "3":
|
|
remote_submenu()
|
|
elif choice == "4":
|
|
ops_submenu()
|
|
elif choice == "5":
|
|
_forensics_submenu()
|
|
elif choice == "99":
|
|
return
|
|
else:
|
|
print(f"\n{COLORS['RED']}Invalid option{COLORS['RESET']}")
|
|
wait_for_input()
|