1use crate::breakpoints::{Breakpoint, BreakpointMap, ModuleBaseCache};
2use crate::defs::{HookAction, HookContext, HookHandler};
3use crate::utils::{
4 get_thread_context, normalize_name, read_remote_or, read_single_byte, resolve_export,
5 set_thread_context, write_process_memory, write_single_byte,
6};
7use cradle_shared::{CradleError, CradleResult};
8use windows_sys::Win32::Foundation::HANDLE;
9
10pub enum DispatchResult {
12 Handled,
14 Skip,
16}
17
18pub struct HookEngine {
21 breakpoints: BreakpointMap,
22 module_cache: ModuleBaseCache,
23 process: HANDLE,
24 pending_restore: Option<PendingRestore>,
25}
26
27struct PendingRestore {
28 addr: usize,
29 thread: HANDLE,
30}
31
32impl HookEngine {
33 pub fn empty() -> HookEngine {
35 Self {
36 breakpoints: BreakpointMap::new(),
37 module_cache: ModuleBaseCache::new(),
38 process: HANDLE::default(),
39 pending_restore: None,
40 }
41 }
42}
43
44impl HookEngine {
45 pub fn new(process: HANDLE) -> Self {
47 Self {
48 breakpoints: BreakpointMap::new(),
49 module_cache: ModuleBaseCache::new(),
50 process,
51 pending_restore: None,
52 }
53 }
54
55 pub fn register_module(&mut self, name: &str, base: usize) {
57 self.module_cache.insert(normalize_name(name), base);
58 }
59
60 pub unsafe fn dispatch(&mut self, thread: HANDLE) -> CradleResult<DispatchResult> {
65 unsafe {
66 let mut ctx = get_thread_context(thread)?;
67 let bp_addr = (ctx.Rip - 1) as usize;
68 let bp = match self.breakpoints.get(&bp_addr) {
69 Some(bp) => bp,
70 None => return Ok(DispatchResult::Skip),
71 };
72 let mut hook_ctx = HookContext {
73 registers: &mut ctx,
74 process: self.process,
75 ret_value: 0,
76 export_name: &bp.export_name,
77 module_name: &bp.module_name,
78 #[cfg(feature = "unstable")]
79 return_hook: None,
80 };
81 let mut action = HookAction::Continue;
82 for handler in &bp.handlers {
83 match handler(&mut hook_ctx) {
84 Ok(HookAction::Continue) => {}
85 Ok(HookAction::Modify) => action = HookAction::Modify,
86 Ok(block @ HookAction::Block(_)) => {
87 action = block;
88 break;
89 }
90 Err(_) => {}
91 }
92 }
93 let ret = hook_ctx.ret_value;
94 match action {
95 HookAction::Continue | HookAction::Modify => {
96 write_process_memory(self.process, bp_addr, &[bp.original_byte])?;
97 ctx.Rip = bp_addr as u64;
98 ctx.EFlags |= 0x100;
99 set_thread_context(thread, &ctx)?;
100 self.pending_restore = Some(PendingRestore {
101 addr: bp_addr,
102 thread,
103 });
104 }
105 HookAction::Block(u) => {
106 let ret_addr: u64 = read_remote_or(self.process, ctx.Rsp as usize, u);
107 ctx.Rip = ret_addr;
108 ctx.Rsp += 8;
109 ctx.Rax = ret;
110 set_thread_context(thread, &ctx)?;
111 }
112 }
113 Ok(DispatchResult::Handled)
114 }
115 }
116
117 pub unsafe fn dispatch_single_step(&mut self, thread: HANDLE) -> CradleResult<DispatchResult> {
122 match self.pending_restore.take() {
123 Some(restore) if restore.thread == thread => {
124 if self.breakpoints.contains_key(&restore.addr) {
125 unsafe {
126 write_process_memory(self.process, restore.addr, &[0xCC])?;
127 }
128 }
129 Ok(DispatchResult::Handled)
130 }
131 Some(restore) => {
132 self.pending_restore = Some(restore);
133 Ok(DispatchResult::Skip)
134 }
135 None => Ok(DispatchResult::Skip),
136 }
137 }
138
139 pub unsafe fn hook_export(
145 &mut self,
146 module: &str,
147 export: &str,
148 handler: HookHandler,
149 ) -> CradleResult {
150 if self.module_cache.is_empty() {
151 return Err(CradleError::ModuleNotFound(
152 "module cache is empty".to_owned(),
153 ));
154 }
155 let module_lower = normalize_name(module);
156 let base = self
157 .module_cache
158 .get(&module_lower)
159 .copied()
160 .ok_or_else(|| CradleError::ModuleNotFound(module.to_string()))?;
161 let addr = resolve_export(self.process, base, export)?;
162 if let Some(bp) = self.breakpoints.get_mut(&addr) {
163 bp.push_handler(handler);
164 return Ok(());
165 }
166 let mut orig = [0u8; 1];
167 unsafe {
168 read_single_byte(self.process, addr, &mut orig)?;
169 write_single_byte(self.process, addr, &[0xCC])?;
170 }
171 self.breakpoints.insert(
172 addr,
173 Breakpoint::new(orig[0], handler, module_lower, export.to_string()),
174 );
175
176 Ok(())
177 }
178 pub unsafe fn unhook(&mut self, addr: usize) -> CradleResult {
183 if let Some(bp) = self.breakpoints.remove(&addr) {
184 unsafe {
185 write_process_memory(self.process, addr, &[bp.original_byte])?;
186 }
187 }
188 Ok(())
189 }
190
191 pub unsafe fn unhook_all(&mut self) -> CradleResult {
196 let addrs: Vec<usize> = self.breakpoints.keys().copied().collect();
197 for addr in addrs {
198 unsafe {
199 self.unhook(addr)?;
200 }
201 }
202 Ok(())
203 }
204
205 pub fn hooked_count(&self) -> usize {
207 self.breakpoints.len()
208 }
209}
210unsafe impl Send for HookEngine {}