From eda467bc4662890a137f9c8fc4a6c9c754d86403 Mon Sep 17 00:00:00 2001 From: Kevin Bataille Date: Wed, 22 Jul 2026 02:31:32 +0200 Subject: [PATCH] Fix 32-bit thread context API selection in DllInjector Because DllInjector enforces matching host/target bitness, a 32-bit caller always handles a 32-bit target. The correct API is native GetThreadContext/SetThreadContext with Context32; the Wow64 APIs are only for 64-bit processes inspecting WOW64 targets, which never happens here. - Collapse the 32-bit path to always use GetThreadContext/SetThreadContext. - Remove the now-unused Wow64GetThreadContext/Wow64SetThreadContext declarations. - Update Context32 doc comment to describe the x86 usage. - ExternalReader access guard now also accepts QueryLimitedInformation. Tests: 207 passing, 4 skipped. --- WhiteMagic/ExternalReader.cs | 5 +++-- WhiteMagic/Injection/DllInjector.cs | 31 ++++++++------------------- WhiteMagic/Native/NativeMethods.cs | 13 ----------- WhiteMagic/Native/NativeStructures.cs | 4 ++-- 4 files changed, 14 insertions(+), 39 deletions(-) diff --git a/WhiteMagic/ExternalReader.cs b/WhiteMagic/ExternalReader.cs index afc591f..c749f29 100644 --- a/WhiteMagic/ExternalReader.cs +++ b/WhiteMagic/ExternalReader.cs @@ -37,10 +37,11 @@ public sealed class ExternalReader : MemoryBase public ExternalReader(System.Diagnostics.Process process, ProcessAccess desiredAccess = DefaultAccess) { _processId = process.Id; - if ((desiredAccess & ProcessAccess.QueryInformation) == 0) + const ProcessAccess queryAccess = ProcessAccess.QueryInformation | ProcessAccess.QueryLimitedInformation; + if ((desiredAccess & queryAccess) == 0) { throw new ArgumentException( - "ExternalReader requires ProcessAccess.QueryInformation to determine target bitness.", + "ExternalReader requires ProcessAccess.QueryInformation or ProcessAccess.QueryLimitedInformation to determine target bitness.", nameof(desiredAccess)); } diff --git a/WhiteMagic/Injection/DllInjector.cs b/WhiteMagic/Injection/DllInjector.cs index 6458b8e..6d87a26 100644 --- a/WhiteMagic/Injection/DllInjector.cs +++ b/WhiteMagic/Injection/DllInjector.cs @@ -215,7 +215,7 @@ public sealed class DllInjector { IntPtr result; - if (Environment.Is64BitProcess) + if (_currentIs64Bit) { var originalContext = new Context64 { ContextFlags = ContextFlags.Amd64Full }; if (!NativeMethods.GetThreadContext(thread, ref originalContext)) @@ -264,32 +264,23 @@ public sealed class DllInjector } else { - // 32-bit process on either a 32-bit or 64-bit (WOW64) host. - bool useWow64 = Environment.Is64BitOperatingSystem; - + // 32-bit process targeting a 32-bit process. The target context is + // a native x86 CONTEXT; the WOW64 APIs are for 64-bit callers only. var originalContext = new Context32 { ContextFlags = ContextFlags.X86Full }; - bool gotContext = useWow64 - ? NativeMethods.Wow64GetThreadContext(thread, ref originalContext) - : NativeMethods.GetThreadContext(thread, ref originalContext); - if (!gotContext) + if (!NativeMethods.GetThreadContext(thread, ref originalContext)) { int error = Marshal.GetLastPInvokeError(); - string api = useWow64 ? "Wow64GetThreadContext" : "GetThreadContext"; - throw new InvalidOperationException($"{api} failed (error {error})."); + throw new InvalidOperationException($"GetThreadContext failed (error {error})."); } var redirectContext = originalContext; redirectContext.Eip = (uint)(nint)remoteBase; redirectContext.Esp = (uint)(nint)stackTop; - bool setContext = useWow64 - ? NativeMethods.Wow64SetThreadContext(thread, ref redirectContext) - : NativeMethods.SetThreadContext(thread, ref redirectContext); - if (!setContext) + if (!NativeMethods.SetThreadContext(thread, ref redirectContext)) { int error = Marshal.GetLastPInvokeError(); - string api = useWow64 ? "Wow64SetThreadContext" : "SetThreadContext"; - throw new InvalidOperationException($"{api} failed (error {error})."); + throw new InvalidOperationException($"SetThreadContext failed (error {error})."); } if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF) @@ -306,14 +297,10 @@ public sealed class DllInjector throw new InvalidOperationException($"SuspendThread failed while capturing result (error {error})."); } - bool restoredContext = useWow64 - ? NativeMethods.Wow64SetThreadContext(thread, ref originalContext) - : NativeMethods.SetThreadContext(thread, ref originalContext); - if (!restoredContext) + if (!NativeMethods.SetThreadContext(thread, ref originalContext)) { int error = Marshal.GetLastPInvokeError(); - string api = useWow64 ? "Wow64SetThreadContext" : "SetThreadContext"; - throw new InvalidOperationException($"{api} restore failed (error {error})."); + throw new InvalidOperationException($"SetThreadContext restore failed (error {error})."); } if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF) diff --git a/WhiteMagic/Native/NativeMethods.cs b/WhiteMagic/Native/NativeMethods.cs index 8dbca1e..ab51b08 100644 --- a/WhiteMagic/Native/NativeMethods.cs +++ b/WhiteMagic/Native/NativeMethods.cs @@ -152,19 +152,6 @@ internal static partial class NativeMethods SafeMemoryHandle thread, ref Context32 context); - /// Sets a 32-bit (WOW64) thread context. - [LibraryImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool Wow64SetThreadContext( - SafeMemoryHandle thread, - ref Context32 context); - - /// Gets a 32-bit (WOW64) thread context. - [LibraryImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool Wow64GetThreadContext( - SafeMemoryHandle thread, - ref Context32 context); // ── Modules ────────────────────────────────────────────────────────────── diff --git a/WhiteMagic/Native/NativeStructures.cs b/WhiteMagic/Native/NativeStructures.cs index 5fef352..1395367 100644 --- a/WhiteMagic/Native/NativeStructures.cs +++ b/WhiteMagic/Native/NativeStructures.cs @@ -29,8 +29,8 @@ public unsafe struct FloatingSaveArea32 } /// -/// A 32-bit (x86/WOW64) thread context. Use it with -/// Wow64GetThreadContext and Wow64SetThreadContext to inspect a 32-bit thread. +/// A 32-bit x86 thread context. Use it with GetThreadContext and +/// SetThreadContext from a 32-bit process targeting a 32-bit thread. /// The total size is 716 bytes. /// [StructLayout(LayoutKind.Sequential)]