Split the library into CORE / ENGINE / HANDLERS layers

CORE (src/core): vmie_mem — guest-physical substrate with a data-driven
segment map (replaces the hardcoded 4 GiB PCI-hole topology). ENGINE
(src/engine): x86-64 paging + Windows bring-up; produces the generic memory
model. HANDLERS (src/handlers): the signature/value/pointer scanners, which
now consume an OS-agnostic contract.

Keystone: gva_ctx is split into vmie_mem (core) + vmie (engine); the generic
access functions take vmie_mem* + cr3 and no longer compile in the Windows
offset table. New public contract include/memmodel.h (vmie_mem, mem_view_t,
vregion, task, range, the gva_* access); win32 surface in include/vmie.h.
Leak relocations: the PE parser, UTF-16 decode and CR3-recovery heuristics
move engine-side; the matcher stays a pure, source-agnostic handler, and the
pointer scanner takes a generic range[] instead of reaching into the process
enumerator.
This commit is contained in:
2026-06-15 02:57:46 +03:00
parent 7c0995a4f2
commit b3441dd6f6
24 changed files with 1014 additions and 766 deletions
+156
View File
@@ -0,0 +1,156 @@
/* memmodel.h - the OS-agnostic memory-model contract (the middle layer).
*
* This is the shared vocabulary between the ENGINE (which turns guest-physical
* RAM into a usable virtual memory model via x86-64 paging + Windows bring-up)
* and the HANDLERS (scanners that consume that model). It names no Windows
* concept: a handler compiled against this header literally cannot mention an
* _EPROCESS, a PEB, or an LDR entry.
*
* Everything here is keyed by a `vmie_mem*` (the opaque physical/paging
* substrate) plus a `cr3` (the address space). The engine handle `vmie` is
* never handed to a handler - only `vmie_mem*` + `cr3`.
*
* Conventions:
* - `cr3` is a raw CR3 / DirectoryTableBase value; low flag bits are masked
* internally, so either the masked PML4 GPA or the raw register works.
* - A "VA" is a 64-bit canonical guest virtual address. Reads/writes that
* cross a page boundary are handled internally (per-page translation).
* - Integer returns: 0 on success, negative on failure, unless stated.
*/
#ifndef VMIE_MEMMODEL_H
#define VMIE_MEMMODEL_H
#include <stdint.h>
#include <stddef.h>
/* Opaque guest-physical memory handle (the mmap'd RAM backing file + segment
* map). Defined in src/core/include/core.h; handlers hold only a pointer and
* pass it, with a cr3, to the address-space primitives below. */
typedef struct vmie_mem vmie_mem;
/* ---- flat memory view (single owner) ------------------------------------- *
* A contiguous view of memory.
* data - host pointer to the bytes (borrowed; not owned by the view)
* size - number of valid bytes at `data`
* base_va - address that data[0] corresponds to (guest VA, or GPA for a
* physical view). All matches are reported as base_va + offset. */
typedef struct {
const uint8_t* data;
size_t size;
uint64_t base_va;
} mem_view_t;
/* ---- region map ---------------------------------------------------------- *
* A vregion is one run of VA-contiguous, present guest pages sharing the same
* effective protection. It is the unit of "what is mapped, and how" and the
* scoping primitive for the scanners (see scan.h).
*
* x86-64 has no read bit: a present page is readable, so VR_R is always set on a
* returned region. Write/execute/user are the EFFECTIVE rights along the whole
* page-table path (RW & US are AND-ed across levels, NX is OR-ed), not just the
* leaf entry, so they reflect what the guest CPU actually enforces. */
#ifndef VMIE_VREGION_DEFINED
#define VMIE_VREGION_DEFINED
#define VR_R 0x1u /* readable (present => always set) */
#define VR_W 0x2u /* writable (RW bit set at every level) */
#define VR_X 0x4u /* executable(NX clear at every level) */
#define VR_U 0x8u /* user-accessible (US bit set at every level) */
typedef struct {
uint64_t va; /* run start VA (clamped into the requested [lo,hi] window) */
uint64_t len; /* run length in bytes */
uint32_t prot; /* OR of VR_* flags */
} vregion;
#endif
/* Canonical VA-window bounds of the memory model, shared by every scanning TU.
* These describe the address space the contract operates over (the [lo,hi]
* windows of gva_regions/gva_sweep), so they are handler-visible.
* USER_MIN is 0x10000: the low 64 KiB is reserved, so no live user pointer
* targets below it - starting there drops a class of false positives. */
#define USER_MIN 0x0000000000010000ull
#define USER_MAX 0x00007FFFFFFFFFFFull
#define KERN_MIN 0xFFFF800000000000ull
/* ---- generic boundary types (replace the Windows-typed process/pmodule) --- *
* A schedulable address space, decoded by the engine from whatever the guest
* OS calls one. `cr3` is all a handler needs to read/write its memory.
* cr3 - DirectoryTableBase (PFN-masked); key to this address space
* pid, ppid - process / parent ids (ppid == (uint64_t)-1 if unavailable)
* name - short image name, NUL-terminated UTF-8 (engine-decoded) */
typedef struct {
uint64_t cr3;
uint64_t pid;
uint64_t ppid;
char name[16];
} task;
/* A named, contiguous VA range (e.g. a loaded module image), the anchor a
* pointer scan walks back to. The engine decodes the name; no LDR entry VA.
* base - range base VA (page-aligned)
* size - range length in bytes
* name - decoded UTF-8 name (e.g. "ntdll.dll"), NUL-terminated */
typedef struct {
uint64_t base;
uint64_t size;
char name[64];
} range;
/* ---- guest memory access (hot path) -------------------------------------- */
/* Read `nmemb` bytes from guest VA `va` (translated under `cr3`) into `dst`.
* Crosses page boundaries internally. Returns 0 on success, -1 if any page in
* the range is not present/translatable (in which case `dst` is partially
* written and must be treated as invalid). */
int gva_read(vmie_mem* m, uintptr_t cr3, uintptr_t va, void* dst, size_t nmemb);
/* Write `nmemb` bytes from `src` to guest VA `va` (translated under `cr3`).
* The mapping is RW and coherent, so the guest observes the change. Returns 0
* on success, -1 if any page in the range is not present/translatable. */
int gva_write(vmie_mem* m, uintptr_t cr3, uintptr_t va, const void* src, size_t nmemb);
/* Zero-copy borrowed read: host pointer to the guest byte at `va` (under `cr3`),
* valid for *avail contiguous bytes (to the end of the containing leaf). NULL if
* `va` is not mapped or the leaf is not fully covered by the image (caller falls
* back to gva_read). Borrowed: valid until the mapping is closed, do NOT retain. */
const void* gva_ptr(vmie_mem* m, uintptr_t cr3, uintptr_t va, size_t* avail);
/* Enumerate mapped memory under `cr3`, clamped to the VA window [lo,hi]
* (inclusive), as runs of equal effective protection.
* lo, hi - inclusive VA window; MUST lie within a single canonical half
* (entirely user or entirely kernel). Use (0, ~0ull) loosely; the
* walk prunes whole subtrees outside the window.
* prot_any - protection filter: 0 keeps every run; otherwise a run is kept
* only if (run.prot & prot_any) != 0 (e.g. VR_W for writable-only)
* out - caller array receiving up to `nmax` `vregion` records
* nmax - capacity of `out`
* Returns the TOTAL number of matching runs found. If the return value exceeds
* `nmax` the output was truncated; enlarge the buffer and retry. */
int gva_regions(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi,
uint32_t prot_any, vregion* out, int nmax);
/* ---- shared windowed sweep engine ---------------------------------------- *
* gva_sweep() streams every mapped byte under `cr3` within [lo,hi] that passes
* the protection filter to `cb`, one contiguous window at a time. Physical
* fragmentation is hidden: each window is a flat buffer (gva_read-filled), and
* adjacent windows of one run share `overlap` leading bytes so an object or
* pattern straddling a window boundary is still seen whole. */
typedef int (*gva_sweep_cb)(void* user, const uint8_t* data, size_t len,
uint64_t base_va, size_t overlap, int last);
/* user - passed through verbatim
* data - host buffer with `len` valid bytes (do not retain past the call)
* len - valid bytes at data
* base_va - guest VA of data[0]
* overlap - bytes at the front of `data` shared with the previous window of
* this run (0 on a run's first window or right after a gap)
* last - nonzero if this window ends a contiguous segment (run end / gap):
* accept hits up to `len`; otherwise drop hits starting in the
* trailing `overlap` zone, the next window re-presents them
* cb returns nonzero to abort the sweep early (e.g. result buffer full).
*
* gva_sweep() returns 0 normally, 1 if a callback aborted it, -1 on allocation
* failure. `overlap` must be < the internal window (1 MiB); patterns longer
* than that are not supported by the windowed path. */
int gva_sweep(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi,
uint32_t prot_any, size_t overlap, gva_sweep_cb cb, void* user);
#endif /* VMIE_MEMMODEL_H */
+20 -16
View File
@@ -1,16 +1,21 @@
/* scan.h - typed value scanner, pointer scanner, and gva<->signature bridges.
*
* Layered above the pure matcher (sigscan.h) and the gva core (include.h): this
* is the gva-bound scanning surface. The value scanner narrows a candidate set
* across successive snapshots; the pointer scanner discovers module-anchored
* Layered above the pure matcher (sigscan.h) and the generic memory-model
* contract (memmodel.h): this is the OS-agnostic scanning surface. Everything
* here is keyed by a `vmie_mem*` + `cr3` (and, for the pointer scan, a decoded
* `range[]`); it names no Windows object. The value scanner narrows a candidate
* set across successive snapshots; the pointer scanner discovers range-anchored
* pointer chains; the gva_sig_* bridges build mem_view_t windows out of guest
* memory and feed them to the signature matcher.
*
* The Windows-typed convenience entry points (scan_new(process*),
* vmie_scan_pointer(process*)) live in the win32 surface (vmie.h).
*/
#ifndef VMIE_SCAN_H
#define VMIE_SCAN_H
#include <stdint.h>
#include <stddef.h>
#include "include.h" /* gva_ctx, process (vregion - internal) */
#include "memmodel.h" /* vmie_mem, range, vregion */
#include "sigscan.h" /* mem_view_t, sig_pattern_t */
/* typed value scanner. ENUMERATOR ORDER IS LOAD-BEARING: scan.c indexes the
@@ -32,32 +37,31 @@ typedef struct { uint64_t addr; uint64_t value; } scan_hit;
#define SCAN_PTR_MAXDEPTH 8 /* DFS depth and size of off[] */
typedef struct {
uint64_t base; /* module-anchored base address */
uint64_t base; /* range-anchored base address */
int depth; /* number of offsets in off[] */
int32_t off[SCAN_PTR_MAXDEPTH]; /* dereference chain */
} scan_ptr_path;
scan* scan_new(gva_ctx* ctx, const process* pr, scan_type t, const void* value,
int be, int aligned, uint64_t lo, uint64_t hi);
scan* scan_new_cr3(gva_ctx* ctx, uintptr_t cr3, scan_type t, const void* value,
scan* scan_new_cr3(vmie_mem* m, uintptr_t cr3, scan_type t, const void* value,
int be, int aligned, uint64_t lo, uint64_t hi);
int64_t scan_next(scan* s, scan_op op, const void* value);
int64_t scan_count(scan* s);
int scan_results(scan* s, uint64_t offset, int max, scan_hit* out);
void scan_free(scan* s);
int scan_pointer(gva_ctx* ctx, const process* pr, uint64_t target,
int max_depth, uint32_t max_off, scan_ptr_path* out, int max);
int scan_pointer(vmie_mem* m, uintptr_t cr3, const range* mods, int nmods,
uint64_t target, int max_depth, uint32_t max_off,
scan_ptr_path* out, int max);
/* gva bridges to the signature matcher: build mem_view from guest memory and feed sigscan.h */
int gva_sig_scan (gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
int gva_sig_scan (vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi,
uint32_t prot_any, const sig_pattern_t* p, uint64_t* out, int max);
int gva_sig_first(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
int gva_sig_first(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi,
uint32_t prot_any, const sig_pattern_t* p, uint64_t* va);
int gva_sig_rip (gva_ctx* ctx, uintptr_t cr3, uint64_t hit_va,
int gva_sig_rip (vmie_mem* m, uintptr_t cr3, uint64_t hit_va,
size_t disp_off, size_t instr_len, uint64_t* target);
int gva_pe_section(gva_ctx* ctx, uintptr_t cr3, uint64_t module_base,
const char* name, uint8_t* buf, size_t bufcap, mem_view_t* out);
int gva_sig_phys (gva_ctx* ctx, const sig_pattern_t* p, uint64_t* out, int max);
/* gva_sig_phys (scan the raw physical image) needs the core segment map, so it
* is an engine bridge, declared in engine.h - not part of the handler surface. */
#endif /* VMIE_SCAN_H */
+4 -33
View File
@@ -6,26 +6,16 @@
* results are reported as addresses in the view's own coordinate space
* (base_va + offset): a guest VA for a virtual view, a GPA for a physical view.
*
* This module is pure: it never touches a gva_ctx and performs no I/O. To scan
* guest memory, build views from the gva layer (see scan.h: gva_sig_scan,
* gva_pe_section, gva_sig_phys) and feed them here.
* This module is pure: it never touches a vmie_mem and performs no I/O. To scan
* guest memory, build views from the gva layer (see scan.h: gva_sig_scan) and
* feed them here.
*/
#ifndef VMIE_SIGSCAN_H
#define VMIE_SIGSCAN_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
/* A contiguous view of memory.
* data - host pointer to the bytes (borrowed; not owned by the view)
* size - number of valid bytes at `data`
* base_va - address that data[0] corresponds to (guest VA, or GPA for a
* physical view). All matches are reported as base_va + offset. */
typedef struct {
const uint8_t* data;
size_t size;
uint64_t base_va;
} mem_view_t;
#include "memmodel.h" /* mem_view_t (the single owner of the view type) */
/* A parsed byte pattern. mask[i] == 1 means bytes[i] must match; 0 = wildcard.
* Owns two heap allocations of `len` bytes each; release with sig_free(). */
@@ -83,23 +73,4 @@ uint64_t sig_rip(mem_view_t v, uint64_t hit_va, size_t disp_off, size_t instr_le
* is actually available. Useful for narrowing a scan to a [start,end] window. */
mem_view_t mem_sub(mem_view_t v, uint64_t start_va, size_t size);
/* Locate a PE section by name within a view that contains at least the image
* headers at `module_base` (the first page is enough).
* module_base - image base VA, must be >= v.base_va and inside `v`
* name - section name, e.g. ".text" (compared up to 8 bytes)
* rva_out - receives the section RVA (relative to module_base); may be NULL
* vsize_out - receives the section virtual size; may be NULL
* Returns true if found. Only the headers need to be present in `v`; the section
* body does not. */
bool pe_find_section(mem_view_t v, uint64_t module_base, const char* name,
uint64_t* rva_out, uint32_t* vsize_out);
/* Locate a PE section AND return a sub-view spanning it. Requires the whole
* section body to be present in `v` (true for an in-memory image dump). Prefer
* scanning ".text" over a whole image: faster, and avoids false hits in data.
* Returns true and fills *out on success. For guest memory, where the body is
* usually not co-resident with the headers, use gva_pe_section (scan.h). */
bool pe_section(mem_view_t v, uint64_t module_base, const char* name,
mem_view_t* out);
#endif /* VMIE_SIGSCAN_H */
+46 -69
View File
@@ -1,30 +1,33 @@
/* include.h - public interface of the Windows VMI core.
/* vmie.h - public Windows-guest surface of the vmi-engine.
*
* The host opens a guest's RAM backing file (a flat, writable, coherent mmap),
* recovers the kernel address space, and reads/writes guest memory by CR3 and
* virtual address. Everything is CR3-keyed, never PID-keyed: a `process` already
* carries its own cr3, which is the key to that address space.
*
* Conventions used throughout this header:
* This header is the Windows-typed surface (process/pmodule/gtext, bring-up,
* enumeration, the win32 scan wrappers). The OS-agnostic memory-model contract
* lives in memmodel.h (pulled in below); the scanners in scan.h/sigscan.h.
*
* Conventions:
* - `cr3` is a raw CR3 / DirectoryTableBase value; low flag bits are masked
* internally, so either the masked PML4 GPA or the raw register works.
* - A "VA" is a 64-bit canonical guest virtual address. A "GPA" is a guest
* physical address. Reads/writes that cross a page boundary are handled
* internally (per-page translation), so callers pass plain ranges.
* - Integer returns: 0 on success, negative on failure, unless stated.
* - The library never takes ownership of caller buffers and never retains a
* pointer past the call that received it, unless explicitly stated.
*/
#ifndef VMIE_INCLUDE_H
#define VMIE_INCLUDE_H
#ifndef VMIE_VMIE_H
#define VMIE_VMIE_H
#include <stdint.h>
#include <stddef.h>
#include "memmodel.h" /* vmie_mem, vregion/VR_*, task/range, gva_read/write/ptr/regions/sweep */
#include "sigscan.h" /* mem_view_t, sig_pattern_t */
#include "scan.h" /* scan_type, scan_ptr_path, generic scan surface */
/* Opaque introspection context. Completed in src/include/memory.h; callers only
* ever hold a pointer. Created by gva_ctx_alloc(), populated by host_bootstrap(),
* released by gva_ctx_free(). */
typedef struct gva_ctx gva_ctx;
/* Opaque introspection context. Completed in src/engine/include/engine.h;
* callers only ever hold a pointer. Created by vmie_open(), populated by
* host_bootstrap(), released by vmie_close(). */
typedef struct vmie vmie;
/* A guest counted string still resident in guest memory (e.g. a UNICODE_STRING
* buffer). Not a copy: `va` points into the guest, decode it with gva_read_text.
@@ -69,26 +72,6 @@ typedef struct {
gtext path;
} pmodule;
/* ---- region map ---------------------------------------------------------- *
* A vregion is one run of VA-contiguous, present guest pages sharing the same
* effective protection. It is the unit of "what is mapped, and how" and the
* scoping primitive for the scanners (see scan.h).
*
* x86-64 has no read bit: a present page is readable, so VR_R is always set on a
* returned region. Write/execute/user are the EFFECTIVE rights along the whole
* page-table path (RW & US are AND-ed across levels, NX is OR-ed), not just the
* leaf entry, so they reflect what the guest CPU actually enforces. */
#define VR_R 0x1u /* readable (present => always set) */
#define VR_W 0x2u /* writable (RW bit set at every level) */
#define VR_X 0x4u /* executable(NX clear at every level) */
#define VR_U 0x8u /* user-accessible (US bit set at every level) */
typedef struct {
uint64_t va; /* run start VA (clamped into the requested [lo,hi] window) */
uint64_t len; /* run length in bytes */
uint32_t prot; /* OR of VR_* flags */
} vregion;
/* ---- lifecycle ----------------------------------------------------------- */
/* Open `ram_path` (the guest RAM backing file) and build a context over it.
@@ -97,33 +80,28 @@ typedef struct {
* pass the value from the VM's memory layout. If total RAM <= low,
* the split is inert.
* Returns a new context (call host_bootstrap() next), or NULL on open/mmap
* failure. Free with gva_ctx_free(). */
gva_ctx* gva_ctx_alloc(const char* ram_path, uint64_t low);
* failure. Free with vmie_close(). */
vmie* vmie_open(const char* ram_path, uint64_t low);
/* Unmap, close, and free a context. Safe on NULL. After this, every pointer
* into guest memory obtained through this context is invalid. */
void gva_ctx_free(gva_ctx* ctx);
void vmie_close(vmie* v);
/* Borrow the engine's guest-memory handle for the generic address-space
* primitives (gva_read/gva_regions/...). The returned pointer is owned by `v`
* and valid until vmie_close(v); do NOT free or retain it past that. NULL on
* NULL `v`. */
vmie_mem* vmie_memory(vmie* v);
/* One-shot bring-up: locate the guest agent beacon in physical RAM, recover a
* bootstrap CR3, find ntoskrnl, build the struct-offset profile, derive the
* permanent System DirectoryTableBase (kernel cr3) and System _EPROCESS, then
* ACK the agent. On success the context is ready for proc_list()/gva_read()/etc.
* Returns 0 on success, or a negative stage code (-1..-6) identifying the step
* that failed. Cold path: call once after gva_ctx_alloc(). */
int host_bootstrap(gva_ctx* ctx);
* that failed. Cold path: call once after vmie_open(). */
int host_bootstrap(vmie* v);
/* ---- guest memory access (hot path) -------------------------------------- */
/* Read `nmemb` bytes from guest VA `va` (translated under `cr3`) into `dst`.
* Crosses page boundaries internally. Returns 0 on success, -1 if any page in
* the range is not present/translatable (in which case `dst` is partially
* written and must be treated as invalid). */
int gva_read(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, void* dst, size_t nmemb);
/* Write `nmemb` bytes from `src` to guest VA `va` (translated under `cr3`).
* The mapping is RW and coherent, so the guest observes the change. Returns 0
* on success, -1 if any page in the range is not present/translatable. */
int gva_write(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, const void* src, size_t nmemb);
/* ---- guest string decode ------------------------------------------------- */
/* Read a UTF-16LE guest string and transcode it to UTF-8.
* va - guest VA of the first UTF-16 code unit
@@ -133,7 +111,7 @@ int gva_write(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, const void* src, size_t
* Returns the number of UTF-8 bytes the full conversion needs, EXCLUDING the
* terminator (like snprintf): if it is >= `size`, output was truncated. When
* `dst` is non-NULL and `size` > 0 the result is always NUL-terminated. */
size_t gva_read_text(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size);
size_t gva_read_text(vmie* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size);
/* ---- enumeration --------------------------------------------------------- */
@@ -143,30 +121,29 @@ size_t gva_read_text(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t nmemb, ch
* nmax - capacity of `dst`
* Returns the number written (<= nmax), or negative on failure (e.g. bootstrap
* not completed). Enumeration stops at `nmax`; raise it to see more. */
int proc_list(gva_ctx* ctx, int skip_system, process* dst, size_t nmax);
int proc_list(vmie* v, int skip_system, process* dst, size_t nmax);
/* Enumerate a process's loaded modules via the PEB loader InLoadOrder list.
* pr - process to inspect (uses pr->cr3 and pr->peb)
* dst - caller array receiving up to `nmax` `pmodule` records
* nmax - capacity of `dst`
* Returns the number written (<= nmax), 0 if the process has no PEB/loader. */
int proc_modules(gva_ctx* ctx, const process* pr, pmodule* dst, size_t nmax);
int proc_modules(vmie* v, const process* pr, pmodule* dst, size_t nmax);
/* Enumerate mapped memory under `cr3`, clamped to the VA window [lo,hi]
* (inclusive), as runs of equal effective protection.
* cr3 - address space to walk (a process cr3, or the kernel cr3)
* lo, hi - inclusive VA window; MUST lie within a single canonical half
* (entirely user or entirely kernel). Use (0, ~0ull) loosely; the
* walk prunes whole subtrees outside the window.
* prot_any - protection filter: 0 keeps every run; otherwise a run is kept
* only if (run.prot & prot_any) != 0 (e.g. VR_W for writable-only,
* VR_X for executable-only)
* out - caller array receiving up to `nmax` `vregion` records
* nmax - capacity of `out`
* Returns the TOTAL number of matching runs found. If the return value exceeds
* `nmax` the output was truncated (only `nmax` runs were written); enlarge the
* buffer and retry for the full map. */
int gva_regions(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
uint32_t prot_any, vregion* out, int nmax);
/* ---- win32 scan wrappers ------------------------------------------------- *
* Convenience entry points over the generic cr3/range scan surface (scan.h).
* They project a Windows `process` to its cr3, and its `pmodule[]` to a decoded
* `range[]` (UTF-8 names), then delegate to scan_new_cr3 / scan_pointer. */
#endif /* VMIE_INCLUDE_H */
/* Open a value-scan session over the user address space of `pr`. Equivalent to
* scan_new_cr3(&v->mem, pr->cr3, ...). Returns NULL on NULL pr or OOM. */
scan* scan_new(vmie* v, const process* pr, scan_type t, const void* value,
int be, int aligned, uint64_t lo, uint64_t hi);
/* Pointer scan over `pr`'s user space, anchored on its loaded modules. Resolves
* `pr`'s module list to range[] (names engine-decoded) and delegates to
* scan_pointer. Returns the number of paths found, or negative on failure. */
int vmie_scan_pointer(vmie* v, const process* pr, uint64_t target,
int max_depth, uint32_t max_off, scan_ptr_path* out, int max);
#endif /* VMIE_VMIE_H */