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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ntmemoryapi
3
- Version: 1.3.0
3
+ Version: 1.5.0
4
4
  Summary: Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps
5
5
  Author: Xenely
6
6
  Requires-Dist: psutil>=7.1.3
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ntmemoryapi"
3
- version = "1.3.0"
3
+ version = "1.5.0"
4
4
  description = "Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps"
5
5
  authors = [
6
6
  {name = "Xenely"}
@@ -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 region_size(self) -> int:
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
- _nt_close = syscall("NtClose", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p])
213
- _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)])
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 = 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)])
216
- _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)])
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 = 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)])
219
- _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)])
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 = 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])
222
- _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])
223
- _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)])
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(include_id: bool = True, include_name: bool = True, include_username: bool = True, current_username_only: bool = True) -> list[dict[str, int | str]]:
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 include_id and not include_name:
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 include_id:
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 include_name:
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 include_username:
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, pid_or_name: int | str, access: int = PROCESS_ALL_ACCESS, current_username_only: bool = True) -> None:
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 pid_or_name:
296
+ match name_or_name:
285
297
 
286
- case pid if type(pid_or_name) is int:
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(pid_or_name) is str:
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.region_size) <= current_address:
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.region_size
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
- # If result failed due memory protection
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
- # If result failed due memory protection
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
- # If result failed due memory protection
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
- # If result failed due memory protection
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) -> 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
- # If result failed due memory protection
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) -> 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
- # If result failed due memory protection
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[T](self, address: int, buffer: T) -> T:
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 due memory protection
744
- if result == 0x8000000D:
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
- # If result failed due memory protection
792
- if result == 0x8000000D:
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
- else:
796
- raise Exception("NtWriteVirtualMemory failed with status: 0x%s" % hex(result)[2:].upper())
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