ntmemoryapi 2.0.2__tar.gz → 2.1.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.0.2 → ntmemoryapi-2.1.0}/PKG-INFO +1 -1
- {ntmemoryapi-2.0.2 → ntmemoryapi-2.1.0}/pyproject.toml +1 -1
- {ntmemoryapi-2.0.2 → ntmemoryapi-2.1.0}/src/ntmemoryapi/__init__.py +55 -61
- ntmemoryapi-2.1.0/src/ntmemoryapi/errors.py +103 -0
- {ntmemoryapi-2.0.2 → ntmemoryapi-2.1.0}/src/ntmemoryapi/misc.py +7 -4
- {ntmemoryapi-2.0.2 → ntmemoryapi-2.1.0}/README.md +0 -0
- {ntmemoryapi-2.0.2 → ntmemoryapi-2.1.0}/src/ntmemoryapi/embed.py +0 -0
|
@@ -12,6 +12,7 @@ import psutil
|
|
|
12
12
|
# Local imports
|
|
13
13
|
from .misc import *
|
|
14
14
|
from .embed import *
|
|
15
|
+
from .errors import *
|
|
15
16
|
|
|
16
17
|
# ==-------------------------------------------------------------------== #
|
|
17
18
|
# Static and global variables, constans #
|
|
@@ -76,7 +77,7 @@ class CLIENT_ID(ctypes.Structure):
|
|
|
76
77
|
|
|
77
78
|
_fields_ = [
|
|
78
79
|
("unique_process", ctypes.c_void_p),
|
|
79
|
-
("unique_thread", ctypes.c_void_p)
|
|
80
|
+
("unique_thread", ctypes.c_void_p),
|
|
80
81
|
]
|
|
81
82
|
|
|
82
83
|
|
|
@@ -89,7 +90,7 @@ class OBJECT_ATTRIBUTES(ctypes.Structure):
|
|
|
89
90
|
("object_name", ctypes.c_void_p),
|
|
90
91
|
("attributes", ctypes.c_ulong),
|
|
91
92
|
("security_descriptor", ctypes.c_void_p),
|
|
92
|
-
("security_quality_of_service", ctypes.c_void_p)
|
|
93
|
+
("security_quality_of_service", ctypes.c_void_p),
|
|
93
94
|
]
|
|
94
95
|
|
|
95
96
|
|
|
@@ -106,7 +107,7 @@ class MODULEENTRY32(ctypes.Structure):
|
|
|
106
107
|
("mod_base_size", ctypes.c_ulong),
|
|
107
108
|
("h_module", ctypes.c_void_p),
|
|
108
109
|
("sz_module", ctypes.c_char * 256),
|
|
109
|
-
("sz_exe_path", ctypes.c_char * 260)
|
|
110
|
+
("sz_exe_path", ctypes.c_char * 260),
|
|
110
111
|
]
|
|
111
112
|
|
|
112
113
|
@property
|
|
@@ -241,8 +242,8 @@ def list_processes(include_process_information: int = PROCESS_ID | PROCESS_NAME)
|
|
|
241
242
|
"""List all of the currently active system processes."""
|
|
242
243
|
|
|
243
244
|
# If `include_id` and `include_name` disabled both
|
|
244
|
-
if not include_process_information & PROCESS_ID
|
|
245
|
-
raise
|
|
245
|
+
if not (include_process_information & (PROCESS_ID | PROCESS_NAME)):
|
|
246
|
+
raise ValueError("Unable to disable ID and name including at once")
|
|
246
247
|
|
|
247
248
|
# Result list of processes
|
|
248
249
|
processes = []
|
|
@@ -251,18 +252,18 @@ def list_processes(include_process_information: int = PROCESS_ID | PROCESS_NAME)
|
|
|
251
252
|
for process in psutil.process_iter():
|
|
252
253
|
|
|
253
254
|
# Save process to list
|
|
254
|
-
|
|
255
|
+
process_info = {}
|
|
255
256
|
|
|
256
257
|
# If process ID including required
|
|
257
258
|
if include_process_information & PROCESS_ID:
|
|
258
|
-
|
|
259
|
+
process_info["id"] = process.pid
|
|
259
260
|
|
|
260
261
|
# If process ID including required
|
|
261
262
|
if include_process_information & PROCESS_NAME:
|
|
262
|
-
|
|
263
|
+
process_info["name"] = process.name()
|
|
263
264
|
|
|
264
265
|
# Save process information to list
|
|
265
|
-
processes.append(
|
|
266
|
+
processes.append(process_info)
|
|
266
267
|
|
|
267
268
|
return processes
|
|
268
269
|
|
|
@@ -287,10 +288,9 @@ def _get_be_buffer(soure_c_type: typing.Any) -> ctypes.BigEndianStructure:
|
|
|
287
288
|
class Process:
|
|
288
289
|
"""Basic class of process, have methods to manipulate it."""
|
|
289
290
|
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
|
|
291
|
+
# ==--------------------------------== #
|
|
292
|
+
# Public methods #
|
|
293
|
+
# ==--------------------------------== #
|
|
294
294
|
def __init__(self, name_or_pid: str | int, access: int = PROCESS_ALL_ACCESS) -> None:
|
|
295
295
|
"""Initialize instance to manipulate process."""
|
|
296
296
|
|
|
@@ -304,12 +304,12 @@ class Process:
|
|
|
304
304
|
self.handle, self.pid, self.name = self.__init_with_pid(name_or_pid, access)
|
|
305
305
|
|
|
306
306
|
case _:
|
|
307
|
-
raise
|
|
307
|
+
raise OpenProcessError("Invalid `name_or_pid` argument value, have to be `int` or `str` type")
|
|
308
308
|
|
|
309
309
|
# Try create file at temp directory to load SIMD KMP .dll (Module to blazingly fast pattern scaning)
|
|
310
310
|
try:
|
|
311
311
|
|
|
312
|
-
# Write library bytes
|
|
312
|
+
# Write library bytes directly from python list
|
|
313
313
|
with open("%s\\simdkmp.dll" % (appdata := os.getenv("APPDATA")), "wb") as file:
|
|
314
314
|
file.write(bytes(embed.kmp))
|
|
315
315
|
|
|
@@ -330,7 +330,7 @@ class Process:
|
|
|
330
330
|
|
|
331
331
|
# Create snapshot to iterate process modules
|
|
332
332
|
if (snapshot := create_tool_help32_snapshot(TH32CS_SNAPMODULE, self.pid)) == -1:
|
|
333
|
-
raise
|
|
333
|
+
raise ctypes.WinError(descr="Unable to create snapshot for `%s` PID" % self.pid)
|
|
334
334
|
|
|
335
335
|
# Result list of process modules
|
|
336
336
|
process_modules = []
|
|
@@ -344,7 +344,7 @@ class Process:
|
|
|
344
344
|
|
|
345
345
|
# Retrieve first process module snapshot
|
|
346
346
|
if not module32_first(snapshot, ctypes.byref(modules_entry)):
|
|
347
|
-
raise
|
|
347
|
+
raise ctypes.WinError(descr="Unable to get first proces module to save to snapshot for `%s` PID" % self.pid)
|
|
348
348
|
|
|
349
349
|
# Iterate all of the process modules using snapshot
|
|
350
350
|
while True:
|
|
@@ -387,7 +387,7 @@ class Process:
|
|
|
387
387
|
break
|
|
388
388
|
|
|
389
389
|
else:
|
|
390
|
-
raise
|
|
390
|
+
raise NtError("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
391
391
|
|
|
392
392
|
# Move to next memory region
|
|
393
393
|
if (next_address := current_address + memory_basic_information.size) <= current_address:
|
|
@@ -424,7 +424,7 @@ class Process:
|
|
|
424
424
|
if module.name.lower() == name.strip().lower():
|
|
425
425
|
return module
|
|
426
426
|
|
|
427
|
-
raise
|
|
427
|
+
raise ValueError("Module with `%s` name not found" % name)
|
|
428
428
|
|
|
429
429
|
def get_memory_region(self, address: int) -> MEMORY_BASIC_INFORMATION:
|
|
430
430
|
"""Get memory region information located at given address."""
|
|
@@ -434,7 +434,7 @@ class Process:
|
|
|
434
434
|
|
|
435
435
|
# Try to get memory region information using it's address
|
|
436
436
|
if (result := _nt_virtual_query_memory(self.handle, address, 0, ctypes.byref(memory_basic_information), ctypes.sizeof(memory_basic_information), None)):
|
|
437
|
-
raise
|
|
437
|
+
raise NtError("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
438
438
|
|
|
439
439
|
return memory_basic_information
|
|
440
440
|
|
|
@@ -446,7 +446,7 @@ class Process:
|
|
|
446
446
|
|
|
447
447
|
# Try to query process information
|
|
448
448
|
if (result := _nt_query_information_process(self.handle, 26, ctypes.byref(wow64_info), ctypes.sizeof(wow64_info), ctypes.byref(ctypes.c_ulong()))):
|
|
449
|
-
raise
|
|
449
|
+
raise NtError("NtQueryInformationProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
450
450
|
|
|
451
451
|
return wow64_info.value is None
|
|
452
452
|
|
|
@@ -459,7 +459,7 @@ class Process:
|
|
|
459
459
|
|
|
460
460
|
# Try to allocate process memory
|
|
461
461
|
if (result := _nt_allocate_virtual_memory(self.handle, ctypes.byref(allocation_address), 0, ctypes.byref(allocation_size), memory_type, memory_protect)):
|
|
462
|
-
raise
|
|
462
|
+
raise NtError("NtAllocateVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
463
463
|
|
|
464
464
|
return allocation_address.value or 0, allocation_size.value or 0
|
|
465
465
|
|
|
@@ -472,7 +472,7 @@ class Process:
|
|
|
472
472
|
|
|
473
473
|
# Try to deallocate process memory
|
|
474
474
|
if (result := _nt_free_virtual_memory(self.handle, ctypes.byref(allocation_address), ctypes.byref(allocation_size), memory_type)):
|
|
475
|
-
raise
|
|
475
|
+
raise NtError("NtFreeVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
476
476
|
|
|
477
477
|
return allocation_size.value or 0
|
|
478
478
|
|
|
@@ -485,7 +485,7 @@ class Process:
|
|
|
485
485
|
|
|
486
486
|
# Try to change memory protection
|
|
487
487
|
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
|
|
488
|
+
raise NtError("NtProtectVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
489
489
|
|
|
490
490
|
return memory_old_protect.value or 0
|
|
491
491
|
|
|
@@ -493,7 +493,7 @@ class Process:
|
|
|
493
493
|
"""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
494
|
|
|
495
495
|
# Validate given pattern
|
|
496
|
-
for byte in pattern.strip().split():
|
|
496
|
+
for byte in pattern.lower().strip().split():
|
|
497
497
|
|
|
498
498
|
# If pattern byte is not wildcard and valid
|
|
499
499
|
if len(byte) == 2 and [item in "0123456789ABCDEFabcdef" for item in byte].count(True) == 2:
|
|
@@ -503,7 +503,7 @@ class Process:
|
|
|
503
503
|
if byte == "??":
|
|
504
504
|
continue
|
|
505
505
|
|
|
506
|
-
raise
|
|
506
|
+
raise ValueError("Invalid pattern: `%s`" % pattern)
|
|
507
507
|
|
|
508
508
|
# Result list of found addresses
|
|
509
509
|
found_addresses = []
|
|
@@ -562,7 +562,7 @@ class Process:
|
|
|
562
562
|
|
|
563
563
|
# If result failed
|
|
564
564
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8()), ctypes.sizeof(buffer), None)):
|
|
565
|
-
raise
|
|
565
|
+
raise MemoryReadError(result, address)
|
|
566
566
|
|
|
567
567
|
return buffer.value
|
|
568
568
|
|
|
@@ -571,7 +571,7 @@ class Process:
|
|
|
571
571
|
|
|
572
572
|
# If result failed
|
|
573
573
|
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)):
|
|
574
|
-
raise
|
|
574
|
+
raise MemoryReadError(result, address)
|
|
575
575
|
|
|
576
576
|
return buffer.value
|
|
577
577
|
|
|
@@ -580,7 +580,7 @@ class Process:
|
|
|
580
580
|
|
|
581
581
|
# If result failed
|
|
582
582
|
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)):
|
|
583
|
-
raise
|
|
583
|
+
raise MemoryReadError(result, address)
|
|
584
584
|
|
|
585
585
|
return buffer.value
|
|
586
586
|
|
|
@@ -589,7 +589,7 @@ class Process:
|
|
|
589
589
|
|
|
590
590
|
# If result failed
|
|
591
591
|
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)):
|
|
592
|
-
raise
|
|
592
|
+
raise MemoryReadError(result, address)
|
|
593
593
|
|
|
594
594
|
return buffer.value
|
|
595
595
|
|
|
@@ -598,7 +598,7 @@ class Process:
|
|
|
598
598
|
|
|
599
599
|
# If result failed
|
|
600
600
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8()), ctypes.sizeof(buffer), None)):
|
|
601
|
-
raise
|
|
601
|
+
raise MemoryReadError(result, address)
|
|
602
602
|
|
|
603
603
|
return buffer.value
|
|
604
604
|
|
|
@@ -607,7 +607,7 @@ class Process:
|
|
|
607
607
|
|
|
608
608
|
# If result failed
|
|
609
609
|
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)):
|
|
610
|
-
raise
|
|
610
|
+
raise MemoryReadError(result, address)
|
|
611
611
|
|
|
612
612
|
return buffer.value
|
|
613
613
|
|
|
@@ -616,7 +616,7 @@ class Process:
|
|
|
616
616
|
|
|
617
617
|
# If result failed
|
|
618
618
|
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)):
|
|
619
|
-
raise
|
|
619
|
+
raise MemoryReadError(result, address)
|
|
620
620
|
|
|
621
621
|
return buffer.value
|
|
622
622
|
|
|
@@ -625,7 +625,7 @@ class Process:
|
|
|
625
625
|
|
|
626
626
|
# If result failed
|
|
627
627
|
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)):
|
|
628
|
-
raise
|
|
628
|
+
raise MemoryReadError(result, address)
|
|
629
629
|
|
|
630
630
|
return buffer.value
|
|
631
631
|
|
|
@@ -634,7 +634,7 @@ class Process:
|
|
|
634
634
|
|
|
635
635
|
# If result failed
|
|
636
636
|
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)):
|
|
637
|
-
raise
|
|
637
|
+
raise MemoryReadError(result, address)
|
|
638
638
|
|
|
639
639
|
return buffer.value
|
|
640
640
|
|
|
@@ -643,7 +643,7 @@ class Process:
|
|
|
643
643
|
|
|
644
644
|
# If result failed
|
|
645
645
|
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)):
|
|
646
|
-
raise
|
|
646
|
+
raise MemoryReadError(result, address)
|
|
647
647
|
|
|
648
648
|
return buffer.value
|
|
649
649
|
|
|
@@ -652,7 +652,7 @@ class Process:
|
|
|
652
652
|
|
|
653
653
|
# If result failed
|
|
654
654
|
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)):
|
|
655
|
-
raise
|
|
655
|
+
raise MemoryReadError(result, address)
|
|
656
656
|
|
|
657
657
|
return bytes(buffer)
|
|
658
658
|
|
|
@@ -661,7 +661,7 @@ class Process:
|
|
|
661
661
|
|
|
662
662
|
# If result failed
|
|
663
663
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
664
|
-
raise
|
|
664
|
+
raise MemoryReadError(result, address)
|
|
665
665
|
|
|
666
666
|
return buffer
|
|
667
667
|
|
|
@@ -670,42 +670,42 @@ class Process:
|
|
|
670
670
|
|
|
671
671
|
# If result failed
|
|
672
672
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8(value)), ctypes.sizeof(buffer), None)):
|
|
673
|
-
raise
|
|
673
|
+
raise MemoryWriteError(result, address)
|
|
674
674
|
|
|
675
675
|
def write_int16(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
676
676
|
"""Write 2 byte signed integer value at given address."""
|
|
677
677
|
|
|
678
678
|
# If result failed
|
|
679
679
|
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)):
|
|
680
|
-
raise
|
|
680
|
+
raise MemoryWriteError(result, address)
|
|
681
681
|
|
|
682
682
|
def write_int32(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
683
683
|
"""Write 4 byte signed integer value at given address."""
|
|
684
684
|
|
|
685
685
|
# If result failed
|
|
686
686
|
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)):
|
|
687
|
-
raise
|
|
687
|
+
raise MemoryWriteError(result, address)
|
|
688
688
|
|
|
689
689
|
def write_int64(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
690
690
|
"""Write 8 byte signed integer value at given address."""
|
|
691
691
|
|
|
692
692
|
# If result failed
|
|
693
693
|
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)):
|
|
694
|
-
raise
|
|
694
|
+
raise MemoryWriteError(result, address)
|
|
695
695
|
|
|
696
696
|
def write_uint8(self, address: int, value: int) -> None:
|
|
697
697
|
"""Write 1 byte unsigned integer value at given address."""
|
|
698
698
|
|
|
699
699
|
# If result failed
|
|
700
700
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8(value)), ctypes.sizeof(buffer), None)):
|
|
701
|
-
raise
|
|
701
|
+
raise MemoryWriteError(result, address)
|
|
702
702
|
|
|
703
703
|
def write_uint16(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
704
704
|
"""Write 2 byte unsigned integer value at given address."""
|
|
705
705
|
|
|
706
706
|
# If result failed
|
|
707
707
|
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)):
|
|
708
|
-
raise
|
|
708
|
+
raise MemoryWriteError(result, address)
|
|
709
709
|
|
|
710
710
|
def write_uint32(self, address: int, value: int, big_endian: bool = False) -> None:
|
|
711
711
|
"""Write 4 byte unsigned integer value at given address."""
|
|
@@ -719,35 +719,35 @@ class Process:
|
|
|
719
719
|
|
|
720
720
|
# If result failed
|
|
721
721
|
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)):
|
|
722
|
-
raise
|
|
722
|
+
raise MemoryWriteError(result, address)
|
|
723
723
|
|
|
724
724
|
def write_float32(self, address: int, value: float | int, big_endian: bool = False) -> float:
|
|
725
725
|
"""Write 4 byte floating-point digit value at given address."""
|
|
726
726
|
|
|
727
727
|
# If result failed
|
|
728
728
|
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)):
|
|
729
|
-
raise
|
|
729
|
+
raise MemoryWriteError(result, address)
|
|
730
730
|
|
|
731
731
|
def write_float64(self, address: int, value: float | int, big_endian: bool = False) -> float:
|
|
732
732
|
"""Write 8 byte floating-point digit value at given address."""
|
|
733
733
|
|
|
734
734
|
# If result failed
|
|
735
735
|
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)):
|
|
736
|
-
raise
|
|
736
|
+
raise MemoryWriteError(result, address)
|
|
737
737
|
|
|
738
738
|
def write_bytes(self, address: int, value: bytes, big_endian: bool = False) -> None:
|
|
739
739
|
"""Write bytes array of variadic size at given address."""
|
|
740
740
|
|
|
741
741
|
# If result failed
|
|
742
742
|
if (result := _nt_write_virtual_memory(self.handle, address, value[::-1] if big_endian else value, len(value), None)):
|
|
743
|
-
raise
|
|
743
|
+
raise MemoryWriteError(result, address)
|
|
744
744
|
|
|
745
745
|
def write_buffer(self, address: int, buffer: typing.Any) -> None:
|
|
746
746
|
"""Write size of buffer byte value at given address. Buffer have to be able passed at `ctypes.byref` and `ctype.sizeof`."""
|
|
747
747
|
|
|
748
748
|
# If result failed
|
|
749
749
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
750
|
-
raise
|
|
750
|
+
raise MemoryWriteError(result, address)
|
|
751
751
|
|
|
752
752
|
def close(self) -> None:
|
|
753
753
|
"""Close opened process using it's handle, have to be called once on stop interacting with process."""
|
|
@@ -755,9 +755,9 @@ class Process:
|
|
|
755
755
|
# Close process
|
|
756
756
|
_nt_close(self.handle)
|
|
757
757
|
|
|
758
|
-
#
|
|
759
|
-
# Private methods
|
|
760
|
-
#
|
|
758
|
+
# ==--------------------------------== #
|
|
759
|
+
# Private methods #
|
|
760
|
+
# ==--------------------------------== #
|
|
761
761
|
def __init_with_pid(self, pid: int, access: int, listed_processes: list[dict[str, int | str]] | None = None) -> tuple[int, int, str]:
|
|
762
762
|
"""Open process handle by it's ID with desired access."""
|
|
763
763
|
|
|
@@ -771,7 +771,7 @@ class Process:
|
|
|
771
771
|
break
|
|
772
772
|
|
|
773
773
|
else:
|
|
774
|
-
raise
|
|
774
|
+
raise OpenProcessError("Process with `%s` ID not found" % pid)
|
|
775
775
|
|
|
776
776
|
# Prepare arguments
|
|
777
777
|
object_attributes = OBJECT_ATTRIBUTES()
|
|
@@ -782,13 +782,7 @@ class Process:
|
|
|
782
782
|
|
|
783
783
|
# Try to open process using it's ID
|
|
784
784
|
if (result := _nt_open_process(ctypes.byref(handle := ctypes.c_void_p()), access, ctypes.byref(object_attributes), ctypes.byref(client_id))):
|
|
785
|
-
|
|
786
|
-
# If result failed due process ID not found
|
|
787
|
-
if result == 0xC000000B:
|
|
788
|
-
raise Exception("Process with `%s` ID not found" % pid)
|
|
789
|
-
|
|
790
|
-
else:
|
|
791
|
-
raise Exception("NtOpenProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
785
|
+
raise NtError("NtOpenProcess failed with status: 0x%s" % hex(result)[2:].upper())
|
|
792
786
|
|
|
793
787
|
return handle.value, pid, process_name
|
|
794
788
|
|
|
@@ -802,4 +796,4 @@ class Process:
|
|
|
802
796
|
if process["name"].lower() == name.strip().lower():
|
|
803
797
|
return self.__init_with_pid(process["id"], access, listed_processes)
|
|
804
798
|
|
|
805
|
-
raise
|
|
799
|
+
raise OpenProcessError("Process with `%s` name not found" % name)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# +-------------------------------------+
|
|
2
|
+
# | ~ Author : Xenely ~ |
|
|
3
|
+
# +=====================================+
|
|
4
|
+
# | GitHub: https://github.com/Xenely14 |
|
|
5
|
+
# | Discord: xenely |
|
|
6
|
+
# +-------------------------------------+
|
|
7
|
+
|
|
8
|
+
# ==-------------------------------------------------------------------== #
|
|
9
|
+
# Classes #
|
|
10
|
+
# ==-------------------------------------------------------------------== #
|
|
11
|
+
class NtError(Exception):
|
|
12
|
+
"""Exception that raises on `nt` functions calls fails."""
|
|
13
|
+
|
|
14
|
+
# ==--------------------------------== #
|
|
15
|
+
# Public methods #
|
|
16
|
+
# ==--------------------------------== #
|
|
17
|
+
def __init__(self, message: str, status: int | None) -> None:
|
|
18
|
+
"""Initialize instance of `nt` error."""
|
|
19
|
+
|
|
20
|
+
# Save initializer arguments as an attributes
|
|
21
|
+
self.message = message
|
|
22
|
+
self.status = status
|
|
23
|
+
|
|
24
|
+
# Call initializer of parrent class
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
|
|
27
|
+
def __str__(self) -> str:
|
|
28
|
+
"""Overload triggered on `str` cast calls"""
|
|
29
|
+
|
|
30
|
+
# If error status is defined
|
|
31
|
+
if self.status is not None:
|
|
32
|
+
return "Nt function failed with `%s` status: %s" % (self.get_hex_status(), self.message)
|
|
33
|
+
|
|
34
|
+
# If error status is not defined
|
|
35
|
+
return "Nt function failed: %s" % self.message
|
|
36
|
+
|
|
37
|
+
def get_hex_status(self, hex_prefix: str | None = "0x") -> str:
|
|
38
|
+
"""Get error `nt`status in hex string view."""
|
|
39
|
+
|
|
40
|
+
return "%s%08x" % (hex_prefix if hex_prefix is not None else "", self.status)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OpenProcessError(NtError):
|
|
44
|
+
"""Exception that raises on process open fail."""
|
|
45
|
+
|
|
46
|
+
# ==--------------------------------== #
|
|
47
|
+
# Public methods #
|
|
48
|
+
# ==--------------------------------== #
|
|
49
|
+
def __init__(self, message: str, status: int | None = None) -> None:
|
|
50
|
+
"""Initialize instance of `nt` error."""
|
|
51
|
+
|
|
52
|
+
# Call initializer of parrent class
|
|
53
|
+
super().__init__(message, status)
|
|
54
|
+
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
"""Overload triggered on `str` cast calls."""
|
|
57
|
+
|
|
58
|
+
# If error status is defined
|
|
59
|
+
if self.status is not None:
|
|
60
|
+
return "Unable to open process due `nt` function failed with `%s` status: %s" % (self.get_hex_status(), self.message)
|
|
61
|
+
|
|
62
|
+
# If error status is not defined
|
|
63
|
+
return "Unable to open process: %s" % self.message
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MemoryReadError(NtError):
|
|
67
|
+
"""Exception that raises on virtual memory read fail."""
|
|
68
|
+
|
|
69
|
+
# ==--------------------------------== #
|
|
70
|
+
# Public methods #
|
|
71
|
+
# ==--------------------------------== #
|
|
72
|
+
def __init__(self, status: int, address: int):
|
|
73
|
+
|
|
74
|
+
# Save initializer arguments as an attributes
|
|
75
|
+
self.address = address
|
|
76
|
+
|
|
77
|
+
# Call initializer of parrent class
|
|
78
|
+
super().__init__(None, status)
|
|
79
|
+
|
|
80
|
+
def __str__(self) -> str:
|
|
81
|
+
"""Overload triggered on `str` cast calls"""
|
|
82
|
+
|
|
83
|
+
return "Unalble to read memory at `0x%08x` address, `nt` function failed with `%s` status" % (self.address, self.get_hex_status())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class MemoryWriteError(NtError):
|
|
87
|
+
"""Exception that raises on virtual memory read fail."""
|
|
88
|
+
|
|
89
|
+
# ==--------------------------------== #
|
|
90
|
+
# Public methods #
|
|
91
|
+
# ==--------------------------------== #
|
|
92
|
+
def __init__(self, status: int, address: int):
|
|
93
|
+
|
|
94
|
+
# Save initializer arguments as an attributes
|
|
95
|
+
self.address = address
|
|
96
|
+
|
|
97
|
+
# Call initializer of parrent class
|
|
98
|
+
super().__init__(None, status)
|
|
99
|
+
|
|
100
|
+
def __str__(self) -> str:
|
|
101
|
+
"""Overload triggered on `str` cast calls"""
|
|
102
|
+
|
|
103
|
+
return "Unalble to write memory at `0x%08x` address, `nt` function failed with `%s` status" % (self.address, self.get_hex_status())
|
|
@@ -46,6 +46,9 @@ class DirectSyscallWrapper:
|
|
|
46
46
|
# Table containing all syscall wrappers allocations
|
|
47
47
|
registred_syscalls_table = dict()
|
|
48
48
|
|
|
49
|
+
# ==--------------------------------== #
|
|
50
|
+
# Public methods #
|
|
51
|
+
# ==--------------------------------== #
|
|
49
52
|
def __init__(self) -> None:
|
|
50
53
|
"""Creates wrapper instance to wrap syscalls."""
|
|
51
54
|
|
|
@@ -70,11 +73,11 @@ class DirectSyscallWrapper:
|
|
|
70
73
|
|
|
71
74
|
# Module loading
|
|
72
75
|
if not (module_handle := _LoadLibraryA(search_module)):
|
|
73
|
-
raise
|
|
76
|
+
raise ctypes.WinError(descr="Unable to load module `%s`" % search_module.decode())
|
|
74
77
|
|
|
75
78
|
# Retrieve function pointer
|
|
76
79
|
if not (serach_function := _GetProcAddress(module_handle, function_name.encode())):
|
|
77
|
-
raise
|
|
80
|
+
raise ctypes.WinError(descr="Function `%s` not found" % function_name)
|
|
78
81
|
|
|
79
82
|
# Syscall id
|
|
80
83
|
syscall_id = None
|
|
@@ -90,7 +93,7 @@ class DirectSyscallWrapper:
|
|
|
90
93
|
|
|
91
94
|
# If syscall ID not found
|
|
92
95
|
if syscall_id is None:
|
|
93
|
-
raise
|
|
96
|
+
raise ValueError("Syscall ID for function `%s` not found" % function_name)
|
|
94
97
|
|
|
95
98
|
# Convert syscall ID to hex-bytes list
|
|
96
99
|
syscall_id_bytes = [hex(item)[2:] if len(hex(item)[2:]) == 2 else "0" + hex(item)[2:] for item in bytes(ctypes.c_ushort(syscall_id))]
|
|
@@ -111,7 +114,7 @@ class DirectSyscallWrapper:
|
|
|
111
114
|
|
|
112
115
|
# Allocate buffer for function machine code
|
|
113
116
|
if not (shellcode_buffer := _VirtualAlloc(0, len(shellcode), 0x1000 | 0x2000, 0x04)):
|
|
114
|
-
raise
|
|
117
|
+
raise ctypes.WinError(descr="Unable to alloate memory for shellcode")
|
|
115
118
|
|
|
116
119
|
# Save allocated buffer into wrapped syscalls table
|
|
117
120
|
self.registred_syscalls_table[id(self)][function_name] = shellcode_buffer
|
|
File without changes
|
|
File without changes
|