1use isr_core::Profile;
2use isr_macros::{Field, offsets};
3use vmi_arch_amd64::{Amd64, ControlRegister, EventMonitor, EventReason, Interrupt, Registers};
4use vmi_core::{
5 Architecture as _, Hex, MemoryAccess, Registers as _, Va, View, VmiContext, VmiCore, VmiDriver,
6 VmiError, VmiEventResponse, VmiHandler, VmiVa as _,
7 os::{ProcessId, VmiOsProcess, VmiOsThread},
8};
9use vmi_os_windows::{WindowsOs, WindowsOsExt as _};
10
11use super::{
12 super::{
13 CallBuilder, InjectorHandler, InjectorResultCode, Recipe, RecipeExecutor,
14 arch::ArchAdapter as _,
15 },
16 OsAdapter,
17};
18use crate::bridge::{BridgeHandler, BridgePacket};
19const INVALID_VIEW: View = View(0xffff);
21offsets! {
25 #[derive(Debug)]
26 pub struct Offsets {
27 struct _KTRAP_FRAME {
28 Rip: Field,
29 Rsp: Field,
30 }
31
32 struct _KTHREAD {
33 TrapFrame: Field,
34 }
35 }
36}
37
38impl<Driver> OsAdapter<Driver> for WindowsOs<Driver>
39where
40 Driver: VmiDriver<Architecture = Amd64>,
41{
42 type Offsets = Offsets;
43
44 fn prepare_function_call(
45 &self,
46 vmi: &VmiCore<Driver>,
47 registers: &mut Registers,
48 builder: CallBuilder,
49 ) -> Result<(), VmiError> {
50 tracing::trace!(
51 rsp = %Hex(registers.rsp),
52 rip = %Hex(registers.rip),
53 "preparing function call"
54 );
55
56 let arguments = Amd64::push_arguments(vmi, registers, &builder.arguments)?;
57
58 tracing::trace!(
59 rsp = %Hex(registers.rsp),
60 "pushed arguments"
61 );
62
63 let mut addr = registers.rsp;
64
65 let nb_args = arguments.len();
66
67 let effective_nb_args = nb_args.max(4) as u64;
81 if (addr - effective_nb_args * 0x8 - 0x8) & 0xf != 8 {
82 addr -= 0x8;
83
84 tracing::trace!(
85 addr = %Hex(addr),
86 "aligning stack"
87 );
88 }
89
90 for index in (4..nb_args).rev() {
98 addr -= 0x8;
99 vmi.write_u64((addr.into(), registers.cr3.into()), arguments[index])?;
100
101 tracing::trace!(
102 index,
103 value = %Hex(arguments[index]),
104 addr = %Hex(addr),
105 "argument (stack)"
106 );
107 }
108
109 if nb_args > 3 {
111 registers.r9 = arguments[3];
112
113 tracing::trace!(
114 index = 3,
115 value = %Hex(arguments[3]),
116 "argument"
117 );
118 }
119
120 if nb_args > 2 {
121 registers.r8 = arguments[2];
122
123 tracing::trace!(
124 index = 2,
125 value = %Hex(arguments[2]),
126 "argument"
127 );
128 }
129
130 if nb_args > 1 {
131 registers.rdx = arguments[1];
132
133 tracing::trace!(
134 index = 1,
135 value = %Hex(arguments[1]),
136 "argument"
137 );
138 }
139
140 if nb_args > 0 {
141 registers.rcx = arguments[0];
142
143 tracing::trace!(
144 index = 0,
145 value = %Hex(arguments[0]),
146 "argument"
147 );
148 }
149
150 addr -= 0x20;
152
153 addr -= 0x8;
155 vmi.write_u64((addr.into(), registers.cr3.into()), registers.rip)?;
156
157 registers.rsp = addr;
159
160 registers.rip = builder.function_address.into();
162
163 tracing::trace!(
164 rsp = %Hex(registers.rsp),
165 rip = %Hex(registers.rip),
166 "finished preparing function call"
167 );
168
169 Ok(())
170 }
171}
172
173impl<Driver, T> InjectorHandler<Driver, WindowsOs<Driver>, T, ()>
174where
175 Driver: VmiDriver<Architecture = Amd64>,
176{
177 pub fn new(
179 vmi: &VmiCore<Driver>,
180 profile: &Profile,
181 pid: ProcessId,
182 recipe: Recipe<Driver, WindowsOs<Driver>, T>,
183 ) -> Result<Self, VmiError> {
184 Self::with_bridge(vmi, profile, pid, (), recipe)
185 }
186}
187
188#[expect(non_snake_case)]
189impl<Driver, T, Bridge> InjectorHandler<Driver, WindowsOs<Driver>, T, Bridge>
190where
191 Driver: VmiDriver<Architecture = Amd64>,
192 Bridge: BridgeHandler<Driver, WindowsOs<Driver>, InjectorResultCode>,
193{
194 pub fn with_bridge(
196 vmi: &VmiCore<Driver>,
197 profile: &Profile,
198 pid: ProcessId,
199 bridge: Bridge,
200 recipe: Recipe<Driver, WindowsOs<Driver>, T>,
201 ) -> Result<Self, VmiError> {
202 let offsets = Offsets::new(profile)?;
203
204 let view = vmi.create_view(MemoryAccess::RWX)?;
205 vmi.switch_to_view(view)?;
206 vmi.monitor_enable(EventMonitor::Register(ControlRegister::Cr3))?;
207 vmi.monitor_enable(EventMonitor::Singlestep)?;
208
209 if !Bridge::EMPTY {
210 vmi.monitor_enable(EventMonitor::CpuId)?;
211 }
212
213 Ok(Self {
214 pid,
215 tid: None,
216 hijacked: false,
217 ip_va: None,
218 ip_pa: None,
219 offsets,
220 recipe: RecipeExecutor::new(recipe),
221 view,
222 bridge,
223 finished: None,
224 })
225 }
226
227 #[tracing::instrument(
228 name = "injector",
229 skip_all,
230 err,
231 fields(
232 vcpu = %vmi.event().vcpu_id(),
233 rip = %Va(vmi.registers().rip),
234 )
235 )]
236 fn dispatch(
237 &mut self,
238 vmi: &VmiContext<Driver, WindowsOs<Driver>>,
239 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
240 match vmi.event().reason() {
241 EventReason::MemoryAccess(_) => self.on_memory_access(vmi),
242 EventReason::WriteControlRegister(_) => {
243 let _ = self.on_write_cr(vmi);
244 Ok(VmiEventResponse::default())
245 }
246 EventReason::CpuId(_) => self.on_cpuid(vmi),
247 _ => panic!("Unhandled event: {:?}", vmi.event().reason()),
248 }
249 }
250
251 #[tracing::instrument(name = "cpuid", skip_all, err)]
252 fn on_cpuid(
253 &mut self,
254 vmi: &VmiContext<Driver, WindowsOs<Driver>>,
255 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
256 let cpuid = vmi.event().reason().as_cpuid();
257
258 let mut registers = vmi.registers().gp_registers();
259 registers.rip += cpuid.instruction_length as u64;
260
261 tracing::trace!(
262 rip = %Va::from(registers.rip),
263 leaf = %Hex(cpuid.leaf),
264 subleaf = %Hex(cpuid.subleaf),
265 );
266
267 if cpuid.leaf != Bridge::MAGIC {
268 return Ok(VmiEventResponse::set_registers(registers));
269 }
270
271 let magic = cpuid.leaf;
272 let request = (cpuid.subleaf & 0xFFFF) as u16;
273 let method = (cpuid.subleaf >> 16) as u16;
274
275 let packet = BridgePacket::new(magic, request, method)
276 .with_value1(registers.r8)
277 .with_value2(registers.r9)
278 .with_value3(registers.r10)
279 .with_value4(registers.r11);
280
281 let result = match self.bridge.dispatch(vmi, packet) {
282 Some(result) => result,
283 None => {
284 tracing::error!(request, method, "Empty bridge response");
285
286 self.finished = Some(Err(packet));
287 vmi.monitor_disable(EventMonitor::CpuId)?;
288
289 return Ok(VmiEventResponse::set_registers(registers));
290 }
291 };
292
293 if let Some(value1) = result.value1() {
294 registers.rax = value1;
295 }
296 if let Some(value2) = result.value2() {
297 registers.rbx = value2;
298 }
299
300 if let Some(result) = result.into_result() {
301 self.finished = Some(Ok(result));
302 vmi.monitor_disable(EventMonitor::CpuId)?;
303 };
304
305 if let Some(verify) = Bridge::VERIFY_VALUE4 {
306 registers.rdx = verify;
307 }
308 if let Some(verify) = Bridge::VERIFY_VALUE3 {
309 registers.rcx = verify;
310 }
311 if let Some(verify) = Bridge::VERIFY_VALUE2 {
312 registers.rbx = verify;
313 }
314 if let Some(verify) = Bridge::VERIFY_VALUE1 {
315 registers.rax = verify;
316 }
317
318 Ok(VmiEventResponse::set_registers(registers))
319 }
320
321 #[tracing::instrument(name = "write_cr", skip_all, err)]
322 fn on_write_cr(
323 &mut self,
324 vmi: &VmiContext<Driver, WindowsOs<Driver>>,
325 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
326 if self.hijacked {
332 return Ok(VmiEventResponse::default());
333 }
334
335 let current_pid = vmi.os().current_process()?.id()?;
340 if current_pid != self.pid {
341 return Ok(VmiEventResponse::default());
342 }
343
344 let KTHREAD_TrapFrame = self.offsets._KTHREAD.TrapFrame.offset();
351 let KTRAP_FRAME_Rsp = self.offsets._KTRAP_FRAME.Rsp.offset();
352 let KTRAP_FRAME_Rip = self.offsets._KTRAP_FRAME.Rip.offset();
353
354 let current_thread = vmi.os().current_thread()?;
355 let current_tid = current_thread.id()?;
356 let current_thread = current_thread.va();
357
358 let trap_frame = vmi.read_va(current_thread + KTHREAD_TrapFrame)?;
359 let sp_va = vmi.read_va(trap_frame + KTRAP_FRAME_Rsp)?;
360 let ip_va = vmi.read_va(trap_frame + KTRAP_FRAME_Rip)?;
361
362 if !vmi.os().is_valid_user_address(ip_va)? {
368 tracing::trace!(%ip_va, "skipping invalid pc");
369
370 return Ok(VmiEventResponse::default());
371 }
372
373 let ip_pa = match vmi.translate_address(ip_va) {
378 Ok(ip_pa) => {
379 tracing::trace!(
380 %current_tid,
381 %sp_va,
382 %ip_va,
383 %ip_pa,
384 "trying to hijack thread"
385 );
386
387 ip_pa
388 }
389
390 Err(err) => {
391 tracing::trace!(
392 %current_tid,
393 %sp_va,
394 %ip_va,
395 ip_pa = "error",
396 "trying to hijack thread"
397 );
398
399 return Err(err);
400 }
401 };
402
403 if let Some(previous_ip_pa) = self.ip_pa {
409 let previous_ip_gfn = Driver::Architecture::gfn_from_pa(previous_ip_pa);
410 vmi.set_memory_access(previous_ip_gfn, self.view, MemoryAccess::RWX)?;
411 }
412
413 let ip_gfn = Driver::Architecture::gfn_from_pa(ip_pa);
421 vmi.set_memory_access(ip_gfn, self.view, MemoryAccess::RW)?;
422
423 self.tid = Some(current_tid);
429 self.ip_va = Some(ip_va);
430 self.ip_pa = Some(ip_pa);
431
432 Ok(VmiEventResponse::default())
433 }
434
435 #[tracing::instrument(name = "memory_access", skip_all, err)]
436 fn on_memory_access(
437 &mut self,
438 vmi: &VmiContext<Driver, WindowsOs<Driver>>,
439 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
440 if vmi.event().view() != Some(self.view) {
445 tracing::trace!(
446 view = %self.view,
447 current_view = %vmi.event().view().unwrap_or(INVALID_VIEW),
448 "not the right view"
449 );
450
451 return Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()));
452 }
453
454 let current_pid = vmi.os().current_process()?.id()?;
462 if current_pid != self.pid {
463 return Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()));
470 }
471
472 let current_tid = vmi.os().current_thread()?.id()?;
477 if Some(current_tid) != self.tid {
478 return Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()));
485 }
486
487 let registers = vmi.registers();
492 let ip = Va(registers.rip);
493 if Some(ip) != self.ip_va {
494 return Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()));
501 }
502
503 if !self.hijacked {
508 tracing::debug!(%current_tid, "thread hijacked");
509 self.hijacked = true;
510
511 vmi.monitor_disable(EventMonitor::Register(ControlRegister::Cr3))?;
512 }
513
514 let new_registers = match self.recipe.execute(vmi)? {
519 Some(registers) => registers,
520 None => {
521 return Ok(
522 VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view())
523 );
524 }
525 };
526
527 if self.recipe.done() {
528 let memory_access = vmi.event().reason().as_memory_access();
535 let gfn = Driver::Architecture::gfn_from_pa(memory_access.pa);
536 vmi.set_memory_access(gfn, self.view, MemoryAccess::RWX)?;
537 vmi.monitor_disable(EventMonitor::Singlestep)?;
538
539 vmi.switch_to_view(vmi.default_view())?;
540 vmi.destroy_view(self.view)?;
541
542 if Bridge::EMPTY {
544 self.finished = Some(Ok(0));
545 }
546 }
547
548 Ok(VmiEventResponse::set_registers(
549 new_registers.gp_registers(),
550 ))
551 }
552}
553
554impl<Driver, T, Bridge> VmiHandler<Driver, WindowsOs<Driver>>
555 for InjectorHandler<Driver, WindowsOs<Driver>, T, Bridge>
556where
557 Driver: VmiDriver<Architecture = Amd64>,
558 Bridge: BridgeHandler<Driver, WindowsOs<Driver>, InjectorResultCode>,
559{
560 type Output = Result<InjectorResultCode, BridgePacket>;
561
562 fn handle_event(
563 &mut self,
564 vmi: VmiContext<Driver, WindowsOs<Driver>>,
565 ) -> VmiEventResponse<Amd64> {
566 vmi.flush_v2p_cache();
567
568 match self.dispatch(&vmi) {
569 Ok(response) => response,
570 Err(VmiError::Translation(pfs)) => {
571 let pf = pfs[0];
572
573 tracing::debug!(?pf, "injecting page fault");
574 let _ =
575 vmi.inject_interrupt(vmi.event().vcpu_id(), Interrupt::page_fault(pf.va, 0));
576
577 VmiEventResponse::default()
578 }
579 Err(err) => panic!("Unhandled error: {err:?}"),
580 }
581 }
582
583 fn check_completion(&self) -> Option<Self::Output> {
584 self.finished
585 }
586}