wraith/manipulation/spoof/
stack.rs1use crate::error::{Result, WraithError};
7use crate::navigation::ModuleQuery;
8use crate::structures::Peb;
9
10#[derive(Debug, Clone)]
12pub struct FakeFrame {
13 pub return_address: usize,
15 pub saved_rbp: usize,
17 pub extra_values: Vec<usize>,
19 pub description: &'static str,
21}
22
23impl FakeFrame {
24 pub const fn simple(return_address: usize, saved_rbp: usize, description: &'static str) -> Self {
26 Self {
27 return_address,
28 saved_rbp,
29 extra_values: Vec::new(),
30 description,
31 }
32 }
33
34 pub fn with_extra(
36 return_address: usize,
37 saved_rbp: usize,
38 extra: Vec<usize>,
39 description: &'static str,
40 ) -> Self {
41 Self {
42 return_address,
43 saved_rbp,
44 extra_values: extra,
45 description,
46 }
47 }
48
49 pub fn size(&self) -> usize {
51 (2 + self.extra_values.len()) * core::mem::size_of::<usize>()
53 }
54}
55
56#[derive(Debug, Clone)]
58pub struct FrameTemplate {
59 pub name: &'static str,
61 pub module: &'static str,
63 pub function: &'static str,
65 pub offset: usize,
67 pub extra_slots: usize,
69 pub description: &'static str,
71}
72
73impl FrameTemplate {
74 pub fn resolve(&self) -> Result<FakeFrame> {
76 let peb = Peb::current()?;
77 let query = ModuleQuery::new(&peb);
78 let module = query.find_by_name(self.module)?;
79 let func_addr = module.get_export(self.function)?;
80 let return_addr = func_addr + self.offset;
81
82 Ok(FakeFrame {
83 return_address: return_addr,
84 saved_rbp: 0, extra_values: vec![0; self.extra_slots],
86 description: self.description,
87 })
88 }
89}
90
91pub static COMMON_FRAME_TEMPLATES: &[FrameTemplate] = &[
93 FrameTemplate {
95 name: "CreateFileW",
96 module: "kernel32.dll",
97 function: "CreateFileW",
98 offset: 0x50, extra_slots: 4,
100 description: "kernel32!CreateFileW frame",
101 },
102 FrameTemplate {
104 name: "ReadFile",
105 module: "kernel32.dll",
106 function: "ReadFile",
107 offset: 0x40,
108 extra_slots: 4,
109 description: "kernel32!ReadFile frame",
110 },
111 FrameTemplate {
113 name: "WriteFile",
114 module: "kernel32.dll",
115 function: "WriteFile",
116 offset: 0x40,
117 extra_slots: 4,
118 description: "kernel32!WriteFile frame",
119 },
120 FrameTemplate {
122 name: "VirtualAlloc",
123 module: "kernel32.dll",
124 function: "VirtualAlloc",
125 offset: 0x30,
126 extra_slots: 2,
127 description: "kernel32!VirtualAlloc frame",
128 },
129 FrameTemplate {
131 name: "VirtualProtect",
132 module: "kernel32.dll",
133 function: "VirtualProtect",
134 offset: 0x30,
135 extra_slots: 2,
136 description: "kernel32!VirtualProtect frame",
137 },
138 FrameTemplate {
140 name: "OpenProcess",
141 module: "kernel32.dll",
142 function: "OpenProcess",
143 offset: 0x40,
144 extra_slots: 3,
145 description: "kernel32!OpenProcess frame",
146 },
147 FrameTemplate {
149 name: "BaseThreadInitThunk",
150 module: "kernel32.dll",
151 function: "BaseThreadInitThunk",
152 offset: 0x14,
153 extra_slots: 1,
154 description: "kernel32!BaseThreadInitThunk frame",
155 },
156 FrameTemplate {
158 name: "RtlUserThreadStart",
159 module: "ntdll.dll",
160 function: "RtlUserThreadStart",
161 offset: 0x21,
162 extra_slots: 1,
163 description: "ntdll!RtlUserThreadStart frame",
164 },
165];
166
167#[derive(Debug)]
169pub struct SyntheticStack {
170 frames: Vec<FakeFrame>,
172 total_size: usize,
174 data: Vec<usize>,
176}
177
178impl SyntheticStack {
179 pub fn new() -> Self {
181 Self {
182 frames: Vec::new(),
183 total_size: 0,
184 data: Vec::new(),
185 }
186 }
187
188 pub fn push_frame(&mut self, frame: FakeFrame) {
190 self.total_size += frame.size();
191 self.frames.push(frame);
192 }
193
194 pub fn build(&mut self) -> &[usize] {
197 self.data.clear();
198
199 let mut prev_rbp: usize = 0;
202
203 for frame in &self.frames {
204 let rbp_pos = self.data.len();
206
207 self.data.push(prev_rbp);
209 self.data.push(frame.return_address);
211 for &val in &frame.extra_values {
213 self.data.push(val);
214 }
215
216 prev_rbp = rbp_pos;
219 }
220
221 &self.data
222 }
223
224 pub fn frame_count(&self) -> usize {
226 self.frames.len()
227 }
228
229 pub fn total_size_bytes(&self) -> usize {
231 self.total_size
232 }
233}
234
235impl Default for SyntheticStack {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241pub struct StackSpoofer {
243 templates: Vec<(FrameTemplate, Option<FakeFrame>)>,
245}
246
247impl StackSpoofer {
248 pub fn new() -> Self {
250 Self {
251 templates: COMMON_FRAME_TEMPLATES
252 .iter()
253 .map(|t| (t.clone(), None))
254 .collect(),
255 }
256 }
257
258 pub fn resolve_all(&mut self) -> Result<()> {
260 for (template, resolved) in &mut self.templates {
261 match template.resolve() {
262 Ok(frame) => *resolved = Some(frame),
263 Err(_) => continue, }
265 }
266 Ok(())
267 }
268
269 pub fn get_frame(&self, name: &str) -> Option<&FakeFrame> {
271 self.templates
272 .iter()
273 .find(|(t, _)| t.name == name)
274 .and_then(|(_, f)| f.as_ref())
275 }
276
277 pub fn build_stack_for(&self, pattern: &[&str]) -> Result<SyntheticStack> {
279 let mut stack = SyntheticStack::new();
280
281 for &name in pattern {
282 if let Some(frame) = self.get_frame(name) {
283 stack.push_frame(frame.clone());
284 } else {
285 return Err(WraithError::SyscallEnumerationFailed {
286 reason: format!("template '{}' not found or not resolved", name),
287 });
288 }
289 }
290
291 Ok(stack)
292 }
293
294 pub fn build_thread_start_stack(&self) -> Result<SyntheticStack> {
296 self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk"])
298 }
299
300 pub fn build_memory_alloc_stack(&self) -> Result<SyntheticStack> {
302 self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk", "VirtualAlloc"])
303 }
304
305 pub fn build_file_io_stack(&self) -> Result<SyntheticStack> {
307 self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk", "CreateFileW"])
308 }
309}
310
311impl Default for StackSpoofer {
312 fn default() -> Self {
313 Self::new()
314 }
315}
316
317pub fn find_return_address_in_module(module_name: &str, function_name: &str) -> Result<usize> {
319 let peb = Peb::current()?;
320 let query = ModuleQuery::new(&peb);
321 let module = query.find_by_name(module_name)?;
322 module.get_export(function_name)
323}
324
325pub fn create_frame_at_function(
327 module_name: &str,
328 function_name: &str,
329 offset: usize,
330) -> Result<FakeFrame> {
331 let addr = find_return_address_in_module(module_name, function_name)?;
332 Ok(FakeFrame::simple(
333 addr + offset,
334 0,
335 "custom frame",
336 ))
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_frame_template_resolve() {
345 for template in COMMON_FRAME_TEMPLATES {
347 if template.module == "kernel32.dll" {
348 match template.resolve() {
349 Ok(frame) => {
350 assert!(frame.return_address > 0, "should have valid return address");
351 break;
352 }
353 Err(_) => continue,
354 }
355 }
356 }
357 }
358
359 #[test]
360 fn test_synthetic_stack_build() {
361 let mut stack = SyntheticStack::new();
362
363 stack.push_frame(FakeFrame::simple(0x12345678, 0, "test frame 1"));
364 stack.push_frame(FakeFrame::simple(0x87654321, 0, "test frame 2"));
365
366 let data = stack.build();
367 assert!(!data.is_empty(), "should build stack data");
368 assert_eq!(stack.frame_count(), 2);
369 }
370
371 #[test]
372 fn test_stack_spoofer() {
373 let mut spoofer = StackSpoofer::new();
374
375 let _ = spoofer.resolve_all();
377 }
378
379 #[test]
380 fn test_find_return_address() {
381 if let Ok(addr) = find_return_address_in_module("kernel32.dll", "VirtualAlloc") {
383 assert!(addr > 0, "should find VirtualAlloc");
384 }
385 }
386}