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
+12 -10
View File
@@ -11,17 +11,19 @@ option(VMIE_LTO "Enable LTO" OFF) # build-only; shipped default is -O2, no
add_library(vmie STATIC
src/core/gpa.c
src/engine/gva.c
src/engine/host.c
src/engine/pe.c
src/engine/proc.c
src/engine/profile.c
src/engine/text.c
src/engine/sigphys.c
src/engine/win32/host.c
src/engine/win32/pe.c
src/engine/win32/proc.c
src/engine/win32/profile.c
src/engine/win32/text.c
src/handlers/scan.c
src/handlers/sigscan.c)
target_include_directories(vmie
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # public API: include/*.h
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/core/include # private: core.h
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include) # private: engine.h, contract.h
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include # private: engine-arch.h, pe.h
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32) # private: engine-win32.h, contract.h
target_compile_options(vmie PRIVATE -O2 -Wall -Wextra)
if(VMIE_LTO)
target_compile_options(vmie PRIVATE -flto)
@@ -39,10 +41,10 @@ set(VMIE_STARTUP ${CMAKE_CURRENT_BINARY_DIR}/vmie-startup.exe)
add_custom_command(
OUTPUT ${VMIE_STARTUP}
COMMAND ${MINGW_CC} -O2 -Wall -Wextra -static -s
-I${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include
-o ${VMIE_STARTUP} ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/guest.c
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/guest.c
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include/contract.h
-I${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32
-o ${VMIE_STARTUP} ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32/guest.c
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32/guest.c
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32/contract.h
COMMENT "Cross-compiling vmie-startup.exe (mingw-w64, x86-64)"
VERBATIM)
add_custom_target(vmie-startup ALL DEPENDS ${VMIE_STARTUP})
+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 */
+11 -11
View File
@@ -2,8 +2,8 @@
*
* Opens a guest RAM backing file, brings up the VMI context, lists processes,
* and for the first user process dumps its loaded modules and mapped regions.
* Public surface only (include/vmie.h); the region walk takes a vmie_mem*,
* borrowed from the engine via vmie_memory().
* Public surface only (include/win32.h); the region walk takes a vmie_mem*,
* borrowed from the engine via vmie_win32_mem().
*
* argv[1] path to the guest RAM backing file
* argv[2] `low` - size in bytes of below-4G guest RAM (strtoull, base 0)
@@ -14,7 +14,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include "vmie.h"
#include "win32.h"
#define DEFAULT_NMAX 512
#define MOD_CAP 256
@@ -41,7 +41,7 @@ static void decode_prot(uint32_t prot, char out[5]) {
out[4] = 0;
}
static void dump_modules(vmie* ctx, const process* pr) {
static void dump_modules(vmie_win32* ctx, const process* pr) {
pmodule mods[MOD_CAP];
const int nm = proc_modules(ctx, pr, mods, MOD_CAP);
if (nm <= 0) {
@@ -68,12 +68,12 @@ static void dump_modules(vmie* ctx, const process* pr) {
}
}
static void dump_regions(vmie* ctx, const process* pr) {
static void dump_regions(vmie_win32* ctx, const process* pr) {
vregion* rg = malloc((size_t)RGN_CAP * sizeof *rg);
if (!rg) {
return;
}
const int total = gva_regions(vmie_memory(ctx), pr->cr3, 0, ~0ull, 0, rg, RGN_CAP);
const int total = gva_regions(vmie_win32_mem(ctx), pr->cr3, 0, ~0ull, 0, rg, RGN_CAP);
const int shown = total < 0 ? 0 : (total < RGN_CAP ? total : RGN_CAP);
for (int i = 0; i < shown; i++) {
char prot[5];
@@ -103,7 +103,7 @@ int main(int argc, char** argv) {
}
}
vmie* ctx = vmie_open(ram_path, low);
vmie_win32* ctx = vmie_win32_open(ram_path, low);
if (!ctx) {
fprintf(stderr, "error: cannot open RAM backing file '%s'\n", ram_path);
return 1;
@@ -112,14 +112,14 @@ int main(int argc, char** argv) {
const int rc = host_bootstrap(ctx);
if (rc != 0) {
fprintf(stderr, "error: bootstrap failed (%d): %s\n", rc, bootstrap_stage(rc));
vmie_close(ctx);
vmie_win32_close(ctx);
return 1;
}
process* procs = malloc(nmax * sizeof *procs);
if (!procs) {
fprintf(stderr, "error: out of memory\n");
vmie_close(ctx);
vmie_win32_close(ctx);
return 1;
}
@@ -127,7 +127,7 @@ int main(int argc, char** argv) {
if (np < 0) {
fprintf(stderr, "error: proc_list failed (%d)\n", np);
free(procs);
vmie_close(ctx);
vmie_win32_close(ctx);
return 1;
}
@@ -157,6 +157,6 @@ int main(int argc, char** argv) {
}
free(procs);
vmie_close(ctx);
vmie_win32_close(ctx);
return 0;
}
+41
View File
@@ -1,5 +1,6 @@
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
@@ -147,3 +148,43 @@ void gpa_close(vmie_mem* m) {
clean_ctx(m);
}
/* ---- public dump source (heap-owned vmie_mem) ---------------------------- *
* Thin wrappers over gpa_open*: heap-allocate a vmie_mem and open into it, so a
* dump (or any flat/segmented RAM image) is a first-class memory source for the
* physical scanners without exposing the win32 engine. */
__attribute__((cold))
vmie_mem* vmie_mem_open(const char* path, uint64_t low) {
vmie_mem* m = calloc(1, sizeof *m);
if (!m) {
return NULL;
}
if (gpa_open(m, path, (uintptr_t)low)) {
free(m);
return NULL;
}
return m;
}
__attribute__((cold))
vmie_mem* vmie_mem_open_segs(const char* path, const gpa_seg* segs, int nseg) {
vmie_mem* m = calloc(1, sizeof *m);
if (!m) {
return NULL;
}
if (gpa_open_segs(m, path, segs, nseg)) {
free(m);
return NULL;
}
return m;
}
__attribute__((cold))
void vmie_mem_close(vmie_mem* m) {
if (!m) {
return;
}
gpa_close(m);
free(m);
}
+5 -8
View File
@@ -2,17 +2,14 @@
#define VMIE_CORE_H
#include <stdint.h>
#include <stddef.h>
#include "memmodel.h" /* gpa_seg (POD, promoted public) + vmie_mem forward decl */
#define VMIE_MAX_SEGS 8
/* One contiguous GPA window backed by a file span: GPA [gpa, gpa+len) maps 1:1
* onto file offset [file_off, file_off+len). The classic single-`low` guest is
* two segs ({0,low,0} below the 4 GiB hole, {4G,fsize-low,low} above it). */
typedef struct gpa_seg {
uint64_t gpa;
uint64_t len;
uint64_t file_off;
} gpa_seg;
/* gpa_seg (the GPA<->file-span descriptor) is defined in memmodel.h, included
* above: one contiguous GPA window mapping 1:1 onto a file span. The classic
* single-`low` guest is two segs ({0,low,0} below the 4 GiB hole, {4G,fsize-low,
* low} above it). */
/* Flat RW mmap of the guest RAM backing file. The GPA<->file-offset map is the
* sorted, dense, in-file segment table seg[0..nseg): each seg is one contiguous
+10 -66
View File
@@ -2,7 +2,7 @@
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "engine.h"
#include "engine-arch.h"
/* PTE permission bits we propagate down the walk. */
#define PTE_RW (1ull << 1)
@@ -45,7 +45,15 @@ static int gva_gpa(vmie_mem* m, uintptr_t cr3, uintptr_t va,
return 0;
}
/* zero-copy borrowed read: leaf-bounded host pointer at `va` (see engine.h). */
/* cold extern translate: GPA of `va` under `cr3`, or -1. Wraps the hot static
* gva_gpa for cold callers outside this TU (win32 bring-up) without exposing the
* inlinable hot primitive. Declared in engine-arch.h. */
__attribute__((cold))
int gva_translate(vmie_mem* m, uintptr_t cr3, uintptr_t va, uintptr_t* gpa) {
return gva_gpa(m, cr3, va, gpa, NULL);
}
/* zero-copy borrowed read: leaf-bounded host pointer at `va` (see memmodel.h). */
__attribute__((hot))
const void* gva_ptr(vmie_mem* m, uintptr_t cr3, uintptr_t va, size_t* avail) {
uintptr_t gpa; size_t leaf;
@@ -91,47 +99,6 @@ int khalf_score(const vmie_mem* m, uint64_t pml4) {
return n;
}
__attribute__((cold))
int cr3_recover(vmie* v, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) {
vmie_mem* m = &v->mem;
int best_score = -1; uint64_t best = 0;
for (size_t off = 0; off + 0x1000 <= m->fsize; off += 0x1000) {
const uintptr_t cand = offset_gpa(m, off);
uintptr_t gpa;
if (gva_gpa(m, cand, va_self, &gpa, NULL)) continue;
if ((gpa & ~0xFFFull) != (target_pa & ~0xFFFull)) continue;
const int score = khalf_score(m, cand);
if (score > best_score) { best_score = score; best = cand; }
}
if (best_score < 0) return -1;
*cr3_out = best;
return 0;
}
/* ---- lifecycle (cold) ---------------------------------------------------- */
__attribute__((cold))
vmie* vmie_open(const char* ram_path, uint64_t low) {
vmie* v = calloc(1, sizeof *v);
if (!v) {
return NULL;
}
if (gpa_open(&v->mem, ram_path, low)) {
free(v);
return NULL;
}
return v;
}
__attribute__((cold))
void vmie_close(vmie* v) {
if (!v) {
return;
}
gpa_close(&v->mem);
free(v);
}
/* ---- region enumeration -------------------------------------------------- */
struct rgn_acc {
@@ -302,26 +269,3 @@ int gva_sweep(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi,
free(rg); free(buf);
return rc;
}
/* ---- physical-image signature bridge ------------------------------------- *
* Iterates the core segment map (each seg is one mem_view_t over its file span)
* and runs the pure matcher. Reaches into vmie_mem, so it lives engine-side. */
struct physcb { uint64_t* out; int max, n; };
static int phys_hit(void* u, uint64_t gpa) {
struct physcb* c = u;
if (c->out && c->n < c->max) c->out[c->n] = gpa;
c->n++;
return 0;
}
int gva_sig_phys(vmie_mem* m, const sig_pattern_t* p, uint64_t* out, int max) {
if (!p || p->len == 0) return -1;
struct physcb c = { out, max, 0 };
for (int i = 0; i < m->nseg; i++) {
const gpa_seg* s = &m->seg[i];
const mem_view_t v = { (const uint8_t*)m->pa + s->file_off, (size_t)s->len, s->gpa };
sig_each(v, p, phys_hit, &c);
}
return c.n;
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef VMIE_ENGINE_ARCH_H
#define VMIE_ENGINE_ARCH_H
#include <stdint.h>
#include <stddef.h>
#include "core.h"
#include "memmodel.h" /* vmie_mem, vregion/VR_*, gva_read/write/ptr/regions/sweep */
/* x86-64 long-mode paging bits, shared by every PT-walking TU. */
#define PFN_MASK (0xFFFFFFFFFFull << 12)
#define PG_P 0x1ull
#define PG_PS 0x80ull
/* sign-extend a 48-bit canonical VA */
#define VA_CANON(v) (((v) & (1ull << 47)) ? ((v) | 0xFFFF000000000000ull) : (v))
/* USER_MIN/USER_MAX/KERN_MIN (the canonical VA-window bounds) live in
* memmodel.h (handler-visible), pulled in above. */
/* gva_ptr is declared in memmodel.h; the engine marks its definition hot. */
/* gva_read/gva_write/gva_regions/gva_sweep + gva_sweep_cb and vregion/VR_*
* are the OS-agnostic contract: declared in memmodel.h, pulled in above. */
/* paging heuristic, shared by the arch walker and the win32 bring-up. Counts
* present kernel-half PML4 entries under `pml4` (an address-space liveness
* score). OS-agnostic: const vmie_mem*, no profile/struct vmie. */
int khalf_score(const vmie_mem* m, uint64_t pml4) __attribute__((cold));
/* cold extern wrapper over the hot static page-table walk: translate `va` under
* `cr3` to a GPA (no leaf length). Returns 0 on success, -1 if not present. For
* cold callers outside gva.c (win32 bring-up); the hot inlinable primitive stays
* private to gva.c. */
int gva_translate(vmie_mem* m, uintptr_t cr3, uintptr_t va, uintptr_t* gpa) __attribute__((cold));
#endif /* VMIE_ENGINE_ARCH_H */
-68
View File
@@ -1,68 +0,0 @@
#ifndef VMIE_ENGINE_H
#define VMIE_ENGINE_H
#include <stdint.h>
#include <stddef.h>
#include "core.h"
#include "memmodel.h" /* vmie_mem, vregion/VR_*, gva_read/write/ptr/regions/sweep */
#include "sigscan.h" /* sig_pattern_t (for gva_sig_phys) */
#include "pe.h" /* PE image parsing + vmie_pe_section (engine-private) */
/* x86-64 long-mode paging bits, shared by every PT-walking TU. */
#define PFN_MASK (0xFFFFFFFFFFull << 12)
#define PG_P 0x1ull
#define PG_PS 0x80ull
/* sign-extend a 48-bit canonical VA */
#define VA_CANON(v) (((v) & (1ull << 47)) ? ((v) | 0xFFFF000000000000ull) : (v))
/* USER_MIN/USER_MAX/KERN_MIN (the canonical VA-window bounds) live in
* memmodel.h (handler-visible), pulled in above. */
typedef struct {
uint8_t guid[16]; /* ntoskrnl CodeView GUID (in-memory byte order) */
uint32_t age; /* CodeView age */
/* _EPROCESS (read under kcr3) */
uint16_t ep_dtb; /* Pcb.DirectoryTableBase (cr3) */
uint16_t ep_pid; /* UniqueProcessId */
uint16_t ep_ppid; /* InheritedFromUniqueProcessId (0=unknown) */
uint16_t ep_links; /* ActiveProcessLinks */
uint16_t ep_name; /* ImageFileName (char[15], ANSI) */
uint16_t ep_peb; /* Peb (0=unknown) */
uint16_t ep_createtime; /* CreateTime (FILETIME, 0=unknown) */
uint16_t ep_imgpath; /* ImageFilePathHint (UNICODE_STRING, 0=unk)*/
/* user-side PEB chain (read under process cr3) */
uint16_t peb_ldr; /* PEB.Ldr */
uint16_t ldr_loadlist; /* PEB_LDR_DATA.InLoadOrderModuleList */
uint16_t lde_base, lde_size, lde_name; /* LDR_DATA_TABLE_ENTRY */
uint16_t lde_fullname; /* LDR_DATA_TABLE_ENTRY.FullDllName */
} profile;
/* sysproc = System _EPROCESS VA: the ActiveProcessLinks ring anchor, captured at
* bootstrap so enumeration needs no export re-resolve. mem is the FIRST member
* so a vmie* aliases a vmie_mem*. prof carried by value. */
typedef struct vmie {
vmie_mem mem;
uint64_t kcr3;
uint64_t kbase;
uint64_t sysproc;
profile prof;
} vmie;
int profile_build(vmie* v, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age);
/* gva_ptr is declared in memmodel.h; the engine marks its definition hot. */
/* bootstrap helpers (gva.c) */
int khalf_score(const vmie_mem* m, uint64_t pml4) __attribute__((cold));
int cr3_recover(vmie* v, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) __attribute__((cold));
/* gva_read/gva_write/gva_regions/gva_sweep + gva_sweep_cb and vregion/VR_*
* are the OS-agnostic contract: declared in memmodel.h, pulled in above. */
/* Scan the raw physical image for a signature, iterating the core segment map
* (each seg is one mem_view_t over its file span). Reaches into vmie_mem, so it
* is an engine bridge, not a handler. Returns the number of GPA hits (writes up
* to `max` to `out`; -1 on a bad pattern). */
int gva_sig_phys(vmie_mem* m, const sig_pattern_t* p, uint64_t* out, int max);
#endif /* VMIE_ENGINE_H */
+62
View File
@@ -0,0 +1,62 @@
/* sigphys.c - physical-image signature bridge (OS-agnostic engine bridge).
*
* Iterates the core segment map (each seg is one mem_view_t over its file span)
* and runs the pure matcher. Reaches into vmie_mem (the segment table), so it is
* an engine bridge, not a handler - but it names no Windows concept and depends
* on no paging/profile, so it lives engine-side, outside src/engine/win32/. The
* dump-only scan set is { core/gpa.c, handlers/sigscan.c, this }, no win32. */
#include <stdint.h>
#include <stddef.h>
#include "core.h"
#include "sigscan.h"
#include "scan.h"
struct physcb { uint64_t* out; int max, n; };
static int phys_hit(void* u, uint64_t gpa) {
struct physcb* c = u;
if (c->out && c->n < c->max) c->out[c->n] = gpa;
c->n++;
return 0;
}
int sig_scan_mem(vmie_mem* m, const sig_pattern_t* p, uint64_t* out, int max) {
if (!p || p->len == 0) return -1;
struct physcb c = { out, max, 0 };
for (int i = 0; i < m->nseg; i++) {
const gpa_seg* s = &m->seg[i];
const mem_view_t v = { (const uint8_t*)m->pa + s->file_off, (size_t)s->len, s->gpa };
sig_each(v, p, phys_hit, &c);
}
return c.n;
}
/* Tagging callback for the multi-source scan: stamps the current source index
* onto each hit and writes attributed records up to capacity. */
struct srccb { sig_hit_src* out; int max, n, source; };
static int src_hit(void* u, uint64_t gpa) {
struct srccb* c = u;
if (c->out && c->n < c->max) {
c->out[c->n].source = c->source;
c->out[c->n].gpa = gpa;
}
c->n++;
return 0;
}
int sig_scan_sources(vmie_mem* const* srcs, int nsrc, const sig_pattern_t* p,
sig_hit_src* out, int max) {
if (!p || p->len == 0) return -1;
struct srccb c = { out, max, 0, 0 };
for (int si = 0; si < nsrc; si++) {
vmie_mem* m = srcs[si];
c.source = si;
for (int i = 0; i < m->nseg; i++) {
const gpa_seg* s = &m->seg[i];
const mem_view_t v = { (const uint8_t*)m->pa + s->file_off, (size_t)s->len, s->gpa };
sig_each(v, p, src_hit, &c);
}
}
return c.n;
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef VMIE_ENGINE_WIN32_H
#define VMIE_ENGINE_WIN32_H
#include <stdint.h>
#include <stddef.h>
#include "engine-arch.h" /* paging macros, khalf_score, gva_* contract */
#include "contract.h" /* guest-agent beacon layout */
#include "pe.h" /* PE image parsing + vmie_pe_section (engine-private) */
typedef struct {
uint8_t guid[16]; /* ntoskrnl CodeView GUID (in-memory byte order) */
uint32_t age; /* CodeView age */
/* _EPROCESS (read under kcr3) */
uint16_t ep_dtb; /* Pcb.DirectoryTableBase (cr3) */
uint16_t ep_pid; /* UniqueProcessId */
uint16_t ep_ppid; /* InheritedFromUniqueProcessId (0=unknown) */
uint16_t ep_links; /* ActiveProcessLinks */
uint16_t ep_name; /* ImageFileName (char[15], ANSI) */
uint16_t ep_peb; /* Peb (0=unknown) */
uint16_t ep_createtime; /* CreateTime (FILETIME, 0=unknown) */
uint16_t ep_imgpath; /* ImageFilePathHint (UNICODE_STRING, 0=unk)*/
/* user-side PEB chain (read under process cr3) */
uint16_t peb_ldr; /* PEB.Ldr */
uint16_t ldr_loadlist; /* PEB_LDR_DATA.InLoadOrderModuleList */
uint16_t lde_base, lde_size, lde_name; /* LDR_DATA_TABLE_ENTRY */
uint16_t lde_fullname; /* LDR_DATA_TABLE_ENTRY.FullDllName */
} profile;
/* sysproc = System _EPROCESS VA: the ActiveProcessLinks ring anchor, captured at
* bootstrap so enumeration needs no export re-resolve. mem is the FIRST member
* so a vmie_win32* aliases a vmie_mem*. prof carried by value. */
typedef struct vmie_win32 {
vmie_mem mem;
uint64_t kcr3;
uint64_t kbase;
uint64_t sysproc;
profile prof;
} vmie_win32;
int profile_build(vmie_win32* v, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age);
/* bootstrap helper (win32 host bring-up) */
int cr3_recover(vmie_win32* v, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) __attribute__((cold));
#endif /* VMIE_ENGINE_WIN32_H */
+51 -5
View File
@@ -1,10 +1,56 @@
#include <string.h>
#include <stddef.h>
#include "vmie.h"
#include "contract.h"
#include "engine.h"
#include <stdlib.h>
#include "win32.h"
#include "engine-win32.h"
#define MZ 0x5A4Du
/* ---- lifecycle (cold) ---------------------------------------------------- */
__attribute__((cold))
vmie_win32* vmie_win32_open(const char* ram_path, uint64_t low) {
vmie_win32* v = calloc(1, sizeof *v);
if (!v) {
return NULL;
}
if (gpa_open(&v->mem, ram_path, low)) {
free(v);
return NULL;
}
return v;
}
__attribute__((cold))
void vmie_win32_close(vmie_win32* v) {
if (!v) {
return;
}
gpa_close(&v->mem);
free(v);
}
/* ---- bootstrap CR3 recovery (cold) --------------------------------------- *
* Scan the image for a candidate PML4 that translates the agent's self-VA to its
* known physical anchor, scored by kernel-half liveness. The page-table walk is
* borrowed from the arch layer via gva_translate (cold), so the hot path stays
* private to gva.c. */
__attribute__((cold))
int cr3_recover(vmie_win32* v, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) {
vmie_mem* m = &v->mem;
int best_score = -1; uint64_t best = 0;
for (size_t off = 0; off + 0x1000 <= m->fsize; off += 0x1000) {
const uintptr_t cand = offset_gpa(m, off);
uintptr_t gpa;
if (gva_translate(m, cand, va_self, &gpa)) continue;
if ((gpa & ~0xFFFull) != (target_pa & ~0xFFFull)) continue;
const int score = khalf_score(m, cand);
if (score > best_score) { best_score = score; best = cand; }
}
if (best_score < 0) return -1;
*cr3_out = best;
return 0;
}
#define DIR_EXPORT 0u
#define DIR_DEBUG 6u
#define DBG_CODEVIEW 2u
@@ -162,12 +208,12 @@ static void beacon_ack(vmie_mem* m, uint64_t anchor_pa) {
gpa_write(m, anchor_pa + offsetof(contract, ack), &ack, 8);
}
vmie_mem* vmie_memory(vmie* v) {
vmie_mem* vmie_win32_mem(vmie_win32* v) {
return v ? &v->mem : NULL;
}
__attribute__((cold))
int host_bootstrap(vmie* v) {
int host_bootstrap(vmie_win32* v) {
vmie_mem* m = &v->mem;
uint64_t anchor_pa, va_self;
uintptr_t cr3boot;
@@ -2,15 +2,15 @@
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "engine.h"
#include "vmie.h"
#include "engine-win32.h"
#include "win32.h"
#define pr_(v) ((v)->prof)
#define RING_GUARD 100000u
#define MOD_GUARD 4096u
static void grab_ustr(vmie* v, uintptr_t cr3, uint64_t va, gtext* out) {
static void grab_ustr(vmie_win32* v, uintptr_t cr3, uint64_t va, gtext* out) {
vmie_mem* m = &v->mem;
uint16_t len = 0;
uint64_t buf = 0;
@@ -23,7 +23,7 @@ static void grab_ustr(vmie* v, uintptr_t cr3, uint64_t va, gtext* out) {
out->len = len;
}
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) {
vmie_mem* m = &v->mem;
const profile* p = &pr_(v);
const uint64_t kcr3 = v->kcr3;
@@ -74,7 +74,7 @@ int proc_list(vmie* v, int skip_system, process* dst, size_t nmax) {
return (int)n;
}
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) {
vmie_mem* m = &v->mem;
const profile* p = &pr_(v);
const uint64_t cr3 = pr->cr3;
@@ -119,7 +119,7 @@ int proc_modules(vmie* v, const process* pr, pmodule* dst, size_t nmax) {
* Project a Windows process/module list onto the generic cr3/range surface and
* delegate to the OS-agnostic scanners (scan.h). */
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) {
if (!pr) {
return NULL;
@@ -129,7 +129,7 @@ scan* scan_new(vmie* v, const process* pr, scan_type t, const void* value,
#define PTR_MOD_CAP 1024u
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) {
if (!pr) {
return -1;
@@ -1,10 +1,10 @@
#include <stdint.h>
#include <string.h>
#include "engine.h"
#include "engine-win32.h"
#define pr_(v) ((v)->prof)
#define RING_CAP 4096 /* USER_MIN/USER_MAX/KERN_MIN come from engine.h */
#define RING_CAP 4096 /* USER_MIN/USER_MAX/KERN_MIN come from engine-arch.h */
#define SCAN_MAX 1024
#define FT_LO 0x01D0000000000000ll
#define FT_HI 0x01F0000000000000ll
@@ -14,7 +14,7 @@ static int canon_ok(uint64_t p, int kernel) {
}
/* Circular LIST_ENTRY walker (Flink at node+0); one primitive for both rings. */
static int list_ring_ok(vmie* v, uintptr_t cr3, uint64_t head, int kernel) {
static int list_ring_ok(vmie_win32* v, uintptr_t cr3, uint64_t head, int kernel) {
vmie_mem* m = &v->mem;
uint64_t node;
if (gva_read(m, cr3, head, &node, 8)) {
@@ -32,7 +32,7 @@ static int list_ring_ok(vmie* v, uintptr_t cr3, uint64_t head, int kernel) {
}
/* Pass 1: ep_name/ep_pid/ep_links/ep_dtb from the System _EPROCESS. */
static int discover_core(vmie* v, uintptr_t cr3, uint64_t sys_ep) {
static int discover_core(vmie_win32* v, uintptr_t cr3, uint64_t sys_ep) {
vmie_mem* m = &v->mem;
profile* p = &pr_(v);
uint8_t buf[0x800];
@@ -88,7 +88,7 @@ static int discover_core(vmie* v, uintptr_t cr3, uint64_t sys_ep) {
}
/* Transient snapshot of (eprocess, pid, cr3) over the active ring. */
static int collect_procs(vmie* v, uintptr_t cr3, uint64_t sys_ep, uint64_t* eps, uint32_t* pids, uint64_t* cr3s, int cap) {
static int collect_procs(vmie_win32* v, uintptr_t cr3, uint64_t sys_ep, uint64_t* eps, uint32_t* pids, uint64_t* cr3s, int cap) {
vmie_mem* m = &v->mem;
const profile* p = &pr_(v);
int n = 0;
@@ -112,7 +112,7 @@ static int collect_procs(vmie* v, uintptr_t cr3, uint64_t sys_ep, uint64_t* eps,
}
/* Pass 2a: ep_ppid by population (creator PID). Best-effort. */
static void discover_ppid(vmie* v, uintptr_t cr3, const uint64_t* eps, const uint32_t* pids, int n) {
static void discover_ppid(vmie_win32* v, uintptr_t cr3, const uint64_t* eps, const uint32_t* pids, int n) {
vmie_mem* m = &v->mem;
int best_off = -1, best_hits = 0;
for (int o = 0x100; o <= 0x600; o += 8) {
@@ -137,7 +137,7 @@ static void discover_ppid(vmie* v, uintptr_t cr3, const uint64_t* eps, const uin
}
/* Pass 2b: ep_createtime (CreateTime, FILETIME) -- every sample in boot range, System earliest. Best-effort. */
static void discover_createtime(vmie* v, uintptr_t cr3, const uint64_t* eps, int n) {
static void discover_createtime(vmie_win32* v, uintptr_t cr3, const uint64_t* eps, int n) {
vmie_mem* m = &v->mem;
for (int o = 0x140; o <= 0x600; o += 8) {
int64_t sysv = 0;
@@ -160,7 +160,7 @@ static void discover_createtime(vmie* v, uintptr_t cr3, const uint64_t* eps, int
/* Pass 2c: ep_imgpath (ImageFilePathHint) -- UNICODE_STRING whose tail equals the
* process's untruncated ImageFileName; probe short-named (<15) procs only. Best-effort. */
static void discover_imgpath(vmie* v, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
static void discover_imgpath(vmie_win32* v, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
vmie_mem* m = &v->mem;
profile* p = &pr_(v);
for (int i = 0; i < n; i++) {
@@ -202,7 +202,7 @@ static void discover_imgpath(vmie* v, uintptr_t cr3, const uint64_t* eps, const
/* Pass 2d: ep_peb + user PEB/Ldr chain; commits the x64-invariant LDR offsets
* (incl. FullDllName) after validating them on the live first entry. */
static int discover_user_chain(vmie* v, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
static int discover_user_chain(vmie_win32* v, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
vmie_mem* m = &v->mem;
profile* p = &pr_(v);
@@ -260,7 +260,7 @@ static int discover_user_chain(vmie* v, uintptr_t cr3, const uint64_t* eps, cons
}
__attribute__((cold))
int profile_build(vmie* v, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age) {
int profile_build(vmie_win32* v, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age) {
memset(&pr_(v), 0, sizeof(pr_(v)));
memcpy(pr_(v).guid, guid, 16);
pr_(v).age = age;
@@ -1,7 +1,7 @@
#include <stdint.h>
#include <stddef.h>
#include "engine.h"
#include "vmie.h"
#include "engine-win32.h"
#include "win32.h"
static void utf8_emit(uint32_t cp, char* dst, size_t size, size_t* need, size_t* wrote) {
uint8_t b[4]; size_t k;
@@ -16,7 +16,7 @@ static void utf8_emit(uint32_t cp, char* dst, size_t size, size_t* need, size_t*
*need += k;
}
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) {
vmie_mem* m = &v->mem;
size_t need = 0, wrote = 0;
uint16_t stage[256];
+13
View File
@@ -66,6 +66,19 @@ bool sig_parse_mask(const uint8_t* b, const char* m, sig_pattern_t* out) {
return true;
}
bool sig_from_bytes(const uint8_t* bytes, size_t len, sig_pattern_t* out) {
if (!bytes || !out || len == 0) return false;
char* mask = malloc(len + 1); /* all-'x' (exact, no wildcard) */
if (!mask) return false;
memset(mask, 'x', len);
mask[len] = 0;
const bool ok = sig_parse_mask(bytes, mask, out);
free(mask);
return ok;
}
void sig_free(sig_pattern_t* p) {
if (!p) return;
free(p->bytes); free(p->mask);