mirror of
https://dev.lirent.ru/Vatrog/vm-introspection-engine.git
synced 2026-07-09 01:46:38 +03:00
Add code-structure analysis: call graph, jump tables, basic blocks, constant xref
Wave 1 of the code-analysis layer, built on the x86-64 decoder: - vmie_win32_callgraph walks each .pdata function with the decoder and emits an edge for every direct call/jmp whose target lands in the module - the intra-module call graph. Indirect edges are left to the IAT and jump tables. - gva_jumptable recovers a switch's case targets from an indirect jump's table: consecutive pointer entries that land in an executable region. - cfg_blocks splits one function view into basic blocks (a generic handler: leaders from intra-function branch targets, cut after jmp/jcc/ret). - gva_imm_xref finds the instructions whose immediate operand equals a constant - the dual of code-xref for magic values, error codes, syscall numbers. The decoder now also reports imm_off/imm_len so a caller can read or match the immediate operand. The generic primitives live in the new codeanalysis.h (jump tables, basic blocks) and scan.h (constant xref); the .pdata-bound call graph stays on the win32 surface and reuses the existing function/section/decode primitives - no second PE or instruction parser.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
#include "pe.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h> /* malloc/free (cold call-graph gather only) */
|
||||
#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 */
|
||||
#include "x86dec.h" /* x86_decode / x86_branch_target (call-graph step) */
|
||||
|
||||
/* IMAGE_SECTION_HEADER: 8-byte Name, then Misc.VirtualSize(+8), VirtualAddress
|
||||
* (+12), and Characteristics(+36); the header is 40 bytes wide. */
|
||||
@@ -406,3 +408,111 @@ int vmie_win32_section_view(vmie_win32* v, uint64_t cr3, uint64_t module_base,
|
||||
out->data = buf; out->size = n; out->base_va = base_va;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- public win32 surface: intra-module call graph ----------------------- *
|
||||
* Reuses the existing primitives only: vmie_win32_functions (.pdata starts),
|
||||
* vmie_win32_section_view (.text bytes), and x86_decode (the light decoder) -
|
||||
* there is no second PE parser and no second decoder here. For each function it
|
||||
* steps the bytes linearly and, on a DIRECT call/jmp (has_rel), resolves the
|
||||
* target and, if it lands inside the image, emits one {from, to, kind} edge.
|
||||
* Cold: one-shot directory + section gather, not a hot loop. */
|
||||
|
||||
/* SizeOfImage lives in the PE32+ OptionalHeader at +0x38; the OptionalHeader
|
||||
* begins at NT(base+lfanew)+0x18 (Signature(4)+FileHeader(20)). */
|
||||
#define OPT_SIZEOFIMAGE_OFF 0x38u
|
||||
|
||||
int vmie_win32_callgraph(vmie_win32* v, uint64_t cr3, uint64_t module_base,
|
||||
call_edge* out, int max) __attribute__((cold));
|
||||
int vmie_win32_callgraph(vmie_win32* v, uint64_t cr3, uint64_t module_base,
|
||||
call_edge* out, int max) {
|
||||
vmie_mem* m = vmie_win32_mem(v);
|
||||
if (!m) { return -1; }
|
||||
|
||||
/* image bounds: [module_base, module_base + SizeOfImage). */
|
||||
uint32_t lfanew;
|
||||
if (gva_read(m, cr3, module_base + 0x3C, &lfanew, 4)) { return -1; }
|
||||
uint32_t size_of_image;
|
||||
if (gva_read(m, cr3, module_base + lfanew + 0x18 + OPT_SIZEOFIMAGE_OFF,
|
||||
&size_of_image, 4)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* locate .text (the executable section the .pdata functions live in). */
|
||||
section_desc sd[96];
|
||||
const int ns = vmie_win32_sections(v, cr3, module_base, sd, 96);
|
||||
if (ns < 0) { return -1; }
|
||||
const int nsuse = ns < 96 ? ns : 96;
|
||||
const section_desc* text = NULL;
|
||||
for (int i = 0; i < nsuse; i++) {
|
||||
if (strcmp(sd[i].name, ".text") == 0) { text = &sd[i]; break; }
|
||||
}
|
||||
if (!text) {
|
||||
/* fall back to the first executable section */
|
||||
for (int i = 0; i < nsuse; i++) {
|
||||
if (sd[i].prot & VR_X) { text = &sd[i]; break; }
|
||||
}
|
||||
}
|
||||
if (!text) { return -1; }
|
||||
|
||||
/* gather the executable section once, addressed at its absolute VA so a
|
||||
* decoded branch target is directly an absolute VA. */
|
||||
uint8_t* tbuf = malloc(text->vsize);
|
||||
if (!tbuf) { return -1; }
|
||||
mem_view_t tv;
|
||||
if (vmie_win32_section_view(v, cr3, module_base, text, ABSOLUTE_VA,
|
||||
tbuf, text->vsize, &tv) != 0) {
|
||||
free(tbuf);
|
||||
return -1;
|
||||
}
|
||||
const uint64_t text_lo = module_base + text->rva; /* tv.base_va */
|
||||
const uint64_t text_hi = text_lo + tv.size; /* exclusive */
|
||||
|
||||
/* function inventory: count, then gather (stack for the common case, heap on
|
||||
* overflow) so every function is stepped, none silently dropped. */
|
||||
const int nfn = vmie_win32_functions(v, cr3, module_base, NULL, 0);
|
||||
if (nfn < 0) { free(tbuf); return -1; }
|
||||
func_range stack_fr[256];
|
||||
func_range* fr = stack_fr;
|
||||
func_range* heap_fr = NULL;
|
||||
if (nfn > (int)(sizeof stack_fr / sizeof stack_fr[0])) {
|
||||
heap_fr = malloc((size_t)nfn * sizeof *heap_fr);
|
||||
if (!heap_fr) { free(tbuf); return -1; }
|
||||
fr = heap_fr;
|
||||
}
|
||||
const int got = vmie_win32_functions(v, cr3, module_base, fr, nfn);
|
||||
if (got < 0) { free(heap_fr); free(tbuf); return -1; }
|
||||
|
||||
int total = 0;
|
||||
for (int f = 0; f < got; f++) {
|
||||
const uint64_t fn_lo = module_base + fr[f].rva;
|
||||
const uint64_t fn_hi = fn_lo + fr[f].size;
|
||||
/* the function must lie inside the gathered section. */
|
||||
if (fn_lo < text_lo || fn_hi > text_hi) { continue; }
|
||||
|
||||
size_t off = (size_t)(fn_lo - text_lo);
|
||||
const size_t end = (size_t)(fn_hi - text_lo);
|
||||
while (off < end) {
|
||||
x86_insn in;
|
||||
const int ilen = x86_decode(tv.data + off, end - off, &in);
|
||||
if (ilen <= 0) { break; } /* desync: stop this fn */
|
||||
const uint64_t ip = text_lo + off;
|
||||
if (in.has_rel && (in.flow == X86_CALL || in.flow == X86_JMP)) {
|
||||
const uint64_t tgt = x86_branch_target(ip, &in);
|
||||
if (tgt >= module_base &&
|
||||
tgt < module_base + (uint64_t)size_of_image) {
|
||||
if (out && total < max) {
|
||||
out[total].from = fr[f].rva;
|
||||
out[total].to = (uint32_t)(tgt - module_base);
|
||||
out[total].kind = (in.flow == X86_CALL) ? 0u : 1u;
|
||||
}
|
||||
total++;
|
||||
}
|
||||
}
|
||||
off += (size_t)ilen;
|
||||
}
|
||||
}
|
||||
|
||||
free(heap_fr);
|
||||
free(tbuf);
|
||||
return total;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user