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.
This commit is contained in:
kbe
2026-07-22 02:31:32 +02:00
parent 286bb9cab1
commit eda467bc46
4 changed files with 14 additions and 39 deletions
+3 -2
View File
@@ -37,10 +37,11 @@ public sealed class ExternalReader : MemoryBase
public ExternalReader(System.Diagnostics.Process process, ProcessAccess desiredAccess = DefaultAccess) public ExternalReader(System.Diagnostics.Process process, ProcessAccess desiredAccess = DefaultAccess)
{ {
_processId = process.Id; _processId = process.Id;
if ((desiredAccess & ProcessAccess.QueryInformation) == 0) const ProcessAccess queryAccess = ProcessAccess.QueryInformation | ProcessAccess.QueryLimitedInformation;
if ((desiredAccess & queryAccess) == 0)
{ {
throw new ArgumentException( throw new ArgumentException(
"ExternalReader requires ProcessAccess.QueryInformation to determine target bitness.", "ExternalReader requires ProcessAccess.QueryInformation or ProcessAccess.QueryLimitedInformation to determine target bitness.",
nameof(desiredAccess)); nameof(desiredAccess));
} }
+9 -22
View File
@@ -215,7 +215,7 @@ public sealed class DllInjector
{ {
IntPtr result; IntPtr result;
if (Environment.Is64BitProcess) if (_currentIs64Bit)
{ {
var originalContext = new Context64 { ContextFlags = ContextFlags.Amd64Full }; var originalContext = new Context64 { ContextFlags = ContextFlags.Amd64Full };
if (!NativeMethods.GetThreadContext(thread, ref originalContext)) if (!NativeMethods.GetThreadContext(thread, ref originalContext))
@@ -264,32 +264,23 @@ public sealed class DllInjector
} }
else else
{ {
// 32-bit process on either a 32-bit or 64-bit (WOW64) host. // 32-bit process targeting a 32-bit process. The target context is
bool useWow64 = Environment.Is64BitOperatingSystem; // a native x86 CONTEXT; the WOW64 APIs are for 64-bit callers only.
var originalContext = new Context32 { ContextFlags = ContextFlags.X86Full }; var originalContext = new Context32 { ContextFlags = ContextFlags.X86Full };
bool gotContext = useWow64 if (!NativeMethods.GetThreadContext(thread, ref originalContext))
? NativeMethods.Wow64GetThreadContext(thread, ref originalContext)
: NativeMethods.GetThreadContext(thread, ref originalContext);
if (!gotContext)
{ {
int error = Marshal.GetLastPInvokeError(); int error = Marshal.GetLastPInvokeError();
string api = useWow64 ? "Wow64GetThreadContext" : "GetThreadContext"; throw new InvalidOperationException($"GetThreadContext failed (error {error}).");
throw new InvalidOperationException($"{api} failed (error {error}).");
} }
var redirectContext = originalContext; var redirectContext = originalContext;
redirectContext.Eip = (uint)(nint)remoteBase; redirectContext.Eip = (uint)(nint)remoteBase;
redirectContext.Esp = (uint)(nint)stackTop; redirectContext.Esp = (uint)(nint)stackTop;
bool setContext = useWow64 if (!NativeMethods.SetThreadContext(thread, ref redirectContext))
? NativeMethods.Wow64SetThreadContext(thread, ref redirectContext)
: NativeMethods.SetThreadContext(thread, ref redirectContext);
if (!setContext)
{ {
int error = Marshal.GetLastPInvokeError(); int error = Marshal.GetLastPInvokeError();
string api = useWow64 ? "Wow64SetThreadContext" : "SetThreadContext"; throw new InvalidOperationException($"SetThreadContext failed (error {error}).");
throw new InvalidOperationException($"{api} failed (error {error}).");
} }
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF) if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
@@ -306,14 +297,10 @@ public sealed class DllInjector
throw new InvalidOperationException($"SuspendThread failed while capturing result (error {error})."); throw new InvalidOperationException($"SuspendThread failed while capturing result (error {error}).");
} }
bool restoredContext = useWow64 if (!NativeMethods.SetThreadContext(thread, ref originalContext))
? NativeMethods.Wow64SetThreadContext(thread, ref originalContext)
: NativeMethods.SetThreadContext(thread, ref originalContext);
if (!restoredContext)
{ {
int error = Marshal.GetLastPInvokeError(); int error = Marshal.GetLastPInvokeError();
string api = useWow64 ? "Wow64SetThreadContext" : "SetThreadContext"; throw new InvalidOperationException($"SetThreadContext restore failed (error {error}).");
throw new InvalidOperationException($"{api} restore failed (error {error}).");
} }
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF) if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
-13
View File
@@ -152,19 +152,6 @@ internal static partial class NativeMethods
SafeMemoryHandle thread, SafeMemoryHandle thread,
ref Context32 context); ref Context32 context);
/// <summary>Sets a 32-bit (WOW64) thread context.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool Wow64SetThreadContext(
SafeMemoryHandle thread,
ref Context32 context);
/// <summary>Gets a 32-bit (WOW64) thread context.</summary>
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool Wow64GetThreadContext(
SafeMemoryHandle thread,
ref Context32 context);
// ── Modules ────────────────────────────────────────────────────────────── // ── Modules ──────────────────────────────────────────────────────────────
+2 -2
View File
@@ -29,8 +29,8 @@ public unsafe struct FloatingSaveArea32
} }
/// <summary> /// <summary>
/// A 32-bit (x86/WOW64) thread context. Use it with /// A 32-bit x86 thread context. Use it with <c>GetThreadContext</c> and
/// <c>Wow64GetThreadContext</c> and <c>Wow64SetThreadContext</c> to inspect a 32-bit thread. /// <c>SetThreadContext</c> from a 32-bit process targeting a 32-bit thread.
/// The total size is 716 bytes. /// The total size is 716 bytes.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]