1use proc_macro::TokenStream;
7use quote::quote;
8use syn::{FnArg, ItemFn, Lit, Token, parse_macro_input, punctuated::Punctuated, spanned::Spanned};
9
10mod interpose;
11
12const SUPPORTED_HOOK_TYPES: &str = "retn (FunctionHook), jmp_back (InlineHook), jmp_to_ret (DynamicRedirect) and jmp_to_addr (Static Redirect)";
13
14impl TargetKind {}
15
16#[proc_macro_attribute]
55pub fn hook(attr: TokenStream, item: TokenStream) -> TokenStream {
56 let args = parse_macro_input!(attr as HookArgs);
57 let mut func = parse_macro_input!(item as ItemFn);
58
59 let func_name = func.sig.ident.clone();
60 let func_vis = &func.vis;
61 let func_attrs = &func.attrs;
62 let is_void = match &func.sig.output {
63 syn::ReturnType::Default => true,
64 syn::ReturnType::Type(_, ty) => {
65 if let syn::Type::Tuple(t) = &**ty {
66 t.elems.is_empty()
67 } else {
68 false
69 }
70 }
71 };
72
73 let target_expr = args.target.emit();
74 let handler_name = syn::Ident::new(&format!("__hook_{}", func_name), func.sig.span());
75
76 let hook_name_str = func_name.to_string();
77
78 let result = match args.kind {
79 HookKind::Retn => {
80 let func_args = func.sig.inputs.clone();
81 let mut arg_decls = Vec::new();
82 let mut arg_names = Vec::new();
83 let mut arg_types = Vec::new();
84
85 for (i, arg) in func_args.iter().enumerate() {
86 match arg {
87 FnArg::Receiver(_) => continue,
88 FnArg::Typed(typed) => {
89 let name = &typed.pat;
90 let ty = &typed.ty;
91 arg_names.push(name);
92 arg_types.push(ty);
93
94 let extraction = match i {
95 0 => quote! { let #name = (*reg).rdi as #ty; },
96 1 => quote! { let #name = (*reg).rsi as #ty; },
97 2 => quote! { let #name = (*reg).rdx as #ty; },
98 3 => quote! { let #name = (*reg).rcx as #ty; },
99 4 => quote! { let #name = (*reg).r8 as #ty; },
100 5 => quote! { let #name = (*reg).r9 as #ty; },
101 _ => {
102 let stack_index = i - 6;
103 quote! { let #name = unsafe { (*reg).get_stack(#stack_index) } as #ty; }
104 }
105 };
106 arg_decls.push(extraction);
107 }
108 }
109 }
110
111 let return_type = match &func.sig.output {
112 syn::ReturnType::Default => quote! { () },
113 syn::ReturnType::Type(_, ty) => quote! { #ty },
114 };
115
116 func.sig
117 .inputs
118 .push(syn::parse_quote! { __orig_ptr: usize });
119 let body = &func.block;
120 let sig = &func.sig;
121
122 let result = if arg_names.is_empty() {
123 quote! {
124 let result = #func_name(orig_func_ptr);
125 }
126 } else {
127 quote! {
128 let result = #func_name(#(#arg_names),*, orig_func_ptr);
129 }
130 };
131
132 let return_val = if is_void {
133 quote! { 0 }
134 } else {
135 quote! { result as usize }
136 };
137 let guard_name = format!("__guard_{}", hook_name_str);
138
139 let m_orig_call = if is_void {
140 quote! {
141 orig(#(#arg_names),*);
142 return 0;
143 }
144 } else {
145 quote! {
146 return orig(#(#arg_names),*) as usize;
147 }
148 };
149 let hook_guard_entry = if args.guard {
150 quote! {
151 if !prehook::hook::HookGuard::try_enter(#guard_name) {
152 let orig: unsafe extern "C" fn(#(#arg_types),*) -> #return_type = std::mem::transmute(orig_func_ptr);
153 #m_orig_call
154 }
155 }
156 } else {
157 quote! {}
158 };
159
160 let hook_guard_exit = if args.guard {
161 quote! {
162 prehook::hook::HookGuard::exit(#guard_name);
163 }
164 } else {
165 quote! {}
166 };
167
168 quote! {
169 #(#func_attrs)*
170 #func_vis #sig {
171 #[allow(unused_macros)]
172 macro_rules! call_original {
173 ($($arg:expr),*) => {
174 unsafe {
175 let orig: unsafe extern "C" fn(#(#arg_types),*) -> #return_type = std::mem::transmute(__orig_ptr);
176 orig($($arg),*)
177 }
178 }
179 }
180 #body
181 }
182
183 #[ctor::ctor]
184 fn #handler_name() {
185 use prehook::hook::{HOOK_REGISTRY, HookInstance};
186 use prehook::hook::RetnRoutine;
187
188 let handler: RetnRoutine = {
189 unsafe extern "win64" fn __hook_handler(
190 reg: *mut prehook::hook::Registers,
191 orig_func_ptr: usize,
192 _stack: usize,
193 ) -> usize {
194 #( #arg_decls )*
195 #hook_guard_entry
196
197 #result
198
199 #hook_guard_exit
200 #return_val
201 }
202 __hook_handler
203 };
204
205 let hook = HookInstance::new_retn(
206 #target_expr,
207 stringify!(#func_name).to_string(),
208 handler,
209 );
210 HOOK_REGISTRY.register(hook);
211 }
212 }
213 }
214 HookKind::JmpBack => {
215 quote! {
216 #func
217
218 #[ctor::ctor]
219 fn #handler_name() {
220 use prehook::hook::{HOOK_REGISTRY, HookInstance};
221 use prehook::hook::JmpBackRoutine;
222
223 unsafe extern "win64" fn __hook_handler(
224 reg: *mut prehook::hook::Registers,
225 _user_data: usize,
226 ) {
227 #func_name(&mut *reg);
228 }
229
230 let hook = HookInstance::new_jmpback(
231 #target_expr,
232 stringify!(#func_name).to_string(),
233 __hook_handler,
234 );
235 HOOK_REGISTRY.register(hook);
236 }
237 }
238 }
239 HookKind::JmpToRet => {
240 quote! {
241 #func
242
243 #[ctor::ctor]
244 fn #handler_name() {
245 use prehook::hook::{HOOK_REGISTRY, HookInstance};
246 use prehook::hook::JmpToRetRoutine;
247
248 static HOOK_NAME: &str = #hook_name_str;
249
250 unsafe extern "win64" fn __hook_handler(
251 reg: *mut prehook::hook::Registers,
252 _orig_func_ptr: usize,
253 _user_data: usize,
254 ) -> usize {
255 let base = prehook::init::MAIN_EXE_BASE.load(std::sync::atomic::Ordering::SeqCst);
256 let hook_point = #target_expr.resolve(base, 0).expect("Failed to resolve hook point address");
257 let result: prehook::hook::Target = #func_name(&mut *reg, _orig_func_ptr);
258
259 result.resolve(base, hook_point).expect("Failed to resolve return address")
260 }
261
262 let hook = HookInstance::new_jmptoret(
263 #target_expr,
264 stringify!(#func_name).to_string(),
265 __hook_handler,
266 );
267 HOOK_REGISTRY.register(hook);
268 }
269 }
270 }
271 HookKind::JmpToAddr => {
272 let target_expr = args.target.emit();
273 let dest_expr = args
274 .dest
275 .expect("dest attribute is required for jmp_to_addr hook")
276 .emit();
277 quote! {
278 #func
279
280 #[ctor::ctor]
281 fn #handler_name() {
282 use prehook::hook::{HOOK_REGISTRY, HookInstance};
283 use prehook::hook::JmpToAddrRoutine;
284
285 unsafe extern "win64" fn __hook_handler(
286 reg: *mut prehook::hook::Registers,
287 _orig_func_ptr: usize,
288 _user_data: usize,
289 ) {
290 #func_name(&mut *reg);
291 }
292
293 let hook = HookInstance::new_jmptoaddr(
294 #target_expr,
295 #dest_expr,
296 stringify!(#func_name).to_string(),
297 __hook_handler,
298 );
299 HOOK_REGISTRY.register(hook);
300 }
301 }
302 }
303 };
304
305 result.into()
306}
307
308#[proc_macro_attribute]
328pub fn interpose(attr: TokenStream, item: TokenStream) -> TokenStream {
329 interpose::interpose_impl(attr, item)
330}
331
332#[derive(Clone, Debug)]
333enum TargetKind {
334 Raw(usize),
335 Symbol(String),
336 SymbolOffset(String, isize),
337 Relative(isize),
338}
339
340impl TargetKind {
341 fn parse_numeric(s: &str) -> isize {
342 let s = s.trim();
343 let (sign, s) = if let Some(stripped) = s.strip_prefix('+') {
344 (1, stripped)
345 } else if let Some(stripped) = s.strip_prefix('-') {
346 (-1, stripped)
347 } else {
348 (1, s)
349 };
350
351 let val = if let Some(hex_val) = s.strip_prefix("0x") {
352 usize::from_str_radix(hex_val, 16).unwrap_or(0)
353 } else {
354 s.parse::<usize>().unwrap_or(0)
355 };
356
357 (val as isize) * sign
358 }
359 fn emit(&self) -> proc_macro2::TokenStream {
360 match self {
361 TargetKind::Raw(v) => quote! { prehook::hook::Target::Raw(#v) },
362 TargetKind::Symbol(s) => quote! { prehook::hook::Target::Symbol(#s.to_string()) },
363 TargetKind::SymbolOffset(s, off) => {
364 quote! { prehook::hook::Target::SymbolOffset(#s.to_string(), #off) }
365 }
366 TargetKind::Relative(off) => quote! { prehook::hook::Target::Relative(#off) },
367 }
368 }
369}
370
371impl syn::parse::Parse for TargetKind {
372 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
373 let lit: Lit = input.parse()?;
374
375 match &lit {
376 Lit::Int(i) => Ok(TargetKind::Raw(i.base10_parse()?)),
378
379 Lit::Str(s) => {
381 let val = s.value();
382 let input_str = val.trim();
383
384 if input_str.is_empty() {
385 return Err(syn::Error::new(lit.span(), "Target string cannot be empty"));
386 }
387
388 if (input_str.starts_with('+') || input_str.starts_with('-'))
391 && !input_str.starts_with("0x")
392 && !input_str.starts_with("-0x")
393 {
394 return Ok(TargetKind::Relative(Self::parse_numeric(input_str)));
395 }
396
397 if let Some(v) = input_str.strip_prefix("0x")
399 && let Ok(val) = usize::from_str_radix(v, 16)
400 {
401 return Ok(TargetKind::Raw(val));
402 }
403
404 if let Some((sym, off)) = input_str.rsplit_once('+') {
406 return Ok(TargetKind::SymbolOffset(
407 sym.to_string(),
408 Self::parse_numeric(off),
409 ));
410 }
411
412 if let Some((sym, off)) = input_str.rsplit_once('-') {
415 let off_trimmed = off.trim();
416 if off_trimmed.starts_with("0x")
417 || off_trimmed.chars().all(|c| c.is_ascii_digit())
418 {
419 return Ok(TargetKind::SymbolOffset(
420 sym.to_string(),
421 -Self::parse_numeric(off_trimmed),
422 ));
423 }
424 }
425
426 Ok(TargetKind::Symbol(input_str.to_string()))
428 }
429 _ => Err(syn::Error::new(
430 lit.span(),
431 "Expected string or integer for hook target",
432 )),
433 }
434 }
435}
436
437enum HookArg {
438 Kind(HookKind),
439 Target(TargetKind),
440 Dest(TargetKind),
441 Offset(usize),
442 Guard(bool),
443}
444
445impl syn::parse::Parse for HookArg {
446 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
447 let ident: syn::Ident = input.parse()?;
448 let _: syn::Token![=] = input.parse()?;
449
450 match ident.to_string().as_str() {
451 "kind" => Ok(Self::Kind(input.parse()?)),
452 "symbol" | "target" => Ok(Self::Target(input.parse()?)),
453 "dest" => Ok(Self::Dest(input.parse()?)),
454 "offset" => Ok(Self::Offset(input.parse::<syn::LitInt>()?.base10_parse()?)),
455 "guard" => Ok(Self::Guard(input.parse::<syn::LitBool>()?.value())),
456 _ => Err(syn::Error::new(
457 ident.span(),
458 format!("Unknown argument: {}", ident),
459 )),
460 }
461 }
462}
463
464#[derive(Debug, Clone, PartialEq, Eq)]
465enum HookKind {
466 Retn,
467 JmpBack,
468 JmpToRet,
469 JmpToAddr,
470}
471
472impl syn::parse::Parse for HookKind {
473 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
474 let lit: Lit = input.parse()?;
475
476 let Lit::Str(s) = &lit else {
477 return Err(syn::Error::new(
478 lit.span(),
479 "Hook kind only expects strings",
480 ));
481 };
482
483 let hook_kind_str = s.value();
484
485 Ok(match hook_kind_str.as_str() {
486 "retn" | "FunctionHook" => Self::Retn,
487 "jmp_back" | "InlineHook" => Self::JmpBack,
488 "jmp_to_ret" | "DynamicRedirect" => Self::JmpToRet,
489 "jmp_to_addr" | "StaticRedirect" => Self::JmpToAddr,
490 _ => {
491 return Err(syn::Error::new(
492 lit.span(),
493 format!("Unsupported hook type. Supported types are: {SUPPORTED_HOOK_TYPES}"),
494 ));
495 }
496 })
497 }
498}
499
500struct HookArgs {
501 target: TargetKind,
502 kind: HookKind,
503 dest: Option<TargetKind>,
504 guard: bool,
505}
506
507impl syn::parse::Parse for HookArgs {
508 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
509 let args: Punctuated<HookArg, Token![,]> =
510 input.parse_terminated(HookArg::parse, Token![,])?;
511
512 let mut kind = None;
513 let mut target = None;
514 let mut dest = None;
515 let mut offset = None;
516 let mut guard = None;
517
518 for arg in args {
520 match arg {
521 HookArg::Kind(k) => kind = Some(k),
522 HookArg::Target(t) => target = Some(t),
523 HookArg::Dest(d) => dest = Some(d),
524 HookArg::Offset(o) => offset = Some(o),
525 HookArg::Guard(g) => guard = Some(g),
526 }
527 }
528
529 if kind.is_none() {
530 return Err(syn::Error::new(
531 input.span(),
532 format!(
533 "You need to specify a hook type. Supported types are: {SUPPORTED_HOOK_TYPES}"
534 ),
535 ));
536 }
537 let kind = kind.unwrap();
538
539 let final_target = match (target, offset) {
540 (Some(TargetKind::Symbol(s)), Some(off)) => {
541 TargetKind::SymbolOffset(s, off as isize)
543 }
544 (Some(t), _) => t, (None, Some(off)) => TargetKind::Raw(off), (None, None) => {
547 return Err(syn::Error::new(
549 input.span(),
550 "No hook target provided. You must specify 'symbol', 'offset', or 'target'.",
551 ));
552 }
553 };
554
555 if let TargetKind::Relative(_) = final_target {
557 return Err(syn::Error::new(
558 input.span(),
559 "You cannot use a relative offset on a hook point. This will likely lead to segfaults. Use raw offset instead",
560 ));
561 }
562
563 if kind == HookKind::JmpToAddr && dest.is_none() {
564 return Err(syn::Error::new(
565 input.span(),
566 "The 'jmp_to_addr' hook requires a 'dest' attribute (e.g., dest = 0x1c0 or dest = '+0x10').",
567 ));
568 }
569
570 Ok(HookArgs {
571 target: final_target,
572 kind,
573 dest,
574 guard: guard.unwrap_or_default(),
575 })
576 }
577}