Add operand-normalized semantic code signatures

New semsig_hash(mem_view_t) primitive (include/semsig.h): a position-,
register-, immediate- and instruction-order-invariant function fingerprint.
It folds per-instruction operand-canonical tokens, splitting the body into
basic blocks via cfg_blocks and using a within-block order-insensitive
sort-then-fold (between-block order preserved), so it survives compiler
register reallocation and instruction scheduling that the byte-mask
func_hash/sig_generate cannot. Distinct hash domain from func_hash.

The operand decode is delegated to an OPTIONAL external disassembler behind
a private adapter seam (src/handlers/semsig_backend.h); semsig.c stays
backend-neutral and includes no backend header. VMIE_DISASM={OFF|zydis|
capstone} gates the build: OFF (default) compiles only semsig_stub.c and
keeps the zero-dependency build (VMIE_HAVE_DISASM=0, packages unchanged).
The backend is brought from VMIE_DISASM_SRC or find_package - never
vendored. Adds the vmie_win32_func_semsig wrapper.

The CI deb job builds with the Zydis backend (libzydis-dev); the runtime
package dependency on libzydis is derived automatically by dpkg-shlibdeps.
This commit is contained in:
2026-06-28 21:51:47 +03:00
parent 8ca84a5861
commit 1b06206043
10 changed files with 955 additions and 1 deletions
+31
View File
@@ -461,6 +461,37 @@ int vmie_win32_inline_hooks(vmie_win32* v, uint64_t cr3, uint64_t module_base,
int vmie_win32_func_imports(vmie_win32* v, uint64_t cr3, uint64_t module_base,
uint32_t func_rva, uint32_t* iat_rvas, int max);
/* Reorder-/register-canonical SEMANTIC hash of ONE function, addressed by its
* func_rva (a .pdata function start, as from vmie_win32_functions or an export).
* The win32 convenience entry point over the generic semsig_hash (semsig.h): it
* locates the function extent from .pdata (like vmie_win32_func_imports),
* gathers its body with gva_read, builds a SECTION_LOCAL mem_view_t over those
* bytes, and DELEGATES to semsig_hash - it does NOT reimplement normalization or
* folding, and it pulls in no disassembler backend itself (the feature is
* inherited transitively via the same VMIE_HAVE_DISASM as the generic).
*
* v - engine handle.
* cr3 - the process address space the module is mapped in.
* module_base - image base VA (its PE headers must be resident).
* func_rva - function start RVA (must match a .pdata function start).
*
* Returns the 64-bit semantic hash, or 0 on any of: func_rva is not a known
* function start, the body is unreadable (paged out), a decode desync, or the
* build has no disassembler backend (VMIE_HAVE_DISASM == 0) - the SAME "no hash"
* sentinel as semsig_hash / func_hash. NOT COMPARABLE to func_hash: it is a
* different (normalized) domain; compare it only to other vmie_win32_func_semsig
* / semsig_hash values. semsig_hash is position-independent, so the SECTION_LOCAL
* gather gives a stable value regardless of where the module is based.
*
* Reorder invariance and its cost (lowered intra-block discrimination => higher
* collision risk) are properties of the underlying semsig_hash - see semsig.h.
*
* Example - name a function semantically against a known-hash table:
* uint64_t h = vmie_win32_func_semsig(v, pr->cr3, m.base, fn_rva);
* if (h && h == known_crt_strlen_semsig) puts("strlen"); */
uint64_t vmie_win32_func_semsig(vmie_win32* v, uint64_t cr3,
uint64_t module_base, uint32_t func_rva);
/* Devirtualization (C++ vtables) needs NO dedicated symbol - it is a
* COMPOSITION of primitives the engine already exposes:
* - a vtable at `vtable_va` is an array of code pointers, so its METHODS are