PyMemoryEditor 3.3.0__py3-none-any.whl → 3.3.2__py3-none-any.whl
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/__init__.py +1 -1
- PyMemoryEditor/app/_auto_refresh_dialog.py +6 -7
- PyMemoryEditor/app/_widgets.py +7 -7
- PyMemoryEditor/app/application.py +3 -4
- PyMemoryEditor/app/cheat_poll_worker.py +5 -5
- PyMemoryEditor/app/cheat_table.py +17 -15
- PyMemoryEditor/app/main_window.py +11 -11
- PyMemoryEditor/app/memory_viewer_dialog.py +5 -5
- PyMemoryEditor/app/open_process_dialog.py +1 -1
- PyMemoryEditor/app/pointer_chain_dialog.py +2 -1
- PyMemoryEditor/app/pointer_scan_dialog.py +5 -5
- PyMemoryEditor/app/results_view.py +6 -6
- PyMemoryEditor/app/scanner_panel.py +1 -1
- PyMemoryEditor/app/value_types.py +3 -3
- PyMemoryEditor/linux/functions.py +29 -18
- PyMemoryEditor/linux/libc.py +2 -3
- PyMemoryEditor/linux/process.py +49 -112
- PyMemoryEditor/macos/functions.py +38 -25
- PyMemoryEditor/macos/process.py +47 -121
- PyMemoryEditor/mcp/config.py +12 -13
- PyMemoryEditor/mcp/policy.py +6 -6
- PyMemoryEditor/mcp/server.py +9 -9
- PyMemoryEditor/mcp/session.py +21 -23
- PyMemoryEditor/mcp/toolset.py +83 -88
- PyMemoryEditor/process/_guard.py +6 -5
- PyMemoryEditor/process/abstract.py +224 -25
- PyMemoryEditor/process/pointer_scan.py +2 -1
- PyMemoryEditor/process/region.py +10 -2
- PyMemoryEditor/process/scanning.py +9 -13
- PyMemoryEditor/util/scan.py +1 -2
- PyMemoryEditor/win32/enums/memory_protections.py +1 -1
- PyMemoryEditor/win32/functions.py +26 -15
- PyMemoryEditor/win32/process.py +52 -128
- {pymemoryeditor-3.3.0.dist-info → pymemoryeditor-3.3.2.dist-info}/METADATA +1 -1
- {pymemoryeditor-3.3.0.dist-info → pymemoryeditor-3.3.2.dist-info}/RECORD +38 -38
- {pymemoryeditor-3.3.0.dist-info → pymemoryeditor-3.3.2.dist-info}/WHEEL +0 -0
- {pymemoryeditor-3.3.0.dist-info → pymemoryeditor-3.3.2.dist-info}/entry_points.txt +0 -0
- {pymemoryeditor-3.3.0.dist-info → pymemoryeditor-3.3.2.dist-info}/licenses/LICENSE +0 -0
PyMemoryEditor/linux/process.py
CHANGED
|
@@ -3,20 +3,12 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import warnings
|
|
6
|
-
from typing import Generator, Optional, Sequence, Tuple, Type, TypeVar, Union
|
|
6
|
+
from typing import Generator, Optional, Pattern, Sequence, Tuple, Type, TypeVar, Union
|
|
7
7
|
|
|
8
8
|
from ..enums import ScanTypesEnum
|
|
9
|
-
from ..util.pattern import compile_pattern
|
|
10
9
|
from ..process import AbstractProcess
|
|
11
|
-
from ..process._guard import guard_generator
|
|
12
10
|
from ..process.errors import ClosedProcess, ProcessIDNotExistsError
|
|
13
|
-
from ..util import
|
|
14
|
-
UNSET,
|
|
15
|
-
prepare_write,
|
|
16
|
-
resolve_bufflength,
|
|
17
|
-
prepare_search_target,
|
|
18
|
-
resolve_bufflength_for_value,
|
|
19
|
-
)
|
|
11
|
+
from ..util import UNSET, prepare_write, resolve_bufflength
|
|
20
12
|
from ..process.module_info import ModuleInfo
|
|
21
13
|
from ..process.region import MemoryRegion
|
|
22
14
|
from ..process.thread_info import ThreadInfo
|
|
@@ -106,7 +98,7 @@ class LinuxProcess(AbstractProcess):
|
|
|
106
98
|
stacklevel=2,
|
|
107
99
|
)
|
|
108
100
|
|
|
109
|
-
def
|
|
101
|
+
def _require_open(self) -> None:
|
|
110
102
|
if self.__closed:
|
|
111
103
|
raise ClosedProcess()
|
|
112
104
|
|
|
@@ -121,10 +113,13 @@ class LinuxProcess(AbstractProcess):
|
|
|
121
113
|
``hot_path`` marks single-value reads, which callers issue in tight
|
|
122
114
|
loops: they are verified only when the cheap pidfd probe is
|
|
123
115
|
available. Without a pidfd the start-time read would roughly
|
|
124
|
-
quadruple the cost of every read, so there they
|
|
125
|
-
|
|
116
|
+
quadruple the cost of every read, so there they are not verified.
|
|
117
|
+
Writes are always verified, and so are scans and listings
|
|
118
|
+
when they are called; their generators are not re-verified between
|
|
119
|
+
items, so one held while the target exits and its pid is reused would
|
|
120
|
+
read the newcomer -- create the scan again after a long pause.
|
|
126
121
|
"""
|
|
127
|
-
self.
|
|
122
|
+
self._require_open()
|
|
128
123
|
if hot_path and self.__pidfd is None:
|
|
129
124
|
return
|
|
130
125
|
if not self._target_alive():
|
|
@@ -182,17 +177,17 @@ class LinuxProcess(AbstractProcess):
|
|
|
182
177
|
self.__require_target()
|
|
183
178
|
return _detect_process_64bit(self.pid)
|
|
184
179
|
|
|
185
|
-
def
|
|
180
|
+
def _require_listing(self) -> None:
|
|
186
181
|
self.__require_target()
|
|
187
|
-
return guard_generator(get_memory_regions(self.pid), self.__require_open)
|
|
188
182
|
|
|
189
|
-
def
|
|
190
|
-
self.
|
|
191
|
-
return guard_generator(get_threads(self.pid), self.__require_open)
|
|
183
|
+
def _iter_memory_regions(self) -> Generator[MemoryRegion, None, None]:
|
|
184
|
+
return get_memory_regions(self.pid)
|
|
192
185
|
|
|
193
|
-
def
|
|
194
|
-
self.
|
|
195
|
-
|
|
186
|
+
def _iter_threads(self) -> Generator[ThreadInfo, None, None]:
|
|
187
|
+
return get_threads(self.pid)
|
|
188
|
+
|
|
189
|
+
def _iter_modules(self) -> Generator[ModuleInfo, None, None]:
|
|
190
|
+
return get_modules(self.pid)
|
|
196
191
|
|
|
197
192
|
def read_process_memory(
|
|
198
193
|
self,
|
|
@@ -209,121 +204,63 @@ class LinuxProcess(AbstractProcess):
|
|
|
209
204
|
self.__require_target(hot_path=True)
|
|
210
205
|
return read_process_memory_into(self.pid, address, buffer)
|
|
211
206
|
|
|
212
|
-
def
|
|
207
|
+
def _scan_addresses(
|
|
213
208
|
self,
|
|
214
209
|
pytype: Type[T],
|
|
215
|
-
bufflength:
|
|
216
|
-
addresses: Sequence[int]
|
|
210
|
+
bufflength: int,
|
|
211
|
+
addresses: Sequence[int],
|
|
217
212
|
*,
|
|
218
|
-
raise_error: bool
|
|
219
|
-
memory_regions: Optional[Sequence[MemoryRegion]]
|
|
213
|
+
raise_error: bool,
|
|
214
|
+
memory_regions: Optional[Sequence[MemoryRegion]],
|
|
220
215
|
) -> Generator[Tuple[int, Optional[T]], None, None]:
|
|
221
|
-
|
|
222
|
-
if addresses is UNSET:
|
|
223
|
-
raise TypeError("addresses is required.")
|
|
224
|
-
scan = search_values_by_addresses(
|
|
216
|
+
return search_values_by_addresses(
|
|
225
217
|
self.pid,
|
|
226
218
|
pytype,
|
|
227
|
-
|
|
219
|
+
bufflength,
|
|
228
220
|
addresses,
|
|
229
221
|
memory_regions=memory_regions,
|
|
230
222
|
raise_error=raise_error,
|
|
231
223
|
)
|
|
232
|
-
return guard_generator(scan, self.__require_open)
|
|
233
224
|
|
|
234
|
-
def
|
|
225
|
+
def _scan_values(
|
|
235
226
|
self,
|
|
236
|
-
pytype: Type
|
|
237
|
-
bufflength:
|
|
238
|
-
|
|
239
|
-
scan_type: ScanTypesEnum
|
|
227
|
+
pytype: Type,
|
|
228
|
+
bufflength: int,
|
|
229
|
+
target_value_bytes: Union[bytes, Tuple[bytes, ...]],
|
|
230
|
+
scan_type: ScanTypesEnum,
|
|
240
231
|
*,
|
|
241
|
-
progress_information: bool
|
|
242
|
-
writeable_only: bool
|
|
243
|
-
memory_regions: Optional[Sequence[MemoryRegion]]
|
|
244
|
-
aligned: bool
|
|
232
|
+
progress_information: bool,
|
|
233
|
+
writeable_only: bool,
|
|
234
|
+
memory_regions: Optional[Sequence[MemoryRegion]],
|
|
235
|
+
aligned: bool,
|
|
245
236
|
) -> Generator[Union[int, Tuple[int, dict]], None, None]:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if scan_type in [ScanTypesEnum.VALUE_BETWEEN, ScanTypesEnum.NOT_VALUE_BETWEEN]:
|
|
249
|
-
raise ValueError(
|
|
250
|
-
"Use the method search_by_value_between(...) to search within a range of values."
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
width = resolve_bufflength_for_value(pytype, bufflength, value)
|
|
254
|
-
# Validate now: the scan is a generator, so its own checks would
|
|
255
|
-
# only raise on the first next(), far from this call.
|
|
256
|
-
prepare_search_target(pytype, width, value)
|
|
257
|
-
scan = search_addresses_by_value(
|
|
237
|
+
return search_addresses_by_value(
|
|
258
238
|
self.pid,
|
|
259
239
|
pytype,
|
|
260
|
-
|
|
261
|
-
value,
|
|
262
|
-
scan_type,
|
|
263
|
-
progress_information,
|
|
264
|
-
writeable_only,
|
|
240
|
+
bufflength,
|
|
241
|
+
value=None, # given already encoded, as target_value_bytes
|
|
242
|
+
scan_type=scan_type,
|
|
243
|
+
progress_information=progress_information,
|
|
244
|
+
writeable_only=writeable_only,
|
|
265
245
|
memory_regions=memory_regions,
|
|
266
246
|
aligned=aligned,
|
|
247
|
+
target_value_bytes=target_value_bytes,
|
|
267
248
|
)
|
|
268
|
-
return guard_generator(scan, self.__require_open)
|
|
269
249
|
|
|
270
|
-
def
|
|
250
|
+
def _scan_pattern(
|
|
271
251
|
self,
|
|
272
|
-
|
|
252
|
+
compiled_pattern: Tuple[Pattern[bytes], int],
|
|
273
253
|
*,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
memory_regions: Optional[Sequence[MemoryRegion]] = None,
|
|
254
|
+
progress_information: bool,
|
|
255
|
+
memory_regions: Optional[Sequence[MemoryRegion]],
|
|
277
256
|
) -> Generator[Union[int, Tuple[int, dict]], None, None]:
|
|
278
|
-
|
|
279
|
-
# Compile now so a malformed pattern raises at the call, not on the
|
|
280
|
-
# first next() of the returned generator.
|
|
281
|
-
compile_pattern(pattern, byte_length=byte_length)
|
|
282
|
-
scan = search_addresses_by_pattern(
|
|
257
|
+
return search_addresses_by_pattern(
|
|
283
258
|
self.pid,
|
|
284
|
-
|
|
285
|
-
byte_length=byte_length,
|
|
259
|
+
compiled_pattern[0],
|
|
286
260
|
progress_information=progress_information,
|
|
287
261
|
memory_regions=memory_regions,
|
|
262
|
+
compiled_pattern=compiled_pattern,
|
|
288
263
|
)
|
|
289
|
-
return guard_generator(scan, self.__require_open)
|
|
290
|
-
|
|
291
|
-
def search_by_value_between(
|
|
292
|
-
self,
|
|
293
|
-
pytype: Type[T],
|
|
294
|
-
bufflength: Optional[int] = None,
|
|
295
|
-
start: Union[bool, int, float, str, bytes] = UNSET,
|
|
296
|
-
end: Union[bool, int, float, str, bytes] = UNSET,
|
|
297
|
-
*,
|
|
298
|
-
not_between: bool = False,
|
|
299
|
-
progress_information: bool = False,
|
|
300
|
-
writeable_only: bool = False,
|
|
301
|
-
memory_regions: Optional[Sequence[MemoryRegion]] = None,
|
|
302
|
-
aligned: bool = True,
|
|
303
|
-
) -> Generator[Union[int, Tuple[int, dict]], None, None]:
|
|
304
|
-
self.__require_target()
|
|
305
|
-
|
|
306
|
-
scan_type = (
|
|
307
|
-
ScanTypesEnum.NOT_VALUE_BETWEEN
|
|
308
|
-
if not_between
|
|
309
|
-
else ScanTypesEnum.VALUE_BETWEEN
|
|
310
|
-
)
|
|
311
|
-
width = resolve_bufflength_for_value(pytype, bufflength, start, end)
|
|
312
|
-
# Validate now: the scan is a generator, so its own checks would
|
|
313
|
-
# only raise on the first next(), far from this call.
|
|
314
|
-
prepare_search_target(pytype, width, (start, end))
|
|
315
|
-
scan = search_addresses_by_value(
|
|
316
|
-
self.pid,
|
|
317
|
-
pytype,
|
|
318
|
-
width,
|
|
319
|
-
(start, end),
|
|
320
|
-
scan_type,
|
|
321
|
-
progress_information,
|
|
322
|
-
writeable_only,
|
|
323
|
-
memory_regions=memory_regions,
|
|
324
|
-
aligned=aligned,
|
|
325
|
-
)
|
|
326
|
-
return guard_generator(scan, self.__require_open)
|
|
327
264
|
|
|
328
265
|
def write_process_memory(
|
|
329
266
|
self,
|
|
@@ -338,7 +275,7 @@ class LinuxProcess(AbstractProcess):
|
|
|
338
275
|
return value
|
|
339
276
|
|
|
340
277
|
def allocate_memory(self, size: int, *, permission=None) -> int:
|
|
341
|
-
self.
|
|
278
|
+
self._require_open()
|
|
342
279
|
raise NotImplementedError(
|
|
343
280
|
"allocate_memory is not supported on Linux: there is no syscall to "
|
|
344
281
|
"allocate memory in another process's address space (mmap only "
|
|
@@ -348,7 +285,7 @@ class LinuxProcess(AbstractProcess):
|
|
|
348
285
|
)
|
|
349
286
|
|
|
350
287
|
def free_memory(self, address: int, size: int = 0) -> bool:
|
|
351
|
-
self.
|
|
288
|
+
self._require_open()
|
|
352
289
|
raise NotImplementedError(
|
|
353
290
|
"free_memory is not supported on Linux (see allocate_memory): "
|
|
354
291
|
"releasing memory in another process would require a ptrace-based "
|
|
@@ -10,12 +10,13 @@ import logging
|
|
|
10
10
|
import os
|
|
11
11
|
import warnings
|
|
12
12
|
from contextlib import contextmanager
|
|
13
|
-
from typing import Generator, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union
|
|
13
|
+
from typing import Generator, Iterator, List, Optional, Pattern, Sequence, Tuple, Type, TypeVar, Union, cast
|
|
14
14
|
|
|
15
15
|
from ..enums import ScanTypesEnum
|
|
16
16
|
from ..process.errors import ProcessIDNotExistsError
|
|
17
17
|
from ..process.module_info import ModuleInfo
|
|
18
18
|
from ..process.region import (
|
|
19
|
+
ADDRESS_SPACE_END,
|
|
19
20
|
default_address_filter,
|
|
20
21
|
default_scan_filter,
|
|
21
22
|
MemoryRegion,
|
|
@@ -96,11 +97,10 @@ _REGION_SKIP_BUMP = 0x1000
|
|
|
96
97
|
# the whole 64-bit address space.
|
|
97
98
|
_REGION_MAX_CONSECUTIVE_FAILURES = 64
|
|
98
99
|
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
_ADDRESS_SPACE_END = 1 << 64
|
|
100
|
+
# The region walk advances a `mach_vm_address_t` (c_uint64) cursor in place,
|
|
101
|
+
# and ctypes wraps silently on overflow: a region reaching the top of the
|
|
102
|
+
# address space would send the cursor back to 0 and restart the walk from the
|
|
103
|
+
# bottom. It stops at ADDRESS_SPACE_END instead.
|
|
104
104
|
|
|
105
105
|
# mach_vm_write's data_count parameter is `mach_msg_type_number_t` — a 32-bit
|
|
106
106
|
# unsigned int per the Mach interface. A write larger than UINT32_MAX would
|
|
@@ -182,7 +182,8 @@ def release_task(task: int) -> bool:
|
|
|
182
182
|
if kr != KERN_SUCCESS:
|
|
183
183
|
# A failed deallocate leaks the send right for the lifetime of the
|
|
184
184
|
# process. It shouldn't happen for a port we own, but swallowing it
|
|
185
|
-
# silently
|
|
185
|
+
# silently would hide real leaks — log it so they're at least
|
|
186
|
+
# visible.
|
|
186
187
|
_logger.warning(
|
|
187
188
|
"mach_port_deallocate failed for task port %d: %s (kr=%d)",
|
|
188
189
|
task,
|
|
@@ -287,8 +288,8 @@ def get_memory_regions(task: int, pid: int = 0) -> Generator[MemoryRegion, None,
|
|
|
287
288
|
``mach_vm_region`` returning :data:`KERN_INVALID_ADDRESS` is the documented
|
|
288
289
|
way the kernel says "no more regions past this address" — the natural end
|
|
289
290
|
of the enumeration. Any *other* non-success code (``KERN_FAILURE``,
|
|
290
|
-
``KERN_NO_ACCESS`` on a guard page, etc.)
|
|
291
|
-
|
|
291
|
+
``KERN_NO_ACCESS`` on a guard page, etc.) does not end the enumeration,
|
|
292
|
+
which would silently truncate the region list: it is logged and the
|
|
292
293
|
cursor is bumped one page so the walk keeps going.
|
|
293
294
|
|
|
294
295
|
That skip is bounded: :data:`MACH_SEND_INVALID_DEST` (the task behind the
|
|
@@ -331,7 +332,7 @@ def get_memory_regions(task: int, pid: int = 0) -> Generator[MemoryRegion, None,
|
|
|
331
332
|
"advancing one page",
|
|
332
333
|
address.value, kr, mach_error_message(kr),
|
|
333
334
|
)
|
|
334
|
-
if address.value + _REGION_SKIP_BUMP >=
|
|
335
|
+
if address.value + _REGION_SKIP_BUMP >= ADDRESS_SPACE_END:
|
|
335
336
|
return
|
|
336
337
|
address.value += _REGION_SKIP_BUMP
|
|
337
338
|
continue
|
|
@@ -358,7 +359,7 @@ def get_memory_regions(task: int, pid: int = 0) -> Generator[MemoryRegion, None,
|
|
|
358
359
|
|
|
359
360
|
if size.value == 0:
|
|
360
361
|
# Defensive: a successful query should never report an empty
|
|
361
|
-
# region. Stopping here
|
|
362
|
+
# region. Stopping here would drop every
|
|
362
363
|
# region above it, so step one page past it like the Windows
|
|
363
364
|
# walk does — counted as a failure, so a kernel answering 0
|
|
364
365
|
# forever can't turn this into a crawl across the address space.
|
|
@@ -375,7 +376,7 @@ def get_memory_regions(task: int, pid: int = 0) -> Generator[MemoryRegion, None,
|
|
|
375
376
|
consecutive_failures = 0
|
|
376
377
|
step = size.value
|
|
377
378
|
|
|
378
|
-
if address.value + step >=
|
|
379
|
+
if address.value + step >= ADDRESS_SPACE_END:
|
|
379
380
|
return # the last region reaches the top of the address space
|
|
380
381
|
address.value += step
|
|
381
382
|
|
|
@@ -392,7 +393,7 @@ def get_memory_regions(task: int, pid: int = 0) -> Generator[MemoryRegion, None,
|
|
|
392
393
|
# KERN_MEMORY_FAILURE beside it (the pair is spelled out in types.py). It is
|
|
393
394
|
# what file-backed, read-only mappings (code segments, dylibs, the dyld shared
|
|
394
395
|
# cache) return, so any scan that walks them — every pattern scan, and any value
|
|
395
|
-
# scan with "writable regions only" turned off — hits it routinely
|
|
396
|
+
# scan with "writable regions only" turned off — hits it routinely and must not
|
|
396
397
|
# abort on the first one.
|
|
397
398
|
_PAGE_GONE_KRS = (
|
|
398
399
|
KERN_INVALID_ADDRESS,
|
|
@@ -416,10 +417,10 @@ _PAGE_GONE_KRS = (
|
|
|
416
417
|
# silently turn "permission denied" into the same `(address, None)` the caller
|
|
417
418
|
# gets for an address in a gap.
|
|
418
419
|
#
|
|
419
|
-
# Honest limits of what is known here.
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
#
|
|
420
|
+
# Honest limits of what is known here. A plain search_by_value over the
|
|
421
|
+
# current process gets kr=2 on GitHub's virtualized macOS runners, on a 22 MB
|
|
422
|
+
# region that reports VM_PROT_READ and Shared=False. The mechanism is not
|
|
423
|
+
# established. It does *not* reproduce
|
|
423
424
|
# locally: probing all 135 scannable regions of a live process yields only kr=1
|
|
424
425
|
# and kr=10, and a page deliberately re-protected to PROT_NONE (or never
|
|
425
426
|
# mapped) answers KERN_INVALID_ADDRESS, not kr=2 — so the usual
|
|
@@ -442,8 +443,8 @@ class MachPartialReadError(MachReadError):
|
|
|
442
443
|
because the read straddled a freed or guarded page) and the caller's
|
|
443
444
|
buffer is part real-bytes, part zero-initialized.
|
|
444
445
|
|
|
445
|
-
|
|
446
|
-
|
|
446
|
+
Accepting the short result would let downstream code decode garbage as
|
|
447
|
+
valid memory, so it raises instead. Mirrors the Win32
|
|
447
448
|
partial-read check on ``ReadProcessMemory``. Scan loops classify this
|
|
448
449
|
as transient so the chunk is skipped instead of aborting.
|
|
449
450
|
"""
|
|
@@ -767,9 +768,9 @@ def read_process_memory(
|
|
|
767
768
|
_mach_read(task, address, ctypes.addressof(data), bufflength)
|
|
768
769
|
|
|
769
770
|
if pytype is str:
|
|
770
|
-
return bytes(data).decode("utf-8", errors="replace")
|
|
771
|
+
return cast(T, bytes(data).decode("utf-8", errors="replace"))
|
|
771
772
|
elif pytype is bytes:
|
|
772
|
-
return bytes(data)
|
|
773
|
+
return cast(T, bytes(data))
|
|
773
774
|
else:
|
|
774
775
|
# Narrow widths pad with zeroes, which reads a signed value as
|
|
775
776
|
# unsigned -- and a scan for the same bytes is signed.
|
|
@@ -866,7 +867,7 @@ def search_addresses_by_value(
|
|
|
866
867
|
task: int,
|
|
867
868
|
pytype: Type[T],
|
|
868
869
|
bufflength: int,
|
|
869
|
-
value: Union[bool, int, float, str, bytes, tuple],
|
|
870
|
+
value: Optional[Union[bool, int, float, str, bytes, tuple]],
|
|
870
871
|
scan_type: ScanTypesEnum = ScanTypesEnum.EXACT_VALUE,
|
|
871
872
|
progress_information: bool = False,
|
|
872
873
|
writeable_only: bool = False,
|
|
@@ -874,6 +875,7 @@ def search_addresses_by_value(
|
|
|
874
875
|
memory_regions: Optional[Sequence[MemoryRegion]] = None,
|
|
875
876
|
pid: int = 0,
|
|
876
877
|
aligned: bool = True,
|
|
878
|
+
target_value_bytes: Optional[Union[bytes, Tuple[bytes, ...]]] = None,
|
|
877
879
|
) -> Generator[Union[int, Tuple[int, dict]], None, None]:
|
|
878
880
|
"""
|
|
879
881
|
Walk every readable, non-shared region of the task and yield addresses
|
|
@@ -881,10 +883,14 @@ def search_addresses_by_value(
|
|
|
881
883
|
(including the dyld shared cache) are skipped via ``default_scan_filter``.
|
|
882
884
|
|
|
883
885
|
Passing a `memory_regions` snapshot skips region enumeration.
|
|
886
|
+
``target_value_bytes`` is ``value`` already encoded (see
|
|
887
|
+
:func:`~PyMemoryEditor.util.prepare_search_target`); when it is given,
|
|
888
|
+
``value`` is not used and may be ``None``.
|
|
884
889
|
"""
|
|
885
890
|
_validate_pytype(pytype)
|
|
886
891
|
|
|
887
|
-
target_value_bytes
|
|
892
|
+
if target_value_bytes is None:
|
|
893
|
+
target_value_bytes = values_to_bytes(pytype, bufflength, value)
|
|
888
894
|
|
|
889
895
|
source_regions = (
|
|
890
896
|
memory_regions if memory_regions is not None else get_memory_regions(task, pid)
|
|
@@ -1385,13 +1391,20 @@ def search_addresses_by_pattern(
|
|
|
1385
1391
|
byte_length: int = 0,
|
|
1386
1392
|
progress_information: bool = False,
|
|
1387
1393
|
memory_regions: Optional[Sequence[MemoryRegion]] = None,
|
|
1394
|
+
compiled_pattern: Optional[Tuple[Pattern[bytes], int]] = None,
|
|
1388
1395
|
pid: int = 0,
|
|
1389
1396
|
) -> Generator[Union[int, Tuple[int, dict]], None, None]:
|
|
1390
1397
|
"""
|
|
1391
1398
|
AOB scan against every readable, non-shared region of the target task. See
|
|
1392
|
-
:meth:`AbstractProcess.search_by_pattern`.
|
|
1399
|
+
:meth:`AbstractProcess.search_by_pattern`. ``compiled_pattern`` is what
|
|
1400
|
+
:func:`~PyMemoryEditor.util.pattern.compile_pattern` returned for
|
|
1401
|
+
``pattern``, when the caller already compiled it.
|
|
1393
1402
|
"""
|
|
1394
|
-
compiled, length =
|
|
1403
|
+
compiled, length = (
|
|
1404
|
+
compiled_pattern
|
|
1405
|
+
if compiled_pattern is not None
|
|
1406
|
+
else compile_pattern(pattern, byte_length=byte_length)
|
|
1407
|
+
)
|
|
1395
1408
|
|
|
1396
1409
|
source_regions = (
|
|
1397
1410
|
memory_regions if memory_regions is not None else get_memory_regions(task, pid)
|