mirror of
https://dev.lirent.ru/Vatrog/vm-introspection-engine.git
synced 2026-06-18 02:06:36 +03:00
b3441dd6f6
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.
163 lines
5.1 KiB
C
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;
|
|
}
|