ntmemoryapi 1.0.0__py3-none-any.whl
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/__init__.py +918 -0
- ntmemoryapi/embed.py +1130 -0
- ntmemoryapi/misc.py +102 -0
- ntmemoryapi-1.0.0.dist-info/METADATA +14 -0
- ntmemoryapi-1.0.0.dist-info/RECORD +6 -0
- ntmemoryapi-1.0.0.dist-info/WHEEL +4 -0
ntmemoryapi/__init__.py
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
# +-------------------------------------+
|
|
2
|
+
# | ~ Author : Xenely ~ |
|
|
3
|
+
# +=====================================+
|
|
4
|
+
# | GitHub: https://github.com/Xenely14 |
|
|
5
|
+
# | Discord: xenely |
|
|
6
|
+
# +-------------------------------------+
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import ctypes
|
|
10
|
+
|
|
11
|
+
# Local imports
|
|
12
|
+
from .misc import *
|
|
13
|
+
from .embed import *
|
|
14
|
+
|
|
15
|
+
# ==-------------------------------------------------------------------== #
|
|
16
|
+
# C-consts #
|
|
17
|
+
# ==-------------------------------------------------------------------== #
|
|
18
|
+
|
|
19
|
+
# Process access flags
|
|
20
|
+
PROCESS_ALL_ACCESS = 0x1F0FFF
|
|
21
|
+
PROCESS_VM_READ = 0x0010
|
|
22
|
+
PROCESS_VM_WRITE = 0x0020
|
|
23
|
+
PROCESS_VM_OPERATION = 0x0008
|
|
24
|
+
PROCESS_QUERY_INFORMATION = 0x0400
|
|
25
|
+
|
|
26
|
+
# Memory protection constants
|
|
27
|
+
PAGE_NOACCESS = 0x01
|
|
28
|
+
PAGE_READONLY = 0x02
|
|
29
|
+
PAGE_READWRITE = 0x04
|
|
30
|
+
PAGE_WRITECOPY = 0x08
|
|
31
|
+
PAGE_EXECUTE = 0x10
|
|
32
|
+
PAGE_EXECUTE_READ = 0x20
|
|
33
|
+
PAGE_EXECUTE_READWRITE = 0x40
|
|
34
|
+
PAGE_EXECUTE_WRITECOPY = 0x80
|
|
35
|
+
PAGE_GUARD = 0x100
|
|
36
|
+
PAGE_NOCACHE = 0x200
|
|
37
|
+
PAGE_WRITECOMBINE = 0x400
|
|
38
|
+
|
|
39
|
+
# Memory state constants
|
|
40
|
+
MEM_COMMIT = 0x1000
|
|
41
|
+
MEM_RESERVE = 0x2000
|
|
42
|
+
MEM_FREE = 0x10000
|
|
43
|
+
MEM_PRIVATE = 0x20000
|
|
44
|
+
MEM_MAPPED = 0x40000
|
|
45
|
+
MEM_IMAGE = 0x1000000
|
|
46
|
+
|
|
47
|
+
# Memory type constants
|
|
48
|
+
MEM_IMAGE = 0x1000000
|
|
49
|
+
MEM_MAPPED = 0x40000
|
|
50
|
+
MEM_PRIVATE = 0x20000
|
|
51
|
+
|
|
52
|
+
# Memory free types
|
|
53
|
+
MEM_DECOMMIT = 0x4000
|
|
54
|
+
MEM_RELEASE = 0x8000
|
|
55
|
+
|
|
56
|
+
# ToolHelp32 constants
|
|
57
|
+
TH32CS_SNAPPROCESS = 0x00000002
|
|
58
|
+
TH32CS_SNAPMODULE = 0x00000008
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ==-------------------------------------------------------------------== #
|
|
62
|
+
# C-structs #
|
|
63
|
+
# ==-------------------------------------------------------------------== #
|
|
64
|
+
class CLIENT_ID(ctypes.Structure):
|
|
65
|
+
"""Client-ID structure that required to open process."""
|
|
66
|
+
|
|
67
|
+
_fields_ = [
|
|
68
|
+
("unique_process", ctypes.c_void_p),
|
|
69
|
+
("unique_thread", ctypes.c_void_p)
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class OBJECT_ATTRIBUTES(ctypes.Structure):
|
|
74
|
+
"""Object attributes structure that can be applied to objects or object handles."""
|
|
75
|
+
|
|
76
|
+
_fields_ = [
|
|
77
|
+
("length", ctypes.c_ulong),
|
|
78
|
+
("root_directory", ctypes.c_void_p),
|
|
79
|
+
("object_name", ctypes.c_void_p),
|
|
80
|
+
("attributes", ctypes.c_ulong),
|
|
81
|
+
("security_descriptor", ctypes.c_void_p),
|
|
82
|
+
("security_quality_of_service", ctypes.c_void_p)
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PROCESSENTRY32(ctypes.Structure):
|
|
87
|
+
"""Process entry structure for processes enumeration."""
|
|
88
|
+
|
|
89
|
+
_fields_ = [
|
|
90
|
+
("dw_size", ctypes.c_ulong),
|
|
91
|
+
("cnt_usage", ctypes.c_ulong),
|
|
92
|
+
("th32_process_id", ctypes.c_ulong),
|
|
93
|
+
("th32_default_heap_id", ctypes.c_void_p),
|
|
94
|
+
("th32_module_id", ctypes.c_ulong),
|
|
95
|
+
("cnt_threads", ctypes.c_ulong),
|
|
96
|
+
("th32_parent_process_id", ctypes.c_ulong),
|
|
97
|
+
("pc_pri_class_base", ctypes.c_long),
|
|
98
|
+
("dw_flags", ctypes.c_ulong),
|
|
99
|
+
("sz_exe_file", ctypes.c_char * 260)
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def pid(self) -> int:
|
|
104
|
+
"""Process ID."""
|
|
105
|
+
|
|
106
|
+
return self.th32_process_id
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def name(self) -> str:
|
|
110
|
+
"""Process name."""
|
|
111
|
+
|
|
112
|
+
return self.sz_exe_file.decode()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MODULEENTRY32(ctypes.Structure):
|
|
116
|
+
"""Module entry structure, describes an entry from a list of the modules belonging to the specified process."""
|
|
117
|
+
|
|
118
|
+
_fields_ = [
|
|
119
|
+
("dw_size", ctypes.c_ulong),
|
|
120
|
+
("module_id", ctypes.c_ulong),
|
|
121
|
+
("process_id", ctypes.c_ulong),
|
|
122
|
+
("glbl_cnt_usage", ctypes.c_ulong),
|
|
123
|
+
("proc_cnt_usage", ctypes.c_ulong),
|
|
124
|
+
("mod_base_addr", ctypes.c_void_p),
|
|
125
|
+
("mod_base_size", ctypes.c_ulong),
|
|
126
|
+
("h_module", ctypes.c_void_p),
|
|
127
|
+
("sz_module", ctypes.c_char * 256),
|
|
128
|
+
("sz_exe_path", ctypes.c_char * 260)
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def name(self) -> str:
|
|
133
|
+
"""Process module name."""
|
|
134
|
+
|
|
135
|
+
return self.sz_module.decode()
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def base(self) -> int:
|
|
139
|
+
"""Process module base address."""
|
|
140
|
+
|
|
141
|
+
return self.mod_base_addr or 0
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def size(self) -> int:
|
|
145
|
+
"""Process module base address."""
|
|
146
|
+
|
|
147
|
+
return self.mod_base_size
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
|
|
151
|
+
"""Memory basic information structure that containg information about a range of pages in the virtual address space of a proces"""
|
|
152
|
+
|
|
153
|
+
_fields_ = [
|
|
154
|
+
("m_base_address", ctypes.c_void_p),
|
|
155
|
+
("m_allocation_base", ctypes.c_void_p),
|
|
156
|
+
("m_allocation_protect", ctypes.c_ulong),
|
|
157
|
+
("m_partition_id", ctypes.c_ushort),
|
|
158
|
+
("m_region_size", ctypes.c_size_t),
|
|
159
|
+
("m_state", ctypes.c_ulong),
|
|
160
|
+
("m_protect", ctypes.c_ulong),
|
|
161
|
+
("m_type", ctypes.c_ulong)
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def base_address(self) -> int:
|
|
166
|
+
"""Memory region base address."""
|
|
167
|
+
|
|
168
|
+
return self.m_base_address or 0
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def allocation_base(self) -> int:
|
|
172
|
+
"""Memory region allocation base."""
|
|
173
|
+
|
|
174
|
+
return self.m_allocation_base or 0
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def allocation_protect(self) -> int:
|
|
178
|
+
"""Memory region allocation protect."""
|
|
179
|
+
|
|
180
|
+
return self.m_allocation_protect
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def partition_id(self) -> int:
|
|
184
|
+
"""Memory region partition ID."""
|
|
185
|
+
|
|
186
|
+
return self.m_partition_id
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def region_size(self) -> int:
|
|
190
|
+
"""Memory region size."""
|
|
191
|
+
|
|
192
|
+
return self.m_region_size
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def state(self) -> int:
|
|
196
|
+
"""Memory region state."""
|
|
197
|
+
|
|
198
|
+
return self.m_state
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def protect(self) -> int:
|
|
202
|
+
"""Memory region protect."""
|
|
203
|
+
|
|
204
|
+
return self.m_protect
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def type(self) -> int:
|
|
208
|
+
"""Memory region type."""
|
|
209
|
+
|
|
210
|
+
return self.m_type
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class PatternScanBuffer(ctypes.Structure):
|
|
214
|
+
"""Structure that containing pointer to array received from SIMD KMP after call of `scanAOB` function."""
|
|
215
|
+
|
|
216
|
+
_fields_ = [
|
|
217
|
+
("pointer", ctypes.c_void_p),
|
|
218
|
+
("size", ctypes.c_size_t),
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
def read(self) -> list:
|
|
222
|
+
"""Read all of the values located at buffer array."""
|
|
223
|
+
|
|
224
|
+
return list((ctypes.c_size_t * self.size).from_address(self.pointer))
|
|
225
|
+
|
|
226
|
+
def free(self, library: ctypes.WinDLL) -> None:
|
|
227
|
+
"""Free memory allocated on `scanAOB` function call."""
|
|
228
|
+
|
|
229
|
+
# If pointer is not defined
|
|
230
|
+
if self.pointer is None:
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
# Free memory
|
|
234
|
+
library.freeScanAOB(ctypes.byref(self))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# ==-------------------------------------------------------------------== #
|
|
238
|
+
# Syscalls #
|
|
239
|
+
# ==-------------------------------------------------------------------== #
|
|
240
|
+
_nt_close = syscall("NtClose", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p])
|
|
241
|
+
_nt_open_process = syscall("NtOpenProcess", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(OBJECT_ATTRIBUTES), ctypes.POINTER(CLIENT_ID)])
|
|
242
|
+
|
|
243
|
+
_nt_read_virtual_memory = syscall("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)])
|
|
244
|
+
_nt_write_virtual_memory = syscall("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)])
|
|
245
|
+
|
|
246
|
+
_nt_virtual_query_memory = syscall("NtQueryVirtualMemory", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)])
|
|
247
|
+
_nt_query_information_process = syscall("NtQueryInformationProcess", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong)])
|
|
248
|
+
|
|
249
|
+
_nt_free_virtual_memory = syscall("NtFreeVirtualMemory", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_ulong), ctypes.c_ulong])
|
|
250
|
+
_nt_allocate_virtual_memory = syscall("NtAllocateVirtualMemory", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong), ctypes.c_ulong, ctypes.c_ulong])
|
|
251
|
+
_nt_protect_virtual_memory = syscall("NtProtectVirtualMemory", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_ulong), ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong)])
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# ==-------------------------------------------------------------------== #
|
|
255
|
+
# Functions #
|
|
256
|
+
# ==-------------------------------------------------------------------== #
|
|
257
|
+
def list_processes(include_id: bool = True, include_name: bool = True) -> list[dict[str, int | str]]:
|
|
258
|
+
"""List all of the currently active system processes."""
|
|
259
|
+
|
|
260
|
+
# If `include_id` and `include_name` disabled both
|
|
261
|
+
if not include_id and not include_name:
|
|
262
|
+
raise Exception("Unable to disable ID and name including at once")
|
|
263
|
+
|
|
264
|
+
# Process snapshot functions
|
|
265
|
+
close_handle = ctypes.windll.kernel32.CloseHandle
|
|
266
|
+
process32_next = ctypes.windll.kernel32.Process32Next
|
|
267
|
+
process32_first = ctypes.windll.kernel32.Process32First
|
|
268
|
+
create_tool_help32_snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
|
269
|
+
|
|
270
|
+
# Create snapshot to iterate processes
|
|
271
|
+
if (snapshot := create_tool_help32_snapshot(TH32CS_SNAPPROCESS, 0)) == -1:
|
|
272
|
+
raise Exception("Unable to create snapshot to iterate processes")
|
|
273
|
+
|
|
274
|
+
# Result list of processes
|
|
275
|
+
processes = list()
|
|
276
|
+
|
|
277
|
+
# Process enumeration
|
|
278
|
+
try:
|
|
279
|
+
|
|
280
|
+
# Process entry to iterate processes
|
|
281
|
+
process_entry = PROCESSENTRY32()
|
|
282
|
+
process_entry.dw_size = ctypes.sizeof(PROCESSENTRY32)
|
|
283
|
+
|
|
284
|
+
# Retrieve first process snapshot
|
|
285
|
+
if not process32_first(snapshot, ctypes.byref(process_entry)):
|
|
286
|
+
raise Exception("Unable to get first process to save to snapshot")
|
|
287
|
+
|
|
288
|
+
# Iterate all of the processes using snapshot
|
|
289
|
+
while True:
|
|
290
|
+
|
|
291
|
+
# Save process to list
|
|
292
|
+
process_dict = dict()
|
|
293
|
+
|
|
294
|
+
# If process ID including required
|
|
295
|
+
if include_id:
|
|
296
|
+
process_dict |= {"id": process_entry.pid}
|
|
297
|
+
|
|
298
|
+
# If process ID including required
|
|
299
|
+
if include_name:
|
|
300
|
+
process_dict |= {"name": process_entry.name}
|
|
301
|
+
|
|
302
|
+
processes.append(process_dict)
|
|
303
|
+
|
|
304
|
+
# Snapshot is over
|
|
305
|
+
if not process32_next(snapshot, ctypes.byref(process_entry)):
|
|
306
|
+
break
|
|
307
|
+
|
|
308
|
+
finally:
|
|
309
|
+
|
|
310
|
+
# Close snapshot handle
|
|
311
|
+
close_handle(snapshot)
|
|
312
|
+
|
|
313
|
+
return processes
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# ==-------------------------------------------------------------------== #
|
|
317
|
+
# Classes #
|
|
318
|
+
# ==-------------------------------------------------------------------== #
|
|
319
|
+
class Process:
|
|
320
|
+
"""Basic class of process, have methods to manipulate it."""
|
|
321
|
+
|
|
322
|
+
# ==-------------------------------------------------------------------== #
|
|
323
|
+
# Methods #
|
|
324
|
+
# ==-------------------------------------------------------------------== #
|
|
325
|
+
|
|
326
|
+
def __init__(self, pid_or_name: int | str, access: int = PROCESS_ALL_ACCESS) -> None:
|
|
327
|
+
"""Initialize instance to manipulate process."""
|
|
328
|
+
|
|
329
|
+
# Open process by it's ID or it's name
|
|
330
|
+
match pid_or_name:
|
|
331
|
+
|
|
332
|
+
case pid if type(pid_or_name) is int:
|
|
333
|
+
self.handle, self.name, self.pid = self.__init_with_pid(pid, access)
|
|
334
|
+
|
|
335
|
+
case name if type(pid_or_name) is str:
|
|
336
|
+
self.handle, self.name, self.pid = self.__init_with_name(name, access)
|
|
337
|
+
|
|
338
|
+
case _:
|
|
339
|
+
raise Exception("Invalid `pid_or_name` argument value, have to be `int` or `str` type")
|
|
340
|
+
|
|
341
|
+
# Try create file at temp directory to load SIMD KMP .dll (Module to blazingly fast pattern scaning)
|
|
342
|
+
try:
|
|
343
|
+
|
|
344
|
+
# Write library bytes directry from python list
|
|
345
|
+
with open("%s\\simdkmp.dll" % (appdata := os.getenv("APPDATA")), "wb") as file:
|
|
346
|
+
file.write(bytes(embed.kmp))
|
|
347
|
+
|
|
348
|
+
except Exception:
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
# Load library
|
|
352
|
+
self.__kmp = ctypes.WinDLL(appdata + "\\simdkmp.dll")
|
|
353
|
+
|
|
354
|
+
def list_modules(self) -> list[MODULEENTRY32]:
|
|
355
|
+
"""List all of the modules loaded to process."""
|
|
356
|
+
|
|
357
|
+
# Modules snapshot functions
|
|
358
|
+
close_handle = ctypes.windll.kernel32.CloseHandle
|
|
359
|
+
module32_next = ctypes.windll.kernel32.Module32Next
|
|
360
|
+
module32_first = ctypes.windll.kernel32.Module32First
|
|
361
|
+
create_tool_help32_snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
|
362
|
+
|
|
363
|
+
# Create snapshot to iterate process modules
|
|
364
|
+
if (snapshot := create_tool_help32_snapshot(TH32CS_SNAPMODULE, self.pid)) == -1:
|
|
365
|
+
raise Exception("Unable to create snapshot to iterate process modules")
|
|
366
|
+
|
|
367
|
+
# Result list of process modules
|
|
368
|
+
process_modules = list()
|
|
369
|
+
|
|
370
|
+
# Process modules enumeration
|
|
371
|
+
try:
|
|
372
|
+
|
|
373
|
+
# Modules entry to iterate process modules
|
|
374
|
+
modules_entry = MODULEENTRY32()
|
|
375
|
+
modules_entry.dw_size = ctypes.sizeof(MODULEENTRY32)
|
|
376
|
+
|
|
377
|
+
# Retrieve first process module snapshot
|
|
378
|
+
if not module32_first(snapshot, ctypes.byref(modules_entry)):
|
|
379
|
+
raise Exception("Unable to get first process module to save to snapshot")
|
|
380
|
+
|
|
381
|
+
# Iterate all of the process modules using snapshot
|
|
382
|
+
while True:
|
|
383
|
+
|
|
384
|
+
# Create copy of module entry
|
|
385
|
+
ctypes.memmove(ctypes.byref(module_entry_copy := MODULEENTRY32()), ctypes.byref(modules_entry), ctypes.sizeof(MODULEENTRY32))
|
|
386
|
+
|
|
387
|
+
# Save process module to list
|
|
388
|
+
process_modules.append(module_entry_copy)
|
|
389
|
+
|
|
390
|
+
# snapshot is over
|
|
391
|
+
if not module32_next(snapshot, ctypes.byref(modules_entry)):
|
|
392
|
+
break
|
|
393
|
+
|
|
394
|
+
finally:
|
|
395
|
+
|
|
396
|
+
# Close snapshot handle
|
|
397
|
+
close_handle(snapshot)
|
|
398
|
+
|
|
399
|
+
return process_modules
|
|
400
|
+
|
|
401
|
+
def list_memory_regions(self, allowed_states: list[int] = list(), allowed_protects: list[int] = list(), allowed_types: list[int] = list()) -> list[MEMORY_BASIC_INFORMATION]:
|
|
402
|
+
"""List all of the aviable process memory regions."""
|
|
403
|
+
|
|
404
|
+
# Result list of memory regions
|
|
405
|
+
memory_regions = list()
|
|
406
|
+
|
|
407
|
+
# Iterate all of the process memory regions
|
|
408
|
+
current_address = 0
|
|
409
|
+
while True:
|
|
410
|
+
|
|
411
|
+
# Prepare arguments
|
|
412
|
+
memory_basic_information = MEMORY_BASIC_INFORMATION()
|
|
413
|
+
|
|
414
|
+
# Try to get memory region information using it's address
|
|
415
|
+
if (result := _nt_virtual_query_memory(self.handle, current_address, 0, ctypes.byref(memory_basic_information), ctypes.sizeof(memory_basic_information), None)):
|
|
416
|
+
|
|
417
|
+
# If result failed due out of process memory space bounds
|
|
418
|
+
if result == 0xC000000D:
|
|
419
|
+
break
|
|
420
|
+
|
|
421
|
+
else:
|
|
422
|
+
raise Exception("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
423
|
+
|
|
424
|
+
# Move to next memory region
|
|
425
|
+
if (next_address := current_address + memory_basic_information.region_size) <= current_address:
|
|
426
|
+
break
|
|
427
|
+
|
|
428
|
+
# Overriding current address
|
|
429
|
+
current_address = next_address
|
|
430
|
+
|
|
431
|
+
# Save memory regions after filtering it
|
|
432
|
+
if allowed_states and memory_basic_information.state not in allowed_states:
|
|
433
|
+
continue
|
|
434
|
+
|
|
435
|
+
if allowed_protects and memory_basic_information.protect not in allowed_protects:
|
|
436
|
+
continue
|
|
437
|
+
|
|
438
|
+
if allowed_types and memory_basic_information.type not in allowed_types:
|
|
439
|
+
continue
|
|
440
|
+
|
|
441
|
+
# Save memory region information if filter passed
|
|
442
|
+
memory_regions.append(memory_basic_information)
|
|
443
|
+
|
|
444
|
+
return memory_regions
|
|
445
|
+
|
|
446
|
+
def get_module(self, name: str) -> MODULEENTRY32:
|
|
447
|
+
"""Get process module information."""
|
|
448
|
+
|
|
449
|
+
# Process modules enumeration
|
|
450
|
+
for module in self.list_modules():
|
|
451
|
+
|
|
452
|
+
# If module have a required name
|
|
453
|
+
if module.name.lower() == name.strip().lower():
|
|
454
|
+
return module
|
|
455
|
+
|
|
456
|
+
raise Exception("Module with `%s` name not found" % name)
|
|
457
|
+
|
|
458
|
+
def get_memory_region(self, address: int) -> MEMORY_BASIC_INFORMATION:
|
|
459
|
+
"""Get memory region information located at given address."""
|
|
460
|
+
|
|
461
|
+
# Prepare arguments
|
|
462
|
+
memory_basic_information = MEMORY_BASIC_INFORMATION()
|
|
463
|
+
|
|
464
|
+
# Try to get memory region information using it's address
|
|
465
|
+
if (result := _nt_virtual_query_memory(self.handle, address, 0, ctypes.byref(memory_basic_information), ctypes.sizeof(memory_basic_information), None)):
|
|
466
|
+
raise Exception("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
467
|
+
|
|
468
|
+
return memory_basic_information
|
|
469
|
+
|
|
470
|
+
def is_64bit(self) -> bool:
|
|
471
|
+
"""Check if process is 64 bit."""
|
|
472
|
+
|
|
473
|
+
# Prepare arguments
|
|
474
|
+
wow64_info = ctypes.c_void_p()
|
|
475
|
+
|
|
476
|
+
# Try to query process information
|
|
477
|
+
if (result := _nt_query_information_process(self.handle, 26, ctypes.byref(wow64_info), ctypes.sizeof(wow64_info), ctypes.byref(ctypes.c_ulong()))):
|
|
478
|
+
raise Exception("NtQueryInformationProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
479
|
+
|
|
480
|
+
return wow64_info.value is None
|
|
481
|
+
|
|
482
|
+
def allocate_memory(self, address: int = 0, size: int = 4096, memory_type: int = MEM_COMMIT, memory_protect: int = PAGE_READWRITE) -> tuple[int, int]:
|
|
483
|
+
"""Allocate process memory."""
|
|
484
|
+
|
|
485
|
+
# Prepare arguments
|
|
486
|
+
allocation_address = ctypes.c_void_p(address)
|
|
487
|
+
allocation_size = ctypes.c_ulong(size)
|
|
488
|
+
|
|
489
|
+
# Try to allocate process memory
|
|
490
|
+
if (result := _nt_allocate_virtual_memory(self.handle, ctypes.byref(allocation_address), 0, ctypes.byref(allocation_size), memory_type, memory_protect)):
|
|
491
|
+
raise Exception("NtAllocateVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
492
|
+
|
|
493
|
+
return allocation_address.value or 0, allocation_size.value or 0
|
|
494
|
+
|
|
495
|
+
def free_memory(self, address: int, size: int = 0, memory_type: int = MEM_RELEASE) -> int:
|
|
496
|
+
"""Deallocate process memory."""
|
|
497
|
+
|
|
498
|
+
# Prepare arguments
|
|
499
|
+
allocation_address = ctypes.c_void_p(address)
|
|
500
|
+
allocation_size = ctypes.c_ulong(size)
|
|
501
|
+
|
|
502
|
+
# Try to deallocate process memory
|
|
503
|
+
if (result := _nt_free_virtual_memory(self.handle, ctypes.byref(allocation_address), ctypes.byref(allocation_size), memory_type)):
|
|
504
|
+
raise Exception("NtFreeVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
505
|
+
|
|
506
|
+
return allocation_size.value or 0
|
|
507
|
+
|
|
508
|
+
def protect_memory(self, address: int, size: int, memory_protect: int) -> int:
|
|
509
|
+
"""Change process memory protection."""
|
|
510
|
+
|
|
511
|
+
# Prepare arguments
|
|
512
|
+
protect_address = ctypes.c_void_p(address)
|
|
513
|
+
protect_size = ctypes.c_ulong(size)
|
|
514
|
+
|
|
515
|
+
# Try to change memory protection
|
|
516
|
+
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()))):
|
|
517
|
+
raise Exception("NtProtectVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
518
|
+
|
|
519
|
+
return memory_old_protect.value or 0
|
|
520
|
+
|
|
521
|
+
def pattern_scan(self, pattern: str, allowed_states: list[int] = [MEM_COMMIT], allowed_protects: list[int] = [PAGE_READWRITE], allowed_types: list[int] = list(), start_address: int | None = None, end_address: int | None = None, return_first: int | None = None) -> list[int]:
|
|
522
|
+
"""Scan process and return address that validates given pattern hex byte mask, use `??` to wildcard byte, for example - "14 00 00 00 DB FF ?? ?? FF FF 00 00"."""
|
|
523
|
+
|
|
524
|
+
# Validate given pattern
|
|
525
|
+
for byte in pattern.strip().split():
|
|
526
|
+
|
|
527
|
+
# If pattern byte is digit or wildcard
|
|
528
|
+
if len(byte) == 2 and [item in "0123456789ABCDEFabcdef" for item in byte].count(True) == 2:
|
|
529
|
+
continue
|
|
530
|
+
|
|
531
|
+
# If pattern byte is wildcard
|
|
532
|
+
if byte == "??":
|
|
533
|
+
continue
|
|
534
|
+
|
|
535
|
+
raise Exception("Invalid pattern: `%s`" % pattern)
|
|
536
|
+
|
|
537
|
+
# Result list of found addresses
|
|
538
|
+
found_addresses = list()
|
|
539
|
+
|
|
540
|
+
# iterate memory regions and finding addresses at them
|
|
541
|
+
for region in self.list_memory_regions(allowed_states, allowed_protects, allowed_types):
|
|
542
|
+
|
|
543
|
+
# If required amount of addresses found
|
|
544
|
+
if return_first is not None and return_first <= 0:
|
|
545
|
+
break
|
|
546
|
+
|
|
547
|
+
# Region bounds
|
|
548
|
+
region_start = region.base_address
|
|
549
|
+
region_end = region.base_address + region.region_size
|
|
550
|
+
|
|
551
|
+
# If start address is defined
|
|
552
|
+
if start_address is not None and region_end <= start_address:
|
|
553
|
+
continue
|
|
554
|
+
|
|
555
|
+
# If end address is defined
|
|
556
|
+
if end_address is not None and region_start >= end_address:
|
|
557
|
+
continue
|
|
558
|
+
|
|
559
|
+
# Scan params
|
|
560
|
+
scan_start = max(region_start, start_address) if start_address is not None else region_start
|
|
561
|
+
scan_size = min(region_end, end_address) - scan_start if end_address is not None else region_end - scan_start
|
|
562
|
+
|
|
563
|
+
# Read region as bytes
|
|
564
|
+
try:
|
|
565
|
+
read_region_bytes = self.read_bytes(scan_start, scan_size)
|
|
566
|
+
|
|
567
|
+
except Exception:
|
|
568
|
+
continue
|
|
569
|
+
|
|
570
|
+
# Pattern scan region using SIMD KMP algorithm
|
|
571
|
+
self.__kmp.scanAOB(read_region_bytes, scan_size, pattern.strip().encode(), ctypes.c_uint64(scan_start), ctypes.c_uint64(return_first if return_first is not None else 0), ctypes.byref(scan_result := PatternScanBuffer()))
|
|
572
|
+
|
|
573
|
+
# Read result addresses
|
|
574
|
+
addresses = scan_result.read()
|
|
575
|
+
|
|
576
|
+
# Free scan result memory
|
|
577
|
+
scan_result.free(self.__kmp)
|
|
578
|
+
|
|
579
|
+
# If return first is defined
|
|
580
|
+
if return_first is not None and addresses:
|
|
581
|
+
return_first -= len(addresses)
|
|
582
|
+
|
|
583
|
+
# Save found addresses
|
|
584
|
+
if addresses:
|
|
585
|
+
found_addresses.extend(addresses)
|
|
586
|
+
|
|
587
|
+
return found_addresses
|
|
588
|
+
|
|
589
|
+
def read_int8(self, address: int) -> int:
|
|
590
|
+
"""Read 1 byte signed integer value located at given address."""
|
|
591
|
+
|
|
592
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8()), ctypes.sizeof(buffer), None)):
|
|
593
|
+
|
|
594
|
+
# If result failed due memory protection
|
|
595
|
+
if result == 0x8000000D:
|
|
596
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
597
|
+
|
|
598
|
+
else:
|
|
599
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
600
|
+
|
|
601
|
+
return buffer.value
|
|
602
|
+
|
|
603
|
+
def read_int16(self, address: int) -> int:
|
|
604
|
+
"""Read 2 byte signed integer value located at given address."""
|
|
605
|
+
|
|
606
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int16()), ctypes.sizeof(buffer), None)):
|
|
607
|
+
|
|
608
|
+
# If result failed due memory protection
|
|
609
|
+
if result == 0x8000000D:
|
|
610
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
611
|
+
|
|
612
|
+
else:
|
|
613
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
614
|
+
|
|
615
|
+
return buffer.value
|
|
616
|
+
|
|
617
|
+
def read_int32(self, address: int) -> int:
|
|
618
|
+
"""Read 4 byte signed integer value located at given address."""
|
|
619
|
+
|
|
620
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int32()), ctypes.sizeof(buffer), None)):
|
|
621
|
+
|
|
622
|
+
# If result failed due memory protection
|
|
623
|
+
if result == 0x8000000D:
|
|
624
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
625
|
+
|
|
626
|
+
else:
|
|
627
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
628
|
+
|
|
629
|
+
return buffer.value
|
|
630
|
+
|
|
631
|
+
def read_int64(self, address: int) -> int:
|
|
632
|
+
"""Read 8 byte signed integer value located at given address."""
|
|
633
|
+
|
|
634
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int64()), ctypes.sizeof(buffer), None)):
|
|
635
|
+
|
|
636
|
+
# If result failed due memory protection
|
|
637
|
+
if result == 0x8000000D:
|
|
638
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
639
|
+
|
|
640
|
+
else:
|
|
641
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
642
|
+
|
|
643
|
+
return buffer.value
|
|
644
|
+
|
|
645
|
+
def read_uint8(self, address: int) -> int:
|
|
646
|
+
"""Read 1 byte unsigned integer value located at given address."""
|
|
647
|
+
|
|
648
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8()), ctypes.sizeof(buffer), None)):
|
|
649
|
+
|
|
650
|
+
# If result failed due memory protection
|
|
651
|
+
if result == 0x8000000D:
|
|
652
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
653
|
+
|
|
654
|
+
else:
|
|
655
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
656
|
+
|
|
657
|
+
def read_uint16(self, address: int) -> int:
|
|
658
|
+
"""Read 2 byte unsigned integer value located at given address."""
|
|
659
|
+
|
|
660
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint16()), ctypes.sizeof(buffer), None)):
|
|
661
|
+
|
|
662
|
+
# If result failed due memory protection
|
|
663
|
+
if result == 0x8000000D:
|
|
664
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
665
|
+
|
|
666
|
+
else:
|
|
667
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
668
|
+
|
|
669
|
+
def read_uint32(self, address: int) -> int:
|
|
670
|
+
"""Read 4 byte unsigned integer value located at given address."""
|
|
671
|
+
|
|
672
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint32()), ctypes.sizeof(buffer), None)):
|
|
673
|
+
|
|
674
|
+
# If result failed due memory protection
|
|
675
|
+
if result == 0x8000000D:
|
|
676
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
677
|
+
|
|
678
|
+
else:
|
|
679
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
680
|
+
|
|
681
|
+
def read_uint64(self, address: int) -> int:
|
|
682
|
+
"""Read 8 byte unsigned integer value located at given address."""
|
|
683
|
+
|
|
684
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint64()), ctypes.sizeof(buffer), None)):
|
|
685
|
+
|
|
686
|
+
# If result failed due memory protection
|
|
687
|
+
if result == 0x8000000D:
|
|
688
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
689
|
+
|
|
690
|
+
else:
|
|
691
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
692
|
+
|
|
693
|
+
def read_float32(self, address: int) -> int:
|
|
694
|
+
"""Read 4 byte floating-point digit value located at given address."""
|
|
695
|
+
|
|
696
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_float()), ctypes.sizeof(buffer), None)):
|
|
697
|
+
|
|
698
|
+
# If result failed due memory protection
|
|
699
|
+
if result == 0x8000000D:
|
|
700
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
701
|
+
|
|
702
|
+
else:
|
|
703
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
704
|
+
|
|
705
|
+
def read_float64(self, address: int) -> int:
|
|
706
|
+
"""Read 8 byte floating-point digit value located at given address."""
|
|
707
|
+
|
|
708
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_double()), ctypes.sizeof(buffer), None)):
|
|
709
|
+
|
|
710
|
+
# If result failed due memory protection
|
|
711
|
+
if result == 0x8000000D:
|
|
712
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
713
|
+
|
|
714
|
+
else:
|
|
715
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
716
|
+
|
|
717
|
+
def read_bytes(self, address: int, size: int) -> bytes:
|
|
718
|
+
"""Read bytes array of variadic size located at given address."""
|
|
719
|
+
|
|
720
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := (ctypes.c_int8 * size)()), ctypes.sizeof(buffer), None)):
|
|
721
|
+
|
|
722
|
+
# If result failed due memory protection
|
|
723
|
+
if result == 0x8000000D:
|
|
724
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
725
|
+
|
|
726
|
+
else:
|
|
727
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
728
|
+
|
|
729
|
+
return bytes(buffer)
|
|
730
|
+
|
|
731
|
+
def read_buffer[T](self, address: int, buffer: T) -> T:
|
|
732
|
+
"""Read size of buffer byte value located at given address to buffer. Buffer have to be able passed at `ctypes.byref` and `ctype.sizeof`."""
|
|
733
|
+
|
|
734
|
+
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
735
|
+
|
|
736
|
+
# If result failed due memory protection
|
|
737
|
+
if result == 0x8000000D:
|
|
738
|
+
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
739
|
+
|
|
740
|
+
else:
|
|
741
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
742
|
+
|
|
743
|
+
return buffer
|
|
744
|
+
|
|
745
|
+
def write_int8(self, address: int, value: int) -> None:
|
|
746
|
+
"""Write 1 byte signed integer value at given address."""
|
|
747
|
+
|
|
748
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8(value)), ctypes.sizeof(buffer), None)):
|
|
749
|
+
|
|
750
|
+
# If result failed due memory protection
|
|
751
|
+
if result == 0x8000000D:
|
|
752
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
753
|
+
|
|
754
|
+
else:
|
|
755
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
756
|
+
|
|
757
|
+
def write_int16(self, address: int, value: int) -> None:
|
|
758
|
+
"""Write 2 byte signed integer value at given address."""
|
|
759
|
+
|
|
760
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int16(value)), ctypes.sizeof(buffer), None)):
|
|
761
|
+
|
|
762
|
+
# If result failed due memory protection
|
|
763
|
+
if result == 0x8000000D:
|
|
764
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
765
|
+
|
|
766
|
+
else:
|
|
767
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
768
|
+
|
|
769
|
+
def write_int32(self, address: int, value: int) -> None:
|
|
770
|
+
"""Write 4 byte signed integer value at given address."""
|
|
771
|
+
|
|
772
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int32(value)), ctypes.sizeof(buffer), None)):
|
|
773
|
+
|
|
774
|
+
# If result failed due memory protection
|
|
775
|
+
if result == 0x8000000D:
|
|
776
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
777
|
+
|
|
778
|
+
else:
|
|
779
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
780
|
+
|
|
781
|
+
def write_int64(self, address: int, value: int) -> None:
|
|
782
|
+
"""Write 8 byte signed integer value at given address."""
|
|
783
|
+
|
|
784
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int64(value)), ctypes.sizeof(buffer), None)):
|
|
785
|
+
|
|
786
|
+
# If result failed due memory protection
|
|
787
|
+
if result == 0x8000000D:
|
|
788
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
789
|
+
|
|
790
|
+
else:
|
|
791
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
792
|
+
|
|
793
|
+
def write_uint8(self, address: int, value: int) -> None:
|
|
794
|
+
"""Write 1 byte unsigned integer value at given address."""
|
|
795
|
+
|
|
796
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8(value)), ctypes.sizeof(buffer), None)):
|
|
797
|
+
|
|
798
|
+
# If result failed due memory protection
|
|
799
|
+
if result == 0x8000000D:
|
|
800
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
801
|
+
|
|
802
|
+
else:
|
|
803
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
804
|
+
|
|
805
|
+
def write_uint16(self, address: int, value: int) -> None:
|
|
806
|
+
"""Write 2 byte unsigned integer value at given address."""
|
|
807
|
+
|
|
808
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint16(value)), ctypes.sizeof(buffer), None)):
|
|
809
|
+
|
|
810
|
+
# If result failed due memory protection
|
|
811
|
+
if result == 0x8000000D:
|
|
812
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
813
|
+
|
|
814
|
+
else:
|
|
815
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
816
|
+
|
|
817
|
+
def write_uint32(self, address: int, value: int) -> None:
|
|
818
|
+
"""Write 4 byte unsigned integer value at given address."""
|
|
819
|
+
|
|
820
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint32(value)), ctypes.sizeof(buffer), None)):
|
|
821
|
+
|
|
822
|
+
# If result failed due memory protection
|
|
823
|
+
if result == 0x8000000D:
|
|
824
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
825
|
+
|
|
826
|
+
else:
|
|
827
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
828
|
+
|
|
829
|
+
def write_uint64(self, address: int, value: int) -> None:
|
|
830
|
+
"""Write 8 byte unsigned integer value at given address."""
|
|
831
|
+
|
|
832
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint64(value)), ctypes.sizeof(buffer), None)):
|
|
833
|
+
|
|
834
|
+
# If result failed due memory protection
|
|
835
|
+
if result == 0x8000000D:
|
|
836
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
837
|
+
|
|
838
|
+
else:
|
|
839
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
840
|
+
|
|
841
|
+
def write_bytes(self, address: int, value: bytes) -> None:
|
|
842
|
+
"""Write bytes array of variadic size at given address."""
|
|
843
|
+
|
|
844
|
+
if (result := _nt_write_virtual_memory(self.handle, address, value, len(value), None)):
|
|
845
|
+
|
|
846
|
+
# If result failed due memory protection
|
|
847
|
+
if result == 0x8000000D:
|
|
848
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
849
|
+
|
|
850
|
+
else:
|
|
851
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
852
|
+
|
|
853
|
+
def write_buffer(self, address: int, buffer: typing.Any) -> None:
|
|
854
|
+
"""Write size of buffer byte value at given address. Buffer have to be able passed at `ctypes.byref` and `ctype.sizeof`."""
|
|
855
|
+
|
|
856
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
857
|
+
|
|
858
|
+
# If result failed due memory protection
|
|
859
|
+
if result == 0x8000000D:
|
|
860
|
+
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
861
|
+
|
|
862
|
+
else:
|
|
863
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
864
|
+
|
|
865
|
+
def close(self) -> None:
|
|
866
|
+
"""Close opened process using it's handle, have to be called once on stop interacting with process."""
|
|
867
|
+
|
|
868
|
+
# Close process
|
|
869
|
+
_nt_close(self.handle)
|
|
870
|
+
|
|
871
|
+
# ==-------------------------------------------------------------------== #
|
|
872
|
+
# Private methods #
|
|
873
|
+
# ==-------------------------------------------------------------------== #
|
|
874
|
+
def __init_with_pid(self, pid: int, access: int, process_name: str | None = None) -> int:
|
|
875
|
+
"""Open process handle by it's ID with desired access."""
|
|
876
|
+
|
|
877
|
+
# Iterate all of the processes if name not defined
|
|
878
|
+
for process in list_processes():
|
|
879
|
+
|
|
880
|
+
# If process have a reqired name
|
|
881
|
+
if process["id"] == pid:
|
|
882
|
+
|
|
883
|
+
process_name = process["name"]
|
|
884
|
+
break
|
|
885
|
+
|
|
886
|
+
else:
|
|
887
|
+
raise Exception("Process with `%s` ID not found" % pid)
|
|
888
|
+
|
|
889
|
+
# Prepare arguments
|
|
890
|
+
object_attributes = OBJECT_ATTRIBUTES()
|
|
891
|
+
object_attributes.length = ctypes.sizeof(OBJECT_ATTRIBUTES)
|
|
892
|
+
|
|
893
|
+
client_id = CLIENT_ID()
|
|
894
|
+
client_id.unique_process = pid
|
|
895
|
+
|
|
896
|
+
# Try to open process using it's ID
|
|
897
|
+
if (result := _nt_open_process(ctypes.byref(handle := ctypes.c_void_p()), access, ctypes.byref(object_attributes), ctypes.byref(client_id))):
|
|
898
|
+
|
|
899
|
+
# If result failed due process ID not found
|
|
900
|
+
if result == 0xC000000B:
|
|
901
|
+
raise Exception("Process with `%s` ID not found" % pid)
|
|
902
|
+
|
|
903
|
+
else:
|
|
904
|
+
raise Exception("NtOpenProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
905
|
+
|
|
906
|
+
return handle.value, process_name, pid
|
|
907
|
+
|
|
908
|
+
def __init_with_name(self, name: str, access: int) -> int:
|
|
909
|
+
"""Open process hanle by it's name with desired access."""
|
|
910
|
+
|
|
911
|
+
# Iterate all of the processes using snapshot
|
|
912
|
+
for process in list_processes():
|
|
913
|
+
|
|
914
|
+
# If process have a reqired name
|
|
915
|
+
if process["name"].lower() == name.strip().lower():
|
|
916
|
+
return self.__init_with_pid(process["id"], access, process["name"])
|
|
917
|
+
|
|
918
|
+
raise Exception("Process with `%s` name not found" % name)
|