From e8cd4e750a7cdd5698b6fff1057e367c71754ddc Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Wed, 7 Apr 2021 02:15:44 +0200 Subject: [PATCH] refactor: new code style in SigScanner.cs --- Dalamud/Game/SigScanner.cs | 442 +++++++++++++++++++++---------------- 1 file changed, 249 insertions(+), 193 deletions(-) diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index cb57f3789..1242935ef 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -4,85 +4,288 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using Serilog; -namespace Dalamud.Game { +namespace Dalamud.Game +{ /// /// A SigScanner facilitates searching for memory signatures in a given ProcessModule. /// - public sealed class SigScanner : IDisposable { + public sealed class SigScanner : IDisposable + { + private IntPtr moduleCopyPtr; + private long moduleCopyOffset; + /// - /// Set up the SigScanner. + /// Initializes a new instance of the class. /// - /// The ProcessModule to be used for scanning + /// The ProcessModule to be used for scanning. /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. - public SigScanner(ProcessModule module, bool doCopy = false) { - Module = module; - Is32BitProcess = !Environment.Is64BitProcess; - IsCopy = doCopy; + public SigScanner(ProcessModule module, bool doCopy = false) + { + this.Module = module; + this.Is32BitProcess = !Environment.Is64BitProcess; + this.IsCopy = doCopy; // Limit the search space to .text section. - SetupSearchSpace(module); + this.SetupSearchSpace(module); - if (IsCopy) - SetupCopiedSegments(); - Log.Verbose("Module base: {Address}", TextSectionBase); - Log.Verbose("Module size: {Size}", TextSectionSize); + if (this.IsCopy) + this.SetupCopiedSegments(); + + Log.Verbose("Module base: {Address}", this.TextSectionBase); + Log.Verbose("Module size: {Size}", this.TextSectionSize); } /// - /// If the search on this module is performed on a copy. + /// Gets a value indicating whether or not the search on this module is performed on a copy. /// public bool IsCopy { get; } /// - /// If the ProcessModule is 32-bit. + /// Gets a value indicating whether or not the ProcessModule is 32-bit. /// public bool Is32BitProcess { get; } /// - /// The base address of the search area. When copied, this will be the address of the copy. + /// Gets the base address of the search area. When copied, this will be the address of the copy. /// - public IntPtr SearchBase => IsCopy ? this.moduleCopyPtr : Module.BaseAddress; + public IntPtr SearchBase => this.IsCopy ? this.moduleCopyPtr : this.Module.BaseAddress; /// - /// The base address of the .text section search area. + /// Gets the base address of the .text section search area. /// - public IntPtr TextSectionBase => new IntPtr(SearchBase.ToInt64() + TextSectionOffset); + public IntPtr TextSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.TextSectionOffset); /// - /// The offset of the .text section from the base of the module. + /// Gets the offset of the .text section from the base of the module. /// public long TextSectionOffset { get; private set; } /// - /// The size of the text section. + /// Gets the size of the text section. /// public int TextSectionSize { get; private set; } /// - /// The base address of the .data section search area. + /// Gets the base address of the .data section search area. /// - public IntPtr DataSectionBase => new IntPtr(SearchBase.ToInt64() + DataSectionOffset); + public IntPtr DataSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.DataSectionOffset); /// - /// The offset of the .data section from the base of the module. + /// Gets the offset of the .data section from the base of the module. /// public long DataSectionOffset { get; private set; } /// - /// The size of the .data section. + /// Gets the size of the .data section. /// public int DataSectionSize { get; private set; } /// - /// The ProcessModule on which the search is performed. + /// Gets the ProcessModule on which the search is performed. /// public ProcessModule Module { get; } - private IntPtr TextSectionTop => TextSectionBase + TextSectionSize; + private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize; - private void SetupSearchSpace(ProcessModule module) { + /// + /// Scan memory for a signature. + /// + /// The base address to scan from. + /// The amount of bytes to scan. + /// The signature to search for. + /// The found offset. + public static IntPtr Scan(IntPtr baseAddress, int size, string signature) + { + var (needle, mask) = ParseSignature(signature); + var index = IndexOf(baseAddress, size, needle, mask); + if (index < 0) + throw new KeyNotFoundException($"Can't find a signature of {signature}"); + return baseAddress + index; + } + + /// + /// Scan for a .data address using a .text function. + /// This is intended to be used with IDA sigs. + /// Place your cursor on the line calling a static address, and create and IDA sig. + /// + /// The signature of the function using the data. + /// The offset from function start of the instruction using the data. + /// An IntPtr to the static memory location. + public IntPtr GetStaticAddressFromSig(string signature, int offset = 0) + { + var instrAddr = this.ScanText(signature); + instrAddr = IntPtr.Add(instrAddr, offset); + var bAddr = (long)this.Module.BaseAddress; + long num; + + do + { + instrAddr = IntPtr.Add(instrAddr, 1); + num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr; + } + while (!(num >= this.DataSectionOffset && num <= this.DataSectionOffset + this.DataSectionSize)); + + return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4); + } + + /// + /// Scan for a byte signature in the .data section. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanData(string signature) + { + var scanRet = Scan(this.DataSectionBase, this.DataSectionSize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + return scanRet; + } + + /// + /// Scan for a byte signature in the whole module search area. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanModule(string signature) + { + var scanRet = Scan(this.SearchBase, this.Module.ModuleMemorySize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + return scanRet; + } + + /// + /// Resolve a RVA address. + /// + /// The address of the next instruction. + /// The relative offset. + /// The calculated offset. + public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) + { + if (this.Is32BitProcess) throw new NotSupportedException("32 bit is not supported."); + return nextInstAddr + relOffset; + } + + /// + /// Scan for a byte signature in the .text section. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanText(string signature) + { + var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; + + var scanRet = Scan(mBase, this.TextSectionSize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + var insnByte = Marshal.ReadByte(scanRet); + + if (insnByte == 0xE8 || insnByte == 0xE9) + return ReadCallSig(scanRet); + + return scanRet; + } + + /// + /// Free the memory of the copied module search area on object disposal, if applicable. + /// + public void Dispose() + { + Marshal.FreeHGlobal(this.moduleCopyPtr); + } + + /// + /// Helper for ScanText to get the correct address for IDA sigs that mark the first CALL location. + /// + /// The address the CALL sig resolved to. + /// The real offset of the signature. + private static IntPtr ReadCallSig(IntPtr sigLocation) + { + var jumpOffset = Marshal.ReadInt32(IntPtr.Add(sigLocation, 1)); + return IntPtr.Add(sigLocation, 5 + jumpOffset); + } + + private static (byte[] needle, bool[] mask) ParseSignature(string signature) + { + signature = signature.Replace(" ", string.Empty); + if (signature.Length % 2 != 0) + throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature)); + var needleLength = signature.Length / 2; + var needle = new byte[needleLength]; + var mask = new bool[needleLength]; + for (var i = 0; i < needleLength; i++) + { + var hexString = signature.Substring(i * 2, 2); + if (hexString == "??" || hexString == "**") + { + needle[i] = 0; + mask[i] = true; + continue; + } + + needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); + mask[i] = false; + } + + return (needle, mask); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) + { + if (needle.Length > bufferLength) return -1; + var badShift = BuildBadCharTable(needle, mask); + var last = needle.Length - 1; + var offset = 0; + var maxoffset = bufferLength - needle.Length; + var buffer = (byte*)bufferPtr; + + while (offset <= maxoffset) + { + int position; + for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) + { + if (position == 0) + return offset; + } + + offset += badShift[*(buffer + offset + last)]; + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int[] BuildBadCharTable(byte[] needle, bool[] mask) + { + int idx; + var last = needle.Length - 1; + var badShift = new int[256]; + for (idx = last; idx > 0 && !mask[idx]; --idx) + { + } + + var diff = last - idx; + if (diff == 0) diff = 1; + + for (idx = 0; idx <= 255; ++idx) + badShift[idx] = diff; + for (idx = last - diff; idx < last; ++idx) + badShift[needle[idx]] = last - idx; + return badShift; + } + + private void SetupSearchSpace(ProcessModule module) + { var baseAddress = module.BaseAddress; // We don't want to read all of IMAGE_DOS_HEADER or IMAGE_NT_HEADER stuff so we cheat here. @@ -97,25 +300,27 @@ namespace Dalamud.Game { var optionalHeader = fileHeader + 20; IntPtr sectionHeader; - if (Is32BitProcess) // IMAGE_OPTIONAL_HEADER32 + if (this.Is32BitProcess) // IMAGE_OPTIONAL_HEADER32 sectionHeader = optionalHeader + 224; else // IMAGE_OPTIONAL_HEADER64 sectionHeader = optionalHeader + 240; // IMAGE_SECTION_HEADER var sectionCursor = sectionHeader; - for (var i = 0; i < numSections; i++) { + for (var i = 0; i < numSections; i++) + { var sectionName = Marshal.ReadInt64(sectionCursor); // .text - switch (sectionName) { + switch (sectionName) + { case 0x747865742E: // .text - TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12); - TextSectionSize = Marshal.ReadInt32(sectionCursor, 8); + this.TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12); + this.TextSectionSize = Marshal.ReadInt32(sectionCursor, 8); break; case 0x617461642E: // .data - DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); - DataSectionSize = Marshal.ReadInt32(sectionCursor, 8); + this.DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); + this.DataSectionSize = Marshal.ReadInt32(sectionCursor, 8); break; } @@ -123,166 +328,17 @@ namespace Dalamud.Game { } } - private IntPtr moduleCopyPtr; - private long moduleCopyOffset; - - private unsafe void SetupCopiedSegments() { - Log.Verbose("module copy START"); + private unsafe void SetupCopiedSegments() + { // .text - this.moduleCopyPtr = Marshal.AllocHGlobal(Module.ModuleMemorySize); - Log.Verbose($"Alloc: {this.moduleCopyPtr.ToInt64():x}"); - Buffer.MemoryCopy(Module.BaseAddress.ToPointer(), this.moduleCopyPtr.ToPointer(), Module.ModuleMemorySize, Module.ModuleMemorySize); - this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - Module.BaseAddress.ToInt64(); - Log.Verbose("copy OK!"); - } + this.moduleCopyPtr = Marshal.AllocHGlobal(this.Module.ModuleMemorySize); + Buffer.MemoryCopy( + this.Module.BaseAddress.ToPointer(), + this.moduleCopyPtr.ToPointer(), + this.Module.ModuleMemorySize, + this.Module.ModuleMemorySize); - /// - /// Free the memory of the copied module search area on object disposal, if applicable. - /// - public void Dispose() { - Marshal.FreeHGlobal(this.moduleCopyPtr); - } - - public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) { - if (Is32BitProcess) throw new NotSupportedException("32 bit is not supported."); - return nextInstAddr + relOffset; - } - - /// - /// Scan for a byte signature in the .text section. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanText(string signature) { - var mBase = IsCopy ? this.moduleCopyPtr : TextSectionBase; - var scanRet = Scan(mBase, TextSectionSize, signature); - if (IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - var insnByte = Marshal.ReadByte(scanRet); - if (insnByte == 0xE8 || insnByte == 0xE9) - return ReadCallSig(scanRet); - return scanRet; - } - - /// - /// Helper for ScanText to get the correct address for IDA sigs that mark the first CALL location. - /// - /// The address the CALL sig resolved to. - /// The real offset of the signature. - private static IntPtr ReadCallSig(IntPtr sigLocation) { - var jumpOffset = Marshal.ReadInt32(IntPtr.Add(sigLocation, 1)); - return IntPtr.Add(sigLocation, 5 + jumpOffset); - } - - /// - /// Scan for a .data address using a .text function. - /// This is intended to be used with IDA sigs. - /// Place your cursor on the line calling a static address, and create and IDA sig. - /// - /// The signature of the function using the data. - /// The offset from function start of the instruction using the data. - /// An IntPtr to the static memory location. - public IntPtr GetStaticAddressFromSig(string signature, int offset = 0) { - var instrAddr = ScanText(signature); - instrAddr = IntPtr.Add(instrAddr, offset); - var bAddr = (long)Module.BaseAddress; - long num; - do { - instrAddr = IntPtr.Add(instrAddr, 1); - num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr; - } while (!(num >= DataSectionOffset && num <= DataSectionOffset + DataSectionSize)); - - return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4); - } - - /// - /// Scan for a byte signature in the .data section. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanData(string signature) { - var scanRet = Scan(DataSectionBase, DataSectionSize, signature); - if (IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - return scanRet; - } - - /// - /// Scan for a byte signature in the whole module search area. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanModule(string signature) { - var scanRet = Scan(SearchBase, Module.ModuleMemorySize, signature); - if (IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - return scanRet; - } - - public IntPtr Scan(IntPtr baseAddress, int size, string signature) { - var (needle, mask) = ParseSignature(signature); - var index = IndexOf(baseAddress, size, needle, mask); - if (index < 0) - throw new KeyNotFoundException($"Can't find a signature of {signature}"); - return baseAddress + index; - } - - private static (byte[] needle, bool[] mask) ParseSignature(string signature) { - signature = signature.Replace(" ", ""); - if (signature.Length % 2 != 0) - throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature)); - var needleLength = signature.Length / 2; - var needle = new byte[needleLength]; - var mask = new bool[needleLength]; - for (var i = 0; i < needleLength; i++) { - var hexString = signature.Substring(i * 2, 2); - if (hexString == "??" || hexString == "**") { - needle[i] = 0; - mask[i] = true; - continue; - } - - needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); - mask[i] = false; - } - - return (needle, mask); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) { - if (needle.Length > bufferLength) return -1; - var badShift = BuildBadCharTable(needle, mask); - var last = needle.Length - 1; - var offset = 0; - var maxoffset = bufferLength - needle.Length; - var buffer = (byte*)bufferPtr; - while (offset <= maxoffset) { - int position; - for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) - if (position == 0) - return offset; - offset += badShift[*(buffer + offset + last)]; - } - - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int[] BuildBadCharTable(byte[] needle, bool[] mask) { - int idx; - var last = needle.Length - 1; - var badShift = new int[256]; - for (idx = last; idx > 0 && !mask[idx]; --idx) { } - - var diff = last - idx; - if (diff == 0) diff = 1; - - for (idx = 0; idx <= 255; ++idx) - badShift[idx] = diff; - for (idx = last - diff; idx < last; ++idx) - badShift[needle[idx]] = last - idx; - return badShift; + this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64(); } } }