specter/memory/allocation/
shellcode.rs1use crate::memory::{code_cave, info::symbol, rw};
4#[cfg(feature = "dev_release")]
5use crate::utils::logger;
6use std::arch::asm;
7use std::collections::HashMap;
8use std::ffi::c_void;
9use thiserror::Error;
10
11const CACHE_LINE_SIZE: usize = 64;
12
13#[derive(Error, Debug)]
14pub enum LoaderError {
16 #[error("Code cave allocation failed: {0}")]
18 AllocationFailed(#[from] code_cave::CodeCaveError),
19 #[error("Symbol not found: {0}")]
21 SymbolNotFound(String),
22 #[error("Write failed: {0}")]
24 WriteFailed(#[from] rw::RwError),
25 #[error("Invalid shellcode size: {0}")]
27 InvalidSize(usize),
28 #[error("Relocation failed at offset {0}")]
30 RelocationFailed(usize),
31}
32
33pub struct LoadedShellcode {
35 pub address: usize,
37 pub size: usize,
39 auto_free: bool,
41}
42
43impl LoadedShellcode {
44 pub unsafe fn execute(&self) -> usize {
49 unsafe {
50 let func: extern "C" fn() -> usize = std::mem::transmute(self.address);
51 func()
52 }
53 }
54
55 pub unsafe fn execute_as<F, R>(&self, callback: impl FnOnce(F) -> R) -> R
60 where
61 F: Copy,
62 {
63 unsafe {
64 let func_ptr = self.address as *const ();
65 let func: F = *(&func_ptr as *const *const () as *const F);
66 callback(func)
67 }
68 }
69
70 pub unsafe fn as_function<F>(&self) -> F
75 where
76 F: Copy,
77 {
78 unsafe {
79 let func_ptr = self.address as *const ();
80 *(&func_ptr as *const *const () as *const F)
81 }
82 }
83
84 pub fn free(self) {
86 if self.address != 0 {
87 let _ = code_cave::free_cave(self.address);
88 }
89 }
90}
91
92impl Drop for LoadedShellcode {
93 fn drop(&mut self) {
94 if self.auto_free && self.address != 0 {
95 let _ = code_cave::free_cave(self.address);
96 }
97 }
98}
99
100#[derive(Debug, Clone)]
101pub struct SymbolRelocation {
103 pub offset: usize,
105 pub symbol_name: String,
107}
108
109pub struct ShellcodeBuilder {
111 code: Vec<u8>,
112 relocations: Vec<SymbolRelocation>,
113 auto_free: bool,
114 target_address: Option<usize>,
115}
116
117impl ShellcodeBuilder {
118 pub fn new(shellcode: &[u8]) -> Self {
123 Self {
124 code: shellcode.to_vec(),
125 relocations: Vec::new(),
126 auto_free: true,
127 target_address: None,
128 }
129 }
130
131 pub fn from_instructions(instructions: &[u32]) -> Self {
136 let bytes: Vec<u8> = instructions
137 .iter()
138 .flat_map(|&instr| instr.to_le_bytes())
139 .collect();
140 Self::new(&bytes)
141 }
142
143 pub fn with_symbol(mut self, offset: usize, symbol_name: &str) -> Self {
151 self.relocations.push(SymbolRelocation {
152 offset,
153 symbol_name: symbol_name.to_string(),
154 });
155 self
156 }
157
158 pub fn no_auto_free(mut self) -> Self {
162 self.auto_free = false;
163 self
164 }
165
166 pub fn near_address(mut self, target: usize) -> Self {
170 self.target_address = Some(target);
171 self
172 }
173
174 pub fn load(self) -> Result<LoadedShellcode, LoaderError> {
185 if self.code.is_empty() {
186 return Err(LoaderError::InvalidSize(0));
187 }
188
189 let mut cave = if let Some(target) = self.target_address {
190 code_cave::allocate_cave_near(target, self.code.len())?
191 } else {
192 code_cave::allocate_cave(self.code.len())?
193 };
194
195 let alignment = cave.address % 4;
196 if alignment != 0 {
197 let adjust = 4 - alignment;
198 cave.address += adjust;
199 cave.size = cave.size.saturating_sub(adjust);
200
201 if cave.size < self.code.len() {
202 return Err(LoaderError::InvalidSize(cave.size));
203 }
204 }
205
206 let mut code = self.code.clone();
207
208 let mut symbol_cache: HashMap<String, usize> = HashMap::new();
209 for reloc in &self.relocations {
210 let symbol_addr = if let Some(&cached) = symbol_cache.get(&reloc.symbol_name) {
211 cached
212 } else {
213 let addr = symbol::resolve_symbol(&reloc.symbol_name)
214 .map_err(|_| LoaderError::SymbolNotFound(reloc.symbol_name.clone()))?;
215
216 symbol_cache.insert(reloc.symbol_name.clone(), addr);
217 addr
218 };
219
220 if reloc.offset + 8 > code.len() {
221 return Err(LoaderError::RelocationFailed(reloc.offset));
222 }
223
224 let addr_bytes = (symbol_addr as u64).to_le_bytes();
225 code[reloc.offset..reloc.offset + 8].copy_from_slice(&addr_bytes);
226 }
227
228 unsafe {
229 rw::write_bytes(cave.address, &code)?;
230 }
231
232 unsafe {
234 for (i, &expected) in code.iter().enumerate() {
235 match rw::read::<u8>(cave.address + i) {
236 Ok(actual) => {
237 if actual != expected {
238 return Err(LoaderError::WriteFailed(rw::RwError::NullPointer));
239 }
240 }
241 Err(e) => {
242 return Err(LoaderError::WriteFailed(e));
243 }
244 }
245 }
246 }
247
248 if !crate::memory::protection::is_executable(cave.address) {
249 return Err(LoaderError::InvalidSize(0));
250 }
251
252 unsafe {
254 invalidate_icache(cave.address as *mut c_void, code.len());
255 }
256
257 #[cfg(feature = "dev_release")]
258 logger::info(&format!(
259 "Shellcode loaded at {:#x} ({} bytes)",
260 cave.address,
261 code.len()
262 ));
263
264 Ok(LoadedShellcode {
265 address: cave.address,
266 size: code.len(),
267 auto_free: self.auto_free,
268 })
269 }
270}
271
272pub trait ShellcodeSource {
274 fn into_bytes(self) -> Vec<u8>;
275}
276
277impl ShellcodeSource for &[u8] {
278 fn into_bytes(self) -> Vec<u8> {
279 self.to_vec()
280 }
281}
282
283impl ShellcodeSource for &[u32] {
284 fn into_bytes(self) -> Vec<u8> {
285 self.iter().flat_map(|&instr| instr.to_le_bytes()).collect()
286 }
287}
288
289impl<const N: usize> ShellcodeSource for &[u32; N] {
290 fn into_bytes(self) -> Vec<u8> {
291 self.iter().flat_map(|&instr| instr.to_le_bytes()).collect()
292 }
293}
294
295impl<const N: usize> ShellcodeSource for &[u8; N] {
296 fn into_bytes(self) -> Vec<u8> {
297 self.to_vec()
298 }
299}
300
301pub fn load(source: impl ShellcodeSource) -> Result<LoadedShellcode, LoaderError> {
309 ShellcodeBuilder::new(&source.into_bytes()).load()
310}
311
312#[inline]
317pub unsafe fn invalidate_icache(start: *mut c_void, len: usize) {
318 unsafe {
319 let start_addr = start as usize;
320 let end_addr = start_addr + len;
321 let mut addr = start_addr & !(CACHE_LINE_SIZE - 1);
322
323 while addr < end_addr {
325 asm!("dc cvau, {x}", x = in(reg) addr, options(nostack, preserves_flags));
326 addr += CACHE_LINE_SIZE;
327 }
328 asm!("dsb ish", options(nostack, preserves_flags));
329
330 addr = start_addr & !(CACHE_LINE_SIZE - 1);
332 while addr < end_addr {
333 asm!("ic ivau, {x}", x = in(reg) addr, options(nostack, preserves_flags));
334 addr += CACHE_LINE_SIZE;
335 }
336 asm!("dsb ish", options(nostack, preserves_flags));
337 asm!("isb", options(nostack, preserves_flags));
338 }
339}