Split the library into CORE / ENGINE / HANDLERS layers

CORE (src/core): vmie_mem — guest-physical substrate with a data-driven
segment map (replaces the hardcoded 4 GiB PCI-hole topology). ENGINE
(src/engine): x86-64 paging + Windows bring-up; produces the generic memory
model. HANDLERS (src/handlers): the signature/value/pointer scanners, which
now consume an OS-agnostic contract.

Keystone: gva_ctx is split into vmie_mem (core) + vmie (engine); the generic
access functions take vmie_mem* + cr3 and no longer compile in the Windows
offset table. New public contract include/memmodel.h (vmie_mem, mem_view_t,
vregion, task, range, the gva_* access); win32 surface in include/vmie.h.
Leak relocations: the PE parser, UTF-16 decode and CR3-recovery heuristics
move engine-side; the matcher stays a pure, source-agnostic handler, and the
pointer scanner takes a generic range[] instead of reaching into the process
enumerator.
This commit is contained in:
2026-06-15 02:57:46 +03:00
parent 7c0995a4f2
commit b3441dd6f6
24 changed files with 1014 additions and 766 deletions
+41
View File
@@ -0,0 +1,41 @@
#include "contract.h"
#include <windows.h>
#ifndef ACK_POLL_MS
#define ACK_POLL_MS 5u
#endif
#ifndef ACK_TIMEOUT_MS
#define ACK_TIMEOUT_MS (120*1000u)
#endif
int main(void) {
contract* c = VirtualAlloc(NULL, sizeof *c, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
uint32_t timeout = ACK_TIMEOUT_MS;
if (c == NULL) {
return 1;
}
VirtualLock(c, sizeof *c);
c->va_self = (uint64_t)(uintptr_t)c;
c->ack = 0;
c->magic1 = CONTRACT_MAGIC1;
MemoryBarrier();
c->magic0 = CONTRACT_MAGIC0;
do {
if (*(volatile uint64_t*)&c->ack == CONTRACT_ACK) {
c->magic0 = 0;
c->magic1 = 0;
VirtualUnlock(c, sizeof *c);
VirtualFree(c, 0, MEM_RELEASE);
break;
}
Sleep(ACK_POLL_MS);
} while (timeout -= ACK_POLL_MS);
return timeout > 0 ? 0 : 255;
}
+327
View File
@@ -0,0 +1,327 @@
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "engine.h"
/* PTE permission bits we propagate down the walk. */
#define PTE_RW (1ull << 1)
#define PTE_US (1ull << 2)
#define PTE_NX (1ull << 63)
/* ---- single-address translation (hot) ----------------------------------- *
* 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(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(m, t + i4 * 8, 8)) || !((e = *pe) & PG_P)) return -1;
t = e & PFN_MASK;
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;
if (leaf) *leaf = (1u << 30) - off;
return 0;
}
t = e & PFN_MASK;
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;
if (leaf) *leaf = (1u << 21) - off;
return 0;
}
t = e & PFN_MASK;
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 engine.h). */
__attribute__((hot))
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(m, cr3, va, &gpa, &leaf)) return NULL;
*avail = leaf;
return gpa_ptr(m, gpa, leaf);
}
__attribute__((hot))
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(m, cr3, va, &gpa, &leaf)) return -1;
const size_t n = leaf < nmemb ? leaf : nmemb;
if (gpa_read(m, gpa, d, n)) return -1;
va += n; d += n; nmemb -= n;
}
return 0;
}
__attribute__((hot))
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(m, cr3, va, &gpa, &leaf)) return -1;
const size_t n = leaf < nmemb ? leaf : nmemb;
if (gpa_write(m, gpa, s, n)) return -1;
va += n; s += n; nmemb -= n;
}
return 0;
}
/* ---- bootstrap helpers (cold) -------------------------------------------- */
__attribute__((cold))
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((vmie_mem*)m, t + i * 8, &e, 8) && (e & PG_P)) n++;
return n;
}
__attribute__((cold))
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 <= m->fsize; off += 0x1000) {
const uintptr_t cand = offset_gpa(m, off);
uintptr_t gpa;
if (gva_gpa(m, cand, va_self, &gpa, NULL)) continue;
if ((gpa & ~0xFFFull) != (target_pa & ~0xFFFull)) continue;
const int score = khalf_score(m, cand);
if (score > best_score) { best_score = score; best = cand; }
}
if (best_score < 0) return -1;
*cr3_out = best;
return 0;
}
/* ---- lifecycle (cold) ---------------------------------------------------- */
__attribute__((cold))
vmie* vmie_open(const char* ram_path, uint64_t low) {
vmie* v = calloc(1, sizeof *v);
if (!v) {
return NULL;
}
if (gpa_open(&v->mem, ram_path, low)) {
free(v);
return NULL;
}
return v;
}
__attribute__((cold))
void vmie_close(vmie* v) {
if (!v) {
return;
}
gpa_close(&v->mem);
free(v);
}
/* ---- region enumeration -------------------------------------------------- */
struct rgn_acc {
vregion* out; int nmax; int n;
uint32_t prot_any;
uint64_t lo, hi;
int have; uint64_t va, len; uint32_t prot;
};
static void rgn_flush(struct rgn_acc* a) {
if (!a->have) return;
if (a->prot_any == 0 || (a->prot & a->prot_any)) {
if (a->n < a->nmax) {
a->out[a->n].va = a->va; a->out[a->n].len = a->len; a->out[a->n].prot = a->prot;
}
a->n++;
}
a->have = 0;
}
/* Clamp a present leaf to [lo,hi] and coalesce it onto the current run. */
static void rgn_leaf(struct rgn_acc* a, uint64_t va, uint64_t size, uint32_t prot) {
uint64_t vend = va + size - 1; /* inclusive last byte */
if (vend < a->lo || va > a->hi) return; /* outside window */
if (va < a->lo) va = a->lo;
if (vend > a->hi) vend = a->hi;
const uint64_t len = vend - va + 1;
if (a->have && prot == a->prot && va == a->va + a->len) {
a->len += len; /* extend current run */
} else {
rgn_flush(a);
a->have = 1; a->va = va; a->len = len; a->prot = prot;
}
}
static uint32_t rgn_prot(int rw, int us, int nx) {
return VR_R | (rw ? VR_W : 0) | (nx ? 0 : VR_X) | (us ? VR_U : 0);
}
/* whole-subtree window test: does [base, base+span) intersect [lo,hi]? */
static int rgn_hit(uint64_t base, uint64_t span, uint64_t lo, uint64_t hi) {
const uint64_t end = base + (span - 1); /* inclusive */
return !(end < lo || base > hi);
}
__attribute__((hot))
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(m, cr3 & PFN_MASK, 4096);
if (!t4) return 0;
for (int i4 = 0; i4 < 512; i4++) {
const uint64_t e4 = t4[i4];
if (!(e4 & PG_P)) continue;
const uint64_t b4 = VA_CANON((uint64_t)i4 << 39);
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(m, e4 & PFN_MASK, 4096);
if (!t3) continue;
for (int i3 = 0; i3 < 512; i3++) {
const uint64_t e3 = t3[i3];
if (!(e3 & PG_P)) continue;
const uint64_t b3 = VA_CANON(((uint64_t)i4 << 39) | ((uint64_t)i3 << 30));
if (!rgn_hit(b3, 1ull << 30, lo, hi)) continue;
const int rw3 = rw4 & ((e3 >> 1) & 1), us3 = us4 & ((e3 >> 2) & 1),
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(m, e3 & PFN_MASK, 4096);
if (!t2) continue;
for (int i2 = 0; i2 < 512; i2++) {
const uint64_t e2 = t2[i2];
if (!(e2 & PG_P)) continue;
const uint64_t b2 = VA_CANON(((uint64_t)i4 << 39) | ((uint64_t)i3 << 30) | ((uint64_t)i2 << 21));
if (!rgn_hit(b2, 1ull << 21, lo, hi)) continue;
const int rw2 = rw3 & ((e2 >> 1) & 1), us2 = us3 & ((e2 >> 2) & 1),
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(m, e2 & PFN_MASK, 4096);
if (!t1) continue;
for (int i1 = 0; i1 < 512; i1++) {
const uint64_t e1 = t1[i1];
if (!(e1 & PG_P)) continue;
const uint64_t b1 = VA_CANON(((uint64_t)i4 << 39) | ((uint64_t)i3 << 30) |
((uint64_t)i2 << 21) | ((uint64_t)i1 << 12));
if (!rgn_hit(b1, 1ull << 12, lo, hi)) continue;
const int rw1 = rw2 & ((e1 >> 1) & 1), us1 = us2 & ((e1 >> 2) & 1),
nx1 = nx2 | ((int)(e1 >> 63) & 1);
rgn_leaf(&a, b1, 1ull << 12, rgn_prot(rw1, us1, nx1));
}
}
}
}
rgn_flush(&a);
return a.n;
}
/* ---- windowed sweep engine ----------------------------------------------- */
#define SWEEP_WIN (1u << 20) /* 1 MiB window (multiple of 8) */
#define SWEEP_RMAX (1u << 16) /* max runs enumerated per sweep */
__attribute__((hot))
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;
vregion* rg = malloc((size_t)SWEEP_RMAX * sizeof *rg);
uint8_t* buf = malloc(SWEEP_WIN);
if (!rg || !buf) { free(rg); free(buf); return -1; }
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;
for (int r = 0; r < nr && !rc; r++) {
uint64_t base = rg[r].va; /* VA of buf[0] */
uint64_t va = rg[r].va;
const uint64_t vend = rg[r].va + (rg[r].len - 1); /* inclusive last */
size_t fill = 0;
while (va <= vend) {
size_t 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 */
va += 0x1000 - (va & 0xFFF);
base = va; fill = 0;
continue;
}
size_t n = avail; /* leaf-contiguous */
if (n > (size_t)(vend - va + 1)) n = (size_t)(vend - va + 1);
if (n > SWEEP_WIN - fill) n = SWEEP_WIN - fill;
const int end = (n == (size_t)(vend - va + 1)); /* chunk hits vend */
if (fill == 0 && avail > 0x1000) { /* large-page lend */
if (cb(user, p, n, va, 0, end)) { rc = 1; break; }
if (end) break; /* avoid va wrap */
va += n;
if (overlap == 0) base = va;
else { memcpy(buf, p + n - overlap, overlap); base = va - overlap; fill = overlap; }
continue;
}
memcpy(buf + fill, p, n); /* buffered window */
fill += n; va += n;
if (end || fill == SWEEP_WIN) {
if (cb(user, buf, fill, base, overlap, end)) { rc = 1; break; }
if (end) { fill = 0; break; } /* avoid va wrap */
if (overlap == 0 || overlap >= fill) {
base = va; fill = 0;
} else { /* carry overlap */
memmove(buf, buf + fill - overlap, overlap);
base = va - overlap; fill = overlap;
}
}
}
if (!rc && fill && cb(user, buf, fill, base, overlap, 1)) rc = 1;
}
free(rg); free(buf);
return rc;
}
/* ---- 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;
}
+209
View File
@@ -0,0 +1,209 @@
#include <string.h>
#include <stddef.h>
#include "vmie.h"
#include "contract.h"
#include "engine.h"
#define MZ 0x5A4Du
#define DIR_EXPORT 0u
#define DIR_DEBUG 6u
#define DBG_CODEVIEW 2u
#define CV_RSDS 0x53445352u
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(m, ptr - m->pa);
*va = c->va_self;
return 0;
}
ptr += 1ull<<12; /* 4KB step: a locked, page-granular beacon */
} while (ptr < end);
return -1;
}
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(m, cr3, base + 0x3C, &lfanew, 4)) {
return -1;
}
const uint64_t dd = base + lfanew + 0x18 + 0x70 + (uint64_t)idx*8;
if (gva_read(m, cr3, dd, rva, 4)) {
return -1;
}
return (size && gva_read(m, cr3, dd + 4, size, 4)) ? -1 : 0;
}
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(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(m, cr3, base + dbg_rva + o + 0x0C, &type, 4)) {
return -1;
}
if (type != DBG_CODEVIEW) {
continue;
}
if (gva_read(m, cr3, base + dbg_rva + o + 0x14, &cv_rva, 4)) { /* AddressOfRawData RVA */
return -1;
}
if (gva_read(m, cr3, base + cv_rva, &sig, 4) || sig != CV_RSDS) {
return -1;
}
if (gva_read(m, cr3, base + cv_rva + 0x04, guid, 16)) {
return -1;
}
if (gva_read(m, cr3, base + cv_rva + 0x14, age, 4)) {
return -1;
}
gva_read(m, cr3, base + cv_rva + 0x18, name, namecap); /* best-effort */
name[namecap - 1] = 0;
return 0;
}
return -1;
}
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(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(m, pdpt + p3*8, &e3, 8) || !(e3 & PG_P)) {
continue;
}
if (e3 & PG_PS) {
continue; /* 1G leaf -- no PE image here */
}
const uint64_t pd = e3 & PFN_MASK;
for (int p2 = 0; p2 < 512; p2++) {
uint64_t e2;
if (gpa_read(m, pd + p2*8, &e2, 8) || !(e2 & PG_P)) {
continue;
}
uint64_t va = (uint64_t)p4<<39 | (uint64_t)p3<<30 | (uint64_t)p2<<21;
va = VA_CANON(va);
uint16_t mz; char pdb[16] = {0};
if (gva_read(m, cr3, va, &mz, 2) || mz != MZ) {
continue;
}
if (pe_pdb(m, cr3, va, guid, age, pdb, sizeof pdb)) {
continue;
}
if (strncmp(pdb, "ntkrnlmp.pdb", 12) != 0) {
continue;
}
*base = va;
return 0;
}
}
}
return -1;
}
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(m, cr3, kbase, DIR_EXPORT, &exp_rva, NULL) || !exp_rva) {
return 0;
}
uint8_t ed[40];
if (gva_read(m, cr3, kbase + exp_rva, ed, sizeof ed)) {
return 0;
}
uint32_t nnames, a_funcs, a_names, a_ords;
memcpy(&nnames, ed + 0x18, 4);
memcpy(&a_funcs, ed + 0x1C, 4);
memcpy(&a_names, ed + 0x20, 4);
memcpy(&a_ords, ed + 0x24, 4);
for (uint32_t i = 0; i < nnames; i++) {
uint32_t nrva; char nm[40];
if (gva_read(m, cr3, kbase + a_names + i*4, &nrva, 4)) {
return 0;
}
if (gva_read(m, cr3, kbase + nrva, nm, sizeof nm)) {
continue;
}
nm[sizeof nm - 1] = 0;
if (strcmp(nm, want) != 0) {
continue;
}
uint16_t ord; uint32_t frva;
if (gva_read(m, cr3, kbase + a_ords + i*2, &ord, 2)) {
return 0;
}
return gva_read(m, cr3, kbase + a_funcs + ord*4, &frva, 4) ? 0 : frva;
}
return 0;
}
static void beacon_ack(vmie_mem* m, uint64_t anchor_pa) {
uint64_t ack = CONTRACT_ACK;
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(vmie* v) {
vmie_mem* m = &v->mem;
uint64_t anchor_pa, va_self;
uintptr_t cr3boot;
uint32_t rva;
uint8_t guid[16];
uint32_t age;
uint64_t sys_ep;
if (beacon_find(m, &anchor_pa, &va_self)) {
return -1;
}
if (cr3_recover(v, va_self, anchor_pa, &cr3boot)) {
return -2;
}
if (find_ntoskrnl(m, cr3boot, &v->kbase, guid, &age)) {
return -3;
}
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(v, cr3boot, sys_ep, guid, age)) {
return -5;
}
uint64_t dtb;
if (gva_read(m, cr3boot, sys_ep + v->prof.ep_dtb, &dtb, 8)) {
return -6;
}
v->kcr3 = dtb & PFN_MASK;
v->sysproc = sys_ep;
beacon_ack(m, anchor_pa);
return 0;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef CONTRACT_MAGIC0
#define CONTRACT_MAGIC0 0x3A7C1E94B2D6F058ull
#endif
#ifndef CONTRACT_MAGIC1
#define CONTRACT_MAGIC1 0x9F41D80E6BC57A23ull
#endif
#ifndef CONTRACT_ACK
#define CONTRACT_ACK 0xACED5EEDACED5EEDull
#endif
#ifndef VMIE_CONTRACT_H
#define VMIE_CONTRACT_H
#include <stdint.h>
#pragma pack(push, 1)
typedef struct {
uint64_t magic0;
uint64_t magic1;
uint64_t va_self;
uint64_t ack;
} contract;
#pragma pack(pop)
#endif
+68
View File
@@ -0,0 +1,68 @@
#ifndef VMIE_ENGINE_H
#define VMIE_ENGINE_H
#include <stdint.h>
#include <stddef.h>
#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 */
+46
View File
@@ -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 <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#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 */
+63
View File
@@ -0,0 +1,63 @@
#include "pe.h"
#include <string.h>
#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;
}
+166
View File
@@ -0,0 +1,166 @@
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#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;
}
+286
View File
@@ -0,0 +1,286 @@
#include <stdint.h>
#include <string.h>
#include "engine.h"
#define pr_(v) ((v)->prof)
#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
static int canon_ok(uint64_t p, int kernel) {
return kernel ? (p >= KERN_MIN) : (p >= USER_MIN && p <= USER_MAX);
}
/* Circular LIST_ENTRY walker (Flink at node+0); one primitive for both rings. */
static int list_ring_ok(vmie* v, uintptr_t cr3, uint64_t head, int kernel) {
vmie_mem* m = &v->mem;
uint64_t node;
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(m, cr3, node, &node, 8)) {
return 0;
}
}
return 0;
}
/* Pass 1: ep_name/ep_pid/ep_links/ep_dtb from the System _EPROCESS. */
static int discover_core(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(m, cr3, sys_ep, buf, sizeof buf)) {
return -1;
}
int name_off = -1;
for (int o = 0x100; o + 7 <= (int)sizeof buf; o++) {
if (!memcmp(buf + o, "System", 6) && buf[o + 6] == 0) {
name_off = o;
break;
}
}
if (name_off < 0) {
return -2;
}
p->ep_name = (uint16_t)name_off;
int pid_off = -1;
for (int o = 0x80; o + 8 <= name_off; o += 8) {
uint64_t val; memcpy(&val, buf + o, 8);
if (val != 4) {
continue;
}
const uint16_t links = (uint16_t)(o + 8);
if (list_ring_ok(v, cr3, sys_ep + links, 1)) {
p->ep_links = links;
pid_off = o;
break;
}
}
if (pid_off < 0) {
return -3;
}
p->ep_pid = (uint16_t)pid_off;
int dtb_off = -1;
for (int o = 0x18; o <= 0x60; o += 8) {
uint64_t val; memcpy(&val, buf + o, 8);
const uint64_t c = val & PFN_MASK;
uint8_t probe;
if (c && khalf_score(m, c) >= 16 && !gva_read(m, c, sys_ep, &probe, 1)) {
dtb_off = o;
break;
}
}
if (dtb_off < 0) {
return -4;
}
p->ep_dtb = (uint16_t)dtb_off;
return 0;
}
/* Transient snapshot of (eprocess, pid, cr3) over the active ring. */
static int collect_procs(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(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(m, cr3, ep + p->ep_links, &node, 8)) {
break;
}
ep = node - p->ep_links;
} while (ep != sys_ep);
return n;
}
/* Pass 2a: ep_ppid by population (creator PID). Best-effort. */
static void discover_ppid(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(m, cr3, eps[i] + o, &cand, 4) || !cand || cand == pids[i]) {
continue;
}
for (int j = 0; j < n; j++) {
if (pids[j] == cand) { hits++; break; }
}
}
if (hits > best_hits) {
best_hits = hits;
best_off = o;
}
}
if (best_off >= 0 && best_hits * 3 >= n) {
pr_(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(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(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) {
ok = 0; break;
}
}
if (ok) {
pr_(v).ep_createtime = (uint16_t)o;
return;
}
}
}
/* Pass 2c: ep_imgpath (ImageFilePathHint) -- UNICODE_STRING whose tail equals the
* process's untruncated ImageFileName; probe short-named (<15) procs only. Best-effort. */
static void discover_imgpath(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(m, cr3, eps[i] + p->ep_name, nm, 15)) {
continue;
}
const size_t nl = strnlen(nm, 15);
if (nl == 0 || nl >= 15) {
continue;
}
for (int o = 0x400; o <= 0x600; o += 8) {
uint16_t len = 0;
uint64_t buf = 0;
if (gva_read(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(m, cr3, buf + len - (uint64_t)nl * 2, w, nl * 2)) {
continue;
}
int match = 1;
for (size_t c = 0; c < nl; c++) {
if ((w[c] < 0x80 ? (char)w[c] : 0) != nm[c]) { match = 0; break; }
}
if (match) {
p->ep_imgpath = (uint16_t)o;
return;
}
}
}
}
/* Pass 2d: ep_peb + user PEB/Ldr chain; commits the x64-invariant LDR offsets
* (incl. FullDllName) after validating them on the live first entry. */
static int discover_user_chain(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];
if (!pcr3) {
continue;
}
for (int po = 0x280; po <= 0x580; po += 8) {
uint64_t 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(m, pcr3, peb + lo, &ldr, 8) || !canon_ok(ldr, 0)) {
continue;
}
for (int ll = 0x10; ll <= 0x20; ll += 8) {
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(m, pcr3, ldr + ll, &entry, 8)) {
continue;
}
if (gva_read(m, pcr3, entry + 0x30, &dllbase, 8) ||
!canon_ok(dllbase, 0) || (dllbase & 0xFFF)) {
continue;
}
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(m, pcr3, entry + 0x48, &flen, 2) || (flen & 1) ||
gva_read(m, pcr3, entry + 0x48 + 8, &fbufp, 8) || !canon_ok(fbufp, 0)) {
continue;
}
p->ep_peb = (uint16_t)po;
p->peb_ldr = (uint16_t)lo;
p->ldr_loadlist = (uint16_t)ll;
p->lde_base = 0x30;
p->lde_size = 0x40;
p->lde_fullname = 0x48;
p->lde_name = 0x58;
return 0;
}
}
}
}
return -1;
}
__attribute__((cold))
int profile_build(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(v, cr3, sys_ep)) {
return -1;
}
uint64_t eps[SCAN_MAX], cr3s[SCAN_MAX];
uint32_t pids[SCAN_MAX];
const int n = collect_procs(v, cr3, sys_ep, eps, pids, cr3s, SCAN_MAX);
if (n <= 1) {
return -2;
}
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;
}
+53
View File
@@ -0,0 +1,53 @@
#include <stdint.h>
#include <stddef.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;
if (cp < 0x80) { b[0]=(uint8_t)cp; k=1; }
else if (cp < 0x800) { b[0]=0xC0|(uint8_t)(cp>>6); b[1]=0x80|(cp&0x3F); k=2; }
else if (cp < 0x10000) { b[0]=0xE0|(uint8_t)(cp>>12); b[1]=0x80|((cp>>6)&0x3F); b[2]=0x80|(cp&0x3F); k=3; }
else { b[0]=0xF0|(uint8_t)(cp>>18); b[1]=0x80|((cp>>12)&0x3F); b[2]=0x80|((cp>>6)&0x3F); b[3]=0x80|(cp&0x3F); k=4; }
if (dst && *need + k < size) {
for (size_t j = 0; j < k; j++) dst[*need + j] = (char)b[j];
*wrote = *need + k; /* end of last full code point */
}
*need += k;
}
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;
nmemb &= ~(size_t)1;
while (nmemb) {
size_t chunk = nmemb < sizeof stage ? nmemb : sizeof stage;
if (gva_read(m, cr3, va, stage, chunk)) {
break;
}
const size_t units = chunk / 2;
for (size_t i = 0; i < units; i++) {
uint32_t u = stage[i];
if (hi) {
if (u >= 0xDC00 && u <= 0xDFFF) {
utf8_emit(0x10000u + ((hi - 0xD800u) << 10) + (u - 0xDC00u), dst, size, &need, &wrote);
hi = 0;
continue;
}
utf8_emit(0xFFFD, dst, size, &need, &wrote);
hi = 0;
}
if (u >= 0xD800 && u <= 0xDBFF) hi = u;
else if (u >= 0xDC00 && u <= 0xDFFF) utf8_emit(0xFFFD, dst, size, &need, &wrote);
else utf8_emit(u, dst, size, &need, &wrote);
}
va += chunk;
nmemb -= chunk;
}
if (hi) utf8_emit(0xFFFD, dst, size, &need, &wrote);
if (dst && size) dst[need < size ? need : wrote] = 0;
return need;
}