wraith/manipulation/inline_hook/hook/
iat.rs1#[cfg(all(not(feature = "std"), feature = "alloc"))]
19use alloc::{format, string::String, vec::Vec};
20
21#[cfg(feature = "std")]
22use std::{format, string::String, vec::Vec};
23
24use crate::error::{Result, WraithError};
25use crate::navigation::{Module, ModuleQuery};
26use crate::structures::pe::{DataDirectoryType, ImportDescriptor, ImportByName};
27use crate::structures::Peb;
28use crate::util::memory::ProtectionGuard;
29
30const PAGE_READWRITE: u32 = 0x04;
31
32#[cfg(target_arch = "x86_64")]
33use crate::structures::pe::{ThunkData64 as ThunkData, IMAGE_ORDINAL_FLAG64 as IMAGE_ORDINAL_FLAG};
34#[cfg(target_arch = "x86")]
35use crate::structures::pe::{ThunkData32 as ThunkData, IMAGE_ORDINAL_FLAG32 as IMAGE_ORDINAL_FLAG};
36
37#[derive(Debug, Clone)]
39pub struct IatEntry {
40 pub entry_address: usize,
42 pub current_value: usize,
44 pub function_name: Option<String>,
46 pub ordinal: Option<u16>,
48 pub dll_name: String,
50}
51
52pub struct IatHook {
54 iat_entry: usize,
56 original: usize,
58 detour: usize,
60 active: bool,
62 auto_restore: bool,
64}
65
66impl IatHook {
67 pub fn new(
81 target_module: &str,
82 import_dll: &str,
83 function_name: &str,
84 detour: usize,
85 ) -> Result<Self> {
86 let peb = Peb::current()?;
87 let query = ModuleQuery::new(&peb);
88 let module = query.find_by_name(target_module)?;
89
90 Self::new_in_module(&module, import_dll, function_name, detour)
91 }
92
93 pub fn new_in_module(
95 module: &Module,
96 import_dll: &str,
97 function_name: &str,
98 detour: usize,
99 ) -> Result<Self> {
100 let iat_entry = find_iat_entry(module, import_dll, function_name)?;
101 Self::new_at_address(iat_entry.entry_address, detour)
102 }
103
104 pub fn new_at_address(iat_entry: usize, detour: usize) -> Result<Self> {
108 if iat_entry == 0 {
109 return Err(WraithError::NullPointer { context: "iat_entry" });
110 }
111
112 let original = unsafe { *(iat_entry as *const usize) };
115
116 let mut hook = Self {
117 iat_entry,
118 original,
119 detour,
120 active: false,
121 auto_restore: true,
122 };
123
124 hook.install()?;
125 Ok(hook)
126 }
127
128 pub fn install(&mut self) -> Result<()> {
130 if self.active {
131 return Ok(());
132 }
133
134 write_iat_entry(self.iat_entry, self.detour)?;
135 self.active = true;
136
137 Ok(())
138 }
139
140 pub fn uninstall(&mut self) -> Result<()> {
142 if !self.active {
143 return Ok(());
144 }
145
146 write_iat_entry(self.iat_entry, self.original)?;
147 self.active = false;
148
149 Ok(())
150 }
151
152 pub fn is_active(&self) -> bool {
154 self.active
155 }
156
157 pub fn original(&self) -> usize {
159 self.original
160 }
161
162 pub fn detour(&self) -> usize {
164 self.detour
165 }
166
167 pub fn iat_entry(&self) -> usize {
169 self.iat_entry
170 }
171
172 pub fn set_auto_restore(&mut self, restore: bool) {
174 self.auto_restore = restore;
175 }
176
177 pub fn leak(mut self) {
179 self.auto_restore = false;
180 core::mem::forget(self);
181 }
182
183 pub fn restore(mut self) -> Result<()> {
185 self.uninstall()?;
186 self.auto_restore = false;
187 Ok(())
188 }
189}
190
191impl Drop for IatHook {
192 fn drop(&mut self) {
193 if self.auto_restore && self.active {
194 let _ = self.uninstall();
195 }
196 }
197}
198
199unsafe impl Send for IatHook {}
201unsafe impl Sync for IatHook {}
202
203pub type IatHookGuard = IatHook;
205
206pub fn find_iat_entry(
208 module: &Module,
209 import_dll: &str,
210 function_name: &str,
211) -> Result<IatEntry> {
212 let entries = enumerate_iat_entries(module)?;
213 let import_dll_lower = import_dll.to_lowercase();
214 let function_name_lower = function_name.to_lowercase();
215
216 for entry in entries {
217 let dll_matches = entry.dll_name.to_lowercase() == import_dll_lower
218 || entry.dll_name.to_lowercase().trim_end_matches(".dll")
219 == import_dll_lower.trim_end_matches(".dll");
220
221 if dll_matches {
222 if let Some(ref name) = entry.function_name {
223 if name.to_lowercase() == function_name_lower {
224 return Ok(entry);
225 }
226 }
227 }
228 }
229
230 Err(WraithError::ModuleNotFound {
231 name: format!("IAT entry for {}!{}", import_dll, function_name),
232 })
233}
234
235pub fn enumerate_iat_entries(module: &Module) -> Result<Vec<IatEntry>> {
237 let nt = module.nt_headers()?;
238 let import_dir = nt
239 .data_directory(DataDirectoryType::Import.index())
240 .ok_or_else(|| WraithError::InvalidPeFormat {
241 reason: "no import directory".into(),
242 })?;
243
244 if !import_dir.is_present() {
245 return Ok(Vec::new());
246 }
247
248 let base = module.base();
249 let mut entries = Vec::new();
250
251 let mut desc_va = base + import_dir.virtual_address as usize;
253 loop {
254 let desc = unsafe { &*(desc_va as *const ImportDescriptor) };
256
257 if desc.is_null() {
258 break;
259 }
260
261 let dll_name_va = base + desc.name as usize;
263 let dll_name = read_cstring(dll_name_va, 256)?;
264
265 let iat_va = base + desc.first_thunk as usize;
267 let int_va = if desc.original_first_thunk != 0 {
268 base + desc.original_first_thunk as usize
269 } else {
270 iat_va };
272
273 let mut thunk_idx = 0usize;
275 loop {
276 let thunk_size = core::mem::size_of::<ThunkData>();
277 let iat_entry_addr = iat_va + thunk_idx * thunk_size;
278 let int_entry_addr = int_va + thunk_idx * thunk_size;
279
280 let iat_thunk = unsafe { *(iat_entry_addr as *const usize) };
282 if iat_thunk == 0 {
283 break;
284 }
285
286 let int_thunk = unsafe { *(int_entry_addr as *const usize) };
287
288 let (function_name, ordinal) = if is_ordinal_import(int_thunk) {
289 (None, Some(get_ordinal(int_thunk)))
290 } else {
291 let hint_name_va = base + (int_thunk & !IMAGE_ORDINAL_FLAG as usize);
293 let hint_name = unsafe { &*(hint_name_va as *const ImportByName) };
295 let name_ptr = hint_name.name.as_ptr();
296 let name = read_cstring(name_ptr as usize, 256).ok();
297 (name, None)
298 };
299
300 entries.push(IatEntry {
301 entry_address: iat_entry_addr,
302 current_value: iat_thunk,
303 function_name,
304 ordinal,
305 dll_name: dll_name.clone(),
306 });
307
308 thunk_idx += 1;
309 }
310
311 desc_va += core::mem::size_of::<ImportDescriptor>();
312 }
313
314 Ok(entries)
315}
316
317pub fn hook_import(
319 import_dll: &str,
320 function_name: &str,
321 detour: usize,
322) -> Result<IatHook> {
323 let peb = Peb::current()?;
324 let query = ModuleQuery::new(&peb);
325 let current = query.current_module()?;
326
327 IatHook::new_in_module(¤t, import_dll, function_name, detour)
328}
329
330pub fn hook_import_all(
332 import_dll: &str,
333 function_name: &str,
334 detour: usize,
335) -> Result<Vec<IatHook>> {
336 let peb = Peb::current()?;
337 let mut hooks = Vec::new();
338
339 for module in crate::navigation::ModuleIterator::new(&peb, crate::navigation::ModuleListType::InLoadOrder)? {
340 if let Ok(hook) = IatHook::new_in_module(&module, import_dll, function_name, detour) {
341 hooks.push(hook);
342 }
343 }
344
345 if hooks.is_empty() {
346 Err(WraithError::ModuleNotFound {
347 name: format!("IAT entry for {}!{} in any module", import_dll, function_name),
348 })
349 } else {
350 Ok(hooks)
351 }
352}
353
354fn write_iat_entry(entry: usize, value: usize) -> Result<()> {
356 let _guard = ProtectionGuard::new(entry, core::mem::size_of::<usize>(), PAGE_READWRITE)?;
357
358 unsafe {
360 *(entry as *mut usize) = value;
361 }
362
363 Ok(())
364}
365
366fn read_cstring(addr: usize, max_len: usize) -> Result<String> {
368 let mut bytes = Vec::new();
369
370 for i in 0..max_len {
371 let byte = unsafe { *((addr + i) as *const u8) };
373 if byte == 0 {
374 break;
375 }
376 bytes.push(byte);
377 }
378
379 String::from_utf8(bytes).map_err(|_| WraithError::InvalidPeFormat {
380 reason: "invalid string encoding".into(),
381 })
382}
383
384#[cfg(target_arch = "x86_64")]
386fn is_ordinal_import(thunk: usize) -> bool {
387 (thunk as u64 & IMAGE_ORDINAL_FLAG) != 0
388}
389
390#[cfg(target_arch = "x86")]
391fn is_ordinal_import(thunk: usize) -> bool {
392 (thunk as u32 & IMAGE_ORDINAL_FLAG) != 0
393}
394
395fn get_ordinal(thunk: usize) -> u16 {
397 (thunk & 0xFFFF) as u16
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_enumerate_iat() {
406 let peb = Peb::current().expect("should get PEB");
407 let query = ModuleQuery::new(&peb);
408 let current = query.current_module().expect("should get current module");
409
410 let entries = enumerate_iat_entries(¤t).expect("should enumerate IAT");
411 assert!(!entries.is_empty(), "should have imports");
412 }
413
414 #[test]
415 fn test_find_kernel32_import() {
416 let peb = Peb::current().expect("should get PEB");
417 let query = ModuleQuery::new(&peb);
418 let current = query.current_module().expect("should get current module");
419
420 let entries = enumerate_iat_entries(¤t).expect("should enumerate IAT");
422 let has_kernel32 = entries.iter().any(|e|
423 e.dll_name.to_lowercase().contains("kernel32")
424 );
425 assert!(has_kernel32, "should import from kernel32");
426 }
427}