PyMemoryEditor 1.5.7__tar.gz → 1.5.8__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.
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PKG-INFO +6 -3
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/__init__.py +1 -1
- pymemoryeditor-1.5.8/PyMemoryEditor/errors.py +6 -0
- pymemoryeditor-1.5.8/PyMemoryEditor/sample/application.py +15 -0
- pymemoryeditor-1.5.8/PyMemoryEditor/sample/main_application_window.py +443 -0
- pymemoryeditor-1.5.8/PyMemoryEditor/sample/open_process_window.py +56 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/util/scan.py +3 -3
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/functions.py +1 -1
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/process.py +4 -2
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/README.md +4 -1
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/pyproject.toml +5 -2
- pymemoryeditor-1.5.7/PyMemoryEditor/errors.py +0 -3
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/.github/workflows/python-package.yml +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/.gitignore +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/LICENSE +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/enums.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/functions.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/process.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/ptrace/__init__.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/ptrace/enums.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/linux/types.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/process/__init__.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/process/errors.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/process/info.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/process/util.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/util/__init__.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/util/search/abstract.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/util/search/kmp.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/__init__.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/memory_protections.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/memory_types.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/process_operations.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/types.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/__init__.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/open_process.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/process/__init__.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/process/errors.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/process/util.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/__init__.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/memory_protections.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/memory_types.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/process_operations.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/scan_types.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/functions.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/types.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/util.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/requirements.txt +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/tests/package.py +0 -0
- {pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/tests/test_editor.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PyMemoryEditor
|
|
3
|
-
Version: 1.5.
|
|
4
|
-
Summary: Multi-platform library developed with ctypes for reading, writing and searching process memory, in a simple and friendly way with Python 3.
|
|
3
|
+
Version: 1.5.8
|
|
4
|
+
Summary: Multi-platform library developed with ctypes for reading, writing and searching process memory, in a simple and friendly way with Python 3.
|
|
5
5
|
Project-URL: Homepage, https://github.com/JeanExtreme002/PyMemoryEditor
|
|
6
6
|
Author-email: Jean Loui Bernard Silva de Jesus <jeanextreme002@gmail.com>
|
|
7
7
|
License-Expression: MIT
|
|
@@ -110,7 +110,7 @@ for address, info process.search_by_value(..., progress_information = True):
|
|
|
110
110
|
print(template.format(address, progress))
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
## Getting memory regions:
|
|
114
114
|
Use the method `get_memory_regions()` to get the base address, size and more information of all memory regions used by the process.
|
|
115
115
|
|
|
116
116
|
```py
|
|
@@ -119,3 +119,6 @@ for memory_region in process.get_memory_regions():
|
|
|
119
119
|
size = memory_region["size"]
|
|
120
120
|
information = memory_region["struct"]
|
|
121
121
|
```
|
|
122
|
+
|
|
123
|
+
# Tkinter application sample:
|
|
124
|
+
You can run a simple tkinter application to scan the memory of a process by typing `pymemoryeditor` at the CLI.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .main_application_window import ApplicationWindow
|
|
2
|
+
from .open_process_window import OpenProcessWindow
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main(*args, **kwargs):
|
|
6
|
+
open_process_window = OpenProcessWindow()
|
|
7
|
+
process = open_process_window.get_process()
|
|
8
|
+
|
|
9
|
+
if process:
|
|
10
|
+
ApplicationWindow(process)
|
|
11
|
+
process.close()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
main()
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
from tkinter import DoubleVar, Frame, Label, Menu, Listbox, Scrollbar, Tk
|
|
2
|
+
from tkinter.ttk import Button, Entry, Menubutton, Progressbar
|
|
3
|
+
|
|
4
|
+
from PyMemoryEditor import ScanTypesEnum
|
|
5
|
+
from PyMemoryEditor.process import AbstractProcess
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ApplicationWindow(Tk):
|
|
9
|
+
"""
|
|
10
|
+
Main window.
|
|
11
|
+
"""
|
|
12
|
+
__comp_methods = {
|
|
13
|
+
ScanTypesEnum.EXACT_VALUE: lambda x, y: x == y,
|
|
14
|
+
ScanTypesEnum.NOT_EXACT_VALUE: lambda x, y: x != y,
|
|
15
|
+
ScanTypesEnum.BIGGER_THAN: lambda x, y: x > y,
|
|
16
|
+
ScanTypesEnum.SMALLER_THAN: lambda x, y: x < y,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def __init__(self, process: AbstractProcess):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.__process = process
|
|
22
|
+
|
|
23
|
+
self.__scan_type = ScanTypesEnum.EXACT_VALUE
|
|
24
|
+
self.__value = 0
|
|
25
|
+
self.__value_type = int
|
|
26
|
+
self.__value_length = 4
|
|
27
|
+
self.__addresses = []
|
|
28
|
+
|
|
29
|
+
self.__updating = False # Indicate the application is updating the values of the found addresses.
|
|
30
|
+
self.__scanning = False # Indicate a scan has started.
|
|
31
|
+
|
|
32
|
+
self["bg"] = "white"
|
|
33
|
+
|
|
34
|
+
self.title(f"PyMemoryEditor (Sample) - Process ID: {process.pid}")
|
|
35
|
+
self.geometry("900x400")
|
|
36
|
+
|
|
37
|
+
self.protocol("WM_DELETE_WINDOW", self.__on_close)
|
|
38
|
+
self.__close = False
|
|
39
|
+
|
|
40
|
+
self.__addresses = []
|
|
41
|
+
|
|
42
|
+
self.__build()
|
|
43
|
+
self.mainloop()
|
|
44
|
+
|
|
45
|
+
def __build(self):
|
|
46
|
+
"""
|
|
47
|
+
Build the widgets of the window.
|
|
48
|
+
"""
|
|
49
|
+
self.__entry_register_int = self.register(self.__validate_int_entry)
|
|
50
|
+
self.__entry_register_hex = self.register(self.__validate_hex_entry)
|
|
51
|
+
|
|
52
|
+
self.__input_frame_1 = Frame(self)
|
|
53
|
+
self.__input_frame_1["bg"] = "white"
|
|
54
|
+
self.__input_frame_1.pack(padx=5, fill="x", expand=True)
|
|
55
|
+
|
|
56
|
+
self.__scan_input_frame = Frame(self.__input_frame_1)
|
|
57
|
+
self.__scan_input_frame["bg"] = "white"
|
|
58
|
+
self.__scan_input_frame.pack(fill="x", expand=True)
|
|
59
|
+
|
|
60
|
+
# Value input.
|
|
61
|
+
Label(self.__scan_input_frame, text="Value: ", bg="white", font=("Arial", 12)).pack(side="left")
|
|
62
|
+
|
|
63
|
+
self.__value_entry = Entry(self.__scan_input_frame)
|
|
64
|
+
self.__value_entry.pack(side="left", expand=True, fill="x")
|
|
65
|
+
|
|
66
|
+
Label(self.__scan_input_frame, bg="white").pack(side="left")
|
|
67
|
+
|
|
68
|
+
# Value length.
|
|
69
|
+
Label(self.__scan_input_frame, text="Length (Bytes): ", bg="white", font=("Arial", 12)).pack(side="left")
|
|
70
|
+
|
|
71
|
+
self.__length_entry = Entry(self.__scan_input_frame, width=5)
|
|
72
|
+
self.__length_entry.insert(0, "4")
|
|
73
|
+
self.__length_entry.config(validate="key", validatecommand=(self.__entry_register_int, "%P"))
|
|
74
|
+
self.__length_entry.pack(side="left")
|
|
75
|
+
|
|
76
|
+
Label(self.__scan_input_frame, bg="white").pack(side="left", padx=5)
|
|
77
|
+
|
|
78
|
+
# Value type input.
|
|
79
|
+
self.__type_menu_button = Menubutton(self.__scan_input_frame, width=10)
|
|
80
|
+
self.__type_menu_button.pack(side="left")
|
|
81
|
+
|
|
82
|
+
self.__type_menu = Menu(tearoff=0, bg="white")
|
|
83
|
+
self.__type_menu.add_command(label="Boolean", command=lambda: self.__set_value_type(0))
|
|
84
|
+
self.__type_menu.add_command(label="Integer", command=lambda: self.__set_value_type(1))
|
|
85
|
+
self.__type_menu.add_command(label="Float", command=lambda: self.__set_value_type(2))
|
|
86
|
+
self.__type_menu.add_command(label="String", command=lambda: self.__set_value_type(3))
|
|
87
|
+
self.__type_menu_button.config(menu=self.__type_menu, text = "Integer")
|
|
88
|
+
|
|
89
|
+
Label(self.__scan_input_frame, bg="white").pack(side="left", padx=10)
|
|
90
|
+
|
|
91
|
+
# Scan type input.
|
|
92
|
+
Label(self.__scan_input_frame, text="Scan Type: ", bg="white", font=("Arial", 12)).pack(side="left")
|
|
93
|
+
|
|
94
|
+
self.__scan_menu_button = Menubutton(self.__scan_input_frame, width=15)
|
|
95
|
+
self.__scan_menu_button.pack(side="left")
|
|
96
|
+
|
|
97
|
+
self.__scan_menu = Menu(tearoff=0, bg="white")
|
|
98
|
+
self.__scan_menu.add_command(label="Exact Value", command=lambda: self.__set_scan_type(0))
|
|
99
|
+
self.__scan_menu.add_command(label="Not Exact Value", command=lambda: self.__set_scan_type(1))
|
|
100
|
+
self.__scan_menu.add_command(label="Smaller Than", command=lambda: self.__set_scan_type(2))
|
|
101
|
+
self.__scan_menu.add_command(label="Bigger Than", command=lambda: self.__set_scan_type(3))
|
|
102
|
+
self.__scan_menu_button.config(menu=self.__scan_menu, text = "Exact Value")
|
|
103
|
+
|
|
104
|
+
Label(self.__scan_input_frame, bg="white").pack(side="left", padx=5)
|
|
105
|
+
|
|
106
|
+
# Buttons for scanning.
|
|
107
|
+
self.__new_scan_button = Button(self.__scan_input_frame, text="First Scan", command=self.__new_scan)
|
|
108
|
+
self.__new_scan_button.pack(side="left")
|
|
109
|
+
|
|
110
|
+
Label(self.__scan_input_frame, bg="white").pack(side="left")
|
|
111
|
+
|
|
112
|
+
self.__next_scan_button = Button(self.__scan_input_frame, command=self.__next_scan)
|
|
113
|
+
self.__next_scan_button.pack(side="left")
|
|
114
|
+
|
|
115
|
+
# Scanning process bar.
|
|
116
|
+
self.__progress_var = DoubleVar()
|
|
117
|
+
|
|
118
|
+
self.__progress_bar = Progressbar(self.__input_frame_1, variable=self.__progress_var)
|
|
119
|
+
self.__progress_bar.pack(pady=5, fill="x", expand=True)
|
|
120
|
+
|
|
121
|
+
# Label for counting and button for updating values.
|
|
122
|
+
self.__result_frame = Frame(self)
|
|
123
|
+
self.__result_frame["bg"] = "white"
|
|
124
|
+
self.__result_frame.pack(padx=5, fill="both", expand=True)
|
|
125
|
+
|
|
126
|
+
self.__count_frame = Frame(self.__result_frame)
|
|
127
|
+
self.__count_frame["bg"] = "white"
|
|
128
|
+
self.__count_frame.pack(pady=5, fill="x", expand=True)
|
|
129
|
+
|
|
130
|
+
self.__count_label = Label(self.__count_frame, font=("Arial", 8), bg="white")
|
|
131
|
+
self.__count_label.config(text="Start a new scan to find memory addresses.")
|
|
132
|
+
self.__count_label.pack(side="left")
|
|
133
|
+
|
|
134
|
+
Button(self.__count_frame, text="Update Values", command=self.__update_values).pack(side="right")
|
|
135
|
+
|
|
136
|
+
# List with addresses and their values.
|
|
137
|
+
self.__list_frame = Frame(self.__result_frame)
|
|
138
|
+
self.__list_frame["bg"] = "white"
|
|
139
|
+
self.__list_frame.pack(fill="both", expand=True)
|
|
140
|
+
|
|
141
|
+
self.__scrollbar = Scrollbar(self.__list_frame, orient="vertical", command=self.__on_move_list_box)
|
|
142
|
+
|
|
143
|
+
self.__address_list = Listbox(self.__list_frame, width=20)
|
|
144
|
+
self.__address_list.bind("<MouseWheel>", self.__on_mouse_wheel)
|
|
145
|
+
self.__address_list.bind("<<ListboxSelect>>", self.__select_address)
|
|
146
|
+
self.__address_list.config(yscrollcommand = self.__scrollbar.set)
|
|
147
|
+
self.__address_list.pack(side="left", fill="y")
|
|
148
|
+
|
|
149
|
+
self.__value_list = Listbox(self.__list_frame)
|
|
150
|
+
self.__value_list.bind("<MouseWheel>", self.__on_mouse_wheel)
|
|
151
|
+
self.__value_list.bind("<<ListboxSelect>>", self.__select_value)
|
|
152
|
+
self.__value_list.config(yscrollcommand = self.__scrollbar.set)
|
|
153
|
+
self.__value_list.pack(side="left", fill="both", expand=True)
|
|
154
|
+
|
|
155
|
+
self.__scrollbar.pack(side="left", fill="y")
|
|
156
|
+
|
|
157
|
+
# Widgets for change the value of a memory address.
|
|
158
|
+
self.__input_frame_2 = Frame(self)
|
|
159
|
+
self.__input_frame_2["bg"] = "white"
|
|
160
|
+
self.__input_frame_2.pack(padx=5, fill="x", expand=True)
|
|
161
|
+
|
|
162
|
+
Label(self.__input_frame_2, text="Address:", bg="white").pack(side="left")
|
|
163
|
+
|
|
164
|
+
self.__address_entry = Entry(self.__input_frame_2)
|
|
165
|
+
self.__address_entry.config(validate="key", validatecommand=(self.__entry_register_hex, "%P"))
|
|
166
|
+
self.__address_entry.pack(side="left")
|
|
167
|
+
|
|
168
|
+
Label(self.__input_frame_2, bg="white").pack(side="left")
|
|
169
|
+
|
|
170
|
+
Label(self.__input_frame_2, text="New Value:", bg="white").pack(side="left")
|
|
171
|
+
|
|
172
|
+
self.__new_value_entry = Entry(self.__input_frame_2)
|
|
173
|
+
self.__new_value_entry.pack(side="left", fill="x", expand=True)
|
|
174
|
+
|
|
175
|
+
Button(self.__input_frame_2, text="Replace", command=self.__write_value).pack(side="left")
|
|
176
|
+
|
|
177
|
+
def __new_scan(self):
|
|
178
|
+
"""
|
|
179
|
+
Start a new seach at the whole memory of the process.
|
|
180
|
+
"""
|
|
181
|
+
if self.__new_scan_button["text"].lower() == "scanning" or self.__updating: return
|
|
182
|
+
self.__next_scan_button.config(text="")
|
|
183
|
+
|
|
184
|
+
self.__addresses = []
|
|
185
|
+
|
|
186
|
+
# If a scan is already in progress, clear all results and get everything ready for a new scan.
|
|
187
|
+
if self.__scanning:
|
|
188
|
+
self.__new_scan_button.config(text="First Scan")
|
|
189
|
+
self.__count_label.config(text="Start a new scan to find memory addresses.")
|
|
190
|
+
self.__address_list.delete(0, "end")
|
|
191
|
+
self.__value_list.delete(0, "end")
|
|
192
|
+
self.__progress_var.set(0)
|
|
193
|
+
self.__scanning = False
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# Get the inputs.
|
|
197
|
+
value = self.__value_entry.get().strip()
|
|
198
|
+
length = int(self.__length_entry.get())
|
|
199
|
+
pytype = self.__value_type
|
|
200
|
+
scan_type = self.__scan_type
|
|
201
|
+
|
|
202
|
+
if not value or length == 0: return
|
|
203
|
+
|
|
204
|
+
# Check if the value is valid for the selected value type.
|
|
205
|
+
try:
|
|
206
|
+
if str(pytype(value)) != value or (pytype is str and length < len(value)):
|
|
207
|
+
raise ValueError()
|
|
208
|
+
except:
|
|
209
|
+
self.__value_entry.delete(0, "end")
|
|
210
|
+
return self.__value_entry.insert(0, "Invalid value")
|
|
211
|
+
|
|
212
|
+
# Start the scan.
|
|
213
|
+
value = pytype(value)
|
|
214
|
+
|
|
215
|
+
self.__value_length = length
|
|
216
|
+
self.__value = value
|
|
217
|
+
|
|
218
|
+
self.after(100, lambda: self.__start_scan(pytype, length, value, scan_type))
|
|
219
|
+
|
|
220
|
+
def __next_scan(self):
|
|
221
|
+
"""
|
|
222
|
+
Filter the found addresses.
|
|
223
|
+
"""
|
|
224
|
+
self.__update_values(remove=True)
|
|
225
|
+
|
|
226
|
+
def __on_close(self, *args):
|
|
227
|
+
"""
|
|
228
|
+
Event to close the program graciously.
|
|
229
|
+
"""
|
|
230
|
+
self.__close = True
|
|
231
|
+
|
|
232
|
+
if self.__updating or self.__new_scan_button["text"].lower() == "scanning":
|
|
233
|
+
self.update()
|
|
234
|
+
return self.after(10, self.__on_close)
|
|
235
|
+
|
|
236
|
+
self.destroy()
|
|
237
|
+
|
|
238
|
+
def __on_move_list_box(self, *args):
|
|
239
|
+
"""
|
|
240
|
+
Event to sync the listboxes.
|
|
241
|
+
"""
|
|
242
|
+
self.__address_list.yview(*args)
|
|
243
|
+
self.__value_list.yview(*args)
|
|
244
|
+
|
|
245
|
+
def __on_mouse_wheel(self, event):
|
|
246
|
+
"""
|
|
247
|
+
Event to sync the listboxes.
|
|
248
|
+
"""
|
|
249
|
+
self.__address_list.yview("scroll", event.delta, "units")
|
|
250
|
+
self.__value_list.yview("scroll", event.delta, "units")
|
|
251
|
+
return "break"
|
|
252
|
+
|
|
253
|
+
def __select_address(self, event):
|
|
254
|
+
"""
|
|
255
|
+
Event to get the selected address and copy it.
|
|
256
|
+
"""
|
|
257
|
+
selection = event.widget.curselection()
|
|
258
|
+
if not selection: return
|
|
259
|
+
|
|
260
|
+
address = self.__address_list.get(int(selection[0])).split(" ")[-1]
|
|
261
|
+
if not address: return
|
|
262
|
+
|
|
263
|
+
self.__address_entry.delete(0, "end")
|
|
264
|
+
self.__address_entry.insert(0, address)
|
|
265
|
+
|
|
266
|
+
def __select_value(self, event):
|
|
267
|
+
"""
|
|
268
|
+
Event to get the selected value and copy it.
|
|
269
|
+
"""
|
|
270
|
+
selection = event.widget.curselection()
|
|
271
|
+
if not selection: return
|
|
272
|
+
|
|
273
|
+
value = self.__value_list.get(int(selection[0]))[len("Value: "):]
|
|
274
|
+
self.__new_value_entry.delete(0, "end")
|
|
275
|
+
self.__new_value_entry.insert(0, value)
|
|
276
|
+
|
|
277
|
+
def __start_scan(self, pytype, length, value, scan_type):
|
|
278
|
+
"""
|
|
279
|
+
Search for a value on the whole memory of the process.
|
|
280
|
+
"""
|
|
281
|
+
self.__new_scan_button.config(text="Scanning")
|
|
282
|
+
self.update()
|
|
283
|
+
|
|
284
|
+
self.__scanning = True
|
|
285
|
+
self.__addresses = []
|
|
286
|
+
|
|
287
|
+
for address, info in self.__process.search_by_value(pytype, length, value, scan_type, progress_information=True):
|
|
288
|
+
if self.__close: break
|
|
289
|
+
|
|
290
|
+
self.__address_list.insert("end", f"Addr: {hex(address)[2:].upper()}")
|
|
291
|
+
self.__value_list.insert("end", f"Value: {value}")
|
|
292
|
+
|
|
293
|
+
self.__progress_var.set(info["progress"] * 100)
|
|
294
|
+
self.__addresses.append(address)
|
|
295
|
+
self.update()
|
|
296
|
+
|
|
297
|
+
self.__count_label.config(text=f"Found {len(self.__addresses)} addresses.")
|
|
298
|
+
|
|
299
|
+
self.__new_scan_button.config(text="New Scan")
|
|
300
|
+
self.__next_scan_button.config(text="Next Scan")
|
|
301
|
+
self.__progress_var.set(100)
|
|
302
|
+
|
|
303
|
+
def __set_scan_type(self, scan_type: int):
|
|
304
|
+
"""
|
|
305
|
+
Method for the Menubutton to select a scan type.
|
|
306
|
+
"""
|
|
307
|
+
# Allow select a new scan type only if program is not getting new addresses or updating their values.
|
|
308
|
+
if self.__new_scan_button["text"].lower() == "scanning" or self.__updating: return
|
|
309
|
+
|
|
310
|
+
self.__scan_type = [
|
|
311
|
+
ScanTypesEnum.EXACT_VALUE,
|
|
312
|
+
ScanTypesEnum.NOT_EXACT_VALUE,
|
|
313
|
+
ScanTypesEnum.SMALLER_THAN,
|
|
314
|
+
ScanTypesEnum.BIGGER_THAN,
|
|
315
|
+
][scan_type]
|
|
316
|
+
|
|
317
|
+
text = " ".join(word.capitalize() for word in self.__scan_type.name.split("_"))
|
|
318
|
+
self.__scan_menu_button.config(text=text)
|
|
319
|
+
|
|
320
|
+
def __set_value_type(self, value_type: int):
|
|
321
|
+
"""
|
|
322
|
+
Method for the Menubutton to select a value type.
|
|
323
|
+
"""
|
|
324
|
+
if self.__scanning: return
|
|
325
|
+
|
|
326
|
+
self.__value_type = [bool, int, float, str][value_type]
|
|
327
|
+
self.__type_menu_button.config(text=["Boolean", "Integer", "Float", "String"][value_type])
|
|
328
|
+
|
|
329
|
+
def __validate_int_entry(self, string):
|
|
330
|
+
"""
|
|
331
|
+
Method to validate if an input is integer.
|
|
332
|
+
"""
|
|
333
|
+
if self.__scanning: return False
|
|
334
|
+
|
|
335
|
+
for char in string:
|
|
336
|
+
if char not in "0123456789": return False
|
|
337
|
+
return True
|
|
338
|
+
|
|
339
|
+
def __validate_hex_entry(self, string):
|
|
340
|
+
"""
|
|
341
|
+
Method to validate if an input is hexadecimal.
|
|
342
|
+
"""
|
|
343
|
+
for char in string.upper():
|
|
344
|
+
if char not in "0123456789ABCDEF": return False
|
|
345
|
+
return True
|
|
346
|
+
|
|
347
|
+
def __update_values(self, index = 0, *, remove = False, first_call = True):
|
|
348
|
+
"""
|
|
349
|
+
Update the values of the found addresses. If "remove" is True, it will
|
|
350
|
+
compare the current value in memory and remove the address from the
|
|
351
|
+
results if the comparison is False.
|
|
352
|
+
"""
|
|
353
|
+
if self.__updating and first_call: return # Allow call the method once.
|
|
354
|
+
|
|
355
|
+
# Return if user asked for closing the application.
|
|
356
|
+
if self.__close:
|
|
357
|
+
self.__updating = False
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
# Get the value to compare.
|
|
361
|
+
if first_call:
|
|
362
|
+
value = self.__value_entry.get().strip()
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
if str(self.__value_type(value)) != value:
|
|
366
|
+
raise ValueError()
|
|
367
|
+
except:
|
|
368
|
+
self.__value_entry.delete(0, "end")
|
|
369
|
+
return self.__value_entry.insert(0, "Invalid value")
|
|
370
|
+
|
|
371
|
+
self.__value = self.__value_type(value)
|
|
372
|
+
self.__progress_var.set(0)
|
|
373
|
+
|
|
374
|
+
# Indicate the application is updating the values.
|
|
375
|
+
self.__updating = True
|
|
376
|
+
|
|
377
|
+
if index % 10 == 0: self.update()
|
|
378
|
+
|
|
379
|
+
# Get the address from the list.
|
|
380
|
+
try:
|
|
381
|
+
address = self.__addresses[index]
|
|
382
|
+
except:
|
|
383
|
+
self.__updating = False
|
|
384
|
+
|
|
385
|
+
self.__count_label.config(text=f"Found {len(self.__addresses)} addresses.")
|
|
386
|
+
return self.__progress_var.set(100 if not first_call else 0)
|
|
387
|
+
|
|
388
|
+
# Get the current value of the address.
|
|
389
|
+
corrupted = False
|
|
390
|
+
value = None
|
|
391
|
+
|
|
392
|
+
try: value = self.__process.read_process_memory(address, self.__value_type, self.__value_length)
|
|
393
|
+
except: corrupted = True
|
|
394
|
+
|
|
395
|
+
# If "remove" is True, compare the value.
|
|
396
|
+
if remove or corrupted:
|
|
397
|
+
compare = self.__comp_methods[self.__scan_type]
|
|
398
|
+
|
|
399
|
+
# Remove the address from the results.
|
|
400
|
+
if corrupted or not compare(value, self.__value):
|
|
401
|
+
self.__address_list.delete(index)
|
|
402
|
+
self.__value_list.delete(index)
|
|
403
|
+
self.__addresses.remove(address)
|
|
404
|
+
index -= 1
|
|
405
|
+
|
|
406
|
+
# Update the value at the listbox.
|
|
407
|
+
else:
|
|
408
|
+
self.__value_list.delete(index)
|
|
409
|
+
self.__value_list.insert(index, f"Value: {value}")
|
|
410
|
+
|
|
411
|
+
# Call the method again recursively
|
|
412
|
+
if index % 10 == 0: self.__progress_var.set((index / len(self.__addresses)) * 100)
|
|
413
|
+
self.after(5, lambda: self.__update_values(index + 1, remove=remove, first_call=False))
|
|
414
|
+
|
|
415
|
+
def __write_value(self):
|
|
416
|
+
"""
|
|
417
|
+
Change the value in memory of an address of the result list.
|
|
418
|
+
"""
|
|
419
|
+
try:
|
|
420
|
+
address = int(self.__address_entry.get().strip(), 16)
|
|
421
|
+
if address not in self.__addresses: raise ValueError()
|
|
422
|
+
except:
|
|
423
|
+
self.__address_entry.delete(0, "end")
|
|
424
|
+
return self.__address_entry.insert(0, "00000000")
|
|
425
|
+
|
|
426
|
+
# Get the inputs.
|
|
427
|
+
value = self.__new_value_entry.get()
|
|
428
|
+
pytype = self.__value_type
|
|
429
|
+
length = self.__value_length
|
|
430
|
+
|
|
431
|
+
if not value or length == 0: return
|
|
432
|
+
|
|
433
|
+
# Check if the value is valid for the selected value type.
|
|
434
|
+
try:
|
|
435
|
+
if str(pytype(value)) != value or (pytype is str and length < len(value)):
|
|
436
|
+
raise ValueError()
|
|
437
|
+
except:
|
|
438
|
+
self.__new_value_entry.delete(0, "end")
|
|
439
|
+
return self.__new_value_entry.insert(0, "Invalid value")
|
|
440
|
+
|
|
441
|
+
# Write the new value.
|
|
442
|
+
self.__process.write_process_memory(address, pytype, length, pytype(value))
|
|
443
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from tkinter import Label, Tk
|
|
2
|
+
from tkinter.ttk import Button, Entry
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from PyMemoryEditor import OpenProcess
|
|
6
|
+
from PyMemoryEditor.errors import ProcessIDNotExistsError, ProcessNotFoundError
|
|
7
|
+
from PyMemoryEditor.process import AbstractProcess
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OpenProcessWindow(Tk):
|
|
11
|
+
"""
|
|
12
|
+
Window for opening a process.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self):
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.__process = None
|
|
17
|
+
|
|
18
|
+
self["bg"] = "white"
|
|
19
|
+
|
|
20
|
+
self.geometry("350x100")
|
|
21
|
+
self.title("PyMemoryEditor (Sample)")
|
|
22
|
+
|
|
23
|
+
Label(self, text="Insert the PID or the process name:", bg="white", font=("Arial", 10)).pack(padx=20)
|
|
24
|
+
|
|
25
|
+
self.__entry = Entry(self)
|
|
26
|
+
self.__entry.pack(padx=50, fill="x", expand=True)
|
|
27
|
+
|
|
28
|
+
Button(self, text="Open Process", command=self.__open_process).pack(pady=10)
|
|
29
|
+
|
|
30
|
+
self.mainloop()
|
|
31
|
+
|
|
32
|
+
def __open_process(self):
|
|
33
|
+
"""
|
|
34
|
+
Open the process by the user input.
|
|
35
|
+
"""
|
|
36
|
+
entry = self.__entry.get().strip()
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
self.__process = OpenProcess(pid = int(entry))
|
|
40
|
+
return self.destroy()
|
|
41
|
+
|
|
42
|
+
except ValueError:
|
|
43
|
+
try:
|
|
44
|
+
self.__process = OpenProcess(process_name = entry)
|
|
45
|
+
return self.destroy()
|
|
46
|
+
except (ProcessIDNotExistsError, ProcessNotFoundError): pass
|
|
47
|
+
except (ProcessIDNotExistsError, ProcessNotFoundError): pass
|
|
48
|
+
|
|
49
|
+
self.__entry.delete(0, "end")
|
|
50
|
+
self.__entry.insert(0, "Process not found.")
|
|
51
|
+
|
|
52
|
+
def get_process(self) -> Optional[AbstractProcess]:
|
|
53
|
+
"""
|
|
54
|
+
Return the opened process.
|
|
55
|
+
"""
|
|
56
|
+
return self.__process
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from ..enums import ScanTypesEnum
|
|
4
4
|
from .search.kmp import KMPSearch
|
|
5
5
|
|
|
6
|
-
from typing import Sequence, Tuple, Union
|
|
6
|
+
from typing import Generator, Sequence, Tuple, Union
|
|
7
7
|
import ctypes
|
|
8
8
|
import sys
|
|
9
9
|
|
|
@@ -15,7 +15,7 @@ def scan_memory_for_exact_value(
|
|
|
15
15
|
target_value_size: int,
|
|
16
16
|
comparison: ScanTypesEnum = ScanTypesEnum.EXACT_VALUE,
|
|
17
17
|
*args, **kwargs
|
|
18
|
-
):
|
|
18
|
+
) -> Generator[int, None, None]:
|
|
19
19
|
"""
|
|
20
20
|
Search for an exact value at the memory region.
|
|
21
21
|
|
|
@@ -50,7 +50,7 @@ def scan_memory(
|
|
|
50
50
|
target_value_size: int,
|
|
51
51
|
scan_type: ScanTypesEnum,
|
|
52
52
|
is_string: bool,
|
|
53
|
-
):
|
|
53
|
+
) -> Generator[int, None, None]:
|
|
54
54
|
"""
|
|
55
55
|
Search for a value at the memory region.
|
|
56
56
|
"""
|
|
@@ -127,7 +127,7 @@ def ReadProcessMemory(
|
|
|
127
127
|
data = get_c_type_of(pytype, bufflength)
|
|
128
128
|
kernel32.ReadProcessMemory(process_handle, ctypes.c_void_p(address), ctypes.byref(data), bufflength, None)
|
|
129
129
|
|
|
130
|
-
return
|
|
130
|
+
return data.value.decode() if pytype is str else data.value
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def SearchAllMemory(
|
|
@@ -56,8 +56,10 @@ class WindowsProcess(AbstractProcess):
|
|
|
56
56
|
"""
|
|
57
57
|
Close the process handle.
|
|
58
58
|
"""
|
|
59
|
-
self.__closed
|
|
60
|
-
|
|
59
|
+
if self.__closed: return True
|
|
60
|
+
|
|
61
|
+
self.__closed = CloseProcessHandle(self.__process_handle) != 0
|
|
62
|
+
return self.__closed
|
|
61
63
|
|
|
62
64
|
def get_memory_regions(self) -> Generator[dict, None, None]:
|
|
63
65
|
"""
|
|
@@ -78,7 +78,7 @@ for address, info process.search_by_value(..., progress_information = True):
|
|
|
78
78
|
print(template.format(address, progress))
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
## Getting memory regions:
|
|
82
82
|
Use the method `get_memory_regions()` to get the base address, size and more information of all memory regions used by the process.
|
|
83
83
|
|
|
84
84
|
```py
|
|
@@ -87,3 +87,6 @@ for memory_region in process.get_memory_regions():
|
|
|
87
87
|
size = memory_region["size"]
|
|
88
88
|
information = memory_region["struct"]
|
|
89
89
|
```
|
|
90
|
+
|
|
91
|
+
# Tkinter application sample:
|
|
92
|
+
You can run a simple tkinter application to scan the memory of a process by typing `pymemoryeditor` at the CLI.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "PyMemoryEditor"
|
|
3
3
|
dynamic = ["version"]
|
|
4
|
-
description = "Multi-platform library developed with ctypes for reading, writing and searching process memory, in a simple and friendly way with Python 3.
|
|
4
|
+
description = "Multi-platform library developed with ctypes for reading, writing and searching process memory, in a simple and friendly way with Python 3."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Jean Loui Bernard Silva de Jesus", email = "jeanextreme002@gmail.com" },
|
|
7
7
|
]
|
|
@@ -58,4 +58,7 @@ path = "PyMemoryEditor/__init__.py"
|
|
|
58
58
|
|
|
59
59
|
[build-system]
|
|
60
60
|
requires = ["hatchling"]
|
|
61
|
-
build-backend = "hatchling.build"
|
|
61
|
+
build-backend = "hatchling.build"
|
|
62
|
+
|
|
63
|
+
[project.scripts]
|
|
64
|
+
pymemoryeditor = "PyMemoryEditor.sample.application:main"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/memory_allocation_states.py
RENAMED
|
File without changes
|
{pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/memory_protections.py
RENAMED
|
File without changes
|
|
File without changes
|
{pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/process_operations.py
RENAMED
|
File without changes
|
{pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/PyMemoryEditor/win32/enums/standard_access_rights.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/memory_types.py
RENAMED
|
File without changes
|
|
File without changes
|
{pymemoryeditor-1.5.7 → pymemoryeditor-1.5.8}/build/lib/PyMemoryEditor/win32/enums/scan_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|