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