ntmemoryapi 2.5.1__tar.gz → 2.6.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.5.1 → ntmemoryapi-2.6.0}/PKG-INFO +2 -1
- {ntmemoryapi-2.5.1 → ntmemoryapi-2.6.0}/pyproject.toml +2 -1
- {ntmemoryapi-2.5.1 → ntmemoryapi-2.6.0}/src/ntmemoryapi/__init__.py +2 -4
- ntmemoryapi-2.5.1/src/ntmemoryapi/misc.py +0 -141
- {ntmemoryapi-2.5.1 → ntmemoryapi-2.6.0}/README.md +0 -0
- {ntmemoryapi-2.5.1 → ntmemoryapi-2.6.0}/src/ntmemoryapi/embed.py +0 -0
- {ntmemoryapi-2.5.1 → ntmemoryapi-2.6.0}/src/ntmemoryapi/errors.py +0 -0
- {ntmemoryapi-2.5.1 → ntmemoryapi-2.6.0}/src/ntmemoryapi/structs.py +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ntmemoryapi
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.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.2.2
|
|
7
|
+
Requires-Dist: syscallwraps>=1.0.4
|
|
7
8
|
Requires-Python: >=3.13
|
|
8
9
|
Description-Content-Type: text/markdown
|
|
9
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ntmemoryapi"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.6.0"
|
|
4
4
|
description = "Simple library for Windows to manipulate process virtual memory with stelthy syscall wraps"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -9,6 +9,7 @@ authors = [
|
|
|
9
9
|
]
|
|
10
10
|
dependencies = [
|
|
11
11
|
"psutil>=7.2.2",
|
|
12
|
+
"syscallwraps>=1.0.4",
|
|
12
13
|
]
|
|
13
14
|
|
|
14
15
|
[build-system]
|
|
@@ -10,9 +10,9 @@ import typing
|
|
|
10
10
|
import ctypes
|
|
11
11
|
import psutil
|
|
12
12
|
import functools
|
|
13
|
+
import syscallwraps
|
|
13
14
|
|
|
14
15
|
# Local imports
|
|
15
|
-
from . import misc
|
|
16
16
|
from . import embed
|
|
17
17
|
from . import errors
|
|
18
18
|
from . import structs
|
|
@@ -20,8 +20,6 @@ from . import structs
|
|
|
20
20
|
# ==-------------------------------------------------------------------== #
|
|
21
21
|
# Static and global variables, constans #
|
|
22
22
|
# ==-------------------------------------------------------------------== #
|
|
23
|
-
syscall_wrapper = misc.DirectSyscallWrapper()
|
|
24
|
-
|
|
25
23
|
PROCESS_ID = 1
|
|
26
24
|
PROCESS_NAME = 2
|
|
27
25
|
|
|
@@ -101,7 +99,7 @@ class PatternScanBuffer(ctypes.Structure):
|
|
|
101
99
|
# ==-------------------------------------------------------------------== #
|
|
102
100
|
# Syscalls #
|
|
103
101
|
# ==-------------------------------------------------------------------== #
|
|
104
|
-
syscall_wrapper =
|
|
102
|
+
syscall_wrapper = syscallwraps.SyscallWrapper()
|
|
105
103
|
|
|
106
104
|
_nt_close = syscall_wrapper.wrap("NtClose", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p])
|
|
107
105
|
_nt_open_process = syscall_wrapper.wrap("NtOpenProcess", result_type=ctypes.c_ulong, arguments_types=[ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(structs.OBJECT_ATTRIBUTES), ctypes.POINTER(structs.CLIENT_ID)])
|
|
@@ -1,141 +0,0 @@
|
|
|
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
|
-
# ==--------------------------------== #
|
|
50
|
-
# Public methods #
|
|
51
|
-
# ==--------------------------------== #
|
|
52
|
-
def __init__(self) -> None:
|
|
53
|
-
"""Creates wrapper instance to wrap syscalls."""
|
|
54
|
-
|
|
55
|
-
# Create list of wrapped syscalls allocations and save it into syscall table to free memory on instance delete
|
|
56
|
-
self.registred_syscalls_table[id(self)] = dict()
|
|
57
|
-
|
|
58
|
-
def __del__(self) -> None:
|
|
59
|
-
|
|
60
|
-
# Get all wrapper allocations addresses and deallocate them
|
|
61
|
-
for address in self.registred_syscalls_table[id(self)].copy().values():
|
|
62
|
-
_VirtualFree(address, 0, 0x8000)
|
|
63
|
-
|
|
64
|
-
# Clean up syscall table
|
|
65
|
-
del self.registred_syscalls_table[id(self)]
|
|
66
|
-
|
|
67
|
-
def wrap(self, function_name: str, *, result_type: typing.Any, arguments_types: list[typing.Any], search_module: bytes = b"ntdll.dll") -> ctypes.WINFUNCTYPE:
|
|
68
|
-
"""Retrieves syscall ID by function name, wraps it into raw function buffer and casts to `WINFUNCTYPE` to make stealthy-callable."""
|
|
69
|
-
|
|
70
|
-
# If syscall wrap already exists
|
|
71
|
-
if (syscall_wrap := self.registred_syscalls_table[id(self)].get(function_name)) is not None:
|
|
72
|
-
return syscall_wrap
|
|
73
|
-
|
|
74
|
-
# Module loading
|
|
75
|
-
if not (module_handle := _LoadLibraryA(search_module)):
|
|
76
|
-
raise ctypes.WinError(descr="Unable to load module `%s`" % search_module.decode())
|
|
77
|
-
|
|
78
|
-
# Retrieve function pointer
|
|
79
|
-
if not (serach_function := _GetProcAddress(module_handle, function_name.encode())):
|
|
80
|
-
raise ctypes.WinError(descr="Function `%s` not found" % function_name)
|
|
81
|
-
|
|
82
|
-
# Syscall id
|
|
83
|
-
syscall_id = None
|
|
84
|
-
|
|
85
|
-
# Retrieve syscall ID from function pointer
|
|
86
|
-
for index in range(0x16 + 1):
|
|
87
|
-
|
|
88
|
-
# If syscall ID found
|
|
89
|
-
if ctypes.cast(serach_function + index, ctypes.POINTER(ctypes.c_ubyte)).contents.value == 0xB8:
|
|
90
|
-
|
|
91
|
-
syscall_id = ctypes.cast(serach_function + index + 1, ctypes.POINTER(ctypes.c_ushort)).contents.value
|
|
92
|
-
break
|
|
93
|
-
|
|
94
|
-
# If syscall ID not found
|
|
95
|
-
if syscall_id is None:
|
|
96
|
-
raise ValueError("Syscall ID for function `%s` not found" % function_name)
|
|
97
|
-
|
|
98
|
-
# Convert syscall ID to hex-bytes list
|
|
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))]
|
|
100
|
-
|
|
101
|
-
# Create bytes buffer with syscall ID inlined
|
|
102
|
-
# NOTE: `ntdll.dll` functions begins the same way, so it's just replica to behave the same.
|
|
103
|
-
#
|
|
104
|
-
# mov r10, rcx
|
|
105
|
-
# mov eax, <syscall id>,
|
|
106
|
-
# syscall
|
|
107
|
-
# ret
|
|
108
|
-
shellcode = bytes.fromhex("""
|
|
109
|
-
4C 8B D1
|
|
110
|
-
B8 %s %s 00 00
|
|
111
|
-
0F 05
|
|
112
|
-
C3
|
|
113
|
-
""" % tuple(syscall_id_bytes))
|
|
114
|
-
|
|
115
|
-
# Allocate buffer for function machine code
|
|
116
|
-
if not (shellcode_buffer := _VirtualAlloc(0, len(shellcode), 0x1000 | 0x2000, 0x04)):
|
|
117
|
-
raise ctypes.WinError(descr="Unable to alloate memory for shellcode")
|
|
118
|
-
|
|
119
|
-
# Save allocated buffer into wrapped syscalls table
|
|
120
|
-
self.registred_syscalls_table[id(self)][function_name] = shellcode_buffer
|
|
121
|
-
|
|
122
|
-
# Copy shellcode into function machine code buffer
|
|
123
|
-
ctypes.memmove(shellcode_buffer, ctypes.create_string_buffer(shellcode, len(shellcode)), len(shellcode))
|
|
124
|
-
|
|
125
|
-
# Update of function machine code buffer memory protection to make it executable
|
|
126
|
-
ctypes.windll.kernel32.VirtualProtect(shellcode_buffer, len(shellcode), 0x20, ctypes.byref(ctypes.c_ulong()))
|
|
127
|
-
|
|
128
|
-
# Return wrapped syscall function
|
|
129
|
-
return ctypes.cast(shellcode_buffer, ctypes.WINFUNCTYPE(result_type, *arguments_types))
|
|
130
|
-
|
|
131
|
-
def clean_up(self, function_name: str) -> None:
|
|
132
|
-
"""Cleans up syscall wrapper, deallocates it's shellcode buffer."""
|
|
133
|
-
|
|
134
|
-
# If function found
|
|
135
|
-
if (address := self.registred_syscalls_table[id(self)].get(function_name)) is not None:
|
|
136
|
-
|
|
137
|
-
# Deallocate syscall byffer
|
|
138
|
-
_VirtualFree(address)
|
|
139
|
-
|
|
140
|
-
# Clean up syscall table
|
|
141
|
-
del self.registred_syscalls_table[id(self)][function_name]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|