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 fn has_breakpoint(&self, addr: usize) -> bool {
62 self.breakpoints.contains_key(&addr)
63 }
64
65 pub unsafe fn dispatch(&mut self, thread: HANDLE) -> CradleResult<DispatchResult> {
70 unsafe {
71 let mut ctx = get_thread_context(thread).map_err(|e| {
72 CradleError::InvalidValue(format!("get thread context failed: {e}"))
73 })?;
74 let bp_addr = (ctx.Rip - 1) as usize;
75 let bp = match self.breakpoints.get(&bp_addr) {
76 Some(bp) => bp,
77 None => return Ok(DispatchResult::Skip),
78 };
79 let mut hook_ctx = HookContext {
80 registers: &mut ctx,
81 process: self.process,
82 ret_value: 0,
83 export_name: &bp.export_name,
84 module_name: &bp.module_name,
85 #[cfg(feature = "unstable")]
86 return_hook: None,
87 };
88 let mut action = HookAction::Continue;
89 for handler in &bp.handlers {
90 match handler(&mut hook_ctx) {
91 Ok(HookAction::Continue) => {}
92 Ok(HookAction::Modify) => action = HookAction::Modify,
93 Ok(block @ HookAction::Block(_)) => {
94 action = block;
95 break;
96 }
97 Err(_) => {}
98 }
99 }
100 let ret = hook_ctx.ret_value;
101 match action {
102 HookAction::Continue | HookAction::Modify => {
103 write_process_memory(self.process, bp_addr, &[bp.original_byte]).map_err(
104 |e| {
105 CradleError::InvalidValue(format!(
106 "{}!{} restore byte at {bp_addr:#x} failed: {e}",
107 bp.module_name, bp.export_name
108 ))
109 },
110 )?;
111 ctx.Rip = bp_addr as u64;
112 ctx.EFlags |= 0x100;
113 set_thread_context(thread, &ctx).map_err(|e| {
114 CradleError::InvalidValue(format!(
115 "{}!{} set context failed at {bp_addr:#x}: {e}",
116 bp.module_name, bp.export_name
117 ))
118 })?;
119 self.pending_restore = Some(PendingRestore {
120 addr: bp_addr,
121 thread,
122 });
123 }
124 HookAction::Block(u) => {
125 let ret_addr: u64 = read_remote_or(self.process, ctx.Rsp as usize, u);
126 ctx.Rip = ret_addr;
127 ctx.Rsp += 8;
128 ctx.Rax = ret;
129 set_thread_context(thread, &ctx).map_err(|e| {
130 CradleError::InvalidValue(format!(
131 "{}!{} block set context failed at {bp_addr:#x}: {e}",
132 bp.module_name, bp.export_name
133 ))
134 })?;
135 }
136 }
137 Ok(DispatchResult::Handled)
138 }
139 }
140
141 pub unsafe fn dispatch_single_step(&mut self, thread: HANDLE) -> CradleResult<DispatchResult> {
146 match self.pending_restore.take() {
147 Some(restore) if restore.thread == thread => {
148 if self.breakpoints.contains_key(&restore.addr) {
149 unsafe {
150 write_process_memory(self.process, restore.addr, &[0xCC])?;
151 }
152 }
153 Ok(DispatchResult::Handled)
154 }
155 Some(restore) => {
156 self.pending_restore = Some(restore);
157 Ok(DispatchResult::Skip)
158 }
159 None => Ok(DispatchResult::Skip),
160 }
161 }
162
163 pub unsafe fn hook_export(
169 &mut self,
170 module: &str,
171 export: &str,
172 handler: HookHandler,
173 ) -> CradleResult {
174 if self.module_cache.is_empty() {
175 return Err(CradleError::ModuleNotFound(
176 "module cache is empty".to_owned(),
177 ));
178 }
179 let module_lower = normalize_name(module);
180 let base = self
181 .module_cache
182 .get(&module_lower)
183 .copied()
184 .ok_or_else(|| CradleError::ModuleNotFound(module.to_string()))?;
185 let addr = resolve_export(self.process, base, export)?;
186 if let Some(bp) = self.breakpoints.get_mut(&addr) {
187 bp.push_handler(handler);
188 return Ok(());
189 }
190 let mut orig = [0u8; 1];
191 unsafe {
192 read_single_byte(self.process, addr, &mut orig)?;
193 write_single_byte(self.process, addr, &[0xCC])?;
194 }
195 self.breakpoints.insert(
196 addr,
197 Breakpoint::new(orig[0], handler, module_lower, export.to_string()),
198 );
199
200 Ok(())
201 }
202 pub unsafe fn unhook(&mut self, addr: usize) -> CradleResult {
207 if let Some(bp) = self.breakpoints.remove(&addr) {
208 unsafe {
209 write_process_memory(self.process, addr, &[bp.original_byte])?;
210 }
211 }
212 Ok(())
213 }
214
215 pub unsafe fn unhook_all(&mut self) -> CradleResult {
220 let addrs: Vec<usize> = self.breakpoints.keys().copied().collect();
221 for addr in addrs {
222 unsafe {
223 self.unhook(addr)?;
224 }
225 }
226 Ok(())
227 }
228
229 pub fn hooked_count(&self) -> usize {
231 self.breakpoints.len()
232 }
233}
234unsafe impl Send for HookEngine {}