wraith/manipulation/hooks/
unhook.rs1#[cfg(all(not(feature = "std"), feature = "alloc"))]
7use alloc::{format, string::String, vec::Vec};
8
9#[cfg(feature = "std")]
10use std::{format, string::String, vec::Vec};
11
12use super::detector::{HookDetector, HookInfo};
13use crate::error::{Result, WraithError};
14use crate::manipulation::manual_map::ParsedPe;
15use crate::navigation::Module;
16use crate::util::memory::ProtectionGuard;
17
18const PAGE_EXECUTE_READWRITE: u32 = 0x40;
20
21#[derive(Debug)]
23pub struct UnhookResult {
24 pub unhooked_count: usize,
26 pub unhooked_functions: Vec<String>,
28 pub failed_functions: Vec<(String, String)>,
30}
31
32impl UnhookResult {
33 pub fn all_successful(&self) -> bool {
35 self.failed_functions.is_empty()
36 }
37
38 pub fn total(&self) -> usize {
40 self.unhooked_count + self.failed_functions.len()
41 }
42}
43
44pub struct Unhooker<'a> {
46 module: &'a Module<'a>,
47 clean_copy: Vec<u8>,
48 parsed_pe: ParsedPe,
49}
50
51impl<'a> Unhooker<'a> {
52 #[cfg(feature = "std")]
54 pub fn new(module: &'a Module<'a>) -> Result<Self> {
55 let path = module.full_path();
56 let clean_copy = std::fs::read(&path).map_err(|_| WraithError::CleanCopyUnavailable)?;
57
58 let parsed_pe = ParsedPe::parse(&clean_copy)?;
59
60 Ok(Self {
61 module,
62 clean_copy,
63 parsed_pe,
64 })
65 }
66
67 #[cfg(not(feature = "std"))]
69 pub fn new(_module: &'a Module<'a>) -> Result<Self> {
70 Err(WraithError::CleanCopyUnavailable)
71 }
72
73 pub fn with_clean_copy(module: &'a Module<'a>, clean_copy: Vec<u8>) -> Result<Self> {
75 let parsed_pe = ParsedPe::parse(&clean_copy)?;
76
77 Ok(Self {
78 module,
79 clean_copy,
80 parsed_pe,
81 })
82 }
83
84 pub fn unhook_function(&self, hook: &HookInfo) -> Result<()> {
86 if hook.original_bytes.is_empty() {
87 return Err(WraithError::UnhookFailed {
88 function: hook.function_name.clone(),
89 reason: "no original bytes available".into(),
90 });
91 }
92
93 let addr = hook.function_address;
94 let size = hook.original_bytes.len();
95
96 let _guard = ProtectionGuard::new(addr, size, PAGE_EXECUTE_READWRITE)?;
98
99 unsafe {
102 core::ptr::copy_nonoverlapping(hook.original_bytes.as_ptr(), addr as *mut u8, size);
103 }
104
105 Ok(())
106 }
107
108 pub fn unhook_all(&self) -> Result<UnhookResult> {
110 let detector = HookDetector::with_clean_copy(self.module, self.clean_copy.clone());
111 let hooks = detector.scan_exports()?;
112
113 let mut result = UnhookResult {
114 unhooked_count: 0,
115 unhooked_functions: Vec::new(),
116 failed_functions: Vec::new(),
117 };
118
119 for hook in hooks {
120 match self.unhook_function(&hook) {
121 Ok(()) => {
122 result.unhooked_count += 1;
123 result.unhooked_functions.push(hook.function_name);
124 }
125 Err(e) => {
126 result
127 .failed_functions
128 .push((hook.function_name, e.to_string()));
129 }
130 }
131 }
132
133 Ok(result)
134 }
135
136 pub fn unhook_text_section(&self) -> Result<()> {
138 let text_section = self
139 .parsed_pe
140 .sections()
141 .iter()
142 .find(|s| s.name_str() == ".text")
143 .ok_or_else(|| WraithError::UnhookFailed {
144 function: ".text".into(),
145 reason: "no .text section found".into(),
146 })?;
147
148 let text_rva = text_section.virtual_address as usize;
149 let text_size = text_section.virtual_size as usize;
150 let text_file_offset = text_section.pointer_to_raw_data as usize;
151 let text_raw_size = text_section.size_of_raw_data as usize;
152
153 let target_addr = self.module.base() + text_rva;
154
155 let _guard = ProtectionGuard::new(target_addr, text_size, PAGE_EXECUTE_READWRITE)?;
157
158 let copy_size = text_raw_size.min(text_size);
160 if text_file_offset + copy_size > self.clean_copy.len() {
161 return Err(WraithError::UnhookFailed {
162 function: ".text".into(),
163 reason: "clean copy too small".into(),
164 });
165 }
166
167 let clean_text = &self.clean_copy[text_file_offset..text_file_offset + copy_size];
168
169 unsafe {
172 core::ptr::copy_nonoverlapping(clean_text.as_ptr(), target_addr as *mut u8, copy_size);
173 }
174
175 Ok(())
176 }
177
178 pub fn unhook_by_name(&self, function_name: &str) -> Result<()> {
180 let addr = self.module.get_export(function_name)?;
181
182 let rva = self.module.va_to_rva(addr).ok_or_else(|| WraithError::UnhookFailed {
184 function: function_name.into(),
185 reason: "address not in module".into(),
186 })?;
187
188 let original = self.get_original_bytes(rva as usize, 32)?;
190
191 let _guard = ProtectionGuard::new(addr, original.len(), PAGE_EXECUTE_READWRITE)?;
193
194 unsafe {
196 core::ptr::copy_nonoverlapping(original.as_ptr(), addr as *mut u8, original.len());
197 }
198
199 Ok(())
200 }
201
202 pub fn unhook_by_names(&self, names: &[&str]) -> UnhookResult {
204 let mut result = UnhookResult {
205 unhooked_count: 0,
206 unhooked_functions: Vec::new(),
207 failed_functions: Vec::new(),
208 };
209
210 for &name in names {
211 match self.unhook_by_name(name) {
212 Ok(()) => {
213 result.unhooked_count += 1;
214 result.unhooked_functions.push(name.to_string());
215 }
216 Err(e) => {
217 result.failed_functions.push((name.to_string(), e.to_string()));
218 }
219 }
220 }
221
222 result
223 }
224
225 fn get_original_bytes(&self, rva: usize, len: usize) -> Result<Vec<u8>> {
227 for section in self.parsed_pe.sections() {
228 let sec_rva = section.virtual_address as usize;
229 let sec_size = section.virtual_size as usize;
230
231 if rva >= sec_rva && rva < sec_rva + sec_size {
232 let offset_in_section = rva - sec_rva;
233 let file_offset = section.pointer_to_raw_data as usize + offset_in_section;
234
235 if file_offset + len <= self.clean_copy.len() {
236 return Ok(self.clean_copy[file_offset..file_offset + len].to_vec());
237 }
238 }
239 }
240
241 Err(WraithError::UnhookFailed {
242 function: format!("RVA {rva:#x}"),
243 reason: "RVA not in any section".into(),
244 })
245 }
246}
247
248pub fn restore_function(module: &Module, function_name: &str) -> Result<()> {
250 let unhooker = Unhooker::new(module)?;
251 unhooker.unhook_by_name(function_name)
252}
253
254pub fn restore_text_section(module: &Module) -> Result<()> {
256 let unhooker = Unhooker::new(module)?;
257 unhooker.unhook_text_section()
258}