1mod hooks;
2
3use std::{
4 collections::HashMap,
5 path::PathBuf,
6 sync::{
7 Arc,
8 atomic::{AtomicBool, Ordering},
9 },
10};
11
12use isr::{Profile, macros::symbols};
13use vmi::{
14 MemoryAccess, Registers as _, View, VmiContext, VmiError, VmiEventResponse, VmiHandler,
15 VmiSession,
16 arch::amd64::{Amd64, EventMonitor, EventReason, ExceptionVector, Interrupt},
17 driver::VmiFullDriver,
18 os::{
19 ProcessId, ProcessObject, ThreadId, ThreadObject, VmiOsProcess as _, VmiOsThread as _,
20 windows::WindowsOs,
21 },
22 utils::{
23 bpm::{Breakpoint, BreakpointController, BreakpointManager},
24 bridge::Bridge,
25 ptm::PageTableMonitor,
26 tracker::Tracker,
27 },
28};
29
30use self::hooks::MonitorHook;
31use crate::{
32 bridge::BridgeOutput,
33 deploy::{DeployBridge, DeployPolicy, DeployStatus, ExecuteResponse},
34 file_transfer::{FileTransfer, FileTransferBridge},
35};
36
37struct Process<Driver>
39where
40 Driver: VmiFullDriver<Architecture = Amd64>,
41{
42 pid: ProcessId,
44
45 ppid: ProcessId,
47
48 name: String,
50
51 file_transfers: HashMap<u64, FileTransfer<Driver>>,
53}
54
55impl<Driver> Process<Driver>
56where
57 Driver: VmiFullDriver<Architecture = Amd64>,
58{
59 fn new(pid: ProcessId, ppid: ProcessId, name: String) -> Self {
61 Self {
62 pid,
63 ppid,
64 name,
65 file_transfers: HashMap::new(),
66 }
67 }
68
69 fn record_file_transfer(&mut self, transfer: FileTransfer<Driver>) -> bool {
73 self.file_transfers
74 .insert(transfer.handle(), transfer)
75 .is_none()
76 }
77
78 fn take_file_transfer(&mut self, handle: u64) -> Option<FileTransfer<Driver>> {
80 self.file_transfers.remove(&handle)
81 }
82}
83
84struct Thread<Driver>
86where
87 Driver: VmiFullDriver<Architecture = Amd64>,
88{
89 #[expect(unused)]
91 tid: ThreadId,
92
93 file_transfer: Option<FileTransfer<Driver>>,
95}
96
97impl<Driver> Thread<Driver>
98where
99 Driver: VmiFullDriver<Architecture = Amd64>,
100{
101 fn new(tid: ThreadId) -> Self {
103 Self {
104 tid,
105 file_transfer: None,
106 }
107 }
108}
109
110symbols! {
111 #[derive(Debug)]
112 struct Symbols {
113 PspInsertProcess: u64,
114 PspInsertThread: u64,
115 KeTerminateThread: u64,
116 MmCleanProcessAddressSpace: u64,
117 NtWriteFile: u64,
118 NtClose: u64,
119 }
120}
121
122struct MonitorState<Driver>
124where
125 Driver: VmiFullDriver<Architecture = Amd64>,
126{
127 tracker: Tracker<Process<Driver>, Thread<Driver>>,
129
130 creator_thread: Option<ThreadObject>,
132
133 target_process: Option<ProcessObject>,
135
136 output: Option<<Monitor<Driver> as VmiHandler<WindowsOs<Driver>>>::Output>,
138}
139
140pub struct Monitor<Driver>
145where
146 Driver: VmiFullDriver<Architecture = Amd64>,
147{
148 terminate_flag: Arc<AtomicBool>,
149 view: View,
150 bpm: BreakpointManager<BreakpointController<Driver>, (), MonitorHook<Driver>>,
151 ptm: PageTableMonitor<Driver, MonitorHook<Driver>>,
152 bridge: Bridge<WindowsOs<Driver>, (DeployBridge, FileTransferBridge)>,
153 state: MonitorState<Driver>,
154}
155
156impl<Driver> Monitor<Driver>
157where
158 Driver: VmiFullDriver<Architecture = Amd64>,
159{
160 pub fn new(
162 session: &VmiSession<WindowsOs<Driver>>,
163 profile: &Profile,
164 terminate_flag: Arc<AtomicBool>,
165 output_directory: PathBuf,
166 ) -> Result<Self, VmiError> {
167 let paused = session.pause_guard()?;
168 let vmi = paused.state();
169
170 let kernel_image_base = vmi.os().kernel_image_base()?;
171 let root = vmi.os().system_process()?.translation_root()?;
172 let symbols = Symbols::new(profile)?;
173
174 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
175 vmi.monitor_enable(EventMonitor::Singlestep)?;
176 vmi.monitor_enable(EventMonitor::Hypercall {
177 allow_userspace: true,
178 })?;
179
180 let view = vmi.create_view(MemoryAccess::RWX)?;
181 vmi.switch_to_view(view)?;
182
183 let mut bpm = BreakpointManager::new();
184 let mut ptm = PageTableMonitor::new();
185
186 macro_rules! install {
187 ($($name:ident),+ $(,)?) => {
188 $(
189 let va = kernel_image_base + symbols.$name;
190 let context = (va, root);
191 let hook = MonitorHook {
192 name: stringify!($name),
193 callback: self::hooks::$name::<Driver>,
194 };
195 let breakpoint = Breakpoint::new(context, view).global().with_tag(hook);
196 bpm.insert(&vmi, breakpoint)?;
197 ptm.monitor(&vmi, context, view, hook)?;
198 tracing::debug!(
199 ?hook,
200 %va,
201 "installed monitor hook"
202 );
203 )+
204 };
205 }
206
207 install!(
208 PspInsertProcess,
209 PspInsertThread,
210 KeTerminateThread,
211 MmCleanProcessAddressSpace,
212 NtWriteFile,
213 NtClose,
214 );
215
216 Ok(Self {
217 terminate_flag,
218 view,
219 bpm,
220 ptm,
221 bridge: Bridge::new((
222 DeployBridge::new(
223 DeployPolicy::default().execute_response(ExecuteResponse::ContinueAndNotify),
224 ),
225 FileTransferBridge::new(output_directory),
226 )),
227 state: MonitorState {
228 tracker: Tracker::default(),
229 creator_thread: None,
230 target_process: None,
231 output: None,
232 },
233 })
234 }
235
236 #[tracing::instrument(skip_all)]
237 fn memory_access(
238 &mut self,
239 vmi: &VmiContext<WindowsOs<Driver>>,
240 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
241 let memory_access = vmi.event().reason().as_memory_access();
242
243 if memory_access.access.contains(MemoryAccess::W) {
244 self.ptm
245 .mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
246
247 Ok(VmiEventResponse::singlestep().with_view(vmi.default_view()))
248 }
249 else if memory_access.access.contains(MemoryAccess::R) {
250 Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
251 }
252 else {
253 panic!("unhandled memory access: {memory_access:?}");
254 }
255 }
256
257 #[tracing::instrument(skip_all)]
258 fn interrupt(
259 &mut self,
260 vmi: &VmiContext<WindowsOs<Driver>>,
261 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
262 let hook = match self.bpm.get_by_event(vmi.event(), ()) {
263 Some(breakpoint) => breakpoint.tag(),
264 None => {
265 if BreakpointController::is_breakpoint(vmi, vmi.event())? {
266 tracing::warn!("unknown breakpoint, reinjecting");
267 return Ok(VmiEventResponse::reinject_interrupt());
268 }
269
270 tracing::warn!("ignoring stale breakpoint event");
271 return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
272 }
273 };
274
275 (hook.callback)(vmi, &mut self.state)
276 }
277
278 #[tracing::instrument(skip_all)]
279 fn singlestep(
280 &mut self,
281 vmi: &VmiContext<WindowsOs<Driver>>,
282 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
283 let events = self.ptm.process_dirty_entries(vmi, vmi.event().vcpu_id())?;
284 self.bpm.handle_ptm_events(vmi, events)?;
285
286 Ok(VmiEventResponse::default().with_view(self.view))
287 }
288
289 #[tracing::instrument(skip_all)]
292 fn hypercall(
293 &mut self,
294 vmi: &VmiContext<WindowsOs<Driver>>,
295 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
296 let hypercall = vmi.event().reason().as_hypercall();
297
298 let mut registers = vmi.registers().gp_registers();
299 registers.rip += hypercall.instruction_length as u64;
300
301 if let Some(result) = self.bridge.dispatch(vmi) {
302 match result {
303 Ok(response) => {
304 response.write_to(&mut registers);
305
306 if let Some(output) = response.into_output() {
307 self.process_bridge(vmi, output)?;
308 }
309 }
310 Err(packet) => tracing::error!(
311 request = packet.request(),
312 method = packet.method(),
313 "empty bridge response"
314 ),
315 }
316 }
317
318 Ok(VmiEventResponse::default().with_registers(registers))
319 }
320
321 fn process_bridge(
323 &mut self,
324 vmi: &VmiContext<WindowsOs<Driver>>,
325 output: BridgeOutput,
326 ) -> Result<(), VmiError> {
327 match output {
328 BridgeOutput::DeployExecuting => {
329 if self.state.creator_thread.is_none() && self.state.target_process.is_none() {
330 let current_thread = vmi.os().current_thread()?;
331 self.state.creator_thread = Some(current_thread.object()?);
332 }
333 else {
334 tracing::warn!(
335 creator_thread = ?self.state.creator_thread,
336 target_process = ?self.state.target_process,
337 "ignoring repeated deploy execute notification"
338 );
339 }
340 }
341 BridgeOutput::DeployWaiting | BridgeOutput::DeployFinished(_) => {}
342 }
343
344 Ok(())
345 }
346
347 #[tracing::instrument(
348 name = "monitor",
349 skip_all,
350 fields(
351 pid = vmi::trace::current_process_id(vmi),
352 tid = vmi::trace::current_thread_id(vmi),
353 )
354 )]
355 fn dispatch(
356 &mut self,
357 vmi: &VmiContext<WindowsOs<Driver>>,
358 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
359 let result = match vmi.event().reason() {
360 EventReason::MemoryAccess(_) => self.memory_access(vmi),
361 EventReason::Interrupt(_) => self.interrupt(vmi),
362 EventReason::Singlestep(_) => self.singlestep(vmi),
363 EventReason::Hypercall(_) => self.hypercall(vmi),
364 reason => panic!("unhandled deploy monitor event: {reason:?}"),
365 };
366
367 if let Err(VmiError::Translation(page_fault)) = result {
368 tracing::warn!(?page_fault, "page fault, injecting");
369
370 vmi.inject_interrupt(
371 vmi.event().vcpu_id(),
372 Interrupt::page_fault(page_fault.va, 0),
373 )?;
374
375 return Ok(VmiEventResponse::default());
376 }
377
378 result
379 }
380}
381
382impl<Driver> VmiHandler<WindowsOs<Driver>> for Monitor<Driver>
383where
384 Driver: VmiFullDriver<Architecture = Amd64>,
385{
386 type Output = Result<Option<ProcessId>, DeployStatus>;
387
388 fn handle_event(&mut self, vmi: VmiContext<WindowsOs<Driver>>) -> VmiEventResponse<Amd64> {
389 vmi.flush_v2p_cache();
390
391 match self.dispatch(&vmi) {
392 Ok(response) => response,
393 Err(err) => panic!("deploy monitor dispatch failed: {err:?}"),
394 }
395 }
396
397 fn cleanup(&mut self, vmi: &VmiSession<WindowsOs<Driver>>) {
398 if let Err(err) = vmi.switch_to_view(vmi.default_view()) {
399 tracing::error!(%err, "failed to switch to the default view");
400 }
401
402 if let Err(err) = vmi.monitor_disable(EventMonitor::Singlestep) {
403 tracing::error!(%err, "failed to disable singlestep monitoring");
404 }
405
406 if let Err(err) = vmi.monitor_disable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))
407 {
408 tracing::error!(%err, "failed to disable breakpoint monitoring");
409 }
410
411 if let Err(err) = vmi.monitor_disable(EventMonitor::Hypercall {
412 allow_userspace: true,
413 }) {
414 tracing::error!(%err, "failed to disable hypercall monitoring");
415 }
416
417 match self.bpm.remove_by_view(vmi, self.view) {
418 Ok(true) => {}
419 Ok(false) => tracing::warn!("no deploy monitor breakpoints to remove"),
420 Err(err) => tracing::error!(%err, "failed to remove deploy monitor breakpoints"),
421 }
422 self.ptm.unmonitor_all(vmi);
423
424 if let Err(err) = vmi.destroy_view(self.view) {
425 tracing::error!(%err, "failed to destroy deploy monitor view");
426 }
427 }
428
429 fn poll(&mut self) -> Option<Self::Output> {
430 self.state.output.or_else(|| {
431 self.terminate_flag
432 .load(Ordering::Relaxed)
433 .then_some(Ok(None))
434 })
435 }
436}