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.
@@ -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)