windows_recipe_writefile/
windows-recipe-writefile.rs

1//! # [`recipe!`] example
2//!
3//! This example demonstrates how to write a recipe with multiple steps.
4//!
5//! The recipe is injected into the `explorer.exe` process and writes
6//! a file to the guest.
7//!
8//! # Possible log output
9//!
10//! ```text
11//! DEBUG found MZ base_address=0xfffff80002861000
12//!  INFO profile already exists profile_path="cache/windows/ntkrnlmp.pdb/3844dbb920174967be7aa4a2c20430fa2/profile.json"
13//!  INFO Creating VMI session
14//!  INFO found explorer.exe pid=1248 object=0xfffffa80030e9060
15//! DEBUG injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: thread hijacked current_tid=1932
16//! DEBUG injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: recipe step index=0
17//!  INFO injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: step 1: kernel32!CreateFileA() target_path="C:\\Users\\John\\Desktop\\test.txt"
18//! DEBUG injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: recipe step index=1
19//!  INFO injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: step 2: kernel32!WriteFile() handle=0x0000000000000000
20//! DEBUG injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: recipe step index=2
21//!  INFO injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: step 3: kernel32!WriteFile() number_of_bytes_written=13
22//!  INFO injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: step 3: kernel32!CloseHandle() handle=0x0000000000000e08
23//! DEBUG injector{vcpu=0 rip=0x0000000077c62c1a}:memory_access: recipe finished result=0x0000000000000001
24//! ```
25
26mod common;
27
28use vmi::{
29    Hex, Va, VcpuId, VmiDriver,
30    arch::amd64::Amd64,
31    os::{VmiOsProcess as _, windows::WindowsOs},
32    utils::injector::{InjectorHandler, Recipe, RecipeControlFlow, recipe},
33};
34
35#[derive(Debug, Default)]
36struct GuestFile {
37    /// Target path in the guest to write the file.
38    target_path: String,
39
40    /// Content to write to the file.
41    content: Vec<u8>,
42
43    /// Handle to the file.
44    /// Assigned in 2nd step.
45    handle: u64,
46
47    /// The number of bytes written to the file.
48    /// Assigned in 2nd step and used in 3rd step.
49    bytes_written_ptr: Va,
50}
51
52impl GuestFile {
53    pub fn new(target_path: impl AsRef<str>, content: impl AsRef<[u8]>) -> Self {
54        Self {
55            target_path: target_path.as_ref().to_string(),
56            content: content.as_ref().to_vec(),
57
58            // Mutable fields.
59            handle: 0,
60            bytes_written_ptr: Va::default(),
61        }
62    }
63}
64
65/// Create a recipe to write a file to the guest.
66///
67/// # Equivalent C pseudo-code
68///
69/// ```c
70/// const char* target_path = "...\\test.txt";
71/// const char content[] = "...";
72///
73/// HANDLE handle = CreateFileA(target_path,            // lpFileName
74///                             GENERIC_WRITE,          // dwDesiredAccess
75///                             0,                      // dwShareMode
76///                             NULL,                   // lpSecurityAttributes
77///                             CREATE_ALWAYS,          // dwCreationDisposition
78///                             FILE_ATTRIBUTE_NORMAL,  // dwFlagsAndAttributes
79///                             NULL);                  // hTemplateFile
80///
81/// if (handle == INVALID_HANDLE_VALUE) {
82///     printf("kernel32!CreateFileA() failed\n");
83///     return;
84/// }
85///
86/// DWORD bytes_written;
87/// if (!WriteFile(handle, content, sizeof(content), &bytes_written, 0)) {
88///     printf("kernel32!WriteFile() failed\n");
89/// }
90///
91/// CloseHandle(handle);
92/// ```
93fn recipe_factory<Driver>(data: GuestFile) -> Recipe<Driver, WindowsOs<Driver>, GuestFile>
94where
95    Driver: VmiDriver<Architecture = Amd64>,
96{
97    recipe![
98        Recipe::<_, WindowsOs<Driver>, _>::new(data),
99        //
100        // Step 1:
101        // - Create a file
102        //
103        {
104            tracing::info!(
105                target_path = data![target_path],
106                "step 1: kernel32!CreateFileA()"
107            );
108
109            const GENERIC_WRITE: u64 = 0x40000000;
110            const CREATE_ALWAYS: u64 = 2;
111            const FILE_ATTRIBUTE_NORMAL: u64 = 0x80;
112
113            inject! {
114                kernel32!CreateFileA(
115                    &data![target_path],        // lpFileName
116                    GENERIC_WRITE,              // dwDesiredAccess
117                    0,                          // dwShareMode
118                    0,                          // lpSecurityAttributes
119                    CREATE_ALWAYS,              // dwCreationDisposition
120                    FILE_ATTRIBUTE_NORMAL,      // dwFlagsAndAttributes
121                    0                           // hTemplateFile
122                )
123            }
124        },
125        //
126        // Step 2:
127        // - Verify the file handle
128        // - Write the content to the file
129        //
130        {
131            let return_value = registers!().rax;
132
133            const INVALID_HANDLE_VALUE: u64 = 0xffff_ffff_ffff_ffff;
134
135            if return_value == INVALID_HANDLE_VALUE {
136                tracing::error!(
137                    return_value = %Hex(return_value),
138                    "step 2: kernel32!CreateFileA() failed"
139                );
140
141                return Ok(RecipeControlFlow::Break);
142            }
143
144            tracing::info!(
145                handle = %Hex(data![handle]),
146                "step 2: kernel32!WriteFile()"
147            );
148
149            // Save the handle.
150            data![handle] = return_value;
151
152            // Allocate a value on the stack to store the output parameter.
153            data![bytes_written_ptr] = copy_to_stack!(0u64)?;
154
155            inject! {
156                kernel32!WriteFile(
157                    data![handle],              // hFile
158                    data![content],             // lpBuffer
159                    data![content].len(),       // nNumberOfBytesToWrite
160                    data![bytes_written_ptr],   // lpNumberOfBytesWritten
161                    0                           // lpOverlapped
162                )
163            }
164        },
165        //
166        // Step 3:
167        // - Verify that the `WriteFile()` call succeeded
168        // - Close the file handle
169        //
170        {
171            let return_value = registers!().rax;
172
173            // Check if the `WriteFile()` call failed.
174            if return_value == 0 {
175                tracing::error!(
176                    return_value = %Hex(return_value),
177                    "step 3: kernel32!WriteFile() failed"
178                );
179
180                // Don't exit, we want to close the handle.
181                // return Ok(RecipeControlFlow::Break);
182            }
183
184            // Read the number of bytes written.
185            let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
186            tracing::info!(number_of_bytes_written, "step 3: kernel32!WriteFile()");
187
188            tracing::info!(
189                handle = %Hex(data![handle]),
190                "step 3: kernel32!CloseHandle()"
191            );
192
193            inject! {
194                kernel32!CloseHandle(
195                    data![handle]               // hObject
196                )
197            }
198        },
199    ]
200}
201
202fn main() -> Result<(), Box<dyn std::error::Error>> {
203    let (session, profile) = common::create_vmi_session()?;
204
205    let explorer_pid = {
206        // This block is used to drop the pause guard after the PID is found.
207        // If the `session.handle()` would be called with the VM paused, no
208        // events would be triggered.
209        let _pause_guard = session.pause_guard()?;
210
211        let registers = session.registers(VcpuId(0))?;
212        let vmi = session.with_registers(&registers);
213
214        let explorer = match common::find_process(&vmi, "explorer.exe")? {
215            Some(explorer) => explorer,
216            None => {
217                tracing::error!("explorer.exe not found");
218                return Ok(());
219            }
220        };
221
222        tracing::info!(
223            pid = %explorer.id()?,
224            object = %explorer.object()?,
225            "found explorer.exe"
226        );
227
228        explorer.id()?
229    };
230
231    session.handle(|session| {
232        InjectorHandler::new(
233            session,
234            &profile,
235            explorer_pid,
236            recipe_factory(GuestFile::new(
237                "C:\\Users\\John\\Desktop\\test.txt",
238                "Hello, World!".as_bytes(),
239            )),
240        )
241    })?;
242
243    Ok(())
244}