ntmemoryapi 2.2.5__tar.gz → 2.4.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.4.0/PKG-INFO +8 -0
- ntmemoryapi-2.4.0/pyproject.toml +13 -0
- {ntmemoryapi-2.2.5 → ntmemoryapi-2.4.0}/src/ntmemoryapi/__init__.py +189 -222
- ntmemoryapi-2.4.0/src/ntmemoryapi/structs.py +219 -0
- ntmemoryapi-2.2.5/PKG-INFO +0 -9
- ntmemoryapi-2.2.5/pyproject.toml +0 -19
- {ntmemoryapi-2.2.5 → ntmemoryapi-2.4.0}/README.md +0 -0
- {ntmemoryapi-2.2.5 → ntmemoryapi-2.4.0}/src/ntmemoryapi/embed.py +0 -0
- {ntmemoryapi-2.2.5 → ntmemoryapi-2.4.0}/src/ntmemoryapi/errors.py +0 -0
- {ntmemoryapi-2.2.5 → ntmemoryapi-2.4.0}/src/ntmemoryapi/misc.py +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ntmemoryapi"
|
|
3
|
+
version = "2.4.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"psutil>=7.2.2",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[build-system]
|
|
12
|
+
requires = ["uv_build>=0.10.4,<0.11.0"]
|
|
13
|
+
build-backend = "uv_build"
|
|
@@ -6,18 +6,20 @@
|
|
|
6
6
|
# +-------------------------------------+
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
+
import typing
|
|
9
10
|
import ctypes
|
|
10
11
|
import psutil
|
|
11
12
|
|
|
12
13
|
# Local imports
|
|
13
|
-
from .
|
|
14
|
-
from .
|
|
15
|
-
from .
|
|
14
|
+
from . import misc
|
|
15
|
+
from . import embed
|
|
16
|
+
from . import errors
|
|
17
|
+
from . import structs
|
|
16
18
|
|
|
17
19
|
# ==-------------------------------------------------------------------== #
|
|
18
20
|
# Static and global variables, constans #
|
|
19
21
|
# ==-------------------------------------------------------------------== #
|
|
20
|
-
syscall_wrapper = DirectSyscallWrapper()
|
|
22
|
+
syscall_wrapper = misc.DirectSyscallWrapper()
|
|
21
23
|
|
|
22
24
|
PROCESS_ID = 1
|
|
23
25
|
PROCESS_NAME = 2
|
|
@@ -72,126 +74,6 @@ TH32CS_SNAPMODULE = 0x00000008
|
|
|
72
74
|
# ==-------------------------------------------------------------------== #
|
|
73
75
|
# C-structs #
|
|
74
76
|
# ==-------------------------------------------------------------------== #
|
|
75
|
-
class CLIENT_ID(ctypes.Structure):
|
|
76
|
-
"""Client-ID structure that required to open process."""
|
|
77
|
-
|
|
78
|
-
_fields_ = [
|
|
79
|
-
("unique_process", ctypes.c_void_p),
|
|
80
|
-
("unique_thread", ctypes.c_void_p),
|
|
81
|
-
]
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class OBJECT_ATTRIBUTES(ctypes.Structure):
|
|
85
|
-
"""Object attributes structure that can be applied to objects or object handles."""
|
|
86
|
-
|
|
87
|
-
_fields_ = [
|
|
88
|
-
("length", ctypes.c_ulong),
|
|
89
|
-
("root_directory", ctypes.c_void_p),
|
|
90
|
-
("object_name", ctypes.c_void_p),
|
|
91
|
-
("attributes", ctypes.c_ulong),
|
|
92
|
-
("security_descriptor", ctypes.c_void_p),
|
|
93
|
-
("security_quality_of_service", ctypes.c_void_p),
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class MODULEENTRY32(ctypes.Structure):
|
|
98
|
-
"""Module entry structure, describes an entry from a list of the modules belonging to the specified process."""
|
|
99
|
-
|
|
100
|
-
_fields_ = [
|
|
101
|
-
("dw_size", ctypes.c_ulong),
|
|
102
|
-
("module_id", ctypes.c_ulong),
|
|
103
|
-
("process_id", ctypes.c_ulong),
|
|
104
|
-
("glbl_cnt_usage", ctypes.c_ulong),
|
|
105
|
-
("proc_cnt_usage", ctypes.c_ulong),
|
|
106
|
-
("mod_base_addr", ctypes.c_void_p),
|
|
107
|
-
("mod_base_size", ctypes.c_ulong),
|
|
108
|
-
("h_module", ctypes.c_void_p),
|
|
109
|
-
("sz_module", ctypes.c_char * 256),
|
|
110
|
-
("sz_exe_path", ctypes.c_char * 260),
|
|
111
|
-
]
|
|
112
|
-
|
|
113
|
-
@property
|
|
114
|
-
def name(self) -> str:
|
|
115
|
-
"""Process module name."""
|
|
116
|
-
|
|
117
|
-
return self.sz_module.decode()
|
|
118
|
-
|
|
119
|
-
@property
|
|
120
|
-
def base(self) -> int:
|
|
121
|
-
"""Process module base address."""
|
|
122
|
-
|
|
123
|
-
return self.mod_base_addr
|
|
124
|
-
|
|
125
|
-
@property
|
|
126
|
-
def size(self) -> int:
|
|
127
|
-
"""Process module base address."""
|
|
128
|
-
|
|
129
|
-
return self.mod_base_size
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
|
|
133
|
-
"""Memory basic information structure that containg information about a range of pages in the virtual address space of a proces"""
|
|
134
|
-
|
|
135
|
-
_fields_ = [
|
|
136
|
-
("m_base_address", ctypes.c_void_p),
|
|
137
|
-
("m_allocation_base", ctypes.c_void_p),
|
|
138
|
-
("m_allocation_protect", ctypes.c_ulong),
|
|
139
|
-
("m_partition_id", ctypes.c_ushort),
|
|
140
|
-
("m_region_size", ctypes.c_size_t),
|
|
141
|
-
("m_state", ctypes.c_ulong),
|
|
142
|
-
("m_protect", ctypes.c_ulong),
|
|
143
|
-
("m_type", ctypes.c_ulong),
|
|
144
|
-
]
|
|
145
|
-
|
|
146
|
-
@property
|
|
147
|
-
def base_address(self) -> int:
|
|
148
|
-
"""Memory region base address."""
|
|
149
|
-
|
|
150
|
-
return self.m_base_address
|
|
151
|
-
|
|
152
|
-
@property
|
|
153
|
-
def allocation_base(self) -> int:
|
|
154
|
-
"""Memory region allocation base."""
|
|
155
|
-
|
|
156
|
-
return self.m_allocation_base
|
|
157
|
-
|
|
158
|
-
@property
|
|
159
|
-
def allocation_protect(self) -> int:
|
|
160
|
-
"""Memory region allocation protect."""
|
|
161
|
-
|
|
162
|
-
return self.m_allocation_protect
|
|
163
|
-
|
|
164
|
-
@property
|
|
165
|
-
def partition_id(self) -> int:
|
|
166
|
-
"""Memory region partition ID."""
|
|
167
|
-
|
|
168
|
-
return self.m_partition_id
|
|
169
|
-
|
|
170
|
-
@property
|
|
171
|
-
def size(self) -> int:
|
|
172
|
-
"""Memory region size."""
|
|
173
|
-
|
|
174
|
-
return self.m_region_size
|
|
175
|
-
|
|
176
|
-
@property
|
|
177
|
-
def state(self) -> int:
|
|
178
|
-
"""Memory region state."""
|
|
179
|
-
|
|
180
|
-
return self.m_state
|
|
181
|
-
|
|
182
|
-
@property
|
|
183
|
-
def protect(self) -> int:
|
|
184
|
-
"""Memory region protect."""
|
|
185
|
-
|
|
186
|
-
return self.m_protect
|
|
187
|
-
|
|
188
|
-
@property
|
|
189
|
-
def type(self) -> int:
|
|
190
|
-
"""Memory region type."""
|
|
191
|
-
|
|
192
|
-
return self.m_type
|
|
193
|
-
|
|
194
|
-
|
|
195
77
|
class PatternScanBuffer(ctypes.Structure):
|
|
196
78
|
"""Structure that containing pointer to array received from SIMD KMP after call of `scanAOB` function."""
|
|
197
79
|
|
|
@@ -219,10 +101,10 @@ class PatternScanBuffer(ctypes.Structure):
|
|
|
219
101
|
# ==-------------------------------------------------------------------== #
|
|
220
102
|
# Syscalls #
|
|
221
103
|
# ==-------------------------------------------------------------------== #
|
|
222
|
-
syscall_wrapper = DirectSyscallWrapper()
|
|
104
|
+
syscall_wrapper = misc.DirectSyscallWrapper()
|
|
223
105
|
|
|
224
106
|
_nt_close = syscall_wrapper.wrap("NtClose", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p])
|
|
225
|
-
_nt_open_process = syscall_wrapper.wrap("NtOpenProcess", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(OBJECT_ATTRIBUTES), ctypes.POINTER(CLIENT_ID)])
|
|
107
|
+
_nt_open_process = syscall_wrapper.wrap("NtOpenProcess", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(structs.OBJECT_ATTRIBUTES), ctypes.POINTER(structs.CLIENT_ID)])
|
|
226
108
|
|
|
227
109
|
_nt_read_virtual_memory = syscall_wrapper.wrap("NtReadVirtualMemory", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong)])
|
|
228
110
|
_nt_write_virtual_memory = syscall_wrapper.wrap("NtWriteVirtualMemory", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong)])
|
|
@@ -291,20 +173,20 @@ class Process:
|
|
|
291
173
|
# ==--------------------------------== #
|
|
292
174
|
# Public methods #
|
|
293
175
|
# ==--------------------------------== #
|
|
294
|
-
def __init__(self, name_or_pid: str | int,
|
|
176
|
+
def __init__(self, name_or_pid: str | int, access_flags: int = PROCESS_ALL_ACCESS) -> None:
|
|
295
177
|
"""Initialize instance to manipulate process."""
|
|
296
178
|
|
|
297
179
|
# Open process by it's ID or it's name
|
|
298
180
|
match name_or_pid:
|
|
299
181
|
|
|
300
182
|
case str():
|
|
301
|
-
self.handle, self.pid, self.name = self.__init_with_name(name_or_pid,
|
|
183
|
+
self.handle, self.pid, self.name, self.access_flags = *self.__init_with_name(name_or_pid, access_flags), access_flags
|
|
302
184
|
|
|
303
185
|
case int():
|
|
304
|
-
self.handle, self.pid, self.name = self.__init_with_pid(name_or_pid,
|
|
186
|
+
self.handle, self.pid, self.name, self.access_flags = *self.__init_with_pid(name_or_pid, access_flags), access_flags
|
|
305
187
|
|
|
306
188
|
case _:
|
|
307
|
-
raise OpenProcessError("Invalid `name_or_pid` argument value, have to be `int` or `str` type")
|
|
189
|
+
raise errors.OpenProcessError("Invalid `name_or_pid` argument value, have to be `int` or `str` type")
|
|
308
190
|
|
|
309
191
|
# Try create file at temp directory to load SIMD KMP .dll (Module to blazingly fast pattern scaning)
|
|
310
192
|
try:
|
|
@@ -319,54 +201,118 @@ class Process:
|
|
|
319
201
|
# Load library
|
|
320
202
|
self.__kmp = ctypes.WinDLL(appdata + "\\simdkmp.dll")
|
|
321
203
|
|
|
322
|
-
def list_modules(self) -> list[MODULEENTRY32]:
|
|
204
|
+
def list_modules(self, method: typing.Literal["pebwalk", "snapshot"] = "pebwalk") -> list[structs.MODULEENTRY32]:
|
|
323
205
|
"""List all of the modules loaded to process."""
|
|
324
206
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
module32_next = ctypes.windll.kernel32.Module32Next
|
|
328
|
-
module32_first = ctypes.windll.kernel32.Module32First
|
|
329
|
-
create_tool_help32_snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
|
207
|
+
def list_modules_peb() -> list[structs.MODULEENTRY32]:
|
|
208
|
+
"""Innter function to list process modules using syscall wraps and manual PEB struct walking."""
|
|
330
209
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
210
|
+
# If privileges are insufficient to list process modules
|
|
211
|
+
if not self.access_flags & PROCESS_VM_READ:
|
|
212
|
+
raise ctypes.WinError(descr="Unable to list process modules, insufficient privilages")
|
|
334
213
|
|
|
335
|
-
|
|
336
|
-
|
|
214
|
+
# If process is not 64-bit
|
|
215
|
+
if not self.is_64bit():
|
|
216
|
+
raise RuntimeError("Unable to list modules for not 64 bit process using `%s` method, use another one method instead")
|
|
337
217
|
|
|
338
|
-
|
|
339
|
-
|
|
218
|
+
# If process PEB retrieve failed
|
|
219
|
+
if not (process_peb := self.get_information().peb):
|
|
220
|
+
raise RuntimeError("Process `%s` PEB address is null-value" % self.name)
|
|
340
221
|
|
|
341
|
-
#
|
|
342
|
-
|
|
343
|
-
modules_entry.dw_size = ctypes.sizeof(MODULEENTRY32)
|
|
222
|
+
# Result list of process modules
|
|
223
|
+
process_modules = []
|
|
344
224
|
|
|
345
|
-
#
|
|
346
|
-
|
|
347
|
-
raise ctypes.WinError(descr="Unable to get first proces module to save to snapshot for `%s` PID" % self.pid)
|
|
225
|
+
# Get PEB_LDR_DATA by fixed offset
|
|
226
|
+
process_peb_ldr = self.read_uint64(process_peb + 0x18)
|
|
348
227
|
|
|
349
|
-
#
|
|
350
|
-
|
|
228
|
+
# Get pointer to linked list head to collect all loaded modules information
|
|
229
|
+
process_peb_modules_ll = self.read_uint64(list_head := process_peb_ldr + 0x20)
|
|
351
230
|
|
|
352
|
-
|
|
353
|
-
|
|
231
|
+
# Get node from modules linked list
|
|
232
|
+
modules_ll_node = self.read_uint64(process_peb_modules_ll)
|
|
354
233
|
|
|
355
|
-
|
|
356
|
-
|
|
234
|
+
# Iterator through all of the modules
|
|
235
|
+
while modules_ll_node and modules_ll_node != list_head:
|
|
357
236
|
|
|
358
|
-
#
|
|
359
|
-
|
|
360
|
-
|
|
237
|
+
# Read module information
|
|
238
|
+
module = self.read_into_buffer(modules_ll_node, structs.LDR_DATA_TABLE_ENTRY())
|
|
239
|
+
|
|
240
|
+
# Get module on disk path
|
|
241
|
+
module_name = module.read_name(self)
|
|
242
|
+
|
|
243
|
+
# Save process module information to list
|
|
244
|
+
process_modules.append(structs.MODULEENTRY32(
|
|
245
|
+
mod_base_addr=module.dll_base,
|
|
246
|
+
sz_exe_path=module_name.encode(),
|
|
247
|
+
sz_module=module_name.split("\\")[-1].encode(),
|
|
248
|
+
))
|
|
249
|
+
|
|
250
|
+
# Update node pointer
|
|
251
|
+
modules_ll_node = module.flink
|
|
252
|
+
|
|
253
|
+
return process_modules
|
|
254
|
+
|
|
255
|
+
def list_modules_snapshot() -> list[structs.MODULEENTRY32]:
|
|
256
|
+
"""Inner function to list process modules using standart WinAPI methods."""
|
|
257
|
+
|
|
258
|
+
# Result list of process modules
|
|
259
|
+
process_modules = []
|
|
260
|
+
|
|
261
|
+
# Modules snapshot functions
|
|
262
|
+
close_handle = ctypes.windll.kernel32.CloseHandle
|
|
263
|
+
module32_next = ctypes.windll.kernel32.Module32Next
|
|
264
|
+
module32_first = ctypes.windll.kernel32.Module32First
|
|
265
|
+
create_tool_help32_snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
|
266
|
+
|
|
267
|
+
# Create snapshot to iterate process modules
|
|
268
|
+
if (snapshot := create_tool_help32_snapshot(TH32CS_SNAPMODULE, self.pid)) == -1:
|
|
269
|
+
raise ctypes.WinError(descr="Unable to create snapshot for `%s` PID" % self.pid)
|
|
270
|
+
|
|
271
|
+
# Process modules enumeration
|
|
272
|
+
try:
|
|
273
|
+
|
|
274
|
+
# Modules entry to iterate process modules
|
|
275
|
+
modules_entry = structs.MODULEENTRY32()
|
|
276
|
+
modules_entry.dw_size = ctypes.sizeof(structs.MODULEENTRY32)
|
|
277
|
+
|
|
278
|
+
# Retrieve first process module snapshot
|
|
279
|
+
if not module32_first(snapshot, ctypes.byref(modules_entry)):
|
|
280
|
+
raise ctypes.WinError(descr="Unable to get first proces module to save to snapshot for `%s` PID" % self.pid)
|
|
281
|
+
|
|
282
|
+
# Iterate all of the process modules using snapshot
|
|
283
|
+
while True:
|
|
361
284
|
|
|
362
|
-
|
|
285
|
+
# Create copy of module entry
|
|
286
|
+
ctypes.memmove(ctypes.byref(module_entry_copy := structs.MODULEENTRY32()), ctypes.byref(modules_entry), ctypes.sizeof(structs.MODULEENTRY32))
|
|
363
287
|
|
|
364
|
-
|
|
365
|
-
|
|
288
|
+
# Save process module information to list
|
|
289
|
+
process_modules.append(module_entry_copy)
|
|
366
290
|
|
|
367
|
-
|
|
291
|
+
# snapshot is over
|
|
292
|
+
if not module32_next(snapshot, ctypes.byref(modules_entry)):
|
|
293
|
+
break
|
|
368
294
|
|
|
369
|
-
|
|
295
|
+
finally:
|
|
296
|
+
|
|
297
|
+
# Close snapshot handle
|
|
298
|
+
close_handle(snapshot)
|
|
299
|
+
|
|
300
|
+
return process_modules
|
|
301
|
+
|
|
302
|
+
# If method is not allowed
|
|
303
|
+
if method.lower() not in [item.lower() for item in allowed_methods] if (allowed_methods := typing.get_args(self.list_modules.__annotations__["method"])) else True:
|
|
304
|
+
raise ValueError("Method literal is invalid, expected one of: `%s`" % ", ".join(allowed_methods))
|
|
305
|
+
|
|
306
|
+
# List modules with selected find method
|
|
307
|
+
match method:
|
|
308
|
+
|
|
309
|
+
case "pebwalk":
|
|
310
|
+
return list_modules_peb()
|
|
311
|
+
|
|
312
|
+
case "snapshot":
|
|
313
|
+
return list_modules_snapshot()
|
|
314
|
+
|
|
315
|
+
def list_memory_regions(self, allowed_states: list[int] = [], allowed_protects: list[int] = [], allowed_types: list[int] = [], memory_regions_filter: typing.Callable[[structs.MEMORY_BASIC_INFORMATION], bool] | None = None) -> list[structs.MEMORY_BASIC_INFORMATION]:
|
|
370
316
|
"""List all of the aviable process memory regions."""
|
|
371
317
|
|
|
372
318
|
# Result list of memory regions
|
|
@@ -377,7 +323,7 @@ class Process:
|
|
|
377
323
|
while True:
|
|
378
324
|
|
|
379
325
|
# Prepare arguments
|
|
380
|
-
memory_basic_information = MEMORY_BASIC_INFORMATION()
|
|
326
|
+
memory_basic_information = structs.MEMORY_BASIC_INFORMATION()
|
|
381
327
|
|
|
382
328
|
# Try to get memory region information using it's address
|
|
383
329
|
if (result := _nt_virtual_query_memory(self.handle, current_address, 0, ctypes.byref(memory_basic_information), ctypes.sizeof(memory_basic_information), None)):
|
|
@@ -387,7 +333,7 @@ class Process:
|
|
|
387
333
|
break
|
|
388
334
|
|
|
389
335
|
else:
|
|
390
|
-
raise NtError("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
336
|
+
raise errors.NtError("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
391
337
|
|
|
392
338
|
# Move to next memory region
|
|
393
339
|
if (next_address := current_address + memory_basic_information.size) <= current_address:
|
|
@@ -414,11 +360,35 @@ class Process:
|
|
|
414
360
|
|
|
415
361
|
return memory_regions
|
|
416
362
|
|
|
417
|
-
def
|
|
363
|
+
def get_information(self) -> structs.PROCESS_BASIC_INFORMATION_64:
|
|
364
|
+
"""Gets process information."""
|
|
365
|
+
|
|
366
|
+
# If privileges are insufficient to query proess information
|
|
367
|
+
if not self.access_flags & PROCESS_QUERY_INFORMATION:
|
|
368
|
+
raise ctypes.WinError(descr="Unable to query process information, insufficient privilages")
|
|
369
|
+
|
|
370
|
+
# If process is not 64-bit
|
|
371
|
+
if not self.is_64bit():
|
|
372
|
+
raise RuntimeError("Unable to get process information for not 64 bit process using, method is not yet implemented")
|
|
373
|
+
|
|
374
|
+
# Prepare arguments
|
|
375
|
+
process_basic_information = structs.PROCESS_BASIC_INFORMATION_64()
|
|
376
|
+
|
|
377
|
+
# Try to get process information
|
|
378
|
+
if (result := _nt_query_information_process(self.handle, 0, ctypes.byref(process_basic_information), ctypes.sizeof(process_basic_information), None)):
|
|
379
|
+
raise errors.NtError("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
380
|
+
|
|
381
|
+
return process_basic_information
|
|
382
|
+
|
|
383
|
+
def get_module(self, name: str, method: typing.Literal["pebwalk", "snapshot"] = "pebwalk") -> structs.MODULEENTRY32:
|
|
418
384
|
"""Get process module information."""
|
|
419
385
|
|
|
386
|
+
# If method is not allowed
|
|
387
|
+
if method.lower() not in [item.lower() for item in allowed_methods] if (allowed_methods := typing.get_args(self.list_modules.__annotations__["method"])) else True:
|
|
388
|
+
raise ValueError("Method literal is invalid, expected one of: `%s`" % ", ".join(allowed_methods))
|
|
389
|
+
|
|
420
390
|
# Process modules enumeration
|
|
421
|
-
for module in self.list_modules():
|
|
391
|
+
for module in self.list_modules(method):
|
|
422
392
|
|
|
423
393
|
# If module have a required name
|
|
424
394
|
if module.name.lower() == name.strip().lower():
|
|
@@ -426,15 +396,15 @@ class Process:
|
|
|
426
396
|
|
|
427
397
|
raise ValueError("Module with `%s` name not found" % name)
|
|
428
398
|
|
|
429
|
-
def get_memory_region(self, address: int) -> MEMORY_BASIC_INFORMATION:
|
|
399
|
+
def get_memory_region(self, address: int) -> structs.MEMORY_BASIC_INFORMATION:
|
|
430
400
|
"""Get memory region information located at given address."""
|
|
431
401
|
|
|
432
402
|
# Prepare arguments
|
|
433
|
-
memory_basic_information = MEMORY_BASIC_INFORMATION()
|
|
403
|
+
memory_basic_information = structs.MEMORY_BASIC_INFORMATION()
|
|
434
404
|
|
|
435
405
|
# Try to get memory region information using it's address
|
|
436
406
|
if (result := _nt_virtual_query_memory(self.handle, address, 0, ctypes.byref(memory_basic_information), ctypes.sizeof(memory_basic_information), None)):
|
|
437
|
-
raise NtError("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
407
|
+
raise errors.NtError("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
438
408
|
|
|
439
409
|
return memory_basic_information
|
|
440
410
|
|
|
@@ -445,8 +415,8 @@ class Process:
|
|
|
445
415
|
wow64_info = ctypes.c_void_p()
|
|
446
416
|
|
|
447
417
|
# Try to query process information
|
|
448
|
-
if (result := _nt_query_information_process(self.handle,
|
|
449
|
-
raise NtError("NtQueryInformationProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
418
|
+
if (result := _nt_query_information_process(self.handle, 0x1A, ctypes.byref(wow64_info), ctypes.sizeof(wow64_info), ctypes.byref(ctypes.c_ulong()))):
|
|
419
|
+
raise errors.NtError("NtQueryInformationProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
450
420
|
|
|
451
421
|
return wow64_info.value is None
|
|
452
422
|
|
|
@@ -459,7 +429,7 @@ class Process:
|
|
|
459
429
|
|
|
460
430
|
# Try to allocate process memory
|
|
461
431
|
if (result := _nt_allocate_virtual_memory(self.handle, ctypes.byref(allocation_address), 0, ctypes.byref(allocation_size), memory_type, memory_protect)):
|
|
462
|
-
raise NtError("NtAllocateVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
432
|
+
raise errors.NtError("NtAllocateVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
463
433
|
|
|
464
434
|
return allocation_address.value or 0, allocation_size.value or 0
|
|
465
435
|
|
|
@@ -472,7 +442,7 @@ class Process:
|
|
|
472
442
|
|
|
473
443
|
# Try to deallocate process memory
|
|
474
444
|
if (result := _nt_free_virtual_memory(self.handle, ctypes.byref(allocation_address), ctypes.byref(allocation_size), memory_type)):
|
|
475
|
-
raise NtError("NtFreeVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
445
|
+
raise errors.NtError("NtFreeVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
476
446
|
|
|
477
447
|
return allocation_size.value or 0
|
|
478
448
|
|
|
@@ -485,11 +455,11 @@ class Process:
|
|
|
485
455
|
|
|
486
456
|
# Try to change memory protection
|
|
487
457
|
if (result := _nt_protect_virtual_memory(self.handle, ctypes.byref(protect_address), ctypes.byref(protect_size), memory_protect, ctypes.byref(memory_old_protect := ctypes.c_ulong()))):
|
|
488
|
-
raise NtError("NtProtectVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
458
|
+
raise errors.NtError("NtProtectVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
489
459
|
|
|
490
460
|
return memory_old_protect.value or 0
|
|
491
461
|
|
|
492
|
-
def pattern_scan(self, pattern: str, allowed_states: list[int] = [MEM_COMMIT], allowed_protects: list[int] = [PAGE_READWRITE], allowed_types: list[int] = [], 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, finalize_regions_callback: typing.Callable[[list[MEMORY_BASIC_INFORMATION]], None] | None = None) -> list[int]:
|
|
462
|
+
def pattern_scan(self, pattern: str, allowed_states: list[int] = [MEM_COMMIT], allowed_protects: list[int] = [PAGE_READWRITE], allowed_types: list[int] = [], start_address: int | None = None, end_address: int | None = None, return_first: int | None = None, memory_regions_filter: typing.Callable[[structs.MEMORY_BASIC_INFORMATION], bool] | None = None, finalize_regions_callback: typing.Callable[[list[structs.MEMORY_BASIC_INFORMATION]], None] | None = None, buffered: bool = True) -> list[int]:
|
|
493
463
|
"""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"."""
|
|
494
464
|
|
|
495
465
|
# Validate given pattern
|
|
@@ -554,7 +524,7 @@ class Process:
|
|
|
554
524
|
try:
|
|
555
525
|
|
|
556
526
|
# Read bytes into pre-allocated buffer
|
|
557
|
-
self.read_into_buffer(region.base_address, region.size
|
|
527
|
+
self.read_into_buffer(region.base_address, read_memory_buffer, region.size)
|
|
558
528
|
|
|
559
529
|
except Exception:
|
|
560
530
|
continue
|
|
@@ -583,7 +553,7 @@ class Process:
|
|
|
583
553
|
|
|
584
554
|
# If result failed
|
|
585
555
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8()), ctypes.sizeof(buffer), None)):
|
|
586
|
-
raise MemoryReadError(result, address)
|
|
556
|
+
raise errors.MemoryReadError(result, address)
|
|
587
557
|
|
|
588
558
|
return buffer.value
|
|
589
559
|
|
|
@@ -592,7 +562,7 @@ class Process:
|
|
|
592
562
|
|
|
593
563
|
# If result failed
|
|
594
564
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_int16)() if big_endian else ctypes.c_int16()), ctypes.sizeof(buffer), None)):
|
|
595
|
-
raise MemoryReadError(result, address)
|
|
565
|
+
raise errors.MemoryReadError(result, address)
|
|
596
566
|
|
|
597
567
|
return buffer.value
|
|
598
568
|
|
|
@@ -601,7 +571,7 @@ class Process:
|
|
|
601
571
|
|
|
602
572
|
# If result failed
|
|
603
573
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_int32)() if big_endian else ctypes.c_int32()), ctypes.sizeof(buffer), None)):
|
|
604
|
-
raise MemoryReadError(result, address)
|
|
574
|
+
raise errors.MemoryReadError(result, address)
|
|
605
575
|
|
|
606
576
|
return buffer.value
|
|
607
577
|
|
|
@@ -610,7 +580,7 @@ class Process:
|
|
|
610
580
|
|
|
611
581
|
# If result failed
|
|
612
582
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_int64)() if big_endian else ctypes.c_int64()), ctypes.sizeof(buffer), None)):
|
|
613
|
-
raise MemoryReadError(result, address)
|
|
583
|
+
raise errors.MemoryReadError(result, address)
|
|
614
584
|
|
|
615
585
|
return buffer.value
|
|
616
586
|
|
|
@@ -619,7 +589,7 @@ class Process:
|
|
|
619
589
|
|
|
620
590
|
# If result failed
|
|
621
591
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8()), ctypes.sizeof(buffer), None)):
|
|
622
|
-
raise MemoryReadError(result, address)
|
|
592
|
+
raise errors.MemoryReadError(result, address)
|
|
623
593
|
|
|
624
594
|
return buffer.value
|
|
625
595
|
|
|
@@ -628,7 +598,7 @@ class Process:
|
|
|
628
598
|
|
|
629
599
|
# If result failed
|
|
630
600
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_uint16)() if big_endian else ctypes.c_uint16()), ctypes.sizeof(buffer), None)):
|
|
631
|
-
raise MemoryReadError(result, address)
|
|
601
|
+
raise errors.MemoryReadError(result, address)
|
|
632
602
|
|
|
633
603
|
return buffer.value
|
|
634
604
|
|
|
@@ -637,7 +607,7 @@ class Process:
|
|
|
637
607
|
|
|
638
608
|
# If result failed
|
|
639
609
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_uint32)() if big_endian else ctypes.c_uint32()), ctypes.sizeof(buffer), None)):
|
|
640
|
-
raise MemoryReadError(result, address)
|
|
610
|
+
raise errors.MemoryReadError(result, address)
|
|
641
611
|
|
|
642
612
|
return buffer.value
|
|
643
613
|
|
|
@@ -646,7 +616,7 @@ class Process:
|
|
|
646
616
|
|
|
647
617
|
# If result failed
|
|
648
618
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_uint64)() if big_endian else ctypes.c_uint64()), ctypes.sizeof(buffer), None)):
|
|
649
|
-
raise MemoryReadError(result, address)
|
|
619
|
+
raise errors.MemoryReadError(result, address)
|
|
650
620
|
|
|
651
621
|
return buffer.value
|
|
652
622
|
|
|
@@ -655,7 +625,7 @@ class Process:
|
|
|
655
625
|
|
|
656
626
|
# If result failed
|
|
657
627
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_float)() if big_endian else ctypes.c_float()), ctypes.sizeof(buffer), None)):
|
|
658
|
-
raise MemoryReadError(result, address)
|
|
628
|
+
raise errors.MemoryReadError(result, address)
|
|
659
629
|
|
|
660
630
|
return buffer.value
|
|
661
631
|
|
|
@@ -664,7 +634,7 @@ class Process:
|
|
|
664
634
|
|
|
665
635
|
# If result failed
|
|
666
636
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_double)() if big_endian else ctypes.c_double()), ctypes.sizeof(buffer), None)):
|
|
667
|
-
raise MemoryReadError(result, address)
|
|
637
|
+
raise errors.MemoryReadError(result, address)
|
|
668
638
|
|
|
669
639
|
return buffer.value
|
|
670
640
|
|
|
@@ -673,109 +643,106 @@ class Process:
|
|
|
673
643
|
|
|
674
644
|
# If result failed
|
|
675
645
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_int8 * size)() if big_endian else (ctypes.c_int8 * size)()), ctypes.sizeof(buffer), None)):
|
|
676
|
-
raise MemoryReadError(result, address)
|
|
646
|
+
raise errors.MemoryReadError(result, address)
|
|
677
647
|
|
|
678
648
|
return bytes(buffer)
|
|
679
649
|
|
|
680
|
-
def
|
|
681
|
-
"""Read
|
|
650
|
+
def read_into_buffer(self, address: int, buffer: typing.Any, read_bytes_size: int | None = None) -> typing.Any:
|
|
651
|
+
"""Read value located at given address into buffer. Buffer have to be able passed at `ctypes.byref` and `ctypes.sizeof`. Read bytes size can be changed by passing `read_bytes_size` argument."""
|
|
652
|
+
|
|
653
|
+
# If read size is invalid
|
|
654
|
+
if read_bytes_size is not None and read_bytes_size <= 0:
|
|
655
|
+
raise ValueError("Unable to read value into buffer due the invalid size: `%s`" % read_bytes_size)
|
|
682
656
|
|
|
683
657
|
# If result failed
|
|
684
|
-
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
685
|
-
raise MemoryReadError(result, address)
|
|
658
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer) if read_bytes_size is None else read_bytes_size, None)):
|
|
659
|
+
raise errors.MemoryReadError(result, address)
|
|
686
660
|
|
|
687
661
|
return buffer
|
|
688
662
|
|
|
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
|
-
|
|
696
663
|
def write_int8(self, address: int, value: int) -> None:
|
|
697
664
|
"""Write 1 byte signed integer value at given address."""
|
|
698
665
|
|
|
699
666
|
# If result failed
|
|
700
667
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8(value)), ctypes.sizeof(buffer), None)):
|
|
701
|
-
raise MemoryWriteError(result, address)
|
|
668
|
+
raise errors.MemoryWriteError(result, address)
|
|
702
669
|
|
|
703
670
|
def write_int16(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
704
671
|
"""Write 2 byte signed integer value at given address."""
|
|
705
672
|
|
|
706
673
|
# If result failed
|
|
707
674
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_int16(value)) if big_endian else ctypes.c_int16(value)), ctypes.sizeof(buffer), None)):
|
|
708
|
-
raise MemoryWriteError(result, address)
|
|
675
|
+
raise errors.MemoryWriteError(result, address)
|
|
709
676
|
|
|
710
677
|
def write_int32(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
711
678
|
"""Write 4 byte signed integer value at given address."""
|
|
712
679
|
|
|
713
680
|
# If result failed
|
|
714
681
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_int32(value)) if big_endian else ctypes.c_int32(value)), ctypes.sizeof(buffer), None)):
|
|
715
|
-
raise MemoryWriteError(result, address)
|
|
682
|
+
raise errors.MemoryWriteError(result, address)
|
|
716
683
|
|
|
717
684
|
def write_int64(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
718
685
|
"""Write 8 byte signed integer value at given address."""
|
|
719
686
|
|
|
720
687
|
# If result failed
|
|
721
688
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_int64(value)) if big_endian else ctypes.c_int64(value)), ctypes.sizeof(buffer), None)):
|
|
722
|
-
raise MemoryWriteError(result, address)
|
|
689
|
+
raise errors.MemoryWriteError(result, address)
|
|
723
690
|
|
|
724
691
|
def write_uint8(self, address: int, value: int) -> None:
|
|
725
692
|
"""Write 1 byte unsigned integer value at given address."""
|
|
726
693
|
|
|
727
694
|
# If result failed
|
|
728
695
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8(value)), ctypes.sizeof(buffer), None)):
|
|
729
|
-
raise MemoryWriteError(result, address)
|
|
696
|
+
raise errors.MemoryWriteError(result, address)
|
|
730
697
|
|
|
731
698
|
def write_uint16(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
732
699
|
"""Write 2 byte unsigned integer value at given address."""
|
|
733
700
|
|
|
734
701
|
# If result failed
|
|
735
702
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_uint16(value)) if big_endian else ctypes.c_uint16(value)), ctypes.sizeof(buffer), None)):
|
|
736
|
-
raise MemoryWriteError(result, address)
|
|
703
|
+
raise errors.MemoryWriteError(result, address)
|
|
737
704
|
|
|
738
705
|
def write_uint32(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
739
706
|
"""Write 4 byte unsigned integer value at given address."""
|
|
740
707
|
|
|
741
708
|
# If result failed
|
|
742
709
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_uint32(value)) if big_endian else ctypes.c_uint32(value)), ctypes.sizeof(buffer), None)):
|
|
743
|
-
raise MemoryWriteError("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
710
|
+
raise errors.MemoryWriteError("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
744
711
|
|
|
745
712
|
def write_uint64(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
746
713
|
"""Write 8 byte unsigned integer value at given address."""
|
|
747
714
|
|
|
748
715
|
# If result failed
|
|
749
716
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_uint64(value)) if big_endian else ctypes.c_uint64(value)), ctypes.sizeof(buffer), None)):
|
|
750
|
-
raise MemoryWriteError(result, address)
|
|
717
|
+
raise errors.MemoryWriteError(result, address)
|
|
751
718
|
|
|
752
719
|
def write_float32(self, address: int, value: float | int, big_endian: bool = False) -> float:
|
|
753
720
|
"""Write 4 byte floating-point digit value at given address."""
|
|
754
721
|
|
|
755
722
|
# If result failed
|
|
756
723
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_float(value)) if big_endian else ctypes.c_float(value)), ctypes.sizeof(buffer), None)):
|
|
757
|
-
raise MemoryWriteError(result, address)
|
|
724
|
+
raise errors.MemoryWriteError(result, address)
|
|
758
725
|
|
|
759
726
|
def write_float64(self, address: int, value: float | int, big_endian: bool = False) -> float:
|
|
760
727
|
"""Write 8 byte floating-point digit value at given address."""
|
|
761
728
|
|
|
762
729
|
# If result failed
|
|
763
730
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := _get_be_buffer(ctypes.c_double(value)) if big_endian else ctypes.c_double(value)), ctypes.sizeof(buffer), None)):
|
|
764
|
-
raise MemoryWriteError(result, address)
|
|
731
|
+
raise errors.MemoryWriteError(result, address)
|
|
765
732
|
|
|
766
733
|
def write_bytes(self, address: int, value: bytes, big_endian: bool = False) -> None:
|
|
767
734
|
"""Write bytes array of variadic size at given address."""
|
|
768
735
|
|
|
769
736
|
# If result failed
|
|
770
737
|
if (result := _nt_write_virtual_memory(self.handle, address, value[::-1] if big_endian else value, len(value), None)):
|
|
771
|
-
raise MemoryWriteError(result, address)
|
|
738
|
+
raise errors.MemoryWriteError(result, address)
|
|
772
739
|
|
|
773
740
|
def write_buffer(self, address: int, buffer: typing.Any) -> None:
|
|
774
|
-
"""Write size of buffer byte value at given address. Buffer have to be able passed at `ctypes.byref` and `
|
|
741
|
+
"""Write size of buffer byte value at given address. Buffer have to be able passed at `ctypes.byref` and `ctypes.sizeof`."""
|
|
775
742
|
|
|
776
743
|
# If result failed
|
|
777
744
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
778
|
-
raise MemoryWriteError(result, address)
|
|
745
|
+
raise errors.MemoryWriteError(result, address)
|
|
779
746
|
|
|
780
747
|
def close(self) -> None:
|
|
781
748
|
"""Close opened process using it's handle, have to be called once on stop interacting with process."""
|
|
@@ -786,7 +753,7 @@ class Process:
|
|
|
786
753
|
# ==--------------------------------== #
|
|
787
754
|
# Private methods #
|
|
788
755
|
# ==--------------------------------== #
|
|
789
|
-
def __init_with_pid(self, pid: int,
|
|
756
|
+
def __init_with_pid(self, pid: int, access_flags: int, listed_processes: list[dict[str, int | str]] | None = None) -> tuple[int, int, str]:
|
|
790
757
|
"""Open process handle by it's ID with desired access."""
|
|
791
758
|
|
|
792
759
|
# Iterate all of the processes
|
|
@@ -799,22 +766,22 @@ class Process:
|
|
|
799
766
|
break
|
|
800
767
|
|
|
801
768
|
else:
|
|
802
|
-
raise OpenProcessError("Process with `%s` ID not found" % pid)
|
|
769
|
+
raise errors.OpenProcessError("Process with `%s` ID not found" % pid)
|
|
803
770
|
|
|
804
771
|
# Prepare arguments
|
|
805
|
-
object_attributes = OBJECT_ATTRIBUTES()
|
|
806
|
-
object_attributes.length = ctypes.sizeof(OBJECT_ATTRIBUTES)
|
|
772
|
+
object_attributes = structs.OBJECT_ATTRIBUTES()
|
|
773
|
+
object_attributes.length = ctypes.sizeof(structs.OBJECT_ATTRIBUTES)
|
|
807
774
|
|
|
808
|
-
client_id = CLIENT_ID()
|
|
775
|
+
client_id = structs.CLIENT_ID()
|
|
809
776
|
client_id.unique_process = pid
|
|
810
777
|
|
|
811
778
|
# Try to open process using it's ID
|
|
812
|
-
if (result := _nt_open_process(ctypes.byref(handle := ctypes.c_void_p()),
|
|
813
|
-
raise NtError("NtOpenProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
779
|
+
if (result := _nt_open_process(ctypes.byref(handle := ctypes.c_void_p()), access_flags, ctypes.byref(object_attributes), ctypes.byref(client_id))):
|
|
780
|
+
raise errors.NtError("NtOpenProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
814
781
|
|
|
815
782
|
return handle.value, pid, process_name
|
|
816
783
|
|
|
817
|
-
def __init_with_name(self, name: str,
|
|
784
|
+
def __init_with_name(self, name: str, access_flags: int) -> tuple[int, int, str]:
|
|
818
785
|
"""Open process hanle by it's name with desired access."""
|
|
819
786
|
|
|
820
787
|
# Iterate all of the processes using snapshot
|
|
@@ -822,6 +789,6 @@ class Process:
|
|
|
822
789
|
|
|
823
790
|
# If process have a reqired name
|
|
824
791
|
if process["name"].lower() == name.strip().lower():
|
|
825
|
-
return self.__init_with_pid(process["id"],
|
|
792
|
+
return self.__init_with_pid(process["id"], access_flags, listed_processes)
|
|
826
793
|
|
|
827
|
-
raise OpenProcessError("Process with `%s` name not found" % name)
|
|
794
|
+
raise errors.OpenProcessError("Process with `%s` name not found" % name)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# +-------------------------------------+
|
|
2
|
+
# | ~ Author : Xenely ~ |
|
|
3
|
+
# +=====================================+
|
|
4
|
+
# | GitHub: https://github.com/Xenely14 |
|
|
5
|
+
# | Discord: xenely |
|
|
6
|
+
# +-------------------------------------+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import typing
|
|
10
|
+
import ctypes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ==-------------------------------------------------------------------== #
|
|
14
|
+
# C-structs #
|
|
15
|
+
# ==-------------------------------------------------------------------== #
|
|
16
|
+
class CLIENT_ID(ctypes.Structure):
|
|
17
|
+
"""Client-ID structure that required to open process."""
|
|
18
|
+
|
|
19
|
+
_fields_ = [
|
|
20
|
+
("unique_process", ctypes.c_void_p),
|
|
21
|
+
("unique_thread", ctypes.c_void_p),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LIST_ENTRY(ctypes.Structure):
|
|
26
|
+
"""Linked list WinAPI node structure."""
|
|
27
|
+
|
|
28
|
+
_fields_ = [
|
|
29
|
+
("flink", ctypes.c_void_p),
|
|
30
|
+
("blink", ctypes.c_void_p),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UNICODE_STRING(ctypes.Structure):
|
|
35
|
+
"""Unicode string structure that holds data to represent UTF-16LE WinAPI strings."""
|
|
36
|
+
|
|
37
|
+
_fields_ = [
|
|
38
|
+
("length", ctypes.c_short),
|
|
39
|
+
("maximum_length", ctypes.c_short),
|
|
40
|
+
("buffer", ctypes.c_void_p),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def read(self, process: typing.Any) -> str:
|
|
44
|
+
"""Read string from process using it's instance."""
|
|
45
|
+
|
|
46
|
+
return process.read_bytes(self.buffer, self.length).decode("utf-16le", errors="ignore")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LDR_DATA_TABLE_ENTRY(ctypes.Structure):
|
|
50
|
+
"""Linked list entry structure that contains imformation about loaded into the process modules."""
|
|
51
|
+
|
|
52
|
+
_fields_ = [
|
|
53
|
+
("flink", ctypes.c_void_p),
|
|
54
|
+
("blink", ctypes.c_void_p),
|
|
55
|
+
("reserved", ctypes.c_byte * 16),
|
|
56
|
+
("dll_base", ctypes.c_void_p),
|
|
57
|
+
("reserved2", ctypes.c_byte * 16),
|
|
58
|
+
("base_dll_name", UNICODE_STRING),
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
def read_name(self, process: typing.Any) -> str:
|
|
62
|
+
"""Read module name."""
|
|
63
|
+
|
|
64
|
+
return self.base_dll_name.read(process)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class OBJECT_ATTRIBUTES(ctypes.Structure):
|
|
68
|
+
"""Object attributes structure that can be applied to objects or object handles."""
|
|
69
|
+
|
|
70
|
+
_fields_ = [
|
|
71
|
+
("length", ctypes.c_ulong),
|
|
72
|
+
("root_directory", ctypes.c_void_p),
|
|
73
|
+
("object_name", ctypes.c_void_p),
|
|
74
|
+
("attributes", ctypes.c_ulong),
|
|
75
|
+
("security_descriptor", ctypes.c_void_p),
|
|
76
|
+
("security_quality_of_service", ctypes.c_void_p),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class MODULEENTRY32(ctypes.Structure):
|
|
81
|
+
"""Module entry structure, describes an entry from a list of the modules belonging to the specified process."""
|
|
82
|
+
|
|
83
|
+
_fields_ = [
|
|
84
|
+
("dw_size", ctypes.c_ulong),
|
|
85
|
+
("module_id", ctypes.c_ulong),
|
|
86
|
+
("process_id", ctypes.c_ulong),
|
|
87
|
+
("glbl_cnt_usage", ctypes.c_ulong),
|
|
88
|
+
("proc_cnt_usage", ctypes.c_ulong),
|
|
89
|
+
("mod_base_addr", ctypes.c_void_p),
|
|
90
|
+
("mod_base_size", ctypes.c_ulong),
|
|
91
|
+
("h_module", ctypes.c_void_p),
|
|
92
|
+
("sz_module", ctypes.c_char * 256),
|
|
93
|
+
("sz_exe_path", ctypes.c_char * 260),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def name(self) -> str:
|
|
98
|
+
"""Process module name."""
|
|
99
|
+
|
|
100
|
+
return self.sz_module.decode()
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def path(self) -> str:
|
|
104
|
+
"""process module on disk path."""
|
|
105
|
+
|
|
106
|
+
return self.sz_exe_path.decode()
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def base(self) -> int:
|
|
110
|
+
"""Process module base address."""
|
|
111
|
+
|
|
112
|
+
return self.mod_base_addr
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def size(self) -> int:
|
|
116
|
+
"""Process module base address."""
|
|
117
|
+
|
|
118
|
+
return self.mod_base_size
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
|
|
122
|
+
"""Memory basic information structure that contains information about a range of pages in the virtual address space of a proces"""
|
|
123
|
+
|
|
124
|
+
_fields_ = [
|
|
125
|
+
("m_base_address", ctypes.c_void_p),
|
|
126
|
+
("m_allocation_base", ctypes.c_void_p),
|
|
127
|
+
("m_allocation_protect", ctypes.c_ulong),
|
|
128
|
+
("m_partition_id", ctypes.c_ushort),
|
|
129
|
+
("m_region_size", ctypes.c_size_t),
|
|
130
|
+
("m_state", ctypes.c_ulong),
|
|
131
|
+
("m_protect", ctypes.c_ulong),
|
|
132
|
+
("m_type", ctypes.c_ulong),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def base_address(self) -> int:
|
|
137
|
+
"""Memory region base address."""
|
|
138
|
+
|
|
139
|
+
return self.m_base_address
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def allocation_base(self) -> int:
|
|
143
|
+
"""Memory region allocation base."""
|
|
144
|
+
|
|
145
|
+
return self.m_allocation_base
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def allocation_protect(self) -> int:
|
|
149
|
+
"""Memory region allocation protect."""
|
|
150
|
+
|
|
151
|
+
return self.m_allocation_protect
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def partition_id(self) -> int:
|
|
155
|
+
"""Memory region partition ID."""
|
|
156
|
+
|
|
157
|
+
return self.m_partition_id
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def size(self) -> int:
|
|
161
|
+
"""Memory region size."""
|
|
162
|
+
|
|
163
|
+
return self.m_region_size
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def state(self) -> int:
|
|
167
|
+
"""Memory region state."""
|
|
168
|
+
|
|
169
|
+
return self.m_state
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def protect(self) -> int:
|
|
173
|
+
"""Memory region protect."""
|
|
174
|
+
|
|
175
|
+
return self.m_protect
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def type(self) -> int:
|
|
179
|
+
"""Memory region type."""
|
|
180
|
+
|
|
181
|
+
return self.m_type
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class PROCESS_BASIC_INFORMATION_X32(ctypes.Structure):
|
|
185
|
+
"""Process basic information structure that contains information about the OS-process."""
|
|
186
|
+
|
|
187
|
+
_fields_ = [
|
|
188
|
+
("ExitStatus", ctypes.c_uint32),
|
|
189
|
+
("PebBaseAddress", ctypes.c_uint32),
|
|
190
|
+
("AffinityMask", ctypes.c_uint32),
|
|
191
|
+
("BasePriority", ctypes.c_int32),
|
|
192
|
+
("UniqueProcessId", ctypes.c_uint32),
|
|
193
|
+
("InheritedFromUniqueProcessId", ctypes.c_uint32),
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def peb(self) -> int:
|
|
198
|
+
"""Process peb base address."""
|
|
199
|
+
|
|
200
|
+
return self.peb_base_address
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class PROCESS_BASIC_INFORMATION_64(ctypes.Structure):
|
|
204
|
+
"""Process basic information structure that contains information about the OS-process."""
|
|
205
|
+
|
|
206
|
+
_fields_ = [
|
|
207
|
+
("exit_status", ctypes.c_void_p),
|
|
208
|
+
("peb_base_address", ctypes.c_void_p),
|
|
209
|
+
("affinity_mask", ctypes.c_void_p),
|
|
210
|
+
("base_priority", ctypes.c_uint32),
|
|
211
|
+
("unique_process_id", ctypes.c_void_p),
|
|
212
|
+
("inherited_from_unique_process_id", ctypes.c_void_p),
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def peb(self) -> int:
|
|
217
|
+
"""Process peb base address."""
|
|
218
|
+
|
|
219
|
+
return self.peb_base_address
|
ntmemoryapi-2.2.5/PKG-INFO
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: ntmemoryapi
|
|
3
|
-
Version: 2.2.5
|
|
4
|
-
Summary: Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps.
|
|
5
|
-
Author: Xenely
|
|
6
|
-
Requires-Dist: psutil>=7.1.3
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
ntmemoryapi-2.2.5/pyproject.toml
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "ntmemoryapi"
|
|
3
|
-
version = "2.2.5"
|
|
4
|
-
description = "Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps."
|
|
5
|
-
authors = [
|
|
6
|
-
{name = "Xenely"}
|
|
7
|
-
]
|
|
8
|
-
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.10"
|
|
10
|
-
dependencies = [
|
|
11
|
-
"psutil>=7.1.3",
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
[project.scripts]
|
|
15
|
-
ntmemoryapi = "ntmemoryapi:main"
|
|
16
|
-
|
|
17
|
-
[build-system]
|
|
18
|
-
requires = ["uv_build>=0.9.9,<0.10.0"]
|
|
19
|
-
build-backend = "uv_build"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|