VMM for AArch64 KVM introspection that runs a vCPU guest and installs Stage2 translation traps by omitting selected IPA pages from KVM memslots, and records every selected KVM_RUN exit as JSONL. Trapped data accesses are handled through KVM_EXIT_MMIO and replayed by the host from a userspace RAM image
cc -Iinclude -D_GNU_SOURCE -O2 -g -std=c11 -Wall -Wextra -Wpedantic \
-o aperture \
src/main.c src/config.c src/interval.c src/loader.c src/ring.c src/trace.c \
src/vmm_kvm_arm64.cOn a non AArch64 development host, replace src/vmm_kvm_arm64.c with src/vmm_stub.c
Flat payloads are baremetal AArch64 code as they are not Linux userspace executables. A minimal payload can write one byte to the default UART page and then stop the VM:
.text
.global _start
_start:
mov x0, #0x1000
movk x0, #0x43ff, lsl #16
mov w1, #'A'
strb w1, [x0]
mov x0, #0x0000
movk x0, #0x43ff, lsl #16
mov x1, #1
str x1, [x0]
1:
wfe
b 1bAssemble that into a flat binary:
clang --target=aarch64-none-elf -c -o payload.o payload.S
llvm-objcopy -O binary -j .text payload.o payload.binaperture emits newline delimited JSON. Event records use host observation order:
{"seq":1,"type":"mmio_write","vcpu":0,"pc":"0x0000000040000020","gpa":"0x0000000040100000","len":8,"value":"0x0000000000000001","exit_reason":6,"flags":0,"label":"credentials"}--dump FILE writes the final guest RAM image after a clean VM exit. The dump path must be a file path and cant be - and must differ from the trace path
RAM dumps are written after the VMM loop completes so denied trap actions and unsupported KVM exits do not produce a dump. This preserves a distinction between clean terminal state and failed introspection
Validate a configuration without running the VM:
./aperture validate \
--payload payload.bin \
--payload-format flat \
--trap 0x40002000:4k:r:read_watch:zero \
--dump guest.ram \
--trace exits.jsonlValidation checks payload presence amongst a long list of other things