Pattern scan (AOB / regex)

A pattern scan locates data by its shape rather than its value — the technique Cheat Engine, IDA and Ghidra use to find code or data that moves between builds. PyMemoryEditor accepts three input forms, all powered by the same search_by_pattern method.

When to use it

You want…Use this
Find every email address in memoryRegex pattern
Locate a function whose absolute address changes between buildsIDA-style byte signature with wildcards
Recognize a custom struct headerRegex or IDA-style pattern

Three pattern formats

1. IDA-style byte signature

The format used by almost every public AOB recipe online — space-separated hex bytes with ? or ?? as one-byte wildcards:

for address in process.search_by_pattern("48 8B ? ? 00 00 89 ?"):
    print(f"Match at 0x{address:X}")
  • Each token is one byte.

  • Whitespace between tokens is free-form.

  • ? and ?? both mean “any byte”.

The number of matched bytes is inferred from the token count — you don’t have to pass byte_length=.

2. Raw bytes regex

Pass a bytes object — it’s compiled with re.DOTALL so . matches any byte (including \n, which is what you want when scanning binary memory):

# Every email address in memory
email = rb"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}"

for address in process.search_by_pattern(email, byte_length=128):
    raw = process.read_process_memory(address, bytes, 128)
    print(address, raw.split(b"\x00", 1)[0].decode("ascii", "replace"))

byte_length is required for regex

A regex doesn’t have a fixed match width, so the scanner can’t infer one. Pass byte_length= set to the maximum number of bytes one match can consume. The scanner uses it to compute chunk-overlap so matches that span a chunk boundary aren’t missed.

3. Pre-compiled re.Pattern[bytes]

If you reuse the same pattern many times:

import re

pattern = re.compile(rb"PLAYER_\d+", re.DOTALL)

for address in process.search_by_pattern(pattern, byte_length=32):
    print(hex(address))

Same rule: byte_length= is required.

Method signature

search_by_pattern(pattern, *, byte_length=0, progress_information=False, memory_regions=None)
Parameters:
  • pattern – an IDA-style hex string, a raw bytes regex, or a compiled re.Pattern[bytes].

  • byte_length (int) – required for regex / compiled patterns — the maximum number of bytes one match can consume. Ignored for IDA-style strings.

  • progress_information (bool) – when True, yields (address, info) tuples (same shape as search_by_value()).

  • memory_regions – optional snapshot from snapshot_memory_regions() to skip region enumeration on iterative workflows.

Returns:

a generator of addresses (or (address, info) tuples).

Examples in the wild

Locating a function after a patch

# Cheat Engine signature for a known function body.
pattern = "48 89 5C 24 ? 57 48 83 EC 20 48 8B D9 48 8B FA"

for address in process.search_by_pattern(pattern):
    print(f"Function at 0x{address:X}")

Because the byte signature stays stable across recompilations (only the addresses change), this finds the same function in every build.

Harvesting data from memory

import re

# Match an IPv4 address as ASCII.
ipv4 = re.compile(rb"(?<![\d.])(\d{1,3}\.){3}\d{1,3}(?!\d)")

for address in process.search_by_pattern(ipv4, byte_length=64):
    raw = process.read_process_memory(address, bytes, 64)
    print(address, raw.split(b"\x00", 1)[0].decode("ascii", "replace"))

Combining with the refine workflow

Pattern scans take a memory_regions= snapshot just like value scans:

regions = process.snapshot_memory_regions()

for address in process.search_by_pattern(pattern, memory_regions=regions):
    ...

Compiling patterns yourself

The pattern compiler is available as a standalone helper, useful for testing without a live process:

from PyMemoryEditor.util.pattern import compile_pattern

regex, byte_length = compile_pattern("48 8B ? 00 00")
print(repr(regex.pattern), byte_length)
# b'H\x8b.\x00\x00' 5

The compiled regex matches the right bytes regardless of how they print: re.escape renders a printable byte as its ASCII character (0x48H) and a non-printable one as \xNN (0x8B\x8b); ? becomes ..

See Utilities API for the full reference.