Skip to main content

prehook_macros/
lib.rs

1//! Procedural macros for the prehook library.
2//! 
3//! This crate provides the `#[hook]` and `#[interpose]` attribute macros, 
4//! which are the primary ways to define interceptions in the `prehook` ecosystem.
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{parse_macro_input, spanned::Spanned, FnArg, ItemFn};
9
10mod interpose;
11
12/// The primary macro for defining a hook.
13/// 
14/// This attribute macro transforms a standard Rust function into a 
15/// hook handler and automatically generates a constructor to register 
16/// the hook with the global `HOOK_REGISTRY`.
17/// 
18/// ### Parameters
19/// - `kind`: (Optional) The type of hook to apply.
20///     - `"retn"` (Default): A function-level hook. Injects `call_original!(...)`.
21///     - `"jmp_back"`: An inline hook that returns to the next instruction.
22///     - `"jmp_to_ret"`: An inline hook that jumps to a dynamic address.
23///     - `"jmp_to_addr"`: An inline hook that jumps to a fixed address.
24/// - `symbol`: (Optional) The name of the symbol to hook.
25/// - `offset`: (Optional) The address offset from the binary's base address.
26/// - `dest`: (Required for `"jmp_to_addr"`) The destination offset to jump to.
27/// 
28/// ### Examples
29/// 
30/// #### Function Hook (`retn`)
31/// ```rust
32/// #[hook(kind = "retn", symbol = "check_license")]
33/// fn my_hook(key: i32) -> i32 {
34///     let result = call_original!(key);
35///     if result == 0 { 1 } else { result }
36/// }
37/// ```
38/// 
39/// #### Inline Hook (`jmp_back`)
40/// ```rust
41/// #[hook(kind = "jmp_back", offset = 0x1234)]
42/// fn my_inline_hook(reg: &mut Registers) {
43///     println!("RAX: 0x{:x}", reg.rax);
44/// }
45/// ```
46#[proc_macro_attribute]
47pub fn hook(attr: TokenStream, item: TokenStream) -> TokenStream {
48    let args = parse_macro_input!(attr as HookArgs);
49    let mut func = parse_macro_input!(item as ItemFn);
50
51    let func_name = func.sig.ident.clone();
52    let func_vis = &func.vis;
53    let func_attrs = &func.attrs;
54
55    let hook_kind_str = args.kind.as_ref().map(|s| s.value()).unwrap_or_else(|| "retn".to_string());
56
57    let offset = args.offset.unwrap_or_else(|| syn::parse_quote! { 0 });
58    let symbol_expr = match &args.symbol {
59        Some(s) => quote! { Some(#s.to_string()) },
60        None => quote! { None },
61    };
62    let handler_name = syn::Ident::new(&format!("__hook_{}", func_name), func.sig.span());
63
64    let result = match hook_kind_str.as_str() {
65        "retn" => {
66            let func_args = func.sig.inputs.clone();
67            let mut arg_decls = Vec::new();
68            let mut arg_names = Vec::new();
69            let mut arg_types = Vec::new();
70
71            for (i, arg) in func_args.iter().enumerate() {
72                match arg {
73                    FnArg::Receiver(_) => continue,
74                    FnArg::Typed(typed) => {
75                        let name = &typed.pat;
76                        let ty = &typed.ty;
77                        arg_names.push(name);
78                        arg_types.push(ty);
79
80                        let extraction = match i {
81                            0 => quote! { let #name = (*reg).rdi as #ty; },
82                            1 => quote! { let #name = (*reg).rsi as #ty; },
83                            2 => quote! { let #name = (*reg).rdx as #ty; },
84                            3 => quote! { let #name = (*reg).rcx as #ty; },
85                            4 => quote! { let #name = (*reg).r8 as #ty; },
86                            5 => quote! { let #name = (*reg).r9 as #ty; },
87                            _ => {
88                                let stack_index = i - 6;
89                                quote! { let #name = unsafe { (*reg).get_stack(#stack_index) } as #ty; }
90                            },
91                        };
92                        arg_decls.push(extraction);
93                    }
94                }
95            }
96
97            let return_type = match &func.sig.output {
98                syn::ReturnType::Default => quote! { () },
99                syn::ReturnType::Type(_, ty) => quote! { #ty },
100            };
101
102            func.sig.inputs.push(syn::parse_quote! { __orig_ptr: usize });
103            let body = &func.block;
104            let sig = &func.sig;
105
106            quote! {
107                #(#func_attrs)*
108                #func_vis #sig {
109                    #[allow(unused_macros)]
110                    macro_rules! call_original {
111                        ($($arg:expr),*) => {
112                            unsafe {
113                                let orig: unsafe extern "win64" fn(#(#arg_types),*) -> #return_type = std::mem::transmute(__orig_ptr);
114                                orig($($arg),*)
115                            }
116                        }
117                    }
118                    #body
119                }
120
121                #[ctor::ctor]
122                fn #handler_name() {
123                    use prehook::hook::{HOOK_REGISTRY, HookInstance};
124                    use prehook::hook::RetnRoutine;
125
126                    let handler: RetnRoutine = {
127                        unsafe extern "win64" fn __hook_handler(
128                            reg: *mut prehook::hook::Registers,
129                            orig_func_ptr: usize,
130                            _stack: usize,
131                        ) -> usize {
132                            #( #arg_decls )*
133                            let result = #func_name(#(#arg_names),*, orig_func_ptr);
134                            result as usize
135                        }
136                        __hook_handler
137                    };
138
139                    let hook = HookInstance::new_retn(
140                        #offset,
141                        #symbol_expr,
142                        stringify!(#func_name).to_string(),
143                        handler,
144                    );
145                    HOOK_REGISTRY.register(hook);
146                }
147            }
148        }
149        "jmp_back" => {
150            quote! {
151                #func
152
153                #[ctor::ctor]
154                fn #handler_name() {
155                    use prehook::hook::{HOOK_REGISTRY, HookInstance};
156                    use prehook::hook::JmpBackRoutine;
157
158                    unsafe extern "win64" fn __hook_handler(
159                        reg: *mut prehook::hook::Registers,
160                        _user_data: usize,
161                    ) {
162                        #func_name(&mut *reg);
163                    }
164
165                    let hook = HookInstance::new_jmpback(
166                        #offset,
167                        #symbol_expr,
168                        stringify!(#func_name).to_string(),
169                        __hook_handler,
170                    );
171                    HOOK_REGISTRY.register(hook);
172                }
173            }
174        }
175        "jmp_to_ret" => {
176            quote! {
177                #func
178
179                #[ctor::ctor]
180                fn #handler_name() {
181                    use prehook::hook::{HOOK_REGISTRY, HookInstance};
182                    use prehook::hook::JmpToRetRoutine;
183
184                    unsafe extern "win64" fn __hook_handler(
185                        reg: *mut prehook::hook::Registers,
186                        _orig_func_ptr: usize,
187                        _user_data: usize,
188                    ) -> usize {
189                        #func_name(&mut *reg)
190                    }
191
192                    let hook = HookInstance::new_jmptoret(
193                        #offset,
194                        #symbol_expr,
195                        stringify!(#func_name).to_string(),
196                        __hook_handler,
197                    );
198                    HOOK_REGISTRY.register(hook);
199                }
200            }
201        }
202        "jmp_to_addr" => {
203            let dest = args.dest.expect("jmp_to_addr requires a dest parameter");
204            quote! {
205                #func
206
207                #[ctor::ctor]
208                fn #handler_name() {
209                    use prehook::hook::{HOOK_REGISTRY, HookInstance};
210                    use prehook::hook::JmpToAddrRoutine;
211
212                    unsafe extern "win64" fn __hook_handler(
213                        reg: *mut prehook::hook::Registers,
214                        _orig_func_ptr: usize,
215                        _user_data: usize,
216                    ) {
217                        #func_name(&mut *reg);
218                    }
219
220                    let hook = HookInstance::new_jmptoaddr(
221                        #offset,
222                        #symbol_expr,
223                        stringify!(#func_name).to_string(),
224                        #dest,
225                        __hook_handler,
226                    );
227                    HOOK_REGISTRY.register(hook);
228                }
229            }
230        }
231        _ => panic!("Unsupported hook kind: {}", hook_kind_str),
232    };
233
234    result.into()
235}
236
237/// The macro for defining a symbol interposer.
238/// 
239/// This attribute macro transforms a standard Rust function into an 
240/// exported symbol (via `#[no_mangle]`) and automatically resolves 
241/// the original function from the next available library using `dlsym(RTLD_NEXT, ...)`.
242/// 
243/// ### Parameters
244/// - `symbol`: (Optional) The name of the symbol to interpose. Defaults to the function name.
245/// 
246/// ### Example
247/// ```rust
248/// #[interpose(symbol = "malloc")]
249/// fn my_malloc(size: usize) -> *mut c_void {
250///     println!("Allocating {} bytes", size);
251///     call_original!(size)
252/// }
253/// ```
254#[proc_macro_attribute]
255pub fn interpose(attr: TokenStream, item: TokenStream) -> TokenStream {
256    interpose::interpose_impl(attr, item)
257}
258
259struct HookArgs {
260    offset: Option<syn::LitInt>,
261    symbol: Option<syn::LitStr>,
262    kind: Option<syn::LitStr>,
263    dest: Option<syn::LitInt>,
264}
265
266impl syn::parse::Parse for HookArgs {
267    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
268        let mut offset = None;
269        let mut symbol = None;
270        let mut kind = None;
271        let mut dest = None;
272
273        while !input.is_empty() {
274            let ident: syn::Ident = input.parse()?;
275            let _: syn::Token![=] = input.parse()?;
276
277            if ident == "offset" {
278                offset = Some(input.parse()?);
279            } else if ident == "symbol" {
280                symbol = Some(input.parse()?);
281            } else if ident == "kind" {
282                kind = Some(input.parse()?);
283            } else if ident == "dest" {
284                dest = Some(input.parse()?);
285            }
286
287            if !input.is_empty() {
288                let _: syn::Token![,] = input.parse()?;
289            }
290        }
291
292        Ok(HookArgs { offset, symbol, kind, dest })
293    }
294}