working on bugs

This commit is contained in:
n0mad1k
2025-04-28 15:18:03 -04:00
parent c5b46c61a6
commit 8396d2fe65
13 changed files with 426 additions and 272 deletions
+3 -3
View File
@@ -107,7 +107,7 @@ After deployment:
## Using Havoc C2
Havoc C2 Framework is installed at `/root/Tools/havoc` on the C2 server.
Havoc C2 Framework is installed at `/root/Tools/Havoc` on the C2 server.
### Connecting to the Teamserver
@@ -126,11 +126,11 @@ From your local machine:
```bash
./havoc client --address YOUR_C2_IP:40056 --username admin --password [password]
```
(The password is stored in `/root/Tools/havoc/data/profiles/default.yaotl`)
(The password is stored in `/root/Tools/Havoc/data/profiles/default.yaotl`)
### Generating Payloads
Pre-generated payloads are available in `/root/Tools/havoc/payloads/`. You can create new payloads using:
Pre-generated payloads are available in `/root/Tools/Havoc/payloads/`. You can create new payloads using:
```bash
cd /root/Tools
+309 -183
View File
@@ -102,6 +102,10 @@ def parse_arguments():
parser.add_argument('--c2-name', help='Name for the C2 instance (default: random)')
parser.add_argument('--redirector-subdomain', default='cdn', help='Subdomain for the redirector (default: cdn)')
parser.add_argument('--c2-subdomain', default='mail', help='Subdomain for the C2 server (default: mail)')
parser.add_argument('--redirector-provider', choices=PROVIDERS, help='Provider to use for redirector (if different from primary provider)')
parser.add_argument('--c2-provider', choices=PROVIDERS, help='Provider to use for C2 (if different from primary provider)')
parser.add_argument('--redirector-region', help='Region for redirector deployment (can be different from C2)')
parser.add_argument('--c2-region', help='Region for C2 deployment (can be different from redirector)')
# Common arguments
parser.add_argument('--teardown', action='store_true', help='Tear down existing infrastructure')
@@ -143,21 +147,21 @@ def interactive_setup(deployment_id=None):
config = {}
print("\n========================================")
print("C2itAll - Interactive Setup Wizard")
print("C2ingRed - Interactive Setup Wizard")
print("========================================\n")
# Create a deployment ID for consistent resource naming
deployment_id = deployment_id or generate_deployment_id()
config['deployment_id'] = deployment_id
# Select provider
# Select primary provider
print("Available cloud providers:")
for i, provider in enumerate(PROVIDERS, 1):
print(f" {i}. {provider.capitalize()}")
while True:
try:
provider_choice = int(input("\nSelect a provider (1-3): "))
provider_choice = int(input("\nSelect a primary provider (1-3): "))
if 1 <= provider_choice <= len(PROVIDERS):
config['provider'] = PROVIDERS[provider_choice - 1]
break
@@ -166,118 +170,200 @@ def interactive_setup(deployment_id=None):
except ValueError:
print("Please enter a valid number")
# Load vars file for the selected provider
provider_dir = config['provider'].capitalize()
if config['provider'] == "aws":
provider_dir = "AWS"
elif config['provider'] == "flokinet":
provider_dir = "FlokiNET"
# Ask if user wants to use cross-provider deployment
cross_provider = input("\nDo you want to deploy redirector and C2 on different providers? (y/n) [default: n]: ").lower() == 'y'
if cross_provider:
print("\nSelect redirector provider:")
for i, provider in enumerate(PROVIDERS, 1):
print(f" {i}. {provider.capitalize()}")
while True:
try:
redirector_provider_choice = int(input("\nSelect redirector provider (1-3): "))
if 1 <= redirector_provider_choice <= len(PROVIDERS):
config['redirector_provider'] = PROVIDERS[redirector_provider_choice - 1]
break
else:
print(f"Please enter a number between 1 and {len(PROVIDERS)}")
except ValueError:
print("Please enter a valid number")
print("\nSelect C2 server provider:")
for i, provider in enumerate(PROVIDERS, 1):
print(f" {i}. {provider.capitalize()}")
while True:
try:
c2_provider_choice = int(input("\nSelect C2 provider (1-3): "))
if 1 <= c2_provider_choice <= len(PROVIDERS):
config['c2_provider'] = PROVIDERS[c2_provider_choice - 1]
break
else:
print(f"Please enter a number between 1 and {len(PROVIDERS)}")
except ValueError:
print("Please enter a valid number")
# Load vars files for all selected providers
providers_to_configure = set([config['provider']])
if cross_provider:
providers_to_configure.add(config['redirector_provider'])
providers_to_configure.add(config['c2_provider'])
vars_file = f"{provider_dir}/vars.yaml"
vars_data = {}
for provider in providers_to_configure:
provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize())
vars_file = f"{provider_dir}/vars.yaml"
if os.path.exists(vars_file):
try:
with open(vars_file, 'r') as f:
vars_data = yaml.safe_load(f) or {}
provider_vars = yaml.safe_load(f) or {}
vars_data[provider] = provider_vars
print(f"Loaded configuration from {vars_file}")
except Exception as e:
print(f"Warning: Failed to load {vars_file}: {e}")
vars_data[provider] = {}
# Provider-specific configuration
if config['provider'] == "aws":
# Set defaults from vars file
default_aws_key = vars_data.get('aws_access_key', '')
default_aws_secret = vars_data.get('aws_secret_key', '')
# Configure each provider
for provider in providers_to_configure:
provider_vars = vars_data.get(provider, {})
config['aws_access_key'] = input(f"\nAWS Access Key [{'*****' if default_aws_key else 'leave blank to use AWS CLI profile'}]: ") or default_aws_key
config['aws_secret_key'] = input(f"AWS Secret Key [{'*****' if default_aws_secret else 'leave blank to use AWS CLI profile'}]: ") or default_aws_secret
print(f"\n--- {provider.capitalize()} Configuration ---")
# Show available regions
aws_regions = vars_data.get('aws_region_choices', [])
if provider == "aws":
# AWS credentials
default_aws_key = provider_vars.get('aws_access_key', '')
default_aws_secret = provider_vars.get('aws_secret_key', '')
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
config['aws_access_key'] = aws_key
config['aws_secret_key'] = aws_secret
# AWS regions
aws_regions = provider_vars.get('aws_region_choices', [])
if aws_regions:
print("\nAvailable AWS regions:")
for i, region in enumerate(aws_regions, 1):
print(f" {i}. {region}")
while True:
region_input = input("\nSelect a region (number) or press Enter for random: ")
if not region_input:
config['aws_region'] = None # Random
break
# Select region for redirector if this is the redirector provider
if provider == config.get('redirector_provider', config['provider']):
region_input = input("\nSelect region for redirector (number or leave blank for random): ")
if region_input:
try:
region_choice = int(region_input)
if 1 <= region_choice <= len(aws_regions):
config['redirector_region'] = aws_regions[region_choice - 1]
else:
print(f"Invalid choice, using random region")
except ValueError:
print("Invalid input, using random region")
# Select region for C2 if this is the C2 provider
if provider == config.get('c2_provider', config['provider']):
region_input = input("\nSelect region for C2 (number or leave blank for random): ")
if region_input:
try:
region_choice = int(region_input)
if 1 <= region_choice <= len(aws_regions):
config['c2_region'] = aws_regions[region_choice - 1]
else:
print(f"Invalid choice, using random region")
except ValueError:
print("Invalid input, using random region")
# If not cross-provider and no specific regions selected, use general region
if not cross_provider and not (config.get('redirector_region') or config.get('c2_region')):
region_input = input("\nSelect region (number or leave blank for random): ")
if region_input:
try:
region_choice = int(region_input)
if 1 <= region_choice <= len(aws_regions):
config['aws_region'] = aws_regions[region_choice - 1]
break
else:
print(f"Please enter a number between 1 and {len(aws_regions)}")
print(f"Invalid choice, using random region")
except ValueError:
print("Please enter a valid number or press Enter")
else:
default_region = vars_data.get('aws_region', 'us-east-1')
config['aws_region'] = input(f"\nAWS Region [default: {default_region}]: ") or default_region
print("Invalid input, using random region")
# Instance size
default_size = vars_data.get('aws_instance_type', 't2.medium')
config['aws_instance_type'] = input(f"Instance Size [default: {default_size}]: ") or default_size
elif provider == "linode":
# Linode token
default_token = provider_vars.get('linode_token', '')
token = input(f"\nLinode API Token [{'*****' if default_token else 'required'}]: ") or default_token
config['linode_token'] = token
elif config['provider'] == "linode":
# Set defaults from vars file
default_token = vars_data.get('linode_token', '')
config['linode_token'] = input(f"\nLinode API Token [{'*****' if default_token else 'required'}]: ") or default_token
# Show available regions
linode_regions = vars_data.get('region_choices', [])
# Linode regions
linode_regions = provider_vars.get('region_choices', [])
if linode_regions:
print("\nAvailable Linode regions:")
for i, region in enumerate(linode_regions, 1):
print(f" {i}. {region}")
while True:
region_input = input("\nSelect a region (number) or press Enter for random: ")
if not region_input:
config['linode_region'] = None # Random
break
# Select region for redirector if this is the redirector provider
if provider == config.get('redirector_provider', config['provider']):
region_input = input("\nSelect region for redirector (number or leave blank for random): ")
if region_input:
try:
region_choice = int(region_input)
if 1 <= region_choice <= len(linode_regions):
config['redirector_region'] = linode_regions[region_choice - 1]
else:
print(f"Invalid choice, using random region")
except ValueError:
print("Invalid input, using random region")
# Select region for C2 if this is the C2 provider
if provider == config.get('c2_provider', config['provider']):
region_input = input("\nSelect region for C2 (number or leave blank for random): ")
if region_input:
try:
region_choice = int(region_input)
if 1 <= region_choice <= len(linode_regions):
config['c2_region'] = linode_regions[region_choice - 1]
else:
print(f"Invalid choice, using random region")
except ValueError:
print("Invalid input, using random region")
# If not cross-provider and no specific regions selected, use general region
if not cross_provider and not (config.get('redirector_region') or config.get('c2_region')):
region_input = input("\nSelect region (number or leave blank for random): ")
if region_input:
try:
region_choice = int(region_input)
if 1 <= region_choice <= len(linode_regions):
config['linode_region'] = linode_regions[region_choice - 1]
break
else:
print(f"Please enter a number between 1 and {len(linode_regions)}")
print(f"Invalid choice, using random region")
except ValueError:
print("Please enter a valid number or press Enter")
else:
# Use defaults or empty values if no regions are defined
default_region = vars_data.get('linode_region', '')
config['linode_region'] = input(f"\nLinode Region [default: {default_region or 'random'}]: ") or default_region
print("Invalid input, using random region")
# Instance size/plan
default_plan = vars_data.get('plan', 'g6-standard-2')
config['plan'] = input(f"Instance Plan [default: {default_plan}]: ") or default_plan
default_plan = provider_vars.get('plan', 'g6-standard-2')
plan = input(f"\nInstance Plan [default: {default_plan}]: ") or default_plan
config['plan'] = plan
elif config['provider'] == "flokinet":
elif provider == "flokinet":
print("\nFlokiNET requires pre-provisioned servers.")
# Set defaults from vars file
default_redirector_ip = vars_data.get('redirector_ip', '')
default_c2_ip = vars_data.get('c2_ip', '')
default_ssh_user = vars_data.get('ssh_user', DEFAULT_SSH_USER['flokinet'])
default_ssh_port = vars_data.get('ssh_port', 22)
default_redirector_ip = provider_vars.get('redirector_ip', '')
default_c2_ip = provider_vars.get('c2_ip', '')
default_ssh_user = provider_vars.get('ssh_user', DEFAULT_SSH_USER['flokinet'])
default_ssh_port = provider_vars.get('ssh_port', 22)
# Configure FlokiNET servers
if provider == config.get('redirector_provider', config['provider']):
config['flokinet_redirector_ip'] = input(f"FlokiNET Redirector IP Address [default: {default_redirector_ip}]: ") or default_redirector_ip
if provider == config.get('c2_provider', config['provider']):
config['flokinet_c2_ip'] = input(f"FlokiNET C2 Server IP Address [default: {default_c2_ip}]: ") or default_c2_ip
config['ssh_user'] = input(f"SSH User [default: {default_ssh_user}]: ") or default_ssh_user
config['ssh_port'] = input(f"SSH Port [default: {default_ssh_port}]: ") or default_ssh_port
ssh_key = input("Path to SSH Private Key [leave blank to generate new key]: ")
if ssh_key:
config['ssh_key'] = os.path.expanduser(ssh_key)
else:
config['ssh_key'] = None # Will generate a new key
# Deployment type with integrated tracker option
print("\nDeployment type:")
print(" 1. Full deployment (Redirector + C2) [default]")
@@ -317,28 +403,25 @@ def interactive_setup(deployment_id=None):
config['deploy_tracker'] = False
# Domain configuration
default_domain = vars_data.get('domain', 'example.com')
default_domain = vars_data.get(config['provider'], {}).get('domain', 'example.com')
config['domain'] = input(f"\nDomain name [default: {default_domain}]: ") or default_domain
# Subdomain configuration - new feature
default_redirector_subdomain = vars_data.get('redirector_subdomain', 'cdn')
# Subdomain configuration
default_redirector_subdomain = vars_data.get(config['provider'], {}).get('redirector_subdomain', 'cdn')
config['redirector_subdomain'] = input(f"Redirector subdomain [default: {default_redirector_subdomain}]: ") or default_redirector_subdomain
default_c2_subdomain = vars_data.get('c2_subdomain', 'mail')
default_c2_subdomain = vars_data.get(config['provider'], {}).get('c2_subdomain', 'mail')
config['c2_subdomain'] = input(f"C2 server subdomain [default: {default_c2_subdomain}]: ") or default_c2_subdomain
# Always use the most up-to-date domain for the email default
default_email = vars_data.get('letsencrypt_email')
if not default_email or "example.com" in default_email:
# Email for Let's Encrypt
default_email = f"admin@{config['domain']}"
config['letsencrypt_email'] = input(f"Email for Let's Encrypt [default: {default_email}]: ") or default_email
# Security options
print("\nSecurity options:")
default_disable_history = vars_data.get('disable_history', True)
default_secure_memory = vars_data.get('secure_memory', True)
default_zero_logs = vars_data.get('zero_logs', True)
default_disable_history = vars_data.get(config['provider'], {}).get('disable_history', True)
default_secure_memory = vars_data.get(config['provider'], {}).get('secure_memory', True)
default_zero_logs = vars_data.get(config['provider'], {}).get('zero_logs', True)
disable_history = input(f"Disable command history? (y/n) [default: {'y' if default_disable_history else 'n'}]: ").lower()
secure_memory = input(f"Enable secure memory settings? (y/n) [default: {'y' if default_secure_memory else 'n'}]: ").lower()
@@ -348,6 +431,48 @@ def interactive_setup(deployment_id=None):
config['secure_memory'] = True if (secure_memory == 'y' or (default_secure_memory and secure_memory != 'n')) else False
config['zero_logs'] = True if (zero_logs == 'y' or (default_zero_logs and zero_logs != 'n')) else False
# Add multi-region option
if not cross_provider and not config.get('c2_only') and not config.get('redirector_only'):
multi_region = input("\nDo you want to deploy redirector and C2 in different regions? (y/n) [default: n]: ").lower() == 'y'
if multi_region:
provider_dir = PROVIDER_DIRS.get(config['provider'], config['provider'].capitalize())
vars_file = f"{provider_dir}/vars.yaml"
if os.path.exists(vars_file):
with open(vars_file, 'r') as f:
vars_data = yaml.safe_load(f) or {}
if config['provider'] == 'aws':
regions = vars_data.get('aws_region_choices', [])
elif config['provider'] == 'linode':
regions = vars_data.get('region_choices', [])
else:
regions = []
if regions:
print("\nAvailable regions:")
for i, region in enumerate(regions, 1):
print(f" {i}. {region}")
redirector_region_input = input("\nSelect redirector region (number): ")
try:
redirector_region_choice = int(redirector_region_input)
if 1 <= redirector_region_choice <= len(regions):
config['redirector_region'] = regions[redirector_region_choice - 1]
except ValueError:
print("Invalid input, using random region for redirector")
c2_region_input = input("Select C2 region (number): ")
try:
c2_region_choice = int(c2_region_input)
if 1 <= c2_region_choice <= len(regions):
config['c2_region'] = regions[c2_region_choice - 1]
except ValueError:
print("Invalid input, using random region for C2")
else:
print("No regions found in vars file, skipping multi-region setup")
# Tracker configuration if enabled
if config['deploy_tracker']:
default_tracker_domain = f"track.{config['domain']}"
@@ -382,11 +507,12 @@ def interactive_setup(deployment_id=None):
if config.get('deploy_tracker'):
config['tracker_name'] = f"t-{deployment_id}"
# Additional settings from vars_data
config['gophish_admin_port'] = vars_data.get('gophish_admin_port', str(random.randint(2000, 9000)))
config['smtp_auth_user'] = vars_data.get('smtp_auth_user', f"user{random.randint(1000, 9999)}")
config['smtp_auth_pass'] = vars_data.get('smtp_auth_pass', ''.join(random.choices(string.ascii_letters + string.digits, k=20)))
config['shell_handler_port'] = vars_data.get('shell_handler_port', str(random.randint(4000, 65000)))
# Additional settings
default_provider_vars = vars_data.get(config['provider'], {})
config['gophish_admin_port'] = default_provider_vars.get('gophish_admin_port', str(random.randint(2000, 9000)))
config['smtp_auth_user'] = default_provider_vars.get('smtp_auth_user', f"user{random.randint(1000, 9999)}")
config['smtp_auth_pass'] = default_provider_vars.get('smtp_auth_pass', ''.join(random.choices(string.ascii_letters + string.digits, k=20)))
config['shell_handler_port'] = default_provider_vars.get('shell_handler_port', str(random.randint(4000, 65000)))
# Set up integrated tracker flag for deployment
if config.get('deploy_tracker') and config.get('integrated_tracker'):
@@ -657,137 +783,87 @@ def deploy_infrastructure(config):
if config.get('linode_token'):
os.environ['LINODE_TOKEN'] = config['linode_token']
# IMPORTANT: Ensure ssh_user is explicitly set to avoid template recursion
if provider == "linode":
config['ssh_user'] = "root"
elif provider == "aws":
config['ssh_user'] = "kali"
# Set correct ssh_user based on provider
if not config.get('ssh_user'):
config['ssh_user'] = DEFAULT_SSH_USER.get(provider, 'root')
# Set tracker deployment flag for C2 configuration
if config.get('deploy_tracker') and config.get('integrated_tracker'):
config['setup_integrated_tracker'] = True
logging.info("Integrated tracker will be deployed on C2 server")
# Handle cross-provider deployment
redirector_provider = config.get('redirector_provider', provider)
c2_provider = config.get('c2_provider', provider)
# Select random region if not specified
if not config.get('region'):
if provider == "linode" and config.get('linode_region'):
# Use the specified linode_region
config['region'] = config['linode_region']
elif provider == "aws" and config.get('aws_region'):
# Use the specified aws_region
config['region'] = config['aws_region']
else:
# If no specific region provided, select random
config['region'] = select_random_region(config)
is_cross_provider = (redirector_provider != c2_provider) or \
(config.get('redirector_region') and config.get('c2_region') and \
config.get('redirector_region') != config.get('c2_region'))
# Set different instance sizes for redirector vs. C2
if provider == "linode":
# Store the original plan
original_plan = config.get('plan', 'g6-standard-2')
if is_cross_provider and not (config.get('redirector_only') or config.get('c2_only')):
return deploy_cross_provider(config, redirector_provider, c2_provider)
# For redirector, use smaller instance size regardless of what was specified
# For FlokiNET, validate required IPs
if provider == "flokinet":
if not config.get('c2_only') and not config.get('flokinet_redirector_ip') and not config.get('redirector_ip'):
logging.error("FlokiNET redirector IP is required")
return False
if not config.get('redirector_only') and not config.get('flokinet_c2_ip') and not config.get('c2_ip'):
logging.error("FlokiNET C2 IP is required")
return False
# Get correct provider directory
provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize())
# Deploy redirector if needed
if not config.get('c2_only'):
redirector_config = config.copy()
redirector_config['plan'] = 'g6-nanode-1' # Smallest viable Linode plan
if config.get('redirector_region'):
redirector_config['region'] = config['redirector_region']
# Use the correct case for provider directory
provider_dir = PROVIDER_DIRS[provider]
playbook = f"{provider_dir}/redirector.yml"
inventory_path = create_inventory_file(redirector_config, "local")
logging.info(f"Deploying redirector using {playbook} with plan: g6-nanode-1")
logging.info(f"Deploying {provider} redirector using {playbook} in region {redirector_config.get('region', 'default')}")
redirector_success, stdout, stderr = run_ansible_playbook(
playbook, inventory_path, redirector_config, redirector_config.get('debug', False)
)
# Log Ansible output
if redirector_config.get('debug', False):
logging.debug(f"Ansible stdout: {stdout}")
if stderr:
logging.debug(f"Ansible stderr: {stderr}")
if os.path.exists(inventory_path):
os.unlink(inventory_path)
if not redirector_success:
logging.error("Redirector deployment failed")
logging.error(f"{provider} redirector deployment failed")
if redirector_config.get('debug'):
logging.error(f"Ansible stderr: {stderr}")
return False
# For C2, use the original plan
# Extract and save redirector IP for C2 configuration
if 'redirector_ip' in redirector_config:
config['redirector_ip'] = redirector_config['redirector_ip']
# Deploy C2 if needed
if not config.get('redirector_only'):
# Restore original plan for C2
config['plan'] = original_plan
c2_config = config.copy()
if config.get('c2_region'):
c2_config['region'] = config['c2_region']
provider_dir = PROVIDER_DIRS[provider]
playbook = f"{provider_dir}/c2.yml"
inventory_path = create_inventory_file(config, "local")
inventory_path = create_inventory_file(c2_config, "local")
logging.info(f"Deploying C2 server using {playbook} with plan: {config['plan']}")
logging.info(f"Deploying {provider} C2 server using {playbook} in region {c2_config.get('region', 'default')}")
c2_success, stdout, stderr = run_ansible_playbook(
playbook, inventory_path, config, config.get('debug', False)
playbook, inventory_path, c2_config, c2_config.get('debug', False)
)
# Log Ansible output
if config.get('debug', False):
logging.debug(f"Ansible stdout: {stdout}")
if stderr:
logging.debug(f"Ansible stderr: {stderr}")
if os.path.exists(inventory_path):
os.unlink(inventory_path)
if not c2_success:
logging.error("C2 server deployment failed")
return False
else:
# For AWS and other providers, follow the same pattern but with their directory names
provider_dir = PROVIDER_DIRS[provider]
# Redirector deployment
if not config.get('c2_only'):
playbook = f"{provider_dir}/redirector.yml"
inventory_path = create_inventory_file(config, "local")
logging.info(f"Deploying redirector using {playbook}")
redirector_success, stdout, stderr = run_ansible_playbook(
playbook, inventory_path, config, config.get('debug', False)
)
# Log Ansible output
if config.get('debug', False):
logging.debug(f"Ansible stdout: {stdout}")
if stderr:
logging.debug(f"Ansible stderr: {stderr}")
if os.path.exists(inventory_path):
os.unlink(inventory_path)
if not redirector_success:
logging.error("Redirector deployment failed")
logging.error(f"{provider} C2 server deployment failed")
if c2_config.get('debug'):
logging.error(f"Ansible stderr: {stderr}")
return False
# C2 deployment
if not config.get('redirector_only'):
playbook = f"{provider_dir}/c2.yml"
inventory_path = create_inventory_file(config, "local")
logging.info(f"Deploying C2 server using {playbook}")
c2_success, stdout, stderr = run_ansible_playbook(
playbook, inventory_path, config, config.get('debug', False)
)
# Log Ansible output
if config.get('debug', False):
logging.debug(f"Ansible stdout: {stdout}")
if stderr:
logging.debug(f"Ansible stderr: {stderr}")
if os.path.exists(inventory_path):
os.unlink(inventory_path)
if not c2_success:
logging.error("C2 server deployment failed")
return False
# Extract and save C2 IP for reference
if 'c2_ip' in c2_config:
config['c2_ip'] = c2_config['c2_ip']
return True
@@ -951,11 +1027,13 @@ def ssh_to_instance(config):
# Build SSH command
ssh_cmd = [
"ssh",
"-t",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "IdentitiesOnly=yes",
"-i", ssh_key,
f"{ssh_user}@{ip}"
f"{ssh_user}@{ip}",
"tmux new"
]
# Add port if specified
@@ -1325,7 +1403,7 @@ def generate_deployment_info(config, success=True):
if havoc_admin_password:
info.append(f"Admin Password: {havoc_admin_password}")
else:
info.append("Admin Password: Check /root/Tools/havoc/data/profiles/default.yaotl on C2 server")
info.append("Admin Password: Check /root/Tools/Havoc/data/profiles/default.yaotl on C2 server")
info.append("")
# DNS Configuration
@@ -1396,6 +1474,54 @@ def generate_deployment_info(config, success=True):
logging.info(f"Deployment information saved to {log_file}")
return log_file
def deploy_cross_provider(config, redirector_provider, c2_provider):
"""Deploy infrastructure across multiple providers"""
# Create copies of config for each provider
redirector_config = config.copy()
redirector_config['provider'] = redirector_provider
redirector_config['c2_only'] = False
redirector_config['redirector_only'] = True
c2_config = config.copy()
c2_config['provider'] = c2_provider
c2_config['c2_only'] = True
c2_config['redirector_only'] = False
# Set specific regions if provided
if config.get('redirector_region'):
redirector_config['region'] = config['redirector_region']
if config.get('c2_region'):
c2_config['region'] = config['c2_region']
logging.info(f"Cross-provider deployment: Redirector using {redirector_provider} in {redirector_config.get('region', 'default region')}")
# Deploy redirector first
redirector_success = deploy_infrastructure(redirector_config)
if not redirector_success:
logging.error("Redirector deployment failed!")
return False
# Pass redirector IP to C2 config
if 'redirector_ip' in redirector_config:
c2_config['redirector_ip'] = redirector_config['redirector_ip']
config['redirector_ip'] = redirector_config['redirector_ip']
logging.info(f"Cross-provider deployment: C2 server using {c2_provider} in {c2_config.get('region', 'default region')}")
# Deploy C2 server
c2_success = deploy_infrastructure(c2_config)
if not c2_success:
logging.error("C2 server deployment failed!")
return False
# Update the original config with IPs from both deployments
if 'c2_ip' in c2_config:
config['c2_ip'] = c2_config['c2_ip']
return True
def main():
"""Main function to run the deployment"""
# Display banner
+2 -2
View File
@@ -6,8 +6,8 @@
set -e
TEMP_DIR=$(mktemp -d)
LOG_FILE="/root/Tools/havoc_installer.log"
HAVOC_DIR="/root/Tools/havoc"
HAVOC_DATA_DIR="/root/Tools/havoc/data"
HAVOC_DIR="/root/Tools/Havoc"
HAVOC_DATA_DIR="/root/Tools/Havoc/data"
COMPILER_URL="http://musl.cc/x86_64-w64-mingw32-cross.tgz"
COMPILER_DIR="/usr/bin/x86_64-w64-mingw32-cross"
COMPILER_PATH="$COMPILER_DIR/bin/x86_64-w64-mingw32-gcc"
+6 -6
View File
@@ -6,7 +6,7 @@ LISTEN_PORT=4444
C2_HOST="127.0.0.1" # This will be replaced by Ansible with actual C2 IP
HAVOC_PORT=40056 # Havoc default Teamserver port
HAVOC_USER="admin"
HAVOC_DIR="/root/Tools/havoc"
HAVOC_DIR="/root/Tools/Havoc"
WINDOWS_PAYLOAD="windows/agent_win.exe"
LINUX_PAYLOAD="linux/agent_linux"
@@ -65,21 +65,21 @@ deploy_agent() {
log "Deploying Havoc agent for detected OS: $os_type"
# Get the latest payload paths from manifest
local manifest="/root/Tools/havoc/payloads/manifest.json"
local manifest="/root/Tools/Havoc/payloads/manifest.json"
if [ -f "$manifest" ]; then
if [ "$os_type" == "windows" ]; then
WIN_EXE=$(jq -r '.windows_exe' "$manifest")
PAYLOAD_PATH="/root/Tools/havoc/payloads/windows/$WIN_EXE"
PAYLOAD_PATH="/root/Tools/Havoc/payloads/windows/$WIN_EXE"
elif [ "$os_type" == "linux" ]; then
LINUX_BIN=$(jq -r '.linux_binary' "$manifest")
PAYLOAD_PATH="/root/Tools/havoc/payloads/linux/$LINUX_BIN"
PAYLOAD_PATH="/root/Tools/Havoc/payloads/linux/$LINUX_BIN"
fi
else
# Use default paths if manifest doesn't exist
if [ "$os_type" == "windows" ]; then
PAYLOAD_PATH="/root/Tools/havoc/payloads/windows/agent_win.exe"
PAYLOAD_PATH="/root/Tools/Havoc/payloads/windows/agent_win.exe"
elif [ "$os_type" == "linux" ]; then
PAYLOAD_PATH="/root/Tools/havoc/payloads/linux/agent_linux"
PAYLOAD_PATH="/root/Tools/Havoc/payloads/linux/agent_linux"
fi
fi
+3 -3
View File
@@ -134,7 +134,7 @@
- name: Install Havoc C2 Framework
shell: "/root/Tools/havoc_installer.sh"
args:
creates: "/root/Tools/havoc"
creates: "/root/Tools/Havoc"
async: 1800 # Allow 30 minutes for completion
poll: 0 # Don't wait for completion
register: havoc_installation_job
@@ -164,7 +164,7 @@
- name: Create Havoc C2 configuration from template
template:
src: "../templates/havoc-config.yaotl.j2"
dest: "/root/Tools/havoc/data/havoc.yaotl"
dest: "/root/Tools/Havoc/data/havoc.yaotl"
mode: '0600'
owner: root
group: root
@@ -204,7 +204,7 @@
- name: Generate Havoc payloads
shell: "/root/Tools/generate_havoc_payloads.sh"
args:
creates: "/root/Tools/havoc/payloads/manifest.json"
creates: "/root/Tools/Havoc/payloads/manifest.json"
register: payload_generation_result
environment:
PATH: "{{ ansible_env.PATH }}:/usr/local/bin"
+1 -1
View File
@@ -3,7 +3,7 @@
# Every deployment produces completely unique payloads
# Configuration (automatically populated by Ansible)
HAVOC_DIR="/root/Tools/havoc"
HAVOC_DIR="/root/Tools/Havoc"
BEACONS_DIR="/root/Tools/beacons"
C2_HOST="{{ ansible_host }}"
REDIRECTOR_HOST="{{ redirector_subdomain }}.{{ domain }}"
+2 -2
View File
@@ -2,13 +2,13 @@
# EDR-evasive payload generator for Havoc C2
# Configuration (automatically populated by Ansible)
PAYLOADS_DIR="/root/Tools/havoc/payloads"
PAYLOADS_DIR="/root/Tools/Havoc/payloads"
C2_HOST="{{ ansible_host }}"
REDIRECTOR_HOST="{{ redirector_subdomain }}.{{ domain }}"
REDIRECTOR_PORT="{{ redirector_port | default('443') }}"
TEAMSERVER_HOST="127.0.0.1"
TEAMSERVER_PORT="40056"
HAVOC_DIR="/root/Tools/havoc"
HAVOC_DIR="/root/Tools/Havoc"
HAVOC_CLIENT="$HAVOC_DIR/Client/havoc"
# Ensure required directories exist
+31 -3
View File
@@ -19,7 +19,8 @@ Operators {
{% for operator in havoc_operators %}
user "{{ operator.name }}" {
Password = "{{ operator.password }}"
Secret = "{{ operator.secret | default(lookup('password', '/dev/null chars=hex_lower length=32')) }}"
# Secret is not in documentation, removed or comment it out
# Secret = "{{ operator.secret | default(lookup('password', '/dev/null chars=hex_lower length=32')) }}"
}
{% endfor %}
{% endif %}
@@ -42,6 +43,9 @@ Listeners {
HostBind = "0.0.0.0"
HostRotation = "{{ havoc_host_rotation | default('round-robin') }}"
PortBind = {{ havoc_http_port | default(8080) }}
{% if havoc_portconn is defined %}
PortConn = {{ havoc_portconn }}
{% endif %}
{% if havoc_user_agent is defined %}
UserAgent = "{{ havoc_user_agent }}"
{% else %}
@@ -128,8 +132,8 @@ Listeners {
Secure = true
Cert {
Cert = "{{ havoc_cert_path | default('/root/Tools/havoc/data/certs/havoc.crt') }}"
Key = "{{ havoc_key_path | default('/root/Tools/havoc/data/certs/havoc.key') }}"
Cert = "{{ havoc_cert_path | default('/root/Tools/Havoc/data/certs/havoc.crt') }}"
Key = "{{ havoc_key_path | default('/root/Tools/Havoc/data/certs/havoc.key') }}"
}
{% if havoc_proxy_enabled | default(false) %}
@@ -153,6 +157,9 @@ Listeners {
Demon {
Sleep = {{ havoc_sleep | default(random_hex[0:2] | int % 5 + 2) }}
Jitter = {{ havoc_jitter | default(random_hex[2:4] | int % 30 + 20) }}
{% if havoc_trust_x_forwarded_for is defined %}
TrustXForwardedFor = {{ havoc_trust_x_forwarded_for | lower }}
{% endif %}
Injection {
{% if havoc_spawn64 is defined %}
@@ -168,3 +175,24 @@ Demon {
{% endif %}
}
}
{% if havoc_service_enabled | default(false) %}
Service {
Endpoint = "{{ havoc_service_endpoint | default('/api/service') }}"
Password = "{{ havoc_service_password | default(lookup('password', '/dev/null chars=ascii_letters,digits length=32')) }}"
}
{% endif %}
{% if havoc_webhook_discord_enabled | default(false) %}
Webhook {
Discord {
Url = "{{ havoc_webhook_discord_url }}"
{% if havoc_webhook_discord_avatar_url is defined %}
AvatarUrl = "{{ havoc_webhook_discord_avatar_url }}"
{% endif %}
{% if havoc_webhook_discord_user is defined %}
User = "{{ havoc_webhook_discord_user }}"
{% endif %}
}
}
{% endif %}
+4 -4
View File
@@ -12,7 +12,7 @@ Teamserver Port: {{ havoc_teamserver_port | default(40056) }}
HTTP Listener Port: {{ havoc_http_port | default(8080) }}
HTTPS Listener Port: {{ havoc_https_port | default(443) }}
Admin User: {{ havoc_admin_user | default('admin') }}
Admin Password: Stored in /root/Tools/havoc/data/profiles/default.yaotl
Admin Password: Stored in /root/Tools/Havoc/data/profiles/default.yaotl
CONNECTING TO THE TEAMSERVER
---------------------------
@@ -29,7 +29,7 @@ From your local machine:
- Host: {{ c2_ip }}
- Port: {{ havoc_teamserver_port | default(40056) }}
- User: {{ havoc_admin_user | default('admin') }}
- Password: See /root/Tools/havoc/data/profiles/default.yaotl
- Password: See /root/Tools/Havoc/data/profiles/default.yaotl
3. CLI Connection:
$ ./havoc client --address {{ c2_ip }}:{{ havoc_teamserver_port | default(40056) }} --username {{ havoc_admin_user | default('admin') }} --password [password]
@@ -44,7 +44,7 @@ To view and manage listeners: Attack → Listeners in the Havoc client.
GENERATING PAYLOADS
-----------------
Pre-generated payloads are available in /root/Tools/havoc/payloads/
Pre-generated payloads are available in /root/Tools/Havoc/payloads/
To generate new payloads:
1. Connect to the Teamserver
@@ -94,7 +94,7 @@ SERVER MANAGEMENT
---------------
- Havoc Teamserver service: systemctl status havoc
- Service configuration: /etc/systemd/system/havoc.service
- Configuration profiles: /root/Tools/havoc/data/profiles/
- Configuration profiles: /root/Tools/Havoc/data/profiles/
TROUBLESHOOTING
--------------
+1 -1
View File
@@ -37,7 +37,7 @@ Custom Tools Installed in ~/Tools:
Other Installed C2 Frameworks:
------------------------------
- Metasploit Framework: system installed (run 'msfconsole')
- Havoc C2: installed in /root/Tools/havoc
- Havoc C2: installed in /root/Tools/Havoc
Security Scripts in /root/Tools/:
-----------------------------
+1 -1
View File
@@ -29,7 +29,7 @@ Custom Tools Installed in ~/Tools:
Other Installed C2 Frameworks:
------------------------------
- Metasploit Framework: system installed (run 'msfconsole')
- Havoc C2: installed in /root/Tools/havoc
- Havoc C2: installed in /root/Tools/Havoc
Also, remember that many reconnaissance and attack tools are now available system-wide due to the apt and pipx installations.
+1 -1
View File
@@ -29,7 +29,7 @@ Custom Tools Installed in ~/Tools:
Other Installed C2 Frameworks:
------------------------------
- Metasploit Framework: system installed (run 'msfconsole')
- Havoc C2: installed in /root/Tools/havoc
- Havoc C2: installed in /root/Tools/Havoc
Also, remember that many reconnaissance and attack tools are now available system-wide due to the apt and pipx installations.
+1 -1
View File
@@ -2,7 +2,7 @@
# Script to serve Havoc C2 payloads generated by generate_havoc_payloads.sh
# Configuration
PAYLOADS_DIR="/root/Tools/havoc/payloads"
PAYLOADS_DIR="/root/Tools/Havoc/payloads"
C2_HOST="{{ ansible_host }}"
LISTEN_PORT=8443