src/PwX.BuildHost.PS51/Stub.cs
|
using System;
using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.InteropServices; #if PWX_ENCRYPTED_PAYLOAD using System.Security.Cryptography; #endif #if PWX_INPROCESS using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Threading; #endif using System.Text; namespace PwX.Stub { internal static class Program { #if PWX_ENCRYPTED_PAYLOAD private const string PayloadResource = "PwX.payload.enc"; #else private const string PayloadResource = "PwX.payload.ps1"; #endif [DllImport("kernel32.dll")] private static extern uint GetConsoleOutputCP(); [DllImport("kernel32.dll")] private static extern bool SetConsoleOutputCP(uint cp); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type); private static int Main(string[] args) { string workDir = null; uint oldCp = 0; try { #if PWX_UTF8_OUTPUT && !PWX_GUI try { oldCp = GetConsoleOutputCP(); if (oldCp != 0) SetConsoleOutputCP(65001); Console.OutputEncoding = Encoding.UTF8; } catch { } #endif #if !PWX_INPROCESS string psExe = Path.Combine(Environment.SystemDirectory, @"WindowsPowerShell\v1.0\powershell.exe"); if (!File.Exists(psExe)) { Fatal("Windows PowerShell 5.1 (powershell.exe) bulunamadi: " + psExe); return 1; } #endif workDir = Path.Combine(Path.GetTempPath(), "pwx-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(workDir); string scriptPath = Path.Combine(workDir, "payload.ps1"); Assembly asm = Assembly.GetExecutingAssembly(); using (Stream s = asm.GetManifestResourceStream(PayloadResource)) { if (s == null) { Fatal("Gomulu betik bulunamadi: " + PayloadResource); return 1; } #if PWX_ENCRYPTED_PAYLOAD byte[] raw; using (MemoryStream ms = new MemoryStream()) { s.CopyTo(ms); raw = ms.ToArray(); } byte[] decrypted = DecryptPayload(raw); File.WriteAllBytes(scriptPath, decrypted); #else using (FileStream fs = new FileStream(scriptPath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { s.CopyTo(fs); } #endif } #if PWX_INPROCESS string scriptText = File.ReadAllText(scriptPath, Encoding.UTF8); InitialSessionState iss = InitialSessionState.CreateDefault(); var host = new PwxInProcessHost(); using (Runspace rs = RunspaceFactory.CreateRunspace(host, iss)) { #if PWX_GUI rs.ApartmentState = ApartmentState.STA; #endif rs.Open(); using (PowerShell ps = PowerShell.Create()) { ps.Runspace = rs; ps.AddScript(scriptText); ScriptArguments.Apply(ps, scriptText, args); #if !PWX_NO_OUTPUT Collection<PSObject> results = ps.Invoke(); #if !PWX_GUI foreach (var item in results) { if (item != null) Console.WriteLine(item.ToString()); } #endif #else ps.Invoke(); #endif if (host.ShouldExit) return host.ExitCode; if (ps.HadErrors && ps.Streams.Error.Count > 0) { #if !PWX_NO_OUTPUT && !PWX_GUI foreach (var err in ps.Streams.Error) { if (err != null) Console.Error.WriteLine(err.ToString()); } #endif return 1; } return 0; } } #else StringBuilder sb = new StringBuilder(); sb.Append("-NoProfile -ExecutionPolicy Bypass -STA "); #if PWX_GUI sb.Append("-WindowStyle Hidden "); #endif sb.Append("-File ").Append(Quote(scriptPath)); foreach (string a in args) sb.Append(' ').Append(Quote(a)); ProcessStartInfo psi = new ProcessStartInfo(psExe, sb.ToString()); psi.UseShellExecute = false; #if PWX_GUI psi.CreateNoWindow = true; psi.WindowStyle = ProcessWindowStyle.Hidden; #else #if PWX_NO_OUTPUT psi.RedirectStandardOutput = true; #endif #endif using (Process p = Process.Start(psi)) { #if PWX_NO_OUTPUT if (psi.RedirectStandardOutput) p.StandardOutput.ReadToEnd(); #endif p.WaitForExit(); return p.ExitCode; } #endif } catch (Exception ex) { Fatal("PwX baslatici hatasi: " + ex.Message); return 1; } finally { #if PWX_UTF8_OUTPUT try { if (oldCp != 0) SetConsoleOutputCP(oldCp); } catch { } #endif try { if (workDir != null && Directory.Exists(workDir)) Directory.Delete(workDir, true); } catch { } } } // CommandLineToArgvW kurallarina uygun guvenli tirnaklama (kullanici girdisi shell komutuna eklenmez) private static string Quote(string arg) { if (arg.Length > 0 && arg.IndexOfAny(new char[] { ' ', '\t', '\n', '\v', '"' }) < 0) return arg; StringBuilder sb = new StringBuilder("\""); int backslashes = 0; foreach (char c in arg) { if (c == '\\') { backslashes++; } else if (c == '"') { sb.Append('\\', backslashes * 2 + 1); sb.Append('"'); backslashes = 0; } else { sb.Append('\\', backslashes); backslashes = 0; sb.Append(c); } } sb.Append('\\', backslashes * 2); sb.Append('"'); return sb.ToString(); } private static void Fatal(string message) { #if PWX_GUI try { MessageBoxW(IntPtr.Zero, message, "PwX", 0x10); } catch { } #else Console.Error.WriteLine(message); #endif } #if PWX_ENCRYPTED_PAYLOAD private static byte[] DecryptPayload(byte[] pkg) { if (pkg == null || pkg.Length < 8 + 1 + 16 + 2 + 32 + 4) throw new CryptographicException("Gecersiz sifreli paket boyutu."); // 1. Magic check: "PWXENC01" byte[] magic = Encoding.ASCII.GetBytes("PWXENC01"); for (int i = 0; i < magic.Length; i++) { if (pkg[i] != magic[i]) throw new CryptographicException("Gecersiz sifreli paket basligi (magic header mismatch)."); } int offset = 8; byte flag = pkg[offset++]; // 0 = standard, 1 = DPAPI bound byte[] iv = new byte[16]; Buffer.BlockCopy(pkg, offset, iv, 0, 16); offset += 16; ushort keyLen = BitConverter.ToUInt16(pkg, offset); offset += 2; if (offset + keyLen + 32 + 4 > pkg.Length) throw new CryptographicException("Bozuk paket: key uzunlugu siniri asti."); byte[] keyBlob = new byte[keyLen]; Buffer.BlockCopy(pkg, offset, keyBlob, 0, keyLen); offset += keyLen; byte[] tag = new byte[32]; Buffer.BlockCopy(pkg, offset, tag, 0, 32); offset += 32; int cipherLen = BitConverter.ToInt32(pkg, offset); offset += 4; if (cipherLen < 0 || offset + cipherLen != pkg.Length) throw new CryptographicException("Bozuk paket: cipher uzunlugu uyusmuyor."); byte[] cipherBytes = new byte[cipherLen]; Buffer.BlockCopy(pkg, offset, cipherBytes, 0, cipherLen); // 2. Unprotect Key if machine-bound (DPAPI) byte[] rawKey; if (flag == 1) { byte[] entropy = Encoding.UTF8.GetBytes("PwX-Machine-Binding-Entropy-v1.8.0"); try { rawKey = ProtectedData.Unprotect(keyBlob, entropy, DataProtectionScope.CurrentUser); } catch (Exception ex) { throw new CryptographicException("Bu calistirilabilir dosya farkli bir makineye veya kullaniciya kilitlenmistir (DPAPI kilidi acilamadi): " + ex.Message); } } else { rawKey = keyBlob; } // 3. Verify HMAC-SHA256 (Constant-time comparison) using (HMACSHA256 hmac = new HMACSHA256(rawKey)) { using (MemoryStream msHmac = new MemoryStream()) { msHmac.WriteByte(flag); msHmac.Write(iv, 0, iv.Length); msHmac.Write(keyBlob, 0, keyBlob.Length); msHmac.Write(cipherBytes, 0, cipherBytes.Length); byte[] computedTag = hmac.ComputeHash(msHmac.ToArray()); if (!FixedTimeEquals(computedTag, tag)) { throw new CryptographicException("Sifreli betik veri butunlugu dogrulanamadi (HMAC-SHA256 imzasi gecersiz; dosya degistirilmis olabilir)."); } } } // 4. AES-256-CBC Decrypt using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider()) { aes.KeySize = 256; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; aes.Key = rawKey; aes.IV = iv; using (ICryptoTransform decryptor = aes.CreateDecryptor()) { return decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); } } } private static bool FixedTimeEquals(byte[] a, byte[] b) { if (a == null || b == null || a.Length != b.Length) return false; int diff = 0; for (int i = 0; i < a.Length; i++) { diff |= (a[i] ^ b[i]); } return diff == 0; } #endif } #if PWX_INPROCESS internal sealed class PwxInProcessHost : PSHost { private readonly PwxInProcessUI _ui = new PwxInProcessUI(); private readonly Guid _instanceId = Guid.NewGuid(); internal bool ShouldExit { get; private set; } internal int ExitCode { get; private set; } public override string Name { get { return "PwX InProcess Host"; } } public override Version Version { get { return typeof(PowerShell).Assembly.GetName().Version; } } public override Guid InstanceId { get { return _instanceId; } } public override PSHostUserInterface UI { get { return _ui; } } public override CultureInfo CurrentCulture { get { return Thread.CurrentThread.CurrentCulture; } } public override CultureInfo CurrentUICulture { get { return Thread.CurrentThread.CurrentUICulture; } } public override void SetShouldExit(int exitCode) { ExitCode = exitCode; ShouldExit = true; } public override void EnterNestedPrompt() { } public override void ExitNestedPrompt() { } public override void NotifyBeginApplication() { } public override void NotifyEndApplication() { } } internal sealed class PwxInProcessUI : PSHostUserInterface { public override PSHostRawUserInterface RawUI { get { return null; } } public override string ReadLine() { return Console.ReadLine(); } public override System.Security.SecureString ReadLineAsSecureString() { return null; } public override void Write(string value) { Console.Write(value); } public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { Console.Write(value); } public override void WriteLine(string value) { Console.WriteLine(value); } public override void WriteErrorLine(string value) { Console.Error.WriteLine(value); } public override void WriteDebugLine(string message) { } public override void WriteProgress(long sourceId, ProgressRecord record) { } public override void WriteVerboseLine(string message) { } public override void WriteWarningLine(string message) { Console.WriteLine("[UYARI] " + message); } public override System.Collections.Generic.Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions) { return null; } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) { return null; } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) { return null; } public override int PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, int defaultChoice) { return defaultChoice; } } internal static class ScriptArguments { internal static void Apply(PowerShell ps, string scriptText, string[] args) { if (args == null || args.Length == 0) return; Dictionary<string, bool> declared = GetDeclaredParameters(scriptText); for (int i = 0; i < args.Length; i++) { string token = args[i]; if (!IsParameterToken(token)) { ps.AddArgument(token); continue; } string name = token.Substring(1); string inlineValue = null; int colon = name.IndexOf(':'); if (colon > 0) { inlineValue = name.Substring(colon + 1); name = name.Substring(0, colon); } bool isSwitch = IsSwitch(declared, name); if (isSwitch) { if (inlineValue == null) ps.AddParameter(name); else ps.AddParameter(name, ParseBool(inlineValue)); } else if (inlineValue != null) { ps.AddParameter(name, inlineValue); } else if (i + 1 < args.Length && !IsParameterToken(args[i + 1])) { ps.AddParameter(name, args[++i]); } else { ps.AddParameter(name); } } } internal static bool IsParameterToken(string s) { return s != null && s.Length > 1 && s[0] == '-' && (char.IsLetter(s[1]) || s[1] == '_'); } private static bool IsSwitch(Dictionary<string, bool> declared, string name) { bool sw; if (declared.TryGetValue(name, out sw)) return sw; var matches = declared.Where(kv => kv.Key.StartsWith(name, StringComparison.OrdinalIgnoreCase)).ToList(); return matches.Count == 1 && matches[0].Value; } private static bool ParseBool(string text) { string t = text.Trim().TrimStart('$'); if (t.Equals("true", StringComparison.OrdinalIgnoreCase) || t == "1") return true; if (t.Equals("false", StringComparison.OrdinalIgnoreCase) || t == "0") return false; throw new ArgumentException("Gecersiz [switch] degeri: " + text); } private static Dictionary<string, bool> GetDeclaredParameters(string scriptText) { var map = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); Token[] tokens; ParseError[] errors; ScriptBlockAst ast = Parser.ParseInput(scriptText, out tokens, out errors); if (ast == null || ast.ParamBlock == null) return map; foreach (ParameterAst p in ast.ParamBlock.Parameters) { bool isSwitch = p.Attributes.OfType<TypeConstraintAst>().Any(tc => { string n = tc.TypeName.Name; return n.Equals("switch", StringComparison.OrdinalIgnoreCase) || n.EndsWith("SwitchParameter", StringComparison.OrdinalIgnoreCase); }); map[p.Name.VariablePath.UserPath] = isSwitch; } return map; } } #endif } |