1
[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?

Johan
  • 35,120
  • 54
  • 178
  • 293
  • 1
    Can you say a bit about why you're using Marshal.SizeOf, which returns the size that an item takes up *when it is being marshalled*, and not getting the size of the object *in memory*? – Eric Lippert Jan 03 '20 at 00:24
  • I agree with Eric, you know a short is 2 bytes, why the long winded (and probably incorrect) method of trying to determine the size? This isn't old C, type sizes are fixed. – Ron Beyer Jan 03 '20 at 00:27
  • Also, are you aware that `Marshal.SizeOf(Type)` is deprecated? – Eric Lippert Jan 03 '20 at 00:27
  • The solution is rather simple - you are using byesToRead for both calls to ReadProcessMemory when it should only apply to the second call. The first one should clearly be reading a 32 bit address each time. Just use 4 or sizeof(int) for the first call. – Mike Marynowski Jan 03 '20 at 00:28
  • 1
    @RonBeyer: To clarify: I'm not saying that the choice of API is necessarily *wrong*; I'm just concerned that the code hasn't been fully thought through, particularly considering that it is apparently crashing. That said, `sizeof(long)` and `sizeof(short)` would be the more typical usages here. – Eric Lippert Jan 03 '20 at 00:28
  • @EricLippert It's a leftover from when I used something like `GetMemoryValue`. I suppose I might as well pass `sizeof(short)`? I would still get the same problem here though – Johan Jan 03 '20 at 00:29
  • 1
    Oh wait..your repeated use of the value byte array is odd. Second run through the loop will use the first value as an address for the next one. Something in your logic seems very wrong. – Mike Marynowski Jan 03 '20 at 00:30
  • @MikeMarynowski But I pass an updated `lpBaseAddress` (second argument) with current value + offset. – Johan Jan 03 '20 at 00:34
  • I agree with Mike. The code is confusing and it's not clear to me exactly what memory layout you are trying to decode here. Can you say more about that? – Eric Lippert Jan 03 '20 at 00:34
  • Why would a base address be the size of the value you want to grab? That makes no sense. – Mike Marynowski Jan 03 '20 at 00:35
  • @MikeMarynowski: It appears that the value read is an offset, but why would the offset be of a different size depending on the size of the data? That's the part that I don't understand. – Eric Lippert Jan 03 '20 at 00:37
  • Right, and he's also looping through a list of offsets and then adding them to the last value he read, which becomes the new base address for the next loop iteration's offset until he's out of offsets and gets to a final value? I don't get it, haha. – Mike Marynowski Jan 03 '20 at 00:40
  • @EricLippert I guess what I'm after is a way to handle a case like this: https://stackoverflow.com/a/47482065/598511, but with a dynamic amount of offsets and any of the data types I mentioned above – Johan Jan 03 '20 at 00:41
  • @MikeMarynowski Please see my last comment, as far as I know, that's the correct approach for fetching values with offsets. It works for all of the data types but two, so I guess it's not that far off – Johan Jan 03 '20 at 00:44
  • Okay, I see. You just have to use 4 (or sizeof(IntPtr) if it could be 32/64 bit) for the number of bytes to read until you get to the final value, at which point you would do a read with the size of the final value. – Mike Marynowski Jan 03 '20 at 00:46
  • Assume all offsets are 0, so it's simpler to understand. Then you want to follow a sequence of values of type `T`, where each value encodes the memory address of the next value, and return the final one? The question you linked seems to do that for `T = IntPtr.Size`, but it's not obvious how values smaller than that encode addresses and not-at-all obvious how larger do. – V0ldek Jan 03 '20 at 00:52
  • [MCVE] would be sooo nice... – Alexei Levenkov Jan 03 '20 at 00:52
  • @AlexeiLevenkov Not that easy without mocking `ReadProcessMemory` I'm afraid – Johan Jan 03 '20 at 01:01

1 Answers1

1

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;
}

Johan
  • 35,120
  • 54
  • 178
  • 293
Mike Marynowski
  • 3,156
  • 22
  • 32
  • Thanks Mike. Shouldn't the size of the first byte array - `value` - be affected by the 4 as well? I.e. size 4. – Johan Jan 03 '20 at 00:59
  • Sorry, fixed that up above. – Mike Marynowski Jan 03 '20 at 01:01
  • No worries, I did a small update to fetching the offsets subsets and the last one. Please have a look. Thanks a lot for your time! – Johan Jan 03 '20 at 01:04
  • No problem. `SkipLast(1)` does the same thing as your edit: skips the last item. – Mike Marynowski Jan 03 '20 at 01:07
  • Yea but I couldn't find it. Is it a third party library? – Johan Jan 03 '20 at 01:09
  • 1
    Ah sorry, it was only added in .NET Core 2.0+ and .NET Standard 2.1+, docs here: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast?view=netcore-3.1. I had a utility extension method before that which did the same thing so I'm used to having it available. – Mike Marynowski Jan 03 '20 at 01:10