mirror of
https://dev.lirent.ru/Vatrog/vm-introspection-engine.git
synced 2026-06-18 04:16:39 +03:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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];
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user