Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Examples/PatternScanner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ static class Program
.WriteTo.Console()
.CreateLogger();

public static unsafe void Main()
public static void Main()
{
var processId = Process.GetProcessesByName("CS2").FirstOrDefault()!.Id;

Expand All @@ -25,8 +25,8 @@ public static unsafe void Main()

IntPtr startAddr = IntPtr.Subtract(localPlayerPtr, 20);

var region = mem.Read(startAddr, 20 + sizeof(IntPtr) + 20);
var ptr = mem.Read(localPlayerPtr, sizeof(IntPtr));
var region = mem.Read(startAddr, 20 + IntPtr.Size + 20);
var ptr = mem.Read(localPlayerPtr, IntPtr.Size);


Log.Information("Region: {Region}", BitConverter.ToString(region));
Expand Down
2 changes: 1 addition & 1 deletion MemNet/Enum/AllocationType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public enum AllocationType : uint
/// <summary>
/// Indicates that the pages of the specified region should be protected for access by multiple processes.
/// </summary>
MEM_SHARED = 0x00001000, // Note: This value overlaps with MEM_COMMIT in some contexts, careful usage is needed.
MEM_SHARED = 0x08000000,

/// <summary>
/// Allocates memory using large page support.
Expand Down
34 changes: 27 additions & 7 deletions MemNet/Library.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,19 @@ public void Open(
/// </summary>
/// <param name="existingHandle">An existing valid handle to the target process.</param>
/// <exception cref="InvalidOperationException">Thrown if the process is already open.</exception>
/// <exception cref="ArgumentException">Thrown if the handle is invalid (zero or -1).</exception>
public void Open(IntPtr existingHandle)
{
if (_processHandle != IntPtr.Zero)
{
throw new InvalidOperationException("Process already open. Close before hijacking another handle.");
}

if (existingHandle == IntPtr.Zero || existingHandle == new IntPtr(-1))
{
throw new ArgumentException("The provided handle is invalid.", nameof(existingHandle));
}

_processHandle = existingHandle;
_logger.Debug("Hijacked handle for process {ProcessId}.", _processId);
}
Expand Down Expand Up @@ -169,7 +175,14 @@ out var returnedBytes
Type = (MemoryType)mbi.Type
};

long nextAddress = mbi.BaseAddress.ToInt64() + mbi.RegionSize;
long baseAddr = mbi.BaseAddress.ToInt64();
long regionSize = mbi.RegionSize.ToInt64();

// Check for overflow before addition
if (baseAddr > long.MaxValue - regionSize)
break;

long nextAddress = baseAddr + regionSize;
if (nextAddress < 0) break;
address = (IntPtr)nextAddress;
}
Expand All @@ -189,11 +202,18 @@ out var returnedBytes
/// <param name="chunkSize">The size of each chunk read from the process memory.</param>
/// <returns>A list of matching addresses.</returns>
/// <exception cref="InvalidOperationException">Thrown if the process is not open.</exception>
/// <exception cref="ArgumentNullException">Thrown if the pattern is null or empty.</exception>
/// <exception cref="ArgumentNullException">Thrown if the pattern is null.</exception>
/// <exception cref="ArgumentException">Thrown if the pattern is empty or contains no valid tokens.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if addresses or chunk size are invalid.</exception>
public List<IntPtr> Search(string pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8196)
public List<IntPtr> Search(string pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8192)
{
var patternTokens = pattern.Split(' ');
if (pattern is null)
throw new ArgumentNullException(nameof(pattern), "The search pattern cannot be null.");

var patternTokens = pattern.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (patternTokens.Length == 0)
throw new ArgumentException("The search pattern is empty or contains only whitespace.", nameof(pattern));

var wildcards = patternTokens.Select(token => new Wildcard(token)).ToArray();
return Search(wildcards, startAddress, endAddress, chunkSize);
}
Expand All @@ -209,7 +229,7 @@ public List<IntPtr> Search(string pattern, IntPtr startAddress, IntPtr endAddres
/// <exception cref="InvalidOperationException">Thrown if the process is not open.</exception>
/// <exception cref="ArgumentNullException">Thrown if the pattern is null or empty.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if addresses or chunk size are invalid.</exception>
public List<IntPtr> Search(Wildcard[] pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8196)
public List<IntPtr> Search(Wildcard[] pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8192)
{
if (_processHandle == IntPtr.Zero)
throw new InvalidOperationException($"Process with ID {_processId} is not open.");
Expand Down Expand Up @@ -319,7 +339,7 @@ public byte[] Read(IntPtr address, int size)
throw new InvalidOperationException($"Process with ID {_processId} is not open.");

if (size <= 0)
throw new ArgumentOutOfRangeException(nameof(size), "Size must be non-negative.");
throw new ArgumentOutOfRangeException(nameof(size), "Size must be positive.");

byte[] buffer = new byte[size];
GCHandle gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
Expand Down Expand Up @@ -403,7 +423,7 @@ public void Write(IntPtr address, byte[] buffer)
throw new InvalidOperationException($"Process with ID {_processId} is not open.");

if (buffer.Length <= 0)
throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer must be non-negative.");
throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer length must be positive.");

GCHandle gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

Expand Down
4 changes: 1 addition & 3 deletions MemNet/Native/ProcessAccessRights.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ public enum ProcessAccessRights : uint
PROCESS_SET_INFORMATION = 0x0200,
PROCESS_QUERY_INFORMATION = 0x0400,
PROCESS_SUSPEND_RESUME = 0x0800,
PROCESS_GET_CONTEXT = 0x1000,
PROCESS_SET_CONTEXT = 0x2000,
PROCESS_QUERY_LIMITED_INFORMATION = 0x2000, // Introduced in Windows Server 2003
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000, // Introduced in Windows Vista
PROCESS_ALL_ACCESS = 0x000F0000 | 0x001FFFFF
}
29 changes: 26 additions & 3 deletions MemNet/Wildcard.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
namespace MemNet;

public readonly struct Wildcard(string token)
public readonly struct Wildcard
{
private readonly int? _highNibble = token[0] == '?' ? null : Convert.ToInt32(token[0].ToString(), 16); // null represents wildcard
private readonly int? _lowNibble = token[1] == '?' ? null : Convert.ToInt32(token[1].ToString(), 16); // null represents wildcard
private readonly int? _highNibble; // null represents wildcard
private readonly int? _lowNibble; // null represents wildcard

public Wildcard(string token)
{
if (string.IsNullOrEmpty(token))
throw new ArgumentException("Token cannot be null or empty.", nameof(token));

if (token.Length != 2)
throw new ArgumentException($"Token must be exactly 2 characters, got {token.Length}: '{token}'", nameof(token));

_highNibble = ParseNibble(token[0], nameof(token));
_lowNibble = ParseNibble(token[1], nameof(token));
}

private static int? ParseNibble(char c, string paramName)
{
if (c == '?')
return null;

if (c is (>= '0' and <= '9') or (>= 'A' and <= 'F') or (>= 'a' and <= 'f'))
return Convert.ToInt32(c.ToString(), 16);

throw new ArgumentException($"Invalid hex character '{c}'. Expected 0-9, A-F, a-f, or '?' for wildcard.", paramName);
}

public bool Matches(byte b)
{
Expand Down