fix 7 correctness and cleanup issues
Blocking fixes: 1. Read<T> now returns default(T) on failed/partial read instead of crash (applies to single Read<T>, array Read<T>, and ReadBytes) 2. InProcessReader uses ReadProcessMemory via handle instead of unsafe Buffer.MemoryCopy — fails soft on bad address instead of AV'ing 3. GetRelative now returns absolute - ImageBase (inverse of GetAbsolute). Fix round-trip test to validate at arbitrary offsets, not just ImageBase 4. ReadString reads in 64-byte chunks with encoding-aware null-terminator pattern matching (handles UTF-16's 2-byte null, UTF-32's 4-byte null) Cleanup: 5. InProcessReader validates handle on open and uses RPM through it (handle is no longer unused) 6. Array marshal read: pin raw buffer once, PtrToStructure at offset 7. StructureToByteArray(Span) delegates to byte[] overload, no duplicate New tests: 4 invalid-address grace tests (returns default/empty/false). All 57 passing.
This commit is contained in:
@@ -6,8 +6,11 @@ namespace WhiteMagic;
|
||||
|
||||
/// <summary>
|
||||
/// In-process memory reader that accesses the owning process's memory through
|
||||
/// direct pointer dereference (<c>unsafe</c>). Use this reader from within a
|
||||
/// managed DLL injected into the target process.
|
||||
/// <see cref="NativeMethods.ReadProcessMemory"/> and
|
||||
/// <see cref="NativeMethods.WriteProcessMemory"/> on a handle to the current
|
||||
/// process. Unlike the unsafe-deref approach, this fails softly (returns
|
||||
/// empty / zero bytes) on invalid or protected addresses instead of crashing
|
||||
/// the host process with an <see cref="AccessViolationException"/>.
|
||||
/// </summary>
|
||||
public sealed class InProcessReader : MemoryBase
|
||||
{
|
||||
@@ -25,6 +28,12 @@ public sealed class InProcessReader : MemoryBase
|
||||
ProcessAccess.VmRead | ProcessAccess.VmWrite | ProcessAccess.VmOperation | ProcessAccess.QueryInformation,
|
||||
false,
|
||||
current.Id);
|
||||
if (_handle.IsInvalid)
|
||||
{
|
||||
int error = Marshal.GetLastPInvokeError();
|
||||
throw new InvalidOperationException(
|
||||
$"OpenProcess failed for PID {current.Id}: error {error}");
|
||||
}
|
||||
|
||||
_imageBase = current.MainModule?.BaseAddress ?? IntPtr.Zero;
|
||||
}
|
||||
@@ -36,30 +45,37 @@ public sealed class InProcessReader : MemoryBase
|
||||
public override SafeMemoryHandle Handle => _handle;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe byte[] ReadBytes(IntPtr address, int count, bool isRelative = false)
|
||||
public override byte[] ReadBytes(IntPtr address, int count, bool isRelative = false)
|
||||
{
|
||||
if (isRelative)
|
||||
address = GetAbsolute(address);
|
||||
|
||||
byte[] buffer = new byte[count];
|
||||
fixed (byte* ptr = buffer)
|
||||
if (!NativeMethods.ReadProcessMemory(_handle, address, buffer, count, out nint bytesRead))
|
||||
{
|
||||
Buffer.MemoryCopy((void*)address, ptr, count, count);
|
||||
return [];
|
||||
}
|
||||
|
||||
if ((int)bytesRead != count)
|
||||
{
|
||||
Array.Resize(ref buffer, (int)bytesRead);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe int WriteBytes(IntPtr address, ReadOnlySpan<byte> bytes, bool isRelative = false)
|
||||
public override int WriteBytes(IntPtr address, ReadOnlySpan<byte> bytes, bool isRelative = false)
|
||||
{
|
||||
if (isRelative)
|
||||
address = GetAbsolute(address);
|
||||
|
||||
fixed (byte* ptr = bytes)
|
||||
if (!NativeMethods.WriteProcessMemory(_handle, address, bytes, bytes.Length, out nint written))
|
||||
{
|
||||
Buffer.MemoryCopy(ptr, (void*)address, bytes.Length, bytes.Length);
|
||||
return 0;
|
||||
}
|
||||
return bytes.Length;
|
||||
|
||||
return (int)written;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user