mirror of
https://dev.lirent.ru/Vatrog/vm-introspection-engine.git
synced 2026-06-18 00:56:37 +03:00
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:
+121
@@ -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 */
|
||||
|
||||
@@ -14,6 +14,23 @@
|
||||
#include <stdbool.h>
|
||||
#include "memmodel.h" /* mem_view_t, vmie_mem */
|
||||
|
||||
/* One enumerated PE section header, decoded by pe_sections. Mirrors the public
|
||||
* win32 section_desc, but stays engine-private (this header is engine-only).
|
||||
* name - section name, NUL-terminated (PE names are <= 8 bytes; name[8] NUL)
|
||||
* rva - section RVA (relative to module_base)
|
||||
* vsize - virtual size in bytes
|
||||
* prot - VR_R/VR_W/VR_X from the section Characteristics (VR_U never set) */
|
||||
typedef struct { char name[9]; uint32_t rva; uint32_t vsize; uint32_t prot; } pe_secrec;
|
||||
|
||||
/* Enumerate the section headers of the PE image based at `module_base` inside a
|
||||
* view holding at least the image headers (the first page is enough).
|
||||
* out, max - caller array receiving up to `max` pe_secrec; out may be NULL to
|
||||
* count only. Headers truncated by the view end are not reported.
|
||||
* Returns the TOTAL section count (may exceed `max`), or -1 if `v` does not hold
|
||||
* a parseable PE at `module_base`. Shares the section-table walk with
|
||||
* pe_find_section (one header parser, no duplication). */
|
||||
int pe_sections(mem_view_t v, uint64_t module_base, pe_secrec* out, int max);
|
||||
|
||||
/* 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`
|
||||
|
||||
+135
-11
@@ -1,12 +1,30 @@
|
||||
#include "pe.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "memmodel.h" /* gva_read */
|
||||
#include "memmodel.h" /* gva_read, VR_* */
|
||||
#include "sigscan.h" /* mem_sub (pure matcher; engine may use it) */
|
||||
#include "win32.h" /* public surface: vmie_win32, section_desc, view_base */
|
||||
|
||||
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;
|
||||
/* IMAGE_SECTION_HEADER: 8-byte Name, then Misc.VirtualSize(+8), VirtualAddress
|
||||
* (+12), and Characteristics(+36); the header is 40 bytes wide. */
|
||||
#define SH_SIZE 40u
|
||||
#define SH_VSIZE_OFF 8u
|
||||
#define SH_VADDR_OFF 12u
|
||||
#define SH_CHAR_OFF 36u
|
||||
|
||||
/* IMAGE_SCN_MEM_* protection bits in IMAGE_SECTION_HEADER.Characteristics. */
|
||||
#define SCN_MEM_EXECUTE 0x20000000u
|
||||
#define SCN_MEM_READ 0x40000000u
|
||||
#define SCN_MEM_WRITE 0x80000000u
|
||||
|
||||
/* Common PE section-table walk: validate the MZ/PE headers reachable inside `v`
|
||||
* at `module_base`, then locate the section-header array. On success fills
|
||||
* *sec_off (byte offset into v.data of the first IMAGE_SECTION_HEADER) and
|
||||
* *nsec (NumberOfSections), and returns true. This is the single header parse
|
||||
* shared by pe_find_section and pe_sections - there is no second PE parser. */
|
||||
static bool pe_section_table(mem_view_t v, uint64_t module_base,
|
||||
size_t* sec_off, uint16_t* nsec) {
|
||||
if (!v.data || 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;
|
||||
@@ -17,23 +35,42 @@ bool pe_find_section(mem_view_t v, uint64_t module_base, const char* name,
|
||||
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 */
|
||||
uint16_t n, opt_size;
|
||||
memcpy(&n, 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 */
|
||||
*sec_off = nt + 24 + opt_size; /* first section header */
|
||||
*nsec = n;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Map IMAGE_SCN_MEM_READ/WRITE/EXECUTE -> VR_R/VR_W/VR_X. Image semantics, not
|
||||
* live PTEs: VR_U is never set here (see section_desc.prot in win32.h). */
|
||||
static uint32_t pe_prot(uint32_t characteristics) {
|
||||
uint32_t prot = 0;
|
||||
if (characteristics & SCN_MEM_READ) { prot |= VR_R; }
|
||||
if (characteristics & SCN_MEM_WRITE) { prot |= VR_W; }
|
||||
if (characteristics & SCN_MEM_EXECUTE) { prot |= VR_X; }
|
||||
return prot;
|
||||
}
|
||||
|
||||
bool pe_find_section(mem_view_t v, uint64_t module_base, const char* name,
|
||||
uint64_t* rva_out, uint32_t* vsize_out) {
|
||||
size_t sec; uint16_t nsec;
|
||||
if (!name || !pe_section_table(v, module_base, &sec, &nsec)) return false;
|
||||
|
||||
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;
|
||||
const size_t sh = sec + (size_t)i * SH_SIZE;
|
||||
if (sh + SH_SIZE > 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 */
|
||||
memcpy(&vsize, v.data + sh + SH_VSIZE_OFF, 4); /* Misc.VirtualSize */
|
||||
memcpy(&vaddr, v.data + sh + SH_VADDR_OFF, 4); /* VirtualAddress */
|
||||
if (rva_out) *rva_out = vaddr;
|
||||
if (vsize_out) *vsize_out = vsize;
|
||||
return true;
|
||||
@@ -42,6 +79,29 @@ bool pe_find_section(mem_view_t v, uint64_t module_base, const char* name,
|
||||
return false;
|
||||
}
|
||||
|
||||
int pe_sections(mem_view_t v, uint64_t module_base, pe_secrec* out, int max) {
|
||||
size_t sec; uint16_t nsec;
|
||||
if (!pe_section_table(v, module_base, &sec, &nsec)) return -1;
|
||||
|
||||
int total = 0;
|
||||
for (uint16_t i = 0; i < nsec; i++) {
|
||||
const size_t sh = sec + (size_t)i * SH_SIZE;
|
||||
if (sh + SH_SIZE > v.size) break; /* headers truncated in view */
|
||||
if (out && total < max) {
|
||||
pe_secrec* r = &out[total];
|
||||
memset(r->name, 0, sizeof r->name);
|
||||
memcpy(r->name, v.data + sh, 8); /* name[8] stays the NUL slot */
|
||||
uint32_t ch;
|
||||
memcpy(&r->vsize, v.data + sh + SH_VSIZE_OFF, 4);
|
||||
memcpy(&r->rva, v.data + sh + SH_VADDR_OFF, 4);
|
||||
memcpy(&ch, v.data + sh + SH_CHAR_OFF, 4);
|
||||
r->prot = pe_prot(ch);
|
||||
}
|
||||
total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -61,3 +121,67 @@ int vmie_pe_section(vmie_mem* m, uintptr_t cr3, uint64_t module_base,
|
||||
out->data = buf; out->size = n; out->base_va = module_base + rva;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- public win32 surface: section enumeration + section views ----------- *
|
||||
* Cold paths (one-shot header parse / section gather, not a hot loop). They
|
||||
* reuse pe_sections / the shared section-table walk above - no second parser -
|
||||
* and never allocate: the section_view buffer is caller-owned. */
|
||||
|
||||
int vmie_win32_sections(vmie_win32* v, uint64_t cr3, uint64_t module_base,
|
||||
section_desc* out, int max) __attribute__((cold));
|
||||
int vmie_win32_sections(vmie_win32* v, uint64_t cr3, uint64_t module_base,
|
||||
section_desc* out, int max) {
|
||||
vmie_mem* m = vmie_win32_mem(v);
|
||||
if (!m) { return -1; }
|
||||
|
||||
uint8_t hdr[0x1000];
|
||||
if (gva_read(m, cr3, module_base, hdr, sizeof hdr)) { return -1; }
|
||||
const mem_view_t hv = { hdr, sizeof hdr, module_base };
|
||||
|
||||
/* count first via pe_sections(out=NULL); if the caller only wants the count
|
||||
* (out==NULL) we are done. */
|
||||
const int total = pe_sections(hv, module_base, NULL, 0);
|
||||
if (total < 0 || !out) { return total; }
|
||||
|
||||
pe_secrec recs[96];
|
||||
int cap = max;
|
||||
if (cap < 0) { cap = 0; }
|
||||
if (cap > (int)(sizeof recs / sizeof recs[0])) {
|
||||
cap = (int)(sizeof recs / sizeof recs[0]);
|
||||
}
|
||||
const int got = pe_sections(hv, module_base, recs, cap);
|
||||
if (got < 0) { return got; }
|
||||
|
||||
int n = got < cap ? got : cap;
|
||||
for (int i = 0; i < n; i++) {
|
||||
memcpy(out[i].name, recs[i].name, sizeof out[i].name);
|
||||
out[i].rva = recs[i].rva;
|
||||
out[i].vsize = recs[i].vsize;
|
||||
out[i].prot = recs[i].prot;
|
||||
}
|
||||
return total; /* total count, even if it exceeded `max` */
|
||||
}
|
||||
|
||||
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)
|
||||
__attribute__((cold));
|
||||
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) {
|
||||
vmie_mem* m = vmie_win32_mem(v);
|
||||
if (!m || !sec || !buf || !out) { return -1; }
|
||||
|
||||
const size_t n = sec->vsize < bufcap ? sec->vsize : bufcap;
|
||||
if (gva_read(m, cr3, module_base + sec->rva, buf, n)) { return -1; }
|
||||
|
||||
uint64_t base_va = 0;
|
||||
switch (mode) {
|
||||
case MODULE_RVA: base_va = sec->rva; break;
|
||||
case ABSOLUTE_VA: base_va = module_base + sec->rva; break;
|
||||
case SECTION_LOCAL:
|
||||
default: base_va = 0; break;
|
||||
}
|
||||
out->data = buf; out->size = n; out->base_va = base_va;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user