mirror of
https://dev.lirent.ru/Vatrog/vm-introspection-engine.git
synced 2026-06-18 02:06:36 +03:00
Windows guest VMI core: host library, CLI, guest agent
Static library over a flat RW mmap of guest RAM: GPA/GVA paging walks, beacon-driven bootstrap, dynamic struct-offset profiling, process and module enumeration, a region map, and value/pointer/signature scanners on a shared windowed sweep. Public API in include/; internals under src/. Thin CLI demonstrator over the public API. Guest agent cross-compiled to Windows x86-64 via mingw-w64. CMake: static library + CLI + guest target, C17.
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
.*/
|
||||||
|
cmake-*/
|
||||||
|
compile*
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.18) # find_program(... REQUIRED)
|
||||||
|
project(w32ms C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 17) # generation B uses no C23 feature
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS ON) # deliberate: strnlen (POSIX) + void* arithmetic (GNU)
|
||||||
|
|
||||||
|
# ---- host: VMI core as a static library ---------------------------------
|
||||||
|
add_library(w32ms STATIC
|
||||||
|
src/gpa.c
|
||||||
|
src/gva.c
|
||||||
|
src/host.c
|
||||||
|
src/proc.c
|
||||||
|
src/profile.c
|
||||||
|
src/text.c
|
||||||
|
src/scan.c
|
||||||
|
src/sigscan.c)
|
||||||
|
target_include_directories(w32ms
|
||||||
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # public API: include/*.h
|
||||||
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) # private: src/include/*.h via "include/..."
|
||||||
|
target_compile_options(w32ms PRIVATE -O2 -Wall -Wextra)
|
||||||
|
|
||||||
|
# ---- host: CLI demonstrator over the library ----------------------------
|
||||||
|
add_executable(w32ms_cli src/cli.c)
|
||||||
|
target_link_libraries(w32ms_cli PRIVATE w32ms)
|
||||||
|
target_compile_options(w32ms_cli PRIVATE -Wall -Wextra)
|
||||||
|
|
||||||
|
# ---- guest: cross-compile to Windows x86-64 via mingw-w64 ---------------
|
||||||
|
find_program(MINGW_CC NAMES x86_64-w64-mingw32-gcc REQUIRED)
|
||||||
|
set(W32MS_GUEST ${CMAKE_CURRENT_BINARY_DIR}/w32ms_guest.exe)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${W32MS_GUEST}
|
||||||
|
COMMAND ${MINGW_CC} -O2 -Wall -Wextra -static -s
|
||||||
|
-I${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
|
-o ${W32MS_GUEST} ${CMAKE_CURRENT_SOURCE_DIR}/src/guest.c
|
||||||
|
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/guest.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/include/contract.h
|
||||||
|
COMMENT "Cross-compiling w32ms_guest.exe (mingw-w64, x86-64)"
|
||||||
|
VERBATIM)
|
||||||
|
add_custom_target(w32ms_guest ALL DEPENDS ${W32MS_GUEST})
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/* include.h - public interface of the Windows VMI core.
|
||||||
|
*
|
||||||
|
* The host opens a guest's RAM backing file (a flat, writable, coherent mmap),
|
||||||
|
* recovers the kernel address space, and reads/writes guest memory by CR3 and
|
||||||
|
* virtual address. Everything is CR3-keyed, never PID-keyed: a `process` already
|
||||||
|
* carries its own cr3, which is the key to that address space.
|
||||||
|
*
|
||||||
|
* Conventions used throughout this header:
|
||||||
|
* - `cr3` is a raw CR3 / DirectoryTableBase value; low flag bits are masked
|
||||||
|
* internally, so either the masked PML4 GPA or the raw register works.
|
||||||
|
* - A "VA" is a 64-bit canonical guest virtual address. A "GPA" is a guest
|
||||||
|
* physical address. Reads/writes that cross a page boundary are handled
|
||||||
|
* internally (per-page translation), so callers pass plain ranges.
|
||||||
|
* - Integer returns: 0 on success, negative on failure, unless stated.
|
||||||
|
* - The library never takes ownership of caller buffers and never retains a
|
||||||
|
* pointer past the call that received it, unless explicitly stated.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef W32MS_INCLUDE_H
|
||||||
|
#define W32MS_INCLUDE_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* Opaque introspection context. Completed in src/include/memory.h; callers only
|
||||||
|
* ever hold a pointer. Created by gva_ctx_alloc(), populated by host_bootstrap(),
|
||||||
|
* released by gva_ctx_free(). */
|
||||||
|
typedef struct gva_ctx gva_ctx;
|
||||||
|
|
||||||
|
/* A guest counted string still resident in guest memory (e.g. a UNICODE_STRING
|
||||||
|
* buffer). Not a copy: `va` points into the guest, decode it with gva_read_text.
|
||||||
|
* va - guest VA of the first UTF-16LE code unit (0 if absent)
|
||||||
|
* len - length in BYTES (not characters); always even for UTF-16 */
|
||||||
|
typedef struct { uint64_t va; uint32_t len; } gtext;
|
||||||
|
|
||||||
|
/* A live process, as produced by proc_list(). Self-contained: `cr3` is all you
|
||||||
|
* need to read/write its user address space, `eprocess`/`peb` re-anchor it in
|
||||||
|
* kernel/user space without another lookup.
|
||||||
|
* cr3 - DirectoryTableBase (PFN-masked); key to this address space
|
||||||
|
* peb - PEB VA (0 for system/kernel-only processes)
|
||||||
|
* eprocess - _EPROCESS VA (kernel object, read under the kernel cr3)
|
||||||
|
* pid, ppid - process / parent ids (ppid == (uint32_t)-1 if unavailable)
|
||||||
|
* create_time - raw KSYSTEM_TIME / FILETIME (100 ns ticks; 0 if unavailable)
|
||||||
|
* name - ImageFileName, NUL-terminated ASCII (up to 15 chars)
|
||||||
|
* path - full image path as a guest UTF-16 string (gtext; may be empty) */
|
||||||
|
typedef struct {
|
||||||
|
uint64_t cr3;
|
||||||
|
uint64_t peb;
|
||||||
|
uint64_t eprocess;
|
||||||
|
uint32_t pid;
|
||||||
|
uint32_t ppid;
|
||||||
|
uint64_t create_time;
|
||||||
|
char name[16];
|
||||||
|
gtext path;
|
||||||
|
} process;
|
||||||
|
|
||||||
|
/* A loaded module (image) inside a process, as produced by proc_modules().
|
||||||
|
* pr - owning process (its cr3 is the address space these VAs live in)
|
||||||
|
* entry - _LDR_DATA_TABLE_ENTRY VA
|
||||||
|
* base - image base VA (page-aligned); pair with `size` for a MODULE scope
|
||||||
|
* size - image size in bytes (SizeOfImage)
|
||||||
|
* name - module file name (gtext UTF-16, e.g. "ntdll.dll")
|
||||||
|
* path - full module path (gtext UTF-16) */
|
||||||
|
typedef struct {
|
||||||
|
const process* pr;
|
||||||
|
uint64_t entry;
|
||||||
|
uint64_t base;
|
||||||
|
uint32_t size;
|
||||||
|
gtext name;
|
||||||
|
gtext path;
|
||||||
|
} pmodule;
|
||||||
|
|
||||||
|
/* ---- region map ---------------------------------------------------------- *
|
||||||
|
* A vregion is one run of VA-contiguous, present guest pages sharing the same
|
||||||
|
* effective protection. It is the unit of "what is mapped, and how" and the
|
||||||
|
* scoping primitive for the scanners (see scan.h).
|
||||||
|
*
|
||||||
|
* x86-64 has no read bit: a present page is readable, so VR_R is always set on a
|
||||||
|
* returned region. Write/execute/user are the EFFECTIVE rights along the whole
|
||||||
|
* page-table path (RW & US are AND-ed across levels, NX is OR-ed), not just the
|
||||||
|
* leaf entry, so they reflect what the guest CPU actually enforces. */
|
||||||
|
#define VR_R 0x1u /* readable (present => always set) */
|
||||||
|
#define VR_W 0x2u /* writable (RW bit set at every level) */
|
||||||
|
#define VR_X 0x4u /* executable(NX clear at every level) */
|
||||||
|
#define VR_U 0x8u /* user-accessible (US bit set at every level) */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t va; /* run start VA (clamped into the requested [lo,hi] window) */
|
||||||
|
uint64_t len; /* run length in bytes */
|
||||||
|
uint32_t prot; /* OR of VR_* flags */
|
||||||
|
} vregion;
|
||||||
|
|
||||||
|
/* ---- lifecycle ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Open `ram_path` (the guest RAM backing file) and build a context over it.
|
||||||
|
* ram_path - path to a writable, share=on RAM backing file
|
||||||
|
* low - size in bytes of below-4G guest RAM (the PCI-hole split point);
|
||||||
|
* pass the value from the VM's memory layout. If total RAM <= low,
|
||||||
|
* the split is inert.
|
||||||
|
* Returns a new context (call host_bootstrap() next), or NULL on open/mmap
|
||||||
|
* failure. Free with gva_ctx_free(). */
|
||||||
|
gva_ctx* gva_ctx_alloc(const char* ram_path, uint64_t low);
|
||||||
|
|
||||||
|
/* Unmap, close, and free a context. Safe on NULL. After this, every pointer
|
||||||
|
* into guest memory obtained through this context is invalid. */
|
||||||
|
void gva_ctx_free(gva_ctx* ctx);
|
||||||
|
|
||||||
|
/* One-shot bring-up: locate the guest agent beacon in physical RAM, recover a
|
||||||
|
* bootstrap CR3, find ntoskrnl, build the struct-offset profile, derive the
|
||||||
|
* permanent System DirectoryTableBase (kernel cr3) and System _EPROCESS, then
|
||||||
|
* ACK the agent. On success the context is ready for proc_list()/gva_read()/etc.
|
||||||
|
* Returns 0 on success, or a negative stage code (-1..-6) identifying the step
|
||||||
|
* that failed. Cold path: call once after gva_ctx_alloc(). */
|
||||||
|
int host_bootstrap(gva_ctx* ctx);
|
||||||
|
|
||||||
|
/* ---- guest memory access (hot path) -------------------------------------- */
|
||||||
|
|
||||||
|
/* Read `nmemb` bytes from guest VA `va` (translated under `cr3`) into `dst`.
|
||||||
|
* Crosses page boundaries internally. Returns 0 on success, -1 if any page in
|
||||||
|
* the range is not present/translatable (in which case `dst` is partially
|
||||||
|
* written and must be treated as invalid). */
|
||||||
|
int gva_read(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, void* dst, size_t nmemb);
|
||||||
|
|
||||||
|
/* Write `nmemb` bytes from `src` to guest VA `va` (translated under `cr3`).
|
||||||
|
* The mapping is RW and coherent, so the guest observes the change. Returns 0
|
||||||
|
* on success, -1 if any page in the range is not present/translatable. */
|
||||||
|
int gva_write(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, const void* src, size_t nmemb);
|
||||||
|
|
||||||
|
/* Read a UTF-16LE guest string and transcode it to UTF-8.
|
||||||
|
* va - guest VA of the first UTF-16 code unit
|
||||||
|
* nmemb - number of BYTES to read from the guest (rounded down to even)
|
||||||
|
* dst - output buffer for NUL-terminated UTF-8 (may be NULL to size only)
|
||||||
|
* size - capacity of `dst` in bytes
|
||||||
|
* Returns the number of UTF-8 bytes the full conversion needs, EXCLUDING the
|
||||||
|
* terminator (like snprintf): if it is >= `size`, output was truncated. When
|
||||||
|
* `dst` is non-NULL and `size` > 0 the result is always NUL-terminated. */
|
||||||
|
size_t gva_read_text(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size);
|
||||||
|
|
||||||
|
/* ---- enumeration --------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Enumerate processes by walking ActiveProcessLinks from System.
|
||||||
|
* skip_system - if nonzero, omit processes with no PEB (System/kernel-only)
|
||||||
|
* dst - caller array receiving up to `nmax` `process` records
|
||||||
|
* nmax - capacity of `dst`
|
||||||
|
* Returns the number written (<= nmax), or negative on failure (e.g. bootstrap
|
||||||
|
* not completed). Enumeration stops at `nmax`; raise it to see more. */
|
||||||
|
int proc_list(gva_ctx* ctx, int skip_system, process* dst, size_t nmax);
|
||||||
|
|
||||||
|
/* Enumerate a process's loaded modules via the PEB loader InLoadOrder list.
|
||||||
|
* pr - process to inspect (uses pr->cr3 and pr->peb)
|
||||||
|
* dst - caller array receiving up to `nmax` `pmodule` records
|
||||||
|
* nmax - capacity of `dst`
|
||||||
|
* Returns the number written (<= nmax), 0 if the process has no PEB/loader. */
|
||||||
|
int proc_modules(gva_ctx* ctx, const process* pr, pmodule* dst, size_t nmax);
|
||||||
|
|
||||||
|
/* Enumerate mapped memory under `cr3`, clamped to the VA window [lo,hi]
|
||||||
|
* (inclusive), as runs of equal effective protection.
|
||||||
|
* cr3 - address space to walk (a process cr3, or the kernel cr3)
|
||||||
|
* lo, hi - inclusive VA window; MUST lie within a single canonical half
|
||||||
|
* (entirely user or entirely kernel). Use (0, ~0ull) loosely; the
|
||||||
|
* walk prunes whole subtrees outside the window.
|
||||||
|
* prot_any - protection filter: 0 keeps every run; otherwise a run is kept
|
||||||
|
* only if (run.prot & prot_any) != 0 (e.g. VR_W for writable-only,
|
||||||
|
* VR_X for executable-only)
|
||||||
|
* out - caller array receiving up to `nmax` `vregion` records
|
||||||
|
* nmax - capacity of `out`
|
||||||
|
* Returns the TOTAL number of matching runs found. If the return value exceeds
|
||||||
|
* `nmax` the output was truncated (only `nmax` runs were written); enlarge the
|
||||||
|
* buffer and retry for the full map. */
|
||||||
|
int gva_regions(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
|
||||||
|
uint32_t prot_any, vregion* out, int nmax);
|
||||||
|
|
||||||
|
#endif /* W32MS_INCLUDE_H */
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/* scan.h - typed value scanner, pointer scanner, and gva<->signature bridges.
|
||||||
|
*
|
||||||
|
* Layered above the pure matcher (sigscan.h) and the gva core (include.h): this
|
||||||
|
* is the gva-bound scanning surface. The value scanner narrows a candidate set
|
||||||
|
* across successive snapshots; the pointer scanner discovers module-anchored
|
||||||
|
* pointer chains; the gva_sig_* bridges build mem_view_t windows out of guest
|
||||||
|
* memory and feed them to the signature matcher.
|
||||||
|
*/
|
||||||
|
#ifndef W32MS_SCAN_H
|
||||||
|
#define W32MS_SCAN_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "include.h" /* gva_ctx, process (vregion - internal) */
|
||||||
|
#include "sigscan.h" /* mem_view_t, sig_pattern_t */
|
||||||
|
|
||||||
|
/* typed value scanner. ENUMERATOR ORDER IS LOAD-BEARING: scan.c indexes the
|
||||||
|
* table g_tsz[] = {1,2,4,8, 1,2,4,8, 4,8, 2} by these values - do not reorder
|
||||||
|
* without updating scan.c. */
|
||||||
|
typedef enum {
|
||||||
|
SCAN_I8, SCAN_I16, SCAN_I32, SCAN_I64, /* signed */
|
||||||
|
SCAN_U8, SCAN_U16, SCAN_U32, SCAN_U64, /* unsigned */
|
||||||
|
SCAN_F32, SCAN_F64, SCAN_F16 /* float */
|
||||||
|
} scan_type;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SCAN_EQ, SCAN_NEQ, SCAN_GT, SCAN_LT, /* require a value argument */
|
||||||
|
SCAN_INC, SCAN_DEC, SCAN_CHANGED, SCAN_UNCHANGED /* relative to the previous snapshot */
|
||||||
|
} scan_op;
|
||||||
|
|
||||||
|
typedef struct scan scan; /* opaque session */
|
||||||
|
typedef struct { uint64_t addr; uint64_t value; } scan_hit;
|
||||||
|
|
||||||
|
#define SCAN_PTR_MAXDEPTH 8 /* DFS depth and size of off[] */
|
||||||
|
typedef struct {
|
||||||
|
uint64_t base; /* module-anchored base address */
|
||||||
|
int depth; /* number of offsets in off[] */
|
||||||
|
int32_t off[SCAN_PTR_MAXDEPTH]; /* dereference chain */
|
||||||
|
} scan_ptr_path;
|
||||||
|
|
||||||
|
scan* scan_new(gva_ctx* ctx, const process* pr, scan_type t, const void* value,
|
||||||
|
int be, int aligned, uint64_t lo, uint64_t hi);
|
||||||
|
scan* scan_new_cr3(gva_ctx* ctx, uintptr_t cr3, scan_type t, const void* value,
|
||||||
|
int be, int aligned, uint64_t lo, uint64_t hi);
|
||||||
|
int64_t scan_next(scan* s, scan_op op, const void* value);
|
||||||
|
int64_t scan_count(scan* s);
|
||||||
|
int scan_results(scan* s, uint64_t offset, int max, scan_hit* out);
|
||||||
|
void scan_free(scan* s);
|
||||||
|
|
||||||
|
int scan_pointer(gva_ctx* ctx, const process* pr, uint64_t target,
|
||||||
|
int max_depth, uint32_t max_off, scan_ptr_path* out, int max);
|
||||||
|
|
||||||
|
/* gva bridges to the signature matcher: build mem_view from guest memory and feed sigscan.h */
|
||||||
|
int gva_sig_scan (gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
|
||||||
|
uint32_t prot_any, const sig_pattern_t* p, uint64_t* out, int max);
|
||||||
|
int gva_sig_first(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
|
||||||
|
uint32_t prot_any, const sig_pattern_t* p, uint64_t* va);
|
||||||
|
int gva_sig_rip (gva_ctx* ctx, uintptr_t cr3, uint64_t hit_va,
|
||||||
|
size_t disp_off, size_t instr_len, uint64_t* target);
|
||||||
|
int gva_pe_section(gva_ctx* ctx, uintptr_t cr3, uint64_t module_base,
|
||||||
|
const char* name, uint8_t* buf, size_t bufcap, mem_view_t* out);
|
||||||
|
int gva_sig_phys (gva_ctx* ctx, const sig_pattern_t* p, uint64_t* out, int max);
|
||||||
|
|
||||||
|
#endif /* W32MS_SCAN_H */
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/* sigscan.h - source-agnostic x86-64 signature scanner.
|
||||||
|
*
|
||||||
|
* Everything operates on a mem_view_t: a flat byte span plus the virtual address
|
||||||
|
* that data[0] maps to. Live guest memory, a retained snapshot, and an on-disk
|
||||||
|
* dump are identical to the matcher - only how you build the view differs. All
|
||||||
|
* results are reported as addresses in the view's own coordinate space
|
||||||
|
* (base_va + offset): a guest VA for a virtual view, a GPA for a physical view.
|
||||||
|
*
|
||||||
|
* This module is pure: it never touches a gva_ctx and performs no I/O. To scan
|
||||||
|
* guest memory, build views from the gva layer (see scan.h: gva_sig_scan,
|
||||||
|
* gva_pe_section, gva_sig_phys) and feed them here.
|
||||||
|
*/
|
||||||
|
#ifndef W32MS_SIGSCAN_H
|
||||||
|
#define W32MS_SIGSCAN_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/* A contiguous view of memory.
|
||||||
|
* data - host pointer to the bytes (borrowed; not owned by the view)
|
||||||
|
* size - number of valid bytes at `data`
|
||||||
|
* base_va - address that data[0] corresponds to (guest VA, or GPA for a
|
||||||
|
* physical view). All matches are reported as base_va + offset. */
|
||||||
|
typedef struct {
|
||||||
|
const uint8_t* data;
|
||||||
|
size_t size;
|
||||||
|
uint64_t base_va;
|
||||||
|
} mem_view_t;
|
||||||
|
|
||||||
|
/* A parsed byte pattern. mask[i] == 1 means bytes[i] must match; 0 = wildcard.
|
||||||
|
* Owns two heap allocations of `len` bytes each; release with sig_free(). */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t* bytes;
|
||||||
|
uint8_t* mask;
|
||||||
|
size_t len;
|
||||||
|
} sig_pattern_t;
|
||||||
|
|
||||||
|
/* Parse an IDA-style string, e.g. "48 8B 05 ? ? ? ? 48 85 C0" ('?' or '??' =
|
||||||
|
* wildcard). On success fills *out and returns true; free it with sig_free().
|
||||||
|
* Returns false on NULL args, an empty string, or a malformed hex byte. */
|
||||||
|
bool sig_parse_ida(const char* ida, sig_pattern_t* out);
|
||||||
|
|
||||||
|
/* Parse code+mask form, e.g. bytes="\x48\x8B\x05\x00\x00\x00\x00", mask="xxx????"
|
||||||
|
* ('x'/'X' = must match, anything else = wildcard). `bytes` must have at least
|
||||||
|
* strlen(mask) readable bytes. Returns true on success (free with sig_free()),
|
||||||
|
* false on NULL args or an empty mask. */
|
||||||
|
bool sig_parse_mask(const uint8_t* bytes, const char* mask, sig_pattern_t* out);
|
||||||
|
|
||||||
|
/* Release a pattern produced by sig_parse_*. Safe on NULL and on an
|
||||||
|
* already-freed pattern (it is zeroed). */
|
||||||
|
void sig_free(sig_pattern_t* p);
|
||||||
|
|
||||||
|
/* Invoke cb(user, match_va) for every match of `p` in `v`, in ascending address
|
||||||
|
* order. The driver anchors on the pattern's first non-wildcard byte and uses
|
||||||
|
* memchr to skip, so it is fast even on sparse matches. `cb` returns nonzero to
|
||||||
|
* stop early. This is the building block under sig_first/sig_all and is what a
|
||||||
|
* windowed caller uses to de-duplicate across window seams (see scan.h). */
|
||||||
|
void sig_each(mem_view_t v, const sig_pattern_t* p,
|
||||||
|
int (*cb)(void* user, uint64_t va), void* user);
|
||||||
|
|
||||||
|
/* First match, or 0 if none. (0 is also a theoretically valid base_va of 0; in
|
||||||
|
* practice view base addresses are nonzero, so 0 reliably means "no match".) */
|
||||||
|
uint64_t sig_first(mem_view_t v, const sig_pattern_t* p);
|
||||||
|
|
||||||
|
/* All matches. If `out` is NULL, returns the total match count (use it to size a
|
||||||
|
* buffer). Otherwise writes up to `max` addresses to `out` and returns how many
|
||||||
|
* were written (capped at `max`). */
|
||||||
|
size_t sig_all(mem_view_t v, const sig_pattern_t* p, uint64_t* out, size_t max);
|
||||||
|
|
||||||
|
/* Resolve an x86-64 RIP-relative operand at a match site.
|
||||||
|
* hit_va - VA of the matched pattern start (== instruction start)
|
||||||
|
* disp_off - byte offset of the int32 displacement within the pattern
|
||||||
|
* instr_len - full instruction length (next RIP = hit_va + instr_len); for the
|
||||||
|
* common "<prefix> disp32" tail this is disp_off + 4
|
||||||
|
* Returns the absolute target VA, or 0 if the displacement bytes lie outside `v`.
|
||||||
|
* The result is an address in the same space as `v` (a guest VA for a guest
|
||||||
|
* view): dereference it with gva_read under the matching cr3. This is how an
|
||||||
|
* unexported global is located from a code signature. */
|
||||||
|
uint64_t sig_rip(mem_view_t v, uint64_t hit_va, size_t disp_off, size_t instr_len);
|
||||||
|
|
||||||
|
/* Clamp a sub-view [start_va, start_va+size) against `v`. Returns a zeroed view
|
||||||
|
* (data == NULL) if start_va is outside `v`; otherwise `size` is trimmed to what
|
||||||
|
* is actually available. Useful for narrowing a scan to a [start,end] window. */
|
||||||
|
mem_view_t mem_sub(mem_view_t v, uint64_t start_va, size_t size);
|
||||||
|
|
||||||
|
/* 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 gva_pe_section (scan.h). */
|
||||||
|
bool pe_section(mem_view_t v, uint64_t module_base, const char* name,
|
||||||
|
mem_view_t* out);
|
||||||
|
|
||||||
|
#endif /* W32MS_SIGSCAN_H */
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/* cli.c - thin demonstrator over the public w32ms API.
|
||||||
|
*
|
||||||
|
* Opens a guest RAM backing file, brings up the VMI context, lists processes,
|
||||||
|
* and for the first user process dumps its loaded modules and mapped regions.
|
||||||
|
* Public surface only (include/include.h); never reaches into src/include.
|
||||||
|
*
|
||||||
|
* argv[1] path to the guest RAM backing file
|
||||||
|
* argv[2] `low` - size in bytes of below-4G guest RAM (strtoull, base 0)
|
||||||
|
* argv[3] optional cap on the process count (default 512)
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "include.h"
|
||||||
|
|
||||||
|
#define DEFAULT_NMAX 512
|
||||||
|
#define MOD_CAP 256
|
||||||
|
#define RGN_CAP 4096
|
||||||
|
#define TEXT_CAP 512
|
||||||
|
|
||||||
|
static const char* bootstrap_stage(int rc) {
|
||||||
|
switch (rc) {
|
||||||
|
case -1: return "beacon not found in guest RAM";
|
||||||
|
case -2: return "could not recover a bootstrap CR3";
|
||||||
|
case -3: return "ntoskrnl not located";
|
||||||
|
case -4: return "PsInitialSystemProcess unresolved";
|
||||||
|
case -5: return "struct-offset profile build failed";
|
||||||
|
case -6: return "kernel DirectoryTableBase read failed";
|
||||||
|
default: return "unknown bootstrap failure";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decode_prot(uint32_t prot, char out[5]) {
|
||||||
|
out[0] = (prot & VR_R) ? 'R' : '-';
|
||||||
|
out[1] = (prot & VR_W) ? 'W' : '-';
|
||||||
|
out[2] = (prot & VR_X) ? 'X' : '-';
|
||||||
|
out[3] = (prot & VR_U) ? 'U' : '-';
|
||||||
|
out[4] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dump_modules(gva_ctx* ctx, const process* pr) {
|
||||||
|
pmodule mods[MOD_CAP];
|
||||||
|
const int nm = proc_modules(ctx, pr, mods, MOD_CAP);
|
||||||
|
if (nm <= 0) {
|
||||||
|
printf(" (no modules)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < nm; i++) {
|
||||||
|
char name[TEXT_CAP], path[TEXT_CAP];
|
||||||
|
/* module strings are user-space: read under the process cr3. */
|
||||||
|
if (mods[i].name.va) {
|
||||||
|
gva_read_text(ctx, pr->cr3, mods[i].name.va, mods[i].name.len, name, sizeof name);
|
||||||
|
} else {
|
||||||
|
name[0] = 0;
|
||||||
|
}
|
||||||
|
if (mods[i].path.va) {
|
||||||
|
gva_read_text(ctx, pr->cr3, mods[i].path.va, mods[i].path.len, path, sizeof path);
|
||||||
|
} else {
|
||||||
|
path[0] = 0;
|
||||||
|
}
|
||||||
|
printf(" %016" PRIx64 " %8" PRIu32 " %-24s %s\n",
|
||||||
|
mods[i].base, mods[i].size,
|
||||||
|
name[0] ? name : "<unreadable>",
|
||||||
|
path[0] ? path : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dump_regions(gva_ctx* ctx, const process* pr) {
|
||||||
|
vregion* rg = malloc((size_t)RGN_CAP * sizeof *rg);
|
||||||
|
if (!rg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int total = gva_regions(ctx, pr->cr3, 0, ~0ull, 0, rg, RGN_CAP);
|
||||||
|
const int shown = total < 0 ? 0 : (total < RGN_CAP ? total : RGN_CAP);
|
||||||
|
for (int i = 0; i < shown; i++) {
|
||||||
|
char prot[5];
|
||||||
|
decode_prot(rg[i].prot, prot);
|
||||||
|
printf(" %016" PRIx64 " %12" PRIu64 " %s\n", rg[i].va, rg[i].len, prot);
|
||||||
|
}
|
||||||
|
if (total > shown) {
|
||||||
|
printf(" ... (%d more regions truncated)\n", total - shown);
|
||||||
|
}
|
||||||
|
free(rg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc < 3) {
|
||||||
|
fprintf(stderr, "usage: %s <ram-file> <low> [nmax]\n",
|
||||||
|
argc > 0 ? argv[0] : "w32ms_cli");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ram_path = argv[1];
|
||||||
|
const uint64_t low = strtoull(argv[2], NULL, 0);
|
||||||
|
size_t nmax = DEFAULT_NMAX;
|
||||||
|
if (argc > 3) {
|
||||||
|
const unsigned long long v = strtoull(argv[3], NULL, 0);
|
||||||
|
if (v > 0) {
|
||||||
|
nmax = (size_t)v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gva_ctx* ctx = gva_ctx_alloc(ram_path, low);
|
||||||
|
if (!ctx) {
|
||||||
|
fprintf(stderr, "error: cannot open RAM backing file '%s'\n", ram_path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int rc = host_bootstrap(ctx);
|
||||||
|
if (rc != 0) {
|
||||||
|
fprintf(stderr, "error: bootstrap failed (%d): %s\n", rc, bootstrap_stage(rc));
|
||||||
|
gva_ctx_free(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
process* procs = malloc(nmax * sizeof *procs);
|
||||||
|
if (!procs) {
|
||||||
|
fprintf(stderr, "error: out of memory\n");
|
||||||
|
gva_ctx_free(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int np = proc_list(ctx, 1, procs, nmax);
|
||||||
|
if (np < 0) {
|
||||||
|
fprintf(stderr, "error: proc_list failed (%d)\n", np);
|
||||||
|
free(procs);
|
||||||
|
gva_ctx_free(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%-6s %-6s %-16s %-18s %-18s %-18s\n",
|
||||||
|
"PID", "PPID", "NAME", "CR3", "EPROCESS", "PEB");
|
||||||
|
for (int i = 0; i < np; i++) {
|
||||||
|
const process* p = &procs[i];
|
||||||
|
char ppid[12];
|
||||||
|
if (p->ppid == (uint32_t)-1) {
|
||||||
|
snprintf(ppid, sizeof ppid, "%s", "?");
|
||||||
|
} else {
|
||||||
|
snprintf(ppid, sizeof ppid, "%" PRIu32, p->ppid);
|
||||||
|
}
|
||||||
|
printf("%-6" PRIu32 " %-6s %-16s %016" PRIx64 " %016" PRIx64 " %016" PRIx64 "\n",
|
||||||
|
p->pid, ppid, p->name[0] ? p->name : "<unnamed>",
|
||||||
|
p->cr3, p->eprocess, p->peb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (np > 0) {
|
||||||
|
const process* first = &procs[0];
|
||||||
|
printf("\nmodules of PID %" PRIu32 " (%s):\n",
|
||||||
|
first->pid, first->name[0] ? first->name : "<unnamed>");
|
||||||
|
dump_modules(ctx, first);
|
||||||
|
printf("\nregions of PID %" PRIu32 " (%s):\n",
|
||||||
|
first->pid, first->name[0] ? first->name : "<unnamed>");
|
||||||
|
dump_regions(ctx, first);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(procs);
|
||||||
|
gva_ctx_free(ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include "include/memory.h"
|
||||||
|
|
||||||
|
#define RAM_H (1ul<<32)
|
||||||
|
#define PROT_RW (PROT_READ | PROT_WRITE)
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
static int gpa_offset(const gpa_ctx* ctx, uintptr_t offs, uintptr_t* out) {
|
||||||
|
if (offs < ctx->low) {
|
||||||
|
*out = offs;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offs >= RAM_H) {
|
||||||
|
const uint64_t x = ctx->low + (offs - RAM_H);
|
||||||
|
|
||||||
|
if (x < ctx->fsize) {
|
||||||
|
*out = x;
|
||||||
|
return 0;
|
||||||
|
} else { /* Out of bounds */ }
|
||||||
|
} else { /* Not RAM */ }
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clean_ctx(gpa_ctx* ctx) {
|
||||||
|
memset(ctx, 0, sizeof(gpa_ctx));
|
||||||
|
ctx->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int out_of_bounds(gpa_ctx* ctx, uintptr_t* offs, const size_t nmemb) {
|
||||||
|
return gpa_offset(ctx, *offs, offs) || *offs + nmemb > ctx->fsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
int gpa_read(gpa_ctx* ctx, uintptr_t offs, void* buf, const size_t nmemb) {
|
||||||
|
if (out_of_bounds(ctx, &offs, nmemb)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(buf, ctx->pa + offs, nmemb);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gpa_write(gpa_ctx* ctx, uintptr_t offs, const void* src, const size_t nmemb) {
|
||||||
|
if (out_of_bounds(ctx, &offs, nmemb)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(ctx->pa + offs, src, nmemb);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zero-copy host pointer to [offs, offs+nmemb) GPA, or NULL if that range is not
|
||||||
|
* fully backed by the mapped image. Same split + bounds check as gpa_read. */
|
||||||
|
void* gpa_ptr(gpa_ctx* ctx, uintptr_t offs, const size_t nmemb) {
|
||||||
|
if (out_of_bounds(ctx, &offs, nmemb)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return (uint8_t*)ctx->pa + offs;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
int gpa_open(gpa_ctx* ctx, const char* path, uintptr_t low) {
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
if ((ctx->fd = open(path, O_RDWR)) < 0) {
|
||||||
|
goto ret_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fstat(ctx->fd, &st)) {
|
||||||
|
goto close_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ctx->pa = mmap(NULL, st.st_size, PROT_RW, MAP_SHARED, ctx->fd, 0)) == MAP_FAILED) {
|
||||||
|
close_:
|
||||||
|
close(ctx->fd);
|
||||||
|
ret_:
|
||||||
|
clean_ctx(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->fsize = st.st_size;
|
||||||
|
ctx->low = low;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
void gpa_close(gpa_ctx* ctx) {
|
||||||
|
if (ctx->pa) {
|
||||||
|
munmap(ctx->pa, ctx->fsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->fd >= 0) {
|
||||||
|
close(ctx->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_ctx(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#include "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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "include/memory.h"
|
||||||
|
#include "../include/include.h"
|
||||||
|
|
||||||
|
/* sign-extend a 48-bit canonical VA */
|
||||||
|
#define VA_CANON(v) (((v) & (1ull << 47)) ? ((v) | 0xFFFF000000000000ull) : (v))
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
static int gva_gpa(gva_ctx* ctx, uintptr_t cr3, uintptr_t va,
|
||||||
|
uintptr_t* gpa, size_t* leaf) {
|
||||||
|
uint64_t t = cr3 & PFN_MASK, e;
|
||||||
|
const unsigned i4 = (va >> 39) & 0x1ff, i3 = (va >> 30) & 0x1ff,
|
||||||
|
i2 = (va >> 21) & 0x1ff, i1 = (va >> 12) & 0x1ff;
|
||||||
|
|
||||||
|
if (gpa_read(&p_(ctx), t + i4 * 8, &e, 8) || !(e & PG_P)) return -1;
|
||||||
|
t = e & PFN_MASK;
|
||||||
|
if (gpa_read(&p_(ctx), t + i3 * 8, &e, 8) || !(e & 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 (gpa_read(&p_(ctx), t + i2 * 8, &e, 8) || !(e & 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 (gpa_read(&p_(ctx), t + i1 * 8, &e, 8) || !(e & 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
int gva_read(gva_ctx* ctx, 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(ctx, cr3, va, &gpa, &leaf)) return -1;
|
||||||
|
const size_t n = leaf < nmemb ? leaf : nmemb;
|
||||||
|
if (gpa_read(&p_(ctx), gpa, d, n)) return -1;
|
||||||
|
va += n; d += n; nmemb -= n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
int gva_write(gva_ctx* ctx, 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(ctx, cr3, va, &gpa, &leaf)) return -1;
|
||||||
|
const size_t n = leaf < nmemb ? leaf : nmemb;
|
||||||
|
if (gpa_write(&p_(ctx), gpa, s, n)) return -1;
|
||||||
|
va += n; s += n; nmemb -= n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- bootstrap helpers (cold) -------------------------------------------- */
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
int khalf_score(const gva_ctx* ctx, 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((gpa_ctx*)&p_(ctx), t + i * 8, &e, 8) && (e & PG_P)) n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
int cr3_recover(gva_ctx* ctx, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) {
|
||||||
|
int best_score = -1; uint64_t best = 0;
|
||||||
|
for (size_t off = 0; off + 0x1000 <= p_(ctx).fsize; off += 0x1000) {
|
||||||
|
const uintptr_t cand = offset_gpa(&p_(ctx), off);
|
||||||
|
uintptr_t gpa;
|
||||||
|
if (gva_gpa(ctx, cand, va_self, &gpa, NULL)) continue;
|
||||||
|
if ((gpa & ~0xFFFull) != (target_pa & ~0xFFFull)) continue;
|
||||||
|
const int score = khalf_score(ctx, 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))
|
||||||
|
gva_ctx* gva_ctx_alloc(const char* ram_path, uint64_t low) {
|
||||||
|
gva_ctx* ctx = calloc(1, sizeof *ctx);
|
||||||
|
if (!ctx) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (gpa_open(&ctx->mem, ram_path, low)) {
|
||||||
|
free(ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
void gva_ctx_free(gva_ctx* ctx) {
|
||||||
|
if (!ctx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpa_close(&ctx->mem);
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int gva_regions(gva_ctx* ctx, 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(&p_(ctx), 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(&p_(ctx), 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(&p_(ctx), 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(&p_(ctx), 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 */
|
||||||
|
|
||||||
|
int gva_sweep(gva_ctx* ctx, 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(ctx, 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;
|
||||||
|
size_t fill = 0;
|
||||||
|
|
||||||
|
while (va < vend) {
|
||||||
|
size_t pg = 0x1000 - (size_t)(va & 0xFFF); /* to page edge */
|
||||||
|
if (pg > (size_t)(vend - va)) pg = (size_t)(vend - va);
|
||||||
|
if (pg > SWEEP_WIN - fill) pg = SWEEP_WIN - fill;
|
||||||
|
|
||||||
|
if (gva_read(ctx, cr3, va, buf + fill, pg)) { /* gap: flush+skip */
|
||||||
|
if (fill && cb(user, buf, fill, base, overlap, 1)) { rc = 1; break; }
|
||||||
|
va += 0x1000 - (va & 0xFFF);
|
||||||
|
base = va; fill = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fill += pg; va += pg;
|
||||||
|
|
||||||
|
if (fill == SWEEP_WIN) {
|
||||||
|
const int last = (va >= vend);
|
||||||
|
if (cb(user, buf, fill, base, overlap, last)) { rc = 1; break; }
|
||||||
|
if (last || 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;
|
||||||
|
}
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "../include/include.h"
|
||||||
|
#include "include/contract.h"
|
||||||
|
#include "include/memory.h"
|
||||||
|
|
||||||
|
#define MZ 0x5A4Du
|
||||||
|
#define DIR_EXPORT 0u
|
||||||
|
#define DIR_DEBUG 6u
|
||||||
|
#define DBG_CODEVIEW 2u
|
||||||
|
#define CV_RSDS 0x53445352u
|
||||||
|
|
||||||
|
static int beacon_find(gva_ctx* ctx, uint64_t* pa, uint64_t* va) {
|
||||||
|
void *ptr = p_(ctx).pa;
|
||||||
|
const void *end = p_(ctx).pa + p_(ctx).fsize;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const contract* c = (void*)ptr;
|
||||||
|
if (c->magic0 == CONTRACT_MAGIC0 && c->magic1 == CONTRACT_MAGIC1) {
|
||||||
|
*pa = offset_gpa(&p_(ctx), ptr - p_(ctx).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(gva_ctx* ctx, uintptr_t cr3, uint64_t base, unsigned idx, uint32_t* rva, uint32_t* size) {
|
||||||
|
uint32_t lfanew;
|
||||||
|
if (gva_read(ctx, cr3, base + 0x3C, &lfanew, 4)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const uint64_t dd = base + lfanew + 0x18 + 0x70 + (uint64_t)idx*8;
|
||||||
|
if (gva_read(ctx, cr3, dd, rva, 4)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return (size && gva_read(ctx, cr3, dd + 4, size, 4)) ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pe_pdb(gva_ctx* ctx, 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(ctx, 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(ctx, cr3, base + dbg_rva + o + 0x0C, &type, 4)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (type != DBG_CODEVIEW) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (gva_read(ctx, cr3, base + dbg_rva + o + 0x14, &cv_rva, 4)) { /* AddressOfRawData RVA */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (gva_read(ctx, cr3, base + cv_rva, &sig, 4) || sig != CV_RSDS) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (gva_read(ctx, cr3, base + cv_rva + 0x04, guid, 16)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (gva_read(ctx, cr3, base + cv_rva + 0x14, age, 4)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
gva_read(ctx, cr3, base + cv_rva + 0x18, name, namecap); /* best-effort */
|
||||||
|
name[namecap - 1] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int find_ntoskrnl(gva_ctx* ctx, 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(&p_(ctx), 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(&p_(ctx), 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(&p_(ctx), pd + p2*8, &e2, 8) || !(e2 & PG_P)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t va = (uint64_t)p4<<39 | (uint64_t)p3<<30 | (uint64_t)p2<<21;
|
||||||
|
if (va & (1ull<<47)) {
|
||||||
|
va |= 0xFFFF000000000000ull; /* canonical sign-extend */
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t mz; char pdb[16];
|
||||||
|
if (gva_read(ctx, cr3, va, &mz, 2) || mz != MZ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pe_pdb(ctx, 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(gva_ctx* ctx, uintptr_t cr3, uint64_t kbase, const char* want) {
|
||||||
|
uint32_t exp_rva;
|
||||||
|
if (pe_datadir(ctx, cr3, kbase, DIR_EXPORT, &exp_rva, NULL) || !exp_rva) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ed[40];
|
||||||
|
if (gva_read(ctx, cr3, kbase + exp_rva, ed, sizeof ed)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const uint32_t nnames = *(uint32_t*)(ed + 0x18);
|
||||||
|
const uint32_t a_funcs = *(uint32_t*)(ed + 0x1C);
|
||||||
|
const uint32_t a_names = *(uint32_t*)(ed + 0x20);
|
||||||
|
const uint32_t a_ords = *(uint32_t*)(ed + 0x24);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < nnames; i++) {
|
||||||
|
uint32_t nrva; char nm[40];
|
||||||
|
if (gva_read(ctx, cr3, kbase + a_names + i*4, &nrva, 4)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (gva_read(ctx, 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(ctx, cr3, kbase + a_ords + i*2, &ord, 2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return gva_read(ctx, cr3, kbase + a_funcs + ord*4, &frva, 4) ? 0 : frva;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void beacon_ack(gva_ctx* ctx, uint64_t anchor_pa) {
|
||||||
|
uint64_t ack = CONTRACT_ACK;
|
||||||
|
gpa_write(&p_(ctx), anchor_pa + offsetof(contract, ack), &ack, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
int host_bootstrap(gva_ctx* ctx) {
|
||||||
|
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(ctx, &anchor_pa, &va_self)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cr3_recover(ctx, va_self, anchor_pa, &cr3boot)) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (find_ntoskrnl(ctx, cr3boot, &ctx->kbase, guid, &age)) {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
rva = ko_export_rva(ctx, cr3boot, ctx->kbase, "PsInitialSystemProcess");
|
||||||
|
if (!rva || gva_read(ctx, cr3boot, ctx->kbase + rva, &sys_ep, 8)) {
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile_build(ctx, cr3boot, sys_ep, guid, age)) {
|
||||||
|
return -5;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t dtb;
|
||||||
|
if (gva_read(ctx, cr3boot, sys_ep + ctx->prof.ep_dtb, &dtb, 8)) {
|
||||||
|
return -6;
|
||||||
|
}
|
||||||
|
ctx->kcr3 = dtb & PFN_MASK;
|
||||||
|
ctx->sysproc = sys_ep;
|
||||||
|
|
||||||
|
beacon_ack(ctx, anchor_pa);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -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 W32MS_CONTRACT_H
|
||||||
|
#define W32MS_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
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#ifndef W32MS_MEMORY_H
|
||||||
|
#define W32MS_MEMORY_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
struct gva_ctx; /* forward: completed below; lets profile.h name it */
|
||||||
|
#include "profile.h"
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
/* Canonical VA window bounds, single-sourced here for every scanning TU.
|
||||||
|
* USER_MIN is 0x10000: Windows reserves the low 64 KiB, so no live user pointer
|
||||||
|
* targets below it - starting there drops a class of false positives. */
|
||||||
|
#define USER_MIN 0x0000000000010000ull
|
||||||
|
#define USER_MAX 0x00007FFFFFFFFFFFull
|
||||||
|
#define KERN_MIN 0xFFFF800000000000ull
|
||||||
|
|
||||||
|
/* Flat RW mmap of the guest RAM backing file. GPA<->file offset has one split
|
||||||
|
* at the 4 GiB PCI hole: [0,low) maps 1:1; [4G,..) maps to file [low,..). */
|
||||||
|
typedef struct gpa_ctx {
|
||||||
|
void* pa;
|
||||||
|
uint64_t low;
|
||||||
|
size_t fsize;
|
||||||
|
int fd;
|
||||||
|
} gpa_ctx;
|
||||||
|
|
||||||
|
/* 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 gva_ctx* aliases a gpa_ctx*. prof carried by value. */
|
||||||
|
typedef struct gva_ctx {
|
||||||
|
gpa_ctx mem;
|
||||||
|
uint64_t kcr3;
|
||||||
|
uint64_t kbase;
|
||||||
|
uint64_t sysproc;
|
||||||
|
profile prof;
|
||||||
|
} gva_ctx;
|
||||||
|
|
||||||
|
#define p_(c) ((c)->mem)
|
||||||
|
|
||||||
|
/* GPA <-> file-offset converters: a single split at the 4 GiB PCI hole.
|
||||||
|
* forward (GPA -> offset) lives in gpa.c (it also carries the bounds check);
|
||||||
|
* inverse (offset -> GPA) is this static inline, shared across PT-walking TUs. */
|
||||||
|
static inline uintptr_t offset_gpa(const gpa_ctx* ctx, uintptr_t addr) {
|
||||||
|
return addr < ctx->low ? addr : (1ull << 32) + (addr - ctx->low);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* guest-physical lifecycle + primitives (gpa.c) */
|
||||||
|
int gpa_open (gpa_ctx* ctx, const char* path, uintptr_t low);
|
||||||
|
void gpa_close(gpa_ctx* ctx);
|
||||||
|
int gpa_read (gpa_ctx* ctx, uintptr_t offs, void* dst, size_t nmemb);
|
||||||
|
int gpa_write(gpa_ctx* ctx, uintptr_t offs, const void* src, size_t nmemb);
|
||||||
|
|
||||||
|
/* Zero-copy access to guest physical memory. Returns a host pointer to `nmemb`
|
||||||
|
* contiguous bytes at GPA `offs`, or NULL if that range is not fully backed by
|
||||||
|
* the mapped image. Valid until the context is freed; writes hit the live guest
|
||||||
|
* (MAP_SHARED). A single 4K/2M/1G leaf never straddles the 4G split, so the
|
||||||
|
* whole leaf (or a 4096-byte page table) can be taken in one call. */
|
||||||
|
void* gpa_ptr(gpa_ctx* ctx, uintptr_t offs, size_t nmemb);
|
||||||
|
|
||||||
|
/* bootstrap helpers (gva.c) */
|
||||||
|
int khalf_score(const gva_ctx* ctx, uint64_t pml4) __attribute__((cold));
|
||||||
|
int cr3_recover(gva_ctx* ctx, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) __attribute__((cold));
|
||||||
|
|
||||||
|
/* ---- shared windowed sweep engine (gva.c) -------------------------------- *
|
||||||
|
* gva_sweep() streams every mapped byte under `cr3` within [lo,hi] that passes
|
||||||
|
* the protection filter to `cb`, one contiguous window at a time. Physical
|
||||||
|
* fragmentation is hidden: each window is a flat buffer (gva_read-filled), and
|
||||||
|
* adjacent windows of one run share `overlap` leading bytes so an object or
|
||||||
|
* pattern straddling a window boundary is still seen whole. Both the value
|
||||||
|
* scanner and the signature scanner ride this one primitive. */
|
||||||
|
typedef int (*gva_sweep_cb)(void* user, const uint8_t* data, size_t len,
|
||||||
|
uint64_t base_va, size_t overlap, int last);
|
||||||
|
/* user - passed through verbatim
|
||||||
|
* data - host buffer with `len` valid bytes (do not retain past the call)
|
||||||
|
* len - valid bytes at data
|
||||||
|
* base_va - guest VA of data[0]
|
||||||
|
* overlap - bytes at the front of `data` shared with the previous window of
|
||||||
|
* this run (0 on a run's first window or right after a gap)
|
||||||
|
* last - nonzero if this window ends a contiguous segment (run end / gap):
|
||||||
|
* accept hits up to `len`; otherwise drop hits starting in the
|
||||||
|
* trailing `overlap` zone, the next window re-presents them
|
||||||
|
* cb returns nonzero to abort the sweep early (e.g. result buffer full).
|
||||||
|
*
|
||||||
|
* gva_sweep() returns 0 normally, 1 if a callback aborted it, -1 on allocation
|
||||||
|
* failure. `overlap` must be < the internal window (1 MiB); patterns longer
|
||||||
|
* than that are not supported by the windowed path. */
|
||||||
|
int gva_sweep(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
|
||||||
|
uint32_t prot_any, size_t overlap, gva_sweep_cb cb, void* user);
|
||||||
|
|
||||||
|
#endif /* W32MS_MEMORY_H */
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef W32MS_PROFILE_H
|
||||||
|
#define W32MS_PROFILE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
int profile_build(struct gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "include/memory.h"
|
||||||
|
#include "../include/include.h"
|
||||||
|
|
||||||
|
#define pr_(ctx) (ctx->prof)
|
||||||
|
|
||||||
|
#define RING_GUARD 100000u
|
||||||
|
#define MOD_GUARD 4096u
|
||||||
|
|
||||||
|
static void grab_ustr(gva_ctx* ctx, uintptr_t cr3, uint64_t va, gtext* out) {
|
||||||
|
uint16_t len = 0;
|
||||||
|
uint64_t buf = 0;
|
||||||
|
out->va = 0;
|
||||||
|
out->len = 0;
|
||||||
|
if (gva_read(ctx, cr3, va, &len, 2) || gva_read(ctx, cr3, va + 8, &buf, 8)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out->va = buf;
|
||||||
|
out->len = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int proc_list(gva_ctx* ctx, int skip_system, process* dst, size_t nmax) {
|
||||||
|
const profile* p = &pr_(ctx);
|
||||||
|
const uint64_t kcr3 = ctx->kcr3;
|
||||||
|
if (!kcr3 || !ctx->sysproc) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t n = 0;
|
||||||
|
unsigned guard = 0;
|
||||||
|
uint64_t ep = ctx->sysproc, node;
|
||||||
|
|
||||||
|
do {
|
||||||
|
uint64_t pid = 0, ppid = 0, dtb = 0, peb = 0;
|
||||||
|
gva_read(ctx, kcr3, ep + p->ep_pid, &pid, 8);
|
||||||
|
gva_read(ctx, kcr3, ep + p->ep_dtb, &dtb, 8);
|
||||||
|
if (p->ep_peb) { gva_read(ctx, kcr3, ep + p->ep_peb, &peb, 8); }
|
||||||
|
if (p->ep_ppid) { gva_read(ctx, 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(ctx, kcr3, ep + p->ep_createtime, &q->create_time, 8);
|
||||||
|
}
|
||||||
|
memset(q->name, 0, sizeof q->name);
|
||||||
|
gva_read(ctx, 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(ctx, kcr3, ep + p->ep_imgpath, &q->path); /* read text under kcr3 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gva_read(ctx, kcr3, ep + p->ep_links, &node, 8)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ep = node - p->ep_links;
|
||||||
|
} while (ep != ctx->sysproc && ++guard < RING_GUARD);
|
||||||
|
|
||||||
|
return (int)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int proc_modules(gva_ctx* ctx, const process* pr, pmodule* dst, size_t nmax) {
|
||||||
|
const profile* p = &pr_(ctx);
|
||||||
|
const uint64_t cr3 = pr->cr3;
|
||||||
|
if (!pr->peb || !cr3) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ldr = 0, head, link;
|
||||||
|
if (gva_read(ctx, cr3, pr->peb + p->peb_ldr, &ldr, 8) || !ldr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
head = ldr + p->ldr_loadlist;
|
||||||
|
if (gva_read(ctx, 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(ctx, cr3, entry + p->lde_base, &base, 8);
|
||||||
|
gva_read(ctx, cr3, entry + p->lde_size, &size, 4);
|
||||||
|
|
||||||
|
pmodule* m = &dst[n++];
|
||||||
|
m->pr = pr;
|
||||||
|
m->entry = entry;
|
||||||
|
m->base = base;
|
||||||
|
m->size = size;
|
||||||
|
grab_ustr(ctx, cr3, entry + p->lde_name, &m->name);
|
||||||
|
grab_ustr(ctx, cr3, entry + p->lde_fullname, &m->path);
|
||||||
|
|
||||||
|
if (gva_read(ctx, cr3, link, &link, 8)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (int)n;
|
||||||
|
}
|
||||||
+278
@@ -0,0 +1,278 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../include/include.h"
|
||||||
|
#include "include/memory.h"
|
||||||
|
|
||||||
|
#define pr_(ctx) ((ctx)->prof)
|
||||||
|
|
||||||
|
#define RING_CAP 4096 /* USER_MIN/USER_MAX/KERN_MIN come from include/memory.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(gva_ctx* ctx, uintptr_t cr3, uint64_t head, int kernel) {
|
||||||
|
uint64_t node;
|
||||||
|
if (gva_read(ctx, 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(ctx, 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(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep) {
|
||||||
|
profile* p = &pr_(ctx);
|
||||||
|
uint8_t buf[0x800];
|
||||||
|
if (gva_read(ctx, 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) {
|
||||||
|
if (*(uint64_t*)(buf + o) != 4) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint16_t links = (uint16_t)(o + 8);
|
||||||
|
if (list_ring_ok(ctx, 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) {
|
||||||
|
const uint64_t c = *(uint64_t*)(buf + o) & PFN_MASK;
|
||||||
|
uint8_t probe;
|
||||||
|
if (c && khalf_score(ctx, c) >= 16 && !gva_read(ctx, 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(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep, uint64_t* eps, uint32_t* pids, uint64_t* cr3s, int cap) {
|
||||||
|
const profile* p = &pr_(ctx);
|
||||||
|
int n = 0;
|
||||||
|
uint64_t ep = sys_ep, node;
|
||||||
|
do {
|
||||||
|
uint64_t pid = 0, dtb = 0;
|
||||||
|
gva_read(ctx, cr3, ep + p->ep_pid, &pid, 8);
|
||||||
|
gva_read(ctx, 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(ctx, 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(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, const uint32_t* pids, int n) {
|
||||||
|
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(ctx, 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_(ctx).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(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, int n) {
|
||||||
|
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(ctx, 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_(ctx).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(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
|
||||||
|
profile* p = &pr_(ctx);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (!cr3s[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
char nm[16] = {0};
|
||||||
|
if (gva_read(ctx, 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(ctx, cr3, eps[i] + o, &len, 2) || gva_read(ctx, 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(ctx, 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(gva_ctx* ctx, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
|
||||||
|
profile* p = &pr_(ctx);
|
||||||
|
|
||||||
|
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(ctx, 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(ctx, pcr3, peb + lo, &ldr, 8) || !canon_ok(ldr, 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int ll = 0x10; ll <= 0x20; ll += 8) {
|
||||||
|
if (!list_ring_ok(ctx, pcr3, ldr + ll, 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t entry = 0, dllbase = 0, bufp = 0, fbufp = 0;
|
||||||
|
uint16_t nlen = 0, flen = 0;
|
||||||
|
if (gva_read(ctx, pcr3, ldr + ll, &entry, 8)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (gva_read(ctx, pcr3, entry + 0x30, &dllbase, 8) ||
|
||||||
|
!canon_ok(dllbase, 0) || (dllbase & 0xFFF)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (gva_read(ctx, pcr3, entry + 0x58, &nlen, 2) || !nlen || (nlen & 1) ||
|
||||||
|
gva_read(ctx, pcr3, entry + 0x58 + 8, &bufp, 8) || !canon_ok(bufp, 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (gva_read(ctx, pcr3, entry + 0x48, &flen, 2) || (flen & 1) ||
|
||||||
|
gva_read(ctx, 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(gva_ctx* ctx, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age) {
|
||||||
|
memset(&pr_(ctx), 0, sizeof(pr_(ctx)));
|
||||||
|
memcpy(pr_(ctx).guid, guid, 16);
|
||||||
|
pr_(ctx).age = age;
|
||||||
|
|
||||||
|
if (discover_core(ctx, cr3, sys_ep)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t eps[SCAN_MAX], cr3s[SCAN_MAX];
|
||||||
|
uint32_t pids[SCAN_MAX];
|
||||||
|
const int n = collect_procs(ctx, cr3, sys_ep, eps, pids, cr3s, SCAN_MAX);
|
||||||
|
if (n <= 1) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
discover_ppid(ctx, cr3, eps, pids, n);
|
||||||
|
discover_createtime(ctx, cr3, eps, n);
|
||||||
|
discover_imgpath(ctx, cr3, eps, cr3s, n);
|
||||||
|
if (discover_user_chain(ctx, cr3, eps, cr3s, n)) {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+430
@@ -0,0 +1,430 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../include/include.h"
|
||||||
|
#include "../include/sigscan.h"
|
||||||
|
#include "../include/scan.h"
|
||||||
|
#include "include/memory.h"
|
||||||
|
|
||||||
|
#define REG_CAP (1 << 16) /* USER_MIN/USER_MAX come from include/memory.h */
|
||||||
|
|
||||||
|
/* ---- typed value codec --------------------------------------------------- */
|
||||||
|
|
||||||
|
static const uint8_t g_tsz[] = { 1,2,4,8, 1,2,4,8, 4,8, 2 };
|
||||||
|
|
||||||
|
enum { K_S, K_U, K_F };
|
||||||
|
static int type_kind(scan_type t) {
|
||||||
|
switch (t) {
|
||||||
|
case SCAN_I8: case SCAN_I16: case SCAN_I32: case SCAN_I64: return K_S;
|
||||||
|
case SCAN_U8: case SCAN_U16: case SCAN_U32: case SCAN_U64: return K_U;
|
||||||
|
default: return K_F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t bswap(uint64_t v, int sz) {
|
||||||
|
uint64_t r = 0;
|
||||||
|
for (int i = 0; i < sz; i++) { r = (r << 8) | (v & 0xff); v >>= 8; }
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IEEE half -> float */
|
||||||
|
static float h2f(uint16_t h) {
|
||||||
|
uint32_t s = (h >> 15) & 1, e = (h >> 10) & 0x1f, m = h & 0x3ff, out;
|
||||||
|
if (e == 0) {
|
||||||
|
if (m == 0) out = s << 31;
|
||||||
|
else { e = 127 - 15 + 1; while (!(m & 0x400)) { m <<= 1; e--; } m &= 0x3ff;
|
||||||
|
out = (s << 31) | (e << 23) | (m << 13); }
|
||||||
|
} else if (e == 0x1f) {
|
||||||
|
out = (s << 31) | (0xffu << 23) | (m << 13);
|
||||||
|
} else {
|
||||||
|
out = (s << 31) | ((e - 15 + 127) << 23) | (m << 13);
|
||||||
|
}
|
||||||
|
float f; memcpy(&f, &out, 4); return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct { int kind; int64_t i; uint64_t u; double f; } sval;
|
||||||
|
|
||||||
|
static sval sload(const uint8_t* p, scan_type t, int be) {
|
||||||
|
sval v; v.kind = type_kind(t);
|
||||||
|
const int sz = g_tsz[t];
|
||||||
|
uint64_t raw = 0; memcpy(&raw, p, sz);
|
||||||
|
if (be) raw = bswap(raw, sz);
|
||||||
|
if (v.kind == K_F) {
|
||||||
|
if (t == SCAN_F16) v.f = h2f((uint16_t)raw);
|
||||||
|
else if (t == SCAN_F32) { float f; uint32_t u = (uint32_t)raw; memcpy(&f, &u, 4); v.f = f; }
|
||||||
|
else { double d; memcpy(&d, &raw, 8); v.f = d; }
|
||||||
|
v.u = raw; v.i = (int64_t)raw;
|
||||||
|
} else if (v.kind == K_U) {
|
||||||
|
v.u = raw; v.i = (int64_t)raw; v.f = (double)raw;
|
||||||
|
} else {
|
||||||
|
int64_t s;
|
||||||
|
switch (sz) { case 1: s = (int8_t)raw; break; case 2: s = (int16_t)raw; break;
|
||||||
|
case 4: s = (int32_t)raw; break; default: s = (int64_t)raw; }
|
||||||
|
v.i = s; v.u = (uint64_t)s; v.f = (double)s;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* relative-epsilon float equality (exact == is too brittle for game values) */
|
||||||
|
static int feq(double a, double b) {
|
||||||
|
double d = a - b; if (d < 0) d = -d;
|
||||||
|
double m = b < 0 ? -b : b; if (m < 1) m = 1;
|
||||||
|
return d <= 1e-3 * m;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int scmp(scan_op op, sval c, sval r) {
|
||||||
|
switch (op) {
|
||||||
|
case SCAN_EQ: return c.kind == K_F ? feq(c.f, r.f) : (c.kind == K_U ? c.u == r.u : c.i == r.i);
|
||||||
|
case SCAN_NEQ: return !scmp(SCAN_EQ, c, r);
|
||||||
|
case SCAN_GT: return c.kind == K_F ? c.f > r.f : (c.kind == K_U ? c.u > r.u : c.i > r.i);
|
||||||
|
case SCAN_LT: return c.kind == K_F ? c.f < r.f : (c.kind == K_U ? c.u < r.u : c.i < r.i);
|
||||||
|
case SCAN_INC: return scmp(SCAN_GT, c, r);
|
||||||
|
case SCAN_DEC: return scmp(SCAN_LT, c, r);
|
||||||
|
case SCAN_CHANGED: return scmp(SCAN_NEQ, c, r);
|
||||||
|
case SCAN_UNCHANGED: return scmp(SCAN_EQ, c, r);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int needs_value(scan_op op) {
|
||||||
|
return op == SCAN_EQ || op == SCAN_NEQ || op == SCAN_GT || op == SCAN_LT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- scan session -------------------------------------------------------- */
|
||||||
|
|
||||||
|
struct scan {
|
||||||
|
gva_ctx* ctx;
|
||||||
|
uintptr_t cr3;
|
||||||
|
scan_type type; int tsz; int be; int aligned; size_t step;
|
||||||
|
uint64_t lo, hi;
|
||||||
|
int snap; /* unknown-mode snapshot pending */
|
||||||
|
vregion* regs; int nregs;
|
||||||
|
uint8_t** snapbuf;
|
||||||
|
uint64_t* addr; /* SoA candidate list */
|
||||||
|
uint8_t* val;
|
||||||
|
uint64_t n, cap;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int push(struct scan* s, uint64_t addr, const uint8_t* val) {
|
||||||
|
if (s->n == s->cap) {
|
||||||
|
const uint64_t nc = s->cap ? s->cap * 2 : 4096;
|
||||||
|
uint64_t* na = realloc(s->addr, nc * sizeof *na);
|
||||||
|
if (!na) return -1;
|
||||||
|
s->addr = na;
|
||||||
|
uint8_t* nv = realloc(s->val, nc * (size_t)s->tsz);
|
||||||
|
if (!nv) return -1;
|
||||||
|
s->val = nv; s->cap = nc;
|
||||||
|
}
|
||||||
|
s->addr[s->n] = addr;
|
||||||
|
memcpy(s->val + s->n * (size_t)s->tsz, val, s->tsz);
|
||||||
|
s->n++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* exact first scan: EQ over swept windows */
|
||||||
|
struct vfirst { struct scan* s; sval ref; };
|
||||||
|
static int vfirst_cb(void* u, const uint8_t* data, size_t len,
|
||||||
|
uint64_t base, size_t ov, int last) {
|
||||||
|
struct vfirst* c = u; struct scan* s = c->s;
|
||||||
|
const size_t tsz = s->tsz;
|
||||||
|
size_t off = 0;
|
||||||
|
if (s->aligned) { const size_t m = (size_t)(base % tsz); if (m) off = tsz - m; }
|
||||||
|
const size_t limit = last ? len : (len > ov ? len - ov : 0);
|
||||||
|
for (; off + tsz <= len; off += s->step) {
|
||||||
|
if (!last && off >= limit) break;
|
||||||
|
const sval cur = sload(data + off, s->type, s->be);
|
||||||
|
if (scmp(SCAN_EQ, cur, c->ref) && push(s, base + off, data + off)) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct scan* scan_make(gva_ctx* ctx, uintptr_t cr3, scan_type t, const void* value,
|
||||||
|
int be, int aligned, uint64_t lo, uint64_t hi) {
|
||||||
|
struct scan* s = calloc(1, sizeof *s);
|
||||||
|
if (!s) return NULL;
|
||||||
|
s->ctx = ctx; s->cr3 = cr3; s->type = t; s->tsz = g_tsz[t];
|
||||||
|
s->be = be; s->aligned = aligned; s->step = aligned ? (size_t)s->tsz : 1;
|
||||||
|
s->lo = lo; s->hi = hi;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
struct vfirst c = { s, sload((const uint8_t*)value, t, 0) };
|
||||||
|
gva_sweep(ctx, cr3, lo, hi, VR_W, aligned ? 0 : (size_t)(s->tsz - 1), vfirst_cb, &c);
|
||||||
|
} else {
|
||||||
|
s->snap = 1;
|
||||||
|
s->regs = malloc((size_t)REG_CAP * sizeof *s->regs);
|
||||||
|
if (!s->regs) { free(s); return NULL; }
|
||||||
|
s->nregs = gva_regions(ctx, cr3, lo, hi, VR_W, s->regs, REG_CAP);
|
||||||
|
if (s->nregs > REG_CAP) s->nregs = REG_CAP;
|
||||||
|
s->snapbuf = calloc((size_t)s->nregs, sizeof(uint8_t*));
|
||||||
|
if (!s->snapbuf && s->nregs) { free(s->regs); free(s); return NULL; }
|
||||||
|
for (int i = 0; i < s->nregs; i++) {
|
||||||
|
uint8_t* b = malloc((size_t)s->regs[i].len);
|
||||||
|
if (!b) continue; /* skip a run we cannot hold */
|
||||||
|
if (gva_read(ctx, cr3, s->regs[i].va, b, (size_t)s->regs[i].len)) { free(b); b = NULL; }
|
||||||
|
s->snapbuf[i] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
scan* scan_new(gva_ctx* ctx, 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_make(ctx, pr->cr3, t, value, be, aligned, lo, hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
scan* scan_new_cr3(gva_ctx* ctx, uintptr_t cr3, scan_type t, const void* value,
|
||||||
|
int be, int aligned, uint64_t lo, uint64_t hi) {
|
||||||
|
return scan_make(ctx, cr3, t, value, be, aligned, lo, hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t scan_next(scan* s, scan_op op, const void* value) {
|
||||||
|
if (!s) return -1;
|
||||||
|
if (needs_value(op) && !value) return -1;
|
||||||
|
sval ref; int have_ref = 0;
|
||||||
|
if (needs_value(op)) { ref = sload((const uint8_t*)value, s->type, 0); have_ref = 1; }
|
||||||
|
|
||||||
|
if (s->snap) { /* seed from snapshot */
|
||||||
|
for (int i = 0; i < s->nregs; i++) {
|
||||||
|
const uint8_t* snap = s->snapbuf[i];
|
||||||
|
if (!snap) continue;
|
||||||
|
const size_t len = (size_t)s->regs[i].len, tsz = s->tsz;
|
||||||
|
uint8_t* live = malloc(len);
|
||||||
|
if (!live) continue;
|
||||||
|
if (gva_read(s->ctx, s->cr3, s->regs[i].va, live, len)) { free(live); continue; }
|
||||||
|
size_t off = 0;
|
||||||
|
if (s->aligned) { const size_t m = (size_t)(s->regs[i].va % tsz); if (m) off = tsz - m; }
|
||||||
|
for (; off + tsz <= len; off += s->step) {
|
||||||
|
const sval cur = sload(live + off, s->type, s->be);
|
||||||
|
const sval r = have_ref ? ref : sload(snap + off, s->type, s->be);
|
||||||
|
if (scmp(op, cur, r) && push(s, s->regs[i].va + off, live + off)) { free(live); goto seeded; }
|
||||||
|
}
|
||||||
|
free(live);
|
||||||
|
}
|
||||||
|
seeded:
|
||||||
|
for (int i = 0; i < s->nregs; i++) free(s->snapbuf[i]);
|
||||||
|
free(s->snapbuf); s->snapbuf = NULL;
|
||||||
|
free(s->regs); s->regs = NULL; s->nregs = 0; s->snap = 0;
|
||||||
|
return (int64_t)s->n;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t w = 0; /* narrow in place */
|
||||||
|
for (uint64_t i = 0; i < s->n; i++) {
|
||||||
|
uint8_t cur_b[8];
|
||||||
|
if (gva_read(s->ctx, s->cr3, s->addr[i], cur_b, s->tsz)) continue; /* page gone */
|
||||||
|
const sval cur = sload(cur_b, s->type, s->be);
|
||||||
|
const sval r = have_ref ? ref : sload(s->val + i * (size_t)s->tsz, s->type, s->be);
|
||||||
|
if (scmp(op, cur, r)) {
|
||||||
|
s->addr[w] = s->addr[i];
|
||||||
|
memcpy(s->val + w * (size_t)s->tsz, cur_b, s->tsz);
|
||||||
|
w++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s->n = w;
|
||||||
|
return (int64_t)s->n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t scan_count(scan* s) { return s ? (int64_t)s->n : -1; }
|
||||||
|
|
||||||
|
int scan_results(scan* s, uint64_t offset, int max, scan_hit* out) {
|
||||||
|
if (!s || !out) return 0;
|
||||||
|
int k = 0;
|
||||||
|
for (uint64_t i = offset; i < s->n && k < max; i++, k++) {
|
||||||
|
out[k].addr = s->addr[i];
|
||||||
|
uint64_t v = 0; memcpy(&v, s->val + i * (size_t)s->tsz, s->tsz);
|
||||||
|
out[k].value = v;
|
||||||
|
}
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scan_free(scan* s) {
|
||||||
|
if (!s) return;
|
||||||
|
if (s->snapbuf) { for (int i = 0; i < s->nregs; i++) free(s->snapbuf[i]); free(s->snapbuf); }
|
||||||
|
free(s->regs); free(s->addr); free(s->val); free(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- pointer scan -------------------------------------------------------- */
|
||||||
|
|
||||||
|
struct pslot { uint64_t val, loc; };
|
||||||
|
static int ps_cmp(const void* a, const void* b) {
|
||||||
|
const uint64_t x = ((const struct pslot*)a)->val, y = ((const struct pslot*)b)->val;
|
||||||
|
return x < y ? -1 : (x > y ? 1 : 0);
|
||||||
|
}
|
||||||
|
static size_t pslot_lb(const struct pslot* a, size_t n, uint64_t key) {
|
||||||
|
size_t lo = 0, hi = n;
|
||||||
|
while (lo < hi) { const size_t m = (lo + hi) / 2; if (a[m].val < key) lo = m + 1; else hi = m; }
|
||||||
|
return lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pscan {
|
||||||
|
struct pslot* idx; size_t nidx;
|
||||||
|
pmodule* mods; int nmods;
|
||||||
|
uint32_t max_off; int max_depth;
|
||||||
|
scan_ptr_path* out; int max, n;
|
||||||
|
int32_t disc[SCAN_PTR_MAXDEPTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int in_module(struct pscan* P, uint64_t a) {
|
||||||
|
for (int i = 0; i < P->nmods; i++)
|
||||||
|
if (a >= P->mods[i].base && a < P->mods[i].base + P->mods[i].size) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ptr_dfs(struct pscan* P, uint64_t need, int hops) {
|
||||||
|
if (hops > 0 && in_module(P, need) && P->n < P->max) {
|
||||||
|
scan_ptr_path* o = &P->out[P->n++];
|
||||||
|
o->base = need; o->depth = hops;
|
||||||
|
for (int k = 0; k < hops; k++) o->off[k] = P->disc[hops - 1 - k];
|
||||||
|
}
|
||||||
|
if (hops >= P->max_depth || P->n >= P->max) return;
|
||||||
|
const uint64_t loV = need > P->max_off ? need - P->max_off : 0;
|
||||||
|
for (size_t i = pslot_lb(P->idx, P->nidx, loV);
|
||||||
|
i < P->nidx && P->idx[i].val <= need; i++) {
|
||||||
|
P->disc[hops] = (int32_t)(need - P->idx[i].val);
|
||||||
|
ptr_dfs(P, P->idx[i].loc, hops + 1);
|
||||||
|
if (P->n >= P->max) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int scan_pointer(gva_ctx* ctx, const process* pr, uint64_t target,
|
||||||
|
int max_depth, uint32_t max_off, scan_ptr_path* out, int max) {
|
||||||
|
if (!pr || max_depth < 1 || max < 1) return -1;
|
||||||
|
if (max_depth > SCAN_PTR_MAXDEPTH) max_depth = SCAN_PTR_MAXDEPTH;
|
||||||
|
const uintptr_t cr3 = pr->cr3;
|
||||||
|
|
||||||
|
vregion* rg = malloc((size_t)REG_CAP * sizeof *rg);
|
||||||
|
if (!rg) return -1;
|
||||||
|
int nr = gva_regions(ctx, cr3, USER_MIN, USER_MAX, VR_W, rg, REG_CAP);
|
||||||
|
if (nr > REG_CAP) nr = REG_CAP;
|
||||||
|
|
||||||
|
struct pslot* idx = NULL; size_t nidx = 0, capi = 0;
|
||||||
|
uint8_t* tmp = NULL; size_t tmpcap = 0;
|
||||||
|
for (int r = 0; r < nr; r++) {
|
||||||
|
const size_t len = (size_t)rg[r].len;
|
||||||
|
if (len > tmpcap) { uint8_t* nt = realloc(tmp, len); if (!nt) continue; tmp = nt; tmpcap = len; }
|
||||||
|
if (gva_read(ctx, cr3, rg[r].va, tmp, len)) continue;
|
||||||
|
for (size_t o = 0; o + 8 <= len; o += 8) {
|
||||||
|
uint64_t v; memcpy(&v, tmp + o, 8);
|
||||||
|
if (v < USER_MIN || v > USER_MAX) continue;
|
||||||
|
if (nidx == capi) {
|
||||||
|
const size_t nc = capi ? capi * 2 : 65536;
|
||||||
|
struct pslot* ni = realloc(idx, nc * sizeof *ni);
|
||||||
|
if (!ni) goto built;
|
||||||
|
idx = ni; capi = nc;
|
||||||
|
}
|
||||||
|
idx[nidx].val = v; idx[nidx].loc = rg[r].va + o; nidx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
built:
|
||||||
|
free(tmp);
|
||||||
|
qsort(idx, nidx, sizeof *idx, ps_cmp);
|
||||||
|
|
||||||
|
pmodule* mods = malloc(1024 * sizeof *mods);
|
||||||
|
int nm = mods ? proc_modules(ctx, pr, mods, 1024) : 0;
|
||||||
|
if (nm < 0) nm = 0;
|
||||||
|
|
||||||
|
struct pscan P; memset(&P, 0, sizeof P);
|
||||||
|
P.idx = idx; P.nidx = nidx; P.mods = mods; P.nmods = nm;
|
||||||
|
P.max_off = max_off; P.max_depth = max_depth; P.out = out; P.max = max;
|
||||||
|
ptr_dfs(&P, target, 0);
|
||||||
|
|
||||||
|
free(idx); free(mods); free(rg);
|
||||||
|
return P.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- guest signature bridges --------------------------------------------- */
|
||||||
|
|
||||||
|
struct sigcb {
|
||||||
|
const sig_pattern_t* p;
|
||||||
|
uint64_t* out; int max, n;
|
||||||
|
uint64_t win_base; size_t win_len, win_ov; int win_last;
|
||||||
|
int first, stop;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int sig_hit(void* u, uint64_t va) {
|
||||||
|
struct sigcb* c = u;
|
||||||
|
const size_t off = (size_t)(va - c->win_base);
|
||||||
|
if (!c->win_last && c->win_len > c->win_ov && off >= c->win_len - c->win_ov)
|
||||||
|
return 0; /* trailing overlap: next window owns it */
|
||||||
|
if (c->out && c->n < c->max) c->out[c->n] = va;
|
||||||
|
c->n++;
|
||||||
|
if (c->first) { c->stop = 1; return 1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sig_sweep_cb(void* u, const uint8_t* data, size_t len,
|
||||||
|
uint64_t base, size_t ov, int last) {
|
||||||
|
struct sigcb* c = u;
|
||||||
|
c->win_base = base; c->win_len = len; c->win_ov = ov; c->win_last = last;
|
||||||
|
const mem_view_t v = { data, len, base };
|
||||||
|
sig_each(v, c->p, sig_hit, c);
|
||||||
|
return c->stop; /* abort sweep after first (gva_sig_first) */
|
||||||
|
}
|
||||||
|
|
||||||
|
int gva_sig_scan(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
|
||||||
|
uint32_t prot_any, const sig_pattern_t* p, uint64_t* out, int max) {
|
||||||
|
if (!p || p->len == 0) return -1;
|
||||||
|
struct sigcb c; memset(&c, 0, sizeof c);
|
||||||
|
c.p = p; c.out = out; c.max = max;
|
||||||
|
if (gva_sweep(ctx, cr3, lo, hi, prot_any, p->len - 1, sig_sweep_cb, &c) < 0) return -1;
|
||||||
|
return c.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gva_sig_first(gva_ctx* ctx, uintptr_t cr3, uint64_t lo, uint64_t hi,
|
||||||
|
uint32_t prot_any, const sig_pattern_t* p, uint64_t* va) {
|
||||||
|
if (!p || p->len == 0 || !va) return -1;
|
||||||
|
uint64_t hit = 0;
|
||||||
|
struct sigcb c; memset(&c, 0, sizeof c);
|
||||||
|
c.p = p; c.out = &hit; c.max = 1; c.first = 1;
|
||||||
|
if (gva_sweep(ctx, cr3, lo, hi, prot_any, p->len - 1, sig_sweep_cb, &c) < 0) return -1;
|
||||||
|
if (c.n == 0) return 1;
|
||||||
|
*va = hit;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gva_sig_rip(gva_ctx* ctx, uintptr_t cr3, uint64_t hit_va,
|
||||||
|
size_t disp_off, size_t instr_len, uint64_t* target) {
|
||||||
|
int32_t disp;
|
||||||
|
if (!target || gva_read(ctx, cr3, hit_va + disp_off, &disp, 4)) return -1;
|
||||||
|
*target = hit_va + instr_len + (int64_t)disp;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gva_pe_section(gva_ctx* ctx, 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(ctx, 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(ctx, cr3, module_base + rva, buf, n)) return -1;
|
||||||
|
out->data = buf; out->size = n; out->base_va = module_base + rva;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(gva_ctx* ctx, const sig_pattern_t* p, uint64_t* out, int max) {
|
||||||
|
if (!p || p->len == 0) return -1;
|
||||||
|
gpa_ctx* m = &p_(ctx);
|
||||||
|
struct physcb c = { out, max, 0 };
|
||||||
|
|
||||||
|
const size_t seg0 = (size_t)(m->low < m->fsize ? m->low : m->fsize);
|
||||||
|
const mem_view_t v0 = { (const uint8_t*)m->pa, seg0, 0 };
|
||||||
|
sig_each(v0, p, phys_hit, &c);
|
||||||
|
|
||||||
|
if (m->fsize > m->low) {
|
||||||
|
const mem_view_t v1 = { (const uint8_t*)m->pa + m->low,
|
||||||
|
(size_t)(m->fsize - m->low), 1ull << 32 };
|
||||||
|
sig_each(v1, p, phys_hit, &c);
|
||||||
|
}
|
||||||
|
return c.n;
|
||||||
|
}
|
||||||
+197
@@ -0,0 +1,197 @@
|
|||||||
|
#include "../include/sigscan.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static int hexval(char c) {
|
||||||
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sig_parse_ida(const char* s, sig_pattern_t* out) {
|
||||||
|
if (!s || !out) return false;
|
||||||
|
|
||||||
|
size_t cap = 0; /* pass 1: count tokens */
|
||||||
|
for (const char* p = s; *p;) {
|
||||||
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
|
if (!*p) break;
|
||||||
|
cap++;
|
||||||
|
while (*p && *p != ' ' && *p != '\t') p++;
|
||||||
|
}
|
||||||
|
if (cap == 0) return false;
|
||||||
|
|
||||||
|
uint8_t* bytes = malloc(cap);
|
||||||
|
uint8_t* mask = malloc(cap);
|
||||||
|
if (!bytes || !mask) { free(bytes); free(mask); return false; }
|
||||||
|
|
||||||
|
size_t n = 0; /* pass 2: parse */
|
||||||
|
for (const char* p = s; *p;) {
|
||||||
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
|
if (!*p) break;
|
||||||
|
if (*p == '?') {
|
||||||
|
bytes[n] = 0; mask[n] = 0;
|
||||||
|
p++; if (*p == '?') p++;
|
||||||
|
} else {
|
||||||
|
const int hi = hexval(p[0]);
|
||||||
|
const int lo = (p[0] && p[1]) ? hexval(p[1]) : -1;
|
||||||
|
if (hi < 0 || lo < 0) { free(bytes); free(mask); return false; }
|
||||||
|
bytes[n] = (uint8_t)((hi << 4) | lo);
|
||||||
|
mask[n] = 1;
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
while (*p && *p != ' ' && *p != '\t') p++; /* tolerate trailing junk */
|
||||||
|
}
|
||||||
|
out->bytes = bytes; out->mask = mask; out->len = n;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sig_parse_mask(const uint8_t* b, const char* m, sig_pattern_t* out) {
|
||||||
|
if (!b || !m || !out) return false;
|
||||||
|
const size_t len = strlen(m);
|
||||||
|
if (len == 0) return false;
|
||||||
|
|
||||||
|
uint8_t* bytes = malloc(len);
|
||||||
|
uint8_t* mask = malloc(len);
|
||||||
|
if (!bytes || !mask) { free(bytes); free(mask); return false; }
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
const bool req = (m[i] == 'x' || m[i] == 'X');
|
||||||
|
mask[i] = req ? 1 : 0;
|
||||||
|
bytes[i] = req ? b[i] : 0;
|
||||||
|
}
|
||||||
|
out->bytes = bytes; out->mask = mask; out->len = len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sig_free(sig_pattern_t* p) {
|
||||||
|
if (!p) return;
|
||||||
|
free(p->bytes); free(p->mask);
|
||||||
|
p->bytes = p->mask = NULL; p->len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sig_each(mem_view_t v, const sig_pattern_t* p,
|
||||||
|
int (*cb)(void*, uint64_t), void* user) {
|
||||||
|
if (!v.data || !p || p->len == 0 || v.size < p->len) return;
|
||||||
|
|
||||||
|
size_t anchor = 0; /* first required byte */
|
||||||
|
while (anchor < p->len && !p->mask[anchor]) anchor++;
|
||||||
|
if (anchor == p->len) return; /* all-wildcard: refuse */
|
||||||
|
|
||||||
|
const uint8_t target = p->bytes[anchor];
|
||||||
|
const size_t last = v.size - p->len; /* max valid start */
|
||||||
|
const size_t hi = last + anchor; /* max anchor index */
|
||||||
|
size_t pos = anchor;
|
||||||
|
|
||||||
|
while (pos <= hi) {
|
||||||
|
const uint8_t* f = (const uint8_t*)memchr(v.data + pos, target, hi - pos + 1);
|
||||||
|
if (!f) break;
|
||||||
|
const size_t idx = (size_t)(f - v.data);
|
||||||
|
const size_t s = idx - anchor;
|
||||||
|
|
||||||
|
int ok = 1;
|
||||||
|
for (size_t i = 0; i < p->len; i++)
|
||||||
|
if (p->mask[i] && v.data[s + i] != p->bytes[i]) { ok = 0; break; }
|
||||||
|
if (ok && cb(user, v.base_va + s)) return;
|
||||||
|
pos = idx + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sig_all/sig_first collectors over sig_each. */
|
||||||
|
struct all_ctx { uint64_t* out; size_t max, n; };
|
||||||
|
static int all_cb(void* u, uint64_t va) {
|
||||||
|
struct all_ctx* c = u;
|
||||||
|
if (c->out) {
|
||||||
|
if (c->n < c->max) c->out[c->n] = va;
|
||||||
|
else return 1; /* buffer full: stop */
|
||||||
|
}
|
||||||
|
c->n++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sig_all(mem_view_t v, const sig_pattern_t* p, uint64_t* out, size_t max) {
|
||||||
|
struct all_ctx c = { out, max, 0 };
|
||||||
|
sig_each(v, p, all_cb, &c);
|
||||||
|
return c.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct first_ctx { uint64_t va; int got; };
|
||||||
|
static int first_cb(void* u, uint64_t va) {
|
||||||
|
struct first_ctx* c = u;
|
||||||
|
c->va = va; c->got = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sig_first(mem_view_t v, const sig_pattern_t* p) {
|
||||||
|
struct first_ctx c = { 0, 0 };
|
||||||
|
sig_each(v, p, first_cb, &c);
|
||||||
|
return c.got ? c.va : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sig_rip(mem_view_t v, uint64_t hit_va, size_t disp_off, size_t instr_len) {
|
||||||
|
if (hit_va < v.base_va) return 0;
|
||||||
|
const size_t off = (size_t)(hit_va - v.base_va) + disp_off;
|
||||||
|
if (off + 4 > v.size) return 0;
|
||||||
|
int32_t disp;
|
||||||
|
memcpy(&disp, v.data + off, 4);
|
||||||
|
return hit_va + instr_len + (int64_t)disp;
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_view_t mem_sub(mem_view_t v, uint64_t start_va, size_t size) {
|
||||||
|
mem_view_t r = {0};
|
||||||
|
if (!v.data || start_va < v.base_va) return r;
|
||||||
|
const size_t off = (size_t)(start_va - v.base_va);
|
||||||
|
if (off >= v.size) return r;
|
||||||
|
const size_t avail = v.size - off;
|
||||||
|
r.data = v.data + off;
|
||||||
|
r.base_va = start_va;
|
||||||
|
r.size = size < avail ? size : avail;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "include/memory.h"
|
||||||
|
#include "../include/include.h"
|
||||||
|
|
||||||
|
static void utf8_emit(uint32_t cp, char* dst, size_t size, size_t* need) {
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
*need += k;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t gva_read_text(gva_ctx* ctx, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size) {
|
||||||
|
size_t need = 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(ctx, 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);
|
||||||
|
hi = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
utf8_emit(0xFFFD, dst, size, &need);
|
||||||
|
hi = 0;
|
||||||
|
}
|
||||||
|
if (u >= 0xD800 && u <= 0xDBFF) hi = u;
|
||||||
|
else if (u >= 0xDC00 && u <= 0xDFFF) utf8_emit(0xFFFD, dst, size, &need);
|
||||||
|
else utf8_emit(u, dst, size, &need);
|
||||||
|
}
|
||||||
|
va += chunk;
|
||||||
|
nmemb -= chunk;
|
||||||
|
}
|
||||||
|
if (hi) utf8_emit(0xFFFD, dst, size, &need);
|
||||||
|
if (dst && size) dst[need < size ? need : size - 1] = 0;
|
||||||
|
return need;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user