using System;
using System.Collections.Concurrent;
using System.Text;
using ProcessNET.Extensions;
using ProcessNET.Native;
using ProcessNET.Native.Types;
namespace ProcessNET.Memory
{
///
/// Class for memory editing a process.
///
///
public abstract class ProcessMemory : IMemory
{
///
/// The open handle to the process which contains the memory of interest,
///
protected readonly SafeMemoryHandle Handle;
///
/// Runtime types names based on their addresses.
///
protected ConcurrentDictionary rttiCache = null;
protected readonly bool is32Bit = false;
///
/// Returns the type of the processor architecture of the process.
///
public bool Is32Bit => is32Bit;
///
/// Initializes a new instance of the class.
///
/// The open handle to the process which contains the memory of interest.
protected ProcessMemory(SafeMemoryHandle handle)
{
Handle = handle;
is32Bit = Kernel32.Is32BitProcess(handle.DangerousGetHandle());
rttiCache = new ConcurrentDictionary();
}
///
/// Writes a set of bytes to memory.
///
/// The address where the bytes start in memory.
/// The length of the byte chunk to read from the memory address.
///
/// The byte array section read from memory.
///
public abstract byte[] Read(IntPtr intPtr, int length);
///
/// Writes a set of bytes to memory.
///
/// The address where the bytes start in memory.
/// The buffer to read data onto, it's size determines the count of bytes to read.
public abstract void Read(IntPtr intPtr, byte[] buffer);
///
/// Reads a string with a specified encoding from memory.
///
/// The address where the string is read.
/// The encoding used.
///
/// The number of maximum bytes to read. The string is automatically cropped at this end ('\0'
/// char).
///
/// The string.
public string ReadString(IntPtr intPtr, Encoding encoding, int maxLength = 512)
{
var buffer = Read(intPtr, maxLength);
var ret = encoding.GetString(buffer);
if (ret.IndexOf('\0') != -1)
ret = ret.Remove(ret.IndexOf('\0'));
return ret;
}
///
/// Reads the value of a specified type from memory.
///
/// The type of the value.
/// The address where the value is read.
/// A value.
public abstract T Read(IntPtr intPtr);
///
/// Reads an array of a specified type from memory.
///
/// The type of the values.
/// The address where the values is read.
/// The number of cells in the array.
/// An array.
public T[] Read(IntPtr intPtr, int length)
{
var buffer = new T[length];
for (var i = 0; i < buffer.Length; i++)
buffer[i] = Read(intPtr);
return buffer;
}
///
/// Write an array of bytes in the remote process.
///
/// The address where the array is written.
/// The array of bytes to write.
public abstract int Write(IntPtr intPtr, byte[] bytesToWrite);
///
/// Writes a string with a specified encoding to memory.
///
/// The address where the string is written.
/// The text to write.
/// The encoding used.
public virtual void WriteString(IntPtr intPtr, string stringToWrite, Encoding encoding)
{
if (stringToWrite[stringToWrite.Length - 1] != '\0')
stringToWrite += '\0';
var bytes = encoding.GetBytes(stringToWrite);
Write(intPtr, bytes);
}
///
/// Writes an array of a specified type to memory,
///
/// The type of the values.
/// The address where the values is written.
/// The array to write.
public void Write(IntPtr intPtr, T[] values)
{
foreach (var value in values)
Write(intPtr, value);
}
///
/// Writes the values of a specified type to memory.
///
/// The type of the value.
/// The address where the value is written.
/// The value to write.
public abstract void Write(IntPtr intPtr, T value);
///
/// Reads the Runtime Type Information (RTTI) at certain address.
///
/// Address to read the type from.
/// Runtime Type Information of the object at the passed address or null if it's not an object.
public string ReadRemoteRuntimeTypeInformation(IntPtr address)
{
if (address.MayBeValid())
{
if(!rttiCache.TryGetValue(address, out string rtti))
{
var objectLocatorPtr = Read(address - (Is32Bit ? 4 : 8));
if (objectLocatorPtr.MayBeValid())
{
if (is32Bit)
rtti = ReadRemoteRuntimeTypeInformation32(objectLocatorPtr);
else
rtti = ReadRemoteRuntimeTypeInformation64(objectLocatorPtr);
rttiCache.AddOrUpdate(address, rtti, (oldK, oldV) => rtti);
}
}
return rtti;
}
return null;
}
private string ReadRemoteRuntimeTypeInformation32(IntPtr address)
{
var classHierarchyDescriptorPtr = Read(address + 0x10);
if (classHierarchyDescriptorPtr.MayBeValid())
{
var baseClassCount = Read(classHierarchyDescriptorPtr + 8);
if (baseClassCount > 0 && baseClassCount < 25)
{
var baseClassArrayPtr = Read(classHierarchyDescriptorPtr + 0xC);
if (baseClassArrayPtr.MayBeValid())
{
var sb = new StringBuilder();
for (var i = 0; i < baseClassCount; ++i)
{
var baseClassDescriptorPtr = Read(baseClassArrayPtr + (4 * i));
if (baseClassDescriptorPtr.MayBeValid())
{
var typeDescriptorPtr = Read(baseClassDescriptorPtr);
if (typeDescriptorPtr.MayBeValid())
{
var name = ReadString(typeDescriptorPtr + 0x0C, Encoding.UTF8, 60);
if (name.EndsWith("@@"))
{
name = DbgHelp.UnDecorateSymbolName($"?{name}", UnDecorateFlags.UNDNAME_NAME_ONLY);
}
sb.Append(name);
sb.Append(" : ");
continue;
}
}
break;
}
if (sb.Length != 0)
{
sb.Length -= 3;
return sb.ToString();
}
}
}
}
return null;
}
private string ReadRemoteRuntimeTypeInformation64(IntPtr address)
{
int baseOffset = Read(address + 0x14);
if (baseOffset != 0)
{
var baseAddress = address - baseOffset;
var classHierarchyDescriptorOffset = Read(address + 0x10);
if (classHierarchyDescriptorOffset != 0)
{
var classHierarchyDescriptorPtr = baseAddress + classHierarchyDescriptorOffset;
var baseClassCount = Read(classHierarchyDescriptorPtr + 0x08);
if (baseClassCount > 0 && baseClassCount < 25)
{
var baseClassArrayOffset = Read(classHierarchyDescriptorPtr + 0x0C);
if (baseClassArrayOffset != 0)
{
var baseClassArrayPtr = baseAddress + baseClassArrayOffset;
var sb = new StringBuilder();
for (var i = 0; i < baseClassCount; ++i)
{
var baseClassDescriptorOffset = Read(baseClassArrayPtr + (4 * i));
if (baseClassDescriptorOffset != 0)
{
var baseClassDescriptorPtr = baseAddress + baseClassDescriptorOffset;
var typeDescriptorOffset = Read(baseClassDescriptorPtr);
if (typeDescriptorOffset != 0)
{
var typeDescriptorPtr = baseAddress + typeDescriptorOffset;
var name = ReadString(typeDescriptorPtr + 0x14, Encoding.UTF8, 60);
if (string.IsNullOrEmpty(name))
{
break;
}
if (name.EndsWith("@@"))
{
name = DbgHelp.UnDecorateSymbolName($"?{name}", UnDecorateFlags.UNDNAME_NAME_ONLY);
}
sb.Append(name);
sb.Append(" : ");
continue;
}
}
break;
}
if (sb.Length != 0)
{
sb.Length -= 3;
return sb.ToString();
}
}
}
}
}
return null;
}
}
}