vmi_utils/injector/os/
windows.rs

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};
19// const INVALID_VA: Va = Va(0xffff_ffff_ffff_ffff);
20const INVALID_VIEW: View = View(0xffff);
21// const INVALID_TID: ThreadId = ThreadId(0xffff_ffff);
22// const INVALID_PID: ProcessId = ProcessId(0xffff_ffff);
23
24offsets! {
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        // According to Microsoft Doc "Building C/C++ Programs":
68        // > The stack will always be maintained 16-byte aligned, except within the
69        // > prolog
70        // > (for example, after the return address is pushed), and except where
71        // > indicated
72        // > in Function Types for a certain class of frame functions.
73        //
74        // Add padding to be aligned to "16+8" boundary.
75        //
76        // https://www.gamasutra.com/view/news/178446/Indepth_Windows_x64_ABI_Stack_frames.php
77        //
78        // This padding on the stack only exists if the maximum number of parameters
79        // passed to functions is greater than 4 and is an odd number.
80        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        // http://www.codemachine.com/presentations/GES2010.TRoy.Slides.pdf
91        //
92        // First 4 parameters to functions are always passed in registers
93        // P1=rcx, P2=rdx, P3=r8, P4=r9
94        // 5th parameter onwards (if any) passed via the stack
95
96        // write parameters (5th onwards) into guest's stack
97        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        // write the first 4 parameters into registers
110        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        // allocate 0x20 "homing space"
151        addr -= 0x20;
152
153        // save the return address
154        addr -= 0x8;
155        vmi.write_u64((addr.into(), registers.cr3.into()), registers.rip)?;
156
157        // grow the stack
158        registers.rsp = addr;
159
160        // set the new instruction pointer
161        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    /// Creates a new injector handler.
178    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    /// Creates a new injector handler.
195    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        //
327        // Early exit if the thread has already been hijacked.
328        // (Besides, in such case, this CR3 monitoring is being disabled anyway.)
329        //
330
331        if self.hijacked {
332            return Ok(VmiEventResponse::default());
333        }
334
335        //
336        // Early exit if the current process is not the target process.
337        //
338
339        let current_pid = vmi.os().current_process()?.id()?;
340        if current_pid != self.pid {
341            return Ok(VmiEventResponse::default());
342        }
343
344        //
345        // Figure out if the current thread is viable for hijacking.
346        // First, fetch the current TID and the next instruction from
347        // trap frame of the current thread.
348        //
349
350        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        //
363        // Verify that the next instruction of this thread is in a user-mode
364        // address space.
365        //
366
367        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        //
374        // Translate the instruction pointer to a physical address.
375        //
376
377        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        //
404        // If we've tried to hijack a thread before, we need to restore the
405        // previous memory access permissions.
406        //
407
408        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        //
414        // Set the memory access permissions for the next user-mode instruction.
415        // This will unset the eXecute permission for the page containing the
416        // next user-mode instruction. This is necessary to trigger a `MemoryAccess`
417        // event when the thread resumes execution.
418        //
419
420        let ip_gfn = Driver::Architecture::gfn_from_pa(ip_pa);
421        vmi.set_memory_access(ip_gfn, self.view, MemoryAccess::RW)?;
422
423        //
424        // Mark down the current TID and the VA/PA of the next user-mode instruction.
425        // The next `MemoryAccess` handler will try to hijack the thread.
426        //
427
428        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        //
441        // Early exit if the memory view is not the target view.
442        //
443
444        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        //
455        // Early exit if the current process is not the target process.
456        // Note that some physical memory pages (especially those containing
457        // mapped files, such as DLLs) are shared among multiple processes.
458        // Therefore this event might have been triggered by a different process.
459        //
460
461        let current_pid = vmi.os().current_process()?.id()?;
462        if current_pid != self.pid {
463            // Too noisy...
464            // tracing::trace!(
465            //     pid = %self.pid,
466            //     %current_pid,
467            //     "not the right process"
468            // );
469            return Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()));
470        }
471
472        //
473        // Early exit if the current thread is not the target thread.
474        //
475
476        let current_tid = vmi.os().current_thread()?.id()?;
477        if Some(current_tid) != self.tid {
478            // Too noisy...
479            // tracing::trace!(
480            //     tid = %self.tid.unwrap_or(INVALID_TID),
481            //     %current_tid,
482            //     "not the right thread"
483            // );
484            return Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()));
485        }
486
487        //
488        // Early exit if this instruction pointer is not the one we're looking for.
489        //
490
491        let registers = vmi.registers();
492        let ip = Va(registers.rip);
493        if Some(ip) != self.ip_va {
494            //tracing::trace!(
495            //    ip = %self.ip_va.unwrap_or(INVALID_VA),
496            //    current_ip = %ip,
497            //    "not the right instruction pointer"
498            //);
499
500            return Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()));
501        }
502
503        //
504        // Hijack the thread, save the current registers, and disable CR3 monitoring.
505        //
506
507        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        //
515        // Execute the next step in the recipe.
516        //
517
518        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            //
529            // If the recipe is finished, restore the previous memory access permissions,
530            // switch back to the default view, disable single-stepping, and restore back
531            // the original registers.
532            //
533
534            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 the bridge was not enabled, we're done.
543            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}