Define the win32 engine; add a dump source and physical sigscan

Name and isolate the Windows engine as one of potentially several. The
public surface moves to include/win32.h with an opaque vmie_win32 handle
(vmie_win32_open/close/mem); the engine's Windows internals — host bring-up,
the struct-offset profile, process/module/PE/text decode — live under
src/engine/win32. The generic address-space layer stays in src/engine
(gva.c + engine-arch.h, carrying no offset table): gva.c is de-profiled, and
CR3 bring-up reaches the hot translator through a cold gva_translate bridge
so the zero-copy hot path stays private and inlinable.

A memory source is now first-class and public: vmie_mem_open/_open_segs/
_close open a flat dump (or an explicit segment map) as a vmie_mem, with
gpa_seg promoted to the public contract. The physical signature scan is
exposed source-agnostically: sig_scan_mem returns GPAs for any vmie_mem,
sig_scan_sources scans several sources with per-source attribution, and
sig_from_bytes builds an exact needle from a byte span. The pure matcher is
unchanged; dumps and the live engine image are scanned uniformly, neither
needing the other.
This commit is contained in:
2026-06-15 08:20:50 +03:00
parent b3441dd6f6
commit 93966c3df2
21 changed files with 383 additions and 211 deletions
+34
View File
@@ -27,6 +27,40 @@
* pass it, with a cr3, to the address-space primitives below. */
typedef struct vmie_mem vmie_mem;
/* One contiguous GPA window backed by a file span: GPA [gpa, gpa+len) maps 1:1
* onto file offset [file_off, file_off+len). A POD descriptor, promoted here so
* an explicit-segment dump can be opened through the public surface below; the
* full vmie_mem (which embeds an array of these) is defined in core.h. */
#ifndef VMIE_GPA_SEG_DEFINED
#define VMIE_GPA_SEG_DEFINED
typedef struct gpa_seg {
uint64_t gpa;
uint64_t len;
uint64_t file_off;
} gpa_seg;
#endif
/* ---- dump source lifecycle ----------------------------------------------- *
* A vmie_mem is the universal memory source: a live win32 physical image and an
* on-disk dump are both vmie_mem. These open a dump (or any flat/segmented RAM
* image) as a heap-owned vmie_mem for the source-agnostic physical scanners
* (scan.h: sig_scan_mem/sig_scan_sources). No paging/cr3: a dump supports the
* physical signature scan only. The win32 engine produces its vmie_mem through
* the win32 surface (win32.h) instead. */
/* Open `path` as a single-`low` image (the classic QEMU split; low >= file size
* => one inert identity segment, i.e. a flat dump). Returns a heap-owned handle,
* or NULL on open/mmap failure. Release with vmie_mem_close(). */
vmie_mem* vmie_mem_open(const char* path, uint64_t low);
/* Open `path` with an explicit segment map (`nseg` entries; see gpa_seg). The
* map must be well-formed against the file size (dense, sorted, in-file).
* Returns a heap-owned handle, or NULL on failure. Release with vmie_mem_close(). */
vmie_mem* vmie_mem_open_segs(const char* path, const gpa_seg* segs, int nseg);
/* Unmap, close, and free a handle from vmie_mem_open*. Safe on NULL. */
void vmie_mem_close(vmie_mem* m);
/* ---- flat memory view (single owner) ------------------------------------- *
* A contiguous view of memory.
* data - host pointer to the bytes (borrowed; not owned by the view)
+19 -3
View File
@@ -9,7 +9,7 @@
* 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).
* vmie_scan_pointer(process*)) live in the win32 surface (win32.h).
*/
#ifndef VMIE_SCAN_H
#define VMIE_SCAN_H
@@ -61,7 +61,23 @@ int gva_sig_first(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi,
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);
/* 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. */
/* ---- physical-image signature scan (OS-agnostic engine bridge) ----------- *
* Scan the raw physical image (the core segment map) for a signature, without a
* cr3 or page tables: each seg is one mem_view_t over its file span, fed to the
* pure matcher. This is the dump path - a dump (vmie_mem_open*) supports the
* physical scan only. Keyed by vmie_mem*, like the rest of this header. */
/* Attributed hit from a multi-source scan: which source matched, and where. */
typedef struct { int source; uint64_t gpa; } sig_hit_src;
/* Scan one physical image for `p`. Writes up to `max` GPA hits to `out` (NULL to
* count only) and returns the TOTAL number of hits, or -1 on a bad pattern. */
int sig_scan_mem (vmie_mem* m, const sig_pattern_t* p, uint64_t* out, int max);
/* Scan `nsrc` physical images for `p`, tagging each hit with its source index.
* Writes up to `max` attributed hits to `out` (NULL to count only) and returns
* the TOTAL across all sources, or -1 on a bad pattern. */
int sig_scan_sources(vmie_mem* const* srcs, int nsrc, const sig_pattern_t* p,
sig_hit_src* out, int max);
#endif /* VMIE_SCAN_H */
+6
View File
@@ -36,6 +36,12 @@ bool sig_parse_ida(const char* ida, sig_pattern_t* out);
* false on NULL args or an empty mask. */
bool sig_parse_mask(const uint8_t* bytes, const char* mask, sig_pattern_t* out);
/* Build an exact (no-wildcard) pattern from `len` raw bytes: every byte must
* match. A thin wrapper over sig_parse_mask with an all-'x' mask, so the result
* is released with sig_free() like any other pattern. Returns true on success,
* false on NULL args, a zero length, or OOM. Touches no vmie_mem (pure). */
bool sig_from_bytes(const uint8_t* bytes, size_t len, sig_pattern_t* out);
/* Release a pattern produced by sig_parse_*. Safe on NULL and on an
* already-freed pattern (it is zeroed). */
void sig_free(sig_pattern_t* p);
+20 -20
View File
@@ -1,4 +1,4 @@
/* vmie.h - public Windows-guest surface of the vmi-engine.
/* win32.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
@@ -16,18 +16,18 @@
* - The library never takes ownership of caller buffers and never retains a
* pointer past the call that received it, unless explicitly stated.
*/
#ifndef VMIE_VMIE_H
#define VMIE_VMIE_H
#ifndef VMIE_WIN32_H
#define VMIE_WIN32_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/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;
/* Opaque introspection context. Completed in src/engine/win32/engine-win32.h;
* callers only ever hold a pointer. Created by vmie_win32_open(), populated by
* host_bootstrap(), released by vmie_win32_close(). */
typedef struct vmie_win32 vmie_win32;
/* 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.
@@ -80,26 +80,26 @@ 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 vmie_close(). */
vmie* vmie_open(const char* ram_path, uint64_t low);
* failure. Free with vmie_win32_close(). */
vmie_win32* vmie_win32_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 vmie_close(vmie* v);
void vmie_win32_close(vmie_win32* 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
* and valid until vmie_win32_close(v); do NOT free or retain it past that. NULL on
* NULL `v`. */
vmie_mem* vmie_memory(vmie* v);
vmie_mem* vmie_win32_mem(vmie_win32* 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 vmie_open(). */
int host_bootstrap(vmie* v);
* that failed. Cold path: call once after vmie_win32_open(). */
int host_bootstrap(vmie_win32* v);
/* ---- guest string decode ------------------------------------------------- */
@@ -111,7 +111,7 @@ int host_bootstrap(vmie* v);
* 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(vmie* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size);
size_t gva_read_text(vmie_win32* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size);
/* ---- enumeration --------------------------------------------------------- */
@@ -121,14 +121,14 @@ size_t gva_read_text(vmie* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* d
* 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(vmie* v, int skip_system, process* dst, size_t nmax);
int proc_list(vmie_win32* 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(vmie* v, const process* pr, pmodule* dst, size_t nmax);
int proc_modules(vmie_win32* v, const process* pr, pmodule* dst, size_t nmax);
/* ---- win32 scan wrappers ------------------------------------------------- *
* Convenience entry points over the generic cr3/range scan surface (scan.h).
@@ -137,13 +137,13 @@ int proc_modules(vmie* v, const process* pr, pmodule* dst, size_t nmax);
/* 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,
scan* scan_new(vmie_win32* 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 vmie_scan_pointer(vmie_win32* v, const process* pr, uint64_t target,
int max_depth, uint32_t max_off, scan_ptr_path* out, int max);
#endif /* VMIE_VMIE_H */
#endif /* VMIE_WIN32_H */