For full documentation, build instructions, guest setup, and usage, see the \ref examples_doc "Example documentation" page.
Requires root and IntroVirt-patched hypervisor. Guest must issue VMCALL with service code in RCX (0xF000, 0xF001, 0xF002).
1
2"""! @example vmcall_interface.py
3
4VMCALL interface (vmcall_interface clone).
5
6Demonstrates: (1) CSTRING_REVERSE - read C string from guest (RDX), reverse in place;
7(2) WRITE_PROTECT - watchpoint on (RDX, R8), inject GP on write;
8(3) PROTECT_PROCESS - block NtTerminateProcess/NtOpenProcess for protected PIDs.
9
10For full documentation, build instructions, guest setup, and usage, see the
11\\ref examples_doc "Example documentation" page.
12
13Usage:
14 python3 vmcall_interface.py DOMAIN
15
16Requires root and IntroVirt-patched hypervisor. Guest must issue VMCALL with
17service code in RCX (0xF000, 0xF001, 0xF002).
18"""
19import argparse
20import signal
21import sys
22import threading
23
24import introvirt
25
26
27CSTRING_REVERSE = 0xF000
28WRITE_PROTECT = 0xF001
29PROTECT_PROCESS = 0xF002
30
31_domain = None
32
33
34def _interrupt_listener():
35 signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGINT])
36 while True:
37 try:
38 signal.sigwait([signal.SIGINT])
39 except (ValueError, OSError):
40 break
41 d = _domain
42 if d is not None:
43 d.interrupt()
44
45
46class WatchpointHandler(introvirt.WatchpointCallback):
47 def __init__(self, domain):
48 super().__init__()
49 self._domain = domain
50
51 def watchpoint_hit(self, event):
52 """Called from C++ when watchpoint fires; must not raise or SWIG raises DirectorMethodException."""
53 try:
54 task = event.task()
55 vcpu = event.vcpu()
56 print(f"Memory access violation in {task.process_name()} [{task.pid()}:{task.tid()}]")
57 if event.mem_access().write_violation():
58 print(f"\tProcess wrote to read-only memory!")
59 print(f"\tPhysical address: 0x{event.mem_access().physical_address_value():x}")
60 print(f"\tRIP: 0x{vcpu.registers().rip():x}")
61 vcpu.inject_exception(introvirt.Exception.GP_FAULT.value, 0)
62 sys.stdout.flush()
63 except Exception as e:
64 import traceback
65 print(f"watchpoint_hit error: {e}", file=sys.stderr)
66 traceback.print_exc(file=sys.stderr)
67 sys.stderr.flush()
68
69
71 def __init__(self, domain):
72 super().__init__()
73 self._domain = domain
74 self._protected_pids = set()
75 self._watchpoints = {}
76 self._pending_terminate = {}
77 self._lock = threading.Lock()
78 self._wp_callback = WatchpointHandler(domain)
79
80 def process_event(self, event):
81 try:
83 self._handle_hypercall(event)
85 self._handle_syscall(event)
87 self._handle_sysret(event)
89 self._handle_mem_access(event)
90 except Exception as e:
91 print(f"process_event error: {e}", file=sys.stderr)
92 import traceback
93 traceback.print_exc(file=sys.stderr)
94
95 def _handle_hypercall(self, event):
96 task = event.task()
97 vcpu = event.vcpu()
98 regs = vcpu.registers()
99 print(f"{task.process_name()} [{task.pid()}:{task.tid()}]")
100 print(f"\tRIP: 0x{regs.rip():x} RCX: 0x{regs.rcx():x} RDX: 0x{regs.rdx():x} R8: 0x{regs.r8():x}")
101 return_code = 1
102 try:
103 if regs.rcx() == CSTRING_REVERSE:
104 print("\tCSTRING_REVERSE requested")
105 return_code = self._service_string_reverse(event)
106 elif regs.rcx() == WRITE_PROTECT:
107 print("\tWRITE_PROTECT requested")
108 return_code = self._service_write_protect(event)
109 elif regs.rcx() == PROTECT_PROCESS:
110 print("\tPROTECT_PROCESS requested")
111 return_code = self._service_protect_process(event)
112 else:
113 print(f"\tUnknown service code: 0x{regs.rcx():x}")
114 except Exception as e:
115 print(f"\tService error: {e}", file=sys.stderr)
116 return_code = -1
117
118 rax_val = return_code if return_code >= 0 else (1 << 64) + return_code
119 introvirt.set_register_rax(regs, rax_val)
120 print(f"\tReturning status code: {return_code}")
121 sys.stdout.flush()
122
123 def _service_string_reverse(self, event) -> int:
124 vcpu = event.vcpu()
125 regs = vcpu.registers()
126 addr = regs.rdx()
127 try:
128 s = introvirt.read_guest_cstring(self._domain, vcpu, addr)
129 print(f"\tReversing input string [{s}]")
130 rev = s[::-1]
131 data = rev.encode("utf-8") + b"\x00"
132 introvirt.write_guest_bytes(self._domain, vcpu, addr, data)
133 print(f"\tReversed string is now [{rev}]")
134 return 0
135 except Exception as e:
136 print(f"\tString reverse failed: {e}", file=sys.stderr)
137 return -1
138
139 def _service_write_protect(self, event) -> int:
140 vcpu = event.vcpu()
141 regs = vcpu.registers()
142 addr = regs.rdx()
143 length = regs.r8()
144 if length == 0:
145 return -1
146 try:
147 wp = introvirt.create_watchpoint(
148 self._domain, vcpu, addr, length,
149 read=False, write=True, execute=False,
150 callback=self._wp_callback,
151 )
152 if wp is not None:
153 pid = event.task().pid()
154 with self._lock:
155 self._watchpoints.setdefault(pid, []).append(wp)
156 print("\tWatchpoint created successfully")
157 return 0
158 except Exception as e:
159 print(f"\tWatchpoint failed: {e}", file=sys.stderr)
160 return -1
161
162 def _service_protect_process(self, event) -> int:
163 pid = event.task().pid()
164 with self._lock:
165 self._protected_pids.add(pid)
166 print(f"\tProtected PID {pid} from termination, injection, and debugging")
167 return 0
168
169 def _handle_syscall(self, event):
170 wevent = introvirt.WindowsEvent_from_event(event)
171 if wevent is None:
172 return
173 index = wevent.syscall().index()
174 handler = wevent.syscall().handler()
175 if handler is None:
176 return
177 handler = introvirt.get_concrete_handler(wevent)
178 task = wevent.task()
179 pid = task.pid()
180 tid = task.tid()
181
182 if index == introvirt.SystemCallIndex.NtTerminateProcess.value:
183 target = handler.target_pid()
184 with self._lock:
185 if target in self._protected_pids:
186 handler.ProcessHandle(0xFFFFFFFFFFFFFFFF)
187 print(f"Blocked termination of protected PID {target} by {task.process_name()}[{pid}:{tid}]")
188 return
189 if target == pid:
190 self._protected_pids.discard(pid)
191 self._watchpoints.pop(pid, None)
192 return
193 if not handler.will_return():
194 return
195 wevent.syscall().hook_return(True)
196 with self._lock:
197 self._pending_terminate[(pid, tid)] = target
198 return
199
200 if index == introvirt.SystemCallIndex.NtOpenProcess.value:
201 target = introvirt.get_nt_open_process_target_pid(handler)
202 with self._lock:
203 if target in self._protected_pids:
204 introvirt.block_open_process_client_id(handler)
205 print(f"Blocked NtOpenProcess for protected PID {target} by {task.process_name()}[{pid}:{tid}]")
206 return
207
208 def _handle_sysret(self, event):
209 wevent = introvirt.WindowsEvent_from_event(event)
210 if wevent is None:
211 return
212 if wevent.syscall().index() != introvirt.SystemCallIndex.NtTerminateProcess.value:
213 return
214 handler = wevent.syscall().handler()
215 if handler is None or not handler.result().NT_SUCCESS():
216 return
217 task = event.task()
218 pid, tid = task.pid(), task.tid()
219 with self._lock:
220 target = self._pending_terminate.pop((pid, tid), None)
221 if target is None:
222 return
223 with self._lock:
224 if target in self._protected_pids:
225 self._protected_pids.discard(target)
226 self._watchpoints.pop(target, None)
227 print(f"{task.process_name()} [{pid}:{tid}]")
228 print(f"\tTerminated PID {target} - protections removed")
229 sys.stdout.flush()
230
231 def _handle_mem_access(self, event):
232 if event.mem_access().write_violation():
233 self._wp_callback.watchpoint_hit(event)
234
235
237 global _domain
238 parser = argparse.ArgumentParser(
239 description="VMCALL interface example: string reverse, write-protect, process protection."
240 )
241 parser.add_argument("domain", metavar="DOMAIN", help="Domain name or ID")
242 args = parser.parse_args()
243
244 try:
246 except Exception as e:
247 print(f"Failed to get hypervisor: {e}", file=sys.stderr)
248 return 1
249 try:
250 _domain = hypervisor.attach_domain(args.domain)
251 except Exception as e:
252 print(f"Failed to attach: {e}", file=sys.stderr)
253 return 1
254 if not _domain.detect_guest():
255 print("Failed to detect guest OS", file=sys.stderr)
256 return 1
257
258 guest = _domain.guest()
259 if guest
is None or introvirt.OS(guest.os()) != introvirt.OS.Windows:
260 print("Windows guest required", file=sys.stderr)
261 return 1
262 win_guest = introvirt.WindowsGuest_from_guest(guest)
263 if win_guest is None:
264 print("Windows guest required", file=sys.stderr)
265 return 1
266
267
268 win_guest.set_system_call_filter(
269 _domain.system_call_filter(), introvirt.SystemCallIndex.NtTerminateProcess.value, True
270 )
271 win_guest.set_system_call_filter(
272 _domain.system_call_filter(), introvirt.SystemCallIndex.NtOpenProcess.value, True
273 )
274 _domain.system_call_filter().enabled(True)
275 _domain.intercept_system_calls(True)
276
277 handler = VmcallHandler(_domain)
278 signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGINT])
279 listener = threading.Thread(target=_interrupt_listener, daemon=True)
280 listener.start()
281 _domain.poll(handler)
282 return 0
283
284
285if __name__ == "__main__":
Interface for an event poller callback.
Definition EventCallback.hh:29
static std::unique_ptr< Hypervisor > instance()
Get an instance of the hypervisor.
int main(int argc, char **argv)
Definition main.c:35
EventType
Enum describing a hypervisor event.
Definition EventType.hh:29