Files
vatrog-vm-introspection-engine/src/cli.c
T
lirent b3441dd6f6 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.
2026-06-15 02:57:46 +03:00

163 lines
5.1 KiB
C

/* cli.c - thin demonstrator over the public vmi-engine API.
*
* 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().
*
* argv[1] path to the guest RAM backing file
* argv[2] `low` - size in bytes of below-4G guest RAM (strtoull, base 0)
* argv[3] optional cap on the process count (default 512)
*/
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include "vmie.h"
#define DEFAULT_NMAX 512
#define MOD_CAP 256
#define RGN_CAP 4096
#define TEXT_CAP 512
static const char* bootstrap_stage(int rc) {
switch (rc) {
case -1: return "beacon not found in guest RAM";
case -2: return "could not recover a bootstrap CR3";
case -3: return "ntoskrnl not located";
case -4: return "PsInitialSystemProcess unresolved";
case -5: return "struct-offset profile build failed";
case -6: return "kernel DirectoryTableBase read failed";
default: return "unknown bootstrap failure";
}
}
static void decode_prot(uint32_t prot, char out[5]) {
out[0] = (prot & VR_R) ? 'R' : '-';
out[1] = (prot & VR_W) ? 'W' : '-';
out[2] = (prot & VR_X) ? 'X' : '-';
out[3] = (prot & VR_U) ? 'U' : '-';
out[4] = 0;
}
static void dump_modules(vmie* ctx, const process* pr) {
pmodule mods[MOD_CAP];
const int nm = proc_modules(ctx, pr, mods, MOD_CAP);
if (nm <= 0) {
printf(" (no modules)\n");
return;
}
for (int i = 0; i < nm; i++) {
char name[TEXT_CAP], path[TEXT_CAP];
/* module strings are user-space: read under the process cr3. */
if (mods[i].name.va) {
gva_read_text(ctx, pr->cr3, mods[i].name.va, mods[i].name.len, name, sizeof name);
} else {
name[0] = 0;
}
if (mods[i].path.va) {
gva_read_text(ctx, pr->cr3, mods[i].path.va, mods[i].path.len, path, sizeof path);
} else {
path[0] = 0;
}
printf(" %016" PRIx64 " %8" PRIu32 " %-24s %s\n",
mods[i].base, mods[i].size,
name[0] ? name : "<unreadable>",
path[0] ? path : "");
}
}
static void dump_regions(vmie* 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 shown = total < 0 ? 0 : (total < RGN_CAP ? total : RGN_CAP);
for (int i = 0; i < shown; i++) {
char prot[5];
decode_prot(rg[i].prot, prot);
printf(" %016" PRIx64 " %12" PRIu64 " %s\n", rg[i].va, rg[i].len, prot);
}
if (total > shown) {
printf(" ... (%d more regions truncated)\n", total - shown);
}
free(rg);
}
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "usage: %s <ram-file> <low> [nmax]\n",
argc > 0 ? argv[0] : "vmie_cli");
return 2;
}
const char* ram_path = argv[1];
const uint64_t low = strtoull(argv[2], NULL, 0);
size_t nmax = DEFAULT_NMAX;
if (argc > 3) {
const unsigned long long v = strtoull(argv[3], NULL, 0);
if (v > 0) {
nmax = (size_t)v;
}
}
vmie* ctx = vmie_open(ram_path, low);
if (!ctx) {
fprintf(stderr, "error: cannot open RAM backing file '%s'\n", ram_path);
return 1;
}
const int rc = host_bootstrap(ctx);
if (rc != 0) {
fprintf(stderr, "error: bootstrap failed (%d): %s\n", rc, bootstrap_stage(rc));
vmie_close(ctx);
return 1;
}
process* procs = malloc(nmax * sizeof *procs);
if (!procs) {
fprintf(stderr, "error: out of memory\n");
vmie_close(ctx);
return 1;
}
const int np = proc_list(ctx, 1, procs, nmax);
if (np < 0) {
fprintf(stderr, "error: proc_list failed (%d)\n", np);
free(procs);
vmie_close(ctx);
return 1;
}
printf("%-6s %-6s %-16s %-18s %-18s %-18s\n",
"PID", "PPID", "NAME", "CR3", "EPROCESS", "PEB");
for (int i = 0; i < np; i++) {
const process* p = &procs[i];
char ppid[12];
if (p->ppid == (uint32_t)-1) {
snprintf(ppid, sizeof ppid, "%s", "?");
} else {
snprintf(ppid, sizeof ppid, "%" PRIu32, p->ppid);
}
printf("%-6" PRIu32 " %-6s %-16s %016" PRIx64 " %016" PRIx64 " %016" PRIx64 "\n",
p->pid, ppid, p->name[0] ? p->name : "<unnamed>",
p->cr3, p->eprocess, p->peb);
}
if (np > 0) {
const process* first = &procs[0];
printf("\nmodules of PID %" PRIu32 " (%s):\n",
first->pid, first->name[0] ? first->name : "<unnamed>");
dump_modules(ctx, first);
printf("\nregions of PID %" PRIu32 " (%s):\n",
first->pid, first->name[0] ? first->name : "<unnamed>");
dump_regions(ctx, first);
}
free(procs);
vmie_close(ctx);
return 0;
}