Files
vatrog-vm-introspection-engine/src/profile.c
T
lirent 1ec70b7ede Windows guest VMI core: host library, CLI, guest agent
Static library over a flat RW mmap of guest RAM: GPA/GVA paging walks,
beacon-driven bootstrap, dynamic struct-offset profiling, process and
module enumeration, a region map, and value/pointer/signature scanners on
a shared windowed sweep. Public API in include/; internals under src/.

Thin CLI demonstrator over the public API. Guest agent cross-compiled to
Windows x86-64 via mingw-w64. CMake: static library + CLI + guest target,
C17.
2026-06-14 21:47:56 +03:00

279 lines
8.9 KiB
C

#include <stdint.h>
#include <string.h>
#include "../include/include.h"
#include "include/memory.h"
#define pr_(ctx) ((ctx)->prof)
#define RING_CAP 4096 /* USER_MIN/USER_MAX/KERN_MIN come from include/memory.h */
#define SCAN_MAX 1024
#define FT_LO 0x01D0000000000000ll
#define FT_HI 0x01F0000000000000ll
static int canon_ok(uint64_t p, int kernel) {
return kernel ? (p >= KERN_MIN) : (p >= USER_MIN && p <= USER_MAX);
}
/* Circular LIST_ENTRY walker (Flink at node+0); one primitive for both rings. */
static int list_ring_ok(gva_ctx* ctx, uintptr_t cr3, uint64_t head, int kernel) {
uint64_t node;
if (gva_read(ctx, cr3, head, &node, 8)) {
return 0;
}
for (int i = 0; i < RING_CAP; i++) {
if (node == head) {
return i > 0;
}
if (!canon_ok(node, kernel) || gva_read(ctx, cr3, node, &node, 8)) {
return 0;
}
}
return 0;
}
/* Pass 1: ep_name/ep_pid/ep_links/ep_dtb from the System _EPROCESS. */
static int discover_core(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep) {
profile* p = &pr_(ctx);
uint8_t buf[0x800];
if (gva_read(ctx, cr3, sys_ep, buf, sizeof buf)) {
return -1;
}
int name_off = -1;
for (int o = 0x100; o + 7 <= (int)sizeof buf; o++) {
if (!memcmp(buf + o, "System", 6) && buf[o + 6] == 0) {
name_off = o;
break;
}
}
if (name_off < 0) {
return -2;
}
p->ep_name = (uint16_t)name_off;
int pid_off = -1;
for (int o = 0x80; o + 8 <= name_off; o += 8) {
if (*(uint64_t*)(buf + o) != 4) {
continue;
}
const uint16_t links = (uint16_t)(o + 8);
if (list_ring_ok(ctx, cr3, sys_ep + links, 1)) {
p->ep_links = links;
pid_off = o;
break;
}
}
if (pid_off < 0) {
return -3;
}
p->ep_pid = (uint16_t)pid_off;
int dtb_off = -1;
for (int o = 0x18; o <= 0x60; o += 8) {
const uint64_t c = *(uint64_t*)(buf + o) & PFN_MASK;
uint8_t probe;
if (c && khalf_score(ctx, c) >= 16 && !gva_read(ctx, c, sys_ep, &probe, 1)) {
dtb_off = o;
break;
}
}
if (dtb_off < 0) {
return -4;
}
p->ep_dtb = (uint16_t)dtb_off;
return 0;
}
/* Transient snapshot of (eprocess, pid, cr3) over the active ring. */
static int collect_procs(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep, uint64_t* eps, uint32_t* pids, uint64_t* cr3s, int cap) {
const profile* p = &pr_(ctx);
int n = 0;
uint64_t ep = sys_ep, node;
do {
uint64_t pid = 0, dtb = 0;
gva_read(ctx, cr3, ep + p->ep_pid, &pid, 8);
gva_read(ctx, cr3, ep + p->ep_dtb, &dtb, 8);
eps[n] = ep;
pids[n] = (uint32_t)pid;
cr3s[n] = dtb & PFN_MASK;
if (++n >= cap) {
break;
}
if (gva_read(ctx, cr3, ep + p->ep_links, &node, 8)) {
break;
}
ep = node - p->ep_links;
} while (ep != sys_ep);
return n;
}
/* Pass 2a: ep_ppid by population (creator PID). Best-effort. */
static void discover_ppid(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, const uint32_t* pids, int n) {
int best_off = -1, best_hits = 0;
for (int o = 0x100; o <= 0x600; o += 8) {
int hits = 0;
for (int i = 0; i < n; i++) {
uint32_t cand = 0;
if (gva_read(ctx, cr3, eps[i] + o, &cand, 4) || !cand || cand == pids[i]) {
continue;
}
for (int j = 0; j < n; j++) {
if (pids[j] == cand) { hits++; break; }
}
}
if (hits > best_hits) {
best_hits = hits;
best_off = o;
}
}
if (best_off >= 0 && best_hits * 3 >= n) {
pr_(ctx).ep_ppid = (uint16_t)best_off;
}
}
/* Pass 2b: ep_createtime (CreateTime, FILETIME) -- every sample in boot range, System earliest. Best-effort. */
static void discover_createtime(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, int n) {
for (int o = 0x140; o <= 0x600; o += 8) {
int64_t sysv = 0;
int ok = 1;
for (int i = 0; i < n; i++) {
int64_t t = 0;
if (gva_read(ctx, cr3, eps[i] + o, &t, 8) || t < FT_LO || t > FT_HI) { ok = 0; break; }
if (i == 0) {
sysv = t;
} else if (t < sysv) {
ok = 0; break;
}
}
if (ok) {
pr_(ctx).ep_createtime = (uint16_t)o;
return;
}
}
}
/* 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(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
profile* p = &pr_(ctx);
for (int i = 0; i < n; i++) {
if (!cr3s[i]) {
continue;
}
char nm[16] = {0};
if (gva_read(ctx, cr3, eps[i] + p->ep_name, nm, 15)) {
continue;
}
const size_t nl = strnlen(nm, 15);
if (nl == 0 || nl >= 15) {
continue;
}
for (int o = 0x400; o <= 0x600; o += 8) {
uint16_t len = 0;
uint64_t buf = 0;
if (gva_read(ctx, cr3, eps[i] + o, &len, 2) || gva_read(ctx, cr3, eps[i] + o + 8, &buf, 8)) {
continue;
}
if ((len & 1) || len < (uint16_t)(nl * 2) || len > 0x800 || buf < KERN_MIN) {
continue;
}
uint16_t w[16];
if (gva_read(ctx, cr3, buf + len - (uint64_t)nl * 2, w, nl * 2)) {
continue;
}
int match = 1;
for (size_t c = 0; c < nl; c++) {
if ((w[c] < 0x80 ? (char)w[c] : 0) != nm[c]) { match = 0; break; }
}
if (match) {
p->ep_imgpath = (uint16_t)o;
return;
}
}
}
}
/* 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(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
profile* p = &pr_(ctx);
for (int i = 0; i < n; i++) {
const uint64_t pcr3 = cr3s[i];
if (!pcr3) {
continue;
}
for (int po = 0x280; po <= 0x580; po += 8) {
uint64_t peb = 0;
if (gva_read(ctx, cr3, eps[i] + po, &peb, 8) || !canon_ok(peb, 0)) {
continue;
}
for (int lo = 0x10; lo <= 0x30; lo += 8) {
uint64_t ldr = 0;
if (gva_read(ctx, pcr3, peb + lo, &ldr, 8) || !canon_ok(ldr, 0)) {
continue;
}
for (int ll = 0x10; ll <= 0x20; ll += 8) {
if (!list_ring_ok(ctx, pcr3, ldr + ll, 0)) {
continue;
}
uint64_t entry = 0, dllbase = 0, bufp = 0, fbufp = 0;
uint16_t nlen = 0, flen = 0;
if (gva_read(ctx, pcr3, ldr + ll, &entry, 8)) {
continue;
}
if (gva_read(ctx, pcr3, entry + 0x30, &dllbase, 8) ||
!canon_ok(dllbase, 0) || (dllbase & 0xFFF)) {
continue;
}
if (gva_read(ctx, pcr3, entry + 0x58, &nlen, 2) || !nlen || (nlen & 1) ||
gva_read(ctx, pcr3, entry + 0x58 + 8, &bufp, 8) || !canon_ok(bufp, 0)) {
continue;
}
if (gva_read(ctx, pcr3, entry + 0x48, &flen, 2) || (flen & 1) ||
gva_read(ctx, pcr3, entry + 0x48 + 8, &fbufp, 8) || !canon_ok(fbufp, 0)) {
continue;
}
p->ep_peb = (uint16_t)po;
p->peb_ldr = (uint16_t)lo;
p->ldr_loadlist = (uint16_t)ll;
p->lde_base = 0x30;
p->lde_size = 0x40;
p->lde_fullname = 0x48;
p->lde_name = 0x58;
return 0;
}
}
}
}
return -1;
}
__attribute__((cold))
int profile_build(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age) {
memset(&pr_(ctx), 0, sizeof(pr_(ctx)));
memcpy(pr_(ctx).guid, guid, 16);
pr_(ctx).age = age;
if (discover_core(ctx, cr3, sys_ep)) {
return -1;
}
uint64_t eps[SCAN_MAX], cr3s[SCAN_MAX];
uint32_t pids[SCAN_MAX];
const int n = collect_procs(ctx, cr3, sys_ep, eps, pids, cr3s, SCAN_MAX);
if (n <= 1) {
return -2;
}
discover_ppid(ctx, cr3, eps, pids, n);
discover_createtime(ctx, cr3, eps, n);
discover_imgpath(ctx, cr3, eps, cr3s, n);
if (discover_user_chain(ctx, cr3, eps, cr3s, n)) {
return -3;
}
return 0;
}