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.
- {ntmemoryapi-2.1.3 → ntmemoryapi-2.2.0}/PKG-INFO +2 -2
- {ntmemoryapi-2.1.3 → ntmemoryapi-2.2.0}/pyproject.toml +2 -2
- {ntmemoryapi-2.1.3 → ntmemoryapi-2.2.0}/src/ntmemoryapi/__init__.py +36 -8
- {ntmemoryapi-2.1.3 → ntmemoryapi-2.2.0}/src/ntmemoryapi/errors.py +32 -21
- {ntmemoryapi-2.1.3 → ntmemoryapi-2.2.0}/README.md +0 -0
- {ntmemoryapi-2.1.3 → ntmemoryapi-2.2.0}/src/ntmemoryapi/embed.py +0 -0
- {ntmemoryapi-2.1.3 → ntmemoryapi-2.2.0}/src/ntmemoryapi/misc.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ntmemoryapi
|
|
3
|
-
Version: 2.
|
|
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.
|
|
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
|
-
#
|
|
509
|
-
|
|
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
|
-
|
|
532
|
-
scan_size = min(region_end, end_address) -
|
|
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
|
-
|
|
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
|
-
|
|
560
|
+
pass
|
|
540
561
|
|
|
541
562
|
# Pattern scan region using SIMD KMP algorithm
|
|
542
|
-
self.__kmp.scanAOB(
|
|
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
|
-
#
|
|
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(
|
|
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__(
|
|
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__(
|
|
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
|
|
File without changes
|
|
File without changes
|