Skip to content

Commit

Permalink
Merge pull request Reloaded-Project#1 from TGEnigma/arbitrary-ranges
Browse files Browse the repository at this point in the history
Add support for scanning arbitrary ranges & saving compiled patterns
  • Loading branch information
Sewer56 authored Nov 13, 2019
2 parents 79e818c + 4ee825d commit 7eb4d6f
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 36 deletions.
3 changes: 0 additions & 3 deletions Reloaded.Memory.SigScan.Tests/ScannerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand Down
81 changes: 62 additions & 19 deletions Reloaded.Memory.Sigscan/Scanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,22 @@ namespace Reloaded.Memory.Sigscan
/// <summary>
/// Provides an implementation of a simple signature scanner sitting ontop of Reloaded.Memory.
/// </summary>
public unsafe class Scanner
public unsafe class Scanner : IDisposable
{
/// <summary>
/// The region of data to be scanned for signatures.
/// </summary>
public byte[] Data => _data.ToArray();
private Memory<byte> _data;

private GCHandle _gcHandle;
private bool _disposedValue;
private GCHandle? _gcHandle;
private byte* _dataPtr;
private int _dataLength;

/// <summary>
/// Creates a signature scanner given the data in which patterns are to be found.
/// </summary>
/// <param name="data">The data to look for signatures inside.</param>
public Scanner(byte[] data)
{
_data = data;
_gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
_dataPtr = (byte*) _gcHandle.AddrOfPinnedObject();
_dataPtr = (byte*)_gcHandle.Value.AddrOfPinnedObject();
_dataLength = data.Length;
}

/// <summary>
Expand All @@ -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;
}

/// <summary>
/// Creates a signature scanner given the data in which patterns are to be found.
/// </summary>
/// <param name="data">The data to look for signatures inside.</param>
/// <param name="length">The length of the data.</param>
public Scanner(byte* data, int length)
{
_dataPtr = data;
_dataLength = length;
}

~Scanner()
{
Dispose(false);
}

/// <summary>
Expand All @@ -67,16 +79,27 @@ public Scanner(Process process, ProcessModule module)
/// <returns>A result indicating an offset (if found) of the pattern.</returns>
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);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="pattern">
/// The compiled pattern to look for inside the given region.
/// </param>
/// <returns>A result indicating an offset (if found) of the pattern.</returns>
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];

Expand Down Expand Up @@ -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.
Expand All @@ -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)
{
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@
namespace Reloaded.Memory.Sigscan.Structs
{
/// <summary>
/// [Internal & Test Use]
/// Represents the pattern to be searched by the scanner.
/// </summary>
public ref struct PatternScanInstructionSet
public ref struct CompiledScanPattern
{
private const string MaskIgnore = "??";

/// <summary>
/// The pattern the instruction set was created from.
/// </summary>
public readonly string Pattern;

/// <summary>
/// The length of the original given pattern.
/// </summary>
public int Length;
public readonly int Length;

/// <summary>
/// Contains the functions that will be executed in order to validate a given block of memory to equal
Expand All @@ -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.
/// </param>
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;

Expand All @@ -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.
Expand Down

0 comments on commit 7eb4d6f

Please sign in to comment.