Fix LibraryLoader crash on error during context-transfer restoration
- Track whether transferred thread's original context was successfully restored. - In the catch block, leak the remote allocation instead of freeing it if the thread was not restored; this prevents the target process from executing freed memory. - Remove redundant 'op == 0x55' check in InstructionAnalyzer (already matched by (op & 0xF8) == 0x50). - Simplify MemoryBase ReadString align-down expression to previousLen - (previousLen % nullLen). - Change StubAllocator size parameter from nint to int for clarity (internal test seam). Tests: 199 passing, 4 integration/interactive skipped.
This commit is contained in:
@@ -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.
|
||||
/// </remarks>
|
||||
internal Func<IntPtr, nint, IntPtr>? StubAllocator { get; set; }
|
||||
internal Func<IntPtr, int, IntPtr>? StubAllocator { get; set; }
|
||||
|
||||
/// <summary>Test seam: overrides remote scratch allocation for string/struct args.
|
||||
/// Defaults to <see cref="NativeMethods.VirtualAllocEx"/>.</summary>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user