using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using ReClassNET.AddressParser; using ReClassNET.Symbols; using ReClassNET.Util; namespace ReClassNET.Memory { public class RemoteProcess { private readonly NativeHelper nativeHelper; public NativeHelper NativeHelper => nativeHelper; private ProcessInfo process; public ProcessInfo Process { get { return process; } set { if (process != value) { process = value; rttiCache.Clear(); ProcessChanged?.Invoke(this); } } } public delegate void RemoteProcessChangedEvent(RemoteProcess sender); public event RemoteProcessChangedEvent ProcessChanged; private readonly Dictionary rttiCache = new Dictionary(); public class Module { public IntPtr Start; public IntPtr End; public string Name; public string Path; } public enum SectionCategory { Unknown, Code, Data } public class Section { public IntPtr Start; public IntPtr End; public string Name; public SectionCategory Category; public NativeMethods.StateEnum State; public NativeMethods.AllocationProtectEnum Protection; public NativeMethods.TypeEnum Type; public string ModuleName; public string ModulePath; } private readonly List modules = new List(); private readonly List
sections = new List
(); private readonly SymbolStore symbols = new SymbolStore(); public SymbolStore Symbols => symbols; public bool IsValid => process != null && nativeHelper.IsProcessValid(process.Handle); public RemoteProcess(NativeHelper nativeHelper) { Contract.Requires(nativeHelper != null); this.nativeHelper = nativeHelper; } #region ReadMemory /// Reads remote memory from the address into the buffer. /// The address to read from. /// [out] The data buffer to fill. If the remote process is not valid, the buffer will get filled with zeros. public void ReadRemoteMemoryIntoBuffer(IntPtr address, ref byte[] data) { Contract.Requires(data != null); if (!IsValid) { Process = null; data.FillWithZero(); return; } nativeHelper.ReadRemoteMemory(Process.Handle, address, data, (uint)data.Length); } /// Reads bytes from the address in the remote process. /// The address to read from. /// The size in bytes to read. /// An array of bytes. public byte[] ReadRemoteMemory(IntPtr address, int size) { Contract.Requires(size >= 0); Contract.Ensures(Contract.Result() != null); var data = new byte[size]; ReadRemoteMemoryIntoBuffer(address, ref data); return data; } /// Reads the object from the address in the remote process. /// Type of the value to read. /// The address to read from. /// The remote object. public T ReadRemoteObject(IntPtr address) where T : struct { var data = ReadRemoteMemory(address, Marshal.SizeOf()); var handle = GCHandle.Alloc(data, GCHandleType.Pinned); var obj = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); handle.Free(); return obj; } /// Reads a string from the address in the remote process with the given length using the provided encoding. /// The encoding used by the string. /// The address of the string. /// The length of the string. /// The string. public string ReadRemoteString(Encoding encoding, IntPtr address, int length) { Contract.Requires(encoding != null); Contract.Requires(length >= 0); Contract.Ensures(Contract.Result() != null); var data = ReadRemoteMemory(address, length); try { var sb = new StringBuilder(encoding.GetString(data)); for (var i = 0; i < sb.Length; ++i) { if (sb[i] == 0) { sb.Length = i; break; } if (!sb[i].IsPrintable()) { sb[i] = '.'; } } return sb.ToString(); } catch { return string.Empty; } } /// Reads a string from the address in the remote process with the given length using UTF8 encoding. The string gets truncated at the first zero character. /// The address of the string. /// The length of the string. /// The string. public string ReadRemoteUTF8StringUntilFirstNullCharacter(IntPtr address, int length) { Contract.Requires(length >= 0); var data = ReadRemoteMemory(address, length); int index = 0; for (; index < data.Length; ++index) { if (data[index] == 0) { break; } } try { return Encoding.UTF8.GetString(data, 0, Math.Min(index, data.Length)); } catch { return string.Empty; } } /// Reads remote runtime type information for the given address from the remote process. /// The address. /// A string containing the runtime type information or null if no information could get found. public string ReadRemoteRuntimeTypeInformation(IntPtr address) { if (address.MayBeValid()) { string rtti = null; if (!rttiCache.TryGetValue(address, out rtti)) { var objectLocatorPtr = ReadRemoteObject(address - IntPtr.Size); if (objectLocatorPtr.MayBeValid()) { #if WIN64 rtti = ReadRemoteRuntimeTypeInformation64(objectLocatorPtr); #else rtti = ReadRemoteRuntimeTypeInformation32(objectLocatorPtr); #endif rttiCache[address] = rtti; } } return rtti; } return null; } private string ReadRemoteRuntimeTypeInformation32(IntPtr address) { var classHierarchyDescriptorPtr = ReadRemoteObject(address + 0x10); if (classHierarchyDescriptorPtr.MayBeValid()) { var baseClassCount = ReadRemoteObject(classHierarchyDescriptorPtr + 8); if (baseClassCount > 0 && baseClassCount < 25) { var baseClassArrayPtr = ReadRemoteObject(classHierarchyDescriptorPtr + 0xC); if (baseClassArrayPtr.MayBeValid()) { var sb = new StringBuilder(); for (var i = 0; i < baseClassCount; ++i) { var baseClassDescriptorPtr = ReadRemoteObject(baseClassArrayPtr + (4 * i)); if (baseClassDescriptorPtr.MayBeValid()) { var typeDescriptorPtr = ReadRemoteObject(baseClassDescriptorPtr); if (typeDescriptorPtr.MayBeValid()) { var name = ReadRemoteUTF8StringUntilFirstNullCharacter(typeDescriptorPtr + 0x0C, 60); if (name.EndsWith("@@")) { name = NativeMethods.UnDecorateSymbolName("?" + name); } 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 = ReadRemoteObject(address + 0x14); if (baseOffset != 0) { var baseAddress = address - baseOffset; var classHierarchyDescriptorOffset = ReadRemoteObject(address + 0x10); if (classHierarchyDescriptorOffset != 0) { var classHierarchyDescriptorPtr = baseAddress + classHierarchyDescriptorOffset; var baseClassCount = ReadRemoteObject(classHierarchyDescriptorPtr + 0x08); if (baseClassCount > 0 && baseClassCount < 25) { var baseClassArrayOffset = ReadRemoteObject(classHierarchyDescriptorPtr + 0x0C); if (baseClassArrayOffset != 0) { var baseClassArrayPtr = baseAddress + baseClassArrayOffset; var sb = new StringBuilder(); for (var i = 0; i < baseClassCount; ++i) { var baseClassDescriptorOffset = ReadRemoteObject(baseClassArrayPtr + (4 * i)); if (baseClassDescriptorOffset != 0) { var baseClassDescriptorPtr = baseAddress + baseClassDescriptorOffset; var typeDescriptorOffset = ReadRemoteObject(baseClassDescriptorPtr); if (typeDescriptorOffset != 0) { var typeDescriptorPtr = baseAddress + typeDescriptorOffset; var name = ReadRemoteUTF8StringUntilFirstNullCharacter(typeDescriptorPtr + 0x14, 60); if (string.IsNullOrEmpty(name)) { break; } if (name.EndsWith("@@")) { name = NativeMethods.UnDecorateSymbolName("?" + name); } sb.Append(name); sb.Append(" : "); continue; } } break; } if (sb.Length != 0) { sb.Length -= 3; return sb.ToString(); } } } } } return null; } #endregion #region WriteMemory /// Writes the given to the in the remote process. /// The address to write to. /// The data to write. /// True if it succeeds, false if it fails. public bool WriteRemoteMemory(IntPtr address, byte[] data) { Contract.Requires(data != null); if (!IsValid) { return false; } return nativeHelper.WriteRemoteMemory(Process.Handle, address, data, (uint)data.Length); } /// Writes the given to the in the remote process. /// Type of the value to write. /// The address to write to. /// The value to write. /// True if it succeeds, false if it fails. public bool WriteRemoteMemory(IntPtr address, T value) where T : struct { var data = new byte[Marshal.SizeOf()]; var handle = GCHandle.Alloc(data, GCHandleType.Pinned); Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false); handle.Free(); return WriteRemoteMemory(address, data); } #endregion public Section GetSectionToPointer(IntPtr address) { lock (sections) { return sections .Where(s => s.Category != SectionCategory.Unknown) .Where(s => address.InRange(s.Start, s.End)) .FirstOrDefault(); } } public Module GetModuleToPointer(IntPtr address) { lock (modules) { return modules .Where(m => address.InRange(m.Start, m.End)) .FirstOrDefault(); } } public Module GetModuleByName(string name) { lock (modules) { return modules .Where(m => m.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) .FirstOrDefault(); } } /// Tries to map the given address to a section or a module of the process. /// The address to map. /// The named address or null if no mapping exists. public string GetNamedAddress(IntPtr address) { var section = GetSectionToPointer(address); if (section != null) { return $"<{section.Category}>{section.ModuleName}.{address.ToString("X")}"; } var module = GetModuleToPointer(address); if (module != null) { return $"{module.Name}.{address.ToString("X")}"; } return null; } /// Updates the process informations. public void UpdateProcessInformations() { UpdateProcessInformationsAsync().Wait(); } /// Updates the process informations asynchronous. /// The Task. public Task UpdateProcessInformationsAsync() { if (!IsValid) { lock(modules) { modules.Clear(); } lock(sections) { sections.Clear(); } return Task.CompletedTask; } return Task.Run(() => { var newModules = new List(); var newSections = new List
(); nativeHelper.EnumerateRemoteSectionsAndModules( process.Handle, delegate (IntPtr baseAddress, IntPtr regionSize, string name, NativeMethods.StateEnum state, NativeMethods.AllocationProtectEnum protection, NativeMethods.TypeEnum type, string modulePath) { var section = new Section { Start = baseAddress, End = baseAddress.Add(regionSize), Name = name, State = state, Protection = protection, Type = type, ModulePath = modulePath, ModuleName = Path.GetFileName(modulePath) }; switch (section.Name) { case ".text": case "code": section.Category = SectionCategory.Code; break; case ".data": case "data": case ".rdata": case ".idata": section.Category = SectionCategory.Data; break; } newSections.Add(section); }, delegate (IntPtr baseAddress, IntPtr regionSize, string modulePath) { newModules.Add(new Module { Start = baseAddress, End = baseAddress.Add(regionSize), Path = modulePath, Name = Path.GetFileName(modulePath) }); } ); lock (modules) { modules.Clear(); modules.AddRange(newModules); } lock (sections) { sections.Clear(); sections.AddRange(newSections); } }); } /// Parse the address formula. /// The address formula. /// The result of the parsed address or . public IntPtr ParseAddress(string addressFormula) { Contract.Requires(addressFormula != null); var reader = new TokenReader(); var tokens = reader.Read(addressFormula); var astBuilder = new AstBuilder(); var operation = astBuilder.Build(tokens); if (operation == null) { return IntPtr.Zero; } var interpreter = new Interpreter(); return interpreter.Execute(operation, this); } /// Loads all symbols asynchronous. /// The progress reporter is called for every module. Can be null. /// The token used to cancel the task. /// The task. public Task LoadAllSymbolsAsync(IProgress>> progress, CancellationToken token) { var copy = modules.ToList(); // Try to resolve all symbols in a background thread. This can take a long time because symbols are downloaded from the internet. // The COM objects can only be used in the thread they were created so we can't use them. // Thats why an other task loads the real symbols afterwards in the UI thread context. return Task.Run( () => { foreach (var module in copy) { token.ThrowIfCancellationRequested(); progress?.Report(Tuple.Create>(module, copy)); Symbols.TryResolveSymbolsForModule(module); } }, token ) .ContinueWith( _ => { foreach (var module in copy) { token.ThrowIfCancellationRequested(); try { Symbols.LoadSymbolsForModule(module); } catch { //ignore } } }, token, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext() ); } } }