ntmemoryapi 2.1.3__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ntmemoryapi
3
- Version: 2.1.3
4
- Summary: Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps
3
+ Version: 2.2.0
4
+ Summary: Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps.
5
5
  Author: Xenely
6
6
  Requires-Dist: psutil>=7.1.3
7
7
  Requires-Python: >=3.10
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "ntmemoryapi"
3
- version = "2.1.3"
4
- description = "Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps"
3
+ version = "2.2.0"
4
+ description = "Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps."
5
5
  authors = [
6
6
  {name = "Xenely"}
7
7
  ]
@@ -505,8 +505,8 @@ class Process:
505
505
 
506
506
  raise ValueError("Invalid pattern: `%s`" % pattern)
507
507
 
508
- # Result list of found addresses
509
- found_addresses = []
508
+ # List of regions to scan
509
+ to_scan_regions = []
510
510
 
511
511
  # iterate memory regions and finding addresses at them
512
512
  for region in self.list_memory_regions(allowed_states, allowed_protects, allowed_types, memory_regions_filter):
@@ -528,18 +528,39 @@ class Process:
528
528
  continue
529
529
 
530
530
  # Scan params
531
- scan_start = max(region_start, start_address) if start_address is not None else region_start
532
- scan_size = min(region_end, end_address) - scan_start if end_address is not None else region_end - scan_start
531
+ scan_adderss = max(region_start, start_address) if start_address is not None else region_start
532
+ scan_size = min(region_end, end_address) - scan_adderss if end_address is not None else region_end - scan_adderss
533
+
534
+ # Save region information to scan later
535
+ to_scan_regions.append((scan_adderss, scan_size))
536
+
537
+ # If no regions to scan presented
538
+ if not to_scan_regions:
539
+ return []
540
+
541
+ # Result list of found addresses
542
+ found_addresses = []
543
+
544
+ # Pre-allocated buffer to hold read memory
545
+ read_memory_buffer = (ctypes.c_byte * max(item[-1] for item in to_scan_regions) + 1)()
546
+
547
+ # Iterate regions to scan read them and find pattern
548
+ for scan_adderss, scan_size in to_scan_regions:
549
+
550
+ # If required amount of addresses found
551
+ if return_first is not None and return_first <= 0:
552
+ break
533
553
 
534
- # Read region as bytes
535
554
  try:
536
- read_region_bytes = self.read_bytes(scan_start, scan_size)
555
+
556
+ # Read bytes into pre-allocated buffer
557
+ self.read_into_buffer(scan_adderss, scan_size, ctypes.addressof(read_memory_buffer))
537
558
 
538
559
  except Exception:
539
- continue
560
+ pass
540
561
 
541
562
  # Pattern scan region using SIMD KMP algorithm
542
- self.__kmp.scanAOB(read_region_bytes, scan_size, pattern.strip().encode(), ctypes.c_uint64(scan_start), ctypes.c_uint64(return_first if return_first is not None else 0), ctypes.byref(scan_result := PatternScanBuffer()))
563
+ self.__kmp.scanAOB(ctypes.addressof(read_memory_buffer), scan_size, pattern.strip().encode(), ctypes.c_uint64(scan_adderss), ctypes.c_uint64(return_first if return_first is not None else 0), ctypes.byref(scan_result := PatternScanBuffer()))
543
564
 
544
565
  # Read result addresses
545
566
  addresses = scan_result.read()
@@ -665,6 +686,13 @@ class Process:
665
686
 
666
687
  return buffer
667
688
 
689
+ def read_into_buffer(self, address: int, size: int, buffer_pointer: int) -> None:
690
+ """Read bytes array of variadic size located at given address into pre-allocated internal buffer by pointer."""
691
+
692
+ # If result failed
693
+ if (result := _nt_read_virtual_memory(self.handle, address, buffer_pointer, size, None)):
694
+ raise MemoryReadError(result, address)
695
+
668
696
  def write_int8(self, address: int, value: int) -> None:
669
697
  """Write 1 byte signed integer value at given address."""
670
698
 
@@ -5,8 +5,11 @@
5
5
  # | Discord: xenely |
6
6
  # +-------------------------------------+
7
7
 
8
+ import typing
9
+
10
+
8
11
  # ==-------------------------------------------------------------------== #
9
- # Classes #
12
+ # Base errors classes #
10
13
  # ==-------------------------------------------------------------------== #
11
14
  class NtError(Exception):
12
15
  """Exception that raises on `nt` functions calls fails."""
@@ -40,6 +43,30 @@ class NtError(Exception):
40
43
  return "%s%08x" % (hex_prefix if hex_prefix is not None else "", self.status)
41
44
 
42
45
 
46
+ class MemoryError(NtError):
47
+ """Exception that raises on virtual memory read fail."""
48
+
49
+ # ==--------------------------------== #
50
+ # Public methods #
51
+ # ==--------------------------------== #
52
+ def __init__(self, status: int, address: int, operation: typing.Literal["read", "write"]):
53
+
54
+ # Save initializer arguments as an attributes
55
+ self.address = address
56
+ self.operation = operation
57
+
58
+ # Call initializer of parrent class
59
+ super().__init__(None, status)
60
+
61
+ def __str__(self) -> str:
62
+ """Overload triggered on `str` cast calls"""
63
+
64
+ return "Unalble to %s memory at `0x%08x` address, `nt` function failed with `%s` status" % (self.operation, self.address, self.get_hex_status())
65
+
66
+
67
+ # ==-------------------------------------------------------------------== #
68
+ # Errors classes #
69
+ # ==-------------------------------------------------------------------== #
43
70
  class OpenProcessError(NtError):
44
71
  """Exception that raises on process open fail."""
45
72
 
@@ -63,7 +90,7 @@ class OpenProcessError(NtError):
63
90
  return "Unable to open process: %s" % self.message
64
91
 
65
92
 
66
- class MemoryReadError(NtError):
93
+ class MemoryReadError(MemoryError):
67
94
  """Exception that raises on virtual memory read fail."""
68
95
 
69
96
  # ==--------------------------------== #
@@ -71,19 +98,11 @@ class MemoryReadError(NtError):
71
98
  # ==--------------------------------== #
72
99
  def __init__(self, status: int, address: int):
73
100
 
74
- # Save initializer arguments as an attributes
75
- self.address = address
76
-
77
101
  # Call initializer of parrent class
78
- super().__init__(None, status)
79
-
80
- def __str__(self) -> str:
81
- """Overload triggered on `str` cast calls"""
102
+ super().__init__(status, address, "read")
82
103
 
83
- return "Unalble to read memory at `0x%08x` address, `nt` function failed with `%s` status" % (self.address, self.get_hex_status())
84
104
 
85
-
86
- class MemoryWriteError(NtError):
105
+ class MemoryWriteError(MemoryError):
87
106
  """Exception that raises on virtual memory read fail."""
88
107
 
89
108
  # ==--------------------------------== #
@@ -91,13 +110,5 @@ class MemoryWriteError(NtError):
91
110
  # ==--------------------------------== #
92
111
  def __init__(self, status: int, address: int):
93
112
 
94
- # Save initializer arguments as an attributes
95
- self.address = address
96
-
97
113
  # Call initializer of parrent class
98
- super().__init__(None, status)
99
-
100
- def __str__(self) -> str:
101
- """Overload triggered on `str` cast calls"""
102
-
103
- return "Unalble to write memory at `0x%08x` address, `nt` function failed with `%s` status" % (self.address, self.get_hex_status())
114
+ super().__init__(status, address, "write")
File without changes