wraith/manipulation/spoof/
stack.rs1#[cfg(all(not(feature = "std"), feature = "alloc"))]
7use alloc::{format, string::String, vec, vec::Vec};
8
9#[cfg(feature = "std")]
10use std::{format, string::String, vec, vec::Vec};
11
12use crate::error::{Result, WraithError};
13use crate::navigation::ModuleQuery;
14use crate::structures::Peb;
15
16#[derive(Debug, Clone)]
18pub struct FakeFrame {
19 pub return_address: usize,
21 pub saved_rbp: usize,
23 pub extra_values: Vec<usize>,
25 pub description: &'static str,
27}
28
29impl FakeFrame {
30 pub const fn simple(return_address: usize, saved_rbp: usize, description: &'static str) -> Self {
32 Self {
33 return_address,
34 saved_rbp,
35 extra_values: Vec::new(),
36 description,
37 }
38 }
39
40 pub fn with_extra(
42 return_address: usize,
43 saved_rbp: usize,
44 extra: Vec<usize>,
45 description: &'static str,
46 ) -> Self {
47 Self {
48 return_address,
49 saved_rbp,
50 extra_values: extra,
51 description,
52 }
53 }
54
55 pub fn size(&self) -> usize {
57 (2 + self.extra_values.len()) * core::mem::size_of::<usize>()
59 }
60}
61
62#[derive(Debug, Clone)]
64pub struct FrameTemplate {
65 pub name: &'static str,
67 pub module: &'static str,
69 pub function: &'static str,
71 pub offset: usize,
73 pub extra_slots: usize,
75 pub description: &'static str,
77}
78
79impl FrameTemplate {
80 pub fn resolve(&self) -> Result<FakeFrame> {
82 let peb = Peb::current()?;
83 let query = ModuleQuery::new(&peb);
84 let module = query.find_by_name(self.module)?;
85 let func_addr = module.get_export(self.function)?;
86 let return_addr = func_addr + self.offset;
87
88 Ok(FakeFrame {
89 return_address: return_addr,
90 saved_rbp: 0, extra_values: vec![0; self.extra_slots],
92 description: self.description,
93 })
94 }
95}
96
97pub static COMMON_FRAME_TEMPLATES: &[FrameTemplate] = &[
99 FrameTemplate {
101 name: "CreateFileW",
102 module: "kernel32.dll",
103 function: "CreateFileW",
104 offset: 0x50, extra_slots: 4,
106 description: "kernel32!CreateFileW frame",
107 },
108 FrameTemplate {
110 name: "ReadFile",
111 module: "kernel32.dll",
112 function: "ReadFile",
113 offset: 0x40,
114 extra_slots: 4,
115 description: "kernel32!ReadFile frame",
116 },
117 FrameTemplate {
119 name: "WriteFile",
120 module: "kernel32.dll",
121 function: "WriteFile",
122 offset: 0x40,
123 extra_slots: 4,
124 description: "kernel32!WriteFile frame",
125 },
126 FrameTemplate {
128 name: "VirtualAlloc",
129 module: "kernel32.dll",
130 function: "VirtualAlloc",
131 offset: 0x30,
132 extra_slots: 2,
133 description: "kernel32!VirtualAlloc frame",
134 },
135 FrameTemplate {
137 name: "VirtualProtect",
138 module: "kernel32.dll",
139 function: "VirtualProtect",
140 offset: 0x30,
141 extra_slots: 2,
142 description: "kernel32!VirtualProtect frame",
143 },
144 FrameTemplate {
146 name: "OpenProcess",
147 module: "kernel32.dll",
148 function: "OpenProcess",
149 offset: 0x40,
150 extra_slots: 3,
151 description: "kernel32!OpenProcess frame",
152 },
153 FrameTemplate {
155 name: "BaseThreadInitThunk",
156 module: "kernel32.dll",
157 function: "BaseThreadInitThunk",
158 offset: 0x14,
159 extra_slots: 1,
160 description: "kernel32!BaseThreadInitThunk frame",
161 },
162 FrameTemplate {
164 name: "RtlUserThreadStart",
165 module: "ntdll.dll",
166 function: "RtlUserThreadStart",
167 offset: 0x21,
168 extra_slots: 1,
169 description: "ntdll!RtlUserThreadStart frame",
170 },
171];
172
173#[derive(Debug)]
175pub struct SyntheticStack {
176 frames: Vec<FakeFrame>,
178 total_size: usize,
180 data: Vec<usize>,
182}
183
184impl SyntheticStack {
185 pub fn new() -> Self {
187 Self {
188 frames: Vec::new(),
189 total_size: 0,
190 data: Vec::new(),
191 }
192 }
193
194 pub fn push_frame(&mut self, frame: FakeFrame) {
196 self.total_size += frame.size();
197 self.frames.push(frame);
198 }
199
200 pub fn build(&mut self) -> &[usize] {
203 self.data.clear();
204
205 let mut prev_rbp: usize = 0;
208
209 for frame in &self.frames {
210 let rbp_pos = self.data.len();
212
213 self.data.push(prev_rbp);
215 self.data.push(frame.return_address);
217 for &val in &frame.extra_values {
219 self.data.push(val);
220 }
221
222 prev_rbp = rbp_pos;
225 }
226
227 &self.data
228 }
229
230 pub fn frame_count(&self) -> usize {
232 self.frames.len()
233 }
234
235 pub fn total_size_bytes(&self) -> usize {
237 self.total_size
238 }
239}
240
241impl Default for SyntheticStack {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247pub struct StackSpoofer {
249 templates: Vec<(FrameTemplate, Option<FakeFrame>)>,
251}
252
253impl StackSpoofer {
254 pub fn new() -> Self {
256 Self {
257 templates: COMMON_FRAME_TEMPLATES
258 .iter()
259 .map(|t| (t.clone(), None))
260 .collect(),
261 }
262 }
263
264 pub fn resolve_all(&mut self) -> Result<()> {
266 for (template, resolved) in &mut self.templates {
267 match template.resolve() {
268 Ok(frame) => *resolved = Some(frame),
269 Err(_) => continue, }
271 }
272 Ok(())
273 }
274
275 pub fn get_frame(&self, name: &str) -> Option<&FakeFrame> {
277 self.templates
278 .iter()
279 .find(|(t, _)| t.name == name)
280 .and_then(|(_, f)| f.as_ref())
281 }
282
283 pub fn build_stack_for(&self, pattern: &[&str]) -> Result<SyntheticStack> {
285 let mut stack = SyntheticStack::new();
286
287 for &name in pattern {
288 if let Some(frame) = self.get_frame(name) {
289 stack.push_frame(frame.clone());
290 } else {
291 return Err(WraithError::SyscallEnumerationFailed {
292 reason: format!("template '{}' not found or not resolved", name),
293 });
294 }
295 }
296
297 Ok(stack)
298 }
299
300 pub fn build_thread_start_stack(&self) -> Result<SyntheticStack> {
302 self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk"])
304 }
305
306 pub fn build_memory_alloc_stack(&self) -> Result<SyntheticStack> {
308 self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk", "VirtualAlloc"])
309 }
310
311 pub fn build_file_io_stack(&self) -> Result<SyntheticStack> {
313 self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk", "CreateFileW"])
314 }
315}
316
317impl Default for StackSpoofer {
318 fn default() -> Self {
319 Self::new()
320 }
321}
322
323pub fn find_return_address_in_module(module_name: &str, function_name: &str) -> Result<usize> {
325 let peb = Peb::current()?;
326 let query = ModuleQuery::new(&peb);
327 let module = query.find_by_name(module_name)?;
328 module.get_export(function_name)
329}
330
331pub fn create_frame_at_function(
333 module_name: &str,
334 function_name: &str,
335 offset: usize,
336) -> Result<FakeFrame> {
337 let addr = find_return_address_in_module(module_name, function_name)?;
338 Ok(FakeFrame::simple(
339 addr + offset,
340 0,
341 "custom frame",
342 ))
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn test_frame_template_resolve() {
351 for template in COMMON_FRAME_TEMPLATES {
353 if template.module == "kernel32.dll" {
354 match template.resolve() {
355 Ok(frame) => {
356 assert!(frame.return_address > 0, "should have valid return address");
357 break;
358 }
359 Err(_) => continue,
360 }
361 }
362 }
363 }
364
365 #[test]
366 fn test_synthetic_stack_build() {
367 let mut stack = SyntheticStack::new();
368
369 stack.push_frame(FakeFrame::simple(0x12345678, 0, "test frame 1"));
370 stack.push_frame(FakeFrame::simple(0x87654321, 0, "test frame 2"));
371
372 let data = stack.build();
373 assert!(!data.is_empty(), "should build stack data");
374 assert_eq!(stack.frame_count(), 2);
375 }
376
377 #[test]
378 fn test_stack_spoofer() {
379 let mut spoofer = StackSpoofer::new();
380
381 let _ = spoofer.resolve_all();
383 }
384
385 #[test]
386 fn test_find_return_address() {
387 if let Ok(addr) = find_return_address_in_module("kernel32.dll", "VirtualAlloc") {
389 assert!(addr > 0, "should find VirtualAlloc");
390 }
391 }
392}