Hey guys. I'm sure none of you know me as I'm new here and I've asked only one question. MMOwned seems to be a great community of like-minded individuals, and I want to give back what I've learned so far, from the forums and Googling things.
A bit of background information on this -- I'm currently developing a small program that will read various things from memory, eventually enough to create an out-of-process bot. I haven't gotten all that far, but I'm rather proud of the classes I have for reading memory.
I use two classes. First, the class Memory is where all the action takes place. Also, in a different namespace (WoWHack.Win32), I have another class, named Kernel32, containing the necessary Win32 functions, structs, enums, constants, etc. That makes this entirely contained -- you do nothing but give Memory a process ID, and then tell it what you want.
Without further ado..
Code:
using System; // Array
using System.Text; // UTF8, UTF16
using System.ComponentModel; // Win32Exception
using System.Runtime.InteropServices; // Marshal
using WoWHack.Win32; // Kernel32
namespace WoWHack.Utilities
{
/// <summary>
/// This is a very generic class for reading a process' memory, a bit similar to a generic buffer.
/// The process handle is opened in the constructor and closed in the deconstructor. This gives
/// the efficiency of not calling OpenProcess and CloseHandle each time, but gives the disadvantage
/// of needing to be constructed after the target process has been opened. However, this is a moot
/// point considering the constructor needs a process ID anyhow.
/// </summary>
public class Memory
{
/// <summary>
/// The ID of the process we're working with.
/// </summary>
private int ProcessID;
/// <summary>
/// A handle to process <b>ProcessID</b>. Created in the constructor and closed in the
/// deconstructor.
/// </summary>
private int ProcessHandle;
public Memory(int ProcessID)
{
this.ProcessID = ProcessID;
System.Diagnostics.Process.EnterDebugMode();
ProcessHandle = Kernel32.OpenProcess(Kernel32.OpenProcess_Access.VMRead | Kernel32.OpenProcess_Access.QueryInformation, false, ProcessID);
if (ProcessHandle == 0)
throw new Win32Exception("Unable to open process ID " + this.ProcessID);
}
/// <summary>
/// Called when this class is destroyed to free resources.
/// </summary>
~Memory()
{
Kernel32.CloseHandle(ProcessHandle);
}
/// <summary>
/// Reads a series of bytes, <b>length</b> long, from <b>address</b>.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <param name="length">Number of bytes to read.</param>
/// <returns></returns>
public byte[] ReadBytes(uint address, int length)
{
int returnLength;
byte[] value = new byte[length];
if (!Kernel32.ReadProcessMemory(ProcessHandle, address, out value, length, out returnLength))
throw new Win32Exception(Marshal.GetLastWin32Error());
return value;
}
/// <summary>
/// Reads an integer from <b>address</b>.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <returns></returns>
public int ReadInt(uint address)
{
int returnLength = 0;
int value = 0;
if (!Kernel32.ReadProcessMemory(ProcessHandle, address, out value, Marshal.SizeOf(value), out returnLength))
throw new Win32Exception(Marshal.GetLastWin32Error());
return value;
}
/// <summary>
/// Reads an unsigned integer from <b>address</b>.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <returns></returns>
public uint ReadUInt(uint address)
{
int returnLength = 0;
int value = 0;
if (!Kernel32.ReadProcessMemory(ProcessHandle, address, out value, Marshal.SizeOf(value), out returnLength))
throw new Win32Exception(Marshal.GetLastWin32Error());
return (uint)value;
}
/// <summary>
/// Reads a long integer from <b>address</b>.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <returns></returns>
public long ReadLong(uint address)
{
long value = 0;
int returnLength = 0;
if (!Kernel32.ReadProcessMemory(ProcessHandle, address, out value, Marshal.SizeOf(value), out returnLength))
throw new Win32Exception(Marshal.GetLastWin32Error());
return value;
}
/// <summary>
/// Reads an unsigned long integer from <b>address</b>.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <returns></returns>
public ulong ReadULong(uint address)
{
long value = 0;
int returnLength = 0;
if (!Kernel32.ReadProcessMemory(ProcessHandle, address, out value, Marshal.SizeOf(value), out returnLength))
throw new Win32Exception(Marshal.GetLastWin32Error());
return (ulong)value;
}
/// <summary>
/// Reads a float from <b>address</b>.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <returns></returns>
public float ReadFloat(uint address)
{
float value = 0f;
int returnLength = 0;
if (!Kernel32.ReadProcessMemory(ProcessHandle, address, out value, Marshal.SizeOf(value), out returnLength))
throw new Win32Exception(Marshal.GetLastWin32Error());
return value;
}
/// <summary>
/// Reads a series of bytes, starting at <b>address</b>, ending with a zero-byte.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <seealso>ReadUTF8String</seealso>
/// <seealso>ReadUTF16String</seealso>
/// <returns></returns>
private byte[] ReadNullTerminatedBytes(uint address)
{
int returnLength = 0;
byte[] ret = new byte[0];
int size = 0;
byte b = 0;
while (true)
{
Kernel32.ReadProcessMemory(ProcessHandle, address++, out b, Marshal.SizeOf(b), out returnLength);
if (b == 0)
return ret;
else
{
Array.Resize(ref ret, size + 1);
ret[size++] = b;
}
}
}
/// <summary>
/// Reads a UTF-8 String, starting at <b>address</b>, ending with a zero-byte.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <returns></returns>
public string ReadUTF8String(uint address)
{
return Encoding.UTF8.GetString(ReadNullTerminatedBytes(address));
}
/// <summary>
/// Reads a UTF-16 String, starting at <b>address</b>, ending with a zero-byte.
/// </summary>
/// <param name="address">Pointer to read from.</param>
/// <returns></returns>
public string ReadUTF16String(uint address)
{
return Encoding.Unicode.GetString(ReadNullTerminatedBytes(address));
}
}
}
Kernel32.cs
Code:
using System.Runtime.InteropServices; // DllImport, DllImportAttribute, MarshalAs, MarshalAsAttribute
// StructLayout, StructLayoutAttribute
namespace WoWHack.Win32
{
/// <summary>
/// This entirely static class contains all the needed kernel32 functions, as well
/// as their related enumerations and structures.
/// </summary>
static class Kernel32
{
#region OpenProcess
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int OpenProcess(
OpenProcess_Access dwDesiredAccess,
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
int dwProcessId
);
public enum OpenProcess_Access : int
{
/// <summary>Enables usage of the process handle in the TerminateProcess function to terminate the process.</summary>
/* 00 00 00 01 */ Terminate = 0x1,
/// <summary>Enables usage of the process handle in the CreateRemoteThread function to create a thread in the process.</summary>
/* 00 00 00 02 */ CreateThread = 0x2,
/// <summary>Enables usage of the process handle in the VirtualProtectEx and WriteProcessMemory functions to modify the virtual memory of the process.</summary>
/* 00 00 00 08 */ VMOperation = 0x8,
/// <summary>Enables usage of the process handle in the ReadProcessMemory function to' read from the virtual memory of the process.</summary>
/* 00 00 00 10 */ VMRead = 0x10,
/// <summary>Enables usage of the process handle in the WriteProcessMemory function to write to the virtual memory of the process.</summary>
/* 00 00 00 20 */ VMWrite = 0x20,
/// <summary>Enables usage of the process handle as either the source or target process in the DuplicateHandle function to duplicate a handle.</summary>
/* 00 00 00 40 */ DuplicateHandle = 0x40,
/// <summary>Enables usage of the process handle in the SetPriorityClass function to set the priority class of the process.</summary>
/* 00 00 02 00 */ SetInformation = 0x200,
/// <summary>Enables usage of the process handle in the GetExitCodeProcess and GetPriorityClass functions to read information from the process object.</summary>
/* 00 00 04 00 */ QueryInformation = 0x400,
/// <summary>Enables usage of the process handle in any of the wait functions to wait for the process to terminate.</summary>
/* 00 10 00 00 */ Synchronize = 0x100000
}
#endregion OpenProcess
#region CloseHandle
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(int hObject);
#endregion
#region ReadProcessMemory
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(
int hProcess,
uint lpBaseAddress,
out byte[] buffer,
int dwSize,
out int lpNumberOfBytesRead
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(
int hProcess,
uint lpBaseAddress,
out byte buffer,
int dwSize,
out int lpNumberOfBytesRead
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(
int hProcess,
uint lpBaseAddress,
out long buffer,
int dwSize,
out int lpNumberOfBytesRead
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(
int hProcess,
uint lpBaseAddress,
out int buffer,
int dwSize,
out int lpNumberOfBytesRead
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(
int hProcess,
uint lpBaseAddress,
out float buffer,
int dwSize,
out int lpNumberOfBytesRead
);
#endregion
#region GetCurrentProcess
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int GetCurrentProcess();
#endregion
}
}
Thanks to:
lanman92 - Teaching me how to enter debug mode without ripping hairs out.
vulcanaoc - Correction on C# naming conventions.
deCutter and ramey - Pointing out the obvious but overlooked fact that WoW uses UTF-8 instead of ASCII.
Whoever +rep'd me - Emotional support.