diff --git a/WhiteMagic/Execution/RemoteThreadExecutor.cs b/WhiteMagic/Execution/RemoteThreadExecutor.cs index d43c933..b584c1a 100644 --- a/WhiteMagic/Execution/RemoteThreadExecutor.cs +++ b/WhiteMagic/Execution/RemoteThreadExecutor.cs @@ -41,7 +41,7 @@ public sealed class RemoteThreadExecutor /// When this delegate returns a non-zero pointer, the executor does not take /// ownership of that memory and will not free it. /// - internal Func? StubAllocator { get; set; } + internal Func? StubAllocator { get; set; } /// Test seam: overrides remote scratch allocation for string/struct args. /// Defaults to . diff --git a/WhiteMagic/Hooking/PrologueDecoder.cs b/WhiteMagic/Hooking/PrologueDecoder.cs index 5c70e84..92fd661 100644 --- a/WhiteMagic/Hooking/PrologueDecoder.cs +++ b/WhiteMagic/Hooking/PrologueDecoder.cs @@ -41,8 +41,8 @@ internal static class PrologueDecoder byte op = bytes[i]; - // push reg / push rbp. - if ((op & 0xF8) == 0x50 || op == 0x55) + // push reg (0x50-0x57), including rbp (0x55). + if ((op & 0xF8) == 0x50) return i + 1; // mov r32/64, r/m32/64. Recognize only the specific forms listed above. diff --git a/WhiteMagic/Injection/DllInjector.cs b/WhiteMagic/Injection/DllInjector.cs index ae26145..2d9f199 100644 --- a/WhiteMagic/Injection/DllInjector.cs +++ b/WhiteMagic/Injection/DllInjector.cs @@ -209,6 +209,8 @@ public sealed class DllInjector throw new InvalidOperationException($"SuspendThread failed (error {error})."); } + bool restored = false; + try { IntPtr result; @@ -257,6 +259,8 @@ public sealed class DllInjector int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"ResumeThread restore failed (error {error})."); } + + restored = true; } else { @@ -302,6 +306,8 @@ public sealed class DllInjector int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"ResumeThread restore failed (error {error})."); } + + restored = true; } if (result == IntPtr.Zero) @@ -311,8 +317,15 @@ public sealed class DllInjector } catch { - // Best effort: resume the thread if we left it suspended. - _ = NativeMethods.ResumeThread(thread); + // If we never successfully restored the thread's original context, the + // thread may still be executing (or about to execute) code inside the + // injected allocation. Freeing that memory now would crash the target + // process, so leak the block and leave the thread suspended. + if (!restored) + { + remoteBase = IntPtr.Zero; + } + throw; } } diff --git a/WhiteMagic/MemoryBase.cs b/WhiteMagic/MemoryBase.cs index 9e59822..f7c864f 100644 --- a/WhiteMagic/MemoryBase.cs +++ b/WhiteMagic/MemoryBase.cs @@ -211,8 +211,8 @@ public abstract class MemoryBase : IDisposable // Search the newly extended buffer at code-unit-aligned positions. A terminator // can start as far back as (nullLen - 1) bytes before the new bytes, so start // the search just before the previous end, rounded up to the next code-unit. - int firstAligned = ((previousLen - nullLen + 1 + nullLen - 1) / nullLen) * nullLen; - firstAligned = Math.Max(0, firstAligned); + int firstAligned = previousLen - (previousLen % nullLen); + if (firstAligned < 0) firstAligned = 0; int limit = accumulated.Count - nullLen; for (int i = firstAligned; i <= limit; i += nullLen)