frida_rs/
interceptor.rs

1//!Frida functions for Interceptor functionality.
2//!
3//!The functions in this module correspond to the JavaScript functions
4//!grouped under
5//![https://frida.re/docs/javascript-api/#interceptor](https://frida.re/docs/javascript-api/#interceptor)
6
7use crate::nativepointer::NativePointer;
8use crate::plumbing;
9use crate::plumbing::utils::this_wrap;
10use js_sys::Object;
11use wasm_bindgen::prelude::*;
12use wasm_bindgen::JsCast;
13
14pub use plumbing::interceptor::{InvocationArgs, InvocationReturnValue};
15
16///Per-invocation object that contains useful properties about the program at
17///the point of interception.
18///
19///This is equivalent to the `this` object accessible in the Interceptor
20///callback in the JavaScript API.
21///
22///The [`get`][get] and [`set`][set] methods can be used to store arbitrary
23///Rust objects that will be accessible at any point during the invocation.
24///For example, this could be used to store values during `on_enter` that can
25///later be retrieved during `on_leave`.
26///
27///[get]: InvocationContext::get
28///[set]: InvocationContext::set
29#[derive(Debug)]
30pub struct InvocationContext {
31    ///Return address of the intercepted function.
32    pub return_address: NativePointer,
33    ///Current CPU context.
34    pub context: crate::cpu::CpuContext,
35    ///OS thread ID.
36    pub thread_id: u32,
37    ///Call depth relative to other invocations.
38    pub depth: u32,
39    _js: plumbing::interceptor::InvocationContext,
40}
41
42impl InvocationContext {
43    ///Get the value stored with the `prop` key.
44    pub fn get<T>(&self, prop: &str) -> T
45    where
46        T: for<'a> serde::Deserialize<'a>,
47    {
48        self._js.get(prop).into_serde().unwrap()
49    }
50
51    ///Stores `val` within the context with the `prop` key. `val` can be any
52    ///serializable Rust object.
53    pub fn set<T>(&self, prop: &str, val: &T)
54    where
55        T: serde::Serialize + ?Sized,
56    {
57        self._js.set(prop, JsValue::from_serde(val).unwrap())
58    }
59}
60
61impl From<plumbing::interceptor::InvocationContext> for InvocationContext {
62    fn from(m: plumbing::interceptor::InvocationContext) -> Self {
63        InvocationContext {
64            return_address: m.return_address(),
65            context: crate::cpu::CpuContext::from(m.context()),
66            thread_id: m.thread_id(),
67            depth: m.depth(),
68            _js: m,
69        }
70    }
71}
72
73// TODO: This feels like a bad hack... see if there is a better way...
74impl wasm_bindgen::describe::WasmDescribe for InvocationContext {
75    fn describe() {
76        plumbing::interceptor::InvocationContext::describe()
77    }
78}
79
80// TODO: This feels like a bad hack... see if there is a better way...
81impl wasm_bindgen::convert::FromWasmAbi for InvocationContext {
82    type Abi = u32;
83
84    unsafe fn from_abi(js: u32) -> InvocationContext {
85        let i = plumbing::interceptor::InvocationContext::from_abi(js);
86        InvocationContext::from(i)
87    }
88}
89
90pub struct InvocationCallbacks {
91    pub on_enter: Option<Box<dyn FnMut(InvocationContext, InvocationArgs)>>,
92    pub on_leave: Option<Box<dyn FnMut(InvocationContext, InvocationReturnValue)>>,
93}
94
95///Intercept calls to `target`.
96///
97///This is equivalent to calling `Interceptor.attach` in the JavaScript API.
98///
99///```
100///let callbacks = InvocationCallbacks {
101///    on_enter: Some(Box::new(move |this: InvocationContext, args: InvocationArg| {
102///        ...
103///    })),
104///    on_leave: Some(Box::new(move |this: InvocationContext, retval: InvocationReturnValue {
105///        ...
106///    }))
107///};
108///
109///Interceptor::attach(target, callbacks);
110///```
111pub fn attach(target: NativePointer, callbacks: InvocationCallbacks) {
112    let callbacks_object = Object::new();
113
114    if let Some(f) = callbacks.on_enter {
115        let on_enter = Closure::wrap(f);
116        let on_enter_wrapped = this_wrap(on_enter.as_ref().unchecked_ref());
117        js_sys::Reflect::set(
118            &callbacks_object,
119            &JsValue::from_str("onEnter"),
120            &on_enter_wrapped,
121        )
122        .unwrap();
123        on_enter.forget();
124    }
125
126    if let Some(f) = callbacks.on_leave {
127        let on_leave = Closure::wrap(f);
128        let on_leave_wrapped = this_wrap(on_leave.as_ref().unchecked_ref());
129        js_sys::Reflect::set(
130            &callbacks_object,
131            &JsValue::from_str("onLeave"),
132            &on_leave_wrapped,
133        )
134        .unwrap();
135        on_leave.forget();
136    }
137
138    plumbing::interceptor::attach(target, callbacks_object);
139}