1use proc_macro::TokenStream;
7use quote::quote;
8use syn::{parse_macro_input, spanned::Spanned, FnArg, ItemFn};
9
10mod interpose;
11
12#[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#[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}