Add memory-region query, enumeration, and scoped protection

Implements VirtualQueryEx + MEMORY_BASIC_INFORMATION wrappers, the immutable MemoryRegion record, the ProtectionScope disposable helper, and MemoryBase.QueryRegion/EnumerateRegions/ChangeProtection. Closes section 1 of add-thread-region-finder.
This commit is contained in:
kbe
2026-07-22 16:04:15 +02:00
parent e8c84f0ba1
commit f0faca3112
7 changed files with 504 additions and 0 deletions
+54
View File
@@ -1,5 +1,7 @@
using WhiteMagic.Hooking;
using WhiteMagic.Memory;
using WhiteMagic.Native;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
@@ -268,6 +270,58 @@ public abstract class MemoryBase : IDisposable
return (IntPtr)((nint)absolute - (nint)ImageBase);
}
// ── Memory region query ────────────────────────────────────────────────
/// <summary>
/// Queries the memory region that contains <paramref name="address"/> in the target
/// process using <c>VirtualQueryEx</c>.
/// </summary>
/// <returns>An immutable snapshot of the region.</returns>
/// <exception cref="InvalidOperationException">The query fails.</exception>
public MemoryRegion QueryRegion(IntPtr address)
{
nuint bufferSize = (nuint)Marshal.SizeOf<MemoryBasicInformation>();
nuint result = NativeMethods.VirtualQueryEx(Handle, address, out MemoryBasicInformation info, bufferSize);
if (result == 0)
{
int error = Marshal.GetLastPInvokeError();
throw new InvalidOperationException($"VirtualQueryEx failed for address 0x{address:X}: error {error}.");
}
return new MemoryRegion(info);
}
/// <summary>
/// Enumerates the memory regions of the target process from the lowest address upward.
/// The walk is lazy; callers can stop early without walking the entire address space.
/// </summary>
public IEnumerable<MemoryRegion> EnumerateRegions()
{
IntPtr address = IntPtr.Zero;
nuint bufferSize = (nuint)Marshal.SizeOf<MemoryBasicInformation>();
while (true)
{
nuint result = NativeMethods.VirtualQueryEx(Handle, address, out MemoryBasicInformation info, bufferSize);
if (result == 0)
yield break;
yield return new MemoryRegion(info);
address = info.BaseAddress + (nint)info.RegionSize;
}
}
/// <summary>
/// Changes the page protection on a region of memory and returns a disposable scope
/// that restores the original protection on dispose, including when an exception escapes
/// the guarded body.
/// </summary>
public ProtectionScope ChangeProtection(IntPtr address, nint size, MemoryProtectionType protection)
{
return new ProtectionScope(this, address, size, protection);
}
// ── Lifecycle ──────────────────────────────────────────────────────────
/// <inheritdoc />