mirror of
https://dev.lirent.ru/Vatrog/vm-introspection-engine.git
synced 2026-06-18 02:06:36 +03:00
Define the win32 engine; add a dump source and physical sigscan
Name and isolate the Windows engine as one of potentially several. The public surface moves to include/win32.h with an opaque vmie_win32 handle (vmie_win32_open/close/mem); the engine's Windows internals — host bring-up, the struct-offset profile, process/module/PE/text decode — live under src/engine/win32. The generic address-space layer stays in src/engine (gva.c + engine-arch.h, carrying no offset table): gva.c is de-profiled, and CR3 bring-up reaches the hot translator through a cold gva_translate bridge so the zero-copy hot path stays private and inlinable. A memory source is now first-class and public: vmie_mem_open/_open_segs/ _close open a flat dump (or an explicit segment map) as a vmie_mem, with gpa_seg promoted to the public contract. The physical signature scan is exposed source-agnostically: sig_scan_mem returns GPAs for any vmie_mem, sig_scan_sources scans several sources with per-source attribution, and sig_from_bytes builds an exact needle from a byte span. The pure matcher is unchanged; dumps and the live engine image are scanned uniformly, neither needing the other.
This commit is contained in:
+12
-10
@@ -11,17 +11,19 @@ option(VMIE_LTO "Enable LTO" OFF) # build-only; shipped default is -O2, no
|
|||||||
add_library(vmie STATIC
|
add_library(vmie STATIC
|
||||||
src/core/gpa.c
|
src/core/gpa.c
|
||||||
src/engine/gva.c
|
src/engine/gva.c
|
||||||
src/engine/host.c
|
src/engine/sigphys.c
|
||||||
src/engine/pe.c
|
src/engine/win32/host.c
|
||||||
src/engine/proc.c
|
src/engine/win32/pe.c
|
||||||
src/engine/profile.c
|
src/engine/win32/proc.c
|
||||||
src/engine/text.c
|
src/engine/win32/profile.c
|
||||||
|
src/engine/win32/text.c
|
||||||
src/handlers/scan.c
|
src/handlers/scan.c
|
||||||
src/handlers/sigscan.c)
|
src/handlers/sigscan.c)
|
||||||
target_include_directories(vmie
|
target_include_directories(vmie
|
||||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # public API: include/*.h
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # public API: include/*.h
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/core/include # private: core.h
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/core/include # private: core.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include) # private: engine.h, contract.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include # private: engine-arch.h, pe.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32) # private: engine-win32.h, contract.h
|
||||||
target_compile_options(vmie PRIVATE -O2 -Wall -Wextra)
|
target_compile_options(vmie PRIVATE -O2 -Wall -Wextra)
|
||||||
if(VMIE_LTO)
|
if(VMIE_LTO)
|
||||||
target_compile_options(vmie PRIVATE -flto)
|
target_compile_options(vmie PRIVATE -flto)
|
||||||
@@ -39,10 +41,10 @@ set(VMIE_STARTUP ${CMAKE_CURRENT_BINARY_DIR}/vmie-startup.exe)
|
|||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${VMIE_STARTUP}
|
OUTPUT ${VMIE_STARTUP}
|
||||||
COMMAND ${MINGW_CC} -O2 -Wall -Wextra -static -s
|
COMMAND ${MINGW_CC} -O2 -Wall -Wextra -static -s
|
||||||
-I${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include
|
-I${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32
|
||||||
-o ${VMIE_STARTUP} ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/guest.c
|
-o ${VMIE_STARTUP} ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32/guest.c
|
||||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/guest.c
|
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32/guest.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include/contract.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/engine/win32/contract.h
|
||||||
COMMENT "Cross-compiling vmie-startup.exe (mingw-w64, x86-64)"
|
COMMENT "Cross-compiling vmie-startup.exe (mingw-w64, x86-64)"
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
add_custom_target(vmie-startup ALL DEPENDS ${VMIE_STARTUP})
|
add_custom_target(vmie-startup ALL DEPENDS ${VMIE_STARTUP})
|
||||||
|
|||||||
@@ -27,6 +27,40 @@
|
|||||||
* pass it, with a cr3, to the address-space primitives below. */
|
* pass it, with a cr3, to the address-space primitives below. */
|
||||||
typedef struct vmie_mem vmie_mem;
|
typedef struct vmie_mem vmie_mem;
|
||||||
|
|
||||||
|
/* One contiguous GPA window backed by a file span: GPA [gpa, gpa+len) maps 1:1
|
||||||
|
* onto file offset [file_off, file_off+len). A POD descriptor, promoted here so
|
||||||
|
* an explicit-segment dump can be opened through the public surface below; the
|
||||||
|
* full vmie_mem (which embeds an array of these) is defined in core.h. */
|
||||||
|
#ifndef VMIE_GPA_SEG_DEFINED
|
||||||
|
#define VMIE_GPA_SEG_DEFINED
|
||||||
|
typedef struct gpa_seg {
|
||||||
|
uint64_t gpa;
|
||||||
|
uint64_t len;
|
||||||
|
uint64_t file_off;
|
||||||
|
} gpa_seg;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ---- dump source lifecycle ----------------------------------------------- *
|
||||||
|
* A vmie_mem is the universal memory source: a live win32 physical image and an
|
||||||
|
* on-disk dump are both vmie_mem. These open a dump (or any flat/segmented RAM
|
||||||
|
* image) as a heap-owned vmie_mem for the source-agnostic physical scanners
|
||||||
|
* (scan.h: sig_scan_mem/sig_scan_sources). No paging/cr3: a dump supports the
|
||||||
|
* physical signature scan only. The win32 engine produces its vmie_mem through
|
||||||
|
* the win32 surface (win32.h) instead. */
|
||||||
|
|
||||||
|
/* Open `path` as a single-`low` image (the classic QEMU split; low >= file size
|
||||||
|
* => one inert identity segment, i.e. a flat dump). Returns a heap-owned handle,
|
||||||
|
* or NULL on open/mmap failure. Release with vmie_mem_close(). */
|
||||||
|
vmie_mem* vmie_mem_open(const char* path, uint64_t low);
|
||||||
|
|
||||||
|
/* Open `path` with an explicit segment map (`nseg` entries; see gpa_seg). The
|
||||||
|
* map must be well-formed against the file size (dense, sorted, in-file).
|
||||||
|
* Returns a heap-owned handle, or NULL on failure. Release with vmie_mem_close(). */
|
||||||
|
vmie_mem* vmie_mem_open_segs(const char* path, const gpa_seg* segs, int nseg);
|
||||||
|
|
||||||
|
/* Unmap, close, and free a handle from vmie_mem_open*. Safe on NULL. */
|
||||||
|
void vmie_mem_close(vmie_mem* m);
|
||||||
|
|
||||||
/* ---- flat memory view (single owner) ------------------------------------- *
|
/* ---- flat memory view (single owner) ------------------------------------- *
|
||||||
* A contiguous view of memory.
|
* A contiguous view of memory.
|
||||||
* data - host pointer to the bytes (borrowed; not owned by the view)
|
* data - host pointer to the bytes (borrowed; not owned by the view)
|
||||||
|
|||||||
+19
-3
@@ -9,7 +9,7 @@
|
|||||||
* memory and feed them to the signature matcher.
|
* memory and feed them to the signature matcher.
|
||||||
*
|
*
|
||||||
* The Windows-typed convenience entry points (scan_new(process*),
|
* The Windows-typed convenience entry points (scan_new(process*),
|
||||||
* vmie_scan_pointer(process*)) live in the win32 surface (vmie.h).
|
* vmie_scan_pointer(process*)) live in the win32 surface (win32.h).
|
||||||
*/
|
*/
|
||||||
#ifndef VMIE_SCAN_H
|
#ifndef VMIE_SCAN_H
|
||||||
#define VMIE_SCAN_H
|
#define VMIE_SCAN_H
|
||||||
@@ -61,7 +61,23 @@ int gva_sig_first(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi,
|
|||||||
int gva_sig_rip (vmie_mem* m, uintptr_t cr3, uint64_t hit_va,
|
int gva_sig_rip (vmie_mem* m, uintptr_t cr3, uint64_t hit_va,
|
||||||
size_t disp_off, size_t instr_len, uint64_t* target);
|
size_t disp_off, size_t instr_len, uint64_t* target);
|
||||||
|
|
||||||
/* gva_sig_phys (scan the raw physical image) needs the core segment map, so it
|
/* ---- physical-image signature scan (OS-agnostic engine bridge) ----------- *
|
||||||
* is an engine bridge, declared in engine.h - not part of the handler surface. */
|
* Scan the raw physical image (the core segment map) for a signature, without a
|
||||||
|
* cr3 or page tables: each seg is one mem_view_t over its file span, fed to the
|
||||||
|
* pure matcher. This is the dump path - a dump (vmie_mem_open*) supports the
|
||||||
|
* physical scan only. Keyed by vmie_mem*, like the rest of this header. */
|
||||||
|
|
||||||
|
/* Attributed hit from a multi-source scan: which source matched, and where. */
|
||||||
|
typedef struct { int source; uint64_t gpa; } sig_hit_src;
|
||||||
|
|
||||||
|
/* Scan one physical image for `p`. Writes up to `max` GPA hits to `out` (NULL to
|
||||||
|
* count only) and returns the TOTAL number of hits, or -1 on a bad pattern. */
|
||||||
|
int sig_scan_mem (vmie_mem* m, const sig_pattern_t* p, uint64_t* out, int max);
|
||||||
|
|
||||||
|
/* Scan `nsrc` physical images for `p`, tagging each hit with its source index.
|
||||||
|
* Writes up to `max` attributed hits to `out` (NULL to count only) and returns
|
||||||
|
* the TOTAL across all sources, or -1 on a bad pattern. */
|
||||||
|
int sig_scan_sources(vmie_mem* const* srcs, int nsrc, const sig_pattern_t* p,
|
||||||
|
sig_hit_src* out, int max);
|
||||||
|
|
||||||
#endif /* VMIE_SCAN_H */
|
#endif /* VMIE_SCAN_H */
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ bool sig_parse_ida(const char* ida, sig_pattern_t* out);
|
|||||||
* false on NULL args or an empty mask. */
|
* false on NULL args or an empty mask. */
|
||||||
bool sig_parse_mask(const uint8_t* bytes, const char* mask, sig_pattern_t* out);
|
bool sig_parse_mask(const uint8_t* bytes, const char* mask, sig_pattern_t* out);
|
||||||
|
|
||||||
|
/* Build an exact (no-wildcard) pattern from `len` raw bytes: every byte must
|
||||||
|
* match. A thin wrapper over sig_parse_mask with an all-'x' mask, so the result
|
||||||
|
* is released with sig_free() like any other pattern. Returns true on success,
|
||||||
|
* false on NULL args, a zero length, or OOM. Touches no vmie_mem (pure). */
|
||||||
|
bool sig_from_bytes(const uint8_t* bytes, size_t len, sig_pattern_t* out);
|
||||||
|
|
||||||
/* Release a pattern produced by sig_parse_*. Safe on NULL and on an
|
/* Release a pattern produced by sig_parse_*. Safe on NULL and on an
|
||||||
* already-freed pattern (it is zeroed). */
|
* already-freed pattern (it is zeroed). */
|
||||||
void sig_free(sig_pattern_t* p);
|
void sig_free(sig_pattern_t* p);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* vmie.h - public Windows-guest surface of the vmi-engine.
|
/* win32.h - public Windows-guest surface of the vmi-engine.
|
||||||
*
|
*
|
||||||
* The host opens a guest's RAM backing file (a flat, writable, coherent mmap),
|
* 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
|
* recovers the kernel address space, and reads/writes guest memory by CR3 and
|
||||||
@@ -16,18 +16,18 @@
|
|||||||
* - The library never takes ownership of caller buffers and never retains a
|
* - The library never takes ownership of caller buffers and never retains a
|
||||||
* pointer past the call that received it, unless explicitly stated.
|
* pointer past the call that received it, unless explicitly stated.
|
||||||
*/
|
*/
|
||||||
#ifndef VMIE_VMIE_H
|
#ifndef VMIE_WIN32_H
|
||||||
#define VMIE_VMIE_H
|
#define VMIE_WIN32_H
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "memmodel.h" /* vmie_mem, vregion/VR_*, task/range, gva_read/write/ptr/regions/sweep */
|
#include "memmodel.h" /* vmie_mem, vregion/VR_*, task/range, gva_read/write/ptr/regions/sweep */
|
||||||
#include "sigscan.h" /* mem_view_t, sig_pattern_t */
|
#include "sigscan.h" /* mem_view_t, sig_pattern_t */
|
||||||
#include "scan.h" /* scan_type, scan_ptr_path, generic scan surface */
|
#include "scan.h" /* scan_type, scan_ptr_path, generic scan surface */
|
||||||
|
|
||||||
/* Opaque introspection context. Completed in src/engine/include/engine.h;
|
/* Opaque introspection context. Completed in src/engine/win32/engine-win32.h;
|
||||||
* callers only ever hold a pointer. Created by vmie_open(), populated by
|
* callers only ever hold a pointer. Created by vmie_win32_open(), populated by
|
||||||
* host_bootstrap(), released by vmie_close(). */
|
* host_bootstrap(), released by vmie_win32_close(). */
|
||||||
typedef struct vmie vmie;
|
typedef struct vmie_win32 vmie_win32;
|
||||||
|
|
||||||
/* A guest counted string still resident in guest memory (e.g. a UNICODE_STRING
|
/* 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.
|
* buffer). Not a copy: `va` points into the guest, decode it with gva_read_text.
|
||||||
@@ -80,26 +80,26 @@ typedef struct {
|
|||||||
* pass the value from the VM's memory layout. If total RAM <= low,
|
* pass the value from the VM's memory layout. If total RAM <= low,
|
||||||
* the split is inert.
|
* the split is inert.
|
||||||
* Returns a new context (call host_bootstrap() next), or NULL on open/mmap
|
* Returns a new context (call host_bootstrap() next), or NULL on open/mmap
|
||||||
* failure. Free with vmie_close(). */
|
* failure. Free with vmie_win32_close(). */
|
||||||
vmie* vmie_open(const char* ram_path, uint64_t low);
|
vmie_win32* vmie_win32_open(const char* ram_path, uint64_t low);
|
||||||
|
|
||||||
/* Unmap, close, and free a context. Safe on NULL. After this, every pointer
|
/* Unmap, close, and free a context. Safe on NULL. After this, every pointer
|
||||||
* into guest memory obtained through this context is invalid. */
|
* into guest memory obtained through this context is invalid. */
|
||||||
void vmie_close(vmie* v);
|
void vmie_win32_close(vmie_win32* v);
|
||||||
|
|
||||||
/* Borrow the engine's guest-memory handle for the generic address-space
|
/* Borrow the engine's guest-memory handle for the generic address-space
|
||||||
* primitives (gva_read/gva_regions/...). The returned pointer is owned by `v`
|
* primitives (gva_read/gva_regions/...). The returned pointer is owned by `v`
|
||||||
* and valid until vmie_close(v); do NOT free or retain it past that. NULL on
|
* and valid until vmie_win32_close(v); do NOT free or retain it past that. NULL on
|
||||||
* NULL `v`. */
|
* NULL `v`. */
|
||||||
vmie_mem* vmie_memory(vmie* v);
|
vmie_mem* vmie_win32_mem(vmie_win32* v);
|
||||||
|
|
||||||
/* One-shot bring-up: locate the guest agent beacon in physical RAM, recover a
|
/* 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
|
* bootstrap CR3, find ntoskrnl, build the struct-offset profile, derive the
|
||||||
* permanent System DirectoryTableBase (kernel cr3) and System _EPROCESS, then
|
* permanent System DirectoryTableBase (kernel cr3) and System _EPROCESS, then
|
||||||
* ACK the agent. On success the context is ready for proc_list()/gva_read()/etc.
|
* 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
|
* Returns 0 on success, or a negative stage code (-1..-6) identifying the step
|
||||||
* that failed. Cold path: call once after vmie_open(). */
|
* that failed. Cold path: call once after vmie_win32_open(). */
|
||||||
int host_bootstrap(vmie* v);
|
int host_bootstrap(vmie_win32* v);
|
||||||
|
|
||||||
/* ---- guest string decode ------------------------------------------------- */
|
/* ---- guest string decode ------------------------------------------------- */
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ int host_bootstrap(vmie* v);
|
|||||||
* Returns the number of UTF-8 bytes the full conversion needs, EXCLUDING the
|
* Returns the number of UTF-8 bytes the full conversion needs, EXCLUDING the
|
||||||
* terminator (like snprintf): if it is >= `size`, output was truncated. When
|
* terminator (like snprintf): if it is >= `size`, output was truncated. When
|
||||||
* `dst` is non-NULL and `size` > 0 the result is always NUL-terminated. */
|
* `dst` is non-NULL and `size` > 0 the result is always NUL-terminated. */
|
||||||
size_t gva_read_text(vmie* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size);
|
size_t gva_read_text(vmie_win32* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size);
|
||||||
|
|
||||||
/* ---- enumeration --------------------------------------------------------- */
|
/* ---- enumeration --------------------------------------------------------- */
|
||||||
|
|
||||||
@@ -121,14 +121,14 @@ size_t gva_read_text(vmie* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* d
|
|||||||
* nmax - capacity of `dst`
|
* nmax - capacity of `dst`
|
||||||
* Returns the number written (<= nmax), or negative on failure (e.g. bootstrap
|
* Returns the number written (<= nmax), or negative on failure (e.g. bootstrap
|
||||||
* not completed). Enumeration stops at `nmax`; raise it to see more. */
|
* not completed). Enumeration stops at `nmax`; raise it to see more. */
|
||||||
int proc_list(vmie* v, int skip_system, process* dst, size_t nmax);
|
int proc_list(vmie_win32* v, int skip_system, process* dst, size_t nmax);
|
||||||
|
|
||||||
/* Enumerate a process's loaded modules via the PEB loader InLoadOrder list.
|
/* Enumerate a process's loaded modules via the PEB loader InLoadOrder list.
|
||||||
* pr - process to inspect (uses pr->cr3 and pr->peb)
|
* pr - process to inspect (uses pr->cr3 and pr->peb)
|
||||||
* dst - caller array receiving up to `nmax` `pmodule` records
|
* dst - caller array receiving up to `nmax` `pmodule` records
|
||||||
* nmax - capacity of `dst`
|
* nmax - capacity of `dst`
|
||||||
* Returns the number written (<= nmax), 0 if the process has no PEB/loader. */
|
* Returns the number written (<= nmax), 0 if the process has no PEB/loader. */
|
||||||
int proc_modules(vmie* v, const process* pr, pmodule* dst, size_t nmax);
|
int proc_modules(vmie_win32* v, const process* pr, pmodule* dst, size_t nmax);
|
||||||
|
|
||||||
/* ---- win32 scan wrappers ------------------------------------------------- *
|
/* ---- win32 scan wrappers ------------------------------------------------- *
|
||||||
* Convenience entry points over the generic cr3/range scan surface (scan.h).
|
* Convenience entry points over the generic cr3/range scan surface (scan.h).
|
||||||
@@ -137,13 +137,13 @@ int proc_modules(vmie* v, const process* pr, pmodule* dst, size_t nmax);
|
|||||||
|
|
||||||
/* Open a value-scan session over the user address space of `pr`. Equivalent to
|
/* Open a value-scan session over the user address space of `pr`. Equivalent to
|
||||||
* scan_new_cr3(&v->mem, pr->cr3, ...). Returns NULL on NULL pr or OOM. */
|
* scan_new_cr3(&v->mem, pr->cr3, ...). Returns NULL on NULL pr or OOM. */
|
||||||
scan* scan_new(vmie* v, const process* pr, scan_type t, const void* value,
|
scan* scan_new(vmie_win32* v, const process* pr, scan_type t, const void* value,
|
||||||
int be, int aligned, uint64_t lo, uint64_t hi);
|
int be, int aligned, uint64_t lo, uint64_t hi);
|
||||||
|
|
||||||
/* Pointer scan over `pr`'s user space, anchored on its loaded modules. Resolves
|
/* Pointer scan over `pr`'s user space, anchored on its loaded modules. Resolves
|
||||||
* `pr`'s module list to range[] (names engine-decoded) and delegates to
|
* `pr`'s module list to range[] (names engine-decoded) and delegates to
|
||||||
* scan_pointer. Returns the number of paths found, or negative on failure. */
|
* scan_pointer. Returns the number of paths found, or negative on failure. */
|
||||||
int vmie_scan_pointer(vmie* v, const process* pr, uint64_t target,
|
int vmie_scan_pointer(vmie_win32* v, const process* pr, uint64_t target,
|
||||||
int max_depth, uint32_t max_off, scan_ptr_path* out, int max);
|
int max_depth, uint32_t max_off, scan_ptr_path* out, int max);
|
||||||
|
|
||||||
#endif /* VMIE_VMIE_H */
|
#endif /* VMIE_WIN32_H */
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
*
|
*
|
||||||
* Opens a guest RAM backing file, brings up the VMI context, lists processes,
|
* 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.
|
* and for the first user process dumps its loaded modules and mapped regions.
|
||||||
* Public surface only (include/vmie.h); the region walk takes a vmie_mem*,
|
* Public surface only (include/win32.h); the region walk takes a vmie_mem*,
|
||||||
* borrowed from the engine via vmie_memory().
|
* borrowed from the engine via vmie_win32_mem().
|
||||||
*
|
*
|
||||||
* argv[1] path to the guest RAM backing file
|
* argv[1] path to the guest RAM backing file
|
||||||
* argv[2] `low` - size in bytes of below-4G guest RAM (strtoull, base 0)
|
* argv[2] `low` - size in bytes of below-4G guest RAM (strtoull, base 0)
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include "vmie.h"
|
#include "win32.h"
|
||||||
|
|
||||||
#define DEFAULT_NMAX 512
|
#define DEFAULT_NMAX 512
|
||||||
#define MOD_CAP 256
|
#define MOD_CAP 256
|
||||||
@@ -41,7 +41,7 @@ static void decode_prot(uint32_t prot, char out[5]) {
|
|||||||
out[4] = 0;
|
out[4] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dump_modules(vmie* ctx, const process* pr) {
|
static void dump_modules(vmie_win32* ctx, const process* pr) {
|
||||||
pmodule mods[MOD_CAP];
|
pmodule mods[MOD_CAP];
|
||||||
const int nm = proc_modules(ctx, pr, mods, MOD_CAP);
|
const int nm = proc_modules(ctx, pr, mods, MOD_CAP);
|
||||||
if (nm <= 0) {
|
if (nm <= 0) {
|
||||||
@@ -68,12 +68,12 @@ static void dump_modules(vmie* ctx, const process* pr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dump_regions(vmie* ctx, const process* pr) {
|
static void dump_regions(vmie_win32* ctx, const process* pr) {
|
||||||
vregion* rg = malloc((size_t)RGN_CAP * sizeof *rg);
|
vregion* rg = malloc((size_t)RGN_CAP * sizeof *rg);
|
||||||
if (!rg) {
|
if (!rg) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const int total = gva_regions(vmie_memory(ctx), pr->cr3, 0, ~0ull, 0, rg, RGN_CAP);
|
const int total = gva_regions(vmie_win32_mem(ctx), pr->cr3, 0, ~0ull, 0, rg, RGN_CAP);
|
||||||
const int shown = total < 0 ? 0 : (total < RGN_CAP ? total : RGN_CAP);
|
const int shown = total < 0 ? 0 : (total < RGN_CAP ? total : RGN_CAP);
|
||||||
for (int i = 0; i < shown; i++) {
|
for (int i = 0; i < shown; i++) {
|
||||||
char prot[5];
|
char prot[5];
|
||||||
@@ -103,7 +103,7 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vmie* ctx = vmie_open(ram_path, low);
|
vmie_win32* ctx = vmie_win32_open(ram_path, low);
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
fprintf(stderr, "error: cannot open RAM backing file '%s'\n", ram_path);
|
fprintf(stderr, "error: cannot open RAM backing file '%s'\n", ram_path);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -112,14 +112,14 @@ int main(int argc, char** argv) {
|
|||||||
const int rc = host_bootstrap(ctx);
|
const int rc = host_bootstrap(ctx);
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
fprintf(stderr, "error: bootstrap failed (%d): %s\n", rc, bootstrap_stage(rc));
|
fprintf(stderr, "error: bootstrap failed (%d): %s\n", rc, bootstrap_stage(rc));
|
||||||
vmie_close(ctx);
|
vmie_win32_close(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
process* procs = malloc(nmax * sizeof *procs);
|
process* procs = malloc(nmax * sizeof *procs);
|
||||||
if (!procs) {
|
if (!procs) {
|
||||||
fprintf(stderr, "error: out of memory\n");
|
fprintf(stderr, "error: out of memory\n");
|
||||||
vmie_close(ctx);
|
vmie_win32_close(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ int main(int argc, char** argv) {
|
|||||||
if (np < 0) {
|
if (np < 0) {
|
||||||
fprintf(stderr, "error: proc_list failed (%d)\n", np);
|
fprintf(stderr, "error: proc_list failed (%d)\n", np);
|
||||||
free(procs);
|
free(procs);
|
||||||
vmie_close(ctx);
|
vmie_win32_close(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +157,6 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(procs);
|
free(procs);
|
||||||
vmie_close(ctx);
|
vmie_win32_close(ctx);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -147,3 +148,43 @@ void gpa_close(vmie_mem* m) {
|
|||||||
|
|
||||||
clean_ctx(m);
|
clean_ctx(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- public dump source (heap-owned vmie_mem) ---------------------------- *
|
||||||
|
* Thin wrappers over gpa_open*: heap-allocate a vmie_mem and open into it, so a
|
||||||
|
* dump (or any flat/segmented RAM image) is a first-class memory source for the
|
||||||
|
* physical scanners without exposing the win32 engine. */
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
vmie_mem* vmie_mem_open(const char* path, uint64_t low) {
|
||||||
|
vmie_mem* m = calloc(1, sizeof *m);
|
||||||
|
if (!m) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (gpa_open(m, path, (uintptr_t)low)) {
|
||||||
|
free(m);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
vmie_mem* vmie_mem_open_segs(const char* path, const gpa_seg* segs, int nseg) {
|
||||||
|
vmie_mem* m = calloc(1, sizeof *m);
|
||||||
|
if (!m) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (gpa_open_segs(m, path, segs, nseg)) {
|
||||||
|
free(m);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
void vmie_mem_close(vmie_mem* m) {
|
||||||
|
if (!m) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpa_close(m);
|
||||||
|
free(m);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,17 +2,14 @@
|
|||||||
#define VMIE_CORE_H
|
#define VMIE_CORE_H
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include "memmodel.h" /* gpa_seg (POD, promoted public) + vmie_mem forward decl */
|
||||||
|
|
||||||
#define VMIE_MAX_SEGS 8
|
#define VMIE_MAX_SEGS 8
|
||||||
|
|
||||||
/* One contiguous GPA window backed by a file span: GPA [gpa, gpa+len) maps 1:1
|
/* gpa_seg (the GPA<->file-span descriptor) is defined in memmodel.h, included
|
||||||
* onto file offset [file_off, file_off+len). The classic single-`low` guest is
|
* above: one contiguous GPA window mapping 1:1 onto a file span. The classic
|
||||||
* two segs ({0,low,0} below the 4 GiB hole, {4G,fsize-low,low} above it). */
|
* single-`low` guest is two segs ({0,low,0} below the 4 GiB hole, {4G,fsize-low,
|
||||||
typedef struct gpa_seg {
|
* low} above it). */
|
||||||
uint64_t gpa;
|
|
||||||
uint64_t len;
|
|
||||||
uint64_t file_off;
|
|
||||||
} gpa_seg;
|
|
||||||
|
|
||||||
/* Flat RW mmap of the guest RAM backing file. The GPA<->file-offset map is the
|
/* Flat RW mmap of the guest RAM backing file. The GPA<->file-offset map is the
|
||||||
* sorted, dense, in-file segment table seg[0..nseg): each seg is one contiguous
|
* sorted, dense, in-file segment table seg[0..nseg): each seg is one contiguous
|
||||||
|
|||||||
+10
-66
@@ -2,7 +2,7 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "engine.h"
|
#include "engine-arch.h"
|
||||||
|
|
||||||
/* PTE permission bits we propagate down the walk. */
|
/* PTE permission bits we propagate down the walk. */
|
||||||
#define PTE_RW (1ull << 1)
|
#define PTE_RW (1ull << 1)
|
||||||
@@ -45,7 +45,15 @@ static int gva_gpa(vmie_mem* m, uintptr_t cr3, uintptr_t va,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* zero-copy borrowed read: leaf-bounded host pointer at `va` (see engine.h). */
|
/* cold extern translate: GPA of `va` under `cr3`, or -1. Wraps the hot static
|
||||||
|
* gva_gpa for cold callers outside this TU (win32 bring-up) without exposing the
|
||||||
|
* inlinable hot primitive. Declared in engine-arch.h. */
|
||||||
|
__attribute__((cold))
|
||||||
|
int gva_translate(vmie_mem* m, uintptr_t cr3, uintptr_t va, uintptr_t* gpa) {
|
||||||
|
return gva_gpa(m, cr3, va, gpa, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* zero-copy borrowed read: leaf-bounded host pointer at `va` (see memmodel.h). */
|
||||||
__attribute__((hot))
|
__attribute__((hot))
|
||||||
const void* gva_ptr(vmie_mem* m, uintptr_t cr3, uintptr_t va, size_t* avail) {
|
const void* gva_ptr(vmie_mem* m, uintptr_t cr3, uintptr_t va, size_t* avail) {
|
||||||
uintptr_t gpa; size_t leaf;
|
uintptr_t gpa; size_t leaf;
|
||||||
@@ -91,47 +99,6 @@ int khalf_score(const vmie_mem* m, uint64_t pml4) {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((cold))
|
|
||||||
int cr3_recover(vmie* v, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) {
|
|
||||||
vmie_mem* m = &v->mem;
|
|
||||||
int best_score = -1; uint64_t best = 0;
|
|
||||||
for (size_t off = 0; off + 0x1000 <= m->fsize; off += 0x1000) {
|
|
||||||
const uintptr_t cand = offset_gpa(m, off);
|
|
||||||
uintptr_t gpa;
|
|
||||||
if (gva_gpa(m, cand, va_self, &gpa, NULL)) continue;
|
|
||||||
if ((gpa & ~0xFFFull) != (target_pa & ~0xFFFull)) continue;
|
|
||||||
const int score = khalf_score(m, 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))
|
|
||||||
vmie* vmie_open(const char* ram_path, uint64_t low) {
|
|
||||||
vmie* v = calloc(1, sizeof *v);
|
|
||||||
if (!v) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (gpa_open(&v->mem, ram_path, low)) {
|
|
||||||
free(v);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((cold))
|
|
||||||
void vmie_close(vmie* v) {
|
|
||||||
if (!v) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gpa_close(&v->mem);
|
|
||||||
free(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- region enumeration -------------------------------------------------- */
|
/* ---- region enumeration -------------------------------------------------- */
|
||||||
|
|
||||||
struct rgn_acc {
|
struct rgn_acc {
|
||||||
@@ -302,26 +269,3 @@ int gva_sweep(vmie_mem* m, uintptr_t cr3, uint64_t lo, uint64_t hi,
|
|||||||
free(rg); free(buf);
|
free(rg); free(buf);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- physical-image signature bridge ------------------------------------- *
|
|
||||||
* Iterates the core segment map (each seg is one mem_view_t over its file span)
|
|
||||||
* and runs the pure matcher. Reaches into vmie_mem, so it lives engine-side. */
|
|
||||||
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(vmie_mem* m, const sig_pattern_t* p, uint64_t* out, int max) {
|
|
||||||
if (!p || p->len == 0) return -1;
|
|
||||||
struct physcb c = { out, max, 0 };
|
|
||||||
|
|
||||||
for (int i = 0; i < m->nseg; i++) {
|
|
||||||
const gpa_seg* s = &m->seg[i];
|
|
||||||
const mem_view_t v = { (const uint8_t*)m->pa + s->file_off, (size_t)s->len, s->gpa };
|
|
||||||
sig_each(v, p, phys_hit, &c);
|
|
||||||
}
|
|
||||||
return c.n;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef VMIE_ENGINE_ARCH_H
|
||||||
|
#define VMIE_ENGINE_ARCH_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "core.h"
|
||||||
|
#include "memmodel.h" /* vmie_mem, vregion/VR_*, gva_read/write/ptr/regions/sweep */
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
/* sign-extend a 48-bit canonical VA */
|
||||||
|
#define VA_CANON(v) (((v) & (1ull << 47)) ? ((v) | 0xFFFF000000000000ull) : (v))
|
||||||
|
|
||||||
|
/* USER_MIN/USER_MAX/KERN_MIN (the canonical VA-window bounds) live in
|
||||||
|
* memmodel.h (handler-visible), pulled in above. */
|
||||||
|
|
||||||
|
/* gva_ptr is declared in memmodel.h; the engine marks its definition hot. */
|
||||||
|
|
||||||
|
/* gva_read/gva_write/gva_regions/gva_sweep + gva_sweep_cb and vregion/VR_*
|
||||||
|
* are the OS-agnostic contract: declared in memmodel.h, pulled in above. */
|
||||||
|
|
||||||
|
/* paging heuristic, shared by the arch walker and the win32 bring-up. Counts
|
||||||
|
* present kernel-half PML4 entries under `pml4` (an address-space liveness
|
||||||
|
* score). OS-agnostic: const vmie_mem*, no profile/struct vmie. */
|
||||||
|
int khalf_score(const vmie_mem* m, uint64_t pml4) __attribute__((cold));
|
||||||
|
|
||||||
|
/* cold extern wrapper over the hot static page-table walk: translate `va` under
|
||||||
|
* `cr3` to a GPA (no leaf length). Returns 0 on success, -1 if not present. For
|
||||||
|
* cold callers outside gva.c (win32 bring-up); the hot inlinable primitive stays
|
||||||
|
* private to gva.c. */
|
||||||
|
int gva_translate(vmie_mem* m, uintptr_t cr3, uintptr_t va, uintptr_t* gpa) __attribute__((cold));
|
||||||
|
|
||||||
|
#endif /* VMIE_ENGINE_ARCH_H */
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
#ifndef VMIE_ENGINE_H
|
|
||||||
#define VMIE_ENGINE_H
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include "core.h"
|
|
||||||
#include "memmodel.h" /* vmie_mem, vregion/VR_*, gva_read/write/ptr/regions/sweep */
|
|
||||||
#include "sigscan.h" /* sig_pattern_t (for gva_sig_phys) */
|
|
||||||
#include "pe.h" /* PE image parsing + vmie_pe_section (engine-private) */
|
|
||||||
|
|
||||||
/* 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
|
|
||||||
|
|
||||||
/* sign-extend a 48-bit canonical VA */
|
|
||||||
#define VA_CANON(v) (((v) & (1ull << 47)) ? ((v) | 0xFFFF000000000000ull) : (v))
|
|
||||||
|
|
||||||
/* USER_MIN/USER_MAX/KERN_MIN (the canonical VA-window bounds) live in
|
|
||||||
* memmodel.h (handler-visible), pulled in above. */
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* 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 vmie* aliases a vmie_mem*. prof carried by value. */
|
|
||||||
typedef struct vmie {
|
|
||||||
vmie_mem mem;
|
|
||||||
uint64_t kcr3;
|
|
||||||
uint64_t kbase;
|
|
||||||
uint64_t sysproc;
|
|
||||||
profile prof;
|
|
||||||
} vmie;
|
|
||||||
|
|
||||||
int profile_build(vmie* v, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age);
|
|
||||||
|
|
||||||
/* gva_ptr is declared in memmodel.h; the engine marks its definition hot. */
|
|
||||||
|
|
||||||
/* bootstrap helpers (gva.c) */
|
|
||||||
int khalf_score(const vmie_mem* m, uint64_t pml4) __attribute__((cold));
|
|
||||||
int cr3_recover(vmie* v, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) __attribute__((cold));
|
|
||||||
|
|
||||||
/* gva_read/gva_write/gva_regions/gva_sweep + gva_sweep_cb and vregion/VR_*
|
|
||||||
* are the OS-agnostic contract: declared in memmodel.h, pulled in above. */
|
|
||||||
|
|
||||||
/* Scan the raw physical image for a signature, iterating the core segment map
|
|
||||||
* (each seg is one mem_view_t over its file span). Reaches into vmie_mem, so it
|
|
||||||
* is an engine bridge, not a handler. Returns the number of GPA hits (writes up
|
|
||||||
* to `max` to `out`; -1 on a bad pattern). */
|
|
||||||
int gva_sig_phys(vmie_mem* m, const sig_pattern_t* p, uint64_t* out, int max);
|
|
||||||
|
|
||||||
#endif /* VMIE_ENGINE_H */
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/* sigphys.c - physical-image signature bridge (OS-agnostic engine bridge).
|
||||||
|
*
|
||||||
|
* Iterates the core segment map (each seg is one mem_view_t over its file span)
|
||||||
|
* and runs the pure matcher. Reaches into vmie_mem (the segment table), so it is
|
||||||
|
* an engine bridge, not a handler - but it names no Windows concept and depends
|
||||||
|
* on no paging/profile, so it lives engine-side, outside src/engine/win32/. The
|
||||||
|
* dump-only scan set is { core/gpa.c, handlers/sigscan.c, this }, no win32. */
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "core.h"
|
||||||
|
#include "sigscan.h"
|
||||||
|
#include "scan.h"
|
||||||
|
|
||||||
|
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 sig_scan_mem(vmie_mem* m, const sig_pattern_t* p, uint64_t* out, int max) {
|
||||||
|
if (!p || p->len == 0) return -1;
|
||||||
|
struct physcb c = { out, max, 0 };
|
||||||
|
|
||||||
|
for (int i = 0; i < m->nseg; i++) {
|
||||||
|
const gpa_seg* s = &m->seg[i];
|
||||||
|
const mem_view_t v = { (const uint8_t*)m->pa + s->file_off, (size_t)s->len, s->gpa };
|
||||||
|
sig_each(v, p, phys_hit, &c);
|
||||||
|
}
|
||||||
|
return c.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tagging callback for the multi-source scan: stamps the current source index
|
||||||
|
* onto each hit and writes attributed records up to capacity. */
|
||||||
|
struct srccb { sig_hit_src* out; int max, n, source; };
|
||||||
|
static int src_hit(void* u, uint64_t gpa) {
|
||||||
|
struct srccb* c = u;
|
||||||
|
if (c->out && c->n < c->max) {
|
||||||
|
c->out[c->n].source = c->source;
|
||||||
|
c->out[c->n].gpa = gpa;
|
||||||
|
}
|
||||||
|
c->n++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sig_scan_sources(vmie_mem* const* srcs, int nsrc, const sig_pattern_t* p,
|
||||||
|
sig_hit_src* out, int max) {
|
||||||
|
if (!p || p->len == 0) return -1;
|
||||||
|
struct srccb c = { out, max, 0, 0 };
|
||||||
|
|
||||||
|
for (int si = 0; si < nsrc; si++) {
|
||||||
|
vmie_mem* m = srcs[si];
|
||||||
|
c.source = si;
|
||||||
|
for (int i = 0; i < m->nseg; i++) {
|
||||||
|
const gpa_seg* s = &m->seg[i];
|
||||||
|
const mem_view_t v = { (const uint8_t*)m->pa + s->file_off, (size_t)s->len, s->gpa };
|
||||||
|
sig_each(v, p, src_hit, &c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.n;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef VMIE_ENGINE_WIN32_H
|
||||||
|
#define VMIE_ENGINE_WIN32_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "engine-arch.h" /* paging macros, khalf_score, gva_* contract */
|
||||||
|
#include "contract.h" /* guest-agent beacon layout */
|
||||||
|
#include "pe.h" /* PE image parsing + vmie_pe_section (engine-private) */
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* 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 vmie_win32* aliases a vmie_mem*. prof carried by value. */
|
||||||
|
typedef struct vmie_win32 {
|
||||||
|
vmie_mem mem;
|
||||||
|
uint64_t kcr3;
|
||||||
|
uint64_t kbase;
|
||||||
|
uint64_t sysproc;
|
||||||
|
profile prof;
|
||||||
|
} vmie_win32;
|
||||||
|
|
||||||
|
int profile_build(vmie_win32* v, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age);
|
||||||
|
|
||||||
|
/* bootstrap helper (win32 host bring-up) */
|
||||||
|
int cr3_recover(vmie_win32* v, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) __attribute__((cold));
|
||||||
|
|
||||||
|
#endif /* VMIE_ENGINE_WIN32_H */
|
||||||
@@ -1,10 +1,56 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "vmie.h"
|
#include <stdlib.h>
|
||||||
#include "contract.h"
|
#include "win32.h"
|
||||||
#include "engine.h"
|
#include "engine-win32.h"
|
||||||
|
|
||||||
#define MZ 0x5A4Du
|
#define MZ 0x5A4Du
|
||||||
|
|
||||||
|
/* ---- lifecycle (cold) ---------------------------------------------------- */
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
vmie_win32* vmie_win32_open(const char* ram_path, uint64_t low) {
|
||||||
|
vmie_win32* v = calloc(1, sizeof *v);
|
||||||
|
if (!v) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (gpa_open(&v->mem, ram_path, low)) {
|
||||||
|
free(v);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((cold))
|
||||||
|
void vmie_win32_close(vmie_win32* v) {
|
||||||
|
if (!v) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpa_close(&v->mem);
|
||||||
|
free(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- bootstrap CR3 recovery (cold) --------------------------------------- *
|
||||||
|
* Scan the image for a candidate PML4 that translates the agent's self-VA to its
|
||||||
|
* known physical anchor, scored by kernel-half liveness. The page-table walk is
|
||||||
|
* borrowed from the arch layer via gva_translate (cold), so the hot path stays
|
||||||
|
* private to gva.c. */
|
||||||
|
__attribute__((cold))
|
||||||
|
int cr3_recover(vmie_win32* v, uint64_t va_self, uint64_t target_pa, uintptr_t* cr3_out) {
|
||||||
|
vmie_mem* m = &v->mem;
|
||||||
|
int best_score = -1; uint64_t best = 0;
|
||||||
|
for (size_t off = 0; off + 0x1000 <= m->fsize; off += 0x1000) {
|
||||||
|
const uintptr_t cand = offset_gpa(m, off);
|
||||||
|
uintptr_t gpa;
|
||||||
|
if (gva_translate(m, cand, va_self, &gpa)) continue;
|
||||||
|
if ((gpa & ~0xFFFull) != (target_pa & ~0xFFFull)) continue;
|
||||||
|
const int score = khalf_score(m, cand);
|
||||||
|
if (score > best_score) { best_score = score; best = cand; }
|
||||||
|
}
|
||||||
|
if (best_score < 0) return -1;
|
||||||
|
*cr3_out = best;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
#define DIR_EXPORT 0u
|
#define DIR_EXPORT 0u
|
||||||
#define DIR_DEBUG 6u
|
#define DIR_DEBUG 6u
|
||||||
#define DBG_CODEVIEW 2u
|
#define DBG_CODEVIEW 2u
|
||||||
@@ -162,12 +208,12 @@ static void beacon_ack(vmie_mem* m, uint64_t anchor_pa) {
|
|||||||
gpa_write(m, anchor_pa + offsetof(contract, ack), &ack, 8);
|
gpa_write(m, anchor_pa + offsetof(contract, ack), &ack, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
vmie_mem* vmie_memory(vmie* v) {
|
vmie_mem* vmie_win32_mem(vmie_win32* v) {
|
||||||
return v ? &v->mem : NULL;
|
return v ? &v->mem : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((cold))
|
__attribute__((cold))
|
||||||
int host_bootstrap(vmie* v) {
|
int host_bootstrap(vmie_win32* v) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
uint64_t anchor_pa, va_self;
|
uint64_t anchor_pa, va_self;
|
||||||
uintptr_t cr3boot;
|
uintptr_t cr3boot;
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "engine.h"
|
#include "engine-win32.h"
|
||||||
#include "vmie.h"
|
#include "win32.h"
|
||||||
|
|
||||||
#define pr_(v) ((v)->prof)
|
#define pr_(v) ((v)->prof)
|
||||||
|
|
||||||
#define RING_GUARD 100000u
|
#define RING_GUARD 100000u
|
||||||
#define MOD_GUARD 4096u
|
#define MOD_GUARD 4096u
|
||||||
|
|
||||||
static void grab_ustr(vmie* v, uintptr_t cr3, uint64_t va, gtext* out) {
|
static void grab_ustr(vmie_win32* v, uintptr_t cr3, uint64_t va, gtext* out) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
uint16_t len = 0;
|
uint16_t len = 0;
|
||||||
uint64_t buf = 0;
|
uint64_t buf = 0;
|
||||||
@@ -23,7 +23,7 @@ static void grab_ustr(vmie* v, uintptr_t cr3, uint64_t va, gtext* out) {
|
|||||||
out->len = len;
|
out->len = len;
|
||||||
}
|
}
|
||||||
|
|
||||||
int proc_list(vmie* v, int skip_system, process* dst, size_t nmax) {
|
int proc_list(vmie_win32* v, int skip_system, process* dst, size_t nmax) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
const profile* p = &pr_(v);
|
const profile* p = &pr_(v);
|
||||||
const uint64_t kcr3 = v->kcr3;
|
const uint64_t kcr3 = v->kcr3;
|
||||||
@@ -74,7 +74,7 @@ int proc_list(vmie* v, int skip_system, process* dst, size_t nmax) {
|
|||||||
return (int)n;
|
return (int)n;
|
||||||
}
|
}
|
||||||
|
|
||||||
int proc_modules(vmie* v, const process* pr, pmodule* dst, size_t nmax) {
|
int proc_modules(vmie_win32* v, const process* pr, pmodule* dst, size_t nmax) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
const profile* p = &pr_(v);
|
const profile* p = &pr_(v);
|
||||||
const uint64_t cr3 = pr->cr3;
|
const uint64_t cr3 = pr->cr3;
|
||||||
@@ -119,7 +119,7 @@ int proc_modules(vmie* v, const process* pr, pmodule* dst, size_t nmax) {
|
|||||||
* Project a Windows process/module list onto the generic cr3/range surface and
|
* Project a Windows process/module list onto the generic cr3/range surface and
|
||||||
* delegate to the OS-agnostic scanners (scan.h). */
|
* delegate to the OS-agnostic scanners (scan.h). */
|
||||||
|
|
||||||
scan* scan_new(vmie* v, const process* pr, scan_type t, const void* value,
|
scan* scan_new(vmie_win32* v, const process* pr, scan_type t, const void* value,
|
||||||
int be, int aligned, uint64_t lo, uint64_t hi) {
|
int be, int aligned, uint64_t lo, uint64_t hi) {
|
||||||
if (!pr) {
|
if (!pr) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -129,7 +129,7 @@ scan* scan_new(vmie* v, const process* pr, scan_type t, const void* value,
|
|||||||
|
|
||||||
#define PTR_MOD_CAP 1024u
|
#define PTR_MOD_CAP 1024u
|
||||||
|
|
||||||
int vmie_scan_pointer(vmie* v, const process* pr, uint64_t target,
|
int vmie_scan_pointer(vmie_win32* v, const process* pr, uint64_t target,
|
||||||
int max_depth, uint32_t max_off, scan_ptr_path* out, int max) {
|
int max_depth, uint32_t max_off, scan_ptr_path* out, int max) {
|
||||||
if (!pr) {
|
if (!pr) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "engine.h"
|
#include "engine-win32.h"
|
||||||
|
|
||||||
#define pr_(v) ((v)->prof)
|
#define pr_(v) ((v)->prof)
|
||||||
|
|
||||||
#define RING_CAP 4096 /* USER_MIN/USER_MAX/KERN_MIN come from engine.h */
|
#define RING_CAP 4096 /* USER_MIN/USER_MAX/KERN_MIN come from engine-arch.h */
|
||||||
#define SCAN_MAX 1024
|
#define SCAN_MAX 1024
|
||||||
#define FT_LO 0x01D0000000000000ll
|
#define FT_LO 0x01D0000000000000ll
|
||||||
#define FT_HI 0x01F0000000000000ll
|
#define FT_HI 0x01F0000000000000ll
|
||||||
@@ -14,7 +14,7 @@ static int canon_ok(uint64_t p, int kernel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Circular LIST_ENTRY walker (Flink at node+0); one primitive for both rings. */
|
/* Circular LIST_ENTRY walker (Flink at node+0); one primitive for both rings. */
|
||||||
static int list_ring_ok(vmie* v, uintptr_t cr3, uint64_t head, int kernel) {
|
static int list_ring_ok(vmie_win32* v, uintptr_t cr3, uint64_t head, int kernel) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
uint64_t node;
|
uint64_t node;
|
||||||
if (gva_read(m, cr3, head, &node, 8)) {
|
if (gva_read(m, cr3, head, &node, 8)) {
|
||||||
@@ -32,7 +32,7 @@ static int list_ring_ok(vmie* v, uintptr_t cr3, uint64_t head, int kernel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Pass 1: ep_name/ep_pid/ep_links/ep_dtb from the System _EPROCESS. */
|
/* Pass 1: ep_name/ep_pid/ep_links/ep_dtb from the System _EPROCESS. */
|
||||||
static int discover_core(vmie* v, uintptr_t cr3, uint64_t sys_ep) {
|
static int discover_core(vmie_win32* v, uintptr_t cr3, uint64_t sys_ep) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
profile* p = &pr_(v);
|
profile* p = &pr_(v);
|
||||||
uint8_t buf[0x800];
|
uint8_t buf[0x800];
|
||||||
@@ -88,7 +88,7 @@ static int discover_core(vmie* v, uintptr_t cr3, uint64_t sys_ep) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Transient snapshot of (eprocess, pid, cr3) over the active ring. */
|
/* Transient snapshot of (eprocess, pid, cr3) over the active ring. */
|
||||||
static int collect_procs(vmie* v, uintptr_t cr3, uint64_t sys_ep, uint64_t* eps, uint32_t* pids, uint64_t* cr3s, int cap) {
|
static int collect_procs(vmie_win32* v, uintptr_t cr3, uint64_t sys_ep, uint64_t* eps, uint32_t* pids, uint64_t* cr3s, int cap) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
const profile* p = &pr_(v);
|
const profile* p = &pr_(v);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
@@ -112,7 +112,7 @@ static int collect_procs(vmie* v, uintptr_t cr3, uint64_t sys_ep, uint64_t* eps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Pass 2a: ep_ppid by population (creator PID). Best-effort. */
|
/* Pass 2a: ep_ppid by population (creator PID). Best-effort. */
|
||||||
static void discover_ppid(vmie* v, uintptr_t cr3, const uint64_t* eps, const uint32_t* pids, int n) {
|
static void discover_ppid(vmie_win32* v, uintptr_t cr3, const uint64_t* eps, const uint32_t* pids, int n) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
int best_off = -1, best_hits = 0;
|
int best_off = -1, best_hits = 0;
|
||||||
for (int o = 0x100; o <= 0x600; o += 8) {
|
for (int o = 0x100; o <= 0x600; o += 8) {
|
||||||
@@ -137,7 +137,7 @@ static void discover_ppid(vmie* v, uintptr_t cr3, const uint64_t* eps, const uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Pass 2b: ep_createtime (CreateTime, FILETIME) -- every sample in boot range, System earliest. Best-effort. */
|
/* Pass 2b: ep_createtime (CreateTime, FILETIME) -- every sample in boot range, System earliest. Best-effort. */
|
||||||
static void discover_createtime(vmie* v, uintptr_t cr3, const uint64_t* eps, int n) {
|
static void discover_createtime(vmie_win32* v, uintptr_t cr3, const uint64_t* eps, int n) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
for (int o = 0x140; o <= 0x600; o += 8) {
|
for (int o = 0x140; o <= 0x600; o += 8) {
|
||||||
int64_t sysv = 0;
|
int64_t sysv = 0;
|
||||||
@@ -160,7 +160,7 @@ static void discover_createtime(vmie* v, uintptr_t cr3, const uint64_t* eps, int
|
|||||||
|
|
||||||
/* Pass 2c: ep_imgpath (ImageFilePathHint) -- UNICODE_STRING whose tail equals the
|
/* Pass 2c: ep_imgpath (ImageFilePathHint) -- UNICODE_STRING whose tail equals the
|
||||||
* process's untruncated ImageFileName; probe short-named (<15) procs only. Best-effort. */
|
* process's untruncated ImageFileName; probe short-named (<15) procs only. Best-effort. */
|
||||||
static void discover_imgpath(vmie* v, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
|
static void discover_imgpath(vmie_win32* v, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
profile* p = &pr_(v);
|
profile* p = &pr_(v);
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
@@ -202,7 +202,7 @@ static void discover_imgpath(vmie* v, uintptr_t cr3, const uint64_t* eps, const
|
|||||||
|
|
||||||
/* Pass 2d: ep_peb + user PEB/Ldr chain; commits the x64-invariant LDR offsets
|
/* Pass 2d: ep_peb + user PEB/Ldr chain; commits the x64-invariant LDR offsets
|
||||||
* (incl. FullDllName) after validating them on the live first entry. */
|
* (incl. FullDllName) after validating them on the live first entry. */
|
||||||
static int discover_user_chain(vmie* v, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
|
static int discover_user_chain(vmie_win32* v, uintptr_t cr3, const uint64_t* eps, const uint64_t* cr3s, int n) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
profile* p = &pr_(v);
|
profile* p = &pr_(v);
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ static int discover_user_chain(vmie* v, uintptr_t cr3, const uint64_t* eps, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((cold))
|
__attribute__((cold))
|
||||||
int profile_build(vmie* v, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age) {
|
int profile_build(vmie_win32* v, uintptr_t cr3, uint64_t sys_ep, const uint8_t guid[16], uint32_t age) {
|
||||||
memset(&pr_(v), 0, sizeof(pr_(v)));
|
memset(&pr_(v), 0, sizeof(pr_(v)));
|
||||||
memcpy(pr_(v).guid, guid, 16);
|
memcpy(pr_(v).guid, guid, 16);
|
||||||
pr_(v).age = age;
|
pr_(v).age = age;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "engine.h"
|
#include "engine-win32.h"
|
||||||
#include "vmie.h"
|
#include "win32.h"
|
||||||
|
|
||||||
static void utf8_emit(uint32_t cp, char* dst, size_t size, size_t* need, size_t* wrote) {
|
static void utf8_emit(uint32_t cp, char* dst, size_t size, size_t* need, size_t* wrote) {
|
||||||
uint8_t b[4]; size_t k;
|
uint8_t b[4]; size_t k;
|
||||||
@@ -16,7 +16,7 @@ static void utf8_emit(uint32_t cp, char* dst, size_t size, size_t* need, size_t*
|
|||||||
*need += k;
|
*need += k;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t gva_read_text(vmie* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size) {
|
size_t gva_read_text(vmie_win32* v, uintptr_t cr3, uintptr_t va, size_t nmemb, char* dst, size_t size) {
|
||||||
vmie_mem* m = &v->mem;
|
vmie_mem* m = &v->mem;
|
||||||
size_t need = 0, wrote = 0;
|
size_t need = 0, wrote = 0;
|
||||||
uint16_t stage[256];
|
uint16_t stage[256];
|
||||||
@@ -66,6 +66,19 @@ bool sig_parse_mask(const uint8_t* b, const char* m, sig_pattern_t* out) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool sig_from_bytes(const uint8_t* bytes, size_t len, sig_pattern_t* out) {
|
||||||
|
if (!bytes || !out || len == 0) return false;
|
||||||
|
|
||||||
|
char* mask = malloc(len + 1); /* all-'x' (exact, no wildcard) */
|
||||||
|
if (!mask) return false;
|
||||||
|
memset(mask, 'x', len);
|
||||||
|
mask[len] = 0;
|
||||||
|
|
||||||
|
const bool ok = sig_parse_mask(bytes, mask, out);
|
||||||
|
free(mask);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
void sig_free(sig_pattern_t* p) {
|
void sig_free(sig_pattern_t* p) {
|
||||||
if (!p) return;
|
if (!p) return;
|
||||||
free(p->bytes); free(p->mask);
|
free(p->bytes); free(p->mask);
|
||||||
|
|||||||
Reference in New Issue
Block a user