This page describes the C++ example programs and tools included with IntroVirt. The main instructional example is vmcall_interface, which shows how to implement a custom hypercall interface between a Windows guest and an IntroVirt tool.
For Python examples and tools (ivsyscallmon, ivcallmon, ivfilemon), see the Python documentation.
Example source files (marked with @example in code) are listed in the Examples menu in the navigation; each entry links to the full source. Use this page for walkthroughs, build, and usage; use the Examples list for the actual code.
vmcall_interface
Source:
The vmcall_interface example demonstrates using the x86 vmcall instruction to communicate between a process running inside a Windows guest and an IntroVirt tool running on the host. The guest requests services (reverse a string, write-protect memory, protect the process) by executing vmcall with a service code; the hypervisor delivers the event to the IntroVirt tool, which performs the action and returns a status in RAX.
A Python port of the host tool is also available (examples/vmcall_interface.py); it uses the raw IntroVirt Python bindings (not pyintrovirt helpers) and implements the same three services.
There are two components:
| Component | Location | Role |
| Host tool | examples/vmcall_interface (built with IntroVirt) | Attaches to the VM, handles hypercall and system-call events, implements the services. |
| Guest executable | examples/guest/vmcall_interface (build on Windows) | Runs inside the VM and issues vmcall to request each service. |
Architecture overview
- Guest code sets RAX = 0xFACE (IntroVirt hypercall opcode), RCX = service code, and optional args in RDX, R8, R9, then executes
vmcall.
- The KVM IntroVirt kernel patch recognizes the hypercall and delivers an EVENT_HYPERCALL to the IntroVirt tool.
- The tool's event callback (introvirt::EventCallback) reads registers, dispatches on the service code, and implements the action (e.g. reverse string in guest memory, create a watchpoint).
- The tool sets RAX to a return code and returns; execution resumes in the guest with that return value.
Service codes used in this example:
| Code | Name | Description |
0xF000 | CSTRING_REVERSE | Reverse a C-string in place (pointer in RDX). |
0xF001 | WRITE_PROTECT | Make a memory region read-only (buffer in RDX, length in R8). |
0xF002 | PROTECT_PROCESS | Protect the calling process from termination, injection, and debugging. |
System call filtering (Windows): For Windows guests, enable the filter at the domain level (domain.system_call_filter().enabled(true)) and set which syscalls to trap at the guest level via WindowsGuest.set_system_call_filter(domain.system_call_filter(), SystemCallIndex_XXX, true). Do not call set_64 or set_32 on the domain filter; the guest converts SystemCallIndex to native indices. This matches ivfilemon, ivcallmon, and vmcall_interface.
Host side: IntroVirt tool
The host tool attaches to a domain, detects a Windows guest, enables system-call interception for NtTerminateProcess and NtOpenProcess (for cleanup and process protection), then polls for events. Hypercalls are handled in EventHandler::handle_hypercall(); system calls are used to clean up watchpoints on exit and to enforce process protection.
Attaching and starting the event loop
- C++
auto hypervisor = Hypervisor::instance();
domain = hypervisor->attach_domain(domain_name);
if (!
domain->detect_guest()) { }
if (
domain->guest()->os() != OS::Windows) { }
auto* guest =
static_cast<WindowsGuest*
>(
domain->guest());
guest->set_system_call_filter(
domain->system_call_filter(),
SystemCallIndex::NtTerminateProcess, true);
guest->set_system_call_filter(
domain->system_call_filter(),
SystemCallIndex::NtOpenProcess, true);
domain->system_call_filter().enabled(
true);
domain->intercept_system_calls(
true);
void sig_handler(int signum)
Definition vmcall_interface.cc:571
EventHandler event_handler
Definition vmcall_interface.cc:457
unique_ptr< Domain > domain
Definition vmcall_interface.cc:48
- Python
domain = hypervisor.attach_domain(domain_name)
if not domain.detect_guest():
raise RuntimeError("Failed to detect guest")
if domain.guest().os() != introvirt.OS_Windows:
raise RuntimeError("Windows guest required")
win_guest = introvirt.WindowsGuest_from_guest(domain.guest())
win_guest.set_system_call_filter(
domain.system_call_filter(), introvirt.SystemCallIndex_NtTerminateProcess, True)
win_guest.set_system_call_filter(
domain.system_call_filter(), introvirt.SystemCallIndex_NtOpenProcess, True)
domain.system_call_filter().enabled(True)
domain.intercept_system_calls(True)
domain.poll(handler)
static std::unique_ptr< Hypervisor > instance()
Get an instance of the hypervisor.
Handling hypercalls
The callback receives EVENT_HYPERCALL, then reads the service code from RCX and dispatches:
- C++
void handle_hypercall(Event& event) {
auto& regs = event.vcpu().registers();
int return_code = 1;
switch (regs.rcx()) {
return_code = service_string_reverse(event);
break;
return_code = service_write_protect(event);
break;
return_code = service_protect_process(event);
break;
default:
return_code = 1;
}
regs.rax(return_code);
}
@ PROTECT_PROCESS
Definition vmcall_interface.cc:55
@ WRITE_PROTECT
Definition vmcall_interface.cc:54
@ CSTRING_REVERSE
Definition vmcall_interface.cc:53
- Python
def _handle_hypercall(self, event):
task = event.task()
vcpu = event.vcpu()
regs = vcpu.registers()
return_code = 1
if regs.rcx() == CSTRING_REVERSE:
return_code = self._service_string_reverse(event)
elif regs.rcx() == WRITE_PROTECT:
return_code = self._service_write_protect(event)
elif regs.rcx() == PROTECT_PROCESS:
return_code = self._service_protect_process(event)
rax_val = return_code if return_code >= 0 else (1 << 64) + return_code
introvirt.set_register_rax(regs, rax_val)
Service: reverse string (CSTRING_REVERSE)
The guest passes a pointer to a C-string in RDX. The tool reads the string from guest memory, reverses it in place, and returns 0 on success:
- C++
int service_string_reverse(Event& event) {
auto& regs = event.vcpu().registers();
try {
guest_ptr<void> pStr(event.vcpu(), regs.rdx());
guest_ptr<char[]> str = map_guest_cstring(pStr);
reverse(str.begin(), str.end());
return 0;
} catch (VirtualAddressNotPresentException& ex) {
return -1;
}
}
- Python
def _service_string_reverse(self, event) -> int:
vcpu = event.vcpu()
addr = vcpu.registers().rdx()
try:
s = introvirt.read_guest_cstring(self._domain, vcpu, addr)
rev = s[::-1]
introvirt.write_guest_bytes(self._domain, vcpu, addr, rev.encode() + b"\x00")
return 0
except Exception:
return -1
Service: write-protect memory (WRITE_PROTECT)
The guest passes a buffer pointer in RDX and length in R8. The tool creates a watchpoint for writes to that region; on write it injects a fault. Watchpoints are stored per-PID and removed when the process exits (via NtTerminateProcess handling):
- C++
int service_write_protect(Event& event) {
auto& regs = event.vcpu().registers();
guest_ptr<void> pBuffer(event.vcpu(), regs.rdx());
uint64_t length = regs.r8();
auto wp =
domain->create_watchpoint(
pBuffer, length, false, true, false,
[this](Event& e) { memory_access_violation(e); });
read_only_protections_[event.task().pid()].push_back(std::move(wp));
return 0;
}
- Python
def _service_write_protect(self, event) -> int:
vcpu = event.vcpu()
regs = vcpu.registers()
addr, length = regs.rdx(), regs.r8()
if length == 0:
return -1
wp = introvirt.create_watchpoint(
self._domain, vcpu, addr, length,
read=False, write=True, execute=False,
callback=self._wp_callback,
)
if wp is not None:
pid = event.task().pid()
self._watchpoints.setdefault(pid, []).append(wp)
return 0
return -1
Service: protect process (PROTECT_PROCESS)
The tool adds the calling process's PID to a protected set. In NtTerminateProcess it blocks termination of that PID; in NtOpenProcess it blocks opening the process with terminate/write/debug rights.
- C++
int service_protect_process(Event& event) {
protected_pids_.insert(event.task().pid());
return 0;
}
- Python
def _service_protect_process(self, event) -> int:
self._protected_pids.add(event.task().pid())
return 0
Guest side: making the vmcall
On x64 Windows the first four integer/pointer arguments use RCX, RDX, R8, R9. The IntroVirt KVM patch treats a vmcall as an IntroVirt hypercall when RAX == 0xFACE. The guest assembler sets RAX, RCX (and RDX/ R8/ R9 as needed), then executes vmcall; the return status is in RAX.
Assembler: hypercall stub (vmcall.asm)
; RAX = 0xFACE identifies the call to IntroVirt.
; RCX = service code; RDX, R8, R9 = arguments (per service).
HypercallReverseCString PROC
mov rax, 0FACEh
mov rdx, rcx ; c_str pointer in RDX
mov rcx, 0F000h ; CSTRING_REVERSE
vmcall
ret
HypercallReverseCString ENDP
HypercallWriteProtectMemory PROC
mov rax, 0FACEh
mov r8, rdx ; length in R8
mov rdx, rcx ; buffer in RDX
mov rcx, 0F001h ; WRITE_PROTECT
vmcall
ret
HypercallWriteProtectMemory ENDP
HypercallProtectProcess PROC
mov rax, 0FACEh
mov rcx, 0F002h ; PROTECT_PROCESS
vmcall
ret
HypercallProtectProcess ENDP
Assemble with LLVM (e.g. llvm-ml -m64 -c vmcall.asm -o vmcall.obj).
Guest C code (main.c)
The C side declares the assembler functions and uses them for each demo:
char test_str[] = "Hello, IntroVirt!";
if (status == 0)
printf("Reversed string: %s\n", test_str);
return status;
}
buffer[i] = 'B';
}
while (running) { Sleep(100); }
return 0;
}
int mem_protect()
Definition main.c:79
uint64_t HypercallReverseCString(char *c_str)
uint64_t HypercallWriteProtectMemory(void *buffer, uint64_t length)
int reverse_string()
Definition main.c:61
#define BUFFER_SIZE
Definition main.c:16
int protect_process()
Definition main.c:125
uint64_t HypercallProtectProcess()
Building
Host tool (Linux, as part of IntroVirt)
Guest executable (Windows)
Build the guest code on a Windows VM with Clang/LLVM on the path (e.g. from LLVM releases).
- Copy the guest example into the VM:
- From that directory in PowerShell:
.\build.ps1
# Produces .\bin\vmcall_test.exe
Usage
- Start the VM (e.g. a Windows guest) and ensure the guest has
vmcall_test.exe (or equivalent) available. Do not run it yet.
- Start the IntroVirt tool on the host, attaching by domain name or PID:
- C++
cd build
sudo ./examples/vmcall_interface -D win10
# Or by domain PID: sudo ./examples/vmcall_interface -D 12345
- Python
cd build
sudo PYTHONPATH=./python python3 ../examples/vmcall_interface.py win10
# Or by domain PID: sudo PYTHONPATH=./python python3 ../examples/vmcall_interface.py 12345
Leave this running. You should see it attach and detect the Windows guest.
- Run the guest executable inside the VM with one of the three commands:
| Guest command | What it does |
vmcall_test.exe reverse-string | Reverses the string "Hello, IntroVirt!" in place via hypercall; prints original and reversed. |
vmcall_test.exe mem-protect | Asks the hypervisor to write-protect a buffer, then writes to it; the write triggers a fault and the program will crash (access violation) if protection is active. Use Ctrl+C to exit. |
vmcall_test.exe protect-process | Marks the process as protected. The hypervisor blocks termination and opening the process with dangerous rights; the process cannot be killed or debugged by other guest programs. Use Ctrl+C in the host vmcall_interface terminal to stop the tool and detach; then the guest process will exit. |
- Stop the host tool with Ctrl+C in the terminal where
vmcall_interface is running so it can detach cleanly from the domain.
Summary
- vmcall_interface shows a full hypercall-based "API" between a Windows guest and an IntroVirt tool: guest uses RAX=0xFACE, RCX=service code, RDX/R8/R9=arguments, then
vmcall; the host handles EVENT_HYPERCALL and returns status in RAX.
- It also uses system-call interception (NtTerminateProcess, NtOpenProcess) to clean up state and to enforce process protection, illustrating how to combine hypercalls with Windows system-call filtering in one tool.
For the full source, see vmcall_interface.cc (C++) or vmcall_interface.py (Python), and examples/guest/vmcall_interface/ for the guest executable.