Initial public portfolio release
Sanitized version of red team infrastructure automation platform. Operational content (implant pipelines, lures, credential capture) replaced with documented stubs. Architecture and infrastructure automation code intact.
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Claude Bot — c2itall integration module
|
||||
Deploy and manage the Matrix-Claude Code bridge bot locally or on a remote host.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
# 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
|
||||
|
||||
# ─── Path Registry ───────────────────────────────────────────────────────────
|
||||
C2ITALL_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
# Look for claude-bot in the ghost_protocol submodule first, then standalone
|
||||
_SUBMODULE_PATH = os.path.join(C2ITALL_ROOT, 'ghost_protocol', 'claude-bot')
|
||||
_STANDALONE_PATH = os.path.expanduser('~/tools/ghost_protocol/claude-bot')
|
||||
|
||||
CLAUDE_BOT_PATH = _SUBMODULE_PATH if os.path.isdir(_SUBMODULE_PATH) else _STANDALONE_PATH
|
||||
INSTALL_SH = os.path.join(CLAUDE_BOT_PATH, 'deploy', 'install.sh')
|
||||
UNINSTALL_SH = os.path.join(CLAUDE_BOT_PATH, 'deploy', 'uninstall.sh')
|
||||
SERVICE_NAME = 'claude-bot'
|
||||
|
||||
|
||||
# ─── SSH helpers (mirrors recon_tools pattern) ───────────────────────────────
|
||||
|
||||
def _build_ssh_cmd(host, ssh_user, ssh_port, ssh_key, command=None, interactive=False):
|
||||
"""Build an SSH command list."""
|
||||
cmd = ['ssh']
|
||||
if ssh_key:
|
||||
cmd.extend(['-i', ssh_key])
|
||||
cmd.extend(['-p', str(ssh_port)])
|
||||
known_hosts = os.path.expanduser('~/.ssh/c2deploy_claudebot_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_port, ssh_key=None):
|
||||
"""Build base SCP command with consistent SSH options."""
|
||||
known_hosts = os.path.expanduser('~/.ssh/c2deploy_claudebot_known_hosts')
|
||||
cmd = [
|
||||
'scp',
|
||||
'-P', str(ssh_port),
|
||||
'-o', 'StrictHostKeyChecking=accept-new',
|
||||
'-o', f'UserKnownHostsFile={known_hosts}',
|
||||
]
|
||||
if ssh_key:
|
||||
cmd.extend(['-i', ssh_key])
|
||||
return cmd
|
||||
|
||||
|
||||
# ─── Config generation ───────────────────────────────────────────────────────
|
||||
|
||||
def _prompt_bot_config():
|
||||
"""Prompt for all bot configuration values. Returns a dict."""
|
||||
print(f"\n{COLORS['CYAN']}Bot Configuration{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}================={COLORS['RESET']}")
|
||||
|
||||
homeserver = input(f" Matrix homeserver URL [{COLORS['CYAN']}https://matrix.example.com{COLORS['RESET']}]: ").strip()
|
||||
if not homeserver:
|
||||
homeserver = 'https://matrix.example.com'
|
||||
|
||||
bot_user = input(f" Bot Matrix user ID [{COLORS['CYAN']}@bot:matrix.example.com{COLORS['RESET']}]: ").strip()
|
||||
if not bot_user:
|
||||
bot_user = '@bot:matrix.example.com'
|
||||
|
||||
bot_password = input(f" Bot Matrix password: ").strip()
|
||||
|
||||
allowed_raw = input(f" Allowed Matrix user IDs (comma-separated): ").strip()
|
||||
allowed_users = [u.strip() for u in allowed_raw.split(',') if u.strip()] if allowed_raw else []
|
||||
|
||||
print(f"\n Claude backend:")
|
||||
print(f" 1) cli (Claude Code CLI — uses Max plan, no extra cost)")
|
||||
print(f" 2) api (Anthropic API — requires API key)")
|
||||
backend_choice = input(f" Select [1]: ").strip() or '1'
|
||||
backend = 'api' if backend_choice == '2' else 'cli'
|
||||
|
||||
api_key = ''
|
||||
if backend == 'api':
|
||||
api_key = input(f" Anthropic API key: ").strip()
|
||||
|
||||
return {
|
||||
'homeserver': homeserver,
|
||||
'bot_user': bot_user,
|
||||
'bot_password': bot_password,
|
||||
'allowed_users': allowed_users,
|
||||
'backend': backend,
|
||||
'api_key': api_key,
|
||||
}
|
||||
|
||||
|
||||
def _generate_config_yaml(cfg):
|
||||
"""Generate config.yaml content from a config dict.
|
||||
Credentials are NOT written here — they come from env vars at runtime."""
|
||||
allowed_block = '\n'.join(f' - "{u}"' for u in cfg['allowed_users'])
|
||||
if not allowed_block:
|
||||
allowed_block = ' - "@yourusername:matrix.example.com"'
|
||||
|
||||
return f"""matrix:
|
||||
homeserver: "{cfg['homeserver']}"
|
||||
user_id: "{cfg['bot_user']}"
|
||||
password: "${{MATRIX_PASSWORD}}"
|
||||
device_name: "claude-bot"
|
||||
store_path: "/opt/claude-bot/store"
|
||||
|
||||
claude:
|
||||
backend: "{cfg['backend']}"
|
||||
cli_path: "/usr/local/bin/claude"
|
||||
cli_model: "sonnet"
|
||||
cli_max_turns: 10
|
||||
api_key: "${{ANTHROPIC_API_KEY}}"
|
||||
api_model: "claude-sonnet-4-20250514"
|
||||
max_tokens: 4096
|
||||
temperature: 0.7
|
||||
system_prompt: |
|
||||
You are a helpful AI assistant in a Matrix chat room.
|
||||
Be concise. Use markdown formatting when helpful.
|
||||
You have full tool access: bash, file editing, web search, code analysis.
|
||||
The user is interacting from a mobile phone, so keep responses focused
|
||||
and avoid unnecessarily long output.
|
||||
|
||||
sessions:
|
||||
mode: "per_room"
|
||||
max_history: 50
|
||||
ttl_hours: 24
|
||||
|
||||
security:
|
||||
allowed_users:
|
||||
{allowed_block}
|
||||
allowed_rooms: []
|
||||
rate_limit:
|
||||
messages_per_minute: 10
|
||||
tokens_per_hour: 100000
|
||||
max_monthly_cost_usd: 50.0
|
||||
|
||||
logging:
|
||||
level: "INFO"
|
||||
file: "/opt/claude-bot/claude-bot.log"
|
||||
"""
|
||||
|
||||
|
||||
# ─── Local actions ───────────────────────────────────────────────────────────
|
||||
|
||||
def _deploy_local():
|
||||
"""Run install.sh locally, interactively."""
|
||||
if not os.path.exists(INSTALL_SH):
|
||||
print(f"\n{COLORS['RED']}install.sh not found at {INSTALL_SH}{COLORS['RESET']}")
|
||||
print(f"{COLORS['YELLOW']}Clone ghost_protocol first: ~/tools/ghost_protocol/claude-bot{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Deploying claude-bot locally...{COLORS['RESET']}")
|
||||
print(f"{COLORS['YELLOW']}Note: script requires sudo — you may be prompted for your password.{COLORS['RESET']}")
|
||||
print()
|
||||
try:
|
||||
subprocess.run(['sudo', 'bash', INSTALL_SH], cwd=os.path.dirname(INSTALL_SH))
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{COLORS['YELLOW']}Installation interrupted{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _status_local():
|
||||
"""Show systemctl status for claude-bot locally."""
|
||||
print(f"\n{COLORS['CYAN']}Local claude-bot status:{COLORS['RESET']}\n")
|
||||
try:
|
||||
subprocess.run(['systemctl', 'status', SERVICE_NAME, '--no-pager'])
|
||||
except FileNotFoundError:
|
||||
print(f"{COLORS['RED']}systemctl not found — is this a systemd system?{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _uninstall_local():
|
||||
"""Run uninstall.sh locally."""
|
||||
if not os.path.exists(UNINSTALL_SH):
|
||||
print(f"\n{COLORS['RED']}uninstall.sh not found at {UNINSTALL_SH}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
confirm = input(f"\n{COLORS['YELLOW']}Uninstall claude-bot locally? (y/N): {COLORS['RESET']}").strip().lower()
|
||||
if confirm != 'y':
|
||||
print(f"{COLORS['GREEN']}Cancelled{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Uninstalling claude-bot...{COLORS['RESET']}")
|
||||
try:
|
||||
subprocess.run(['sudo', 'bash', UNINSTALL_SH], cwd=os.path.dirname(UNINSTALL_SH))
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{COLORS['YELLOW']}Uninstall interrupted{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
|
||||
# ─── Remote actions ──────────────────────────────────────────────────────────
|
||||
|
||||
def _prompt_remote_target():
|
||||
"""Prompt for host/user/port/key. Returns (host, user, port, key) or None on cancel."""
|
||||
print(f"\n{COLORS['CYAN']}Remote Target{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}============={COLORS['RESET']}")
|
||||
|
||||
host = input(f" Remote host (IP or hostname): ").strip()
|
||||
if not host:
|
||||
print(f"{COLORS['YELLOW']}No host provided{COLORS['RESET']}")
|
||||
return None, None, None, None
|
||||
|
||||
ssh_user = input(f" SSH user [{COLORS['CYAN']}root{COLORS['RESET']}]: ").strip() or 'root'
|
||||
ssh_port_raw = input(f" SSH port [{COLORS['CYAN']}22{COLORS['RESET']}]: ").strip() or '22'
|
||||
try:
|
||||
ssh_port = int(ssh_port_raw)
|
||||
except ValueError:
|
||||
ssh_port = 22
|
||||
|
||||
ssh_key = input(f" SSH key path (blank for default): ").strip() or None
|
||||
return host, ssh_user, ssh_port, ssh_key
|
||||
|
||||
|
||||
def _deploy_remote():
|
||||
"""Copy claude-bot directory to remote, generate config.yaml, run install.sh over SSH."""
|
||||
if not os.path.isdir(CLAUDE_BOT_PATH):
|
||||
print(f"\n{COLORS['RED']}claude-bot source not found at {CLAUDE_BOT_PATH}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
host, ssh_user, ssh_port, ssh_key = _prompt_remote_target()
|
||||
if not host:
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
cfg = _prompt_bot_config()
|
||||
config_yaml = _generate_config_yaml(cfg)
|
||||
|
||||
remote_staging = '/tmp/claude-bot-deploy'
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Copying claude-bot to {ssh_user}@{host}:{remote_staging}...{COLORS['RESET']}")
|
||||
|
||||
# Create remote staging dir
|
||||
mkdir_cmd = _build_ssh_cmd(host, ssh_user, ssh_port, ssh_key,
|
||||
f'rm -rf {remote_staging} && mkdir -p {remote_staging}')
|
||||
result = subprocess.run(mkdir_cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"{COLORS['RED']}Failed to create remote staging dir: {result.stderr}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
# SCP the entire claude-bot directory
|
||||
scp_cmd = _build_scp_cmd(ssh_port, ssh_key)
|
||||
scp_cmd.extend(['-r', CLAUDE_BOT_PATH + '/'])
|
||||
scp_cmd.append(f'{ssh_user}@{host}:{remote_staging}/')
|
||||
result = subprocess.run(scp_cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"{COLORS['RED']}SCP failed: {result.stderr}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
print(f" {COLORS['GREEN']}Files copied{COLORS['RESET']}")
|
||||
|
||||
# Write config.yaml to a tempfile, then SCP it into the staging dir
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as tf:
|
||||
tf.write(config_yaml)
|
||||
tmp_config = tf.name
|
||||
|
||||
try:
|
||||
scp_cfg = _build_scp_cmd(ssh_port, ssh_key)
|
||||
scp_cfg.extend([tmp_config, f'{ssh_user}@{host}:{remote_staging}/config.yaml'])
|
||||
result = subprocess.run(scp_cfg, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"{COLORS['RED']}Failed to copy config.yaml: {result.stderr}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
print(f" {COLORS['GREEN']}config.yaml placed in staging directory{COLORS['RESET']}")
|
||||
finally:
|
||||
os.unlink(tmp_config)
|
||||
|
||||
# Build env for sensitive credentials
|
||||
env_prefix = ''
|
||||
if cfg['bot_password']:
|
||||
env_prefix += f"MATRIX_PASSWORD={cfg['bot_password']!r} "
|
||||
if cfg['api_key']:
|
||||
env_prefix += f"ANTHROPIC_API_KEY={cfg['api_key']!r} "
|
||||
|
||||
# Run install.sh on the remote host
|
||||
print(f"\n{COLORS['CYAN']}Running install.sh on {host}...{COLORS['RESET']}")
|
||||
install_cmd_str = (
|
||||
f'cd {remote_staging}/deploy && '
|
||||
f'{env_prefix}sudo --preserve-env=MATRIX_PASSWORD,ANTHROPIC_API_KEY '
|
||||
f'bash {remote_staging}/deploy/install.sh'
|
||||
)
|
||||
ssh_install = _build_ssh_cmd(host, ssh_user, ssh_port, ssh_key,
|
||||
install_cmd_str, interactive=True)
|
||||
try:
|
||||
subprocess.run(ssh_install)
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{COLORS['YELLOW']}Remote install interrupted{COLORS['RESET']}")
|
||||
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _status_remote():
|
||||
"""SSH to a remote host and show systemctl status claude-bot."""
|
||||
host, ssh_user, ssh_port, ssh_key = _prompt_remote_target()
|
||||
if not host:
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Status on {ssh_user}@{host}:{COLORS['RESET']}\n")
|
||||
ssh_cmd = _build_ssh_cmd(host, ssh_user, ssh_port, ssh_key,
|
||||
f'systemctl status {SERVICE_NAME} --no-pager',
|
||||
interactive=True)
|
||||
try:
|
||||
subprocess.run(ssh_cmd)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
wait_for_input()
|
||||
|
||||
|
||||
# ─── Main menu ───────────────────────────────────────────────────────────────
|
||||
|
||||
def claude_bot_menu():
|
||||
"""Main entry point — called from c2itall deploy.py tools_menu()."""
|
||||
while True:
|
||||
clear_screen()
|
||||
print_banner()
|
||||
print(f"{COLORS['WHITE']}CLAUDE BOT{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}=========={COLORS['RESET']}")
|
||||
print(f" Matrix-Claude Code bridge bot")
|
||||
|
||||
# Show whether source is available
|
||||
if os.path.isdir(CLAUDE_BOT_PATH):
|
||||
print(f" Source: {COLORS['GREEN']}{CLAUDE_BOT_PATH}{COLORS['RESET']}")
|
||||
else:
|
||||
print(f" Source: {COLORS['RED']}NOT FOUND — {CLAUDE_BOT_PATH}{COLORS['RESET']}")
|
||||
print()
|
||||
|
||||
print(f"1) Deploy Locally")
|
||||
print(f"2) Deploy Remote {COLORS['CYAN']}(SSH — copy + config + install){COLORS['RESET']}")
|
||||
print(f"3) Status {COLORS['GRAY']}(local systemctl){COLORS['RESET']}")
|
||||
print(f"4) Status Remote {COLORS['GRAY']}(SSH systemctl){COLORS['RESET']}")
|
||||
print(f"5) Uninstall Local")
|
||||
print(f"99) Return to Tools Menu")
|
||||
|
||||
choice = input(f"\nSelect an option: ").strip()
|
||||
|
||||
if choice == '1':
|
||||
_deploy_local()
|
||||
elif choice == '2':
|
||||
_deploy_remote()
|
||||
elif choice == '3':
|
||||
_status_local()
|
||||
elif choice == '4':
|
||||
_status_remote()
|
||||
elif choice == '5':
|
||||
_uninstall_local()
|
||||
elif choice == '99':
|
||||
return
|
||||
else:
|
||||
print(f"\n{COLORS['RED']}Invalid option. Please try again.{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
@@ -0,0 +1,872 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user