From b3441dd6f6b04786f9296d342edd5b7d9b4cf537 Mon Sep 17 00:00:00 2001 From: Gregory Lirent Date: Mon, 15 Jun 2026 02:57:46 +0300 Subject: [PATCH] Split the library into CORE / ENGINE / HANDLERS layers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- CMakeLists.txt | 32 +++--- include/memmodel.h | 156 ++++++++++++++++++++++++++ include/scan.h | 36 +++--- include/sigscan.h | 37 +------ include/{include.h => vmie.h} | 115 ++++++++----------- src/cli.c | 21 ++-- src/core/gpa.c | 149 +++++++++++++++++++++++++ src/core/include/core.h | 57 ++++++++++ src/{ => engine}/guest.c | 2 +- src/{ => engine}/gva.c | 109 +++++++++++------- src/{ => engine}/host.c | 95 ++++++++-------- src/{ => engine}/include/contract.h | 0 src/engine/include/engine.h | 68 ++++++++++++ src/engine/include/pe.h | 46 ++++++++ src/engine/pe.c | 63 +++++++++++ src/engine/proc.c | 166 ++++++++++++++++++++++++++++ src/{ => engine}/profile.c | 112 ++++++++++--------- src/{ => engine}/text.c | 9 +- src/gpa.c | 108 ------------------ src/{ => handlers}/scan.c | 111 +++++-------------- src/{ => handlers}/sigscan.c | 47 +------- src/include/memory.h | 101 ----------------- src/include/profile.h | 28 ----- src/proc.c | 112 ------------------- 24 files changed, 1014 insertions(+), 766 deletions(-) create mode 100644 include/memmodel.h rename include/{include.h => vmie.h} (55%) create mode 100644 src/core/gpa.c create mode 100644 src/core/include/core.h rename src/{ => engine}/guest.c (96%) rename src/{ => engine}/gva.c (75%) rename src/{ => engine}/host.c (52%) rename src/{ => engine}/include/contract.h (100%) create mode 100644 src/engine/include/engine.h create mode 100644 src/engine/include/pe.h create mode 100644 src/engine/pe.c create mode 100644 src/engine/proc.c rename src/{ => engine}/profile.c (63%) rename src/{ => engine}/text.c (89%) delete mode 100644 src/gpa.c rename src/{ => handlers}/scan.c (76%) rename src/{ => handlers}/sigscan.c (72%) delete mode 100644 src/include/memory.h delete mode 100644 src/include/profile.h delete mode 100644 src/proc.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 40e7fdc..df56684 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,17 +9,19 @@ option(VMIE_LTO "Enable LTO" OFF) # build-only; shipped default is -O2, no # ---- host: VMI core as a static library --------------------------------- add_library(vmie STATIC - src/gpa.c - src/gva.c - src/host.c - src/proc.c - src/profile.c - src/text.c - src/scan.c - src/sigscan.c) + src/core/gpa.c + src/engine/gva.c + src/engine/host.c + src/engine/pe.c + src/engine/proc.c + src/engine/profile.c + src/engine/text.c + src/handlers/scan.c + src/handlers/sigscan.c) target_include_directories(vmie - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # public API: include/*.h - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) # private: src/include/*.h via "include/..." + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # public API: include/*.h + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/core/include # private: core.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include) # private: engine.h, contract.h target_compile_options(vmie PRIVATE -O2 -Wall -Wextra) if(VMIE_LTO) target_compile_options(vmie PRIVATE -flto) @@ -28,7 +30,7 @@ endif() # ---- host: CLI demonstrator over the library ---------------------------- add_executable(vmie_cli src/cli.c) -target_link_libraries(vmie_cli PRIVATE vmie) +target_link_libraries(vmie_cli PRIVATE vmie) # public include/ comes via vmie (PUBLIC) target_compile_options(vmie_cli PRIVATE -Wall -Wextra) # ---- guest: cross-compile to Windows x86-64 via mingw-w64 --------------- @@ -37,10 +39,10 @@ set(VMIE_STARTUP ${CMAKE_CURRENT_BINARY_DIR}/vmie-startup.exe) add_custom_command( OUTPUT ${VMIE_STARTUP} COMMAND ${MINGW_CC} -O2 -Wall -Wextra -static -s - -I${CMAKE_CURRENT_SOURCE_DIR}/src - -o ${VMIE_STARTUP} ${CMAKE_CURRENT_SOURCE_DIR}/src/guest.c - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/guest.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/include/contract.h + -I${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include + -o ${VMIE_STARTUP} ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/guest.c + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/guest.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include/contract.h COMMENT "Cross-compiling vmie-startup.exe (mingw-w64, x86-64)" VERBATIM) add_custom_target(vmie-startup ALL DEPENDS ${VMIE_STARTUP}) diff --git a/include/memmodel.h b/include/memmodel.h new file mode 100644 index 0000000..7a2d142 --- /dev/null +++ b/include/memmodel.h @@ -0,0 +1,156 @@ +/* memmodel.h - the OS-agnostic memory-model contract (the middle layer). + * + * This is the shared vocabulary between the ENGINE (which turns guest-physical + * RAM into a usable virtual memory model via x86-64 paging + Windows bring-up) + * and the HANDLERS (scanners that consume that model). It names no Windows + * concept: a handler compiled against this header literally cannot mention an + * _EPROCESS, a PEB, or an LDR entry. + * + * Everything here is keyed by a `vmie_mem*` (the opaque physical/paging + * substrate) plus a `cr3` (the address space). The engine handle `vmie` is + * never handed to a handler - only `vmie_mem*` + `cr3`. + * + * Conventions: + * - `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. Reads/writes that + * cross a page boundary are handled internally (per-page translation). + * - Integer returns: 0 on success, negative on failure, unless stated. + */ +#ifndef VMIE_MEMMODEL_H +#define VMIE_MEMMODEL_H +#include +#include + +/* Opaque guest-physical memory handle (the mmap'd RAM backing file + segment + * map). Defined in src/core/include/core.h; handlers hold only a pointer and + * pass it, with a cr3, to the address-space primitives below. */ +typedef struct vmie_mem vmie_mem; + +/* ---- flat memory view (single owner) ------------------------------------- * + * A contiguous view of memory. + * data - host pointer to the bytes (borrowed; not owned by the view) + * size - number of valid bytes at `data` + * base_va - address that data[0] corresponds to (guest VA, or GPA for a + * physical view). All matches are reported as base_va + offset. */ +typedef struct { + const uint8_t* data; + size_t size; + uint64_t base_va; +} mem_view_t; + +/* ---- 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. */ +#ifndef VMIE_VREGION_DEFINED +#define VMIE_VREGION_DEFINED +#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; +#endif + +/* Canonical VA-window bounds of the memory model, shared by every scanning TU. + * These describe the address space the contract operates over (the [lo,hi] + * windows of gva_regions/gva_sweep), so they are handler-visible. + * USER_MIN is 0x10000: the low 64 KiB is reserved, so no live user pointer + * targets below it - starting there drops a class of false positives. */ +#define USER_MIN 0x0000000000010000ull +#define USER_MAX 0x00007FFFFFFFFFFFull +#define KERN_MIN 0xFFFF800000000000ull + +/* ---- generic boundary types (replace the Windows-typed process/pmodule) --- * + * A schedulable address space, decoded by the engine from whatever the guest + * OS calls one. `cr3` is all a handler needs to read/write its memory. + * cr3 - DirectoryTableBase (PFN-masked); key to this address space + * pid, ppid - process / parent ids (ppid == (uint64_t)-1 if unavailable) + * name - short image name, NUL-terminated UTF-8 (engine-decoded) */ +typedef struct { + uint64_t cr3; + uint64_t pid; + uint64_t ppid; + char name[16]; +} task; + +/* A named, contiguous VA range (e.g. a loaded module image), the anchor a + * pointer scan walks back to. The engine decodes the name; no LDR entry VA. + * base - range base VA (page-aligned) + * size - range length in bytes + * name - decoded UTF-8 name (e.g. "ntdll.dll"), NUL-terminated */ +typedef struct { + uint64_t base; + uint64_t size; + char name[64]; +} range; + +/* ---- 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(vmie_mem* m, 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(vmie_mem* m, uintptr_t cr3, uintptr_t va, const void* src, size_t nmemb); + +/* Zero-copy borrowed read: host pointer to the guest byte at `va` (under `cr3`), + * valid for *avail contiguous bytes (to the end of the containing leaf). NULL if + * `va` is not mapped or the leaf is not fully covered by the image (caller falls + * back to gva_read). Borrowed: valid until the mapping is closed, do NOT retain. */ +const void* gva_ptr(vmie_mem* m, uintptr_t cr3, uintptr_t va, size_t* avail); + +/* Enumerate mapped memory under `cr3`, clamped to the VA window [lo,hi] + * (inclusive), as runs of equal effective protection. + * 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) + * 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; enlarge the buffer and retry. */ +int gva_regions(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi, + uint32_t prot_any, vregion* out, int nmax); + +/* ---- shared windowed sweep engine ---------------------------------------- * + * gva_sweep() streams every mapped byte under `cr3` within [lo,hi] that passes + * the protection filter to `cb`, one contiguous window at a time. Physical + * fragmentation is hidden: each window is a flat buffer (gva_read-filled), and + * adjacent windows of one run share `overlap` leading bytes so an object or + * pattern straddling a window boundary is still seen whole. */ +typedef int (*gva_sweep_cb)(void* user, const uint8_t* data, size_t len, + uint64_t base_va, size_t overlap, int last); +/* user - passed through verbatim + * data - host buffer with `len` valid bytes (do not retain past the call) + * len - valid bytes at data + * base_va - guest VA of data[0] + * overlap - bytes at the front of `data` shared with the previous window of + * this run (0 on a run's first window or right after a gap) + * last - nonzero if this window ends a contiguous segment (run end / gap): + * accept hits up to `len`; otherwise drop hits starting in the + * trailing `overlap` zone, the next window re-presents them + * cb returns nonzero to abort the sweep early (e.g. result buffer full). + * + * gva_sweep() returns 0 normally, 1 if a callback aborted it, -1 on allocation + * failure. `overlap` must be < the internal window (1 MiB); patterns longer + * than that are not supported by the windowed path. */ +int gva_sweep(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi, + uint32_t prot_any, size_t overlap, gva_sweep_cb cb, void* user); + +#endif /* VMIE_MEMMODEL_H */ diff --git a/include/scan.h b/include/scan.h index 0d99167..68ae0bc 100644 --- a/include/scan.h +++ b/include/scan.h @@ -1,16 +1,21 @@ /* scan.h - typed value scanner, pointer scanner, and gva<->signature bridges. * - * Layered above the pure matcher (sigscan.h) and the gva core (include.h): this - * is the gva-bound scanning surface. The value scanner narrows a candidate set - * across successive snapshots; the pointer scanner discovers module-anchored + * Layered above the pure matcher (sigscan.h) and the generic memory-model + * contract (memmodel.h): this is the OS-agnostic scanning surface. Everything + * here is keyed by a `vmie_mem*` + `cr3` (and, for the pointer scan, a decoded + * `range[]`); it names no Windows object. The value scanner narrows a candidate + * set across successive snapshots; the pointer scanner discovers range-anchored * pointer chains; the gva_sig_* bridges build mem_view_t windows out of guest * memory and feed them to the signature matcher. + * + * The Windows-typed convenience entry points (scan_new(process*), + * vmie_scan_pointer(process*)) live in the win32 surface (vmie.h). */ #ifndef VMIE_SCAN_H #define VMIE_SCAN_H #include #include -#include "include.h" /* gva_ctx, process (vregion - internal) */ +#include "memmodel.h" /* vmie_mem, range, vregion */ #include "sigscan.h" /* mem_view_t, sig_pattern_t */ /* typed value scanner. ENUMERATOR ORDER IS LOAD-BEARING: scan.c indexes the @@ -32,32 +37,31 @@ typedef struct { uint64_t addr; uint64_t value; } scan_hit; #define SCAN_PTR_MAXDEPTH 8 /* DFS depth and size of off[] */ typedef struct { - uint64_t base; /* module-anchored base address */ + uint64_t base; /* range-anchored base address */ int depth; /* number of offsets in off[] */ int32_t off[SCAN_PTR_MAXDEPTH]; /* dereference chain */ } scan_ptr_path; -scan* scan_new(gva_ctx* ctx, const process* pr, scan_type t, const void* value, - int be, int aligned, uint64_t lo, uint64_t hi); -scan* scan_new_cr3(gva_ctx* ctx, uintptr_t cr3, scan_type t, const void* value, +scan* scan_new_cr3(vmie_mem* m, uintptr_t cr3, scan_type t, const void* value, int be, int aligned, uint64_t lo, uint64_t hi); int64_t scan_next(scan* s, scan_op op, const void* value); int64_t scan_count(scan* s); int scan_results(scan* s, uint64_t offset, int max, scan_hit* out); void scan_free(scan* s); -int scan_pointer(gva_ctx* ctx, const process* pr, uint64_t target, - int max_depth, uint32_t max_off, scan_ptr_path* out, int max); +int scan_pointer(vmie_mem* m, uintptr_t cr3, const range* mods, int nmods, + uint64_t target, int max_depth, uint32_t max_off, + scan_ptr_path* out, int max); /* gva bridges to the signature matcher: build mem_view from guest memory and feed sigscan.h */ -int gva_sig_scan (gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, +int gva_sig_scan (vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi, uint32_t prot_any, const sig_pattern_t* p, uint64_t* out, int max); -int gva_sig_first(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, +int gva_sig_first(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi, uint32_t prot_any, const sig_pattern_t* p, uint64_t* va); -int gva_sig_rip (gva_ctx* ctx, uintptr_t cr3, uint64_t hit_va, +int gva_sig_rip (vmie_mem* m, uintptr_t cr3, uint64_t hit_va, size_t disp_off, size_t instr_len, uint64_t* target); -int gva_pe_section(gva_ctx* ctx, uintptr_t cr3, uint64_t module_base, - const char* name, uint8_t* buf, size_t bufcap, mem_view_t* out); -int gva_sig_phys (gva_ctx* ctx, const sig_pattern_t* p, uint64_t* out, int max); + +/* gva_sig_phys (scan the raw physical image) needs the core segment map, so it + * is an engine bridge, declared in engine.h - not part of the handler surface. */ #endif /* VMIE_SCAN_H */ diff --git a/include/sigscan.h b/include/sigscan.h index a27df7d..575ca99 100644 --- a/include/sigscan.h +++ b/include/sigscan.h @@ -6,26 +6,16 @@ * results are reported as addresses in the view's own coordinate space * (base_va + offset): a guest VA for a virtual view, a GPA for a physical view. * - * This module is pure: it never touches a gva_ctx and performs no I/O. To scan - * guest memory, build views from the gva layer (see scan.h: gva_sig_scan, - * gva_pe_section, gva_sig_phys) and feed them here. + * This module is pure: it never touches a vmie_mem and performs no I/O. To scan + * guest memory, build views from the gva layer (see scan.h: gva_sig_scan) and + * feed them here. */ #ifndef VMIE_SIGSCAN_H #define VMIE_SIGSCAN_H #include #include #include - -/* A contiguous view of memory. - * data - host pointer to the bytes (borrowed; not owned by the view) - * size - number of valid bytes at `data` - * base_va - address that data[0] corresponds to (guest VA, or GPA for a - * physical view). All matches are reported as base_va + offset. */ -typedef struct { - const uint8_t* data; - size_t size; - uint64_t base_va; -} mem_view_t; +#include "memmodel.h" /* mem_view_t (the single owner of the view type) */ /* A parsed byte pattern. mask[i] == 1 means bytes[i] must match; 0 = wildcard. * Owns two heap allocations of `len` bytes each; release with sig_free(). */ @@ -83,23 +73,4 @@ uint64_t sig_rip(mem_view_t v, uint64_t hit_va, size_t disp_off, size_t instr_le * is actually available. Useful for narrowing a scan to a [start,end] window. */ mem_view_t mem_sub(mem_view_t v, uint64_t start_va, size_t size); -/* Locate a PE section by name within a view that contains at least the image - * headers at `module_base` (the first page is enough). - * module_base - image base VA, must be >= v.base_va and inside `v` - * name - section name, e.g. ".text" (compared up to 8 bytes) - * rva_out - receives the section RVA (relative to module_base); may be NULL - * vsize_out - receives the section virtual size; may be NULL - * Returns true if found. Only the headers need to be present in `v`; the section - * body does not. */ -bool pe_find_section(mem_view_t v, uint64_t module_base, const char* name, - uint64_t* rva_out, uint32_t* vsize_out); - -/* Locate a PE section AND return a sub-view spanning it. Requires the whole - * section body to be present in `v` (true for an in-memory image dump). Prefer - * scanning ".text" over a whole image: faster, and avoids false hits in data. - * Returns true and fills *out on success. For guest memory, where the body is - * usually not co-resident with the headers, use gva_pe_section (scan.h). */ -bool pe_section(mem_view_t v, uint64_t module_base, const char* name, - mem_view_t* out); - #endif /* VMIE_SIGSCAN_H */ \ No newline at end of file diff --git a/include/include.h b/include/vmie.h similarity index 55% rename from include/include.h rename to include/vmie.h index 2e49510..4c6617e 100644 --- a/include/include.h +++ b/include/vmie.h @@ -1,30 +1,33 @@ -/* include.h - public interface of the Windows VMI core. +/* vmie.h - public Windows-guest surface of the vmi-engine. * * 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: + * This header is the Windows-typed surface (process/pmodule/gtext, bring-up, + * enumeration, the win32 scan wrappers). The OS-agnostic memory-model contract + * lives in memmodel.h (pulled in below); the scanners in scan.h/sigscan.h. + * + * Conventions: * - `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 +#ifndef VMIE_VMIE_H +#define VMIE_VMIE_H #include #include +#include "memmodel.h" /* vmie_mem, vregion/VR_*, task/range, gva_read/write/ptr/regions/sweep */ +#include "sigscan.h" /* mem_view_t, sig_pattern_t */ +#include "scan.h" /* scan_type, scan_ptr_path, generic scan surface */ -/* 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; +/* Opaque introspection context. Completed in src/engine/include/engine.h; + * callers only ever hold a pointer. Created by vmie_open(), populated by + * host_bootstrap(), released by vmie_close(). */ +typedef struct vmie vmie; /* 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. @@ -69,26 +72,6 @@ typedef struct { 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. @@ -97,33 +80,28 @@ typedef struct { * 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); + * failure. Free with vmie_close(). */ +vmie* vmie_open(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); +void vmie_close(vmie* v); + +/* Borrow the engine's guest-memory handle for the generic address-space + * primitives (gva_read/gva_regions/...). The returned pointer is owned by `v` + * and valid until vmie_close(v); do NOT free or retain it past that. NULL on + * NULL `v`. */ +vmie_mem* vmie_memory(vmie* v); /* 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); + * that failed. Cold path: call once after vmie_open(). */ +int host_bootstrap(vmie* v); -/* ---- 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); +/* ---- guest string decode ------------------------------------------------- */ /* Read a UTF-16LE guest string and transcode it to UTF-8. * va - guest VA of the first UTF-16 code unit @@ -133,7 +111,7 @@ int gva_write(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, const void* src, size_t * 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); +size_t gva_read_text(vmie* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size); /* ---- enumeration --------------------------------------------------------- */ @@ -143,30 +121,29 @@ size_t gva_read_text(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t nmemb, ch * 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); +int proc_list(vmie* v, 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); +int proc_modules(vmie* v, 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); +/* ---- win32 scan wrappers ------------------------------------------------- * + * Convenience entry points over the generic cr3/range scan surface (scan.h). + * They project a Windows `process` to its cr3, and its `pmodule[]` to a decoded + * `range[]` (UTF-8 names), then delegate to scan_new_cr3 / scan_pointer. */ -#endif /* VMIE_INCLUDE_H */ \ No newline at end of file +/* Open a value-scan session over the user address space of `pr`. Equivalent to + * scan_new_cr3(&v->mem, pr->cr3, ...). Returns NULL on NULL pr or OOM. */ +scan* scan_new(vmie* v, const process* pr, scan_type t, const void* value, + int be, int aligned, uint64_t lo, uint64_t hi); + +/* Pointer scan over `pr`'s user space, anchored on its loaded modules. Resolves + * `pr`'s module list to range[] (names engine-decoded) and delegates to + * scan_pointer. Returns the number of paths found, or negative on failure. */ +int vmie_scan_pointer(vmie* v, const process* pr, uint64_t target, + int max_depth, uint32_t max_off, scan_ptr_path* out, int max); + +#endif /* VMIE_VMIE_H */ diff --git a/src/cli.c b/src/cli.c index e103e4e..df8307b 100644 --- a/src/cli.c +++ b/src/cli.c @@ -2,7 +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/include.h); never reaches into src/include. + * 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) @@ -13,7 +14,7 @@ #include #include #include -#include "include.h" +#include "vmie.h" #define DEFAULT_NMAX 512 #define MOD_CAP 256 @@ -40,7 +41,7 @@ static void decode_prot(uint32_t prot, char out[5]) { out[4] = 0; } -static void dump_modules(gva_ctx* ctx, const process* pr) { +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) { @@ -67,12 +68,12 @@ static void dump_modules(gva_ctx* ctx, const process* pr) { } } -static void dump_regions(gva_ctx* ctx, const process* pr) { +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(ctx, pr->cr3, 0, ~0ull, 0, rg, RGN_CAP); + 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]; @@ -102,7 +103,7 @@ int main(int argc, char** argv) { } } - gva_ctx* ctx = gva_ctx_alloc(ram_path, low); + vmie* ctx = vmie_open(ram_path, low); if (!ctx) { fprintf(stderr, "error: cannot open RAM backing file '%s'\n", ram_path); return 1; @@ -111,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)); - gva_ctx_free(ctx); + vmie_close(ctx); return 1; } process* procs = malloc(nmax * sizeof *procs); if (!procs) { fprintf(stderr, "error: out of memory\n"); - gva_ctx_free(ctx); + vmie_close(ctx); return 1; } @@ -126,7 +127,7 @@ int main(int argc, char** argv) { if (np < 0) { fprintf(stderr, "error: proc_list failed (%d)\n", np); free(procs); - gva_ctx_free(ctx); + vmie_close(ctx); return 1; } @@ -156,6 +157,6 @@ int main(int argc, char** argv) { } free(procs); - gva_ctx_free(ctx); + vmie_close(ctx); return 0; } diff --git a/src/core/gpa.c b/src/core/gpa.c new file mode 100644 index 0000000..849f129 --- /dev/null +++ b/src/core/gpa.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include +#include +#include +#include "core.h" + +#define RAM_H (1ul<<32) +#define PROT_RW (PROT_READ | PROT_WRITE) + +static void clean_ctx(vmie_mem* m) { + memset(m, 0, sizeof(vmie_mem)); + m->fd = -1; +} + +/* Resolve GPA `g` over the segment map: on success returns the seg covering it + * and the file offset of `g`; NULL if `g` falls outside every seg (a hole). The + * `g - s->gpa < s->len` test is one branch and folds the lower+upper bound. */ +__attribute__((hot)) +static const gpa_seg* gpa_seg_of(const vmie_mem* m, uint64_t g, uintptr_t* off) { + for (int i = 0; i < m->nseg; i++) { + const gpa_seg* s = &m->seg[i]; + const uint64_t rel = g - s->gpa; + if (rel < s->len) { + *off = (uintptr_t)(s->file_off + rel); + return s; + } + } + return NULL; +} + +/* `*offs` (a GPA) resolves to an in-file offset AND the whole [*, *+nmemb) range + * fits within its single seg. `nmemb > s->len - rel` is at once the file-bounds + * check and the no-straddle/seam reject (a range may not cross a seg boundary). */ +__attribute__((hot)) +static int out_of_bounds(vmie_mem* m, uintptr_t* offs, const size_t nmemb) { + const uint64_t g = *offs; + const gpa_seg* s = gpa_seg_of(m, g, offs); + return !s || nmemb > s->len - (g - s->gpa); +} + +__attribute__((hot)) +int gpa_read(vmie_mem* m, uintptr_t offs, void* buf, const size_t nmemb) { + if (out_of_bounds(m, &offs, nmemb)) { + return -1; + } + memcpy(buf, m->pa + offs, nmemb); + return 0; +} + +int gpa_write(vmie_mem* m, uintptr_t offs, const void* src, const size_t nmemb) { + if (out_of_bounds(m, &offs, nmemb)) { + return -1; + } + memcpy(m->pa + offs, src, nmemb); + return 0; +} + +/* Zero-copy host pointer to [offs, offs+nmemb) GPA, or NULL if that range is not + * fully backed by the mapped image. Same split + bounds check as gpa_read. */ +__attribute__((hot)) +void* gpa_ptr(vmie_mem* m, uintptr_t offs, const size_t nmemb) { + if (out_of_bounds(m, &offs, nmemb)) { + return NULL; + } + return (uint8_t*)m->pa + offs; +} + +/* segment table is well-formed against fsize: nonempty, in range, sorted, dense + * (each seg starts where the previous file span ended), all spans in-file. */ +static int segs_valid(const gpa_seg* segs, int nseg, uint64_t fsize) { + if (nseg < 1 || nseg > VMIE_MAX_SEGS) { + return 0; + } + uint64_t foff = 0; + for (int i = 0; i < nseg; i++) { + if (segs[i].file_off != foff + || segs[i].len == 0 + || segs[i].len > fsize - foff + || (i > 0 && segs[i].gpa < segs[i - 1].gpa + segs[i - 1].len)) { + return 0; + } + foff += segs[i].len; + } + return 1; +} + +__attribute__((cold)) +int gpa_open_segs(vmie_mem* m, const char* path, const gpa_seg* segs, int nseg) { + struct stat st; + + if ((m->fd = open(path, O_RDWR)) < 0) { + goto ret_; + } + + if (fstat(m->fd, &st) || !segs_valid(segs, nseg, (uint64_t)st.st_size)) { + goto close_; + } + + if ((m->pa = mmap(NULL, st.st_size, PROT_RW, MAP_SHARED, m->fd, 0)) == MAP_FAILED) { + close_: + close(m->fd); + ret_: + clean_ctx(m); + return -1; + } + + m->fsize = st.st_size; + m->nseg = nseg; + memcpy(m->seg, segs, (size_t)nseg * sizeof *segs); + + return 0; +} + +/* Convenience: the classic single-`low` QEMU map. Below the 4 GiB PCI hole the + * file maps 1:1 ([0,low)->file[0,low)); at and above 4 GiB it resumes at file + * offset low. When low >= fsize the hole is never reached, so one inert identity + * seg covering the whole image suffices. */ +__attribute__((cold)) +int gpa_open(vmie_mem* m, const char* path, uintptr_t low) { + struct stat st; + if (stat(path, &st)) { + clean_ctx(m); + return -1; + } + const uint64_t fsize = (uint64_t)st.st_size; + + const gpa_seg one[1] = { { 0, fsize, 0 } }; + const gpa_seg two[2] = { { 0, low, 0 }, { RAM_H, fsize > low ? fsize - low : 0, low } }; + const gpa_seg* segs = low >= fsize ? one : two; + const int nseg = low >= fsize ? 1 : 2; + + return gpa_open_segs(m, path, segs, nseg); +} + +__attribute__((cold)) +void gpa_close(vmie_mem* m) { + if (m->pa) { + munmap(m->pa, m->fsize); + } + + if (m->fd >= 0) { + close(m->fd); + } + + clean_ctx(m); +} diff --git a/src/core/include/core.h b/src/core/include/core.h new file mode 100644 index 0000000..8d83ff8 --- /dev/null +++ b/src/core/include/core.h @@ -0,0 +1,57 @@ +#ifndef VMIE_CORE_H +#define VMIE_CORE_H +#include +#include + +#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; + +/* 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 + * 1:1 window, with internal seams between segs that no access may straddle. */ +typedef struct vmie_mem { + void* pa; + size_t fsize; + int fd; + int nseg; + gpa_seg seg[VMIE_MAX_SEGS]; +} vmie_mem; + +/* GPA <-> file-offset converters over the segment map. + * forward (GPA -> offset) lives in gpa.c (it also carries the bounds check); + * inverse (offset -> GPA) is this static inline, shared across PT-walking TUs: + * find the seg whose file span contains `off` and return its GPA. The image is + * dense (segs tile file offsets gaplessly), so a covering seg always exists. */ +static inline uintptr_t offset_gpa(const vmie_mem* m, uintptr_t off) { + for (int i = 0; i < m->nseg; i++) { + const uint64_t rel = (uint64_t)off - m->seg[i].file_off; + if (rel < m->seg[i].len) { + return (uintptr_t)(m->seg[i].gpa + rel); + } + } + return off; +} + +/* guest-physical lifecycle + primitives (gpa.c) */ +int gpa_open_segs(vmie_mem* m, const char* path, const gpa_seg* segs, int nseg); +int gpa_open (vmie_mem* m, const char* path, uintptr_t low); +void gpa_close(vmie_mem* m); +int gpa_read (vmie_mem* m, uintptr_t offs, void* dst, size_t nmemb); +int gpa_write(vmie_mem* m, uintptr_t offs, const void* src, size_t nmemb); + +/* Zero-copy access to guest physical memory. Returns a host pointer to `nmemb` + * contiguous bytes at GPA `offs`, or NULL if that range is not fully backed by + * the mapped image. Valid until the mapping is closed; writes hit the live guest + * (MAP_SHARED). A single 4K/2M/1G leaf never straddles the 4G split, so the + * whole leaf (or a 4096-byte page table) can be taken in one call. */ +void* gpa_ptr(vmie_mem* m, uintptr_t offs, size_t nmemb); + +#endif /* VMIE_CORE_H */ diff --git a/src/guest.c b/src/engine/guest.c similarity index 96% rename from src/guest.c rename to src/engine/guest.c index c0e3a6a..7b1851f 100644 --- a/src/guest.c +++ b/src/engine/guest.c @@ -1,4 +1,4 @@ -#include "include/contract.h" +#include "contract.h" #include #ifndef ACK_POLL_MS diff --git a/src/gva.c b/src/engine/gva.c similarity index 75% rename from src/gva.c rename to src/engine/gva.c index 6b6921d..0604e9b 100644 --- a/src/gva.c +++ b/src/engine/gva.c @@ -2,8 +2,7 @@ #include #include #include -#include "include/memory.h" -#include "../include/include.h" +#include "engine.h" /* PTE permission bits we propagate down the walk. */ #define PTE_RW (1ull << 1) @@ -14,16 +13,16 @@ * Translate `va` under `cr3` to a GPA. On success: *gpa = GPA of `va`, and * *leaf (if non-NULL) = bytes from `va` to the end of the containing leaf. */ __attribute__((hot)) -static int gva_gpa(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, +static int gva_gpa(vmie_mem* m, uintptr_t cr3, uintptr_t va, uintptr_t* gpa, size_t* leaf) { uint64_t t = cr3 & PFN_MASK, e; const uint64_t* pe; const unsigned i4 = (va >> 39) & 0x1ff, i3 = (va >> 30) & 0x1ff, i2 = (va >> 21) & 0x1ff, i1 = (va >> 12) & 0x1ff; - if (!(pe = gpa_ptr(&p_(ctx), t + i4 * 8, 8)) || !((e = *pe) & PG_P)) return -1; + if (!(pe = gpa_ptr(m, t + i4 * 8, 8)) || !((e = *pe) & PG_P)) return -1; t = e & PFN_MASK; - if (!(pe = gpa_ptr(&p_(ctx), t + i3 * 8, 8)) || !((e = *pe) & PG_P)) return -1; + if (!(pe = gpa_ptr(m, t + i3 * 8, 8)) || !((e = *pe) & PG_P)) return -1; if (e & PG_PS) { /* 1 GiB leaf */ const uint64_t off = va & 0x3FFFFFFF; *gpa = (e & PFN_MASK & ~0x3FFFFFFFull) + off; @@ -31,7 +30,7 @@ static int gva_gpa(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, return 0; } t = e & PFN_MASK; - if (!(pe = gpa_ptr(&p_(ctx), t + i2 * 8, 8)) || !((e = *pe) & PG_P)) return -1; + if (!(pe = gpa_ptr(m, t + i2 * 8, 8)) || !((e = *pe) & PG_P)) return -1; if (e & PG_PS) { /* 2 MiB leaf */ const uint64_t off = va & 0x1FFFFF; *gpa = (e & PFN_MASK & ~0x1FFFFFull) + off; @@ -39,43 +38,43 @@ static int gva_gpa(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, return 0; } t = e & PFN_MASK; - if (!(pe = gpa_ptr(&p_(ctx), t + i1 * 8, 8)) || !((e = *pe) & PG_P)) return -1; + if (!(pe = gpa_ptr(m, t + i1 * 8, 8)) || !((e = *pe) & PG_P)) return -1; const uint64_t off = va & 0xFFF; /* 4 KiB leaf */ *gpa = (e & PFN_MASK) + off; if (leaf) *leaf = 0x1000 - off; return 0; } -/* zero-copy borrowed read: leaf-bounded host pointer at `va` (see memory.h). */ +/* zero-copy borrowed read: leaf-bounded host pointer at `va` (see engine.h). */ __attribute__((hot)) -const void* gva_ptr(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t* avail) { +const void* gva_ptr(vmie_mem* m, uintptr_t cr3, uintptr_t va, size_t* avail) { uintptr_t gpa; size_t leaf; - if (gva_gpa(ctx, cr3, va, &gpa, &leaf)) return NULL; + if (gva_gpa(m, cr3, va, &gpa, &leaf)) return NULL; *avail = leaf; - return gpa_ptr(&p_(ctx), gpa, leaf); + return gpa_ptr(m, gpa, leaf); } __attribute__((hot)) -int gva_read(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, void* dst, size_t nmemb) { +int gva_read(vmie_mem* m, uintptr_t cr3, uintptr_t va, void* dst, size_t nmemb) { uint8_t* d = dst; while (nmemb) { uintptr_t gpa; size_t leaf; - if (gva_gpa(ctx, cr3, va, &gpa, &leaf)) return -1; + if (gva_gpa(m, cr3, va, &gpa, &leaf)) return -1; const size_t n = leaf < nmemb ? leaf : nmemb; - if (gpa_read(&p_(ctx), gpa, d, n)) return -1; + if (gpa_read(m, gpa, d, n)) return -1; va += n; d += n; nmemb -= n; } return 0; } __attribute__((hot)) -int gva_write(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, const void* src, size_t nmemb) { +int gva_write(vmie_mem* m, uintptr_t cr3, uintptr_t va, const void* src, size_t nmemb) { const uint8_t* s = src; while (nmemb) { uintptr_t gpa; size_t leaf; - if (gva_gpa(ctx, cr3, va, &gpa, &leaf)) return -1; + if (gva_gpa(m, cr3, va, &gpa, &leaf)) return -1; const size_t n = leaf < nmemb ? leaf : nmemb; - if (gpa_write(&p_(ctx), gpa, s, n)) return -1; + if (gpa_write(m, gpa, s, n)) return -1; va += n; s += n; nmemb -= n; } return 0; @@ -84,23 +83,24 @@ int gva_write(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, const void* src, size_t /* ---- bootstrap helpers (cold) -------------------------------------------- */ __attribute__((cold)) -int khalf_score(const gva_ctx* ctx, uint64_t pml4) { +int khalf_score(const vmie_mem* m, uint64_t pml4) { const uint64_t t = pml4 & PFN_MASK; int n = 0; uint64_t e; for (int i = 256; i < 512; i++) - if (!gpa_read((gpa_ctx*)&p_(ctx), t + i * 8, &e, 8) && (e & PG_P)) n++; + if (!gpa_read((vmie_mem*)m, t + i * 8, &e, 8) && (e & PG_P)) n++; return n; } __attribute__((cold)) -int cr3_recover(gva_ctx* ctx, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) { +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 <= p_(ctx).fsize; off += 0x1000) { - const uintptr_t cand = offset_gpa(&p_(ctx), off); + 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(ctx, cand, va_self, &gpa, NULL)) continue; + if (gva_gpa(m, cand, va_self, &gpa, NULL)) continue; if ((gpa & ~0xFFFull) != (target_pa & ~0xFFFull)) continue; - const int score = khalf_score(ctx, cand); + const int score = khalf_score(m, cand); if (score > best_score) { best_score = score; best = cand; } } if (best_score < 0) return -1; @@ -111,25 +111,25 @@ int cr3_recover(gva_ctx* ctx, uint64_t va_self, uint64_t target_pa, uintptr_t* c /* ---- lifecycle (cold) ---------------------------------------------------- */ __attribute__((cold)) -gva_ctx* gva_ctx_alloc(const char* ram_path, uint64_t low) { - gva_ctx* ctx = calloc(1, sizeof *ctx); - if (!ctx) { +vmie* vmie_open(const char* ram_path, uint64_t low) { + vmie* v = calloc(1, sizeof *v); + if (!v) { return NULL; } - if (gpa_open(&ctx->mem, ram_path, low)) { - free(ctx); + if (gpa_open(&v->mem, ram_path, low)) { + free(v); return NULL; } - return ctx; + return v; } __attribute__((cold)) -void gva_ctx_free(gva_ctx* ctx) { - if (!ctx) { +void vmie_close(vmie* v) { + if (!v) { return; } - gpa_close(&ctx->mem); - free(ctx); + gpa_close(&v->mem); + free(v); } /* ---- region enumeration -------------------------------------------------- */ @@ -178,12 +178,12 @@ static int rgn_hit(uint64_t base, uint64_t span, uint64_t lo, uint64_t hi) { } __attribute__((hot)) -int gva_regions(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, +int gva_regions(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi, uint32_t prot_any, vregion* out, int nmax) { if (nmax <= 0) return 0; struct rgn_acc a = { out, nmax, 0, prot_any, lo, hi, 0, 0, 0, 0 }; - const uint64_t* t4 = gpa_ptr(&p_(ctx), cr3 & PFN_MASK, 4096); + const uint64_t* t4 = gpa_ptr(m, cr3 & PFN_MASK, 4096); if (!t4) return 0; for (int i4 = 0; i4 < 512; i4++) { @@ -193,7 +193,7 @@ int gva_regions(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, if (!rgn_hit(b4, 1ull << 39, lo, hi)) continue; const int rw4 = (e4 >> 1) & 1, us4 = (e4 >> 2) & 1, nx4 = (int)(e4 >> 63) & 1; - const uint64_t* t3 = gpa_ptr(&p_(ctx), e4 & PFN_MASK, 4096); + const uint64_t* t3 = gpa_ptr(m, e4 & PFN_MASK, 4096); if (!t3) continue; for (int i3 = 0; i3 < 512; i3++) { const uint64_t e3 = t3[i3]; @@ -204,7 +204,7 @@ int gva_regions(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, nx3 = nx4 | ((int)(e3 >> 63) & 1); if (e3 & PG_PS) { rgn_leaf(&a, b3, 1ull << 30, rgn_prot(rw3, us3, nx3)); continue; } - const uint64_t* t2 = gpa_ptr(&p_(ctx), e3 & PFN_MASK, 4096); + const uint64_t* t2 = gpa_ptr(m, e3 & PFN_MASK, 4096); if (!t2) continue; for (int i2 = 0; i2 < 512; i2++) { const uint64_t e2 = t2[i2]; @@ -215,7 +215,7 @@ int gva_regions(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, nx2 = nx3 | ((int)(e2 >> 63) & 1); if (e2 & PG_PS) { rgn_leaf(&a, b2, 1ull << 21, rgn_prot(rw2, us2, nx2)); continue; } - const uint64_t* t1 = gpa_ptr(&p_(ctx), e2 & PFN_MASK, 4096); + const uint64_t* t1 = gpa_ptr(m, e2 & PFN_MASK, 4096); if (!t1) continue; for (int i1 = 0; i1 < 512; i1++) { const uint64_t e1 = t1[i1]; @@ -240,7 +240,7 @@ int gva_regions(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, #define SWEEP_RMAX (1u << 16) /* max runs enumerated per sweep */ __attribute__((hot)) -int gva_sweep(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, +int gva_sweep(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi, uint32_t prot_any, size_t overlap, gva_sweep_cb cb, void* user) { if (overlap >= SWEEP_WIN) return -1; @@ -248,7 +248,7 @@ int gva_sweep(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, uint8_t* buf = malloc(SWEEP_WIN); if (!rg || !buf) { free(rg); free(buf); return -1; } - int nr = gva_regions(ctx, cr3, lo, hi, prot_any, rg, SWEEP_RMAX); + int nr = gva_regions(m, cr3, lo, hi, prot_any, rg, SWEEP_RMAX); if (nr > (int)SWEEP_RMAX) nr = (int)SWEEP_RMAX; int rc = 0; @@ -260,7 +260,7 @@ int gva_sweep(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, while (va <= vend) { size_t avail; - const uint8_t* p = gva_ptr(ctx, cr3, va, &avail); + const uint8_t* p = gva_ptr(m, cr3, va, &avail); if (!p) { /* gap: flush+skip */ if (fill && cb(user, buf, fill, base, overlap, 1)) { rc = 1; break; } if (vend - va < 0x1000 - (va & 0xFFF)) break; /* skip past top: done */ @@ -301,4 +301,27 @@ int gva_sweep(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, free(rg); free(buf); return rc; -} \ No newline at end of file +} + +/* ---- 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; +} diff --git a/src/host.c b/src/engine/host.c similarity index 52% rename from src/host.c rename to src/engine/host.c index 8ae0154..43ebc3f 100644 --- a/src/host.c +++ b/src/engine/host.c @@ -1,8 +1,8 @@ #include #include -#include "../include/include.h" -#include "include/contract.h" -#include "include/memory.h" +#include "vmie.h" +#include "contract.h" +#include "engine.h" #define MZ 0x5A4Du #define DIR_EXPORT 0u @@ -10,14 +10,14 @@ #define DBG_CODEVIEW 2u #define CV_RSDS 0x53445352u -static int beacon_find(gva_ctx* ctx, uint64_t* pa, uint64_t* va) { - void *ptr = p_(ctx).pa; - const void *end = p_(ctx).pa + p_(ctx).fsize; +static int beacon_find(vmie_mem* m, uint64_t* pa, uint64_t* va) { + void *ptr = m->pa; + const void *end = m->pa + m->fsize; do { const contract* c = (void*)ptr; if (c->magic0 == CONTRACT_MAGIC0 && c->magic1 == CONTRACT_MAGIC1) { - *pa = offset_gpa(&p_(ctx), ptr - p_(ctx).pa); + *pa = offset_gpa(m, ptr - m->pa); *va = c->va_self; return 0; } @@ -28,64 +28,64 @@ static int beacon_find(gva_ctx* ctx, uint64_t* pa, uint64_t* va) { return -1; } -static int pe_datadir(gva_ctx* ctx, uintptr_t cr3, uint64_t base, unsigned idx, uint32_t* rva, uint32_t* size) { +static int pe_datadir(vmie_mem* m, uintptr_t cr3, uint64_t base, unsigned idx, uint32_t* rva, uint32_t* size) { uint32_t lfanew; - if (gva_read(ctx, cr3, base + 0x3C, &lfanew, 4)) { + if (gva_read(m, cr3, base + 0x3C, &lfanew, 4)) { return -1; } const uint64_t dd = base + lfanew + 0x18 + 0x70 + (uint64_t)idx*8; - if (gva_read(ctx, cr3, dd, rva, 4)) { + if (gva_read(m, cr3, dd, rva, 4)) { return -1; } - return (size && gva_read(ctx, cr3, dd + 4, size, 4)) ? -1 : 0; + return (size && gva_read(m, cr3, dd + 4, size, 4)) ? -1 : 0; } -static int pe_pdb(gva_ctx* ctx, uintptr_t cr3, uint64_t base, uint8_t guid[16], uint32_t* age, char* name, size_t namecap) { +static int pe_pdb(vmie_mem* m, uintptr_t cr3, uint64_t base, uint8_t guid[16], uint32_t* age, char* name, size_t namecap) { uint32_t dbg_rva, dbg_sz; - if (pe_datadir(ctx, cr3, base, DIR_DEBUG, &dbg_rva, &dbg_sz) || !dbg_rva) { + if (pe_datadir(m, cr3, base, DIR_DEBUG, &dbg_rva, &dbg_sz) || !dbg_rva) { return -1; } for (uint32_t o = 0; o + 0x1C <= dbg_sz; o += 0x1C) { /* IMAGE_DEBUG_DIRECTORY[] (28B) */ uint32_t type, cv_rva, sig; - if (gva_read(ctx, cr3, base + dbg_rva + o + 0x0C, &type, 4)) { + if (gva_read(m, cr3, base + dbg_rva + o + 0x0C, &type, 4)) { return -1; } if (type != DBG_CODEVIEW) { continue; } - if (gva_read(ctx, cr3, base + dbg_rva + o + 0x14, &cv_rva, 4)) { /* AddressOfRawData RVA */ + if (gva_read(m, cr3, base + dbg_rva + o + 0x14, &cv_rva, 4)) { /* AddressOfRawData RVA */ return -1; } - if (gva_read(ctx, cr3, base + cv_rva, &sig, 4) || sig != CV_RSDS) { + if (gva_read(m, cr3, base + cv_rva, &sig, 4) || sig != CV_RSDS) { return -1; } - if (gva_read(ctx, cr3, base + cv_rva + 0x04, guid, 16)) { + if (gva_read(m, cr3, base + cv_rva + 0x04, guid, 16)) { return -1; } - if (gva_read(ctx, cr3, base + cv_rva + 0x14, age, 4)) { + if (gva_read(m, cr3, base + cv_rva + 0x14, age, 4)) { return -1; } - gva_read(ctx, cr3, base + cv_rva + 0x18, name, namecap); /* best-effort */ + gva_read(m, cr3, base + cv_rva + 0x18, name, namecap); /* best-effort */ name[namecap - 1] = 0; return 0; } return -1; } -static int find_ntoskrnl(gva_ctx* ctx, uintptr_t cr3, uint64_t* base, uint8_t guid[16], uint32_t* age) { +static int find_ntoskrnl(vmie_mem* m, uintptr_t cr3, uint64_t* base, uint8_t guid[16], uint32_t* age) { const uint64_t t = cr3 & PFN_MASK; for (int p4 = 256; p4 < 512; p4++) { uint64_t e4; - if (gpa_read(&p_(ctx), t + p4*8, &e4, 8) || !(e4 & PG_P)) { + if (gpa_read(m, t + p4*8, &e4, 8) || !(e4 & PG_P)) { continue; } const uint64_t pdpt = e4 & PFN_MASK; for (int p3 = 0; p3 < 512; p3++) { uint64_t e3; - if (gpa_read(&p_(ctx), pdpt + p3*8, &e3, 8) || !(e3 & PG_P)) { + if (gpa_read(m, pdpt + p3*8, &e3, 8) || !(e3 & PG_P)) { continue; } if (e3 & PG_PS) { @@ -95,7 +95,7 @@ static int find_ntoskrnl(gva_ctx* ctx, uintptr_t cr3, uint64_t* base, uint8_t gu for (int p2 = 0; p2 < 512; p2++) { uint64_t e2; - if (gpa_read(&p_(ctx), pd + p2*8, &e2, 8) || !(e2 & PG_P)) { + if (gpa_read(m, pd + p2*8, &e2, 8) || !(e2 & PG_P)) { continue; } @@ -103,10 +103,10 @@ static int find_ntoskrnl(gva_ctx* ctx, uintptr_t cr3, uint64_t* base, uint8_t gu va = VA_CANON(va); uint16_t mz; char pdb[16] = {0}; - if (gva_read(ctx, cr3, va, &mz, 2) || mz != MZ) { + if (gva_read(m, cr3, va, &mz, 2) || mz != MZ) { continue; } - if (pe_pdb(ctx, cr3, va, guid, age, pdb, sizeof pdb)) { + if (pe_pdb(m, cr3, va, guid, age, pdb, sizeof pdb)) { continue; } if (strncmp(pdb, "ntkrnlmp.pdb", 12) != 0) { @@ -120,14 +120,14 @@ static int find_ntoskrnl(gva_ctx* ctx, uintptr_t cr3, uint64_t* base, uint8_t gu return -1; } -static uint32_t ko_export_rva(gva_ctx* ctx, uintptr_t cr3, uint64_t kbase, const char* want) { +static uint32_t ko_export_rva(vmie_mem* m, uintptr_t cr3, uint64_t kbase, const char* want) { uint32_t exp_rva; - if (pe_datadir(ctx, cr3, kbase, DIR_EXPORT, &exp_rva, NULL) || !exp_rva) { + if (pe_datadir(m, cr3, kbase, DIR_EXPORT, &exp_rva, NULL) || !exp_rva) { return 0; } uint8_t ed[40]; - if (gva_read(ctx, cr3, kbase + exp_rva, ed, sizeof ed)) { + if (gva_read(m, cr3, kbase + exp_rva, ed, sizeof ed)) { return 0; } uint32_t nnames, a_funcs, a_names, a_ords; @@ -138,10 +138,10 @@ static uint32_t ko_export_rva(gva_ctx* ctx, uintptr_t cr3, uint64_t kbase, const for (uint32_t i = 0; i < nnames; i++) { uint32_t nrva; char nm[40]; - if (gva_read(ctx, cr3, kbase + a_names + i*4, &nrva, 4)) { + if (gva_read(m, cr3, kbase + a_names + i*4, &nrva, 4)) { return 0; } - if (gva_read(ctx, cr3, kbase + nrva, nm, sizeof nm)) { + if (gva_read(m, cr3, kbase + nrva, nm, sizeof nm)) { continue; } nm[sizeof nm - 1] = 0; @@ -149,21 +149,26 @@ static uint32_t ko_export_rva(gva_ctx* ctx, uintptr_t cr3, uint64_t kbase, const continue; } uint16_t ord; uint32_t frva; - if (gva_read(ctx, cr3, kbase + a_ords + i*2, &ord, 2)) { + if (gva_read(m, cr3, kbase + a_ords + i*2, &ord, 2)) { return 0; } - return gva_read(ctx, cr3, kbase + a_funcs + ord*4, &frva, 4) ? 0 : frva; + return gva_read(m, cr3, kbase + a_funcs + ord*4, &frva, 4) ? 0 : frva; } return 0; } -static void beacon_ack(gva_ctx* ctx, uint64_t anchor_pa) { +static void beacon_ack(vmie_mem* m, uint64_t anchor_pa) { uint64_t ack = CONTRACT_ACK; - gpa_write(&p_(ctx), anchor_pa + offsetof(contract, ack), &ack, 8); + gpa_write(m, anchor_pa + offsetof(contract, ack), &ack, 8); +} + +vmie_mem* vmie_memory(vmie* v) { + return v ? &v->mem : NULL; } __attribute__((cold)) -int host_bootstrap(gva_ctx* ctx) { +int host_bootstrap(vmie* v) { + vmie_mem* m = &v->mem; uint64_t anchor_pa, va_self; uintptr_t cr3boot; uint32_t rva; @@ -171,34 +176,34 @@ int host_bootstrap(gva_ctx* ctx) { uint32_t age; uint64_t sys_ep; - if (beacon_find(ctx, &anchor_pa, &va_self)) { + if (beacon_find(m, &anchor_pa, &va_self)) { return -1; } - if (cr3_recover(ctx, va_self, anchor_pa, &cr3boot)) { + if (cr3_recover(v, va_self, anchor_pa, &cr3boot)) { return -2; } - if (find_ntoskrnl(ctx, cr3boot, &ctx->kbase, guid, &age)) { + if (find_ntoskrnl(m, cr3boot, &v->kbase, guid, &age)) { return -3; } - rva = ko_export_rva(ctx, cr3boot, ctx->kbase, "PsInitialSystemProcess"); - if (!rva || gva_read(ctx, cr3boot, ctx->kbase + rva, &sys_ep, 8)) { + rva = ko_export_rva(m, cr3boot, v->kbase, "PsInitialSystemProcess"); + if (!rva || gva_read(m, cr3boot, v->kbase + rva, &sys_ep, 8)) { return -4; } - if (profile_build(ctx, cr3boot, sys_ep, guid, age)) { + if (profile_build(v, cr3boot, sys_ep, guid, age)) { return -5; } uint64_t dtb; - if (gva_read(ctx, cr3boot, sys_ep + ctx->prof.ep_dtb, &dtb, 8)) { + if (gva_read(m, cr3boot, sys_ep + v->prof.ep_dtb, &dtb, 8)) { return -6; } - ctx->kcr3 = dtb & PFN_MASK; - ctx->sysproc = sys_ep; + v->kcr3 = dtb & PFN_MASK; + v->sysproc = sys_ep; - beacon_ack(ctx, anchor_pa); + beacon_ack(m, anchor_pa); return 0; } diff --git a/src/include/contract.h b/src/engine/include/contract.h similarity index 100% rename from src/include/contract.h rename to src/engine/include/contract.h diff --git a/src/engine/include/engine.h b/src/engine/include/engine.h new file mode 100644 index 0000000..77bcd04 --- /dev/null +++ b/src/engine/include/engine.h @@ -0,0 +1,68 @@ +#ifndef VMIE_ENGINE_H +#define VMIE_ENGINE_H +#include +#include +#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 */ diff --git a/src/engine/include/pe.h b/src/engine/include/pe.h new file mode 100644 index 0000000..112e724 --- /dev/null +++ b/src/engine/include/pe.h @@ -0,0 +1,46 @@ +/* pe.h - PE/COFF image parsing (engine-private, Windows-specific). + * + * Locating a section by name inside a mapped PE image is a Windows-image + * concern, not a property of the source-agnostic matcher: it lives in the + * engine, alongside the rest of the Windows bring-up. Handlers never see this + * header - they consume only the generic memory model (memmodel.h) and the pure + * matcher (sigscan.h). The engine uses these to build mem_view_t windows out of + * a guest image and feed them to the matcher. + */ +#ifndef VMIE_PE_H +#define VMIE_PE_H +#include +#include +#include +#include "memmodel.h" /* mem_view_t, vmie_mem */ + +/* Locate a PE section by name within a view that contains at least the image + * headers at `module_base` (the first page is enough). + * module_base - image base VA, must be >= v.base_va and inside `v` + * name - section name, e.g. ".text" (compared up to 8 bytes) + * rva_out - receives the section RVA (relative to module_base); may be NULL + * vsize_out - receives the section virtual size; may be NULL + * Returns true if found. Only the headers need to be present in `v`; the section + * body does not. */ +bool pe_find_section(mem_view_t v, uint64_t module_base, const char* name, + uint64_t* rva_out, uint32_t* vsize_out); + +/* Locate a PE section AND return a sub-view spanning it. Requires the whole + * section body to be present in `v` (true for an in-memory image dump). Prefer + * scanning ".text" over a whole image: faster, and avoids false hits in data. + * Returns true and fills *out on success. For guest memory, where the body is + * usually not co-resident with the headers, use vmie_pe_section. */ +bool pe_section(mem_view_t v, uint64_t module_base, const char* name, + mem_view_t* out); + +/* Read a PE section out of guest memory under `cr3` into `buf`. + * module_base - image base VA (headers read from the first page) + * name - section name, e.g. ".text" + * buf, bufcap - destination buffer and its capacity (section is truncated to fit) + * out - on success, a view spanning the bytes read into `buf` + * Returns 0 on success, -1 if the headers/section are unreadable or absent. The + * guest image body need not be co-resident with the headers (unlike pe_section).*/ +int vmie_pe_section(vmie_mem* m, uintptr_t cr3, uint64_t module_base, + const char* name, uint8_t* buf, size_t bufcap, mem_view_t* out); + +#endif /* VMIE_PE_H */ diff --git a/src/engine/pe.c b/src/engine/pe.c new file mode 100644 index 0000000..4237c8e --- /dev/null +++ b/src/engine/pe.c @@ -0,0 +1,63 @@ +#include "pe.h" + +#include +#include "memmodel.h" /* gva_read */ +#include "sigscan.h" /* mem_sub (pure matcher; engine may use it) */ + +bool pe_find_section(mem_view_t v, uint64_t module_base, const char* name, + uint64_t* rva_out, uint32_t* vsize_out) { + if (!v.data || !name || module_base < v.base_va) return false; + const size_t mo = (size_t)(module_base - v.base_va); + if (mo + 0x40 > v.size) return false; + if (v.data[mo] != 'M' || v.data[mo + 1] != 'Z') return false; + + int32_t e_lfanew; + memcpy(&e_lfanew, v.data + mo + 0x3C, 4); + const size_t nt = mo + (size_t)(uint32_t)e_lfanew; + if (nt + 0x18 > v.size) return false; + if (memcmp(v.data + nt, "PE\0\0", 4) != 0) return false; + + uint16_t nsec, opt_size; + memcpy(&nsec, v.data + nt + 6, 2); /* NumberOfSections */ + memcpy(&opt_size, v.data + nt + 20, 2); /* SizeOfOptionalHeader */ + + const size_t sec = nt + 24 + opt_size; /* first section header */ + size_t want = strlen(name); + if (want > 8) want = 8; + + for (uint16_t i = 0; i < nsec; i++) { + const size_t sh = sec + (size_t)i * 40; + if (sh + 40 > v.size) break; + char nm[9] = {0}; + memcpy(nm, v.data + sh, 8); + if (strncmp(nm, name, want) == 0 && (want == 8 || nm[want] == '\0')) { + uint32_t vsize, vaddr; + memcpy(&vsize, v.data + sh + 8, 4); /* Misc.VirtualSize */ + memcpy(&vaddr, v.data + sh + 12, 4); /* VirtualAddress */ + if (rva_out) *rva_out = vaddr; + if (vsize_out) *vsize_out = vsize; + return true; + } + } + return false; +} + +bool pe_section(mem_view_t v, uint64_t module_base, const char* name, mem_view_t* out) { + uint64_t rva; uint32_t vsize; + if (!out || !pe_find_section(v, module_base, name, &rva, &vsize)) return false; + *out = mem_sub(v, module_base + rva, vsize); + return out->data != NULL; +} + +int vmie_pe_section(vmie_mem* m, uintptr_t cr3, uint64_t module_base, + const char* name, uint8_t* buf, size_t bufcap, mem_view_t* out) { + uint8_t hdr[0x1000]; + if (!out || !buf || gva_read(m, cr3, module_base, hdr, sizeof hdr)) return -1; + const mem_view_t hv = { hdr, sizeof hdr, module_base }; + uint64_t rva; uint32_t vsize; + if (!pe_find_section(hv, module_base, name, &rva, &vsize)) return -1; + const size_t n = vsize < bufcap ? vsize : bufcap; + if (gva_read(m, cr3, module_base + rva, buf, n)) return -1; + out->data = buf; out->size = n; out->base_va = module_base + rva; + return 0; +} diff --git a/src/engine/proc.c b/src/engine/proc.c new file mode 100644 index 0000000..f775871 --- /dev/null +++ b/src/engine/proc.c @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include "engine.h" +#include "vmie.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) { + vmie_mem* m = &v->mem; + uint16_t len = 0; + uint64_t buf = 0; + out->va = 0; + out->len = 0; + if (gva_read(m, cr3, va, &len, 2) || gva_read(m, cr3, va + 8, &buf, 8)) { + return; + } + out->va = buf; + out->len = len; +} + +int proc_list(vmie* 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; + if (!kcr3 || !v->sysproc) { + return -1; + } + + size_t n = 0; + unsigned guard = 0; + uint64_t ep = v->sysproc, node; + + do { + uint64_t pid = 0, ppid = 0, dtb = 0, peb = 0; + gva_read(m, kcr3, ep + p->ep_pid, &pid, 8); + gva_read(m, kcr3, ep + p->ep_dtb, &dtb, 8); + if (p->ep_peb) { gva_read(m, kcr3, ep + p->ep_peb, &peb, 8); } + if (p->ep_ppid) { gva_read(m, kcr3, ep + p->ep_ppid, &ppid, 8); } + + if (!skip_system || peb) { + if (n >= nmax) { + return (int)n; + } + process* q = &dst[n++]; + q->eprocess = ep; + q->cr3 = dtb & PFN_MASK; + q->peb = peb; + q->pid = (uint32_t)pid; + q->ppid = p->ep_ppid ? (uint32_t)ppid : (uint32_t)-1; + q->create_time = 0; + if (p->ep_createtime) { + gva_read(m, kcr3, ep + p->ep_createtime, &q->create_time, 8); + } + memset(q->name, 0, sizeof q->name); + gva_read(m, kcr3, ep + p->ep_name, q->name, sizeof q->name - 1); + q->path.va = 0; + q->path.len = 0; + if (p->ep_imgpath) { + grab_ustr(v, kcr3, ep + p->ep_imgpath, &q->path); /* read text under kcr3 */ + } + } + + if (gva_read(m, kcr3, ep + p->ep_links, &node, 8)) { + break; + } + ep = node - p->ep_links; + } while (ep != v->sysproc && ++guard < RING_GUARD); + + return (int)n; +} + +int proc_modules(vmie* 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; + if (!pr->peb || !cr3) { + return 0; + } + + uint64_t ldr = 0, head, link; + if (gva_read(m, cr3, pr->peb + p->peb_ldr, &ldr, 8) || !ldr) { + return 0; + } + head = ldr + p->ldr_loadlist; + if (gva_read(m, cr3, head, &link, 8)) { + return 0; + } + + size_t n = 0; + unsigned guard = 0; + while (link != head && n < nmax && ++guard < MOD_GUARD) { + const uint64_t entry = link; /* InLoadOrderLinks at offset 0 of the entry */ + uint64_t base = 0; + uint32_t size = 0; + gva_read(m, cr3, entry + p->lde_base, &base, 8); + gva_read(m, cr3, entry + p->lde_size, &size, 4); + + pmodule* mod = &dst[n++]; + mod->pr = pr; + mod->entry = entry; + mod->base = base; + mod->size = size; + grab_ustr(v, cr3, entry + p->lde_name, &mod->name); + grab_ustr(v, cr3, entry + p->lde_fullname, &mod->path); + + if (gva_read(m, cr3, link, &link, 8)) { + break; + } + } + return (int)n; +} + +/* ---- win32 scan wrappers ------------------------------------------------- * + * 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, + int be, int aligned, uint64_t lo, uint64_t hi) { + if (!pr) { + return NULL; + } + return scan_new_cr3(&v->mem, pr->cr3, t, value, be, aligned, lo, hi); +} + +#define PTR_MOD_CAP 1024u + +int vmie_scan_pointer(vmie* v, const process* pr, uint64_t target, + int max_depth, uint32_t max_off, scan_ptr_path* out, int max) { + if (!pr) { + return -1; + } + pmodule* mods = malloc(PTR_MOD_CAP * sizeof *mods); + if (!mods) { + return -1; + } + int nm = proc_modules(v, pr, mods, PTR_MOD_CAP); + if (nm < 0) { + nm = 0; + } + + range* rng = nm ? malloc((size_t)nm * sizeof *rng) : NULL; + if (nm && !rng) { + free(mods); + return -1; + } + for (int i = 0; i < nm; i++) { + rng[i].base = mods[i].base; + rng[i].size = mods[i].size; + rng[i].name[0] = 0; + if (mods[i].name.va) { + gva_read_text(v, pr->cr3, mods[i].name.va, mods[i].name.len, + rng[i].name, sizeof rng[i].name); + } + } + + const int rc = scan_pointer(&v->mem, pr->cr3, rng, nm, target, + max_depth, max_off, out, max); + free(rng); + free(mods); + return rc; +} diff --git a/src/profile.c b/src/engine/profile.c similarity index 63% rename from src/profile.c rename to src/engine/profile.c index c76e65c..57b1647 100644 --- a/src/profile.c +++ b/src/engine/profile.c @@ -1,11 +1,10 @@ #include #include -#include "../include/include.h" -#include "include/memory.h" +#include "engine.h" -#define pr_(ctx) ((ctx)->prof) +#define pr_(v) ((v)->prof) -#define RING_CAP 4096 /* USER_MIN/USER_MAX/KERN_MIN come from include/memory.h */ +#define RING_CAP 4096 /* USER_MIN/USER_MAX/KERN_MIN come from engine.h */ #define SCAN_MAX 1024 #define FT_LO 0x01D0000000000000ll #define FT_HI 0x01F0000000000000ll @@ -15,16 +14,17 @@ 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(gva_ctx* ctx, uintptr_t cr3, uint64_t head, int kernel) { +static int list_ring_ok(vmie* v, uintptr_t cr3, uint64_t head, int kernel) { + vmie_mem* m = &v->mem; uint64_t node; - if (gva_read(ctx, cr3, head, &node, 8)) { + if (gva_read(m, 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)) { + if (!canon_ok(node, kernel) || gva_read(m, cr3, node, &node, 8)) { return 0; } } @@ -32,10 +32,11 @@ static int list_ring_ok(gva_ctx* ctx, 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(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep) { - profile* p = &pr_(ctx); +static int discover_core(vmie* v, uintptr_t cr3, uint64_t sys_ep) { + vmie_mem* m = &v->mem; + profile* p = &pr_(v); uint8_t buf[0x800]; - if (gva_read(ctx, cr3, sys_ep, buf, sizeof buf)) { + if (gva_read(m, cr3, sys_ep, buf, sizeof buf)) { return -1; } @@ -53,12 +54,12 @@ static int discover_core(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep) { int pid_off = -1; for (int o = 0x80; o + 8 <= name_off; o += 8) { - uint64_t v; memcpy(&v, buf + o, 8); - if (v != 4) { + uint64_t val; memcpy(&val, buf + o, 8); + if (val != 4) { continue; } const uint16_t links = (uint16_t)(o + 8); - if (list_ring_ok(ctx, cr3, sys_ep + links, 1)) { + if (list_ring_ok(v, cr3, sys_ep + links, 1)) { p->ep_links = links; pid_off = o; break; @@ -71,10 +72,10 @@ static int discover_core(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep) { int dtb_off = -1; for (int o = 0x18; o <= 0x60; o += 8) { - uint64_t v; memcpy(&v, buf + o, 8); - const uint64_t c = v & PFN_MASK; + uint64_t val; memcpy(&val, buf + o, 8); + const uint64_t c = val & PFN_MASK; uint8_t probe; - if (c && khalf_score(ctx, c) >= 16 && !gva_read(ctx, c, sys_ep, &probe, 1)) { + if (c && khalf_score(m, c) >= 16 && !gva_read(m, c, sys_ep, &probe, 1)) { dtb_off = o; break; } @@ -87,21 +88,22 @@ static int discover_core(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep) { } /* 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); +static int collect_procs(vmie* 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; 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); + gva_read(m, cr3, ep + p->ep_pid, &pid, 8); + gva_read(m, 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)) { + if (gva_read(m, cr3, ep + p->ep_links, &node, 8)) { break; } ep = node - p->ep_links; @@ -110,13 +112,14 @@ static int collect_procs(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep, uint64_t* } /* 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) { +static void discover_ppid(vmie* 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) { 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]) { + if (gva_read(m, cr3, eps[i] + o, &cand, 4) || !cand || cand == pids[i]) { continue; } for (int j = 0; j < n; j++) { @@ -129,18 +132,19 @@ static void discover_ppid(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, cons } } if (best_off >= 0 && best_hits * 3 >= n) { - pr_(ctx).ep_ppid = (uint16_t)best_off; + pr_(v).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) { +static void discover_createtime(vmie* 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; 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 (gva_read(m, cr3, eps[i] + o, &t, 8) || t < FT_LO || t > FT_HI) { ok = 0; break; } if (i == 0) { sysv = t; } else if (t < sysv) { @@ -148,7 +152,7 @@ static void discover_createtime(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps } } if (ok) { - pr_(ctx).ep_createtime = (uint16_t)o; + pr_(v).ep_createtime = (uint16_t)o; return; } } @@ -156,14 +160,15 @@ static void discover_createtime(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps /* 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); +static void discover_imgpath(vmie* 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++) { if (!cr3s[i]) { continue; } char nm[16] = {0}; - if (gva_read(ctx, cr3, eps[i] + p->ep_name, nm, 15)) { + if (gva_read(m, cr3, eps[i] + p->ep_name, nm, 15)) { continue; } const size_t nl = strnlen(nm, 15); @@ -173,14 +178,14 @@ static void discover_imgpath(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, c 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)) { + if (gva_read(m, cr3, eps[i] + o, &len, 2) || gva_read(m, 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)) { + if (gva_read(m, cr3, buf + len - (uint64_t)nl * 2, w, nl * 2)) { continue; } int match = 1; @@ -197,8 +202,9 @@ static void discover_imgpath(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, c /* 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); +static int discover_user_chain(vmie* 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++) { const uint64_t pcr3 = cr3s[i]; @@ -207,34 +213,34 @@ static int discover_user_chain(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, } 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)) { + if (gva_read(m, 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)) { + if (gva_read(m, 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)) { + if (!list_ring_ok(v, 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)) { + if (gva_read(m, pcr3, ldr + ll, &entry, 8)) { continue; } - if (gva_read(ctx, pcr3, entry + 0x30, &dllbase, 8) || + if (gva_read(m, 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)) { + if (gva_read(m, pcr3, entry + 0x58, &nlen, 2) || !nlen || (nlen & 1) || + gva_read(m, 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)) { + if (gva_read(m, pcr3, entry + 0x48, &flen, 2) || (flen & 1) || + gva_read(m, pcr3, entry + 0x48 + 8, &fbufp, 8) || !canon_ok(fbufp, 0)) { continue; } @@ -254,26 +260,26 @@ static int discover_user_chain(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, } __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; +int profile_build(vmie* 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; - if (discover_core(ctx, cr3, sys_ep)) { + if (discover_core(v, 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); + const int n = collect_procs(v, 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)) { + discover_ppid(v, cr3, eps, pids, n); + discover_createtime(v, cr3, eps, n); + discover_imgpath(v, cr3, eps, cr3s, n); + if (discover_user_chain(v, cr3, eps, cr3s, n)) { return -3; } return 0; diff --git a/src/text.c b/src/engine/text.c similarity index 89% rename from src/text.c rename to src/engine/text.c index b1ed11a..7a50cb2 100644 --- a/src/text.c +++ b/src/engine/text.c @@ -1,7 +1,7 @@ #include #include -#include "include/memory.h" -#include "../include/include.h" +#include "engine.h" +#include "vmie.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,8 @@ static void utf8_emit(uint32_t cp, char* dst, size_t size, size_t* need, size_t* *need += k; } -size_t gva_read_text(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size) { +size_t gva_read_text(vmie* 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]; uint32_t hi = 0; @@ -24,7 +25,7 @@ size_t gva_read_text(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t nmemb, ch while (nmemb) { size_t chunk = nmemb < sizeof stage ? nmemb : sizeof stage; - if (gva_read(ctx, cr3, va, stage, chunk)) { + if (gva_read(m, cr3, va, stage, chunk)) { break; } const size_t units = chunk / 2; diff --git a/src/gpa.c b/src/gpa.c deleted file mode 100644 index 99edbea..0000000 --- a/src/gpa.c +++ /dev/null @@ -1,108 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "include/memory.h" - -#define RAM_H (1ul<<32) -#define PROT_RW (PROT_READ | PROT_WRITE) - -__attribute__((hot)) -static int gpa_offset(const gpa_ctx* ctx, uintptr_t offs, uintptr_t* out) { - if (offs < ctx->low) { - *out = offs; - return 0; - } - - if (offs >= RAM_H) { - const uint64_t x = ctx->low + (offs - RAM_H); - - if (x < ctx->fsize) { - *out = x; - return 0; - } else { /* Out of bounds */ } - } else { /* Not RAM */ } - - return -1; -} - -static void clean_ctx(gpa_ctx* ctx) { - memset(ctx, 0, sizeof(gpa_ctx)); - ctx->fd = -1; -} - -static int out_of_bounds(gpa_ctx* ctx, uintptr_t* offs, const size_t nmemb) { - return gpa_offset(ctx, *offs, offs) - || nmemb > ctx->fsize - *offs - || (*offs < ctx->low && nmemb > ctx->low - *offs); /* range crosses split */ -} - -__attribute__((hot)) -int gpa_read(gpa_ctx* ctx, uintptr_t offs, void* buf, const size_t nmemb) { - if (out_of_bounds(ctx, &offs, nmemb)) { - return -1; - } - memcpy(buf, ctx->pa + offs, nmemb); - return 0; -} - -int gpa_write(gpa_ctx* ctx, uintptr_t offs, const void* src, const size_t nmemb) { - if (out_of_bounds(ctx, &offs, nmemb)) { - return -1; - } - memcpy(ctx->pa + offs, src, nmemb); - return 0; -} - -/* Zero-copy host pointer to [offs, offs+nmemb) GPA, or NULL if that range is not - * fully backed by the mapped image. Same split + bounds check as gpa_read. */ -__attribute__((hot)) -void* gpa_ptr(gpa_ctx* ctx, uintptr_t offs, const size_t nmemb) { - if (out_of_bounds(ctx, &offs, nmemb)) { - return NULL; - } - return (uint8_t*)ctx->pa + offs; -} - -__attribute__((cold)) -int gpa_open(gpa_ctx* ctx, const char* path, uintptr_t low) { - struct stat st; - - if ((ctx->fd = open(path, O_RDWR)) < 0) { - goto ret_; - } - - if (fstat(ctx->fd, &st)) { - goto close_; - } - - if ((ctx->pa = mmap(NULL, st.st_size, PROT_RW, MAP_SHARED, ctx->fd, 0)) == MAP_FAILED) { - close_: - close(ctx->fd); - ret_: - clean_ctx(ctx); - return -1; - } - - ctx->fsize = st.st_size; - ctx->low = low; - - return 0; -} - -__attribute__((cold)) -void gpa_close(gpa_ctx* ctx) { - if (ctx->pa) { - munmap(ctx->pa, ctx->fsize); - } - - if (ctx->fd >= 0) { - close(ctx->fd); - } - - clean_ctx(ctx); -} - diff --git a/src/scan.c b/src/handlers/scan.c similarity index 76% rename from src/scan.c rename to src/handlers/scan.c index f2233b4..91627f2 100644 --- a/src/scan.c +++ b/src/handlers/scan.c @@ -2,12 +2,11 @@ #include #include #include -#include "../include/include.h" -#include "../include/sigscan.h" -#include "../include/scan.h" -#include "include/memory.h" +#include "memmodel.h" /* vmie_mem, range, USER_MIN/USER_MAX (the pointer-scan VA window) */ +#include "sigscan.h" +#include "scan.h" -#define REG_CAP (1 << 16) /* USER_MIN/USER_MAX come from include/memory.h */ +#define REG_CAP (1 << 16) /* ---- typed value codec --------------------------------------------------- */ @@ -94,7 +93,7 @@ static int needs_value(scan_op op) { /* ---- scan session -------------------------------------------------------- */ struct scan { - gva_ctx* ctx; + vmie_mem* m; uintptr_t cr3; scan_type type; int tsz; int be; int aligned; size_t step; uint64_t lo, hi; @@ -139,46 +138,35 @@ static int vfirst_cb(void* u, const uint8_t* data, size_t len, return 0; } -static struct scan* scan_make(gva_ctx* ctx, uintptr_t cr3, scan_type t, const void* value, - int be, int aligned, uint64_t lo, uint64_t hi) { +scan* scan_new_cr3(vmie_mem* m, uintptr_t cr3, scan_type t, const void* value, + int be, int aligned, uint64_t lo, uint64_t hi) { struct scan* s = calloc(1, sizeof *s); if (!s) return NULL; - s->ctx = ctx; s->cr3 = cr3; s->type = t; s->tsz = g_tsz[t]; + s->m = m; s->cr3 = cr3; s->type = t; s->tsz = g_tsz[t]; s->be = be; s->aligned = aligned; s->step = aligned ? (size_t)s->tsz : 1; s->lo = lo; s->hi = hi; if (value) { struct vfirst c = { s, sload((const uint8_t*)value, t, 0) }; - gva_sweep(ctx, cr3, lo, hi, VR_W, aligned ? 0 : (size_t)(s->tsz - 1), vfirst_cb, &c); + gva_sweep(m, cr3, lo, hi, VR_W, aligned ? 0 : (size_t)(s->tsz - 1), vfirst_cb, &c); } else { s->snap = 1; s->regs = malloc((size_t)REG_CAP * sizeof *s->regs); if (!s->regs) { free(s); return NULL; } - s->nregs = gva_regions(ctx, cr3, lo, hi, VR_W, s->regs, REG_CAP); + s->nregs = gva_regions(m, cr3, lo, hi, VR_W, s->regs, REG_CAP); if (s->nregs > REG_CAP) s->nregs = REG_CAP; s->snapbuf = calloc((size_t)s->nregs, sizeof(uint8_t*)); if (!s->snapbuf && s->nregs) { free(s->regs); free(s); return NULL; } for (int i = 0; i < s->nregs; i++) { uint8_t* b = malloc((size_t)s->regs[i].len); if (!b) continue; /* skip a run we cannot hold */ - if (gva_read(ctx, cr3, s->regs[i].va, b, (size_t)s->regs[i].len)) { free(b); b = NULL; } + if (gva_read(m, cr3, s->regs[i].va, b, (size_t)s->regs[i].len)) { free(b); b = NULL; } s->snapbuf[i] = b; } } return s; } -scan* scan_new(gva_ctx* ctx, const process* pr, scan_type t, const void* value, - int be, int aligned, uint64_t lo, uint64_t hi) { - if (!pr) return NULL; - return scan_make(ctx, pr->cr3, t, value, be, aligned, lo, hi); -} - -scan* scan_new_cr3(gva_ctx* ctx, uintptr_t cr3, scan_type t, const void* value, - int be, int aligned, uint64_t lo, uint64_t hi) { - return scan_make(ctx, cr3, t, value, be, aligned, lo, hi); -} - int64_t scan_next(scan* s, scan_op op, const void* value) { if (!s) return -1; if (needs_value(op) && !value) return -1; @@ -192,7 +180,7 @@ int64_t scan_next(scan* s, scan_op op, const void* value) { const size_t len = (size_t)s->regs[i].len, tsz = s->tsz; uint8_t* live = malloc(len); if (!live) continue; - if (gva_read(s->ctx, s->cr3, s->regs[i].va, live, len)) { free(live); continue; } + if (gva_read(s->m, s->cr3, s->regs[i].va, live, len)) { free(live); continue; } size_t off = 0; if (s->aligned) { const size_t m = (size_t)(s->regs[i].va % tsz); if (m) off = tsz - m; } for (; off + tsz <= len; off += s->step) { @@ -212,7 +200,7 @@ int64_t scan_next(scan* s, scan_op op, const void* value) { uint64_t w = 0; /* narrow in place */ for (uint64_t i = 0; i < s->n; i++) { uint8_t cur_b[8]; - if (gva_read(s->ctx, s->cr3, s->addr[i], cur_b, s->tsz)) continue; /* page gone */ + if (gva_read(s->m, s->cr3, s->addr[i], cur_b, s->tsz)) continue; /* page gone */ const sval cur = sload(cur_b, s->type, s->be); const sval r = have_ref ? ref : sload(s->val + i * (size_t)s->tsz, s->type, s->be); if (scmp(op, cur, r)) { @@ -259,7 +247,7 @@ static size_t pslot_lb(const struct pslot* a, size_t n, uint64_t key) { struct pscan { struct pslot* idx; size_t nidx; - pmodule* mods; int nmods; + const range* mods; int nmods; uint32_t max_off; int max_depth; scan_ptr_path* out; int max, n; int32_t disc[SCAN_PTR_MAXDEPTH]; @@ -287,15 +275,16 @@ static void ptr_dfs(struct pscan* P, uint64_t need, int hops) { } } -int scan_pointer(gva_ctx* ctx, const process* pr, uint64_t target, - int max_depth, uint32_t max_off, scan_ptr_path* out, int max) { - if (!pr || max_depth < 1 || max < 1) return -1; +int scan_pointer(vmie_mem* m, uintptr_t cr3, const range* mods, int nmods, + uint64_t target, int max_depth, uint32_t max_off, + scan_ptr_path* out, int max) { + if (max_depth < 1 || max < 1) return -1; if (max_depth > SCAN_PTR_MAXDEPTH) max_depth = SCAN_PTR_MAXDEPTH; - const uintptr_t cr3 = pr->cr3; + if (nmods < 0) nmods = 0; vregion* rg = malloc((size_t)REG_CAP * sizeof *rg); if (!rg) return -1; - int nr = gva_regions(ctx, cr3, USER_MIN, USER_MAX, VR_W, rg, REG_CAP); + int nr = gva_regions(m, cr3, USER_MIN, USER_MAX, VR_W, rg, REG_CAP); if (nr > REG_CAP) nr = REG_CAP; struct pslot* idx = NULL; size_t nidx = 0, capi = 0; @@ -303,7 +292,7 @@ int scan_pointer(gva_ctx* ctx, const process* pr, uint64_t target, for (int r = 0; r < nr; r++) { const size_t len = (size_t)rg[r].len; if (len > tmpcap) { uint8_t* nt = realloc(tmp, len); if (!nt) continue; tmp = nt; tmpcap = len; } - if (gva_read(ctx, cr3, rg[r].va, tmp, len)) continue; + if (gva_read(m, cr3, rg[r].va, tmp, len)) continue; for (size_t o = 0; o + 8 <= len; o += 8) { uint64_t v; memcpy(&v, tmp + o, 8); if (v < USER_MIN || v > USER_MAX) continue; @@ -320,16 +309,12 @@ built: free(tmp); qsort(idx, nidx, sizeof *idx, ps_cmp); - pmodule* mods = malloc(1024 * sizeof *mods); - int nm = mods ? proc_modules(ctx, pr, mods, 1024) : 0; - if (nm < 0) nm = 0; - struct pscan P; memset(&P, 0, sizeof P); - P.idx = idx; P.nidx = nidx; P.mods = mods; P.nmods = nm; + P.idx = idx; P.nidx = nidx; P.mods = mods; P.nmods = nmods; P.max_off = max_off; P.max_depth = max_depth; P.out = out; P.max = max; ptr_dfs(&P, target, 0); - free(idx); free(mods); free(rg); + free(idx); free(rg); return P.n; } @@ -362,69 +347,31 @@ static int sig_sweep_cb(void* u, const uint8_t* data, size_t len, return c->stop; /* abort sweep after first (gva_sig_first) */ } -int gva_sig_scan(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, +int gva_sig_scan(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi, uint32_t prot_any, const sig_pattern_t* p, uint64_t* out, int max) { if (!p || p->len == 0) return -1; struct sigcb c; memset(&c, 0, sizeof c); c.p = p; c.out = out; c.max = max; - if (gva_sweep(ctx, cr3, lo, hi, prot_any, p->len - 1, sig_sweep_cb, &c) < 0) return -1; + if (gva_sweep(m, cr3, lo, hi, prot_any, p->len - 1, sig_sweep_cb, &c) < 0) return -1; return c.n; } -int gva_sig_first(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, +int gva_sig_first(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi, uint32_t prot_any, const sig_pattern_t* p, uint64_t* va) { if (!p || p->len == 0 || !va) return -1; uint64_t hit = 0; struct sigcb c; memset(&c, 0, sizeof c); c.p = p; c.out = &hit; c.max = 1; c.first = 1; - if (gva_sweep(ctx, cr3, lo, hi, prot_any, p->len - 1, sig_sweep_cb, &c) < 0) return -1; + if (gva_sweep(m, cr3, lo, hi, prot_any, p->len - 1, sig_sweep_cb, &c) < 0) return -1; if (c.n == 0) return 1; *va = hit; return 0; } -int gva_sig_rip(gva_ctx* ctx, uintptr_t cr3, uint64_t hit_va, +int gva_sig_rip(vmie_mem* m, uintptr_t cr3, uint64_t hit_va, size_t disp_off, size_t instr_len, uint64_t* target) { int32_t disp; - if (!target || gva_read(ctx, cr3, hit_va + disp_off, &disp, 4)) return -1; + if (!target || gva_read(m, cr3, hit_va + disp_off, &disp, 4)) return -1; *target = hit_va + instr_len + (int64_t)disp; return 0; } - -int gva_pe_section(gva_ctx* ctx, uintptr_t cr3, uint64_t module_base, - const char* name, uint8_t* buf, size_t bufcap, mem_view_t* out) { - uint8_t hdr[0x1000]; - if (!out || !buf || gva_read(ctx, cr3, module_base, hdr, sizeof hdr)) return -1; - const mem_view_t hv = { hdr, sizeof hdr, module_base }; - uint64_t rva; uint32_t vsize; - if (!pe_find_section(hv, module_base, name, &rva, &vsize)) return -1; - const size_t n = vsize < bufcap ? vsize : bufcap; - if (gva_read(ctx, cr3, module_base + rva, buf, n)) return -1; - out->data = buf; out->size = n; out->base_va = module_base + rva; - return 0; -} - -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(gva_ctx* ctx, const sig_pattern_t* p, uint64_t* out, int max) { - if (!p || p->len == 0) return -1; - gpa_ctx* m = &p_(ctx); - struct physcb c = { out, max, 0 }; - - const size_t seg0 = (size_t)(m->low < m->fsize ? m->low : m->fsize); - const mem_view_t v0 = { (const uint8_t*)m->pa, seg0, 0 }; - sig_each(v0, p, phys_hit, &c); - - if (m->fsize > m->low) { - const mem_view_t v1 = { (const uint8_t*)m->pa + m->low, - (size_t)(m->fsize - m->low), 1ull << 32 }; - sig_each(v1, p, phys_hit, &c); - } - return c.n; -} \ No newline at end of file diff --git a/src/sigscan.c b/src/handlers/sigscan.c similarity index 72% rename from src/sigscan.c rename to src/handlers/sigscan.c index f141ee8..db368a6 100644 --- a/src/sigscan.c +++ b/src/handlers/sigscan.c @@ -1,4 +1,4 @@ -#include "../include/sigscan.h" +#include "sigscan.h" #include #include @@ -150,49 +150,4 @@ mem_view_t mem_sub(mem_view_t v, uint64_t start_va, size_t size) { r.base_va = start_va; r.size = size < avail ? size : avail; return r; -} - -bool pe_find_section(mem_view_t v, uint64_t module_base, const char* name, - uint64_t* rva_out, uint32_t* vsize_out) { - if (!v.data || !name || module_base < v.base_va) return false; - const size_t mo = (size_t)(module_base - v.base_va); - if (mo + 0x40 > v.size) return false; - if (v.data[mo] != 'M' || v.data[mo + 1] != 'Z') return false; - - int32_t e_lfanew; - memcpy(&e_lfanew, v.data + mo + 0x3C, 4); - const size_t nt = mo + (size_t)(uint32_t)e_lfanew; - if (nt + 0x18 > v.size) return false; - if (memcmp(v.data + nt, "PE\0\0", 4) != 0) return false; - - uint16_t nsec, opt_size; - memcpy(&nsec, v.data + nt + 6, 2); /* NumberOfSections */ - memcpy(&opt_size, v.data + nt + 20, 2); /* SizeOfOptionalHeader */ - - const size_t sec = nt + 24 + opt_size; /* first section header */ - size_t want = strlen(name); - if (want > 8) want = 8; - - for (uint16_t i = 0; i < nsec; i++) { - const size_t sh = sec + (size_t)i * 40; - if (sh + 40 > v.size) break; - char nm[9] = {0}; - memcpy(nm, v.data + sh, 8); - if (strncmp(nm, name, want) == 0 && (want == 8 || nm[want] == '\0')) { - uint32_t vsize, vaddr; - memcpy(&vsize, v.data + sh + 8, 4); /* Misc.VirtualSize */ - memcpy(&vaddr, v.data + sh + 12, 4); /* VirtualAddress */ - if (rva_out) *rva_out = vaddr; - if (vsize_out) *vsize_out = vsize; - return true; - } - } - return false; -} - -bool pe_section(mem_view_t v, uint64_t module_base, const char* name, mem_view_t* out) { - uint64_t rva; uint32_t vsize; - if (!out || !pe_find_section(v, module_base, name, &rva, &vsize)) return false; - *out = mem_sub(v, module_base + rva, vsize); - return out->data != NULL; } \ No newline at end of file diff --git a/src/include/memory.h b/src/include/memory.h deleted file mode 100644 index 73ced58..0000000 --- a/src/include/memory.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef VMIE_MEMORY_H -#define VMIE_MEMORY_H -#include -#include -struct gva_ctx; /* forward: completed below; lets profile.h name it */ -#include "profile.h" - -/* 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)) - -/* Canonical VA window bounds, single-sourced here for every scanning TU. - * USER_MIN is 0x10000: Windows reserves the low 64 KiB, so no live user pointer - * targets below it - starting there drops a class of false positives. */ -#define USER_MIN 0x0000000000010000ull -#define USER_MAX 0x00007FFFFFFFFFFFull -#define KERN_MIN 0xFFFF800000000000ull - -/* Flat RW mmap of the guest RAM backing file. GPA<->file offset has one split - * at the 4 GiB PCI hole: [0,low) maps 1:1; [4G,..) maps to file [low,..). */ -typedef struct gpa_ctx { - void* pa; - uint64_t low; - size_t fsize; - int fd; -} gpa_ctx; - -/* 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 gva_ctx* aliases a gpa_ctx*. prof carried by value. */ -typedef struct gva_ctx { - gpa_ctx mem; - uint64_t kcr3; - uint64_t kbase; - uint64_t sysproc; - profile prof; -} gva_ctx; - -#define p_(c) ((c)->mem) - -/* GPA <-> file-offset converters: a single split at the 4 GiB PCI hole. - * forward (GPA -> offset) lives in gpa.c (it also carries the bounds check); - * inverse (offset -> GPA) is this static inline, shared across PT-walking TUs. */ -static inline uintptr_t offset_gpa(const gpa_ctx* ctx, uintptr_t addr) { - return addr < ctx->low ? addr : (1ull << 32) + (addr - ctx->low); -} - -/* guest-physical lifecycle + primitives (gpa.c) */ -int gpa_open (gpa_ctx* ctx, const char* path, uintptr_t low); -void gpa_close(gpa_ctx* ctx); -int gpa_read (gpa_ctx* ctx, uintptr_t offs, void* dst, size_t nmemb); -int gpa_write(gpa_ctx* ctx, uintptr_t offs, const void* src, size_t nmemb); - -/* Zero-copy access to guest physical memory. Returns a host pointer to `nmemb` - * contiguous bytes at GPA `offs`, or NULL if that range is not fully backed by - * the mapped image. Valid until the context is freed; writes hit the live guest - * (MAP_SHARED). A single 4K/2M/1G leaf never straddles the 4G split, so the - * whole leaf (or a 4096-byte page table) can be taken in one call. */ -void* gpa_ptr(gpa_ctx* ctx, uintptr_t offs, size_t nmemb); - -/* Zero-copy borrowed read: host pointer to the guest byte at `va` (under `cr3`), - * valid for *avail contiguous bytes (to the end of the containing leaf). NULL if - * `va` is not mapped or the leaf is not fully covered by the image (caller falls - * back to gva_read). Borrowed: valid until gva_ctx_free, do NOT retain/free. */ -const void* gva_ptr(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t* avail) __attribute__((hot)); - -/* bootstrap helpers (gva.c) */ -int khalf_score(const gva_ctx* ctx, uint64_t pml4) __attribute__((cold)); -int cr3_recover(gva_ctx* ctx, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) __attribute__((cold)); - -/* ---- shared windowed sweep engine (gva.c) -------------------------------- * - * gva_sweep() streams every mapped byte under `cr3` within [lo,hi] that passes - * the protection filter to `cb`, one contiguous window at a time. Physical - * fragmentation is hidden: each window is a flat buffer (gva_read-filled), and - * adjacent windows of one run share `overlap` leading bytes so an object or - * pattern straddling a window boundary is still seen whole. Both the value - * scanner and the signature scanner ride this one primitive. */ -typedef int (*gva_sweep_cb)(void* user, const uint8_t* data, size_t len, - uint64_t base_va, size_t overlap, int last); -/* user - passed through verbatim - * data - host buffer with `len` valid bytes (do not retain past the call) - * len - valid bytes at data - * base_va - guest VA of data[0] - * overlap - bytes at the front of `data` shared with the previous window of - * this run (0 on a run's first window or right after a gap) - * last - nonzero if this window ends a contiguous segment (run end / gap): - * accept hits up to `len`; otherwise drop hits starting in the - * trailing `overlap` zone, the next window re-presents them - * cb returns nonzero to abort the sweep early (e.g. result buffer full). - * - * gva_sweep() returns 0 normally, 1 if a callback aborted it, -1 on allocation - * failure. `overlap` must be < the internal window (1 MiB); patterns longer - * than that are not supported by the windowed path. */ -int gva_sweep(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi, - uint32_t prot_any, size_t overlap, gva_sweep_cb cb, void* user); - -#endif /* VMIE_MEMORY_H */ \ No newline at end of file diff --git a/src/include/profile.h b/src/include/profile.h deleted file mode 100644 index 3607c99..0000000 --- a/src/include/profile.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef VMIE_PROFILE_H -#define VMIE_PROFILE_H - -#include - -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; - -int profile_build(struct gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age); - -#endif - diff --git a/src/proc.c b/src/proc.c deleted file mode 100644 index 64041e6..0000000 --- a/src/proc.c +++ /dev/null @@ -1,112 +0,0 @@ -#include -#include -#include -#include "include/memory.h" -#include "../include/include.h" - -#define pr_(ctx) ((ctx)->prof) - -#define RING_GUARD 100000u -#define MOD_GUARD 4096u - -static void grab_ustr(gva_ctx* ctx, uintptr_t cr3, uint64_t va, gtext* out) { - uint16_t len = 0; - uint64_t buf = 0; - out->va = 0; - out->len = 0; - if (gva_read(ctx, cr3, va, &len, 2) || gva_read(ctx, cr3, va + 8, &buf, 8)) { - return; - } - out->va = buf; - out->len = len; -} - -int proc_list(gva_ctx* ctx, int skip_system, process* dst, size_t nmax) { - const profile* p = &pr_(ctx); - const uint64_t kcr3 = ctx->kcr3; - if (!kcr3 || !ctx->sysproc) { - return -1; - } - - size_t n = 0; - unsigned guard = 0; - uint64_t ep = ctx->sysproc, node; - - do { - uint64_t pid = 0, ppid = 0, dtb = 0, peb = 0; - gva_read(ctx, kcr3, ep + p->ep_pid, &pid, 8); - gva_read(ctx, kcr3, ep + p->ep_dtb, &dtb, 8); - if (p->ep_peb) { gva_read(ctx, kcr3, ep + p->ep_peb, &peb, 8); } - if (p->ep_ppid) { gva_read(ctx, kcr3, ep + p->ep_ppid, &ppid, 8); } - - if (!skip_system || peb) { - if (n >= nmax) { - return (int)n; - } - process* q = &dst[n++]; - q->eprocess = ep; - q->cr3 = dtb & PFN_MASK; - q->peb = peb; - q->pid = (uint32_t)pid; - q->ppid = p->ep_ppid ? (uint32_t)ppid : (uint32_t)-1; - q->create_time = 0; - if (p->ep_createtime) { - gva_read(ctx, kcr3, ep + p->ep_createtime, &q->create_time, 8); - } - memset(q->name, 0, sizeof q->name); - gva_read(ctx, kcr3, ep + p->ep_name, q->name, sizeof q->name - 1); - q->path.va = 0; - q->path.len = 0; - if (p->ep_imgpath) { - grab_ustr(ctx, kcr3, ep + p->ep_imgpath, &q->path); /* read text under kcr3 */ - } - } - - if (gva_read(ctx, kcr3, ep + p->ep_links, &node, 8)) { - break; - } - ep = node - p->ep_links; - } while (ep != ctx->sysproc && ++guard < RING_GUARD); - - return (int)n; -} - -int proc_modules(gva_ctx* ctx, const process* pr, pmodule* dst, size_t nmax) { - const profile* p = &pr_(ctx); - const uint64_t cr3 = pr->cr3; - if (!pr->peb || !cr3) { - return 0; - } - - uint64_t ldr = 0, head, link; - if (gva_read(ctx, cr3, pr->peb + p->peb_ldr, &ldr, 8) || !ldr) { - return 0; - } - head = ldr + p->ldr_loadlist; - if (gva_read(ctx, cr3, head, &link, 8)) { - return 0; - } - - size_t n = 0; - unsigned guard = 0; - while (link != head && n < nmax && ++guard < MOD_GUARD) { - const uint64_t entry = link; /* InLoadOrderLinks at offset 0 of the entry */ - uint64_t base = 0; - uint32_t size = 0; - gva_read(ctx, cr3, entry + p->lde_base, &base, 8); - gva_read(ctx, cr3, entry + p->lde_size, &size, 4); - - pmodule* m = &dst[n++]; - m->pr = pr; - m->entry = entry; - m->base = base; - m->size = size; - grab_ustr(ctx, cr3, entry + p->lde_name, &m->name); - grab_ustr(ctx, cr3, entry + p->lde_fullname, &m->path); - - if (gva_read(ctx, cr3, link, &link, 8)) { - break; - } - } - return (int)n; -} \ No newline at end of file