From 4ee825d3acb78119a0d2c94ddad2f4f4de478d25 Mon Sep 17 00:00:00 2001 From: "Chris Weermann (TGE)" Date: Sun, 10 Nov 2019 14:15:01 +0100 Subject: [PATCH] Add support for scanning arbitrary ranges & saving compiled patterns --- Reloaded.Memory.SigScan.Tests/ScannerTests.cs | 3 - .../Benchmarks/Parsing/ParsePattern.cs | 6 +- Reloaded.Memory.Sigscan/Scanner.cs | 81 ++++++++++++++----- ...structionSet.cs => CompiledScanPattern.cs} | 21 +++-- 4 files changed, 75 insertions(+), 36 deletions(-) rename Reloaded.Memory.Sigscan/Structs/{PatternScanInstructionSet.cs => CompiledScanPattern.cs} (90%) diff --git a/Reloaded.Memory.SigScan.Tests/ScannerTests.cs b/Reloaded.Memory.SigScan.Tests/ScannerTests.cs index 28966f0..fbb5bed 100644 --- a/Reloaded.Memory.SigScan.Tests/ScannerTests.cs +++ b/Reloaded.Memory.SigScan.Tests/ScannerTests.cs @@ -39,9 +39,6 @@ public void InstantiateFromCurrentProcess() // Test fails if function throws. var thisProcess = Process.GetCurrentProcess(); var scanner = new Scanner(thisProcess, thisProcess.MainModule); - - Assert.NotEmpty(scanner.Data); - Assert.NotNull (scanner.Data); } [Fact] diff --git a/Reloaded.Memory.Sigscan.Benchmark/Benchmarks/Parsing/ParsePattern.cs b/Reloaded.Memory.Sigscan.Benchmark/Benchmarks/Parsing/ParsePattern.cs index f6b4aca..97248a1 100644 --- a/Reloaded.Memory.Sigscan.Benchmark/Benchmarks/Parsing/ParsePattern.cs +++ b/Reloaded.Memory.Sigscan.Benchmark/Benchmarks/Parsing/ParsePattern.cs @@ -12,7 +12,7 @@ public class ParsePattern [Benchmark] public PatternScanInstructionSet MakeInstructionSetWithMask() { - return PatternScanInstructionSet.FromStringPattern("DA 69 ?? ?? FE B9"); + return new PatternScanInstructionSet("DA 69 ?? ?? FE B9"); } [Benchmark] @@ -25,7 +25,7 @@ public SimplePatternScanData MakeScanDataWithMask() [Benchmark] public PatternScanInstructionSet MakeInstructionSetWithoutMask() { - return PatternScanInstructionSet.FromStringPattern("DA 69 DD AA FE B9"); + return new PatternScanInstructionSet("DA 69 DD AA FE B9"); } [Benchmark] @@ -38,7 +38,7 @@ public SimplePatternScanData MakeScanDataWithoutMask() [Benchmark] public PatternScanInstructionSet MakeInstructionSetWithoutMaskLong() { - return PatternScanInstructionSet.FromStringPattern("DA 69 DD AA FE B9 BB CC DD EE FF"); + return new PatternScanInstructionSet("DA 69 DD AA FE B9 BB CC DD EE FF"); } [Benchmark] diff --git a/Reloaded.Memory.Sigscan/Scanner.cs b/Reloaded.Memory.Sigscan/Scanner.cs index e153b6f..cb8d27b 100644 --- a/Reloaded.Memory.Sigscan/Scanner.cs +++ b/Reloaded.Memory.Sigscan/Scanner.cs @@ -16,16 +16,12 @@ namespace Reloaded.Memory.Sigscan /// /// Provides an implementation of a simple signature scanner sitting ontop of Reloaded.Memory. /// - public unsafe class Scanner + public unsafe class Scanner : IDisposable { - /// - /// The region of data to be scanned for signatures. - /// - public byte[] Data => _data.ToArray(); - private Memory _data; - - private GCHandle _gcHandle; + private bool _disposedValue; + private GCHandle? _gcHandle; private byte* _dataPtr; + private int _dataLength; /// /// Creates a signature scanner given the data in which patterns are to be found. @@ -33,9 +29,9 @@ public unsafe class Scanner /// The data to look for signatures inside. public Scanner(byte[] data) { - _data = data; _gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); - _dataPtr = (byte*) _gcHandle.AddrOfPinnedObject(); + _dataPtr = (byte*)_gcHandle.Value.AddrOfPinnedObject(); + _dataLength = data.Length; } /// @@ -49,9 +45,25 @@ public Scanner(Process process, ProcessModule module) var externalProcess = new ExternalMemory(process); externalProcess.ReadRaw(module.BaseAddress, out var data, module.ModuleMemorySize); - _data = data; _gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); - _dataPtr = (byte*)_gcHandle.AddrOfPinnedObject(); + _dataPtr = (byte*)_gcHandle.Value.AddrOfPinnedObject(); + _dataLength = data.Length; + } + + /// + /// Creates a signature scanner given the data in which patterns are to be found. + /// + /// The data to look for signatures inside. + /// The length of the data. + public Scanner(byte* data, int length) + { + _dataPtr = data; + _dataLength = length; + } + + ~Scanner() + { + Dispose(false); } /// @@ -67,16 +79,27 @@ public Scanner(Process process, ProcessModule module) /// A result indicating an offset (if found) of the pattern. public PatternScanResult CompiledFindPattern(string pattern) { - var instructionSet = PatternScanInstructionSet.FromStringPattern(pattern); - int numberOfInstructions = instructionSet.NumberOfInstructions; - int dataLength = _data.Length; + var instructionSet = new CompiledScanPattern(pattern); + return CompiledFindPattern(instructionSet); + } + /// + /// Attempts to find a given pattern inside the memory region this class was created with. + /// This method generally works better when the expected offset is bigger than 4096. + /// + /// + /// The compiled pattern to look for inside the given region. + /// + /// A result indicating an offset (if found) of the pattern. + public PatternScanResult CompiledFindPattern(CompiledScanPattern pattern) + { + int numberOfInstructions = pattern.NumberOfInstructions; byte* dataBasePointer = _dataPtr; byte* currentDataPointer; - int lastIndex = dataLength - Math.Max(instructionSet.Length, sizeof(long)) + 1; + int lastIndex = _dataLength - Math.Max(pattern.Length, sizeof(long)) + 1; // Note: All of this has to be manually inlined otherwise performance suffers, this is a bit ugly though :/ - fixed (GenericInstruction* instructions = instructionSet.Instructions) + fixed (GenericInstruction* instructions = pattern.Instructions) { var firstInstruction = instructions[0]; @@ -125,7 +148,7 @@ as opposing to having to dereference a pointer and then take an offset from the // Check last few bytes in cases pattern was not found and long overflows into possibly unallocated memory. - return SimpleFindPattern(pattern, lastIndex); + return SimpleFindPattern(pattern.Pattern, lastIndex); // PS. This function is a prime example why the `goto` statement is frowned upon. // I have to use it here for performance though. @@ -150,7 +173,7 @@ public PatternScanResult SimpleFindPattern(string pattern, int startingIndex = 0 var patternData = target.Bytes; var patternMask = target.Mask; - int lastIndex = (_data.Span.Length - patternMask.Length) + 1; + int lastIndex = (_dataLength - patternMask.Length) + 1; fixed (byte* patternDataPtr = patternData) { @@ -187,5 +210,25 @@ public PatternScanResult SimpleFindPattern(string pattern, int startingIndex = 0 return new PatternScanResult(-1); } } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _gcHandle?.Free(); + } + + _disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/Reloaded.Memory.Sigscan/Structs/PatternScanInstructionSet.cs b/Reloaded.Memory.Sigscan/Structs/CompiledScanPattern.cs similarity index 90% rename from Reloaded.Memory.Sigscan/Structs/PatternScanInstructionSet.cs rename to Reloaded.Memory.Sigscan/Structs/CompiledScanPattern.cs index 8c4f247..6732af7 100644 --- a/Reloaded.Memory.Sigscan/Structs/PatternScanInstructionSet.cs +++ b/Reloaded.Memory.Sigscan/Structs/CompiledScanPattern.cs @@ -12,17 +12,21 @@ namespace Reloaded.Memory.Sigscan.Structs { /// - /// [Internal & Test Use] /// Represents the pattern to be searched by the scanner. /// - public ref struct PatternScanInstructionSet + public ref struct CompiledScanPattern { private const string MaskIgnore = "??"; + /// + /// The pattern the instruction set was created from. + /// + public readonly string Pattern; + /// /// The length of the original given pattern. /// - public int Length; + public readonly int Length; /// /// Contains the functions that will be executed in order to validate a given block of memory to equal @@ -43,15 +47,9 @@ public ref struct PatternScanInstructionSet /// Example: "11 22 33 ?? 55". /// Key: ?? represents a byte that should be ignored, anything else if a hex byte. i.e. 11 represents 0x11, 1F represents 0x1F. /// - public static PatternScanInstructionSet FromStringPattern(string stringPattern) - { - var instructionSet = new PatternScanInstructionSet(); - instructionSet.Initialize(stringPattern); - return instructionSet; - } - - private unsafe void Initialize(string stringPattern) + public CompiledScanPattern(string stringPattern) { + Pattern = stringPattern; string[] entries = stringPattern.Split(' '); Length = entries.Length; @@ -70,6 +68,7 @@ private unsafe void Initialize(string stringPattern) // Get bytes to make instructions with. Instructions = new GenericInstruction[Length]; + NumberOfInstructions = 0; // Optimization for short-medium patterns with masks. // Check if our pattern is 1-8 bytes and contains any skips.