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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ntmemoryapi
3
- Version: 1.0.0
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
- # Process snapshot functions
265
- close_handle = ctypes.windll.kernel32.CloseHandle
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 enumeration
278
- try:
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
- # Retrieve first process snapshot
285
- if not process32_first(snapshot, ctypes.byref(process_entry)):
286
- raise Exception("Unable to get first process to save to snapshot")
245
+ # If process started not from current username
246
+ if current_username_only and process.username() != current_username:
247
+ continue
287
248
 
288
- # Iterate all of the processes using snapshot
289
- while True:
249
+ # Save process to list
250
+ process_dict = dict()
290
251
 
291
- # Save process to list
292
- process_dict = dict()
252
+ # If process ID including required
253
+ if include_id:
254
+ process_dict |= {"id": process.pid}
293
255
 
294
- # If process ID including required
295
- if include_id:
296
- process_dict |= {"id": process_entry.pid}
256
+ # If process ID including required
257
+ if include_name:
258
+ process_dict |= {"name": process.name()}
297
259
 
298
- # If process ID including required
299
- if include_name:
300
- process_dict |= {"name": process_entry.name}
260
+ # If process username including required
261
+ if include_username:
262
+ process_dict |= {"username": process.username()}
301
263
 
302
- processes.append(process_dict)
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.0.0"
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