Search code examples
c#pinvoke

Generic method for reading memory values with offsets


[DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory")]
public static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, 
                                          [In, Out] byte[] buffer, int size, out IntPtr lpNumberOfBytesRead);

private byte[] ReadBytes(Process process, MemoryAddress address, int bytesToRead)
{
    var value = new byte[bytesToRead];
    var baseAddress = GetBaseAddressByModuleName(process, address.Module.Value) + address.BaseAddress;
    var handle = GetProcessHandle(process);

    ReadProcessMemory(handle, (IntPtr)baseAddress, value, bytesToRead, out var bytesRead);

    foreach (var offset in address.Offsets)
    {
        ReadProcessMemory(handle,
            new IntPtr(BitConverter.ToInt32(value, 0) + offset),
            value,
            bytesToRead,
            out bytesRead);
    }

    return value;
}

private static int GetSize<T>()
{
    return Marshal.SizeOf(typeof(T));
}

This works fine, along with float, int, double and strings:

public long ReadLong(Process process, MemoryAddress address)
{
    return ReadBytes(process, address, GetSize<long>()).ConvertTo<long>();
}

Here is my problem (ConvertTo is just an extension that uses BitConverter.):

public short ReadShort(Process process, MemoryAddress address)
{
    return ReadBytes(process, address, GetSize<short>()).ConvertTo<short>();
}

public byte ReadByte(Process process, MemoryAddress address)
{
    return ReadBytes(process, address, 1)[0];
}

When those methods are used, an exception is thrown in the foreach loop of ReadBytes: System.ArgumentException: 'Destination array is not long enough to copy all the items in the collection. Check array index and length.'

I presume it's related to BitConverter.ToInt32. Using ToInt16 for short gets rid of the exception, but yields the wrong result. How to treat short and byte values with offsets correctly?


Solution

  • I see what you are doing now - you are following a series of object references to a value stored in the last object. Something like this should work for 32-bit address pointers, which is what it sounds like you are doing:

    private byte[] ReadBytes(Process process, MemoryAddress address, int bytesToRead)
    {
        var value = new byte[bytesToRead];
        var currentAddress = new byte[4];
        var baseAddress = GetBaseAddressByModuleName(process, address.Module.Value) + address.BaseAddress;
        var handle = GetProcessHandle(process);
    
        ReadProcessMemory(handle, (IntPtr)baseAddress, currentAddress, 4, out var bytesRead);
    
        foreach (var offset in address.Offsets.Take(address.Offsets.Length - 1))
        {
            ReadProcessMemory(handle,
                new IntPtr(BitConverter.ToInt32(currentAddress, 0) + offset),
                currentAddress,
                4,
                out bytesRead);
        }
    
        ReadProcessMemory(handle,
            new IntPtr(BitConverter.ToInt32(currentAddress, 0) + address.Offsets.Last()),
            value,
            bytesToRead,
            out bytesRead);
    
        return value;
    }