#!/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}")

