PyMemoryEditor 1.5.8__tar.gz → 1.5.9__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.8 → pymemoryeditor-1.5.9}/.github/workflows/python-package.yml +1 -1
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PKG-INFO +13 -5
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/__init__.py +1 -1
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/linux/functions.py +68 -4
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/linux/process.py +25 -4
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/process/__init__.py +16 -1
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/sample/main_application_window.py +40 -47
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/util/__init__.py +16 -1
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/functions.py +70 -6
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/process.py +28 -4
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/README.md +12 -4
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/tests/test_editor.py +84 -19
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/.gitignore +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/LICENSE +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/enums.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/errors.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/linux/ptrace/__init__.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/linux/ptrace/enums.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/linux/types.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/process/errors.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/process/info.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/process/util.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/sample/application.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/sample/open_process_window.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/util/scan.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/util/search/abstract.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/util/search/kmp.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/__init__.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/memory_protections.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/memory_types.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/process_operations.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/types.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/__init__.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/open_process.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/process/__init__.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/process/errors.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/process/util.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/__init__.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/memory_protections.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/memory_types.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/process_operations.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/scan_types.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/functions.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/types.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/util.py +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/pyproject.toml +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/requirements.txt +0 -0
- {pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/tests/package.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PyMemoryEditor
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.9
|
|
4
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>
|
|
@@ -47,6 +47,9 @@ reading and writing values in the process memory.
|
|
|
47
47
|
pip3 install PyMemoryEditor
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
### Tkinter application sample:
|
|
51
|
+
You can run a simple tkinter application to scan the memory of a process by typing `pymemoryeditor` at the CLI.
|
|
52
|
+
|
|
50
53
|
# Basic Usage:
|
|
51
54
|
Import `PyMemoryEditor` and open a process using the `OpenProcess` class, passing a window title, process name <br>
|
|
52
55
|
or PID as an argument. You can use the context manager to do this.
|
|
@@ -103,13 +106,21 @@ All methods described above work even for strings, including the method `search_
|
|
|
103
106
|
## Progress information on searching:
|
|
104
107
|
These methods has the `progress_information` parameter that returns a dictionary containing the search progress information.
|
|
105
108
|
```py
|
|
106
|
-
for address, info process.search_by_value(..., progress_information = True):
|
|
109
|
+
for address, info in process.search_by_value(..., progress_information = True):
|
|
107
110
|
template = "Address: 0x{:<10X} | Progress: {:.1f}%"
|
|
108
111
|
progress = info["progress"] * 100
|
|
109
112
|
|
|
110
113
|
print(template.format(address, progress))
|
|
111
114
|
```
|
|
112
115
|
|
|
116
|
+
# Reading multiple addresses efficiently:
|
|
117
|
+
If you have a large number of addresses where their values need to be read from memory, using the `search_by_addresses` method is much more efficient than reading the value of each address one by one.
|
|
118
|
+
```py
|
|
119
|
+
for address, value in process.search_by_addresses(int, 4, addresses_list):
|
|
120
|
+
print(f"Address", address, "holds the value", value)
|
|
121
|
+
```
|
|
122
|
+
The key advantage of this method is that it reads a memory page just once, obtaining the values of the addresses within the page. This approach reduces the frequency of system calls.
|
|
123
|
+
|
|
113
124
|
## Getting memory regions:
|
|
114
125
|
Use the method `get_memory_regions()` to get the base address, size and more information of all memory regions used by the process.
|
|
115
126
|
|
|
@@ -119,6 +130,3 @@ for memory_region in process.get_memory_regions():
|
|
|
119
130
|
size = memory_region["size"]
|
|
120
131
|
information = memory_region["struct"]
|
|
121
132
|
```
|
|
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.
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
# https://man7.org/linux/man-pages/man5/proc.5.html
|
|
8
8
|
|
|
9
9
|
from ctypes import addressof, sizeof
|
|
10
|
-
from typing import Generator, Tuple, Type, TypeVar, Union
|
|
10
|
+
from typing import Dict, Generator, Optional, Sequence, Tuple, Type, TypeVar, Union
|
|
11
11
|
|
|
12
12
|
from ..enums import ScanTypesEnum
|
|
13
|
-
from ..util import get_c_type_of, scan_memory, scan_memory_for_exact_value
|
|
13
|
+
from ..util import convert_from_bytearray, get_c_type_of, scan_memory, scan_memory_for_exact_value
|
|
14
14
|
from .ptrace import libc
|
|
15
15
|
from .types import MEMORY_BASIC_INFORMATION, iovec
|
|
16
16
|
|
|
@@ -67,10 +67,10 @@ def read_process_memory(
|
|
|
67
67
|
pid, (iovec * 1)(iovec(addressof(data), sizeof(data))),
|
|
68
68
|
1, (iovec * 1)(iovec(address, sizeof(data))), 1, 0
|
|
69
69
|
)
|
|
70
|
-
return
|
|
70
|
+
return data.value.decode() if pytype is str else data.value
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def
|
|
73
|
+
def search_addresses_by_value(
|
|
74
74
|
pid: int,
|
|
75
75
|
pytype: Type[T],
|
|
76
76
|
bufflength: int,
|
|
@@ -147,6 +147,70 @@ def search_all_memory(
|
|
|
147
147
|
checked_memory_size += size
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
def search_values_by_addresses(
|
|
151
|
+
pid: int,
|
|
152
|
+
pytype: Type[T],
|
|
153
|
+
bufflength: int,
|
|
154
|
+
addresses: Sequence[int],
|
|
155
|
+
*,
|
|
156
|
+
memory_regions: Optional[Sequence[Dict]] = None,
|
|
157
|
+
raise_error: bool = False,
|
|
158
|
+
) -> Generator[Tuple[int, Optional[T]], None, None]:
|
|
159
|
+
"""
|
|
160
|
+
Search the whole memory space, accessible to the process,
|
|
161
|
+
for the provided list of addresses, returning their values.
|
|
162
|
+
"""
|
|
163
|
+
if pytype not in [bool, int, float, str, bytes]:
|
|
164
|
+
raise ValueError("The type must be bool, int, float, str or bytes.")
|
|
165
|
+
|
|
166
|
+
memory_regions = list(memory_regions) if memory_regions else list()
|
|
167
|
+
addresses = sorted(addresses)
|
|
168
|
+
|
|
169
|
+
# If no memory page has been given, get all readable memory pages.
|
|
170
|
+
if not memory_regions:
|
|
171
|
+
for region in get_memory_regions(pid):
|
|
172
|
+
if not b"r" in region["struct"].Privileges: continue
|
|
173
|
+
memory_regions.append(region)
|
|
174
|
+
|
|
175
|
+
memory_regions.sort(key=lambda region: region["address"])
|
|
176
|
+
address_index = 0
|
|
177
|
+
|
|
178
|
+
# Walk by each memory region.
|
|
179
|
+
for region in memory_regions:
|
|
180
|
+
if address_index >= len(addresses): continue
|
|
181
|
+
|
|
182
|
+
target_address = addresses[address_index]
|
|
183
|
+
|
|
184
|
+
# Check if the memory region contains the target address.
|
|
185
|
+
base_address, size = region["address"], region["size"]
|
|
186
|
+
if not (base_address <= target_address < base_address + size): continue
|
|
187
|
+
|
|
188
|
+
region_data = (ctypes.c_byte * size)()
|
|
189
|
+
|
|
190
|
+
# Get data from the region.
|
|
191
|
+
libc.process_vm_readv(
|
|
192
|
+
pid, (iovec * 1)(iovec(addressof(region_data), sizeof(region_data))),
|
|
193
|
+
1, (iovec * 1)(iovec(base_address, sizeof(region_data))), 1, 0
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Get the value of each address.
|
|
197
|
+
while base_address <= target_address < base_address + size:
|
|
198
|
+
offset = target_address - base_address
|
|
199
|
+
address_index += 1
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
data = region_data[offset: offset + bufflength]
|
|
203
|
+
data = (ctypes.c_byte * bufflength)(*data)
|
|
204
|
+
yield target_address, convert_from_bytearray(data, pytype, bufflength)
|
|
205
|
+
|
|
206
|
+
except Exception as error:
|
|
207
|
+
if raise_error: raise error
|
|
208
|
+
yield target_address, None
|
|
209
|
+
|
|
210
|
+
if address_index >= len(addresses): break
|
|
211
|
+
target_address = addresses[address_index]
|
|
212
|
+
|
|
213
|
+
|
|
150
214
|
def write_process_memory(
|
|
151
215
|
pid: int,
|
|
152
216
|
address: int,
|
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
from ..enums import ScanTypesEnum
|
|
4
4
|
from ..errors import ClosedProcess
|
|
5
5
|
from ..process import AbstractProcess
|
|
6
|
-
from .functions import
|
|
7
|
-
|
|
6
|
+
from .functions import (
|
|
7
|
+
get_memory_regions,
|
|
8
|
+
read_process_memory,
|
|
9
|
+
search_addresses_by_value,
|
|
10
|
+
search_values_by_addresses,
|
|
11
|
+
write_process_memory
|
|
12
|
+
)
|
|
13
|
+
from typing import Generator, Optional, Sequence, Tuple, Type, TypeVar, Union
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
T = TypeVar("T")
|
|
@@ -68,6 +74,21 @@ class LinuxProcess(AbstractProcess):
|
|
|
68
74
|
|
|
69
75
|
return read_process_memory(self.pid, address, pytype, bufflength)
|
|
70
76
|
|
|
77
|
+
def search_by_addresses(
|
|
78
|
+
self,
|
|
79
|
+
pytype: Type[T],
|
|
80
|
+
bufflength: int,
|
|
81
|
+
addresses: Sequence[int],
|
|
82
|
+
*,
|
|
83
|
+
raise_error: bool = False,
|
|
84
|
+
) -> Generator[Tuple[int, Optional[T]], None, None]:
|
|
85
|
+
"""
|
|
86
|
+
Search the whole memory space, accessible to the process,
|
|
87
|
+
for the provided list of addresses, returning their values.
|
|
88
|
+
"""
|
|
89
|
+
if self.__closed: raise ClosedProcess()
|
|
90
|
+
return search_values_by_addresses(self.pid, pytype, bufflength, addresses, raise_error=raise_error)
|
|
91
|
+
|
|
71
92
|
def search_by_value(
|
|
72
93
|
self,
|
|
73
94
|
pytype: Type[T],
|
|
@@ -94,7 +115,7 @@ class LinuxProcess(AbstractProcess):
|
|
|
94
115
|
if scan_type in [ScanTypesEnum.VALUE_BETWEEN, ScanTypesEnum.NOT_VALUE_BETWEEN]:
|
|
95
116
|
raise ValueError("Use the method search_by_value_between(...) to search within a range of values.")
|
|
96
117
|
|
|
97
|
-
return
|
|
118
|
+
return search_addresses_by_value(self.pid, pytype, bufflength, value, scan_type, progress_information, writeable_only)
|
|
98
119
|
|
|
99
120
|
def search_by_value_between(
|
|
100
121
|
self,
|
|
@@ -122,7 +143,7 @@ class LinuxProcess(AbstractProcess):
|
|
|
122
143
|
if self.__closed: raise ClosedProcess()
|
|
123
144
|
|
|
124
145
|
scan_type = ScanTypesEnum.NOT_VALUE_BETWEEN if not_between else ScanTypesEnum.VALUE_BETWEEN
|
|
125
|
-
return
|
|
146
|
+
return search_addresses_by_value(self.pid, pytype, bufflength, (start, end), scan_type, progress_information, writeable_only)
|
|
126
147
|
|
|
127
148
|
def write_process_memory(
|
|
128
149
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
-
from typing import Generator, Optional, Tuple, Type, TypeVar, Union
|
|
3
|
+
from typing import Generator, Optional, Sequence, Tuple, Type, TypeVar, Union
|
|
4
4
|
|
|
5
5
|
from ..enums import ScanTypesEnum
|
|
6
6
|
from ..process.info import ProcessInfo
|
|
@@ -61,6 +61,21 @@ class AbstractProcess(ABC):
|
|
|
61
61
|
"""
|
|
62
62
|
raise NotImplementedError()
|
|
63
63
|
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def search_by_addresses(
|
|
66
|
+
self,
|
|
67
|
+
pytype: Type[T],
|
|
68
|
+
bufflength: int,
|
|
69
|
+
addresses: Sequence[int],
|
|
70
|
+
*,
|
|
71
|
+
raise_error: bool = False,
|
|
72
|
+
) -> Generator[Tuple[int, Optional[T]], None, None]:
|
|
73
|
+
"""
|
|
74
|
+
Search the whole memory space, accessible to the process,
|
|
75
|
+
for the provided list of addresses, returning their values.
|
|
76
|
+
"""
|
|
77
|
+
raise NotImplementedError()
|
|
78
|
+
|
|
64
79
|
@abstractmethod
|
|
65
80
|
def search_by_value(
|
|
66
81
|
self,
|
{pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/sample/main_application_window.py
RENAMED
|
@@ -21,7 +21,6 @@ class ApplicationWindow(Tk):
|
|
|
21
21
|
self.__process = process
|
|
22
22
|
|
|
23
23
|
self.__scan_type = ScanTypesEnum.EXACT_VALUE
|
|
24
|
-
self.__value = 0
|
|
25
24
|
self.__value_type = int
|
|
26
25
|
self.__value_length = 4
|
|
27
26
|
self.__addresses = []
|
|
@@ -211,9 +210,7 @@ class ApplicationWindow(Tk):
|
|
|
211
210
|
|
|
212
211
|
# Start the scan.
|
|
213
212
|
value = pytype(value)
|
|
214
|
-
|
|
215
213
|
self.__value_length = length
|
|
216
|
-
self.__value = value
|
|
217
214
|
|
|
218
215
|
self.after(100, lambda: self.__start_scan(pytype, length, value, scan_type))
|
|
219
216
|
|
|
@@ -288,7 +285,7 @@ class ApplicationWindow(Tk):
|
|
|
288
285
|
if self.__close: break
|
|
289
286
|
|
|
290
287
|
self.__address_list.insert("end", f"Addr: {hex(address)[2:].upper()}")
|
|
291
|
-
self.__value_list.insert("end", f"Value:
|
|
288
|
+
self.__value_list.insert("end", f"Value: loading...")
|
|
292
289
|
|
|
293
290
|
self.__progress_var.set(info["progress"] * 100)
|
|
294
291
|
self.__addresses.append(address)
|
|
@@ -296,6 +293,8 @@ class ApplicationWindow(Tk):
|
|
|
296
293
|
|
|
297
294
|
self.__count_label.config(text=f"Found {len(self.__addresses)} addresses.")
|
|
298
295
|
|
|
296
|
+
self.__update_values()
|
|
297
|
+
|
|
299
298
|
self.__new_scan_button.config(text="New Scan")
|
|
300
299
|
self.__next_scan_button.config(text="Next Scan")
|
|
301
300
|
self.__progress_var.set(100)
|
|
@@ -344,73 +343,67 @@ class ApplicationWindow(Tk):
|
|
|
344
343
|
if char not in "0123456789ABCDEF": return False
|
|
345
344
|
return True
|
|
346
345
|
|
|
347
|
-
def __update_values(self,
|
|
346
|
+
def __update_values(self, *, remove: bool = False):
|
|
348
347
|
"""
|
|
349
348
|
Update the values of the found addresses. If "remove" is True, it will
|
|
350
349
|
compare the current value in memory and remove the address from the
|
|
351
350
|
results if the comparison is False.
|
|
352
351
|
"""
|
|
353
|
-
if self.__updating
|
|
352
|
+
if self.__updating: return
|
|
354
353
|
|
|
355
|
-
|
|
356
|
-
if self.__close:
|
|
357
|
-
self.__updating = False
|
|
358
|
-
return
|
|
354
|
+
if not self.__addresses: return self.__progress_var.set(100)
|
|
359
355
|
|
|
360
356
|
# Get the value to compare.
|
|
361
|
-
|
|
362
|
-
|
|
357
|
+
value = self.__value_entry.get().strip()
|
|
358
|
+
total = len(self.__addresses)
|
|
363
359
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
360
|
+
try:
|
|
361
|
+
if str(self.__value_type(value)) != value:
|
|
362
|
+
raise ValueError()
|
|
363
|
+
except:
|
|
364
|
+
self.__value_entry.delete(0, "end")
|
|
365
|
+
return self.__value_entry.insert(0, "Invalid value")
|
|
370
366
|
|
|
371
|
-
|
|
372
|
-
|
|
367
|
+
self.__value = self.__value_type(value)
|
|
368
|
+
self.__progress_var.set(0)
|
|
373
369
|
|
|
374
370
|
# Indicate the application is updating the values.
|
|
375
371
|
self.__updating = True
|
|
376
372
|
|
|
377
|
-
|
|
373
|
+
new_scan_button_text = self.__new_scan_button["text"]
|
|
374
|
+
self.__new_scan_button.config(text="Updating")
|
|
378
375
|
|
|
379
|
-
# Get the address
|
|
380
|
-
|
|
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)
|
|
376
|
+
# Get the address and its current value in memory.
|
|
377
|
+
total, count = len(self.__addresses), 0
|
|
387
378
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
379
|
+
for address, value in self.__process.search_by_addresses(self.__value_type, self.__value_length, self.__addresses):
|
|
380
|
+
self.__progress_var.set((count / total) * 100)
|
|
381
|
+
self.update()
|
|
391
382
|
|
|
392
|
-
|
|
393
|
-
|
|
383
|
+
index = self.__addresses.index(address)
|
|
384
|
+
count += 1
|
|
394
385
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
386
|
+
# Return if user asked for closing the application.
|
|
387
|
+
if self.__close:
|
|
388
|
+
self.__updating = False
|
|
389
|
+
return
|
|
398
390
|
|
|
399
|
-
#
|
|
400
|
-
if
|
|
391
|
+
# If value is corrupted or "remove" is True and comparison is False, remove the value from the results.
|
|
392
|
+
if value is None or (remove and not self.__comp_methods[self.__scan_type](value, self.__value)):
|
|
401
393
|
self.__address_list.delete(index)
|
|
402
394
|
self.__value_list.delete(index)
|
|
403
395
|
self.__addresses.remove(address)
|
|
404
|
-
index -= 1
|
|
405
396
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
397
|
+
# Update the value at the listbox.
|
|
398
|
+
else:
|
|
399
|
+
self.__value_list.delete(index)
|
|
400
|
+
self.__value_list.insert(index, f"Value: {value}")
|
|
401
|
+
|
|
402
|
+
self.__new_scan_button.config(text=new_scan_button_text)
|
|
403
|
+
self.__updating = False
|
|
410
404
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
self.after(5, lambda: self.__update_values(index + 1, remove=remove, first_call=False))
|
|
405
|
+
self.__count_label.config(text=f"Found {len(self.__addresses)} addresses.")
|
|
406
|
+
self.__progress_var.set(100)
|
|
414
407
|
|
|
415
408
|
def __write_value(self):
|
|
416
409
|
"""
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
from .scan import scan_memory, scan_memory_for_exact_value
|
|
4
|
-
from typing import Type
|
|
4
|
+
from typing import Type, TypeVar
|
|
5
5
|
import ctypes
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def convert_from_bytearray(bytearray: ctypes.Array, pytype: Type[T], length: int = 1) -> T:
|
|
12
|
+
"""
|
|
13
|
+
Convert a byte array to a Python type.
|
|
14
|
+
"""
|
|
15
|
+
if pytype is bytes: return bytes(bytearray)
|
|
16
|
+
if pytype is str: return bytes(bytearray).decode()
|
|
17
|
+
|
|
18
|
+
c_value = get_c_type_of(pytype, length)
|
|
19
|
+
|
|
20
|
+
return c_value.__class__.from_buffer(bytearray).value
|
|
21
|
+
|
|
22
|
+
|
|
8
23
|
def get_c_type_of(pytype: Type, length: int = 1) -> ctypes._SimpleCData:
|
|
9
24
|
"""
|
|
10
25
|
Return a C type of a primitive type of the Python language.
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
# ...
|
|
8
8
|
|
|
9
9
|
from ..enums import ScanTypesEnum
|
|
10
|
-
from ..util import get_c_type_of, scan_memory, scan_memory_for_exact_value
|
|
10
|
+
from ..util import convert_from_bytearray, get_c_type_of, scan_memory, scan_memory_for_exact_value
|
|
11
11
|
|
|
12
12
|
from .enums import MemoryAllocationStatesEnum, MemoryProtectionsEnum, MemoryTypesEnum
|
|
13
13
|
from .types import MEMORY_BASIC_INFORMATION, SYSTEM_INFO, WNDENUMPROC
|
|
14
14
|
|
|
15
|
-
from typing import Generator, Tuple, Type, TypeVar, Union
|
|
15
|
+
from typing import Dict, Generator, Optional, Sequence, Tuple, Type, TypeVar, Union
|
|
16
16
|
|
|
17
17
|
import ctypes
|
|
18
18
|
import ctypes.wintypes
|
|
@@ -130,7 +130,7 @@ def ReadProcessMemory(
|
|
|
130
130
|
return data.value.decode() if pytype is str else data.value
|
|
131
131
|
|
|
132
132
|
|
|
133
|
-
def
|
|
133
|
+
def SearchAddressesByValue(
|
|
134
134
|
process_handle: int,
|
|
135
135
|
pytype: Type[T],
|
|
136
136
|
bufflength: int,
|
|
@@ -163,7 +163,7 @@ def SearchAllMemory(
|
|
|
163
163
|
# Get the memory regions, computing the total amount of memory to be scanned.
|
|
164
164
|
checked_memory_size = 0
|
|
165
165
|
memory_total = 0
|
|
166
|
-
|
|
166
|
+
memory_regions = list()
|
|
167
167
|
|
|
168
168
|
for region in GetMemoryRegions(process_handle):
|
|
169
169
|
|
|
@@ -176,10 +176,10 @@ def SearchAllMemory(
|
|
|
176
176
|
if writeable_only and region["struct"].Protect & MemoryProtectionsEnum.PAGE_READWRITEABLE.value == 0: continue
|
|
177
177
|
|
|
178
178
|
memory_total += region["size"]
|
|
179
|
-
|
|
179
|
+
memory_regions.append(region)
|
|
180
180
|
|
|
181
181
|
# Check each memory region used by the process.
|
|
182
|
-
for region in
|
|
182
|
+
for region in memory_regions:
|
|
183
183
|
address, size = region["address"], region["size"]
|
|
184
184
|
region_data = (ctypes.c_byte * size)()
|
|
185
185
|
|
|
@@ -206,6 +206,70 @@ def SearchAllMemory(
|
|
|
206
206
|
checked_memory_size += size
|
|
207
207
|
|
|
208
208
|
|
|
209
|
+
def SearchValuesByAddresses(
|
|
210
|
+
process_handle: int,
|
|
211
|
+
pytype: Type[T],
|
|
212
|
+
bufflength: int,
|
|
213
|
+
addresses: Sequence[int],
|
|
214
|
+
*,
|
|
215
|
+
memory_regions: Optional[Sequence[Dict]] = None,
|
|
216
|
+
raise_error: bool = False,
|
|
217
|
+
) -> Generator[Tuple[int, Optional[T]], None, None]:
|
|
218
|
+
"""
|
|
219
|
+
Search the whole memory space, accessible to the process,
|
|
220
|
+
for the provided list of addresses, returning their values.
|
|
221
|
+
"""
|
|
222
|
+
if pytype not in [bool, int, float, str, bytes]:
|
|
223
|
+
raise ValueError("The type must be bool, int, float, str or bytes.")
|
|
224
|
+
|
|
225
|
+
memory_regions = list(memory_regions) if memory_regions else list()
|
|
226
|
+
addresses = sorted(addresses)
|
|
227
|
+
|
|
228
|
+
# If no memory page has been given, get all committed, non-shared and readable memory pages.
|
|
229
|
+
if not memory_regions:
|
|
230
|
+
for region in GetMemoryRegions(process_handle):
|
|
231
|
+
if region["struct"].State != MemoryAllocationStatesEnum.MEM_COMMIT.value: continue
|
|
232
|
+
if region["struct"].Type != MemoryTypesEnum.MEM_PRIVATE.value: continue
|
|
233
|
+
if region["struct"].Protect & MemoryProtectionsEnum.PAGE_READABLE.value == 0: continue
|
|
234
|
+
|
|
235
|
+
memory_regions.append(region)
|
|
236
|
+
|
|
237
|
+
memory_regions.sort(key=lambda region: region["address"])
|
|
238
|
+
address_index = 0
|
|
239
|
+
|
|
240
|
+
# Walk by each memory region.
|
|
241
|
+
for region in memory_regions:
|
|
242
|
+
if address_index >= len(addresses): continue
|
|
243
|
+
|
|
244
|
+
target_address = addresses[address_index]
|
|
245
|
+
|
|
246
|
+
# Check if the memory region contains the target address.
|
|
247
|
+
base_address, size = region["address"], region["size"]
|
|
248
|
+
if not (base_address <= target_address < base_address + size): continue
|
|
249
|
+
|
|
250
|
+
region_data = (ctypes.c_byte * size)()
|
|
251
|
+
|
|
252
|
+
# Get data from the region.
|
|
253
|
+
kernel32.ReadProcessMemory(process_handle, ctypes.c_void_p(base_address), ctypes.byref(region_data), size, None)
|
|
254
|
+
|
|
255
|
+
# Get the value of each address.
|
|
256
|
+
while base_address <= target_address < base_address + size:
|
|
257
|
+
offset = target_address - base_address
|
|
258
|
+
address_index += 1
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
data = region_data[offset: offset + bufflength]
|
|
262
|
+
data = (ctypes.c_byte * bufflength)(*data)
|
|
263
|
+
yield target_address, convert_from_bytearray(data, pytype, bufflength)
|
|
264
|
+
|
|
265
|
+
except Exception as error:
|
|
266
|
+
if raise_error: raise error
|
|
267
|
+
yield target_address, None
|
|
268
|
+
|
|
269
|
+
if address_index >= len(addresses): break
|
|
270
|
+
target_address = addresses[address_index]
|
|
271
|
+
|
|
272
|
+
|
|
209
273
|
def WriteProcessMemory(
|
|
210
274
|
process_handle: int,
|
|
211
275
|
address: int,
|
|
@@ -10,11 +10,12 @@ from .functions import (
|
|
|
10
10
|
GetMemoryRegions,
|
|
11
11
|
GetProcessHandle,
|
|
12
12
|
ReadProcessMemory,
|
|
13
|
-
|
|
13
|
+
SearchAddressesByValue,
|
|
14
|
+
SearchValuesByAddresses,
|
|
14
15
|
WriteProcessMemory
|
|
15
16
|
)
|
|
16
17
|
|
|
17
|
-
from typing import Generator, Optional, Tuple, Type, TypeVar, Union
|
|
18
|
+
from typing import Generator, Optional, Sequence, Tuple, Type, TypeVar, Union
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
T = TypeVar("T")
|
|
@@ -70,6 +71,29 @@ class WindowsProcess(AbstractProcess):
|
|
|
70
71
|
|
|
71
72
|
return GetMemoryRegions(self.__process_handle)
|
|
72
73
|
|
|
74
|
+
def search_by_addresses(
|
|
75
|
+
self,
|
|
76
|
+
pytype: Type[T],
|
|
77
|
+
bufflength: int,
|
|
78
|
+
addresses: Sequence[int],
|
|
79
|
+
*,
|
|
80
|
+
raise_error: bool = False,
|
|
81
|
+
) -> Generator[Tuple[int, Optional[T]], None, None]:
|
|
82
|
+
"""
|
|
83
|
+
Search the whole memory space, accessible to the process,
|
|
84
|
+
for the provided list of addresses, returning their values.
|
|
85
|
+
"""
|
|
86
|
+
if self.__closed: raise ClosedProcess()
|
|
87
|
+
|
|
88
|
+
valid_permissions = [
|
|
89
|
+
ProcessOperationsEnum.PROCESS_ALL_ACCESS.value,
|
|
90
|
+
ProcessOperationsEnum.PROCESS_VM_READ.value
|
|
91
|
+
]
|
|
92
|
+
if self.__permission.value not in valid_permissions:
|
|
93
|
+
raise PermissionError("The handle does not have permission to read the process memory.")
|
|
94
|
+
|
|
95
|
+
return SearchValuesByAddresses(self.__process_handle, pytype, bufflength, addresses, raise_error=raise_error)
|
|
96
|
+
|
|
73
97
|
def search_by_value(
|
|
74
98
|
self,
|
|
75
99
|
pytype: Type[T],
|
|
@@ -103,7 +127,7 @@ class WindowsProcess(AbstractProcess):
|
|
|
103
127
|
if scan_type in [ScanTypesEnum.VALUE_BETWEEN, ScanTypesEnum.NOT_VALUE_BETWEEN]:
|
|
104
128
|
raise ValueError("Use the method search_by_value_between(...) to search within a range of values.")
|
|
105
129
|
|
|
106
|
-
return
|
|
130
|
+
return SearchAddressesByValue(self.__process_handle, pytype, bufflength, value, scan_type, progress_information, writeable_only)
|
|
107
131
|
|
|
108
132
|
def search_by_value_between(
|
|
109
133
|
self,
|
|
@@ -138,7 +162,7 @@ class WindowsProcess(AbstractProcess):
|
|
|
138
162
|
raise PermissionError("The handle does not have permission to read the process memory.")
|
|
139
163
|
|
|
140
164
|
scan_type = ScanTypesEnum.NOT_VALUE_BETWEEN if not_between else ScanTypesEnum.VALUE_BETWEEN
|
|
141
|
-
return
|
|
165
|
+
return SearchAddressesByValue(self.__process_handle, pytype, bufflength, (start, end), scan_type, progress_information, writeable_only)
|
|
142
166
|
|
|
143
167
|
def read_process_memory(
|
|
144
168
|
self,
|
|
@@ -15,6 +15,9 @@ reading and writing values in the process memory.
|
|
|
15
15
|
pip3 install PyMemoryEditor
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
### Tkinter application sample:
|
|
19
|
+
You can run a simple tkinter application to scan the memory of a process by typing `pymemoryeditor` at the CLI.
|
|
20
|
+
|
|
18
21
|
# Basic Usage:
|
|
19
22
|
Import `PyMemoryEditor` and open a process using the `OpenProcess` class, passing a window title, process name <br>
|
|
20
23
|
or PID as an argument. You can use the context manager to do this.
|
|
@@ -71,13 +74,21 @@ All methods described above work even for strings, including the method `search_
|
|
|
71
74
|
## Progress information on searching:
|
|
72
75
|
These methods has the `progress_information` parameter that returns a dictionary containing the search progress information.
|
|
73
76
|
```py
|
|
74
|
-
for address, info process.search_by_value(..., progress_information = True):
|
|
77
|
+
for address, info in process.search_by_value(..., progress_information = True):
|
|
75
78
|
template = "Address: 0x{:<10X} | Progress: {:.1f}%"
|
|
76
79
|
progress = info["progress"] * 100
|
|
77
80
|
|
|
78
81
|
print(template.format(address, progress))
|
|
79
82
|
```
|
|
80
83
|
|
|
84
|
+
# Reading multiple addresses efficiently:
|
|
85
|
+
If you have a large number of addresses where their values need to be read from memory, using the `search_by_addresses` method is much more efficient than reading the value of each address one by one.
|
|
86
|
+
```py
|
|
87
|
+
for address, value in process.search_by_addresses(int, 4, addresses_list):
|
|
88
|
+
print(f"Address", address, "holds the value", value)
|
|
89
|
+
```
|
|
90
|
+
The key advantage of this method is that it reads a memory page just once, obtaining the values of the addresses within the page. This approach reduces the frequency of system calls.
|
|
91
|
+
|
|
81
92
|
## Getting memory regions:
|
|
82
93
|
Use the method `get_memory_regions()` to get the base address, size and more information of all memory regions used by the process.
|
|
83
94
|
|
|
@@ -87,6 +98,3 @@ for memory_region in process.get_memory_regions():
|
|
|
87
98
|
size = memory_region["size"]
|
|
88
99
|
information = memory_region["struct"]
|
|
89
100
|
```
|
|
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.
|
|
@@ -68,13 +68,15 @@ def test_read_int():
|
|
|
68
68
|
|
|
69
69
|
def test_read_string():
|
|
70
70
|
# Get a random text to compare the result.
|
|
71
|
-
target_value = ctypes.create_string_buffer(
|
|
71
|
+
target_value = ctypes.create_string_buffer(20)
|
|
72
|
+
target_value.value = generate_text(20).encode()
|
|
73
|
+
|
|
72
74
|
address = ctypes.addressof(target_value)
|
|
73
75
|
data_length = ctypes.sizeof(target_value)
|
|
74
76
|
|
|
75
77
|
# Read the process memory and compare the result.
|
|
76
78
|
result = process.read_process_memory(address, str, data_length)
|
|
77
|
-
assert type(result) is str and result ==
|
|
79
|
+
assert type(result) is str and result == target_value.value.decode()
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
def test_write_bool():
|
|
@@ -134,20 +136,69 @@ def test_write_string():
|
|
|
134
136
|
original_value = generate_text(20).encode()
|
|
135
137
|
new_value = generate_text(20).encode()
|
|
136
138
|
|
|
137
|
-
target_value = ctypes.create_string_buffer(
|
|
139
|
+
target_value = ctypes.create_string_buffer(20)
|
|
140
|
+
target_value.value = original_value
|
|
141
|
+
|
|
138
142
|
address = ctypes.addressof(target_value)
|
|
139
143
|
data_length = ctypes.sizeof(target_value)
|
|
140
144
|
|
|
141
145
|
# Write to the process memory and compare the result.
|
|
142
|
-
process.write_process_memory(address, str, data_length, new_value)
|
|
146
|
+
process.write_process_memory(address, str, data_length, new_value.decode())
|
|
143
147
|
assert target_value.value != original_value and target_value.value == new_value
|
|
144
148
|
|
|
145
149
|
|
|
150
|
+
def test_search_by_int_addresses():
|
|
151
|
+
# Get random values to compare the result.
|
|
152
|
+
test_length = 10
|
|
153
|
+
|
|
154
|
+
target_values = [ctypes.c_int(random.randint(0, 10000)) for i in range(test_length)]
|
|
155
|
+
data_length = ctypes.sizeof(target_values[0])
|
|
156
|
+
|
|
157
|
+
target_values = {ctypes.addressof(v): v for v in target_values}
|
|
158
|
+
addresses = list(target_values.keys())
|
|
159
|
+
|
|
160
|
+
for address, value in process.search_by_addresses(int, data_length, addresses):
|
|
161
|
+
assert target_values[address].value == value and type(value) is int
|
|
162
|
+
|
|
163
|
+
def test_search_by_float_addresses():
|
|
164
|
+
# Get random values to compare the result.
|
|
165
|
+
test_length = 10
|
|
166
|
+
|
|
167
|
+
target_values = [ctypes.c_double(random.randint(0, 10000) / random.randint(0, 10000)) for i in range(test_length)]
|
|
168
|
+
data_length = ctypes.sizeof(target_values[0])
|
|
169
|
+
|
|
170
|
+
target_values = {ctypes.addressof(v): v for v in target_values}
|
|
171
|
+
addresses = list(target_values.keys())
|
|
172
|
+
|
|
173
|
+
for address, value in process.search_by_addresses(float, data_length, addresses):
|
|
174
|
+
assert target_values[address].value == value and type(value) is float
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_search_by_string_addresses():
|
|
178
|
+
# Get random values to compare the result.
|
|
179
|
+
string_length, test_length = 20, 10
|
|
180
|
+
|
|
181
|
+
target_values = list()
|
|
182
|
+
|
|
183
|
+
for i in range(test_length):
|
|
184
|
+
value = ctypes.create_string_buffer(string_length)
|
|
185
|
+
value.value = generate_text(string_length).encode()
|
|
186
|
+
target_values.append(value)
|
|
187
|
+
|
|
188
|
+
data_length = ctypes.sizeof(target_values[0])
|
|
189
|
+
|
|
190
|
+
target_values = {ctypes.addressof(v): v for v in target_values}
|
|
191
|
+
addresses = list(target_values.keys())
|
|
192
|
+
|
|
193
|
+
for address, value in process.search_by_addresses(str, data_length, addresses):
|
|
194
|
+
assert target_values[address].value.decode() == value and type(value) is str
|
|
195
|
+
|
|
196
|
+
|
|
146
197
|
def test_search_by_int():
|
|
147
198
|
# Get random values to compare the result.
|
|
148
|
-
|
|
199
|
+
test_length = 10
|
|
149
200
|
|
|
150
|
-
target_values = [ctypes.c_int(random.randint(0, 10000)) for i in range(
|
|
201
|
+
target_values = [ctypes.c_int(random.randint(0, 10000)) for i in range(test_length)]
|
|
151
202
|
addresses = [ctypes.addressof(v) for v in target_values]
|
|
152
203
|
data_length = ctypes.sizeof(target_values[0])
|
|
153
204
|
|
|
@@ -172,15 +223,15 @@ def test_search_by_int():
|
|
|
172
223
|
value = process.read_process_memory(found_address, int, data_length)
|
|
173
224
|
if min_value <= value <= max_value: correct += 1
|
|
174
225
|
|
|
175
|
-
assert found /
|
|
226
|
+
assert found / test_length >= 0.7
|
|
176
227
|
assert correct / total >= 0.7 # Some of the addresses are beyond our control and may have their values changed.
|
|
177
228
|
|
|
178
229
|
|
|
179
230
|
def test_search_by_float():
|
|
180
231
|
# Get random values to compare the result.
|
|
181
|
-
|
|
232
|
+
test_length = 10
|
|
182
233
|
|
|
183
|
-
target_values = [ctypes.c_double(random.randint(0, 10000)) for i in range(
|
|
234
|
+
target_values = [ctypes.c_double(random.randint(0, 10000)) for i in range(test_length)]
|
|
184
235
|
addresses = [ctypes.addressof(v) for v in target_values]
|
|
185
236
|
data_length = ctypes.sizeof(target_values[0])
|
|
186
237
|
|
|
@@ -205,15 +256,21 @@ def test_search_by_float():
|
|
|
205
256
|
value = process.read_process_memory(found_address, float, data_length)
|
|
206
257
|
if min_value <= value <= max_value: correct += 1
|
|
207
258
|
|
|
208
|
-
assert found /
|
|
259
|
+
assert found / test_length >= 0.7
|
|
209
260
|
assert correct / total >= 0.7 # Some of the addresses are beyond our control and may have their values changed.
|
|
210
261
|
|
|
211
262
|
|
|
212
263
|
def test_search_by_string():
|
|
213
264
|
# Get random values to compare the result.
|
|
214
|
-
|
|
265
|
+
string_length, test_length = 20, 10
|
|
266
|
+
|
|
267
|
+
target_values = list()
|
|
268
|
+
|
|
269
|
+
for i in range(test_length):
|
|
270
|
+
value = ctypes.create_string_buffer(string_length)
|
|
271
|
+
value.value = generate_text(string_length).encode()
|
|
272
|
+
target_values.append(value)
|
|
215
273
|
|
|
216
|
-
target_values = [ctypes.create_string_buffer(generate_text(20).encode()) for i in range(length)]
|
|
217
274
|
data_length = ctypes.sizeof(target_values[0])
|
|
218
275
|
|
|
219
276
|
total = 0
|
|
@@ -231,22 +288,30 @@ def test_search_by_string():
|
|
|
231
288
|
total += 1
|
|
232
289
|
|
|
233
290
|
# Check if the address really points to a valid value.
|
|
234
|
-
|
|
235
|
-
|
|
291
|
+
try:
|
|
292
|
+
value = process.read_process_memory(found_address, str, data_length)
|
|
293
|
+
if value == target_value.value.decode(): correct += 1
|
|
294
|
+
except: pass
|
|
236
295
|
|
|
237
|
-
assert found /
|
|
296
|
+
assert found / test_length >= 0.7
|
|
238
297
|
assert correct / total >= 0.7 # Some of the addresses are beyond our control and may have their values changed.
|
|
239
298
|
|
|
240
299
|
|
|
241
300
|
def test_search_by_string_between():
|
|
242
301
|
# Get random values to compare the result.
|
|
243
|
-
|
|
302
|
+
string_length, test_length = 20, 10
|
|
303
|
+
|
|
304
|
+
values = list()
|
|
305
|
+
|
|
306
|
+
for i in range(test_length * 2):
|
|
307
|
+
value = ctypes.create_string_buffer(string_length)
|
|
308
|
+
value.value = generate_text(string_length).encode()
|
|
309
|
+
values.append(value)
|
|
244
310
|
|
|
245
|
-
values = [ctypes.create_string_buffer(generate_text(20).encode()) for i in range(length * 2)]
|
|
246
311
|
values.sort(key = lambda target_value: target_value.value)
|
|
247
312
|
|
|
248
313
|
# Half of the set of strings is the target and the other half contains string that should be ignored by the scanner.
|
|
249
|
-
target_values = [target_value for target_value in values[
|
|
314
|
+
target_values = [target_value for target_value in values[test_length // 4: test_length - test_length // 4]]
|
|
250
315
|
|
|
251
316
|
addresses = [ctypes.addressof(v) for v in values]
|
|
252
317
|
target_addresses = [ctypes.addressof(v) for v in target_values]
|
|
@@ -269,7 +334,7 @@ def test_search_by_string_between():
|
|
|
269
334
|
elif found_address in addresses:
|
|
270
335
|
raise ValueError("Scanner returned the address of a clearly invalid string.")
|
|
271
336
|
|
|
272
|
-
assert found /
|
|
337
|
+
assert found / test_length >= 0.5
|
|
273
338
|
|
|
274
339
|
|
|
275
340
|
def test_close_process():
|
|
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.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/memory_allocation_states.py
RENAMED
|
File without changes
|
{pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/memory_protections.py
RENAMED
|
File without changes
|
|
File without changes
|
{pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/PyMemoryEditor/win32/enums/process_operations.py
RENAMED
|
File without changes
|
{pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/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.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/build/lib/PyMemoryEditor/win32/enums/memory_types.py
RENAMED
|
File without changes
|
|
File without changes
|
{pymemoryeditor-1.5.8 → pymemoryeditor-1.5.9}/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
|