vmi_utils/injector/
recipe.rs

1use std::collections::HashMap;
2
3use vmi_core::{Architecture, Hex, Registers, Va, VmiDriver, VmiError, VmiState, os::VmiOs};
4
5/// The control flow of a recipe step.
6pub enum RecipeControlFlow {
7    /// Continue to the next step.
8    Continue,
9
10    /// Stop executing the recipe.
11    Break,
12
13    /// Repeat the current step.
14    Repeat,
15
16    /// Skip the next step.
17    Skip,
18
19    /// Jump to a specific step.
20    Goto(usize),
21}
22
23/// A function that executes a single step in an injection recipe.
24pub type RecipeStepFn<Driver, Os, T> = Box<
25    dyn Fn(&mut RecipeContext<'_, Driver, Os, T>) -> Result<RecipeControlFlow, VmiError>
26        + Send
27        + Sync
28        + 'static,
29>;
30
31/// A cache of symbols for a single image.
32/// The key is the symbol name and the value is the virtual address.
33pub type SymbolCache = HashMap<String, Va>;
34
35/// A cache of symbols for multiple images.
36/// The key is the image filename and the value is the symbol cache.
37pub type ImageSymbolCache = HashMap<String, SymbolCache>;
38
39/// A sequence of injection steps to be executed in order.
40pub struct Recipe<Driver, Os, T>
41where
42    Driver: VmiDriver,
43    Os: VmiOs<Driver>,
44{
45    /// The ordered list of steps to execute.
46    pub(super) steps: Vec<RecipeStepFn<Driver, Os, T>>,
47
48    /// User-provided data shared between steps.
49    pub(super) data: T,
50}
51
52impl<Driver, Os, T> Recipe<Driver, Os, T>
53where
54    Driver: VmiDriver,
55    Os: VmiOs<Driver>,
56{
57    /// Creates a new recipe with the given data.
58    pub fn new(data: T) -> Self {
59        Self {
60            steps: Vec::new(),
61            data,
62        }
63    }
64
65    /// Adds a new step to the recipe.
66    pub fn step<F>(mut self, f: F) -> Self
67    where
68        F: Fn(&mut RecipeContext<'_, Driver, Os, T>) -> Result<RecipeControlFlow, VmiError>
69            + Send
70            + Sync
71            + 'static,
72    {
73        self.steps.push(Box::new(f));
74        self
75    }
76}
77
78/// Context provided to each recipe step during execution.
79pub struct RecipeContext<'a, Driver, Os, T>
80where
81    Driver: VmiDriver,
82    Os: VmiOs<Driver>,
83{
84    /// The VMI state.
85    pub vmi: &'a VmiState<'a, Driver, Os>,
86
87    /// The CPU registers.
88    pub registers: &'a mut <<Driver as VmiDriver>::Architecture as Architecture>::Registers,
89
90    /// User-provided data shared between steps.
91    pub data: &'a mut T,
92
93    /// Cache of resolved symbols for each module.
94    pub cache: &'a mut ImageSymbolCache,
95}
96
97/// Manages the execution of a recipe's steps.
98pub struct RecipeExecutor<Driver, Os, T>
99where
100    Driver: VmiDriver,
101    Os: VmiOs<Driver>,
102{
103    /// The recipe being executed.
104    recipe: Recipe<Driver, Os, T>,
105
106    /// Cache of resolved symbols per module.
107    cache: ImageSymbolCache,
108
109    /// Original state of CPU registers to restore after completion.
110    original_registers: Option<<Driver::Architecture as Architecture>::Registers>,
111
112    /// Index of the current step being executed.
113    index: Option<usize>,
114
115    /// The previous stack pointer value.
116    previous_stack_pointer: Option<Va>,
117}
118
119impl<Driver, Os, T> RecipeExecutor<Driver, Os, T>
120where
121    Driver: VmiDriver,
122    Os: VmiOs<Driver>,
123{
124    /// Creates a new recipe executor with the given recipe.
125    pub fn new(recipe: Recipe<Driver, Os, T>) -> Self {
126        Self {
127            recipe,
128            cache: ImageSymbolCache::new(),
129            original_registers: None,
130            index: None,
131            previous_stack_pointer: None,
132        }
133    }
134
135    /// Executes the next step in the recipe.
136    ///
137    /// Returns the new CPU registers after executing the step.
138    /// If the recipe has finished executing, returns the original registers.
139    pub fn execute(
140        &mut self,
141        vmi: &VmiState<Driver, Os>,
142    ) -> Result<Option<<<Driver as VmiDriver>::Architecture as Architecture>::Registers>, VmiError>
143    {
144        // If the stack pointer has decreased, we are likely in a recursive call or APC.
145        // In this case, we should not execute the recipe.
146        if self.has_stack_pointer_decreased(vmi) {
147            return Ok(None);
148        }
149
150        let index = match &mut self.index {
151            Some(index) => index,
152            None => {
153                self.original_registers = Some(*vmi.registers());
154                self.index.insert(0)
155            }
156        };
157
158        if let Some(step) = self.recipe.steps.get(*index) {
159            tracing::debug!(index, "recipe step");
160
161            let mut registers = *vmi.registers();
162
163            let next = step(&mut RecipeContext {
164                vmi,
165                registers: &mut registers,
166                data: &mut self.recipe.data,
167                cache: &mut self.cache,
168            })?;
169
170            // Update the stack pointer after executing the step.
171            self.previous_stack_pointer = Some(Va(registers.stack_pointer()));
172
173            match next {
174                RecipeControlFlow::Continue => {
175                    *index += 1;
176                    return Ok(Some(registers));
177                }
178                RecipeControlFlow::Break => {}
179                RecipeControlFlow::Repeat => {
180                    return Ok(Some(registers));
181                }
182                RecipeControlFlow::Skip => {
183                    *index += 2;
184                    return Ok(Some(registers));
185                }
186                RecipeControlFlow::Goto(i) => {
187                    *index = i;
188                    return Ok(Some(registers));
189                }
190            }
191        }
192
193        tracing::debug!(
194            result = %Hex(vmi.registers().result()),
195            "recipe finished"
196        );
197
198        self.index = None;
199        let original_registers = self.original_registers.expect("original_registers");
200        Ok(Some(original_registers))
201    }
202
203    /// Returns whether the stack pointer has decreased since the last step.
204    ///
205    /// This is used to detect irrelevant reentrancy in the recipe,
206    /// such as recursive calls, interrupts, or APCs.
207    fn has_stack_pointer_decreased(&self, vmi: &VmiState<Driver, Os>) -> bool {
208        let previous_stack_pointer = match self.previous_stack_pointer {
209            Some(previous_stack_pointer) => previous_stack_pointer,
210            None => return false,
211        };
212
213        let current_stack_pointer = Va(vmi.registers().stack_pointer());
214
215        let result = current_stack_pointer < previous_stack_pointer;
216        if result {
217            tracing::trace!(
218                %previous_stack_pointer,
219                %current_stack_pointer,
220                "stack pointer decreased"
221            );
222        }
223
224        result
225    }
226
227    /// Resets the executor to the initial state.
228    pub fn reset(&mut self) {
229        self.index = None;
230    }
231
232    /// Returns whether the recipe has finished executing.
233    pub fn done(&self) -> bool {
234        self.index.is_none() && self.original_registers.is_some()
235    }
236}