Files
vatrog-vm-introspection-engine/include/include.h
T
lirent 7c0995a4f2 Rename project w32ms -> vmi-engine
Library vmie (libvmie.a), CLI vmie_cli, guest agent vmie-startup.exe,
symbol prefix VMIE_ (header guards, the LTO build option). No behavior change.
2026-06-15 01:49:16 +03:00

172 lines
8.9 KiB
C

/* include.h - public interface of the Windows VMI core.
*
* 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
* virtual address. Everything is CR3-keyed, never PID-keyed: a `process` already
* carries its own cr3, which is the key to that address space.
*
* Conventions used throughout this header:
* - `cr3` is a raw CR3 / DirectoryTableBase value; low flag bits are masked
* internally, so either the masked PML4 GPA or the raw register works.
* - A "VA" is a 64-bit canonical guest virtual address. A "GPA" is a guest
* physical address. Reads/writes that cross a page boundary are handled
* internally (per-page translation), so callers pass plain ranges.
* - Integer returns: 0 on success, negative on failure, unless stated.
* - The library never takes ownership of caller buffers and never retains a
* pointer past the call that received it, unless explicitly stated.
*/
#ifndef VMIE_INCLUDE_H
#define VMIE_INCLUDE_H
#include <stdint.h>
#include <stddef.h>
/* Opaque introspection context. Completed in src/include/memory.h; callers only
* ever hold a pointer. Created by gva_ctx_alloc(), populated by host_bootstrap(),
* released by gva_ctx_free(). */
typedef struct gva_ctx gva_ctx;
/* 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.
* va - guest VA of the first UTF-16LE code unit (0 if absent)
* len - length in BYTES (not characters); always even for UTF-16 */
typedef struct { uint64_t va; uint32_t len; } gtext;
/* A live process, as produced by proc_list(). Self-contained: `cr3` is all you
* need to read/write its user address space, `eprocess`/`peb` re-anchor it in
* kernel/user space without another lookup.
* cr3 - DirectoryTableBase (PFN-masked); key to this address space
* peb - PEB VA (0 for system/kernel-only processes)
* eprocess - _EPROCESS VA (kernel object, read under the kernel cr3)
* pid, ppid - process / parent ids (ppid == (uint32_t)-1 if unavailable)
* create_time - raw KSYSTEM_TIME / FILETIME (100 ns ticks; 0 if unavailable)
* name - ImageFileName, NUL-terminated ASCII (up to 15 chars)
* path - full image path as a guest UTF-16 string (gtext; may be empty) */
typedef struct {
uint64_t cr3;
uint64_t peb;
uint64_t eprocess;
uint32_t pid;
uint32_t ppid;
uint64_t create_time;
char name[16];
gtext path;
} process;
/* A loaded module (image) inside a process, as produced by proc_modules().
* pr - owning process (its cr3 is the address space these VAs live in)
* entry - _LDR_DATA_TABLE_ENTRY VA
* base - image base VA (page-aligned); pair with `size` for a MODULE scope
* size - image size in bytes (SizeOfImage)
* name - module file name (gtext UTF-16, e.g. "ntdll.dll")
* path - full module path (gtext UTF-16) */
typedef struct {
const process* pr;
uint64_t entry;
uint64_t base;
uint32_t size;
gtext name;
gtext path;
} pmodule;
/* ---- region map ---------------------------------------------------------- *
* A vregion is one run of VA-contiguous, present guest pages sharing the same
* effective protection. It is the unit of "what is mapped, and how" and the
* scoping primitive for the scanners (see scan.h).
*
* x86-64 has no read bit: a present page is readable, so VR_R is always set on a
* returned region. Write/execute/user are the EFFECTIVE rights along the whole
* page-table path (RW & US are AND-ed across levels, NX is OR-ed), not just the
* leaf entry, so they reflect what the guest CPU actually enforces. */
#define VR_R 0x1u /* readable (present => always set) */
#define VR_W 0x2u /* writable (RW bit set at every level) */
#define VR_X 0x4u /* executable(NX clear at every level) */
#define VR_U 0x8u /* user-accessible (US bit set at every level) */
typedef struct {
uint64_t va; /* run start VA (clamped into the requested [lo,hi] window) */
uint64_t len; /* run length in bytes */
uint32_t prot; /* OR of VR_* flags */
} vregion;
/* ---- lifecycle ----------------------------------------------------------- */
/* Open `ram_path` (the guest RAM backing file) and build a context over it.
* ram_path - path to a writable, share=on RAM backing file
* low - size in bytes of below-4G guest RAM (the PCI-hole split point);
* 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 gva_ctx_free(). */
gva_ctx* gva_ctx_alloc(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 gva_ctx_free(gva_ctx* ctx);
/* 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 gva_ctx_alloc(). */
int host_bootstrap(gva_ctx* ctx);
/* ---- guest memory access (hot path) -------------------------------------- */
/* Read `nmemb` bytes from guest VA `va` (translated under `cr3`) into `dst`.
* Crosses page boundaries internally. Returns 0 on success, -1 if any page in
* the range is not present/translatable (in which case `dst` is partially
* written and must be treated as invalid). */
int gva_read(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, void* dst, size_t nmemb);
/* Write `nmemb` bytes from `src` to guest VA `va` (translated under `cr3`).
* The mapping is RW and coherent, so the guest observes the change. Returns 0
* on success, -1 if any page in the range is not present/translatable. */
int gva_write(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, const void* src, size_t nmemb);
/* Read a UTF-16LE guest string and transcode it to UTF-8.
* va - guest VA of the first UTF-16 code unit
* nmemb - number of BYTES to read from the guest (rounded down to even)
* dst - output buffer for NUL-terminated UTF-8 (may be NULL to size only)
* size - capacity of `dst` in bytes
* 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(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size);
/* ---- enumeration --------------------------------------------------------- */
/* Enumerate processes by walking ActiveProcessLinks from System.
* skip_system - if nonzero, omit processes with no PEB (System/kernel-only)
* dst - caller array receiving up to `nmax` `process` records
* 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(gva_ctx* ctx, 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(gva_ctx* ctx, const process* pr, pmodule* dst, size_t nmax);
/* Enumerate mapped memory under `cr3`, clamped to the VA window [lo,hi]
* (inclusive), as runs of equal effective protection.
* cr3 - address space to walk (a process cr3, or the kernel cr3)
* lo, hi - inclusive VA window; MUST lie within a single canonical half
* (entirely user or entirely kernel). Use (0, ~0ull) loosely; the
* walk prunes whole subtrees outside the window.
* prot_any - protection filter: 0 keeps every run; otherwise a run is kept
* only if (run.prot & prot_any) != 0 (e.g. VR_W for writable-only,
* VR_X for executable-only)
* out - caller array receiving up to `nmax` `vregion` records
* nmax - capacity of `out`
* Returns the TOTAL number of matching runs found. If the return value exceeds
* `nmax` the output was truncated (only `nmax` runs were written); enlarge the
* buffer and retry for the full map. */
int gva_regions(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
uint32_t prot_any, vregion* out, int nmax);
#endif /* VMIE_INCLUDE_H */