diff --git a/WhiteMagic/ExternalReader.cs b/WhiteMagic/ExternalReader.cs
index 74c3785..afc591f 100644
--- a/WhiteMagic/ExternalReader.cs
+++ b/WhiteMagic/ExternalReader.cs
@@ -37,6 +37,13 @@ public sealed class ExternalReader : MemoryBase
public ExternalReader(System.Diagnostics.Process process, ProcessAccess desiredAccess = DefaultAccess)
{
_processId = process.Id;
+ if ((desiredAccess & ProcessAccess.QueryInformation) == 0)
+ {
+ throw new ArgumentException(
+ "ExternalReader requires ProcessAccess.QueryInformation to determine target bitness.",
+ nameof(desiredAccess));
+ }
+
_handle = NativeMethods.OpenProcess(desiredAccess, false, _processId);
if (_handle.IsInvalid)
{
@@ -46,11 +53,12 @@ public sealed class ExternalReader : MemoryBase
}
// Derive target bitness. A 64-bit host sees a 32-bit target as WOW64.
- // A 32-bit host can only open 32-bit targets. If the API fails, fall
- // back to the current process bitness (self-open path).
+ // A 32-bit host can only open 32-bit targets.
if (!NativeMethods.IsWow64Process(_handle, out bool wow64))
{
- wow64 = false;
+ int error = Marshal.GetLastPInvokeError();
+ throw new InvalidOperationException(
+ $"IsWow64Process failed for PID {_processId}: error {error}.");
}
_is64Bit = Environment.Is64BitProcess && !wow64;
diff --git a/WhiteMagic/Injection/DllInjector.cs b/WhiteMagic/Injection/DllInjector.cs
index 2d9f199..6458b8e 100644
--- a/WhiteMagic/Injection/DllInjector.cs
+++ b/WhiteMagic/Injection/DllInjector.cs
@@ -215,7 +215,7 @@ public sealed class DllInjector
{
IntPtr result;
- if (_currentIs64Bit)
+ if (Environment.Is64BitProcess)
{
var originalContext = new Context64 { ContextFlags = ContextFlags.Amd64Full };
if (!NativeMethods.GetThreadContext(thread, ref originalContext))
@@ -264,21 +264,32 @@ public sealed class DllInjector
}
else
{
+ // 32-bit process on either a 32-bit or 64-bit (WOW64) host.
+ bool useWow64 = Environment.Is64BitOperatingSystem;
+
var originalContext = new Context32 { ContextFlags = ContextFlags.X86Full };
- if (!NativeMethods.Wow64GetThreadContext(thread, ref originalContext))
+ bool gotContext = useWow64
+ ? NativeMethods.Wow64GetThreadContext(thread, ref originalContext)
+ : NativeMethods.GetThreadContext(thread, ref originalContext);
+ if (!gotContext)
{
int error = Marshal.GetLastPInvokeError();
- throw new InvalidOperationException($"Wow64GetThreadContext failed (error {error}).");
+ string api = useWow64 ? "Wow64GetThreadContext" : "GetThreadContext";
+ throw new InvalidOperationException($"{api} failed (error {error}).");
}
var redirectContext = originalContext;
redirectContext.Eip = (uint)(nint)remoteBase;
redirectContext.Esp = (uint)(nint)stackTop;
- if (!NativeMethods.Wow64SetThreadContext(thread, ref redirectContext))
+ bool setContext = useWow64
+ ? NativeMethods.Wow64SetThreadContext(thread, ref redirectContext)
+ : NativeMethods.SetThreadContext(thread, ref redirectContext);
+ if (!setContext)
{
int error = Marshal.GetLastPInvokeError();
- throw new InvalidOperationException($"Wow64SetThreadContext failed (error {error}).");
+ string api = useWow64 ? "Wow64SetThreadContext" : "SetThreadContext";
+ throw new InvalidOperationException($"{api} failed (error {error}).");
}
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
@@ -295,10 +306,14 @@ public sealed class DllInjector
throw new InvalidOperationException($"SuspendThread failed while capturing result (error {error}).");
}
- if (!NativeMethods.Wow64SetThreadContext(thread, ref originalContext))
+ bool restoredContext = useWow64
+ ? NativeMethods.Wow64SetThreadContext(thread, ref originalContext)
+ : NativeMethods.SetThreadContext(thread, ref originalContext);
+ if (!restoredContext)
{
int error = Marshal.GetLastPInvokeError();
- throw new InvalidOperationException($"Wow64SetThreadContext restore failed (error {error}).");
+ string api = useWow64 ? "Wow64SetThreadContext" : "SetThreadContext";
+ throw new InvalidOperationException($"{api} restore failed (error {error}).");
}
if (NativeMethods.ResumeThread(thread) == 0xFFFFFFFF)
diff --git a/WhiteMagic/Native/NativeMethods.cs b/WhiteMagic/Native/NativeMethods.cs
index 5160985..8dbca1e 100644
--- a/WhiteMagic/Native/NativeMethods.cs
+++ b/WhiteMagic/Native/NativeMethods.cs
@@ -138,6 +138,20 @@ internal static partial class NativeMethods
SafeMemoryHandle thread,
ref Context64 context);
+ /// Sets a 32-bit thread context (x86 or WOW64).
+ [LibraryImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static partial bool SetThreadContext(
+ SafeMemoryHandle thread,
+ ref Context32 context);
+
+ /// Gets a 32-bit thread context (x86 or WOW64).
+ [LibraryImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static partial bool GetThreadContext(
+ SafeMemoryHandle thread,
+ ref Context32 context);
+
/// Sets a 32-bit (WOW64) thread context.
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
diff --git a/WhiteMagicTest/MemoryHardeningTests.cs b/WhiteMagicTest/MemoryHardeningTests.cs
index e8647a7..d8c5958 100644
--- a/WhiteMagicTest/MemoryHardeningTests.cs
+++ b/WhiteMagicTest/MemoryHardeningTests.cs
@@ -151,6 +151,15 @@ public class MemoryHardeningTests
Assert.False(reader.Handle.IsInvalid);
}
+ [Fact]
+ public void ExternalReader_throws_when_query_information_access_missing()
+ {
+ var ex = Assert.Throws(() =>
+ new ExternalReader(Process.GetCurrentProcess(), ProcessAccess.VmRead));
+
+ Assert.Equal("desiredAccess", ex.ParamName);
+ }
+
///
/// A that serves bytes from an in-memory buffer and
/// caps every read to maxChunk bytes, to exercise partial-read handling.