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