Inspiration

  • I have reverse engineered, decompiled, and written my own rootkits for a while and recently saw a post from link science direct and their explination of a KVMInspector I found it very intestering and wanted to learn more about it.
  • I prevously had experinece with Rust and ASM and hypervisors which was the perfect combo for this project.
  • I had found other projects close to this one and the idea landed in my head after digging around through some saved gitub repos, blogs, and rss feeds.

What it does

  • Spectral attaches to a running KVM virtual machine from the host and performs live memory forensics without deploying anything inside the guest. It reads guest physical memory directly, walks Windows kernel structures, enumerates all running processes, and cross-references the EPROCESS linked list against PspCidTable to detect DKOM-hidden processes the guest OS itself cannot see.

These links explain this better than I can :)

How we built it

  • Built in Rust using the KVM ioctl interface (/dev/kvm) and kvm-ioctls crate. We read guest vCPU state via KVM_GET_SREGS to extract CR3, mapped guest physical addresses to host virtual addresses, then walked Windows kernel structures manually using known offsets for a target Win11 build. The asm handles raw VMX register access where needed (I had issues doing this with inline asm since qemu is a giant library).

Challenges we ran into

  • I had alot of issues and stuff. Weather its my hardware being weak or the vm breaking. These are the two most notable

Bug 1 is the Infinite loop / hang (ptrace_inject.rs)

The waitpid loop never checked for process death. If QEMU crashed while the gadget was executing, waitpid returned WIFEXITED/WIFSIGNALED, PTRACE_GETREGS failed with ESRCH leaving rip zeroed, and the loop spun forever since 0 >= gadget_addr+2 never satisfied. Required SIGKILL to escape. Fix: check WIFEXITED || WIFSIGNALED immediately after waitpid and return an error.

Bug is the VM crash (the root cause) inject_ioctl was called twice in sequence once for sregs, once for regs. The first call used PTRACE_CONT data=0 to suppress SIGSTOP delivery then PTRACE_DETACH, which left the QEMU main thread running while all vCPU threads remained in group-stop. The second call then PTRACE_SEIZEd a running main thread and blocked on waitpid waiting for a stop that required the main thread to receive another signal. With vCPUs frozen and the main thread blocked, QEMU's watchdog detected deadlocked vCPUs and killed the guest — which unblocked waitpid with WIFEXITED, triggering Bug 1. Fix: batch both ioctls in a single injection session without PTRACE_DETACH in between, and add PTRACE_INTERRUPT after PTRACE_SEIZE to handle the running-thread case.

Accomplishments that we're proud of

  • Reading live kernel structures from outside a running OS with no guest-side code at all. The DKOM detection working on a real hidden process was the moment it clicked

What we learned

  • How KVM exposes VM state via ioctls, how VMX guest/host memory separation works in practice, Windows kernel internals at the structure level (EPROCESS, PspCidTable, VAD trees), and how enterprise hypervisor security tools actually work under the hood.

What's next for Spectral - KVM-hypervisor-introspector

  • I will make a blog post about this on my website link
  • I will also present this to the Cyber Security club at my highschool since I am hoping that somone there interested about this
  • I need to make actual README.md on the github as well
  • I want to expand onto more cloud based ane vm based security this was amazing to do

Built With

  • asm
  • cybersecurity
  • kvm
  • kvm-ioctls
  • rust
  • vmm-sys-util
  • x86-64
Share this project:

Updates