working out bugs

This commit is contained in:
n0mad1k
2025-04-09 15:57:22 -04:00
parent b623e76a5a
commit 80072a6bdc
3 changed files with 386 additions and 136 deletions
+2
View File
@@ -1,2 +1,4 @@
vars.yaml vars.yaml
venv venv
deployment*
config.yml
+2 -2
View File
@@ -15,12 +15,12 @@
- name: Create Linode redirector instance - name: Create Linode redirector instance
community.general.linode_v4: community.general.linode_v4:
label: "{{ instance_name }}" label: "{{ instance_name }}"
type: "{{ instance_type }}" type: "{{ plan }}"
region: "{{ region }}" region: "{{ region }}"
image: "{{ linode_image }}" image: "{{ linode_image }}"
root_pass: "{{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}" root_pass: "{{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}"
authorized_keys: authorized_keys:
- "{{ lookup('file', ssh_key) }}" - "{{ ssh_key_content }}"
state: present state: present
register: redirector_instance register: redirector_instance
+355 -107
View File
@@ -311,13 +311,24 @@ def deploy_aws(config):
os.environ['AWS_ACCESS_KEY_ID'] = config['aws_key'] os.environ['AWS_ACCESS_KEY_ID'] = config['aws_key']
os.environ['AWS_SECRET_ACCESS_KEY'] = config['aws_secret'] os.environ['AWS_SECRET_ACCESS_KEY'] = config['aws_secret']
# Prepare SSH key - for AWS we'll use AWS key pairs instead of local files
ssh_key_name = config.get('ssh_key_name')
if not ssh_key_name:
# Generate a random key name for AWS
rand_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
timestamp = int(time.time()) % 10000
ssh_key_name = f"c2ingred-{rand_suffix}-{timestamp}"
config['ssh_key_name'] = ssh_key_name
print(f"[+] Using AWS key pair name: {ssh_key_name}")
# Create a temporary inventory file for Ansible # Create a temporary inventory file for Ansible
with open('inventory_aws.yml', 'w') as f: with open('inventory_aws.yml', 'w') as f:
f.write("---\n") f.write("---\n")
f.write("all:\n") f.write("all:\n")
f.write(" vars:\n") f.write(" vars:\n")
f.write(f" ansible_ssh_private_key_file: {config['ssh_key']}\n") f.write(f" ansible_ssh_private_key_file: ~/.ssh/{ssh_key_name}.pem\n")
f.write(f" ansible_user: {config['ssh_user']}\n") f.write(f" ansible_user: {config.get('ssh_user', 'ec2-user')}\n")
f.write(f" ansible_python_interpreter: /usr/bin/python3\n") f.write(f" ansible_python_interpreter: /usr/bin/python3\n")
f.write(" children:\n") f.write(" children:\n")
f.write(" redirectors:\n") f.write(" redirectors:\n")
@@ -329,74 +340,136 @@ def deploy_aws(config):
f.write(" c2:\n") f.write(" c2:\n")
f.write(" ansible_host: '{{ c2_ip }}'\n") f.write(" ansible_host: '{{ c2_ip }}'\n")
# Generate default names if not provided
rand_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
timestamp = int(time.time()) % 10000
redirector_name = config.get('redirector_name', f"srv-{rand_suffix}-{timestamp}")
c2_name = config.get('c2_name', f"node-{rand_suffix}-{timestamp}")
# Update config with generated names for logging
config['redirector_name'] = redirector_name
config['c2_name'] = c2_name
# Log important information
print(f"[+] Deployment information:")
print(f" - AWS Key Pair Name: {ssh_key_name}")
print(f" - Redirector Name: {redirector_name}")
print(f" - C2 Name: {c2_name}")
print(f" - AWS Region: {config.get('aws_region', 'default region')}")
print(f" - Domain: {config.get('domain', 'example.com')}")
# Run AWS provisioning playbook # Run AWS provisioning playbook
success = False
deployed_instances = []
try: try:
# Create extra_vars dictionary with all config values # Create extra_vars dictionary with all config values
extra_vars = { extra_vars = {
'aws_access_key': config['aws_key'], 'aws_access_key': config['aws_key'],
'aws_secret_key': config['aws_secret'], 'aws_secret_key': config['aws_secret'],
'aws_region': config['aws_region'], 'aws_region': config.get('aws_region', DEFAULT_REGION.get('AWS')),
'ami_map': config['ami_map'], 'ami_map': config.get('ami_map', {}),
'size': config['size'], 'size': config.get('size', DEFAULT_SIZE.get('AWS')),
'aws_instance_type': config['size'], 'aws_instance_type': config.get('size', DEFAULT_SIZE.get('AWS')),
'redirector_name': config['redirector_name'], 'redirector_name': redirector_name,
'c2_name': config['c2_name'], 'c2_name': c2_name,
'teardown': config['teardown'], 'teardown': config.get('teardown', False),
'disable_history': config['disable_history'], 'disable_history': config.get('disable_history', True),
'secure_memory': config['secure_memory'], 'secure_memory': config.get('secure_memory', True),
'zero_logs': config['zero_logs'], 'zero_logs': config.get('zero_logs', True),
'redirector_only': config['redirector_only'], 'redirector_only': config.get('redirector_only', False),
'c2_only': config['c2_only'], 'c2_only': config.get('c2_only', False),
'debug': config['debug'], 'debug': config.get('debug', False),
'domain': config['domain'], 'domain': config.get('domain', 'example.com'),
'letsencrypt_email': config['letsencrypt_email'], 'letsencrypt_email': config.get('letsencrypt_email', 'admin@example.com'),
'gophish_admin_port': config['gophish_admin_port'], 'gophish_admin_port': config.get('gophish_admin_port', str(random.randint(2000, 9000))),
'smtp_auth_user': config['smtp_auth_user'], 'smtp_auth_user': config.get('smtp_auth_user', f"user{random.randint(1000, 9999)}"),
'smtp_auth_pass': config['smtp_auth_pass'], 'smtp_auth_pass': config.get('smtp_auth_pass', ''.join(random.choices(string.ascii_letters + string.digits + "!@#$%^&*", k=20))),
'key_name': ssh_key_name,
'private_key_path': f"~/.ssh/{ssh_key_name}.pem",
} }
extra_vars_str = ' '.join([f"{k}={v}" for k, v in extra_vars.items() if not isinstance(v, dict)]) # Format extra vars for command line, handling special characters
extra_vars_list = []
for k, v in extra_vars.items():
if isinstance(v, bool):
extra_vars_list.append(f"{k}={str(v).lower()}")
elif isinstance(v, (int, float)):
extra_vars_list.append(f"{k}={v}")
elif isinstance(v, str):
# Escape quotes in string values
escaped_v = v.replace("'", "'\\''")
extra_vars_list.append(f"{k}='{escaped_v}'")
elif not isinstance(v, dict): # Skip dict values
extra_vars_list.append(f"{k}={v}")
extra_vars_str = " ".join(extra_vars_list)
# Handle the ami_map special case # Handle the ami_map special case
ami_map_str = "" ami_map_str = ""
if config['ami_map']: if config.get('ami_map'):
ami_map_str = f"ami_map='{json.dumps(config['ami_map'])}'" ami_map_str = f"ami_map='{json.dumps(config['ami_map'])}'"
# Determine playbook to run based on deployment mode # Determine playbook to run based on deployment mode
if config['redirector_only']: if extra_vars['redirector_only']:
playbook = "AWS/redirector.yml" playbook = "AWS/redirector.yml"
cmd = f"ansible-playbook -i inventory_aws.yml {playbook} -e '{extra_vars_str} {ami_map_str}'" cmd = f"ansible-playbook -i inventory_aws.yml {playbook} -e '{extra_vars_str} {ami_map_str}'"
if config['debug']: if extra_vars['debug']:
print(f"[+] Running: {cmd}") print(f"[+] Running: {cmd}")
subprocess.run(cmd, shell=True, check=True) subprocess.run(cmd, shell=True, check=True)
elif config['c2_only']: deployed_instances.append(redirector_name)
elif extra_vars['c2_only']:
playbook = "AWS/c2.yml" playbook = "AWS/c2.yml"
cmd = f"ansible-playbook -i inventory_aws.yml {playbook} -e '{extra_vars_str} {ami_map_str}'" cmd = f"ansible-playbook -i inventory_aws.yml {playbook} -e '{extra_vars_str} {ami_map_str}'"
if config['debug']: if extra_vars['debug']:
print(f"[+] Running: {cmd}") print(f"[+] Running: {cmd}")
subprocess.run(cmd, shell=True, check=True) subprocess.run(cmd, shell=True, check=True)
deployed_instances.append(c2_name)
else: else:
# For full deployment, run redirector first, then C2 # For full deployment, run redirector first, then C2
# First deploy redirector # First deploy redirector
redirector_cmd = f"ansible-playbook -i inventory_aws.yml AWS/redirector.yml -e '{extra_vars_str} {ami_map_str}'" redirector_cmd = f"ansible-playbook -i inventory_aws.yml AWS/redirector.yml -e '{extra_vars_str} {ami_map_str}'"
if config['debug']: if extra_vars['debug']:
print(f"[+] Running: {redirector_cmd}") print(f"[+] Running: {redirector_cmd}")
subprocess.run(redirector_cmd, shell=True, check=True) subprocess.run(redirector_cmd, shell=True, check=True)
deployed_instances.append(redirector_name)
# Then deploy C2 server with redirector IP # Then deploy C2 server with redirector IP
c2_cmd = f"ansible-playbook -i inventory_aws.yml AWS/c2.yml -e '{extra_vars_str} {ami_map_str}'" c2_cmd = f"ansible-playbook -i inventory_aws.yml AWS/c2.yml -e '{extra_vars_str} {ami_map_str}'"
if config['debug']: if extra_vars['debug']:
print(f"[+] Running: {c2_cmd}") print(f"[+] Running: {c2_cmd}")
subprocess.run(c2_cmd, shell=True, check=True) subprocess.run(c2_cmd, shell=True, check=True)
deployed_instances.append(c2_name)
print("[+] AWS infrastructure deployed successfully!") print("[+] AWS infrastructure deployed successfully!")
success = True
return True return True
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"[-] Error: Failed to deploy AWS infrastructure: {e}") print(f"[-] Error: Failed to deploy AWS infrastructure: {e}")
# Clean up deployed instances if any
if deployed_instances:
print(f"[!] Cleaning up deployed instances due to failure...")
for instance in deployed_instances:
cleanup_cmd = f"aws ec2 terminate-instances --instance-ids {instance}"
try:
subprocess.run(cleanup_cmd, shell=True, check=False)
print(f"[+] Terminated instance: {instance}")
except Exception as cleanup_err:
print(f"[!] Failed to terminate instance {instance}: {cleanup_err}")
# Delete the key pair
delete_key_cmd = f"aws ec2 delete-key-pair --key-name {ssh_key_name}"
try:
subprocess.run(delete_key_cmd, shell=True, check=False)
print(f"[+] Deleted key pair: {ssh_key_name}")
except Exception as key_err:
print(f"[!] Failed to delete key pair {ssh_key_name}: {key_err}")
return False return False
finally: finally:
# Clean up temporary inventory file # Clean up temporary inventory file
@@ -410,13 +483,19 @@ def deploy_linode(config):
# Set Linode environment variables # Set Linode environment variables
os.environ['LINODE_TOKEN'] = config['linode_token'] os.environ['LINODE_TOKEN'] = config['linode_token']
# Prepare SSH key
ssh_key, ssh_key_content = prepare_ssh_key(config)
if not ssh_key or not ssh_key_content:
print("[-] Failed to prepare SSH key, aborting deployment")
return False
# Create a temporary inventory file for Ansible # Create a temporary inventory file for Ansible
with open('inventory_linode.yml', 'w') as f: with open('inventory_linode.yml', 'w') as f:
f.write("---\n") f.write("---\n")
f.write("all:\n") f.write("all:\n")
f.write(" vars:\n") f.write(" vars:\n")
f.write(f" ansible_ssh_private_key_file: {config['ssh_key']}\n") f.write(f" ansible_ssh_private_key_file: {ssh_key}\n")
f.write(f" ansible_user: {config['ssh_user']}\n") f.write(f" ansible_user: {config.get('ssh_user', 'root')}\n")
f.write(f" ansible_python_interpreter: /usr/bin/python3\n") f.write(f" ansible_python_interpreter: /usr/bin/python3\n")
f.write(" children:\n") f.write(" children:\n")
f.write(" redirectors:\n") f.write(" redirectors:\n")
@@ -428,75 +507,142 @@ def deploy_linode(config):
f.write(" c2:\n") f.write(" c2:\n")
f.write(" ansible_host: '{{ c2_ip }}'\n") f.write(" ansible_host: '{{ c2_ip }}'\n")
# Generate default names if not provided
rand_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
timestamp = int(time.time()) % 10000 # Last 4 digits of timestamp
redirector_name = config.get('redirector_name', f"srv-{rand_suffix}-{timestamp}")
c2_name = config.get('c2_name', f"node-{rand_suffix}-{timestamp}")
# Update config with generated names for logging
config['redirector_name'] = redirector_name
config['c2_name'] = c2_name
# Log important information
print(f"[+] Deployment information:")
print(f" - SSH Key: {ssh_key}")
print(f" - Redirector Name: {redirector_name}")
print(f" - C2 Name: {c2_name}")
print(f" - Domain: {config.get('domain', 'example.com')}")
# Run Linode provisioning playbook # Run Linode provisioning playbook
success = False
deployed_instances = []
try: try:
extra_vars = { extra_vars = {
'linode_token': config['linode_token'], 'linode_token': config['linode_token'],
'region': config['linode_region'], 'region': config.get('linode_region', DEFAULT_REGION.get('Linode', 'us-east')),
'plan': config['size'], 'plan': config.get('plan', DEFAULT_SIZE.get('Linode', 'g6-standard-1')),
'redirector_name': config['redirector_name'], 'redirector_name': redirector_name,
'c2_name': config['c2_name'], 'c2_name': c2_name,
'teardown': config['teardown'], 'teardown': config.get('teardown', False),
'disable_history': config['disable_history'], 'disable_history': config.get('disable_history', True),
'secure_memory': config['secure_memory'], 'secure_memory': config.get('secure_memory', True),
'zero_logs': config['zero_logs'], 'zero_logs': config.get('zero_logs', True),
'redirector_only': config['redirector_only'], 'redirector_only': config.get('redirector_only', False),
'c2_only': config['c2_only'], 'c2_only': config.get('c2_only', False),
'debug': config['debug'], 'debug': config.get('debug', False),
'domain': config['domain'], 'domain': config.get('domain', 'example.com'),
'letsencrypt_email': config['letsencrypt_email'], 'letsencrypt_email': config.get('letsencrypt_email', 'admin@example.com'),
'gophish_admin_port': config['gophish_admin_port'], 'gophish_admin_port': config.get('gophish_admin_port', str(random.randint(2000, 9000))),
'smtp_auth_user': config['smtp_auth_user'], 'smtp_auth_user': config.get('smtp_auth_user', f"user{random.randint(1000, 9999)}"),
'smtp_auth_pass': config['smtp_auth_pass'], 'smtp_auth_pass': config.get('smtp_auth_pass', ''.join(random.choices(string.ascii_letters + string.digits + "!@#$%^&*", k=20))),
'ssh_key_path': ssh_key, # Path to the private key
'ssh_key': ssh_key_content, # Content of public key
} }
extra_vars_str = ' '.join([f"{k}={v}" for k, v in extra_vars.items()]) # Format extra vars for command line, handling special characters
extra_vars_list = []
for k, v in extra_vars.items():
if isinstance(v, bool):
extra_vars_list.append(f"{k}={str(v).lower()}")
elif isinstance(v, (int, float)):
extra_vars_list.append(f"{k}={v}")
elif isinstance(v, str):
# Escape quotes in string values
escaped_v = v.replace("'", "'\\''")
extra_vars_list.append(f"{k}='{escaped_v}'")
extra_vars_str = " ".join(extra_vars_list)
# Determine playbook to run based on deployment mode # Determine playbook to run based on deployment mode
if config['redirector_only']: if extra_vars['redirector_only']:
playbook = "Linode/redirector.yml" playbook = "Linode/redirector.yml"
cmd = f"ansible-playbook -i inventory_linode.yml {playbook} -e '{extra_vars_str}'" cmd = f"ansible-playbook -i inventory_linode.yml {playbook} -e '{extra_vars_str}'"
if config['debug']: if extra_vars['debug']:
print(f"[+] Running: {cmd}") print(f"[+] Running: {cmd}")
subprocess.run(cmd, shell=True, check=True) subprocess.run(cmd, shell=True, check=True)
elif config['c2_only']: deployed_instances.append(redirector_name)
elif extra_vars['c2_only']:
playbook = "Linode/c2.yml" playbook = "Linode/c2.yml"
cmd = f"ansible-playbook -i inventory_linode.yml {playbook} -e '{extra_vars_str}'" cmd = f"ansible-playbook -i inventory_linode.yml {playbook} -e '{extra_vars_str}'"
if config['debug']: if extra_vars['debug']:
print(f"[+] Running: {cmd}") print(f"[+] Running: {cmd}")
subprocess.run(cmd, shell=True, check=True) subprocess.run(cmd, shell=True, check=True)
deployed_instances.append(c2_name)
else: else:
# For full deployment, run redirector first, then C2 # For full deployment, run redirector first, then C2
# First deploy redirector # First deploy redirector
redirector_cmd = f"ansible-playbook -i inventory_linode.yml Linode/redirector.yml -e '{extra_vars_str}'" redirector_cmd = f"ansible-playbook -i inventory_linode.yml Linode/redirector.yml -e '{extra_vars_str}'"
if config['debug']: if extra_vars['debug']:
print(f"[+] Running: {redirector_cmd}") print(f"[+] Running: {redirector_cmd}")
subprocess.run(redirector_cmd, shell=True, check=True) subprocess.run(redirector_cmd, shell=True, check=True)
deployed_instances.append(redirector_name)
# Then deploy C2 server with redirector IP # Then deploy C2 server with redirector IP
c2_cmd = f"ansible-playbook -i inventory_linode.yml Linode/c2.yml -e '{extra_vars_str}'" c2_cmd = f"ansible-playbook -i inventory_linode.yml Linode/c2.yml -e '{extra_vars_str}'"
if config['debug']: if extra_vars['debug']:
print(f"[+] Running: {c2_cmd}") print(f"[+] Running: {c2_cmd}")
subprocess.run(c2_cmd, shell=True, check=True) subprocess.run(c2_cmd, shell=True, check=True)
deployed_instances.append(c2_name)
print("[+] Linode infrastructure deployed successfully!") print("[+] Linode infrastructure deployed successfully!")
success = True
return True return True
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"[-] Error: Failed to deploy Linode infrastructure: {e}") print(f"[-] Error: Failed to deploy Linode infrastructure: {e}")
# Clean up deployed instances if any
if deployed_instances:
print(f"[!] Cleaning up deployed instances due to failure...")
for instance in deployed_instances:
cleanup_cmd = f"linode-cli linodes delete {instance} --yes"
try:
subprocess.run(cleanup_cmd, shell=True, check=False)
print(f"[+] Deleted instance: {instance}")
except Exception as cleanup_err:
print(f"[!] Failed to delete instance {instance}: {cleanup_err}")
return False return False
finally: finally:
# Clean up temporary inventory file # Clean up temporary inventory file
if os.path.exists('inventory_linode.yml'): if os.path.exists('inventory_linode.yml'):
os.remove('inventory_linode.yml') os.remove('inventory_linode.yml')
# If this is a new key and deployment failed, remove it
if not success and ssh_key and ssh_key.startswith(os.path.expanduser("~/.ssh/c2ingred_")):
try:
os.remove(ssh_key)
if os.path.exists(f"{ssh_key}.pub"):
os.remove(f"{ssh_key}.pub")
print(f"[+] Removed generated SSH key due to deployment failure")
except Exception as key_err:
print(f"[!] Failed to remove SSH key {ssh_key}: {key_err}")
def deploy_flokinet(config): def deploy_flokinet(config):
"""Deploy infrastructure using FlokiNET provider""" """Deploy infrastructure using FlokiNET provider"""
print("[+] Deploying FlokiNET infrastructure...") print("[+] Deploying FlokiNET infrastructure...")
# Prepare SSH key
ssh_key, ssh_key_content = prepare_ssh_key(config)
if not ssh_key or not ssh_key_content:
print("[-] Failed to prepare SSH key, aborting deployment")
return False
# OPSEC: Use the random SSH port from config # OPSEC: Use the random SSH port from config
ssh_port = config.get('ssh_port', random.randint(20000, 65000)) ssh_port = config.get('ssh_port', random.randint(20000, 65000))
print(f"[+] Using SSH port: {ssh_port}") print(f"[+] Using SSH port: {ssh_port}")
@@ -563,11 +709,22 @@ def deploy_flokinet(config):
# Prepare vars.yaml with dynamic IPs # Prepare vars.yaml with dynamic IPs
prepare_flokinet_vars(config) prepare_flokinet_vars(config)
# Log important information
print(f"[+] Deployment information:")
print(f" - SSH Key: {ssh_key}")
print(f" - SSH Port: {ssh_port}")
print(f" - Redirector IP: {redirector_ip}")
print(f" - C2 IP: {c2_ip}")
print(f" - Domain: {config.get('domain', 'example.com')}")
# Create temporary inventory files for Ansible with randomized names for OPSEC # Create temporary inventory files for Ansible with randomized names for OPSEC
inventory_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)) inventory_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
redirector_inventory = None redirector_inventory = None
c2_inventory = None c2_inventory = None
success = False
try:
if not config['c2_only']: if not config['c2_only']:
redirector_inventory = f'inventory_r_{inventory_suffix}.yml' redirector_inventory = f'inventory_r_{inventory_suffix}.yml'
with open(redirector_inventory, 'w') as f: with open(redirector_inventory, 'w') as f:
@@ -578,8 +735,7 @@ def deploy_flokinet(config):
f.write(f" redirector:\n") f.write(f" redirector:\n")
f.write(f" ansible_host: {redirector_ip}\n") f.write(f" ansible_host: {redirector_ip}\n")
f.write(f" ansible_user: {config['ssh_user']}\n") f.write(f" ansible_user: {config['ssh_user']}\n")
if config.get('ssh_key'): f.write(f" ansible_ssh_private_key_file: {ssh_key}\n")
f.write(f" ansible_ssh_private_key_file: {config['ssh_key']}\n")
f.write(f" ansible_python_interpreter: /usr/bin/python3\n") f.write(f" ansible_python_interpreter: /usr/bin/python3\n")
f.write(f" ansible_port: {ssh_port}\n") # Use custom SSH port f.write(f" ansible_port: {ssh_port}\n") # Use custom SSH port
@@ -593,29 +749,40 @@ def deploy_flokinet(config):
f.write(f" c2:\n") f.write(f" c2:\n")
f.write(f" ansible_host: {c2_ip}\n") f.write(f" ansible_host: {c2_ip}\n")
f.write(f" ansible_user: {config['ssh_user']}\n") f.write(f" ansible_user: {config['ssh_user']}\n")
if config.get('ssh_key'): f.write(f" ansible_ssh_private_key_file: {ssh_key}\n")
f.write(f" ansible_ssh_private_key_file: {config['ssh_key']}\n")
f.write(f" ansible_python_interpreter: /usr/bin/python3\n") f.write(f" ansible_python_interpreter: /usr/bin/python3\n")
f.write(f" ansible_port: {ssh_port}\n") # Use custom SSH port f.write(f" ansible_port: {ssh_port}\n") # Use custom SSH port
# Run FlokiNET provisioning playbooks
try:
# Build extra vars string # Build extra vars string
extra_vars = { extra_vars = {
'redirector_ip': redirector_ip, 'redirector_ip': redirector_ip,
'c2_ip': c2_ip, 'c2_ip': c2_ip,
'disable_history': config['disable_history'], 'disable_history': config.get('disable_history', True),
'secure_memory': config['secure_memory'], 'secure_memory': config.get('secure_memory', True),
'zero_logs': config['zero_logs'], 'zero_logs': config.get('zero_logs', True),
'domain': config['domain'], 'domain': config.get('domain', 'example.com'),
'letsencrypt_email': config['letsencrypt_email'], 'letsencrypt_email': config.get('letsencrypt_email', 'admin@example.com'),
'gophish_admin_port': config['gophish_admin_port'], 'gophish_admin_port': config.get('gophish_admin_port', str(random.randint(2000, 9000))),
'smtp_auth_user': config['smtp_auth_user'], 'smtp_auth_user': config.get('smtp_auth_user', f"user{random.randint(1000, 9999)}"),
'smtp_auth_pass': config['smtp_auth_pass'], 'smtp_auth_pass': config.get('smtp_auth_pass', ''.join(random.choices(string.ascii_letters + string.digits + "!@#$%^&*", k=20))),
'ssh_port': ssh_port 'ssh_port': ssh_port,
'ssh_key_path': ssh_key,
'ssh_key_content': ssh_key_content,
} }
extra_vars_str = ' '.join([f"{k}='{v}'" for k, v in extra_vars.items()]) # Format extra vars for command line, handling special characters
extra_vars_list = []
for k, v in extra_vars.items():
if isinstance(v, bool):
extra_vars_list.append(f"{k}={str(v).lower()}")
elif isinstance(v, (int, float)):
extra_vars_list.append(f"{k}={v}")
elif isinstance(v, str):
# Escape quotes in string values
escaped_v = v.replace("'", "'\\''")
extra_vars_list.append(f"{k}='{escaped_v}'")
extra_vars_str = " ".join(extra_vars_list)
# Common provisioning for both servers # Common provisioning for both servers
if not config['c2_only'] and redirector_inventory: if not config['c2_only'] and redirector_inventory:
@@ -647,6 +814,7 @@ def deploy_flokinet(config):
subprocess.run(cmd, shell=True, check=True) subprocess.run(cmd, shell=True, check=True)
print("[+] FlokiNET infrastructure deployed successfully!") print("[+] FlokiNET infrastructure deployed successfully!")
success = True
return True return True
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"[-] Error: Failed to deploy FlokiNET infrastructure: {e}") print(f"[-] Error: Failed to deploy FlokiNET infrastructure: {e}")
@@ -659,9 +827,19 @@ def deploy_flokinet(config):
with open(inv_file, 'w') as f: with open(inv_file, 'w') as f:
f.write('\0' * 1024) # Overwrite with null bytes f.write('\0' * 1024) # Overwrite with null bytes
os.remove(inv_file) os.remove(inv_file)
if config['debug']: if config.get('debug'):
print(f"[+] Removed temporary inventory file: {inv_file}") print(f"[+] Removed temporary inventory file: {inv_file}")
# If this is a new key and deployment failed, remove it
if not success and ssh_key and ssh_key.startswith(os.path.expanduser("~/.ssh/c2ingred_")):
try:
os.remove(ssh_key)
if os.path.exists(f"{ssh_key}.pub"):
os.remove(f"{ssh_key}.pub")
print(f"[+] Removed generated SSH key due to deployment failure")
except Exception as key_err:
print(f"[!] Failed to remove SSH key {ssh_key}: {key_err}")
def prepare_flokinet_vars(config): def prepare_flokinet_vars(config):
"""Prepare FlokiNET vars.yaml with dynamic values""" """Prepare FlokiNET vars.yaml with dynamic values"""
vars_file = 'FlokiNET/vars.yaml' vars_file = 'FlokiNET/vars.yaml'
@@ -738,9 +916,9 @@ def ensure_provider_files_exist(config):
# Basic file existence checks # Basic file existence checks
required_files = { required_files = {
'aws': ['redirector.yml', 'c2.yml'], 'AWS': ['redirector.yml', 'c2.yml'],
'linode': ['redirector.yml', 'c2.yml'], 'Linode': ['redirector.yml', 'c2.yml'],
'flokinet': ['provision.yml', 'redirector.yml', 'c2.yml'] 'FlokiNET': ['provision.yml', 'redirector.yml', 'c2.yml']
} }
# Check for shared files directory # Check for shared files directory
@@ -759,7 +937,7 @@ def ensure_provider_files_exist(config):
missing_files = [] missing_files = []
# Check provider-specific files # Check provider-specific files
for file in required_files.get(provider.lower(), []): for file in required_files.get(provider, []):
full_path = f"{provider}/{file}" full_path = f"{provider}/{file}"
if not os.path.exists(full_path): if not os.path.exists(full_path):
missing_files.append(full_path) missing_files.append(full_path)
@@ -778,13 +956,13 @@ def ensure_provider_files_exist(config):
return True return True
def generate_ssh_key(instance_label=None): def generate_ssh_key():
"""Generate a new SSH key with randomized name for better OPSEC""" """Generate a new SSH key with randomized name for better OPSEC"""
# Create a unique key name based on instance label or random string # Create a unique key name with random string
if not instance_label: rand_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
instance_label = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8)) timestamp = int(time.time()) % 10000 # Last 4 digits of timestamp
key_name = f"c2ingred_{instance_label}_{int(time.time())}" key_name = f"c2ingred_{rand_suffix}_{timestamp}"
key_path = os.path.expanduser(f"~/.ssh/{key_name}") key_path = os.path.expanduser(f"~/.ssh/{key_name}")
print(f"[+] Generating new SSH key: {key_path}") print(f"[+] Generating new SSH key: {key_path}")
@@ -793,11 +971,52 @@ def generate_ssh_key(instance_label=None):
# Set proper permissions # Set proper permissions
os.chmod(key_path, 0o600) os.chmod(key_path, 0o600)
os.chmod(f"{key_path}.pub", 0o644) os.chmod(f"{key_path}.pub", 0o644)
# Log the key info for reference
print(f"[+] SSH private key: {key_path}")
print(f"[+] SSH public key: {key_path}.pub")
return key_path return key_path
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"[-] Error: Failed to generate SSH key: {e}") print(f"[-] Error: Failed to generate SSH key: {e}")
return None return None
def prepare_ssh_key(config):
"""Prepare and validate SSH key, generating a new one if needed"""
# Check if SSH key is provided
ssh_key = config.get('ssh_key')
# If key not provided or doesn't exist, generate a new one
if not ssh_key or not os.path.exists(ssh_key):
if ssh_key and not os.path.exists(ssh_key):
print(f"[-] Warning: Specified SSH key {ssh_key} not found")
ssh_key = generate_ssh_key()
if not ssh_key:
return None, None
config['ssh_key'] = ssh_key
# Ensure the public key exists
ssh_public_key = f"{ssh_key}.pub"
if not os.path.exists(ssh_public_key):
print(f"[-] Warning: SSH public key not found at {ssh_public_key}, generating new key pair")
ssh_key = generate_ssh_key()
if not ssh_key:
return None, None
config['ssh_key'] = ssh_key
ssh_public_key = f"{ssh_key}.pub"
# Read the public key content
try:
with open(ssh_public_key, 'r') as f:
ssh_key_content = f.read().strip()
return ssh_key, ssh_key_content
except Exception as e:
print(f"[-] Error reading SSH public key: {e}")
return None, None
def interactive_config(): def interactive_config():
"""Interactively collect configuration""" """Interactively collect configuration"""
config = {} config = {}
@@ -1082,14 +1301,14 @@ def cleanup_sensitive_files(config):
return True return True
def save_config(config): def save_config(config):
"""Save configuration to config.yml file""" """Save configuration to config.yml file and create detailed deployment log"""
# Make a copy to avoid modifying the original # Make a copy to avoid modifying the original
config_to_save = config.copy() config_to_save = config.copy()
# Remove ALL sensitive data before saving # Remove ALL sensitive data before saving
sensitive_keys = [ sensitive_keys = [
'aws_key', 'aws_secret', 'linode_token', 'aws_key', 'aws_secret', 'linode_token',
'smtp_auth_pass', 'ssh_key', 'ssh_key_path', 'smtp_auth_pass', 'ssh_key_content',
'flokinet_redirector_ip', 'flokinet_c2_ip', 'flokinet_redirector_ip', 'flokinet_c2_ip',
'c2_ip', 'redirector_ip' 'c2_ip', 'redirector_ip'
] ]
@@ -1106,7 +1325,8 @@ def save_config(config):
# Use a more secure way to write sensitive files # Use a more secure way to write sensitive files
# Ensure the file doesn't exist first to prevent race conditions # Ensure the file doesn't exist first to prevent race conditions
if os.path.exists('config.yml'): if os.path.exists('config.yml'):
os.remove('config.yml') # Instead of removing, just open in write mode to overwrite
pass
# Create with restricted permissions # Create with restricted permissions
with open('config.yml', 'w') as f: with open('config.yml', 'w') as f:
@@ -1128,18 +1348,27 @@ def save_config(config):
# Log general info # Log general info
f.write(f"Provider: {config['provider']}\n") f.write(f"Provider: {config['provider']}\n")
# Log SSH key information
f.write(f"SSH Key: {config.get('ssh_key', 'Not specified')}\n")
if 'ssh_key_name' in config:
f.write(f"SSH Key Name: {config['ssh_key_name']}\n")
# Log custom SSH port if used
if config.get('ssh_port') and config.get('ssh_port') != 22:
f.write(f"SSH Port: {config['ssh_port']}\n")
# Log provider-specific info without exposing sensitive details # Log provider-specific info without exposing sensitive details
if config['provider'] == 'aws': if config['provider'] == 'AWS':
f.write(f"AWS Region: {config.get('aws_region', 'random')}\n") f.write(f"AWS Region: {config.get('aws_region', 'random')}\n")
f.write(f"Instance Type: {config.get('size', 'default')}\n") f.write(f"Instance Type: {config.get('size', 'default')}\n")
f.write(f"Redirector Name: {config['redirector_name']}\n") f.write(f"Redirector Name: {config.get('redirector_name', 'Not specified')}\n")
f.write(f"C2 Name: {config['c2_name']}\n") f.write(f"C2 Name: {config.get('c2_name', 'Not specified')}\n")
elif config['provider'] == 'linode': elif config['provider'] == 'Linode':
f.write(f"Linode Region: {config.get('linode_region', 'random')}\n") f.write(f"Linode Region: {config.get('linode_region', 'random')}\n")
f.write(f"Plan: {config.get('size', 'default')}\n") f.write(f"Plan: {config.get('plan', 'default')}\n")
f.write(f"Redirector Name: {config['redirector_name']}\n") f.write(f"Redirector Name: {config.get('redirector_name', 'Not specified')}\n")
f.write(f"C2 Name: {config['c2_name']}\n") f.write(f"C2 Name: {config.get('c2_name', 'Not specified')}\n")
elif config['provider'] == 'flokinet': elif config['provider'] == 'FlokiNET':
# IPs are sensitive for FlokiNET, only log region # IPs are sensitive for FlokiNET, only log region
f.write(f"FlokiNET Region: {config.get('region', 'anonymous')}\n") f.write(f"FlokiNET Region: {config.get('region', 'anonymous')}\n")
# Note that we're not logging IPs directly # Note that we're not logging IPs directly
@@ -1152,6 +1381,14 @@ def save_config(config):
if not config.get('redirector_only'): if not config.get('redirector_only'):
f.write(f"C2 Domain: mail.{config['domain']}\n") f.write(f"C2 Domain: mail.{config['domain']}\n")
# Log gophish admin port
if config.get('gophish_admin_port'):
f.write(f"GoPhish Admin Port: {config['gophish_admin_port']}\n")
# Log SMTP auth user
if config.get('smtp_auth_user'):
f.write(f"SMTP Auth User: {config['smtp_auth_user']}\n")
# Log OPSEC settings # Log OPSEC settings
f.write(f"\nOPSEC Settings:\n") f.write(f"\nOPSEC Settings:\n")
f.write(f"- Zero-logs: {'Enabled' if config.get('zero_logs') else 'Disabled'}\n") f.write(f"- Zero-logs: {'Enabled' if config.get('zero_logs') else 'Disabled'}\n")
@@ -1175,6 +1412,12 @@ def main():
else: else:
config = load_config(args) config = load_config(args)
# Ensure provider has proper case
if config['provider'] and config['provider'].lower() in ['aws', 'linode', 'flokinet']:
# Convert to proper case if necessary
provider_map = {'aws': 'AWS', 'linode': 'Linode', 'flokinet': 'FlokiNET'}
config['provider'] = provider_map.get(config['provider'].lower(), config['provider'])
# Validate configuration # Validate configuration
if not validate_config(config): if not validate_config(config):
return return
@@ -1192,29 +1435,30 @@ def main():
# Save configuration # Save configuration
save_config(config) save_config(config)
# Deploy infrastructure based on provider
success = False success = False
try: try:
# Deploy infrastructure based on provider if config['provider'] == 'AWS':
if config['provider'] == 'aws':
success = deploy_aws(config) success = deploy_aws(config)
elif config['provider'] == 'linode': elif config['provider'] == 'Linode':
success = deploy_linode(config) success = deploy_linode(config)
elif config['provider'] == 'flokinet': elif config['provider'] == 'FlokiNET':
success = deploy_flokinet(config) success = deploy_flokinet(config)
else: else:
print(f"[-] Error: Unsupported provider: {config['provider']}") print(f"[-] Error: Unsupported provider: {config['provider']}")
return return
finally: except Exception as e:
# OPSEC: Always offer to clean up sensitive files, regardless of success print(f"[-] Unexpected error during deployment: {e}")
if not config.get('debug'): # Skip cleanup in debug mode return
cleanup_sensitive_files(config)
# Only offer cleanup if deployment was successful
if success: if success:
print("\n[+] Deployment completed successfully!") print("\n[+] Deployment completed successfully!")
if config['provider'] != 'flokinet':
if config['provider'] != 'FlokiNET':
print("\n[+] Your infrastructure is now ready to use!") print("\n[+] Your infrastructure is now ready to use!")
print(f"[+] Redirector: {config['redirector_name']}") print(f"[+] Redirector: {config.get('redirector_name', 'redirector')}")
print(f"[+] C2 Server: {config['c2_name']}") print(f"[+] C2 Server: {config.get('c2_name', 'c2')}")
else: else:
print("\n[+] Your FlokiNET infrastructure is now configured with:") print("\n[+] Your FlokiNET infrastructure is now configured with:")
print(f"[+] Zero-logs configuration") print(f"[+] Zero-logs configuration")
@@ -1222,7 +1466,7 @@ def main():
print(f"[+] Sliver C2 framework") print(f"[+] Sliver C2 framework")
print(f"[+] Anti-forensics capabilities") print(f"[+] Anti-forensics capabilities")
print("\n[+] To use the automated shell handler, configure your Rubber Ducky with:") print("\n[+] To use the automated shell handler, configure your Rubber Ducky with:")
print(f"[+] Redirector IP: {config['flokinet_redirector_ip']}") print(f"[+] Redirector IP: {config.get('flokinet_redirector_ip', 'IP_NOT_PROVIDED')}")
if config.get('domain'): if config.get('domain'):
print(f"[+] or Domain: cdn.{config['domain']}") print(f"[+] or Domain: cdn.{config['domain']}")
print(f"[+] Port: {config.get('shell_handler_port', 4444)} (shell handler port)") print(f"[+] Port: {config.get('shell_handler_port', 4444)} (shell handler port)")
@@ -1231,16 +1475,20 @@ def main():
if config.get('domain'): if config.get('domain'):
print("\n[!] Don't forget to configure your DNS records:") print("\n[!] Don't forget to configure your DNS records:")
if not config.get('c2_only'): if not config.get('c2_only'):
print(f"[!] - Add A record for cdn.{config['domain']} pointing to {config['flokinet_redirector_ip']}") print(f"[!] - Add A record for cdn.{config['domain']} pointing to {config.get('flokinet_redirector_ip', 'IP_NOT_PROVIDED')}")
if not config.get('redirector_only'): if not config.get('redirector_only'):
print(f"[!] - Add A record for mail.{config['domain']} pointing to {config['flokinet_c2_ip']}") print(f"[!] - Add A record for mail.{config['domain']} pointing to {config.get('flokinet_c2_ip', 'IP_NOT_PROVIDED')}")
else:
print("\n[-] Deployment failed.")
# Display non-standard SSH port warning if applicable # Display non-standard SSH port warning if applicable
if success and config.get('ssh_port') != 22: if config.get('ssh_port') != 22:
print(f"\n[!] IMPORTANT: Non-standard SSH port {config.get('ssh_port')} is being used") print(f"\n[!] IMPORTANT: Non-standard SSH port {config.get('ssh_port')} is being used")
print(f"[!] Future SSH connections will require: ssh -p {config.get('ssh_port')} user@host") print(f"[!] Future SSH connections will require: ssh -p {config.get('ssh_port')} user@host")
# OPSEC: Clean up sensitive files after successful deployment
if not config.get('debug'): # Skip cleanup in debug mode
cleanup_sensitive_files(config)
else:
print("\n[-] Deployment failed.")
if __name__ == "__main__": if __name__ == "__main__":
main() main()