Add PE section enumeration and section views (section-local / RVA / absolute)

vmie_win32_sections lists a module's PE sections (name, RVA, virtual size,
VR_* protection) for any image base in a process address space - including a
base found by scanning, not only loader-list modules. vmie_win32_section_view
gathers a section's bytes into a caller buffer and returns a mem_view_t whose
base_va is chosen by view_base: SECTION_LOCAL (0, section-relative offsets),
MODULE_RVA (ASLR-stable module RVAs), or ABSOLUTE_VA (live VA). Because the pure
scanners report base_va + offset, the mode directly selects the coordinate space
of every hit - feeding a view to sig_all or x86_decode yields section-relative,
RVA, or absolute results with no extra work.

The MZ/PE header walk is factored into one helper that both pe_find_section and
the new enumerator share - no second parser. The whole public surface is
documented with the operational nuances (coordinate stability, borrowed-buffer
lifetime, truncation, residency) and worked examples.
This commit is contained in:
2026-06-16 19:06:59 +03:00
parent 3199fbf258
commit 06230ac680
3 changed files with 273 additions and 11 deletions
+121
View File
@@ -146,4 +146,125 @@ scan* scan_new(vmie_win32* v, const process* pr, scan_type t, const void* value,
int vmie_scan_pointer(vmie_win32* v, const process* pr, uint64_t target,
int max_depth, uint32_t max_off, scan_ptr_path* out, int max);
/* ---- PE sections + section views ----------------------------------------- *
* A section is a PE-image concept, so it is keyed by (vmie_win32*, cr3,
* module_base): the address space and where the image is based in it. The
* module need NOT be in the loader list - any valid PE base works, including
* one found by scanning for MZ/PE (a manually-mapped or hidden module). */
/* Coordinate space of a section view's mem_view_t.base_va. The pure scanners
* (sig_all/sig_each, x86_decode callers, ...) report every result as
* base_va + offset, so this enum decides what coordinate the hits/targets come
* back in:
* SECTION_LOCAL - base_va = 0 => results are section-relative
* offsets [0, vsize). The most stable form: independent of
* the image base AND of where the section sits in the image.
* Use when you only care about positions inside one section.
* MODULE_RVA - base_va = section RVA => results are module-relative (RVA).
* ASLR-stable across runs of the same binary; the canonical
* form for portable signatures and for correlating across
* sections of one module. Recommended for sig-gen.
* ABSOLUTE_VA - base_va = module_base + RVA => results are live guest
* virtual addresses, valid for gva_read/gva_ptr under this
* cr3 NOW. NOT stable across runs (ASLR). Use when you must
* dereference a hit immediately in the live process. */
typedef enum { SECTION_LOCAL, MODULE_RVA, ABSOLUTE_VA } view_base;
/* One PE section, as enumerated by vmie_win32_sections.
* name - section name, NUL-terminated; PE names are <= 8 bytes (name[8] is
* the NUL slot). Names are NOT unique in a malformed/packed image -
* prefer iterating by index over matching by name.
* rva - section RVA: byte offset of the section from the module base
* (so ABSOLUTE_VA = module_base + rva).
* vsize - virtual size in bytes (the in-memory size; may exceed the on-disk
* raw size). Size the buffer for vmie_win32_section_view from THIS.
* prot - effective protection as VR_* flags (VR_R/VR_W/VR_X), derived from
* the section Characteristics (IMAGE_SCN_MEM_READ/WRITE/EXECUTE).
* VR_U is never set: these are image semantics, not live PTE rights. */
typedef struct { char name[9]; uint32_t rva; uint32_t vsize; uint32_t prot; } section_desc;
/* Enumerate the sections of the PE image based at `module_base` in the address
* space `cr3`.
* v - engine handle
* cr3 - the process address space the module is mapped in (e.g. a
* process->cr3). The module need NOT be in the loader list -
* any valid PE base works (e.g. one found by scanning for
* MZ/PE, i.e. a manually-mapped / hidden module).
* module_base - image base VA; only the PE headers (first page) must be
* resident and readable under `cr3`.
* out, max - caller array receiving up to `max` section_desc; `out` may be
* NULL to count only (then `max` is ignored).
* Returns the TOTAL section count (may exceed `max` => enlarge and retry), or
* -1 if the headers are absent/unreadable or `module_base` is not a PE.
*
* The returned `rva`/`vsize` are ASLR-independent (image-relative): stable
* across runs of the same binary. The absolute placement is module_base + rva.
*
* Example - list the sections of the first module of a process:
* pmodule m; proc_modules(v, pr, &m, 1);
* section_desc s[32];
* int n = vmie_win32_sections(v, pr->cr3, m.base, s, 32);
* for (int i = 0; i < n && i < 32; i++)
* printf("%-8s rva=%#x vsize=%#x %c%c%c\n", s[i].name, s[i].rva, s[i].vsize,
* (s[i].prot & VR_R) ? 'R' : '-', (s[i].prot & VR_W) ? 'W' : '-',
* (s[i].prot & VR_X) ? 'X' : '-'); */
int vmie_win32_sections(vmie_win32* v, uint64_t cr3, uint64_t module_base,
section_desc* out, int max);
/* Gather a section's bytes from the live process into `buf` and return a flat
* mem_view_t over them in the coordinate space chosen by `mode`. This is the
* "section memory, addressed from 0" entry point.
* v, cr3, module_base - as in vmie_win32_sections.
* sec - the section to open (from vmie_win32_sections; carries
* rva/vsize). Must be non-NULL.
* mode - view_base: sets out->base_va (see view_base).
* SECTION_LOCAL => 0, MODULE_RVA => sec->rva,
* ABSOLUTE_VA => module_base + sec->rva.
* buf, bufcap - caller-owned destination; size it to sec->vsize (from
* enumeration). If bufcap < sec->vsize the section is TRUNCATED:
* out->size = bufcap (this is NOT an error). The returned view
* BORROWS buf - it is valid only while `buf` lives and is left
* unmodified; the library retains no pointer past this call.
* out - on success: out->data = buf, out->size = min(sec->vsize,
* bufcap), out->base_va per `mode`. Must be non-NULL.
* Returns 0 on success, or -1 if `sec`/`buf`/`out` is NULL, the headers are
* unreadable, or the section bytes are not fully resident (paged out / sparse)
* so the read fails.
*
* Reversing nuance: .text/.rdata are normally fully resident; a section that is
* partly paged out yields -1 - re-try when resident, or sweep the live VA range
* with gva_sweep instead. The base-mode also picks the coordinate stability:
* SECTION_LOCAL/MODULE_RVA are ASLR-stable (offset / RVA), ABSOLUTE_VA is the
* live VA for this run only.
*
* Example - find an IDA pattern in .text as RVAs (ASLR-stable across runs):
* section_desc s[32];
* int n = vmie_win32_sections(v, cr3, base, s, 32);
* for (int i = 0; i < n && i < 32; i++) if (!strcmp(s[i].name, ".text")) {
* uint8_t* buf = malloc(s[i].vsize);
* mem_view_t tv;
* if (vmie_win32_section_view(v, cr3, base, &s[i], MODULE_RVA,
* buf, s[i].vsize, &tv) == 0) {
* sig_pattern_t p; sig_parse_ida("48 8B 05 ? ? ? ?", &p);
* uint64_t rvas[64];
* int h = sig_all(tv, &p, rvas, 64); // rvas[] are module RVAs, stable
* sig_free(&p);
* }
* free(buf);
* }
*
* Example - step instructions section-locally (offset 0 == section start):
* mem_view_t tv;
* vmie_win32_section_view(v, cr3, base, &text, SECTION_LOCAL,
* buf, text.vsize, &tv);
* for (size_t off = 0; off < tv.size; ) {
* x86_insn in;
* int len = x86_decode(tv.data + off, tv.size - off, &in); // off == section-local addr
* if (len <= 0) { off++; continue; }
* off += (size_t)len;
* } */
int vmie_win32_section_view(vmie_win32* v, uint64_t cr3, uint64_t module_base,
const section_desc* sec, view_base mode,
uint8_t* buf, size_t bufcap, mem_view_t* out);
#endif /* VMIE_WIN32_H */