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(®isters);
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}