In many projects, I needed to access another process’s memory but there are only a few examples that are not using C#’s full potential and some of them are very old. I came up with a simple way of reading and writing memory with very little code.

Keep in mind that this way only works for unmanaged data types like int or byte, to read strings from memory you need to do some more work, have a look into this class: https://github.com/Jnnshschl/AmeisenBotX/blob/master/AmeisenBotX.Memory/XMemory.cs

1
2
3
4
5
6
7
8
9
Memory mem = new();
mem.Attach(__PROCESS__)

mem.Write(new IntPtr(0x400000), 4711);

if (mem.Read(new IntPtr(0x408192), out int i))
{
    // ...
}

Needed imports and structures from ntdll.dll:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[DllImport("ntdll", SetLastError = false)]
public static extern int NtClose(IntPtr hObject);

[DllImport("ntdll", SetLastError = true)]
public static extern bool NtOpenProcess(out IntPtr processHandle, ProcessAccessFlags accessMask, ref ObjectAttributes objectAttributes, ref ClientId clientId);

[DllImport("ntdll", SetLastError = true)]
public static extern bool NtReadVirtualMemory(IntPtr processHandle, IntPtr baseAddress, void* buffer, int size, out IntPtr numberOfBytesRead);

[DllImport("ntdll", SetLastError = true)]
public static extern bool NtWriteVirtualMemory(IntPtr processHandle, IntPtr baseAddress, void* buffer, int size, out IntPtr numberOfBytesWritten);

public struct ClientId
{
    public IntPtr UniqueProcess;
    public IntPtr UniqueThread;
}

[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct ObjectAttributes
{
    public int Length;
    public IntPtr RootDirectory;
    public IntPtr ObjectName;
    public uint Attributes;
    public IntPtr SecurityDescriptor;
    public IntPtr SecurityQualityOfService;
}

Memory class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public unsafe class Memory
{
    public IntPtr ProcessHandle { get; private set; }

    public void Attach(Process process)
    {
        ObjectAttributes attr = new();
        attr.Length = sizeof(ObjectAttributes);

        ClientId clientID = new();
        clientID.UniqueProcess = new(process.Id);

        if (!NtOpenProcess(out IntPtr processHandle, ProcessAccessFlags.VirtualMemoryReadWrite, ref attr, ref clientID))
        {
            ProcessHandle = processHandle;
        }
    }

    public void Detach()
    {
        if (ProcessHandle != IntPtr.Zero)
        {
            NtClose(ProcessHandle);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool Read<T>(IntPtr address, out T value) where T : unmanaged
    {
        int size = sizeof(T);

        fixed (byte* pBuffer = stackalloc byte[size])
        {
            if (!NtReadVirtualMemory(ProcessHandle, address, pBuffer, size, out _))
            {
                value = *(T*)pBuffer;
                return true;
            }
        }

        value = default;
        return false;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool Write<T>(IntPtr address, T value) where T : unmanaged
    {
        return !NtWriteVirtualMemory(ProcessHandle, address, &value, sizeof(T), out _);
    }
}