windows_recipe_messagebox/
windows-recipe-messagebox.rs

1//! # [`recipe!`] example
2//!
3//! This example demonstrates how to write a simple recipe.
4//!
5//! The recipe is injected into the `explorer.exe` process and shows
6//! a message box.
7//!
8//! # Possible log output
9//!
10//! ```text
11//! DEBUG domain_id=XenDomainId(102)
12//! DEBUG found MZ base_address=0xfffff80002861000
13//!  INFO profile already exists profile_path="cache/windows/ntkrnlmp.pdb/3844dbb920174967be7aa4a2c20430fa2/profile.json"
14//!  INFO Creating VMI session
15//!  INFO found explorer.exe pid=1248 object=0xfffffa80030e9060
16//! DEBUG injector{vcpu=2 rip=0x0000000077c618ca}:memory_access: thread hijacked current_tid=1488
17//! DEBUG injector{vcpu=2 rip=0x0000000077c618ca}:memory_access: recipe step index=0
18//! DEBUG injector{vcpu=1 rip=0x0000000077c618ca}:memory_access: recipe finished result=0x0000000000000001
19//! ```
20
21mod common;
22
23use vmi::{
24    VcpuId, VmiDriver,
25    arch::amd64::Amd64,
26    os::{VmiOsProcess as _, windows::WindowsOs},
27    utils::injector::{InjectorHandler, Recipe, recipe},
28};
29
30struct MessageBox {
31    caption: String,
32    text: String,
33}
34
35impl MessageBox {
36    pub fn new(caption: impl AsRef<str>, text: impl AsRef<str>) -> Self {
37        Self {
38            caption: caption.as_ref().to_string(),
39            text: text.as_ref().to_string(),
40        }
41    }
42}
43
44#[rustfmt::skip]
45fn recipe_factory<Driver>(data: MessageBox) -> Recipe<Driver, WindowsOs<Driver>, MessageBox>
46where
47    Driver: VmiDriver<Architecture = Amd64>,
48{
49    recipe![
50        Recipe::<_, WindowsOs<Driver>, _>::new(data),
51        {
52            inject! {
53                user32!MessageBoxA(
54                    0,                          // hWnd
55                    data![text],                // lpText
56                    data![caption],             // lpCaption
57                    0                           // uType
58                )
59            }
60        }
61    ]
62}
63
64fn main() -> Result<(), Box<dyn std::error::Error>> {
65    let (session, profile) = common::create_vmi_session()?;
66
67    let explorer_pid = {
68        // This block is used to drop the pause guard after the PID is found.
69        // If the `session.handle()` would be called with the VM paused, no
70        // events would be triggered.
71        let _pause_guard = session.pause_guard()?;
72
73        let registers = session.registers(VcpuId(0))?;
74        let vmi = session.with_registers(&registers);
75
76        let explorer = match common::find_process(&vmi, "explorer.exe")? {
77            Some(explorer) => explorer,
78            None => {
79                tracing::error!("explorer.exe not found");
80                return Ok(());
81            }
82        };
83
84        tracing::info!(
85            pid = %explorer.id()?,
86            object = %explorer.object()?,
87            "found explorer.exe"
88        );
89
90        explorer.id()?
91    };
92
93    session.handle(|session| {
94        InjectorHandler::new(
95            session,
96            &profile,
97            explorer_pid,
98            recipe_factory(MessageBox::new(
99                "Hello, World!",
100                "This is a message box from the VMI!",
101            )),
102        )
103    })?;
104
105    Ok(())
106}