From edaddebfee95252499dab90420d906f9a18dc77f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Feb 2026 16:21:31 +0000 Subject: [PATCH 1/2] Fix multiple code quality issues and bugs - Wildcard.cs: Add input validation for token length to prevent IndexOutOfRangeException on malformed tokens - AllocationType.cs: Fix MEM_SHARED value conflict (was 0x00001000, now 0x08000000) to avoid collision with MEM_COMMIT - ProcessAccessRights.cs: Fix PROCESS_QUERY_LIMITED_INFORMATION value (was 0x2000, now 0x1000) to avoid collision with PROCESS_SET_CONTEXT - Library.cs: Add handle validation in Open(IntPtr) to reject invalid handles (zero or -1) - Library.cs: Fix misleading error messages ("non-negative" -> "positive") - Library.cs: Fix integer overflow check in Query() with proper bounds - Library.cs: Fix default chunk size from 8196 to 8192 (power of 2) - Library.cs: Add pattern validation in Search() method - Program.cs: Remove unnecessary unsafe keyword https://claude.ai/code/session_01TJUYtWajTHrmWrhTco8sBS --- Examples/PatternScanner/Program.cs | 2 +- MemNet/Enum/AllocationType.cs | 2 +- MemNet/Library.cs | 31 ++++++++++++++++++++++------ MemNet/Native/ProcessAccessRights.cs | 2 +- MemNet/Wildcard.cs | 18 +++++++++++++--- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Examples/PatternScanner/Program.cs b/Examples/PatternScanner/Program.cs index e3be910..fe7e8cb 100644 --- a/Examples/PatternScanner/Program.cs +++ b/Examples/PatternScanner/Program.cs @@ -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; diff --git a/MemNet/Enum/AllocationType.cs b/MemNet/Enum/AllocationType.cs index 2ee87bc..fa7b312 100644 --- a/MemNet/Enum/AllocationType.cs +++ b/MemNet/Enum/AllocationType.cs @@ -25,7 +25,7 @@ public enum AllocationType : uint /// /// Indicates that the pages of the specified region should be protected for access by multiple processes. /// - MEM_SHARED = 0x00001000, // Note: This value overlaps with MEM_COMMIT in some contexts, careful usage is needed. + MEM_SHARED = 0x08000000, /// /// Allocates memory using large page support. diff --git a/MemNet/Library.cs b/MemNet/Library.cs index ddfacd2..c1ce1df 100644 --- a/MemNet/Library.cs +++ b/MemNet/Library.cs @@ -71,6 +71,7 @@ public void Open( /// /// An existing valid handle to the target process. /// Thrown if the process is already open. + /// Thrown if the handle is invalid (zero or -1). public void Open(IntPtr existingHandle) { if (_processHandle != IntPtr.Zero) @@ -78,6 +79,11 @@ public void Open(IntPtr existingHandle) 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); } @@ -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; } @@ -191,9 +204,15 @@ out var returnedBytes /// Thrown if the process is not open. /// Thrown if the pattern is null or empty. /// Thrown if addresses or chunk size are invalid. - public List Search(string pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8196) + public List Search(string pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8192) { - var patternTokens = pattern.Split(' '); + if (string.IsNullOrWhiteSpace(pattern)) + throw new ArgumentNullException(nameof(pattern), "The search pattern cannot be null or empty."); + + var patternTokens = pattern.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (patternTokens.Length == 0) + throw new ArgumentException("The search pattern contains no valid tokens.", nameof(pattern)); + var wildcards = patternTokens.Select(token => new Wildcard(token)).ToArray(); return Search(wildcards, startAddress, endAddress, chunkSize); } @@ -209,7 +228,7 @@ public List Search(string pattern, IntPtr startAddress, IntPtr endAddres /// Thrown if the process is not open. /// Thrown if the pattern is null or empty. /// Thrown if addresses or chunk size are invalid. - public List Search(Wildcard[] pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8196) + public List Search(Wildcard[] pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8192) { if (_processHandle == IntPtr.Zero) throw new InvalidOperationException($"Process with ID {_processId} is not open."); @@ -319,7 +338,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); @@ -403,7 +422,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); diff --git a/MemNet/Native/ProcessAccessRights.cs b/MemNet/Native/ProcessAccessRights.cs index 5ba8223..82e151e 100644 --- a/MemNet/Native/ProcessAccessRights.cs +++ b/MemNet/Native/ProcessAccessRights.cs @@ -25,6 +25,6 @@ public enum ProcessAccessRights : uint 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 } \ No newline at end of file diff --git a/MemNet/Wildcard.cs b/MemNet/Wildcard.cs index e0db5a4..bf2daa4 100644 --- a/MemNet/Wildcard.cs +++ b/MemNet/Wildcard.cs @@ -1,9 +1,21 @@ 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 = token[0] == '?' ? null : Convert.ToInt32(token[0].ToString(), 16); + _lowNibble = token[1] == '?' ? null : Convert.ToInt32(token[1].ToString(), 16); + } public bool Matches(byte b) { From 299f4388da5bba5ddcfeaa408a0d8fa5455a4051 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 8 Feb 2026 03:44:12 +0000 Subject: [PATCH 2/2] Address code review feedback - Library.cs: Fix exception types - ArgumentNullException only for null, ArgumentException for empty/whitespace patterns per .NET guidelines - Program.cs: Replace sizeof(IntPtr) with IntPtr.Size to avoid requiring unsafe context - ProcessAccessRights.cs: Remove PROCESS_GET_CONTEXT and PROCESS_SET_CONTEXT as they are thread rights, not process rights (avoids value collision) - Wildcard.cs: Add explicit hex digit validation with clear error message instead of letting Convert.ToInt32 throw FormatException https://claude.ai/code/session_01TJUYtWajTHrmWrhTco8sBS --- Examples/PatternScanner/Program.cs | 4 ++-- MemNet/Library.cs | 9 +++++---- MemNet/Native/ProcessAccessRights.cs | 2 -- MemNet/Wildcard.cs | 15 +++++++++++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Examples/PatternScanner/Program.cs b/Examples/PatternScanner/Program.cs index fe7e8cb..6dac8bc 100644 --- a/Examples/PatternScanner/Program.cs +++ b/Examples/PatternScanner/Program.cs @@ -25,8 +25,8 @@ public static 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)); diff --git a/MemNet/Library.cs b/MemNet/Library.cs index c1ce1df..cb89a05 100644 --- a/MemNet/Library.cs +++ b/MemNet/Library.cs @@ -202,16 +202,17 @@ out var returnedBytes /// The size of each chunk read from the process memory. /// A list of matching addresses. /// Thrown if the process is not open. - /// Thrown if the pattern is null or empty. + /// Thrown if the pattern is null. + /// Thrown if the pattern is empty or contains no valid tokens. /// Thrown if addresses or chunk size are invalid. public List Search(string pattern, IntPtr startAddress, IntPtr endAddress, int chunkSize = 8192) { - if (string.IsNullOrWhiteSpace(pattern)) - throw new ArgumentNullException(nameof(pattern), "The search pattern cannot be null or empty."); + 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 contains no valid tokens.", nameof(pattern)); + 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); diff --git a/MemNet/Native/ProcessAccessRights.cs b/MemNet/Native/ProcessAccessRights.cs index 82e151e..c8c834c 100644 --- a/MemNet/Native/ProcessAccessRights.cs +++ b/MemNet/Native/ProcessAccessRights.cs @@ -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 = 0x1000, // Introduced in Windows Vista PROCESS_ALL_ACCESS = 0x000F0000 | 0x001FFFFF } \ No newline at end of file diff --git a/MemNet/Wildcard.cs b/MemNet/Wildcard.cs index bf2daa4..8ead039 100644 --- a/MemNet/Wildcard.cs +++ b/MemNet/Wildcard.cs @@ -13,8 +13,19 @@ public Wildcard(string token) if (token.Length != 2) throw new ArgumentException($"Token must be exactly 2 characters, got {token.Length}: '{token}'", nameof(token)); - _highNibble = token[0] == '?' ? null : Convert.ToInt32(token[0].ToString(), 16); - _lowNibble = token[1] == '?' ? null : Convert.ToInt32(token[1].ToString(), 16); + _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)