Got attack box and c2-redirector working

This commit is contained in:
n0mad1k
2025-08-21 16:25:00 -04:00
parent 1450a55e50
commit 66687b3009
93 changed files with 14924 additions and 3727 deletions
+1
View File
@@ -0,0 +1 @@
# Utils package for C2ingRed
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""
AWS provider utilities for C2ingRed deployment system
"""
import random
import logging
from .common import COLORS, load_vars_file, confirm_action
def get_aws_credentials(provider_vars=None):
"""Get AWS credentials from user or vars file"""
if not provider_vars:
provider_vars = load_vars_file('aws')
default_aws_key = provider_vars.get('aws_access_key', '')
default_aws_secret = provider_vars.get('aws_secret_key', '')
print(f"\n{COLORS['BLUE']}AWS Configuration{COLORS['RESET']}")
aws_key = input(f"AWS Access Key [{'*****' if default_aws_key else 'leave blank to use AWS CLI profile'}]: ") or default_aws_key
aws_secret = input(f"AWS Secret Key [{'*****' if default_aws_secret else 'leave blank to use AWS CLI profile'}]: ") or default_aws_secret
return {
'aws_access_key': aws_key,
'aws_secret_key': aws_secret
}
def select_aws_region(provider_vars=None, component=None):
"""Let the user select an AWS region"""
if not provider_vars:
provider_vars = load_vars_file('aws')
regions = provider_vars.get('aws_region_choices', [])
component_str = f" for {component}" if component else ""
if not regions:
print(f"{COLORS['YELLOW']}No regions found for AWS, using us-east-1{COLORS['RESET']}")
return "us-east-1"
print(f"\nAvailable AWS regions{component_str}:")
for i, region in enumerate(regions, 1):
print(f" {i}. {region}")
region_input = input(f"\nSelect region{component_str} (number or leave blank for random): ")
if not region_input:
return random.choice(regions)
try:
region_choice = int(region_input)
if 1 <= region_choice <= len(regions):
return regions[region_choice - 1]
else:
print(f"{COLORS['RED']}Invalid choice, using random region{COLORS['RESET']}")
return random.choice(regions)
except ValueError:
print(f"{COLORS['RED']}Invalid input, using random region{COLORS['RESET']}")
return random.choice(regions)
def gather_aws_config():
"""Gather all AWS-specific configuration"""
provider_vars = load_vars_file('aws')
config = {}
# Get credentials
aws_creds = get_aws_credentials(provider_vars)
config.update(aws_creds)
# Get region
config['aws_region'] = select_aws_region(provider_vars)
# Additional AWS-specific settings
config['aws_instance_type'] = provider_vars.get('aws_instance_type', 't3.micro')
config['aws_volume_size'] = provider_vars.get('aws_volume_size', 20)
return config
+320
View File
@@ -0,0 +1,320 @@
#!/usr/bin/env python3
"""
Cleanup and teardown engine for C2ingRed deployments
"""
import os
import sys
import subprocess
import logging
import json
import glob
# Add project root to path
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from utils.common import COLORS, PROVIDER_DIRS, confirm_action
def teardown_by_deployment_id(deployment_id):
"""Teardown infrastructure by deployment ID"""
print(f"\n{COLORS['BLUE']}Teardown Infrastructure by ID: {deployment_id}{COLORS['RESET']}")
# Look for deployment logs to determine provider and configuration
log_files = glob.glob(f"logs/deployment_{deployment_id}*.log")
if not log_files:
print(f"{COLORS['RED']}No deployment logs found for ID: {deployment_id}{COLORS['RESET']}")
return False
# Try to find deployment info files
info_files = glob.glob(f"logs/deployment_info_{deployment_id}*.txt")
if info_files:
config = parse_deployment_info(info_files[0])
if config:
return execute_teardown(config)
print(f"{COLORS['YELLOW']}Could not determine deployment configuration from logs{COLORS['RESET']}")
print(f"{COLORS['YELLOW']}Manual cleanup may be required{COLORS['RESET']}")
return False
def teardown_all_infrastructure():
"""Teardown all deployed infrastructure"""
print(f"\n{COLORS['RED']}⚠️ WARNING: This will attempt to destroy ALL infrastructure!{COLORS['RESET']}")
if not confirm_action("Are you absolutely sure?", default=False):
return False
# Find all deployment info files
info_files = glob.glob("logs/deployment_info_*.txt")
if not info_files:
print(f"{COLORS['GREEN']}No deployment info files found - nothing to teardown{COLORS['RESET']}")
return True
print(f"Found {len(info_files)} deployments to teardown:")
success_count = 0
for info_file in info_files:
config = parse_deployment_info(info_file)
if config:
deployment_id = config.get('deployment_id', 'unknown')
print(f"\nTearing down deployment: {deployment_id}")
if execute_teardown(config):
success_count += 1
print(f"\n{COLORS['GREEN']}Successfully tore down {success_count}/{len(info_files)} deployments{COLORS['RESET']}")
return success_count == len(info_files)
def parse_deployment_info(info_file):
"""Parse deployment info from file to extract configuration"""
try:
config = {}
with open(info_file, 'r') as f:
lines = f.readlines()
in_config_section = False
for line in lines:
line = line.strip()
if line == "Configuration:":
in_config_section = True
continue
elif line.startswith("-" * 30):
continue
elif line.startswith("Access Information:"):
break
if in_config_section and ": " in line:
key, value = line.split(": ", 1)
# Convert string values back to appropriate types
if value.lower() == 'true':
value = True
elif value.lower() == 'false':
value = False
elif value.lower() == 'none':
value = None
config[key] = value
return config
except Exception as e:
logging.error(f"Failed to parse deployment info from {info_file}: {e}")
return None
def execute_teardown(config):
"""Execute teardown based on configuration"""
provider = config.get('provider')
deployment_id = config.get('deployment_id')
if not provider or not deployment_id:
print(f"{COLORS['RED']}Missing provider or deployment ID{COLORS['RESET']}")
return False
print(f"{COLORS['BLUE']}Executing teardown for {provider} deployment {deployment_id}...{COLORS['RESET']}")
# Set up logging for teardown
from utils.common import setup_logging
setup_logging(deployment_id, "teardown")
try:
# Set provider-specific environment variables
set_provider_environment_for_teardown(config)
# Get correct provider directory
provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize())
# Execute teardown playbook
playbook = f"providers/{provider_dir}/cleanup.yml"
if not os.path.exists(playbook):
print(f"{COLORS['YELLOW']}Teardown playbook not found: {playbook}{COLORS['RESET']}")
print(f"{COLORS['YELLOW']}Manual cleanup required{COLORS['RESET']}")
return manual_cleanup_guidance(config)
success = run_teardown_playbook(playbook, config)
if success:
# Clean up local SSH keys
cleanup_ssh_keys_for_deployment(deployment_id)
# Move deployment logs to cleanup folder
archive_deployment_logs(deployment_id)
print(f"{COLORS['GREEN']}Teardown completed successfully{COLORS['RESET']}")
else:
print(f"{COLORS['RED']}Teardown failed - manual cleanup may be required{COLORS['RESET']}")
return success
except Exception as e:
logging.error(f"Teardown execution failed: {e}")
print(f"{COLORS['RED']}Teardown execution failed: {e}{COLORS['RESET']}")
return False
def set_provider_environment_for_teardown(config):
"""Set provider-specific environment variables for teardown"""
provider = config.get('provider')
if provider == "aws":
if config.get('aws_access_key'):
os.environ['AWS_ACCESS_KEY_ID'] = config['aws_access_key']
if config.get('aws_secret_key'):
os.environ['AWS_SECRET_ACCESS_KEY'] = config['aws_secret_key']
elif provider == "linode":
# Try to get token from config first, then from vars file
if config.get('linode_token'):
os.environ['LINODE_TOKEN'] = config['linode_token']
else:
# Load token from provider vars file
try:
from utils.common import load_vars_file, PROVIDER_DIRS
provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize())
vars_file = f"providers/{provider_dir}/vars.yaml"
vars_data = load_vars_file(vars_file)
if vars_data and vars_data.get('linode_token'):
os.environ['LINODE_TOKEN'] = vars_data['linode_token']
config['linode_token'] = vars_data['linode_token'] # Add to config for later use
else:
logging.warning("Linode token not found in vars file - cleanup may fail")
except Exception as e:
logging.warning(f"Failed to load Linode token from vars file: {e}")
def run_teardown_playbook(playbook, config):
"""Run the teardown Ansible playbook"""
try:
cmd = [
'ansible-playbook',
playbook,
'--extra-vars', create_teardown_vars(config)
]
if config.get('debug'):
cmd.append('-vvv')
logging.info(f"Executing teardown: {' '.join(cmd)}")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
if result.returncode == 0:
logging.info("Teardown playbook executed successfully")
return True
else:
logging.error(f"Teardown playbook failed with return code {result.returncode}")
logging.error(f"STDOUT: {result.stdout}")
logging.error(f"STDERR: {result.stderr}")
return False
except Exception as e:
logging.error(f"Error executing teardown playbook: {e}")
return False
def create_teardown_vars(config):
"""Create Ansible extra vars for teardown"""
teardown_vars = {
'deployment_id': config.get('deployment_id'),
'provider': config.get('provider'),
'operation': 'teardown'
}
# Add instance names for proper cleanup
if config.get('redirector_name'):
teardown_vars['redirector_name'] = config['redirector_name']
if config.get('c2_name'):
teardown_vars['c2_name'] = config['c2_name']
if config.get('tracker_name'):
teardown_vars['tracker_name'] = config['tracker_name']
if config.get('attack_box_name'):
teardown_vars['attack_box_name'] = config['attack_box_name']
# Add provider-specific vars if present
if config.get('aws_access_key'):
teardown_vars['aws_access_key'] = config['aws_access_key']
if config.get('aws_secret_key'):
teardown_vars['aws_secret_key'] = config['aws_secret_key']
if config.get('aws_region'):
teardown_vars['aws_region'] = config['aws_region']
if config.get('linode_token'):
teardown_vars['linode_token'] = config['linode_token']
if config.get('linode_region'):
teardown_vars['linode_region'] = config['linode_region']
return json.dumps(teardown_vars)
def cleanup_ssh_keys_for_deployment(deployment_id):
"""Clean up SSH keys for a specific deployment"""
ssh_dir = os.path.expanduser("~/.ssh")
key_pattern = f"c2deploy_{deployment_id}*"
for key_file in glob.glob(os.path.join(ssh_dir, key_pattern)):
try:
os.remove(key_file)
logging.info(f"Removed SSH key: {key_file}")
except Exception as e:
logging.warning(f"Failed to remove SSH key {key_file}: {e}")
def archive_deployment_logs(deployment_id):
"""Archive deployment logs after successful teardown"""
try:
# Create archive directory
archive_dir = "logs/archive"
os.makedirs(archive_dir, exist_ok=True)
# Move all files related to this deployment
log_patterns = [
f"logs/deployment_{deployment_id}*",
f"logs/deployment_info_{deployment_id}*",
f"logs/teardown_{deployment_id}*"
]
for pattern in log_patterns:
for file_path in glob.glob(pattern):
archive_path = os.path.join(archive_dir, os.path.basename(file_path))
os.rename(file_path, archive_path)
logging.info(f"Archived: {file_path} -> {archive_path}")
except Exception as e:
logging.warning(f"Failed to archive logs for {deployment_id}: {e}")
def manual_cleanup_guidance(config):
"""Provide manual cleanup guidance when automated teardown isn't available"""
provider = config.get('provider')
deployment_id = config.get('deployment_id')
print(f"\n{COLORS['YELLOW']}Manual Cleanup Required{COLORS['RESET']}")
print(f"{COLORS['YELLOW']}========================{COLORS['RESET']}")
print(f"Deployment ID: {deployment_id}")
print(f"Provider: {provider}")
if provider == "aws":
print(f"\nAWS Resources to check:")
print(f"- EC2 instances with tags containing: {deployment_id}")
print(f"- Security groups with names containing: {deployment_id}")
print(f"- Key pairs with names containing: {deployment_id}")
print(f"- EIPs associated with the deployment")
elif provider == "linode":
print(f"\nLinode Resources to check:")
print(f"- Linodes with labels containing: {deployment_id}")
print(f"- NodeBalancers with labels containing: {deployment_id}")
print(f"- Firewalls with labels containing: {deployment_id}")
elif provider == "flokinet":
print(f"\nFlokiNET Resources to check:")
print(f"- Check your FlokiNET control panel for resources created")
print(f"- Look for servers with the deployment ID: {deployment_id}")
print(f"\nLocal cleanup:")
print(f"- SSH keys: ~/.ssh/c2deploy_{deployment_id}*")
print(f"- Log files: logs/*{deployment_id}*")
return False # Manual cleanup required
if __name__ == "__main__":
print("Cleanup engine loaded")
+224
View File
@@ -0,0 +1,224 @@
#!/usr/bin/env python3
"""
Common utilities and constants for C2ingRed deployment system
"""
import os
import sys
import random
import string
import logging
import subprocess
import re
from datetime import datetime
# Constants for providers
PROVIDERS = ["aws", "linode", "flokinet"]
DEFAULT_SSH_USER = {
"aws": "kali",
"linode": "root",
"flokinet": "root"
}
# Directory names - maintain correct case for each provider
PROVIDER_DIRS = {
"aws": "AWS",
"linode": "Linode",
"flokinet": "FlokiNET"
}
# Color codes for terminal output
COLORS = {
"RESET": "\033[0m",
"RED": "\033[91m",
"GREEN": "\033[92m",
"YELLOW": "\033[93m",
"BLUE": "\033[94m",
"PURPLE": "\033[95m",
"CYAN": "\033[96m",
"WHITE": "\033[97m",
"GRAY": "\033[90m"
}
def clear_screen():
"""Clear the terminal screen"""
os.system('cls' if os.name == 'nt' else 'clear')
def print_banner():
"""Print the C2ingRed banner"""
banner = f"""
{COLORS['BLUE']}========================================================{COLORS['RESET']}
{COLORS['BLUE']} ██████╗██████╗ ██╗███╗ ██╗ ██████╗ ██████╗ ███████╗██████╗{COLORS['RESET']}
{COLORS['BLUE']} ██╔════╝╚════██╗██║████╗ ██║██╔════╝ ██╔══██╗██╔════╝██╔══██╗{COLORS['RESET']}
{COLORS['BLUE']} ██║ █████╔╝██║██╔██╗ ██║██║ ███╗██████╔╝█████╗ ██║ ██║{COLORS['RESET']}
{COLORS['BLUE']} ██║ ██╔═══╝ ██║██║╚██╗██║██║ ██║██╔══██╗██╔══╝ ██║ ██║{COLORS['RESET']}
{COLORS['BLUE']} ╚██████╗███████╗██║██║ ╚████║╚██████╔╝██║ ██║███████╗██████╔╝{COLORS['RESET']}
{COLORS['BLUE']} ╚═════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═════╝{COLORS['RESET']}
{COLORS['BLUE']} {COLORS['RESET']}
{COLORS['BLUE']} Red Team Infrastructure Deployment Tool {COLORS['RESET']}
{COLORS['BLUE']}========================================================{COLORS['RESET']}
"""
print(banner)
def generate_random_string(length=8):
"""Generate a random string of letters and digits."""
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
def generate_deployment_id():
"""Generate a consistent deployment ID for all resources in this deployment"""
from utils.name_generator import generate_deployment_id as generate_verb_animal_id
return generate_verb_animal_id()
def archive_old_logs(max_logs_to_keep=2):
"""Archive old log files to keep logs directory clean - more aggressive archiving"""
import glob
import shutil
from pathlib import Path
log_dir = "logs"
archive_dir = os.path.join(log_dir, "archive")
if not os.path.exists(log_dir):
return
# Create archive directory if it doesn't exist
os.makedirs(archive_dir, exist_ok=True)
# Get all log files (excluding archive directory) - ANY existing logs should be archived
log_files = []
for pattern in ["deployment_*.log", "teardown_*.log", "20*.log"]:
log_files.extend(glob.glob(os.path.join(log_dir, pattern)))
# Remove duplicates and filter out archive directory
log_files = [f for f in set(log_files) if "archive" not in f]
# Sort by modification time (newest first)
log_files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
# Archive ALL deployment logs to keep directory clean for new deployments
if len(log_files) > 0:
print(f"Found {len(log_files)} log files to archive")
archived_count = 0
for log_file in log_files:
try:
filename = os.path.basename(log_file)
# Add timestamp to archived filename to prevent conflicts
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archived_filename = f"{timestamp}_{filename}"
archive_path = os.path.join(archive_dir, archived_filename)
shutil.move(log_file, archive_path)
archived_count += 1
print(f"Archived: {filename} -> archive/{archived_filename}")
except Exception as e:
print(f"Failed to archive {log_file}: {e}")
if archived_count > 0:
print(f"Archived {archived_count} log files to {archive_dir}")
# Also archive old deployment info files
info_files = glob.glob(os.path.join(log_dir, "deployment_info_*.txt"))
if len(info_files) > 0:
print(f"Found {len(info_files)} info files to archive")
for info_file in info_files:
try:
filename = os.path.basename(info_file)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archived_filename = f"{timestamp}_{filename}"
archive_path = os.path.join(archive_dir, archived_filename)
shutil.move(info_file, archive_path)
print(f"Archived: {filename} -> archive/{archived_filename}")
except Exception as e:
print(f"Failed to archive {info_file}: {e}")
def setup_logging(deployment_id=None, operation_type="deployment"):
"""Set up logging for the deployment or teardown"""
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
# Archive old logs before starting new deployment
if operation_type == "deployment":
archive_old_logs()
# Create distinct log files for deployment vs teardown operations
if operation_type == "teardown":
log_file = os.path.join(log_dir, f"teardown_{deployment_id}.log")
else:
log_file = os.path.join(log_dir, f"deployment_{deployment_id}.log")
# Clear any existing handlers
root_logger = logging.getLogger()
root_logger.handlers.clear()
# Configure file handler to log DEBUG and above for full verbosity
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(file_formatter)
# Add console handler for INFO level and above
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter('[%(levelname)s] %(message)s')
console_handler.setFormatter(console_formatter)
# Configure root logger
root_logger.setLevel(logging.DEBUG) # Capture everything at root level
root_logger.addHandler(file_handler)
root_logger.addHandler(console_handler)
logging.info(f"{operation_type.capitalize()} operation started")
logging.info(f"Deployment ID: {deployment_id}")
logging.info(f"Full verbose output will be captured in: {log_file}")
return log_file
def load_vars_file(provider):
"""Load vars.yaml for the specified provider"""
if provider not in PROVIDER_DIRS:
return {}
# Use correct case for directory
provider_dir = PROVIDER_DIRS[provider]
vars_file = f"providers/{provider_dir}/vars.yaml"
if os.path.exists(vars_file):
try:
import yaml
with open(vars_file, 'r') as f:
return yaml.safe_load(f) or {}
except Exception as e:
logging.error(f"Failed to load {vars_file}: {e}")
return {}
else:
logging.warning(f"Vars file not found: {vars_file}")
return {}
def validate_ip_address(ip):
"""Validate IP address format"""
pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
return re.match(pattern, ip) is not None
def get_public_ip():
"""Get the user's public IP address"""
try:
import requests
return requests.get('https://api.ipify.org', timeout=5).text.strip()
except:
return None
def confirm_action(message, default=False):
"""Ask for user confirmation with a yes/no prompt"""
prompt = f"{message} ({'Y/n' if default else 'y/N'}): "
response = input(prompt).lower().strip()
if not response:
return default
return response in ['y', 'yes']
def wait_for_input(message="Press Enter to continue..."):
"""Wait for user input before continuing"""
input(f"\n{message}")
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""
FlokiNET provider utilities for C2ingRed deployment system
"""
import logging
from .common import COLORS, load_vars_file, validate_ip_address
def get_flokinet_credentials(provider_vars=None):
"""Get FlokiNET server IPs from user or vars file"""
if not provider_vars:
provider_vars = load_vars_file('flokinet')
default_redirector_ip = provider_vars.get('redirector_ip', '')
default_c2_ip = provider_vars.get('c2_ip', '')
print(f"\n{COLORS['BLUE']}FlokiNET Configuration{COLORS['RESET']}")
print(f"{COLORS['YELLOW']}Note: FlokiNET requires pre-provisioned servers{COLORS['RESET']}")
redirector_ip = input(f"FlokiNET Redirector IP Address [default: {default_redirector_ip}]: ") or default_redirector_ip
c2_ip = input(f"FlokiNET C2 Server IP Address [default: {default_c2_ip}]: ") or default_c2_ip
# Validate IP addresses
if redirector_ip and not validate_ip_address(redirector_ip):
print(f"{COLORS['RED']}Invalid redirector IP address{COLORS['RESET']}")
return None
if c2_ip and not validate_ip_address(c2_ip):
print(f"{COLORS['RED']}Invalid C2 server IP address{COLORS['RESET']}")
return None
return {
'flokinet_redirector_ip': redirector_ip,
'flokinet_c2_ip': c2_ip
}
def gather_flokinet_config():
"""Gather all FlokiNET-specific configuration"""
provider_vars = load_vars_file('flokinet')
config = {}
# Get server IPs
flokinet_ips = get_flokinet_credentials(provider_vars)
if not flokinet_ips:
return None
config.update(flokinet_ips)
# FlokiNET-specific settings
config['ssh_user'] = 'root' # FlokiNET typically uses root
return config
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Linode provider utilities for C2ingRed deployment system
"""
import random
import logging
from .common import COLORS, load_vars_file
def get_linode_credentials(provider_vars=None):
"""Get Linode API token from user or vars file"""
if not provider_vars:
provider_vars = load_vars_file('linode')
default_token = provider_vars.get('linode_token', '')
print(f"\n{COLORS['BLUE']}Linode Configuration{COLORS['RESET']}")
token = input(f"Linode API Token [{'*****' if default_token else 'required'}]: ") or default_token
if not token:
print(f"{COLORS['RED']}Linode API token is required{COLORS['RESET']}")
return None
return {'linode_token': token}
def select_linode_region(provider_vars=None, component=None):
"""Let the user select a Linode region"""
if not provider_vars:
provider_vars = load_vars_file('linode')
regions = provider_vars.get('region_choices', [])
component_str = f" for {component}" if component else ""
if not regions:
print(f"{COLORS['YELLOW']}No regions found for Linode, using us-east{COLORS['RESET']}")
return "us-east"
print(f"\nAvailable Linode regions{component_str}:")
for i, region in enumerate(regions, 1):
print(f" {i}. {region}")
region_input = input(f"\nSelect region{component_str} (number or leave blank for random): ")
if not region_input:
# Use random from reliable regions only (first 8 are most reliable)
reliable_regions = regions[:8] if len(regions) >= 8 else regions
return random.choice(reliable_regions)
try:
region_choice = int(region_input)
if 1 <= region_choice <= len(regions):
return regions[region_choice - 1]
else:
print(f"{COLORS['RED']}Invalid choice, using random region{COLORS['RESET']}")
return random.choice(regions)
except ValueError:
print(f"{COLORS['RED']}Invalid input, using random region{COLORS['RESET']}")
return random.choice(regions)
def gather_linode_config():
"""Gather all Linode-specific configuration"""
provider_vars = load_vars_file('linode')
config = {}
# Get credentials
linode_creds = get_linode_credentials(provider_vars)
if not linode_creds:
return None
config.update(linode_creds)
# Get region
config['linode_region'] = select_linode_region(provider_vars)
# Additional Linode-specific settings
config['linode_instance_type'] = provider_vars.get('linode_instance_type', 'g6-nanode-1')
config['linode_image'] = provider_vars.get('linode_image', 'linode/kali')
return config
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""
Name generation utility for c2itall deployments
Generates verb-animal names similar to FourEyes with shared deployment IDs
"""
import random
import os
def load_word_list(filename):
"""Load words from a text file, one word per line"""
try:
# Check if we have FourEyes word lists
foureyes_path = "/home/n0mad1k/Tools/FourEyes"
if os.path.exists(foureyes_path):
file_path = os.path.join(foureyes_path, filename)
if os.path.exists(file_path):
with open(file_path, 'r') as f:
words = [line.strip().lower() for line in f if line.strip()]
return [word for word in words if word] # Remove empty strings
except Exception:
pass
# Fallback word lists if FourEyes not available
if 'verbs' in filename:
return [
"blazing", "soaring", "charging", "prowling", "hunting", "stalking",
"striking", "rushing", "dashing", "racing", "flying", "diving",
"leaping", "climbing", "sliding", "spinning", "rolling", "sneaking",
"roaming", "wandering", "running", "jumping", "swimming", "crawling",
"fighting", "defending", "attacking", "scanning", "searching", "finding"
]
else: # animals
return [
"wolf", "eagle", "tiger", "falcon", "bear", "lion", "shark", "hawk",
"panther", "cobra", "viper", "rhino", "bull", "fox", "raven", "crow",
"spider", "scorpion", "mantis", "dragon", "phoenix", "griffin",
"badger", "wolverine", "lynx", "jaguar", "cheetah", "leopard"
]
def _generate_verb_animal():
"""Generate a verb-animal combination"""
verbs = load_word_list('verbs.txt')
animals = load_word_list('animals.txt')
verb = random.choice(verbs)
animal = random.choice(animals)
return f"{verb}{animal}"
def generate_deployment_id():
"""Generate a deployment ID using verb-animal combination"""
return _generate_verb_animal()
def generate_attack_box_name(deployment_id):
"""Generate attack box name with a- prefix using shared deployment ID"""
return f"a-{deployment_id}"
def generate_redirector_name(deployment_id):
"""Generate redirector name with r- prefix using shared deployment ID"""
return f"r-{deployment_id}"
def generate_c2_name(deployment_id):
"""Generate C2 server name with s- prefix using shared deployment ID"""
return f"s-{deployment_id}"
def generate_phishing_name(deployment_id):
"""Generate phishing server name with p- prefix using shared deployment ID"""
return f"p-{deployment_id}"
def generate_tracker_name(deployment_id):
"""Generate tracker name with t- prefix using shared deployment ID"""
return f"t-{deployment_id}"
if __name__ == "__main__":
# Test the name generation
deployment_id = generate_deployment_id()
print(f"Testing shared deployment ID: {deployment_id}")
print(f"Attack Box: {generate_attack_box_name(deployment_id)}")
print(f"Redirector: {generate_redirector_name(deployment_id)}")
print(f"C2 Server: {generate_c2_name(deployment_id)}")
print(f"Phishing: {generate_phishing_name(deployment_id)}")
print(f"Tracker: {generate_tracker_name(deployment_id)}")
+244
View File
@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
Common naming utilities for C2itall deployments
Provides consistent naming options across all deployment types
"""
import os
import glob
from utils.common import COLORS
def get_existing_deployments():
"""Get list of existing deployments from deployment info files"""
try:
info_files = glob.glob("logs/deployment_info_*.txt")
deployments = []
for info_file in info_files:
try:
deployment_info = {}
with open(info_file, 'r') as f:
lines = f.readlines()
for line in lines:
line = line.strip()
if ": " in line and not line.startswith("-"):
parts = line.split(": ", 1)
if len(parts) == 2:
key, value = parts
deployment_info[key] = value
if deployment_info.get('deployment_id'):
deployments.append(deployment_info)
except Exception as e:
continue # Skip problematic files
return deployments
except Exception as e:
return []
def select_deployment_for_naming(exclude_types=None, current_deployment_id=None):
"""
Allow user to select an existing deployment to base naming on
Args:
exclude_types: List of deployment types to exclude (e.g., ['attack_box'])
current_deployment_id: Current deployment ID to exclude from list
"""
deployments = get_existing_deployments()
if not deployments:
print(f"{COLORS['YELLOW']}No existing deployments found{COLORS['RESET']}")
return None
print(f"\n{COLORS['CYAN']}Existing Deployments:{COLORS['RESET']}")
print(f"{COLORS['CYAN']}==================={COLORS['RESET']}")
# Filter deployments based on criteria
filtered_deployments = []
for deployment in deployments:
deployment_id = deployment.get('deployment_id', 'unknown')
# Skip current deployment
if current_deployment_id and deployment_id == current_deployment_id:
continue
# Skip excluded types
deployment_type = deployment.get('deployment_type', 'unknown')
if exclude_types and deployment_type in exclude_types:
continue
# Check if this is an attack box deployment (legacy check)
is_attack_box = (
deployment.get('deployment_type') == 'attack_box' or
deployment.get('attack_box_deployment') == 'True' or
deployment.get('attack_box_name')
)
if exclude_types and 'attack_box' in exclude_types and is_attack_box:
continue
filtered_deployments.append(deployment)
if not filtered_deployments:
print(f"{COLORS['YELLOW']}No suitable deployments found for naming{COLORS['RESET']}")
return None
# Display available deployments
for i, deployment in enumerate(filtered_deployments, 1):
deployment_id = deployment.get('deployment_id', 'unknown')
provider = deployment.get('provider', 'unknown')
domain = deployment.get('domain', 'N/A')
deployment_type = deployment.get('deployment_type', 'unknown')
print(f"{i}. {deployment_id}")
print(f" Type: {deployment_type}")
print(f" Provider: {provider}")
print(f" Domain: {domain}")
# Show instance names if available
instances = []
if deployment.get('redirector_name'):
instances.append(f"Redirector: {deployment.get('redirector_name')}")
if deployment.get('c2_name'):
instances.append(f"C2: {deployment.get('c2_name')}")
if deployment.get('tracker_name'):
instances.append(f"Tracker: {deployment.get('tracker_name')}")
if deployment.get('attack_box_name'):
instances.append(f"Attack Box: {deployment.get('attack_box_name')}")
if instances:
print(f" Instances: {', '.join(instances)}")
print()
# Get user selection
while True:
try:
choice = input(f"Select deployment (1-{len(filtered_deployments)}) or 'c' to cancel: ").strip()
if choice.lower() == 'c':
return None
choice_num = int(choice)
if 1 <= choice_num <= len(filtered_deployments):
selected = filtered_deployments[choice_num - 1]
return selected.get('deployment_id')
else:
print(f"{COLORS['RED']}Invalid choice. Please try again.{COLORS['RESET']}")
except ValueError:
print(f"{COLORS['RED']}Invalid input. Please enter a number or 'c'.{COLORS['RESET']}")
def get_deployment_name_with_options(deployment_type, deployment_id, prefix="", existing_name=None):
"""
Get deployment name with multiple naming options
Args:
deployment_type: Type of deployment (c2, redirector, tracker, attack_box, etc.)
deployment_id: Current deployment ID
prefix: Prefix for the name (e.g., 'r-', 'c-', 'a-', etc.)
existing_name: Existing name if updating
Returns:
Chosen name for the deployment
"""
print(f"\n{COLORS['BLUE']}{deployment_type.title()} Naming Options:{COLORS['RESET']}")
print(f"1) Auto-generate name ({prefix}{deployment_id})")
print(f"2) Name after existing deployment")
print(f"3) Custom name")
if existing_name:
print(f"4) Keep current name ({existing_name})")
default_choice = "4"
else:
default_choice = "1"
naming_choice = input(f"Select naming option [{default_choice}]: ").strip() or default_choice
if naming_choice == "1":
# Auto-generate using deployment ID
chosen_name = f"{prefix}{deployment_id}"
print(f"Using auto-generated name: {COLORS['CYAN']}{chosen_name}{COLORS['RESET']}")
elif naming_choice == "2":
# Name after existing deployment
# Exclude attack boxes when naming other types, but allow other types when naming attack boxes
exclude_types = ['attack_box'] if deployment_type != 'attack_box' else []
selected_deployment_id = select_deployment_for_naming(
exclude_types=exclude_types,
current_deployment_id=deployment_id
)
if selected_deployment_id:
chosen_name = f"{prefix}{selected_deployment_id}"
print(f"{deployment_type.title()} will be named: {COLORS['CYAN']}{chosen_name}{COLORS['RESET']}")
print(f"This associates it with deployment: {COLORS['YELLOW']}{selected_deployment_id}{COLORS['RESET']}")
else:
print(f"{COLORS['YELLOW']}No deployment selected, using auto-generated name{COLORS['RESET']}")
chosen_name = f"{prefix}{deployment_id}"
elif naming_choice == "3":
# Custom name
while True:
custom_name = input(f"Enter custom {deployment_type} name: ").strip()
if custom_name:
# Ensure it starts with the correct prefix for consistency
if prefix and not custom_name.startswith(prefix):
chosen_name = f"{prefix}{custom_name}"
print(f"Prefixed with '{prefix}': {COLORS['CYAN']}{chosen_name}{COLORS['RESET']}")
else:
chosen_name = custom_name
break
else:
print(f"{COLORS['RED']}Name cannot be empty. Please try again.{COLORS['RESET']}")
elif naming_choice == "4" and existing_name:
# Keep existing name
chosen_name = existing_name
print(f"Keeping current name: {COLORS['CYAN']}{chosen_name}{COLORS['RESET']}")
else:
# Default fallback
print(f"{COLORS['YELLOW']}Invalid choice, using auto-generated name{COLORS['RESET']}")
chosen_name = f"{prefix}{deployment_id}"
return chosen_name
def show_naming_relationship(name, deployment_id, deployment_type):
"""Show the relationship between the chosen name and deployment"""
if not name:
return
# Determine prefix based on deployment type
prefix_map = {
'redirector': 'r-',
'c2': 's-', # s for server
'tracker': 't-',
'attack_box': 'a-',
'payload': 'p-'
}
expected_prefix = prefix_map.get(deployment_type, '')
if expected_prefix and name.startswith(expected_prefix):
target_deployment = name[len(expected_prefix):] # Remove prefix
if target_deployment != deployment_id:
return {
'target_deployment': target_deployment,
'relationship_text': f"Named after deployment: {target_deployment}",
'purpose_text': f"This {deployment_type} supports the {target_deployment} engagement"
}
return None
def get_deployment_type_prefix(deployment_type):
"""Get the standard prefix for a deployment type"""
prefix_map = {
'redirector': 'r-',
'c2': 's-', # s for server
'tracker': 't-',
'attack_box': 'a-',
'payload': 'p-',
'phishing': 'p-'
}
return prefix_map.get(deployment_type, '')
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""
Provider selection and configuration utilities
"""
from .common import COLORS, PROVIDERS
from .aws_utils import gather_aws_config
from .linode_utils import gather_linode_config
from .flokinet_utils import gather_flokinet_config
def select_provider():
"""Let the user select a cloud provider"""
print(f"\n{COLORS['BLUE']}Available cloud providers:{COLORS['RESET']}")
for i, provider in enumerate(PROVIDERS, 1):
print(f" {i}. {provider.capitalize()}")
while True:
try:
provider_choice = input(f"\nSelect a provider (1-{len(PROVIDERS)} or 99 to cancel): ")
if provider_choice == "99":
return None
provider_choice = int(provider_choice)
if 1 <= provider_choice <= len(PROVIDERS):
return PROVIDERS[provider_choice - 1]
else:
print(f"{COLORS['RED']}Please enter a number between 1 and {len(PROVIDERS)}{COLORS['RESET']}")
except ValueError:
print(f"{COLORS['RED']}Please enter a valid number{COLORS['RESET']}")
def gather_provider_config(provider):
"""Gather configuration for the specified provider"""
if provider == "aws":
return gather_aws_config()
elif provider == "linode":
return gather_linode_config()
elif provider == "flokinet":
return gather_flokinet_config()
else:
print(f"{COLORS['RED']}Unknown provider: {provider}{COLORS['RESET']}")
return None
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
SSH utilities for C2ingRed deployment system
"""
import os
import subprocess
import logging
import re
import glob
from .common import COLORS, generate_random_string
def generate_ssh_key(deployment_id=None):
"""Generate an SSH key for deployment with proper tracking for cleanup"""
# Use deployment_id if provided, otherwise generate random suffix
if not deployment_id:
deployment_id = generate_random_string(6)
ssh_key_path = os.path.expanduser(f"~/.ssh/c2deploy_{deployment_id}")
ssh_key_pub_path = f"{ssh_key_path}.pub"
# Check if key already exists
if os.path.exists(ssh_key_path):
logging.info(f"SSH key already exists at {ssh_key_path}")
return ssh_key_path
try:
# Generate the SSH key
subprocess.run([
"ssh-keygen", "-t", "rsa", "-b", "4096",
"-f", ssh_key_path, "-q", "-N", ""
], check=True)
# Set proper permissions
os.chmod(ssh_key_path, 0o600)
# Track generated keys for cleanup
if not hasattr(generate_ssh_key, 'generated_keys'):
generate_ssh_key.generated_keys = []
generate_ssh_key.generated_keys.append(ssh_key_path)
logging.info(f"Generated SSH key: {ssh_key_path}")
return ssh_key_path
except subprocess.CalledProcessError as e:
logging.error(f"Failed to generate SSH key: {e}")
return None
def get_ssh_public_key(private_key_path):
"""Get the public key content from a private key file"""
public_key_path = f"{private_key_path}.pub"
if not os.path.exists(public_key_path):
logging.error(f"Public key file not found: {public_key_path}")
return None
try:
with open(public_key_path, 'r') as f:
return f.read().strip()
except Exception as e:
logging.error(f"Failed to read public key: {e}")
return None
def extract_attack_box_ip_from_logs(config):
"""Extract attack box IP from deployment logs or Ansible output"""
deployment_id = config.get('deployment_id', 'unknown')
# Check deployment log file first
log_files_to_check = [
f"logs/deployment_{deployment_id}.log",
# Also check archived logs
f"logs/archive/deployment_{deployment_id}.log"
]
# Check for timestamped archived logs
archive_pattern = f"logs/archive/*_deployment_{deployment_id}.log"
archived_logs = glob.glob(archive_pattern)
if archived_logs:
# Get the most recent archived log
log_files_to_check.append(max(archived_logs, key=os.path.getmtime))
for log_file in log_files_to_check:
if os.path.exists(log_file):
try:
with open(log_file, 'r') as f:
content = f.read()
# Look for various IP patterns in the log
ip_patterns = [
r'"attack_box_ip":\s*"([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"',
r'attack_box_ip.*?([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',
r'"ipv4":\s*\["([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"\]',
r'ansible_host["\s]*=[\s]*([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',
r'instance_ip["\s]*:[\s]*([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',
r'Target: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',
r'IP["\s]*:[\s]*([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',
# Look for IP in Ansible task output patterns
r'([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\s+:\s+ok=',
r'PLAY RECAP.*?([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',
r'changed: \[([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\]',
r'ok: \[([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\]'
]
for pattern in ip_patterns:
match = re.search(pattern, content)
if match:
ip = match.group(1)
logging.info(f"Extracted attack box IP from logs: {ip}")
return ip
except Exception as e:
logging.warning(f"Could not read deployment log {log_file}: {e}")
# Check deployment info file
info_file = f"logs/deployment_info_{deployment_id}.txt"
if os.path.exists(info_file):
try:
with open(info_file, 'r') as f:
content = f.read()
# Look for IP in SSH command or other contexts
ip_patterns = [
r'root@([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',
r'Instance IP:\s*([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})',
r'IP["\s]*:[\s]*([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'
]
for pattern in ip_patterns:
match = re.search(pattern, content)
if match:
ip = match.group(1)
logging.info(f"Extracted attack box IP from deployment info: {ip}")
return ip
except Exception as e:
logging.warning(f"Could not read deployment info: {e}")
logging.warning("Could not extract attack box IP from logs")
return None
def ssh_to_instance(config):
"""SSH into an instance after deployment"""
ssh_key_path = config.get('ssh_key_path', '').replace('.pub', '')
# Handle attack box deployments
if config.get('attack_box_deployment'):
instance_ip = config.get('attack_box_ip')
instance_name = config.get('attack_box_name', 'attack box')
# If no IP stored in config, try to extract from deployment logs
if not instance_ip:
instance_ip = extract_attack_box_ip_from_logs(config)
# Determine which instance to connect to for C2 deployments
elif config.get('c2_only') or (not config.get('redirector_only') and not config.get('deploy_tracker')):
# Connect to C2 server
instance_ip = config.get('c2_ip')
instance_name = config.get('c2_name', 'C2 server')
elif config.get('redirector_only'):
# Connect to redirector
instance_ip = config.get('redirector_ip')
instance_name = config.get('redirector_name', 'redirector')
elif config.get('deploy_tracker') and not config.get('integrated_tracker'):
# Connect to tracker
instance_ip = config.get('tracker_ip')
instance_name = config.get('tracker_name', 'tracker')
else:
# Default to C2 server
instance_ip = config.get('c2_ip')
instance_name = config.get('c2_name', 'C2 server')
if not instance_ip:
print(f"{COLORS['RED']}No instance IP found for SSH connection{COLORS['RESET']}")
return
ssh_user = config.get('ssh_user', 'root')
print(f"{COLORS['GREEN']}Connecting to {instance_name} ({instance_ip})...{COLORS['RESET']}")
ssh_command = [
"ssh",
"-i", ssh_key_path,
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "IdentitiesOnly=yes",
f"{ssh_user}@{instance_ip}"
]
try:
subprocess.run(ssh_command)
except KeyboardInterrupt:
print(f"\n{COLORS['YELLOW']}SSH session ended{COLORS['RESET']}")
except Exception as e:
print(f"{COLORS['RED']}SSH connection failed: {e}{COLORS['RESET']}")
def cleanup_ssh_keys(deployment_id=None, keep_keys=False):
"""Clean up generated SSH keys"""
if keep_keys:
return
if hasattr(generate_ssh_key, 'generated_keys'):
for key_path in generate_ssh_key.generated_keys:
# Only remove keys we generated for this deployment
if deployment_id and f"_{deployment_id}" in key_path:
try:
if os.path.exists(key_path):
os.remove(key_path)
if os.path.exists(f"{key_path}.pub"):
os.remove(f"{key_path}.pub")
logging.info(f"Removed SSH key: {key_path}")
except Exception as e:
logging.error(f"Failed to remove SSH key {key_path}: {e}")