ntmemoryapi 2.2.4__tar.gz → 2.3.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.3.0/PKG-INFO +8 -0
- ntmemoryapi-2.3.0/pyproject.toml +13 -0
- {ntmemoryapi-2.2.4 → ntmemoryapi-2.3.0}/src/ntmemoryapi/__init__.py +185 -222
- ntmemoryapi-2.3.0/src/ntmemoryapi/structs.py +219 -0
- ntmemoryapi-2.2.4/PKG-INFO +0 -9
- ntmemoryapi-2.2.4/pyproject.toml +0 -19
- {ntmemoryapi-2.2.4 → ntmemoryapi-2.3.0}/README.md +0 -0
- {ntmemoryapi-2.2.4 → ntmemoryapi-2.3.0}/src/ntmemoryapi/embed.py +0 -0
- {ntmemoryapi-2.2.4 → ntmemoryapi-2.3.0}/src/ntmemoryapi/errors.py +0 -0
- {ntmemoryapi-2.2.4 → ntmemoryapi-2.3.0}/src/ntmemoryapi/misc.py +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ntmemoryapi"
|
|
3
|
+
version = "2.3.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 procewss 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,7 +360,27 @@ 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) -> structs.MODULEENTRY32:
|
|
418
384
|
"""Get process module information."""
|
|
419
385
|
|
|
420
386
|
# Process modules enumeration
|
|
@@ -426,15 +392,15 @@ class Process:
|
|
|
426
392
|
|
|
427
393
|
raise ValueError("Module with `%s` name not found" % name)
|
|
428
394
|
|
|
429
|
-
def get_memory_region(self, address: int) -> MEMORY_BASIC_INFORMATION:
|
|
395
|
+
def get_memory_region(self, address: int) -> structs.MEMORY_BASIC_INFORMATION:
|
|
430
396
|
"""Get memory region information located at given address."""
|
|
431
397
|
|
|
432
398
|
# Prepare arguments
|
|
433
|
-
memory_basic_information = MEMORY_BASIC_INFORMATION()
|
|
399
|
+
memory_basic_information = structs.MEMORY_BASIC_INFORMATION()
|
|
434
400
|
|
|
435
401
|
# Try to get memory region information using it's address
|
|
436
402
|
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())
|
|
403
|
+
raise errors.NtError("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
438
404
|
|
|
439
405
|
return memory_basic_information
|
|
440
406
|
|
|
@@ -445,8 +411,8 @@ class Process:
|
|
|
445
411
|
wow64_info = ctypes.c_void_p()
|
|
446
412
|
|
|
447
413
|
# 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())
|
|
414
|
+
if (result := _nt_query_information_process(self.handle, 0x1A, ctypes.byref(wow64_info), ctypes.sizeof(wow64_info), ctypes.byref(ctypes.c_ulong()))):
|
|
415
|
+
raise errors.NtError("NtQueryInformationProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
450
416
|
|
|
451
417
|
return wow64_info.value is None
|
|
452
418
|
|
|
@@ -459,7 +425,7 @@ class Process:
|
|
|
459
425
|
|
|
460
426
|
# Try to allocate process memory
|
|
461
427
|
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())
|
|
428
|
+
raise errors.NtError("NtAllocateVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
463
429
|
|
|
464
430
|
return allocation_address.value or 0, allocation_size.value or 0
|
|
465
431
|
|
|
@@ -472,7 +438,7 @@ class Process:
|
|
|
472
438
|
|
|
473
439
|
# Try to deallocate process memory
|
|
474
440
|
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())
|
|
441
|
+
raise errors.NtError("NtFreeVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
476
442
|
|
|
477
443
|
return allocation_size.value or 0
|
|
478
444
|
|
|
@@ -485,11 +451,11 @@ class Process:
|
|
|
485
451
|
|
|
486
452
|
# Try to change memory protection
|
|
487
453
|
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())
|
|
454
|
+
raise errors.NtError("NtProtectVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
489
455
|
|
|
490
456
|
return memory_old_protect.value or 0
|
|
491
457
|
|
|
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]:
|
|
458
|
+
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
459
|
"""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
460
|
|
|
495
461
|
# Validate given pattern
|
|
@@ -554,10 +520,10 @@ class Process:
|
|
|
554
520
|
try:
|
|
555
521
|
|
|
556
522
|
# Read bytes into pre-allocated buffer
|
|
557
|
-
self.read_into_buffer(region.base_address, region.size
|
|
523
|
+
self.read_into_buffer(region.base_address, read_memory_buffer, region.size)
|
|
558
524
|
|
|
559
525
|
except Exception:
|
|
560
|
-
|
|
526
|
+
continue
|
|
561
527
|
|
|
562
528
|
# Pattern scan region using SIMD KMP algorithm
|
|
563
529
|
self.__kmp.scanAOB(ctypes.c_void_p(ctypes.addressof(read_memory_buffer)), region.size, pattern.strip().encode(), ctypes.c_uint64(region.base_address), ctypes.c_uint64(return_first if return_first is not None else 0), ctypes.byref(scan_result := PatternScanBuffer()))
|
|
@@ -583,7 +549,7 @@ class Process:
|
|
|
583
549
|
|
|
584
550
|
# If result failed
|
|
585
551
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8()), ctypes.sizeof(buffer), None)):
|
|
586
|
-
raise MemoryReadError(result, address)
|
|
552
|
+
raise errors.MemoryReadError(result, address)
|
|
587
553
|
|
|
588
554
|
return buffer.value
|
|
589
555
|
|
|
@@ -592,7 +558,7 @@ class Process:
|
|
|
592
558
|
|
|
593
559
|
# If result failed
|
|
594
560
|
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)
|
|
561
|
+
raise errors.MemoryReadError(result, address)
|
|
596
562
|
|
|
597
563
|
return buffer.value
|
|
598
564
|
|
|
@@ -601,7 +567,7 @@ class Process:
|
|
|
601
567
|
|
|
602
568
|
# If result failed
|
|
603
569
|
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)
|
|
570
|
+
raise errors.MemoryReadError(result, address)
|
|
605
571
|
|
|
606
572
|
return buffer.value
|
|
607
573
|
|
|
@@ -610,7 +576,7 @@ class Process:
|
|
|
610
576
|
|
|
611
577
|
# If result failed
|
|
612
578
|
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)
|
|
579
|
+
raise errors.MemoryReadError(result, address)
|
|
614
580
|
|
|
615
581
|
return buffer.value
|
|
616
582
|
|
|
@@ -619,7 +585,7 @@ class Process:
|
|
|
619
585
|
|
|
620
586
|
# If result failed
|
|
621
587
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8()), ctypes.sizeof(buffer), None)):
|
|
622
|
-
raise MemoryReadError(result, address)
|
|
588
|
+
raise errors.MemoryReadError(result, address)
|
|
623
589
|
|
|
624
590
|
return buffer.value
|
|
625
591
|
|
|
@@ -628,7 +594,7 @@ class Process:
|
|
|
628
594
|
|
|
629
595
|
# If result failed
|
|
630
596
|
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)
|
|
597
|
+
raise errors.MemoryReadError(result, address)
|
|
632
598
|
|
|
633
599
|
return buffer.value
|
|
634
600
|
|
|
@@ -637,7 +603,7 @@ class Process:
|
|
|
637
603
|
|
|
638
604
|
# If result failed
|
|
639
605
|
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)
|
|
606
|
+
raise errors.MemoryReadError(result, address)
|
|
641
607
|
|
|
642
608
|
return buffer.value
|
|
643
609
|
|
|
@@ -646,7 +612,7 @@ class Process:
|
|
|
646
612
|
|
|
647
613
|
# If result failed
|
|
648
614
|
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)
|
|
615
|
+
raise errors.MemoryReadError(result, address)
|
|
650
616
|
|
|
651
617
|
return buffer.value
|
|
652
618
|
|
|
@@ -655,7 +621,7 @@ class Process:
|
|
|
655
621
|
|
|
656
622
|
# If result failed
|
|
657
623
|
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)
|
|
624
|
+
raise errors.MemoryReadError(result, address)
|
|
659
625
|
|
|
660
626
|
return buffer.value
|
|
661
627
|
|
|
@@ -664,7 +630,7 @@ class Process:
|
|
|
664
630
|
|
|
665
631
|
# If result failed
|
|
666
632
|
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)
|
|
633
|
+
raise errors.MemoryReadError(result, address)
|
|
668
634
|
|
|
669
635
|
return buffer.value
|
|
670
636
|
|
|
@@ -673,109 +639,106 @@ class Process:
|
|
|
673
639
|
|
|
674
640
|
# If result failed
|
|
675
641
|
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)
|
|
642
|
+
raise errors.MemoryReadError(result, address)
|
|
677
643
|
|
|
678
644
|
return bytes(buffer)
|
|
679
645
|
|
|
680
|
-
def
|
|
681
|
-
"""Read
|
|
646
|
+
def read_into_buffer(self, address: int, buffer: typing.Any, read_bytes_size: int | None = None) -> typing.Any:
|
|
647
|
+
"""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."""
|
|
648
|
+
|
|
649
|
+
# If read size is invalid
|
|
650
|
+
if read_bytes_size is not None and read_bytes_size <= 0:
|
|
651
|
+
raise ValueError("Unable to read value into buffer due the invalid size: `%s`" % read_bytes_size)
|
|
682
652
|
|
|
683
653
|
# 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)
|
|
654
|
+
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)):
|
|
655
|
+
raise errors.MemoryReadError(result, address)
|
|
686
656
|
|
|
687
657
|
return buffer
|
|
688
658
|
|
|
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
659
|
def write_int8(self, address: int, value: int) -> None:
|
|
697
660
|
"""Write 1 byte signed integer value at given address."""
|
|
698
661
|
|
|
699
662
|
# If result failed
|
|
700
663
|
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)
|
|
664
|
+
raise errors.MemoryWriteError(result, address)
|
|
702
665
|
|
|
703
666
|
def write_int16(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
704
667
|
"""Write 2 byte signed integer value at given address."""
|
|
705
668
|
|
|
706
669
|
# If result failed
|
|
707
670
|
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)
|
|
671
|
+
raise errors.MemoryWriteError(result, address)
|
|
709
672
|
|
|
710
673
|
def write_int32(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
711
674
|
"""Write 4 byte signed integer value at given address."""
|
|
712
675
|
|
|
713
676
|
# If result failed
|
|
714
677
|
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)
|
|
678
|
+
raise errors.MemoryWriteError(result, address)
|
|
716
679
|
|
|
717
680
|
def write_int64(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
718
681
|
"""Write 8 byte signed integer value at given address."""
|
|
719
682
|
|
|
720
683
|
# If result failed
|
|
721
684
|
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)
|
|
685
|
+
raise errors.MemoryWriteError(result, address)
|
|
723
686
|
|
|
724
687
|
def write_uint8(self, address: int, value: int) -> None:
|
|
725
688
|
"""Write 1 byte unsigned integer value at given address."""
|
|
726
689
|
|
|
727
690
|
# If result failed
|
|
728
691
|
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)
|
|
692
|
+
raise errors.MemoryWriteError(result, address)
|
|
730
693
|
|
|
731
694
|
def write_uint16(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
732
695
|
"""Write 2 byte unsigned integer value at given address."""
|
|
733
696
|
|
|
734
697
|
# If result failed
|
|
735
698
|
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)
|
|
699
|
+
raise errors.MemoryWriteError(result, address)
|
|
737
700
|
|
|
738
701
|
def write_uint32(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
739
702
|
"""Write 4 byte unsigned integer value at given address."""
|
|
740
703
|
|
|
741
704
|
# If result failed
|
|
742
705
|
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())
|
|
706
|
+
raise errors.MemoryWriteError("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
744
707
|
|
|
745
708
|
def write_uint64(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
746
709
|
"""Write 8 byte unsigned integer value at given address."""
|
|
747
710
|
|
|
748
711
|
# If result failed
|
|
749
712
|
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)
|
|
713
|
+
raise errors.MemoryWriteError(result, address)
|
|
751
714
|
|
|
752
715
|
def write_float32(self, address: int, value: float | int, big_endian: bool = False) -> float:
|
|
753
716
|
"""Write 4 byte floating-point digit value at given address."""
|
|
754
717
|
|
|
755
718
|
# If result failed
|
|
756
719
|
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)
|
|
720
|
+
raise errors.MemoryWriteError(result, address)
|
|
758
721
|
|
|
759
722
|
def write_float64(self, address: int, value: float | int, big_endian: bool = False) -> float:
|
|
760
723
|
"""Write 8 byte floating-point digit value at given address."""
|
|
761
724
|
|
|
762
725
|
# If result failed
|
|
763
726
|
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)
|
|
727
|
+
raise errors.MemoryWriteError(result, address)
|
|
765
728
|
|
|
766
729
|
def write_bytes(self, address: int, value: bytes, big_endian: bool = False) -> None:
|
|
767
730
|
"""Write bytes array of variadic size at given address."""
|
|
768
731
|
|
|
769
732
|
# If result failed
|
|
770
733
|
if (result := _nt_write_virtual_memory(self.handle, address, value[::-1] if big_endian else value, len(value), None)):
|
|
771
|
-
raise MemoryWriteError(result, address)
|
|
734
|
+
raise errors.MemoryWriteError(result, address)
|
|
772
735
|
|
|
773
736
|
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 `
|
|
737
|
+
"""Write size of buffer byte value at given address. Buffer have to be able passed at `ctypes.byref` and `ctypes.sizeof`."""
|
|
775
738
|
|
|
776
739
|
# If result failed
|
|
777
740
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
778
|
-
raise MemoryWriteError(result, address)
|
|
741
|
+
raise errors.MemoryWriteError(result, address)
|
|
779
742
|
|
|
780
743
|
def close(self) -> None:
|
|
781
744
|
"""Close opened process using it's handle, have to be called once on stop interacting with process."""
|
|
@@ -786,7 +749,7 @@ class Process:
|
|
|
786
749
|
# ==--------------------------------== #
|
|
787
750
|
# Private methods #
|
|
788
751
|
# ==--------------------------------== #
|
|
789
|
-
def __init_with_pid(self, pid: int,
|
|
752
|
+
def __init_with_pid(self, pid: int, access_flags: int, listed_processes: list[dict[str, int | str]] | None = None) -> tuple[int, int, str]:
|
|
790
753
|
"""Open process handle by it's ID with desired access."""
|
|
791
754
|
|
|
792
755
|
# Iterate all of the processes
|
|
@@ -799,22 +762,22 @@ class Process:
|
|
|
799
762
|
break
|
|
800
763
|
|
|
801
764
|
else:
|
|
802
|
-
raise OpenProcessError("Process with `%s` ID not found" % pid)
|
|
765
|
+
raise errors.OpenProcessError("Process with `%s` ID not found" % pid)
|
|
803
766
|
|
|
804
767
|
# Prepare arguments
|
|
805
|
-
object_attributes = OBJECT_ATTRIBUTES()
|
|
806
|
-
object_attributes.length = ctypes.sizeof(OBJECT_ATTRIBUTES)
|
|
768
|
+
object_attributes = structs.OBJECT_ATTRIBUTES()
|
|
769
|
+
object_attributes.length = ctypes.sizeof(structs.OBJECT_ATTRIBUTES)
|
|
807
770
|
|
|
808
|
-
client_id = CLIENT_ID()
|
|
771
|
+
client_id = structs.CLIENT_ID()
|
|
809
772
|
client_id.unique_process = pid
|
|
810
773
|
|
|
811
774
|
# 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())
|
|
775
|
+
if (result := _nt_open_process(ctypes.byref(handle := ctypes.c_void_p()), access_flags, ctypes.byref(object_attributes), ctypes.byref(client_id))):
|
|
776
|
+
raise errors.NtError("NtOpenProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
814
777
|
|
|
815
778
|
return handle.value, pid, process_name
|
|
816
779
|
|
|
817
|
-
def __init_with_name(self, name: str,
|
|
780
|
+
def __init_with_name(self, name: str, access_flags: int) -> tuple[int, int, str]:
|
|
818
781
|
"""Open process hanle by it's name with desired access."""
|
|
819
782
|
|
|
820
783
|
# Iterate all of the processes using snapshot
|
|
@@ -822,6 +785,6 @@ class Process:
|
|
|
822
785
|
|
|
823
786
|
# If process have a reqired name
|
|
824
787
|
if process["name"].lower() == name.strip().lower():
|
|
825
|
-
return self.__init_with_pid(process["id"],
|
|
788
|
+
return self.__init_with_pid(process["id"], access_flags, listed_processes)
|
|
826
789
|
|
|
827
|
-
raise OpenProcessError("Process with `%s` name not found" % name)
|
|
790
|
+
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.4/PKG-INFO
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: ntmemoryapi
|
|
3
|
-
Version: 2.2.4
|
|
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.4/pyproject.toml
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "ntmemoryapi"
|
|
3
|
-
version = "2.2.4"
|
|
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
|