wraith/manipulation/inline_hook/hook/
eat.rs1#[cfg(all(not(feature = "std"), feature = "alloc"))]
19use alloc::{collections::BTreeMap, format, string::String, vec::Vec};
20
21#[cfg(feature = "std")]
22use std::{collections::HashMap, format, string::String, vec::Vec};
23
24use crate::error::{Result, WraithError};
25use crate::navigation::{Module, ModuleQuery};
26use crate::structures::pe::{DataDirectoryType, ExportDirectory};
27use crate::structures::Peb;
28use crate::util::memory::ProtectionGuard;
29
30const PAGE_READWRITE: u32 = 0x04;
31const MAX_EXPORT_COUNT: usize = 0x10000;
32
33#[derive(Debug, Clone)]
35pub struct EatEntry {
36 pub entry_address: usize,
38 pub current_rva: u32,
40 pub current_address: usize,
42 pub function_name: Option<String>,
44 pub ordinal: u32,
46 pub is_forwarded: bool,
48 pub forwarder: Option<String>,
50}
51
52pub struct EatHook {
54 eat_entry: usize,
56 module_base: usize,
58 original_rva: u32,
60 detour_rva: u32,
62 detour: usize,
64 active: bool,
66 auto_restore: bool,
68}
69
70impl EatHook {
71 pub fn new(module_name: &str, function_name: &str, detour: usize) -> Result<Self> {
88 let peb = Peb::current()?;
89 let query = ModuleQuery::new(&peb);
90 let module = query.find_by_name(module_name)?;
91
92 Self::new_in_module(&module, function_name, detour)
93 }
94
95 pub fn new_in_module(module: &Module, function_name: &str, detour: usize) -> Result<Self> {
97 let eat_entry = find_eat_entry(module, function_name)?;
98
99 if eat_entry.is_forwarded {
100 return Err(WraithError::ForwardedExport {
101 forwarder: eat_entry.forwarder.unwrap_or_default(),
102 });
103 }
104
105 Self::new_at_address(eat_entry.entry_address, module.base(), detour)
106 }
107
108 pub fn new_at_address(eat_entry: usize, module_base: usize, detour: usize) -> Result<Self> {
110 if eat_entry == 0 {
111 return Err(WraithError::NullPointer { context: "eat_entry" });
112 }
113
114 let original_rva = unsafe { *(eat_entry as *const u32) };
117
118 let detour_rva = address_to_rva(module_base, detour)?;
120
121 let mut hook = Self {
122 eat_entry,
123 module_base,
124 original_rva,
125 detour_rva,
126 detour,
127 active: false,
128 auto_restore: true,
129 };
130
131 hook.install()?;
132 Ok(hook)
133 }
134
135 pub fn install(&mut self) -> Result<()> {
137 if self.active {
138 return Ok(());
139 }
140
141 write_eat_entry(self.eat_entry, self.detour_rva)?;
142 self.active = true;
143
144 Ok(())
145 }
146
147 pub fn uninstall(&mut self) -> Result<()> {
149 if !self.active {
150 return Ok(());
151 }
152
153 write_eat_entry(self.eat_entry, self.original_rva)?;
154 self.active = false;
155
156 Ok(())
157 }
158
159 pub fn is_active(&self) -> bool {
161 self.active
162 }
163
164 pub fn original(&self) -> usize {
166 self.module_base + self.original_rva as usize
167 }
168
169 pub fn original_rva(&self) -> u32 {
171 self.original_rva
172 }
173
174 pub fn detour(&self) -> usize {
176 self.detour
177 }
178
179 pub fn eat_entry(&self) -> usize {
181 self.eat_entry
182 }
183
184 pub fn set_auto_restore(&mut self, restore: bool) {
186 self.auto_restore = restore;
187 }
188
189 pub fn leak(mut self) {
191 self.auto_restore = false;
192 core::mem::forget(self);
193 }
194
195 pub fn restore(mut self) -> Result<()> {
197 self.uninstall()?;
198 self.auto_restore = false;
199 Ok(())
200 }
201}
202
203impl Drop for EatHook {
204 fn drop(&mut self) {
205 if self.auto_restore && self.active {
206 let _ = self.uninstall();
207 }
208 }
209}
210
211unsafe impl Send for EatHook {}
213unsafe impl Sync for EatHook {}
214
215pub type EatHookGuard = EatHook;
217
218pub fn find_eat_entry(module: &Module, function_name: &str) -> Result<EatEntry> {
220 let entries = enumerate_eat_entries(module)?;
221
222 for entry in entries {
223 if let Some(ref name) = entry.function_name {
224 if name == function_name {
225 return Ok(entry);
226 }
227 }
228 }
229
230 Err(WraithError::ModuleNotFound {
231 name: format!("EAT entry for {}", function_name),
232 })
233}
234
235pub fn find_eat_entry_by_ordinal(module: &Module, ordinal: u32) -> Result<EatEntry> {
237 let entries = enumerate_eat_entries(module)?;
238
239 for entry in entries {
240 if entry.ordinal == ordinal {
241 return Ok(entry);
242 }
243 }
244
245 Err(WraithError::ModuleNotFound {
246 name: format!("EAT entry for ordinal {}", ordinal),
247 })
248}
249
250pub fn enumerate_eat_entries(module: &Module) -> Result<Vec<EatEntry>> {
252 let nt = module.nt_headers()?;
253 let export_dir = nt
254 .data_directory(DataDirectoryType::Export.index())
255 .ok_or_else(|| WraithError::InvalidPeFormat {
256 reason: "no export directory".into(),
257 })?;
258
259 if !export_dir.is_present() {
260 return Ok(Vec::new());
261 }
262
263 let base = module.base();
264 let export_va = base + export_dir.virtual_address as usize;
265 let export_end = export_dir.virtual_address + export_dir.size;
266
267 let exports = unsafe { &*(export_va as *const ExportDirectory) };
269
270 let num_functions = exports.number_of_functions as usize;
271 let num_names = exports.number_of_names as usize;
272 let ordinal_base = exports.base;
273
274 if num_functions > MAX_EXPORT_COUNT || num_names > MAX_EXPORT_COUNT {
275 return Err(WraithError::InvalidPeFormat {
276 reason: format!("unreasonable export count: {} functions", num_functions),
277 });
278 }
279
280 let functions_va = base + exports.address_of_functions as usize;
281 let names_va = base + exports.address_of_names as usize;
282 let ordinals_va = base + exports.address_of_name_ordinals as usize;
283
284 let mut entries = Vec::with_capacity(num_functions);
285
286 #[cfg(feature = "std")]
288 let mut name_map = HashMap::new();
289 #[cfg(not(feature = "std"))]
290 let mut name_map = BTreeMap::new();
291 for i in 0..num_names {
292 let ordinal = unsafe { *((ordinals_va + i * 2) as *const u16) };
294 let name_rva = unsafe { *((names_va + i * 4) as *const u32) };
295 let name_va = base + name_rva as usize;
296 if let Ok(name) = read_cstring(name_va, 256) {
297 name_map.insert(ordinal as usize, name);
298 }
299 }
300
301 for i in 0..num_functions {
303 let entry_addr = functions_va + i * 4;
304 let func_rva = unsafe { *(entry_addr as *const u32) };
306
307 if func_rva == 0 {
308 continue; }
310
311 let ordinal = ordinal_base + i as u32;
312
313 let is_forwarded = func_rva >= export_dir.virtual_address && func_rva < export_end;
315 let forwarder = if is_forwarded {
316 let forwarder_va = base + func_rva as usize;
317 read_cstring(forwarder_va, 256).ok()
318 } else {
319 None
320 };
321
322 entries.push(EatEntry {
323 entry_address: entry_addr,
324 current_rva: func_rva,
325 current_address: base + func_rva as usize,
326 function_name: name_map.get(&i).cloned(),
327 ordinal,
328 is_forwarded,
329 forwarder,
330 });
331 }
332
333 Ok(entries)
334}
335
336fn address_to_rva(module_base: usize, address: usize) -> Result<u32> {
338 if address < module_base {
339 return Err(WraithError::InvalidPeFormat {
340 reason: format!(
341 "address {:#x} is below module base {:#x}",
342 address, module_base
343 ),
344 });
345 }
346
347 let offset = address - module_base;
348
349 if offset > u32::MAX as usize {
350 return Err(WraithError::InvalidPeFormat {
351 reason: format!(
352 "offset {:#x} exceeds u32 max for RVA encoding",
353 offset
354 ),
355 });
356 }
357
358 Ok(offset as u32)
359}
360
361fn write_eat_entry(entry: usize, rva: u32) -> Result<()> {
363 let _guard = ProtectionGuard::new(entry, core::mem::size_of::<u32>(), PAGE_READWRITE)?;
364
365 unsafe {
367 *(entry as *mut u32) = rva;
368 }
369
370 Ok(())
371}
372
373fn read_cstring(addr: usize, max_len: usize) -> Result<String> {
375 let mut bytes = Vec::new();
376
377 for i in 0..max_len {
378 let byte = unsafe { *((addr + i) as *const u8) };
380 if byte == 0 {
381 break;
382 }
383 bytes.push(byte);
384 }
385
386 String::from_utf8(bytes).map_err(|_| WraithError::InvalidPeFormat {
387 reason: "invalid string encoding".into(),
388 })
389}
390
391pub struct EatHookBuilder {
393 module_name: Option<String>,
394 function_name: Option<String>,
395 detour: Option<usize>,
396}
397
398impl EatHookBuilder {
399 pub fn new() -> Self {
401 Self {
402 module_name: None,
403 function_name: None,
404 detour: None,
405 }
406 }
407
408 pub fn module(mut self, name: &str) -> Self {
410 self.module_name = Some(name.to_string());
411 self
412 }
413
414 pub fn function(mut self, name: &str) -> Self {
416 self.function_name = Some(name.to_string());
417 self
418 }
419
420 pub fn detour(mut self, addr: usize) -> Self {
422 self.detour = Some(addr);
423 self
424 }
425
426 pub fn build(self) -> Result<EatHook> {
428 let module_name = self.module_name.ok_or(WraithError::NullPointer {
429 context: "module_name not set",
430 })?;
431 let function_name = self.function_name.ok_or(WraithError::NullPointer {
432 context: "function_name not set",
433 })?;
434 let detour = self.detour.ok_or(WraithError::NullPointer {
435 context: "detour not set",
436 })?;
437
438 EatHook::new(&module_name, &function_name, detour)
439 }
440}
441
442impl Default for EatHookBuilder {
443 fn default() -> Self {
444 Self::new()
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 #[test]
453 fn test_enumerate_eat() {
454 let peb = Peb::current().expect("should get PEB");
455 let query = ModuleQuery::new(&peb);
456 let ntdll = query.ntdll().expect("should get ntdll");
457
458 let entries = enumerate_eat_entries(&ntdll).expect("should enumerate EAT");
459 assert!(!entries.is_empty(), "ntdll should have exports");
460 }
461
462 #[test]
463 fn test_find_ntclose() {
464 let peb = Peb::current().expect("should get PEB");
465 let query = ModuleQuery::new(&peb);
466 let ntdll = query.ntdll().expect("should get ntdll");
467
468 let entry = find_eat_entry(&ntdll, "NtClose").expect("should find NtClose");
469 assert!(entry.function_name.as_deref() == Some("NtClose"));
470 assert!(!entry.is_forwarded);
471 }
472
473 #[test]
474 fn test_address_to_rva() {
475 let base = 0x10000usize;
476 let addr = 0x10500usize;
477
478 let rva = address_to_rva(base, addr).expect("should convert");
479 assert_eq!(rva, 0x500);
480 }
481}