ntmemoryapi 1.0.0__tar.gz → 1.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-1.0.0 → ntmemoryapi-1.2.0}/PKG-INFO +2 -1
- {ntmemoryapi-1.0.0 → ntmemoryapi-1.2.0}/ntmemoryapi/__init__.py +36 -79
- {ntmemoryapi-1.0.0 → ntmemoryapi-1.2.0}/pyproject.toml +7 -3
- {ntmemoryapi-1.0.0 → ntmemoryapi-1.2.0}/README.md +0 -0
- {ntmemoryapi-1.0.0 → ntmemoryapi-1.2.0}/ntmemoryapi/embed.py +0 -0
- {ntmemoryapi-1.0.0 → ntmemoryapi-1.2.0}/ntmemoryapi/misc.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ntmemoryapi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps
|
|
5
5
|
Author: Xenely
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -9,6 +9,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: psutil (>=7.1.0,<8.0.0)
|
|
12
13
|
Description-Content-Type: text/markdown
|
|
13
14
|
|
|
14
15
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
9
|
import ctypes
|
|
10
|
+
import psutil
|
|
10
11
|
|
|
11
12
|
# Local imports
|
|
12
13
|
from .misc import *
|
|
@@ -83,35 +84,6 @@ class OBJECT_ATTRIBUTES(ctypes.Structure):
|
|
|
83
84
|
]
|
|
84
85
|
|
|
85
86
|
|
|
86
|
-
class PROCESSENTRY32(ctypes.Structure):
|
|
87
|
-
"""Process entry structure for processes enumeration."""
|
|
88
|
-
|
|
89
|
-
_fields_ = [
|
|
90
|
-
("dw_size", ctypes.c_ulong),
|
|
91
|
-
("cnt_usage", ctypes.c_ulong),
|
|
92
|
-
("th32_process_id", ctypes.c_ulong),
|
|
93
|
-
("th32_default_heap_id", ctypes.c_void_p),
|
|
94
|
-
("th32_module_id", ctypes.c_ulong),
|
|
95
|
-
("cnt_threads", ctypes.c_ulong),
|
|
96
|
-
("th32_parent_process_id", ctypes.c_ulong),
|
|
97
|
-
("pc_pri_class_base", ctypes.c_long),
|
|
98
|
-
("dw_flags", ctypes.c_ulong),
|
|
99
|
-
("sz_exe_file", ctypes.c_char * 260)
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
@property
|
|
103
|
-
def pid(self) -> int:
|
|
104
|
-
"""Process ID."""
|
|
105
|
-
|
|
106
|
-
return self.th32_process_id
|
|
107
|
-
|
|
108
|
-
@property
|
|
109
|
-
def name(self) -> str:
|
|
110
|
-
"""Process name."""
|
|
111
|
-
|
|
112
|
-
return self.sz_exe_file.decode()
|
|
113
|
-
|
|
114
|
-
|
|
115
87
|
class MODULEENTRY32(ctypes.Structure):
|
|
116
88
|
"""Module entry structure, describes an entry from a list of the modules belonging to the specified process."""
|
|
117
89
|
|
|
@@ -254,61 +226,43 @@ _nt_protect_virtual_memory = syscall("NtProtectVirtualMemory", result_type=ctype
|
|
|
254
226
|
# ==-------------------------------------------------------------------== #
|
|
255
227
|
# Functions #
|
|
256
228
|
# ==-------------------------------------------------------------------== #
|
|
257
|
-
def list_processes(include_id: bool = True, include_name: bool = True) -> list[dict[str, int | str]]:
|
|
229
|
+
def list_processes(include_id: bool = True, include_name: bool = True, include_username: bool = True, current_username_only: bool = True) -> list[dict[str, int | str]]:
|
|
258
230
|
"""List all of the currently active system processes."""
|
|
259
231
|
|
|
260
232
|
# If `include_id` and `include_name` disabled both
|
|
261
233
|
if not include_id and not include_name:
|
|
262
234
|
raise Exception("Unable to disable ID and name including at once")
|
|
263
235
|
|
|
264
|
-
#
|
|
265
|
-
|
|
266
|
-
process32_next = ctypes.windll.kernel32.Process32Next
|
|
267
|
-
process32_first = ctypes.windll.kernel32.Process32First
|
|
268
|
-
create_tool_help32_snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
|
269
|
-
|
|
270
|
-
# Create snapshot to iterate processes
|
|
271
|
-
if (snapshot := create_tool_help32_snapshot(TH32CS_SNAPPROCESS, 0)) == -1:
|
|
272
|
-
raise Exception("Unable to create snapshot to iterate processes")
|
|
236
|
+
# Current process username
|
|
237
|
+
current_username = psutil.Process().username()
|
|
273
238
|
|
|
274
239
|
# Result list of processes
|
|
275
240
|
processes = list()
|
|
276
241
|
|
|
277
|
-
# Process
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
# Process entry to iterate processes
|
|
281
|
-
process_entry = PROCESSENTRY32()
|
|
282
|
-
process_entry.dw_size = ctypes.sizeof(PROCESSENTRY32)
|
|
242
|
+
# Process iteration
|
|
243
|
+
for process in psutil.process_iter():
|
|
283
244
|
|
|
284
|
-
#
|
|
285
|
-
if
|
|
286
|
-
|
|
245
|
+
# If process started not from current username
|
|
246
|
+
if current_username_only and process.username() != current_username:
|
|
247
|
+
continue
|
|
287
248
|
|
|
288
|
-
#
|
|
289
|
-
|
|
249
|
+
# Save process to list
|
|
250
|
+
process_dict = dict()
|
|
290
251
|
|
|
291
|
-
|
|
292
|
-
|
|
252
|
+
# If process ID including required
|
|
253
|
+
if include_id:
|
|
254
|
+
process_dict |= {"id": process.pid}
|
|
293
255
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
256
|
+
# If process ID including required
|
|
257
|
+
if include_name:
|
|
258
|
+
process_dict |= {"name": process.name()}
|
|
297
259
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
260
|
+
# If process username including required
|
|
261
|
+
if include_username:
|
|
262
|
+
process_dict |= {"username": process.username()}
|
|
301
263
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
# Snapshot is over
|
|
305
|
-
if not process32_next(snapshot, ctypes.byref(process_entry)):
|
|
306
|
-
break
|
|
307
|
-
|
|
308
|
-
finally:
|
|
309
|
-
|
|
310
|
-
# Close snapshot handle
|
|
311
|
-
close_handle(snapshot)
|
|
264
|
+
# Save process information to list
|
|
265
|
+
processes.append(process_dict)
|
|
312
266
|
|
|
313
267
|
return processes
|
|
314
268
|
|
|
@@ -323,17 +277,17 @@ class Process:
|
|
|
323
277
|
# Methods #
|
|
324
278
|
# ==-------------------------------------------------------------------== #
|
|
325
279
|
|
|
326
|
-
def __init__(self, pid_or_name: int | str, access: int = PROCESS_ALL_ACCESS) -> None:
|
|
280
|
+
def __init__(self, pid_or_name: int | str, access: int = PROCESS_ALL_ACCESS, current_username_only: bool = True) -> None:
|
|
327
281
|
"""Initialize instance to manipulate process."""
|
|
328
282
|
|
|
329
283
|
# Open process by it's ID or it's name
|
|
330
284
|
match pid_or_name:
|
|
331
285
|
|
|
332
286
|
case pid if type(pid_or_name) is int:
|
|
333
|
-
self.handle, self.name, self.pid = self.__init_with_pid(pid, access)
|
|
287
|
+
self.handle, self.name, self.pid = self.__init_with_pid(pid, access, current_username_only)
|
|
334
288
|
|
|
335
289
|
case name if type(pid_or_name) is str:
|
|
336
|
-
self.handle, self.name, self.pid = self.__init_with_name(name, access)
|
|
290
|
+
self.handle, self.name, self.pid = self.__init_with_name(name, access, current_username_only)
|
|
337
291
|
|
|
338
292
|
case _:
|
|
339
293
|
raise Exception("Invalid `pid_or_name` argument value, have to be `int` or `str` type")
|
|
@@ -398,7 +352,7 @@ class Process:
|
|
|
398
352
|
|
|
399
353
|
return process_modules
|
|
400
354
|
|
|
401
|
-
def list_memory_regions(self, allowed_states: list[int] = list(), allowed_protects: list[int] = list(), allowed_types: list[int] = list()) -> list[MEMORY_BASIC_INFORMATION]:
|
|
355
|
+
def list_memory_regions(self, allowed_states: list[int] = list(), allowed_protects: list[int] = list(), allowed_types: list[int] = list(), memory_regions_filter: typing.Callable[[MEMORY_BASIC_INFORMATION], bool] | None = None) -> list[MEMORY_BASIC_INFORMATION]:
|
|
402
356
|
"""List all of the aviable process memory regions."""
|
|
403
357
|
|
|
404
358
|
# Result list of memory regions
|
|
@@ -438,6 +392,9 @@ class Process:
|
|
|
438
392
|
if allowed_types and memory_basic_information.type not in allowed_types:
|
|
439
393
|
continue
|
|
440
394
|
|
|
395
|
+
if memory_regions_filter and not memory_regions_filter(memory_basic_information):
|
|
396
|
+
continue
|
|
397
|
+
|
|
441
398
|
# Save memory region information if filter passed
|
|
442
399
|
memory_regions.append(memory_basic_information)
|
|
443
400
|
|
|
@@ -518,7 +475,7 @@ class Process:
|
|
|
518
475
|
|
|
519
476
|
return memory_old_protect.value or 0
|
|
520
477
|
|
|
521
|
-
def pattern_scan(self, pattern: str, allowed_states: list[int] = [MEM_COMMIT], allowed_protects: list[int] = [PAGE_READWRITE], allowed_types: list[int] = list(), start_address: int | None = None, end_address: int | None = None, return_first: int | None = None) -> list[int]:
|
|
478
|
+
def pattern_scan(self, pattern: str, allowed_states: list[int] = [MEM_COMMIT], allowed_protects: list[int] = [PAGE_READWRITE], allowed_types: list[int] = list(), start_address: int | None = None, end_address: int | None = None, return_first: int | None = None, memory_regions_filter: typing.Callable[[MEMORY_BASIC_INFORMATION], bool] | None = None) -> list[int]:
|
|
522
479
|
"""Scan process and return address that validates given pattern hex byte mask, use `??` to wildcard byte, for example - "14 00 00 00 DB FF ?? ?? FF FF 00 00"."""
|
|
523
480
|
|
|
524
481
|
# Validate given pattern
|
|
@@ -538,7 +495,7 @@ class Process:
|
|
|
538
495
|
found_addresses = list()
|
|
539
496
|
|
|
540
497
|
# iterate memory regions and finding addresses at them
|
|
541
|
-
for region in self.list_memory_regions(allowed_states, allowed_protects, allowed_types):
|
|
498
|
+
for region in self.list_memory_regions(allowed_states, allowed_protects, allowed_types, memory_regions_filter):
|
|
542
499
|
|
|
543
500
|
# If required amount of addresses found
|
|
544
501
|
if return_first is not None and return_first <= 0:
|
|
@@ -871,11 +828,11 @@ class Process:
|
|
|
871
828
|
# ==-------------------------------------------------------------------== #
|
|
872
829
|
# Private methods #
|
|
873
830
|
# ==-------------------------------------------------------------------== #
|
|
874
|
-
def __init_with_pid(self, pid: int, access: int, process_name: str | None = None) -> int:
|
|
831
|
+
def __init_with_pid(self, pid: int, access: int, current_username_only: bool, process_name: str | None = None) -> int:
|
|
875
832
|
"""Open process handle by it's ID with desired access."""
|
|
876
833
|
|
|
877
834
|
# Iterate all of the processes if name not defined
|
|
878
|
-
for process in list_processes():
|
|
835
|
+
for process in list_processes(current_username_only):
|
|
879
836
|
|
|
880
837
|
# If process have a reqired name
|
|
881
838
|
if process["id"] == pid:
|
|
@@ -905,14 +862,14 @@ class Process:
|
|
|
905
862
|
|
|
906
863
|
return handle.value, process_name, pid
|
|
907
864
|
|
|
908
|
-
def __init_with_name(self, name: str, access: int) -> int:
|
|
865
|
+
def __init_with_name(self, name: str, access: int, current_username_only: bool) -> int:
|
|
909
866
|
"""Open process hanle by it's name with desired access."""
|
|
910
867
|
|
|
911
868
|
# Iterate all of the processes using snapshot
|
|
912
|
-
for process in list_processes():
|
|
869
|
+
for process in list_processes(current_username_only):
|
|
913
870
|
|
|
914
871
|
# If process have a reqired name
|
|
915
872
|
if process["name"].lower() == name.strip().lower():
|
|
916
|
-
return self.__init_with_pid(process["id"], access, process["name"])
|
|
873
|
+
return self.__init_with_pid(process["id"], access, current_username_only, process["name"])
|
|
917
874
|
|
|
918
875
|
raise Exception("Process with `%s` name not found" % name)
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ntmemoryapi"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
description = "Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Xenely"}
|
|
7
7
|
]
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
|
-
dependencies = [
|
|
10
|
+
dependencies = [
|
|
11
|
+
"psutil (>=7.1.0,<8.0.0)"
|
|
12
|
+
]
|
|
11
13
|
|
|
12
14
|
[build-system]
|
|
13
|
-
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
14
15
|
build-backend = "poetry.core.masonry.api"
|
|
16
|
+
requires = [
|
|
17
|
+
"poetry-core>=2.0.0,<3.0.0"
|
|
18
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|