#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{format, string::String, vec, vec::Vec};
#[cfg(feature = "std")]
use std::{format, string::String, vec, vec::Vec};
use crate::error::{Result, WraithError};
use crate::navigation::ModuleQuery;
use crate::structures::Peb;
#[derive(Debug, Clone)]
pub struct FakeFrame {
pub return_address: usize,
pub saved_rbp: usize,
pub extra_values: Vec<usize>,
pub description: &'static str,
}
impl FakeFrame {
pub const fn simple(return_address: usize, saved_rbp: usize, description: &'static str) -> Self {
Self {
return_address,
saved_rbp,
extra_values: Vec::new(),
description,
}
}
pub fn with_extra(
return_address: usize,
saved_rbp: usize,
extra: Vec<usize>,
description: &'static str,
) -> Self {
Self {
return_address,
saved_rbp,
extra_values: extra,
description,
}
}
pub fn size(&self) -> usize {
(2 + self.extra_values.len()) * core::mem::size_of::<usize>()
}
}
#[derive(Debug, Clone)]
pub struct FrameTemplate {
pub name: &'static str,
pub module: &'static str,
pub function: &'static str,
pub offset: usize,
pub extra_slots: usize,
pub description: &'static str,
}
impl FrameTemplate {
pub fn resolve(&self) -> Result<FakeFrame> {
let peb = Peb::current()?;
let query = ModuleQuery::new(&peb);
let module = query.find_by_name(self.module)?;
let func_addr = module.get_export(self.function)?;
let return_addr = func_addr + self.offset;
Ok(FakeFrame {
return_address: return_addr,
saved_rbp: 0, extra_values: vec![0; self.extra_slots],
description: self.description,
})
}
}
pub static COMMON_FRAME_TEMPLATES: &[FrameTemplate] = &[
FrameTemplate {
name: "CreateFileW",
module: "kernel32.dll",
function: "CreateFileW",
offset: 0x50, extra_slots: 4,
description: "kernel32!CreateFileW frame",
},
FrameTemplate {
name: "ReadFile",
module: "kernel32.dll",
function: "ReadFile",
offset: 0x40,
extra_slots: 4,
description: "kernel32!ReadFile frame",
},
FrameTemplate {
name: "WriteFile",
module: "kernel32.dll",
function: "WriteFile",
offset: 0x40,
extra_slots: 4,
description: "kernel32!WriteFile frame",
},
FrameTemplate {
name: "VirtualAlloc",
module: "kernel32.dll",
function: "VirtualAlloc",
offset: 0x30,
extra_slots: 2,
description: "kernel32!VirtualAlloc frame",
},
FrameTemplate {
name: "VirtualProtect",
module: "kernel32.dll",
function: "VirtualProtect",
offset: 0x30,
extra_slots: 2,
description: "kernel32!VirtualProtect frame",
},
FrameTemplate {
name: "OpenProcess",
module: "kernel32.dll",
function: "OpenProcess",
offset: 0x40,
extra_slots: 3,
description: "kernel32!OpenProcess frame",
},
FrameTemplate {
name: "BaseThreadInitThunk",
module: "kernel32.dll",
function: "BaseThreadInitThunk",
offset: 0x14,
extra_slots: 1,
description: "kernel32!BaseThreadInitThunk frame",
},
FrameTemplate {
name: "RtlUserThreadStart",
module: "ntdll.dll",
function: "RtlUserThreadStart",
offset: 0x21,
extra_slots: 1,
description: "ntdll!RtlUserThreadStart frame",
},
];
#[derive(Debug)]
pub struct SyntheticStack {
frames: Vec<FakeFrame>,
total_size: usize,
data: Vec<usize>,
}
impl SyntheticStack {
pub fn new() -> Self {
Self {
frames: Vec::new(),
total_size: 0,
data: Vec::new(),
}
}
pub fn push_frame(&mut self, frame: FakeFrame) {
self.total_size += frame.size();
self.frames.push(frame);
}
pub fn build(&mut self) -> &[usize] {
self.data.clear();
let mut prev_rbp: usize = 0;
for frame in &self.frames {
let rbp_pos = self.data.len();
self.data.push(prev_rbp);
self.data.push(frame.return_address);
for &val in &frame.extra_values {
self.data.push(val);
}
prev_rbp = rbp_pos;
}
&self.data
}
pub fn frame_count(&self) -> usize {
self.frames.len()
}
pub fn total_size_bytes(&self) -> usize {
self.total_size
}
}
impl Default for SyntheticStack {
fn default() -> Self {
Self::new()
}
}
pub struct StackSpoofer {
templates: Vec<(FrameTemplate, Option<FakeFrame>)>,
}
impl StackSpoofer {
pub fn new() -> Self {
Self {
templates: COMMON_FRAME_TEMPLATES
.iter()
.map(|t| (t.clone(), None))
.collect(),
}
}
pub fn resolve_all(&mut self) -> Result<()> {
for (template, resolved) in &mut self.templates {
match template.resolve() {
Ok(frame) => *resolved = Some(frame),
Err(_) => continue, }
}
Ok(())
}
pub fn get_frame(&self, name: &str) -> Option<&FakeFrame> {
self.templates
.iter()
.find(|(t, _)| t.name == name)
.and_then(|(_, f)| f.as_ref())
}
pub fn build_stack_for(&self, pattern: &[&str]) -> Result<SyntheticStack> {
let mut stack = SyntheticStack::new();
for &name in pattern {
if let Some(frame) = self.get_frame(name) {
stack.push_frame(frame.clone());
} else {
return Err(WraithError::SyscallEnumerationFailed {
reason: format!("template '{}' not found or not resolved", name),
});
}
}
Ok(stack)
}
pub fn build_thread_start_stack(&self) -> Result<SyntheticStack> {
self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk"])
}
pub fn build_memory_alloc_stack(&self) -> Result<SyntheticStack> {
self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk", "VirtualAlloc"])
}
pub fn build_file_io_stack(&self) -> Result<SyntheticStack> {
self.build_stack_for(&["RtlUserThreadStart", "BaseThreadInitThunk", "CreateFileW"])
}
}
impl Default for StackSpoofer {
fn default() -> Self {
Self::new()
}
}
pub fn find_return_address_in_module(module_name: &str, function_name: &str) -> Result<usize> {
let peb = Peb::current()?;
let query = ModuleQuery::new(&peb);
let module = query.find_by_name(module_name)?;
module.get_export(function_name)
}
pub fn create_frame_at_function(
module_name: &str,
function_name: &str,
offset: usize,
) -> Result<FakeFrame> {
let addr = find_return_address_in_module(module_name, function_name)?;
Ok(FakeFrame::simple(
addr + offset,
0,
"custom frame",
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_template_resolve() {
for template in COMMON_FRAME_TEMPLATES {
if template.module == "kernel32.dll" {
match template.resolve() {
Ok(frame) => {
assert!(frame.return_address > 0, "should have valid return address");
break;
}
Err(_) => continue,
}
}
}
}
#[test]
fn test_synthetic_stack_build() {
let mut stack = SyntheticStack::new();
stack.push_frame(FakeFrame::simple(0x12345678, 0, "test frame 1"));
stack.push_frame(FakeFrame::simple(0x87654321, 0, "test frame 2"));
let data = stack.build();
assert!(!data.is_empty(), "should build stack data");
assert_eq!(stack.frame_count(), 2);
}
#[test]
fn test_stack_spoofer() {
let mut spoofer = StackSpoofer::new();
let _ = spoofer.resolve_all();
}
#[test]
fn test_find_return_address() {
if let Ok(addr) = find_return_address_in_module("kernel32.dll", "VirtualAlloc") {
assert!(addr > 0, "should find VirtualAlloc");
}
}
}