ntmemoryapi 1.3.0__tar.gz → 1.5.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.
Potentially problematic release.
This version of ntmemoryapi might be problematic. Click here for more details.
- {ntmemoryapi-1.3.0 → ntmemoryapi-1.5.0}/PKG-INFO +1 -1
- {ntmemoryapi-1.3.0 → ntmemoryapi-1.5.0}/pyproject.toml +1 -1
- {ntmemoryapi-1.3.0 → ntmemoryapi-1.5.0}/src/ntmemoryapi/__init__.py +98 -168
- ntmemoryapi-1.5.0/src/ntmemoryapi/misc.py +138 -0
- ntmemoryapi-1.3.0/src/ntmemoryapi/misc.py +0 -102
- {ntmemoryapi-1.3.0 → ntmemoryapi-1.5.0}/README.md +0 -0
- {ntmemoryapi-1.3.0 → ntmemoryapi-1.5.0}/src/ntmemoryapi/embed.py +0 -0
|
@@ -13,6 +13,16 @@ import psutil
|
|
|
13
13
|
from .misc import *
|
|
14
14
|
from .embed import *
|
|
15
15
|
|
|
16
|
+
# ==-------------------------------------------------------------------== #
|
|
17
|
+
# Static and global variables, constans #
|
|
18
|
+
# ==-------------------------------------------------------------------== #
|
|
19
|
+
syscall_wrapper = DirectSyscallWrapper()
|
|
20
|
+
|
|
21
|
+
PROCESS_ID = 0x001
|
|
22
|
+
PROCESS_NAME = 0x010
|
|
23
|
+
PROCESS_USERNAME = 0x100
|
|
24
|
+
|
|
25
|
+
|
|
16
26
|
# ==-------------------------------------------------------------------== #
|
|
17
27
|
# C-consts #
|
|
18
28
|
# ==-------------------------------------------------------------------== #
|
|
@@ -158,7 +168,7 @@ class MEMORY_BASIC_INFORMATION(ctypes.Structure):
|
|
|
158
168
|
return self.m_partition_id
|
|
159
169
|
|
|
160
170
|
@property
|
|
161
|
-
def
|
|
171
|
+
def size(self) -> int:
|
|
162
172
|
"""Memory region size."""
|
|
163
173
|
|
|
164
174
|
return self.m_region_size
|
|
@@ -209,28 +219,30 @@ class PatternScanBuffer(ctypes.Structure):
|
|
|
209
219
|
# ==-------------------------------------------------------------------== #
|
|
210
220
|
# Syscalls #
|
|
211
221
|
# ==-------------------------------------------------------------------== #
|
|
212
|
-
|
|
213
|
-
|
|
222
|
+
syscall_wrapper = DirectSyscallWrapper()
|
|
223
|
+
|
|
224
|
+
_nt_close = syscall_wrapper.wrap("NtClose", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p])
|
|
225
|
+
_nt_open_process = syscall_wrapper.wrap("NtOpenProcess", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(OBJECT_ATTRIBUTES), ctypes.POINTER(CLIENT_ID)])
|
|
214
226
|
|
|
215
|
-
_nt_read_virtual_memory =
|
|
216
|
-
_nt_write_virtual_memory =
|
|
227
|
+
_nt_read_virtual_memory = syscall_wrapper.wrap("NtReadVirtualMemory", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong)])
|
|
228
|
+
_nt_write_virtual_memory = syscall_wrapper.wrap("NtWriteVirtualMemory", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong)])
|
|
217
229
|
|
|
218
|
-
_nt_virtual_query_memory =
|
|
219
|
-
_nt_query_information_process =
|
|
230
|
+
_nt_virtual_query_memory = syscall_wrapper.wrap("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)])
|
|
231
|
+
_nt_query_information_process = syscall_wrapper.wrap("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)])
|
|
220
232
|
|
|
221
|
-
_nt_free_virtual_memory =
|
|
222
|
-
_nt_allocate_virtual_memory =
|
|
223
|
-
_nt_protect_virtual_memory =
|
|
233
|
+
_nt_free_virtual_memory = syscall_wrapper.wrap("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])
|
|
234
|
+
_nt_allocate_virtual_memory = syscall_wrapper.wrap("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])
|
|
235
|
+
_nt_protect_virtual_memory = syscall_wrapper.wrap("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)])
|
|
224
236
|
|
|
225
237
|
|
|
226
238
|
# ==-------------------------------------------------------------------== #
|
|
227
239
|
# Functions #
|
|
228
240
|
# ==-------------------------------------------------------------------== #
|
|
229
|
-
def list_processes(
|
|
241
|
+
def list_processes(include_process_information: int = PROCESS_ID | PROCESS_NAME | PROCESS_USERNAME, current_username_only: bool = True) -> list[dict[str, int | str]]:
|
|
230
242
|
"""List all of the currently active system processes."""
|
|
231
243
|
|
|
232
244
|
# If `include_id` and `include_name` disabled both
|
|
233
|
-
if not
|
|
245
|
+
if not include_process_information & PROCESS_ID and not include_process_information & PROCESS_NAME:
|
|
234
246
|
raise Exception("Unable to disable ID and name including at once")
|
|
235
247
|
|
|
236
248
|
# Current process username
|
|
@@ -250,15 +262,15 @@ def list_processes(include_id: bool = True, include_name: bool = True, include_u
|
|
|
250
262
|
process_dict = dict()
|
|
251
263
|
|
|
252
264
|
# If process ID including required
|
|
253
|
-
if
|
|
265
|
+
if include_process_information & PROCESS_ID:
|
|
254
266
|
process_dict |= {"id": process.pid}
|
|
255
267
|
|
|
256
268
|
# If process ID including required
|
|
257
|
-
if
|
|
269
|
+
if include_process_information & PROCESS_NAME:
|
|
258
270
|
process_dict |= {"name": process.name()}
|
|
259
271
|
|
|
260
272
|
# If process username including required
|
|
261
|
-
if
|
|
273
|
+
if include_process_information & PROCESS_USERNAME:
|
|
262
274
|
process_dict |= {"username": process.username()}
|
|
263
275
|
|
|
264
276
|
# Save process information to list
|
|
@@ -277,16 +289,16 @@ class Process:
|
|
|
277
289
|
# Methods #
|
|
278
290
|
# ==-------------------------------------------------------------------== #
|
|
279
291
|
|
|
280
|
-
def __init__(self,
|
|
292
|
+
def __init__(self, name_or_name: int | str, access: int = PROCESS_ALL_ACCESS, current_username_only: bool = True) -> None:
|
|
281
293
|
"""Initialize instance to manipulate process."""
|
|
282
294
|
|
|
283
295
|
# Open process by it's ID or it's name
|
|
284
|
-
match
|
|
296
|
+
match name_or_name:
|
|
285
297
|
|
|
286
|
-
case pid if type(
|
|
298
|
+
case pid if type(name_or_name) is int:
|
|
287
299
|
self.handle, self.name, self.pid = self.__init_with_pid(pid, access, current_username_only)
|
|
288
300
|
|
|
289
|
-
case name if type(
|
|
301
|
+
case name if type(name_or_name) is str:
|
|
290
302
|
self.handle, self.name, self.pid = self.__init_with_name(name, access, current_username_only)
|
|
291
303
|
|
|
292
304
|
case _:
|
|
@@ -376,7 +388,7 @@ class Process:
|
|
|
376
388
|
raise Exception("NtVirtualQueryMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
377
389
|
|
|
378
390
|
# Move to next memory region
|
|
379
|
-
if (next_address := current_address + memory_basic_information.
|
|
391
|
+
if (next_address := current_address + memory_basic_information.size) <= current_address:
|
|
380
392
|
break
|
|
381
393
|
|
|
382
394
|
# Overriding current address
|
|
@@ -503,7 +515,7 @@ class Process:
|
|
|
503
515
|
|
|
504
516
|
# Region bounds
|
|
505
517
|
region_start = region.base_address
|
|
506
|
-
region_end = region.base_address + region.
|
|
518
|
+
region_end = region.base_address + region.size
|
|
507
519
|
|
|
508
520
|
# If start address is defined
|
|
509
521
|
if start_address is not None and region_end <= start_address:
|
|
@@ -546,278 +558,196 @@ class Process:
|
|
|
546
558
|
def read_int8(self, address: int) -> int:
|
|
547
559
|
"""Read 1 byte signed integer value located at given address."""
|
|
548
560
|
|
|
561
|
+
# If result failed
|
|
549
562
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8()), ctypes.sizeof(buffer), None)):
|
|
550
|
-
|
|
551
|
-
# If result failed due memory protection
|
|
552
|
-
if result == 0x8000000D:
|
|
553
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
554
|
-
|
|
555
|
-
else:
|
|
556
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
563
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
557
564
|
|
|
558
565
|
return buffer.value
|
|
559
566
|
|
|
560
567
|
def read_int16(self, address: int) -> int:
|
|
561
568
|
"""Read 2 byte signed integer value located at given address."""
|
|
562
569
|
|
|
570
|
+
# If result failed
|
|
563
571
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int16()), ctypes.sizeof(buffer), None)):
|
|
564
|
-
|
|
565
|
-
# If result failed due memory protection
|
|
566
|
-
if result == 0x8000000D:
|
|
567
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
568
|
-
|
|
569
|
-
else:
|
|
570
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
572
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
571
573
|
|
|
572
574
|
return buffer.value
|
|
573
575
|
|
|
574
576
|
def read_int32(self, address: int) -> int:
|
|
575
577
|
"""Read 4 byte signed integer value located at given address."""
|
|
576
578
|
|
|
579
|
+
# If result failed
|
|
577
580
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int32()), ctypes.sizeof(buffer), None)):
|
|
578
|
-
|
|
579
|
-
# If result failed due memory protection
|
|
580
|
-
if result == 0x8000000D:
|
|
581
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
582
|
-
|
|
583
|
-
else:
|
|
584
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
581
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
585
582
|
|
|
586
583
|
return buffer.value
|
|
587
584
|
|
|
588
585
|
def read_int64(self, address: int) -> int:
|
|
589
586
|
"""Read 8 byte signed integer value located at given address."""
|
|
590
587
|
|
|
588
|
+
# If result failed
|
|
591
589
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int64()), ctypes.sizeof(buffer), None)):
|
|
592
|
-
|
|
593
|
-
# If result failed due memory protection
|
|
594
|
-
if result == 0x8000000D:
|
|
595
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
596
|
-
|
|
597
|
-
else:
|
|
598
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
590
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
599
591
|
|
|
600
592
|
return buffer.value
|
|
601
593
|
|
|
602
594
|
def read_uint8(self, address: int) -> int:
|
|
603
595
|
"""Read 1 byte unsigned integer value located at given address."""
|
|
604
596
|
|
|
597
|
+
# If result failed
|
|
605
598
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8()), ctypes.sizeof(buffer), None)):
|
|
599
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
606
600
|
|
|
607
|
-
|
|
608
|
-
if result == 0x8000000D:
|
|
609
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
610
|
-
|
|
611
|
-
else:
|
|
612
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
601
|
+
return buffer.value
|
|
613
602
|
|
|
614
603
|
def read_uint16(self, address: int) -> int:
|
|
615
604
|
"""Read 2 byte unsigned integer value located at given address."""
|
|
616
605
|
|
|
606
|
+
# If result failed
|
|
617
607
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint16()), ctypes.sizeof(buffer), None)):
|
|
608
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
618
609
|
|
|
619
|
-
|
|
620
|
-
if result == 0x8000000D:
|
|
621
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
622
|
-
|
|
623
|
-
else:
|
|
624
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
610
|
+
return buffer.value
|
|
625
611
|
|
|
626
612
|
def read_uint32(self, address: int) -> int:
|
|
627
613
|
"""Read 4 byte unsigned integer value located at given address."""
|
|
628
614
|
|
|
615
|
+
# If result failed
|
|
629
616
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint32()), ctypes.sizeof(buffer), None)):
|
|
617
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
630
618
|
|
|
631
|
-
|
|
632
|
-
if result == 0x8000000D:
|
|
633
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
634
|
-
|
|
635
|
-
else:
|
|
636
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
619
|
+
return buffer.value
|
|
637
620
|
|
|
638
621
|
def read_uint64(self, address: int) -> int:
|
|
639
622
|
"""Read 8 byte unsigned integer value located at given address."""
|
|
640
623
|
|
|
624
|
+
# If result failed
|
|
641
625
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint64()), ctypes.sizeof(buffer), None)):
|
|
626
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
642
627
|
|
|
643
|
-
|
|
644
|
-
if result == 0x8000000D:
|
|
645
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
646
|
-
|
|
647
|
-
else:
|
|
648
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
628
|
+
return buffer.value
|
|
649
629
|
|
|
650
|
-
def read_float32(self, address: int) ->
|
|
630
|
+
def read_float32(self, address: int) -> float:
|
|
651
631
|
"""Read 4 byte floating-point digit value located at given address."""
|
|
652
632
|
|
|
633
|
+
# If result failed
|
|
653
634
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_float()), ctypes.sizeof(buffer), None)):
|
|
635
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
654
636
|
|
|
655
|
-
|
|
656
|
-
if result == 0x8000000D:
|
|
657
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
658
|
-
|
|
659
|
-
else:
|
|
660
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
637
|
+
return buffer.value
|
|
661
638
|
|
|
662
|
-
def read_float64(self, address: int) ->
|
|
639
|
+
def read_float64(self, address: int) -> float:
|
|
663
640
|
"""Read 8 byte floating-point digit value located at given address."""
|
|
664
641
|
|
|
642
|
+
# If result failed
|
|
665
643
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_double()), ctypes.sizeof(buffer), None)):
|
|
644
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
666
645
|
|
|
667
|
-
|
|
668
|
-
if result == 0x8000000D:
|
|
669
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
670
|
-
|
|
671
|
-
else:
|
|
672
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
646
|
+
return buffer.value
|
|
673
647
|
|
|
674
648
|
def read_bytes(self, address: int, size: int) -> bytes:
|
|
675
649
|
"""Read bytes array of variadic size located at given address."""
|
|
676
650
|
|
|
651
|
+
# If result failed
|
|
677
652
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer := (ctypes.c_int8 * size)()), ctypes.sizeof(buffer), None)):
|
|
678
|
-
|
|
679
|
-
# If result failed due memory protection
|
|
680
|
-
if result == 0x8000000D:
|
|
681
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
682
|
-
|
|
683
|
-
else:
|
|
684
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
653
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
685
654
|
|
|
686
655
|
return bytes(buffer)
|
|
687
656
|
|
|
688
|
-
def read_buffer
|
|
657
|
+
def read_buffer(self, address: int, buffer: typing.Any) -> typing.Any:
|
|
689
658
|
"""Read size of buffer byte value located at given address to buffer. Buffer have to be able passed at `ctypes.byref` and `ctype.sizeof`."""
|
|
690
659
|
|
|
660
|
+
# If result failed
|
|
691
661
|
if (result := _nt_read_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
692
|
-
|
|
693
|
-
# If result failed due memory protection
|
|
694
|
-
if result == 0x8000000D:
|
|
695
|
-
raise Exception("Unable to read value located at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
696
|
-
|
|
697
|
-
else:
|
|
698
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
662
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
699
663
|
|
|
700
664
|
return buffer
|
|
701
665
|
|
|
702
666
|
def write_int8(self, address: int, value: int) -> None:
|
|
703
667
|
"""Write 1 byte signed integer value at given address."""
|
|
704
668
|
|
|
669
|
+
# If result failed
|
|
705
670
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int8(value)), ctypes.sizeof(buffer), None)):
|
|
706
|
-
|
|
707
|
-
# If result failed due memory protection
|
|
708
|
-
if result == 0x8000000D:
|
|
709
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
710
|
-
|
|
711
|
-
else:
|
|
712
|
-
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
671
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
713
672
|
|
|
714
673
|
def write_int16(self, address: int, value: int) -> None:
|
|
715
674
|
"""Write 2 byte signed integer value at given address."""
|
|
716
675
|
|
|
676
|
+
# If result failed
|
|
717
677
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int16(value)), ctypes.sizeof(buffer), None)):
|
|
718
|
-
|
|
719
|
-
# If result failed due memory protection
|
|
720
|
-
if result == 0x8000000D:
|
|
721
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
722
|
-
|
|
723
|
-
else:
|
|
724
|
-
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
678
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
725
679
|
|
|
726
680
|
def write_int32(self, address: int, value: int) -> None:
|
|
727
681
|
"""Write 4 byte signed integer value at given address."""
|
|
728
682
|
|
|
683
|
+
# If result failed
|
|
729
684
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int32(value)), ctypes.sizeof(buffer), None)):
|
|
730
|
-
|
|
731
|
-
# If result failed due memory protection
|
|
732
|
-
if result == 0x8000000D:
|
|
733
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
734
|
-
|
|
735
|
-
else:
|
|
736
|
-
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
685
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
737
686
|
|
|
738
687
|
def write_int64(self, address: int, value: int) -> None:
|
|
739
688
|
"""Write 8 byte signed integer value at given address."""
|
|
740
689
|
|
|
741
690
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int64(value)), ctypes.sizeof(buffer), None)):
|
|
742
691
|
|
|
743
|
-
# If result failed
|
|
744
|
-
if result
|
|
745
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
746
|
-
|
|
747
|
-
else:
|
|
692
|
+
# If result failed
|
|
693
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_int64(value)), ctypes.sizeof(buffer), None)):
|
|
748
694
|
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
749
695
|
|
|
750
696
|
def write_uint8(self, address: int, value: int) -> None:
|
|
751
697
|
"""Write 1 byte unsigned integer value at given address."""
|
|
752
698
|
|
|
699
|
+
# If result failed
|
|
753
700
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint8(value)), ctypes.sizeof(buffer), None)):
|
|
754
|
-
|
|
755
|
-
# If result failed due memory protection
|
|
756
|
-
if result == 0x8000000D:
|
|
757
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
758
|
-
|
|
759
|
-
else:
|
|
760
|
-
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
701
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
761
702
|
|
|
762
703
|
def write_uint16(self, address: int, value: int) -> None:
|
|
763
704
|
"""Write 2 byte unsigned integer value at given address."""
|
|
764
705
|
|
|
706
|
+
# If result failed
|
|
765
707
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint16(value)), ctypes.sizeof(buffer), None)):
|
|
766
|
-
|
|
767
|
-
# If result failed due memory protection
|
|
768
|
-
if result == 0x8000000D:
|
|
769
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
770
|
-
|
|
771
|
-
else:
|
|
772
|
-
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
708
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
773
709
|
|
|
774
710
|
def write_uint32(self, address: int, value: int) -> None:
|
|
775
711
|
"""Write 4 byte unsigned integer value at given address."""
|
|
776
712
|
|
|
713
|
+
# If result failed
|
|
777
714
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint32(value)), ctypes.sizeof(buffer), None)):
|
|
778
|
-
|
|
779
|
-
# If result failed due memory protection
|
|
780
|
-
if result == 0x8000000D:
|
|
781
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
782
|
-
|
|
783
|
-
else:
|
|
784
|
-
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
715
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
785
716
|
|
|
786
717
|
def write_uint64(self, address: int, value: int) -> None:
|
|
787
718
|
"""Write 8 byte unsigned integer value at given address."""
|
|
788
719
|
|
|
720
|
+
# If result failed
|
|
789
721
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_uint64(value)), ctypes.sizeof(buffer), None)):
|
|
722
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
790
723
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
724
|
+
def write_float32(self, address: int, value: float | int) -> float:
|
|
725
|
+
"""Write 4 byte floating-point digit value at given address."""
|
|
794
726
|
|
|
795
|
-
|
|
796
|
-
|
|
727
|
+
# If result failed
|
|
728
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_float(value)), ctypes.sizeof(buffer), None)):
|
|
729
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
730
|
+
|
|
731
|
+
def write_float64(self, address: int, value: float | int) -> float:
|
|
732
|
+
"""Write 8 byte floating-point digit value at given address."""
|
|
733
|
+
|
|
734
|
+
# If result failed
|
|
735
|
+
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer := ctypes.c_double(value)), ctypes.sizeof(buffer), None)):
|
|
736
|
+
raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
797
737
|
|
|
798
738
|
def write_bytes(self, address: int, value: bytes) -> None:
|
|
799
739
|
"""Write bytes array of variadic size at given address."""
|
|
800
740
|
|
|
741
|
+
# If result failed
|
|
801
742
|
if (result := _nt_write_virtual_memory(self.handle, address, value, len(value), None)):
|
|
802
|
-
|
|
803
|
-
# If result failed due memory protection
|
|
804
|
-
if result == 0x8000000D:
|
|
805
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
806
|
-
|
|
807
|
-
else:
|
|
808
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
743
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
809
744
|
|
|
810
745
|
def write_buffer(self, address: int, buffer: typing.Any) -> None:
|
|
811
746
|
"""Write size of buffer byte value at given address. Buffer have to be able passed at `ctypes.byref` and `ctype.sizeof`."""
|
|
812
747
|
|
|
748
|
+
# If result failed
|
|
813
749
|
if (result := _nt_write_virtual_memory(self.handle, address, ctypes.byref(buffer), ctypes.sizeof(buffer), None)):
|
|
814
|
-
|
|
815
|
-
# If result failed due memory protection
|
|
816
|
-
if result == 0x8000000D:
|
|
817
|
-
raise Exception("Unable to write value at `0x%s` address due memory protection" % hex(address)[2:].upper())
|
|
818
|
-
|
|
819
|
-
else:
|
|
820
|
-
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
750
|
+
raise Exception("NtReadVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
|
|
821
751
|
|
|
822
752
|
def close(self) -> None:
|
|
823
753
|
"""Close opened process using it's handle, have to be called once on stop interacting with process."""
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# +-------------------------------------+
|
|
2
|
+
# | ~ Author : Xenely ~ |
|
|
3
|
+
# +=====================================+
|
|
4
|
+
# | GitHub: https://github.com/Xenely14 |
|
|
5
|
+
# | Discord: xenely |
|
|
6
|
+
# +-------------------------------------+
|
|
7
|
+
|
|
8
|
+
import ctypes
|
|
9
|
+
import typing
|
|
10
|
+
|
|
11
|
+
# ==-------------------------------------------------------------------== #
|
|
12
|
+
# DLL functions #
|
|
13
|
+
# ==-------------------------------------------------------------------== #
|
|
14
|
+
|
|
15
|
+
# DLL libraries loading
|
|
16
|
+
_kernel32 = ctypes.windll.kernel32
|
|
17
|
+
|
|
18
|
+
# DLL libraries functions loading
|
|
19
|
+
_LoadLibraryA = _kernel32.LoadLibraryA
|
|
20
|
+
_GetProcAddress = _kernel32.GetProcAddress
|
|
21
|
+
_VirtualFree = _kernel32.VirtualFree
|
|
22
|
+
_VirtualAlloc = _kernel32.VirtualAlloc
|
|
23
|
+
_VirtualProtect = _kernel32.VirtualProtect
|
|
24
|
+
|
|
25
|
+
# Define of DLL libraries functions return type
|
|
26
|
+
_LoadLibraryA.restype = ctypes.c_void_p
|
|
27
|
+
_GetProcAddress.restype = ctypes.c_void_p
|
|
28
|
+
_VirtualFree.restype = ctypes.c_long
|
|
29
|
+
_VirtualAlloc.restype = ctypes.c_void_p
|
|
30
|
+
_VirtualProtect.restype = ctypes.c_long
|
|
31
|
+
|
|
32
|
+
# Define of DLL libraries functions argument types
|
|
33
|
+
_LoadLibraryA .argtypes = [ctypes.c_char_p]
|
|
34
|
+
_GetProcAddress.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
|
|
35
|
+
_VirtualFree.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_ulong]
|
|
36
|
+
_VirtualAlloc.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_ulong, ctypes.c_ulong]
|
|
37
|
+
_VirtualProtect.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong)]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ==-------------------------------------------------------------------== #
|
|
41
|
+
# Classes #
|
|
42
|
+
# ==-------------------------------------------------------------------== #
|
|
43
|
+
class DirectSyscallWrapper:
|
|
44
|
+
"""Class to create syscall wrappers to make syscall calls """
|
|
45
|
+
|
|
46
|
+
# Table containing all syscall wrappers allocations
|
|
47
|
+
registred_syscalls_table = dict()
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
"""Creates wrapper instance to wrap syscalls."""
|
|
51
|
+
|
|
52
|
+
# Create list of wrapped syscalls allocations and save it into syscall table to free memory on instance delete
|
|
53
|
+
self.registred_syscalls_table[id(self)] = dict()
|
|
54
|
+
|
|
55
|
+
def __del__(self) -> None:
|
|
56
|
+
|
|
57
|
+
# Get all wrapper allocations addresses and deallocate them
|
|
58
|
+
for address in self.registred_syscalls_table[id(self)].copy().values():
|
|
59
|
+
print(_VirtualFree(address, 0, 0x8000))
|
|
60
|
+
|
|
61
|
+
# Clean up syscall table
|
|
62
|
+
del self.registred_syscalls_table[id(self)]
|
|
63
|
+
|
|
64
|
+
def wrap(self, function_name: str, *, result_type: typing.Any, arguments_types: list[typing.Any], search_module: bytes = b"ntdll.dll") -> ctypes.WINFUNCTYPE:
|
|
65
|
+
"""Retrieves syscall ID by function name, wraps it into raw function buffer and casts to `WINFUNCTYPE` to make stealthy-callable."""
|
|
66
|
+
|
|
67
|
+
# If syscall wrap already exists
|
|
68
|
+
if (syscall_wrap := self.registred_syscalls_table[id(self)].get(function_name)) is not None:
|
|
69
|
+
return syscall_wrap
|
|
70
|
+
|
|
71
|
+
# Module loading
|
|
72
|
+
if not (module_handle := _LoadLibraryA(search_module)):
|
|
73
|
+
raise Exception("Unable to load module `%s`" % search_module.decode())
|
|
74
|
+
|
|
75
|
+
# Retrieve function pointer
|
|
76
|
+
if not (serach_function := _GetProcAddress(module_handle, function_name.encode())):
|
|
77
|
+
raise Exception("Function `%s` not found" % function_name)
|
|
78
|
+
|
|
79
|
+
# Syscall id
|
|
80
|
+
syscall_id = None
|
|
81
|
+
|
|
82
|
+
# Retrieve syscall ID from function pointer
|
|
83
|
+
for index in range(0x16 + 1):
|
|
84
|
+
|
|
85
|
+
# If syscall ID found
|
|
86
|
+
if ctypes.cast(serach_function + index, ctypes.POINTER(ctypes.c_ubyte)).contents.value == 0xB8:
|
|
87
|
+
|
|
88
|
+
syscall_id = ctypes.cast(serach_function + index + 1, ctypes.POINTER(ctypes.c_ushort)).contents.value
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
# If syscall ID not found
|
|
92
|
+
if syscall_id is None:
|
|
93
|
+
raise Exception("Syscall ID for function `%s` not found" % function_name)
|
|
94
|
+
|
|
95
|
+
# Convert syscall ID to hex-bytes list
|
|
96
|
+
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))]
|
|
97
|
+
|
|
98
|
+
# Create bytes buffer with syscall ID inlined
|
|
99
|
+
# NOTE: `ntdll.dll` functions begins the same way, so it's just replica to behave the same.
|
|
100
|
+
#
|
|
101
|
+
# mov r10, rcx
|
|
102
|
+
# mov eax, <syscall id>,
|
|
103
|
+
# syscall
|
|
104
|
+
# ret
|
|
105
|
+
shellcode = bytes.fromhex("""
|
|
106
|
+
4C 8B D1
|
|
107
|
+
B8 %s %s 00 00
|
|
108
|
+
0F 05
|
|
109
|
+
C3
|
|
110
|
+
""" % tuple([*syscall_id_bytes]))
|
|
111
|
+
|
|
112
|
+
# Allocate buffer for function machine code
|
|
113
|
+
if not (shellcode_buffer := _VirtualAlloc(0, len(shellcode), 0x1000 | 0x2000, 0x04)):
|
|
114
|
+
raise Exception("Unable to alloate memory for shellcode")
|
|
115
|
+
|
|
116
|
+
# Save allocated buffer into wrapped syscalls table
|
|
117
|
+
self.registred_syscalls_table[id(self)][function_name] = shellcode_buffer
|
|
118
|
+
|
|
119
|
+
# Copy shellcode into function machine code buffer
|
|
120
|
+
ctypes.memmove(shellcode_buffer, ctypes.create_string_buffer(shellcode, len(shellcode)), len(shellcode))
|
|
121
|
+
|
|
122
|
+
# Update of function machine code buffer memory protection to make it executable
|
|
123
|
+
ctypes.windll.kernel32.VirtualProtect(shellcode_buffer, len(shellcode), 0x20, ctypes.byref(ctypes.c_ulong()))
|
|
124
|
+
|
|
125
|
+
# Return wrapped syscall function
|
|
126
|
+
return ctypes.cast(shellcode_buffer, ctypes.WINFUNCTYPE(result_type, *arguments_types))
|
|
127
|
+
|
|
128
|
+
def clean_up(self, function_name: str) -> None:
|
|
129
|
+
"""Cleans up syscall wrapper, deallocates it's shellcode buffer."""
|
|
130
|
+
|
|
131
|
+
# If function found
|
|
132
|
+
if (address := self.registred_syscalls_table[id(self)].get(function_name)) is not None:
|
|
133
|
+
|
|
134
|
+
# Deallocate syscall byffer
|
|
135
|
+
_VirtualFree(address)
|
|
136
|
+
|
|
137
|
+
# Clean up syscall table
|
|
138
|
+
del self.registred_syscalls_table[id(self)][function_name]
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# +-------------------------------------+
|
|
2
|
-
# | ~ Author : Xenely ~ |
|
|
3
|
-
# +=====================================+
|
|
4
|
-
# | GitHub: https://github.com/Xenely14 |
|
|
5
|
-
# | Discord: xenely |
|
|
6
|
-
# +-------------------------------------+
|
|
7
|
-
|
|
8
|
-
import ctypes
|
|
9
|
-
import struct
|
|
10
|
-
import typing
|
|
11
|
-
import ctypes.wintypes
|
|
12
|
-
|
|
13
|
-
# ==-------------------------------------------------------------------== #
|
|
14
|
-
# DLL functions #
|
|
15
|
-
# ==-------------------------------------------------------------------== #
|
|
16
|
-
|
|
17
|
-
# DLL libraries loading
|
|
18
|
-
_kernel32 = ctypes.windll.kernel32
|
|
19
|
-
|
|
20
|
-
# DLL libraries functions loading
|
|
21
|
-
_LoadLibraryA = _kernel32.LoadLibraryA
|
|
22
|
-
_GetProcAddress = _kernel32.GetProcAddress
|
|
23
|
-
_VirtualProtect = _kernel32.VirtualProtect
|
|
24
|
-
|
|
25
|
-
# Define of DLL libraries functions return type
|
|
26
|
-
_LoadLibraryA.restype = ctypes.wintypes.LPVOID
|
|
27
|
-
_GetProcAddress.restype = ctypes.wintypes.LPVOID
|
|
28
|
-
_VirtualProtect.restype = ctypes.wintypes.BOOL
|
|
29
|
-
|
|
30
|
-
# Define of DLL libraries functions argument types
|
|
31
|
-
_LoadLibraryA .argtypes = [ctypes.wintypes.LPCSTR]
|
|
32
|
-
_GetProcAddress.argtypes = [ctypes.wintypes.LPVOID, ctypes.wintypes.LPCSTR]
|
|
33
|
-
_VirtualProtect.argtypes = [ctypes.wintypes.LPVOID, ctypes.c_size_t, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD)]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# ==-------------------------------------------------------------------== #
|
|
37
|
-
# Functions #
|
|
38
|
-
# ==-------------------------------------------------------------------== #
|
|
39
|
-
def syscall(nt_function_name: str, *, result_type: typing.Any, arguments_types: list[typing.Any], module: bytes = b"ntdll.dll") -> ctypes.WINFUNCTYPE:
|
|
40
|
-
"""Finds function in DLL module by it name, retrieves it's syscall ID, wraps it into raw function buffer and casts to `WINFUNCTYPE` to make call shadowed."""
|
|
41
|
-
|
|
42
|
-
# Module loading
|
|
43
|
-
if not (module_handle := _LoadLibraryA(module)):
|
|
44
|
-
raise Exception("Unable to load module `%s`" % module.decode())
|
|
45
|
-
|
|
46
|
-
# Retrieve nt-function pointer
|
|
47
|
-
if not (nt_function := _GetProcAddress(module_handle, nt_function_name.encode())):
|
|
48
|
-
raise Exception("Function `%s` not found" % nt_function_name)
|
|
49
|
-
|
|
50
|
-
offset = 0
|
|
51
|
-
syscall_id = None
|
|
52
|
-
|
|
53
|
-
# Retrieve syscall ID from nt-function pointer
|
|
54
|
-
while True:
|
|
55
|
-
|
|
56
|
-
# Syscall ID not found
|
|
57
|
-
if offset > 0x16:
|
|
58
|
-
break
|
|
59
|
-
|
|
60
|
-
# Retrieve syscall ID from memory
|
|
61
|
-
if ctypes.cast(nt_function + offset, ctypes.POINTER(ctypes.c_ubyte)).contents.value == 0xB8:
|
|
62
|
-
syscall_id = ctypes.cast(nt_function + offset + 1, ctypes.POINTER(ctypes.c_ushort)).contents.value
|
|
63
|
-
break
|
|
64
|
-
|
|
65
|
-
offset += 1
|
|
66
|
-
|
|
67
|
-
# Syscall ID not found
|
|
68
|
-
if syscall_id is None:
|
|
69
|
-
raise Exception("Syscall ID for function `%s` not found" % nt_function_name)
|
|
70
|
-
|
|
71
|
-
# Convert syscall ID to hex-bytes list
|
|
72
|
-
syscall_id_bytes = [hex(item)[2:] if len(hex(item)[2:]) == 2 else "0" + hex(item)[2:] for item in struct.pack("<h", syscall_id)]
|
|
73
|
-
|
|
74
|
-
# NOTE: I actually don't know why does this code require access `gs` segment.
|
|
75
|
-
# I've just used this repo as reference: https://github.com/opcode86/SysCaller
|
|
76
|
-
#
|
|
77
|
-
# mov rax, gs:[0x60]
|
|
78
|
-
# mov r10, rcx
|
|
79
|
-
# mov eax, <syscall id>,
|
|
80
|
-
# syscall
|
|
81
|
-
# ret
|
|
82
|
-
shellcode = bytes.fromhex("""
|
|
83
|
-
65 48 8B 04 25 60 00
|
|
84
|
-
00 00
|
|
85
|
-
4C 8B D1
|
|
86
|
-
B8 %s %s 00 00
|
|
87
|
-
0F 05
|
|
88
|
-
C3
|
|
89
|
-
""" % tuple([*syscall_id_bytes]))
|
|
90
|
-
|
|
91
|
-
# Allocate buffer for function machine code
|
|
92
|
-
buffer = (ctypes.c_uint8 * len(shellcode))()
|
|
93
|
-
shellcode_buffer = ctypes.cast(buffer, ctypes.c_void_p)
|
|
94
|
-
|
|
95
|
-
# Copy shellcode into function machine code buffer
|
|
96
|
-
ctypes.memmove(shellcode_buffer, ctypes.create_string_buffer(shellcode, len(shellcode)), len(shellcode))
|
|
97
|
-
|
|
98
|
-
# Update of function machine code buffer memory protection to make it executable
|
|
99
|
-
ctypes.windll.kernel32.VirtualProtect(shellcode_buffer, len(shellcode), 0x40, ctypes.wintypes.LPDWORD(ctypes.wintypes.DWORD()))
|
|
100
|
-
|
|
101
|
-
# Return wrapped syscall function
|
|
102
|
-
return ctypes.cast(shellcode_buffer, ctypes.WINFUNCTYPE(result_type, *arguments_types))
|
|
File without changes
|
|
File without changes
|