retour\arch\x86\trampoline/
mod.rs

1use self::disasm::*;
2use crate::arch::x86::thunk;
3use crate::error::{Error, Result};
4use crate::pic;
5use std::mem;
6
7mod disasm;
8
9/// A trampoline generator (x86/x64).
10pub struct Trampoline {
11  emitter: pic::CodeEmitter,
12  prolog_size: usize,
13}
14
15impl Trampoline {
16  /// Constructs a new trampoline for an address.
17  pub unsafe fn new(target: *const (), margin: usize) -> Result<Trampoline> {
18    Builder::new(target, margin).build()
19  }
20
21  /// Returns a reference to the trampoline's code emitter.
22  pub fn emitter(&self) -> &pic::CodeEmitter {
23    &self.emitter
24  }
25
26  /// Returns the size of the prolog (i.e the amount of disassembled bytes).
27  pub fn prolog_size(&self) -> usize {
28    self.prolog_size
29  }
30}
31
32/// A trampoline builder.
33struct Builder {
34  /// Disassembler for x86/x64.
35  disassembler: Disassembler,
36  /// Target destination for a potential internal branch.
37  branch_address: Option<usize>,
38  /// Total amount of bytes disassembled.
39  total_bytes_disassembled: usize,
40  /// The preferred minimum amount of bytes disassembled.
41  margin: usize,
42  /// Whether disassembling has finished or not.
43  finished: bool,
44  /// The target the trampoline is adapted for.
45  target: *const (),
46}
47
48impl Builder {
49  /// Returns a trampoline builder.
50  pub fn new(target: *const (), margin: usize) -> Self {
51    Builder {
52      disassembler: Disassembler::new(target),
53      branch_address: None,
54      total_bytes_disassembled: 0,
55      finished: false,
56      target,
57      margin,
58    }
59  }
60
61  /// Creates a trampoline with the supplied settings.
62  ///
63  /// Margins larger than five bytes may lead to undefined behavior.
64  pub unsafe fn build(mut self) -> Result<Trampoline> {
65    let mut emitter = pic::CodeEmitter::new();
66
67    while !self.finished {
68      let instruction = self.next_instruction()?;
69      let thunk = self.process_instruction(&instruction)?;
70
71      // If the trampoline displacement is larger than the target
72      // function, all instructions will be displaced, and if there is
73      // internal branching, it will end up at the wrong instructions.
74      if self.is_instruction_in_branch(&instruction) && instruction.len() != thunk.len() {
75        Err(Error::UnsupportedInstruction)?;
76      } else {
77        emitter.add_thunk(thunk);
78      }
79
80      // Determine whether enough bytes for the margin has been disassembled
81      if self.total_bytes_disassembled >= self.margin && !self.finished {
82        // Add a jump to the first instruction after the prolog
83        emitter.add_thunk(thunk::jmp(instruction.next_instruction_address()));
84        self.finished = true;
85      }
86    }
87
88    Ok(Trampoline {
89      prolog_size: self.total_bytes_disassembled,
90      emitter,
91    })
92  }
93
94  /// Disassembles the next instruction and returns its properties.
95  unsafe fn next_instruction(&mut self) -> Result<Instruction> {
96    let instruction_address = self.target as usize + self.total_bytes_disassembled;
97
98    // Disassemble the next instruction
99    match Instruction::new(&mut self.disassembler, instruction_address as *const _) {
100      None => Err(Error::InvalidCode)?,
101      Some(instruction) => {
102        // Keep track of the total amount of bytes
103        self.total_bytes_disassembled += instruction.len();
104        Ok(instruction)
105      },
106    }
107  }
108
109  /// Returns an instruction after analysing and potentially modifies it.
110  unsafe fn process_instruction(
111    &mut self,
112    instruction: &Instruction,
113  ) -> Result<Box<dyn pic::Thunkable>> {
114    if let Some(displacement) = instruction.rip_operand_displacement() {
115      return self.handle_rip_relative_instruction(instruction, displacement);
116    } else if let Some(displacement) = instruction.relative_branch_displacement() {
117      return self.handle_relative_branch(instruction, displacement);
118    } else if instruction.is_return() {
119      // In case the operand is not placed in a branch, the function
120      // returns unconditionally (i.e it terminates here).
121      self.finished = !self.is_instruction_in_branch(instruction);
122    }
123
124    // The instruction does not use any position-dependant operands,
125    // therefore the bytes can be copied directly from source.
126    Ok(Box::new(instruction.as_slice().to_vec()))
127  }
128
129  /// Adjusts the offsets for RIP relative operands. They are only available
130  /// in x64 processes. The operands offsets needs to be adjusted for their
131  /// new position. An example would be:
132  ///
133  /// ```asm
134  /// mov eax, [rip+0x10]   ; the displacement before relocation
135  /// mov eax, [rip+0x4892] ; theoretical adjustment after relocation
136  /// ```
137  unsafe fn handle_rip_relative_instruction(
138    &mut self,
139    instruction: &Instruction,
140    displacement: isize,
141  ) -> Result<Box<dyn pic::Thunkable>> {
142    // If the instruction is an unconditional jump, processing stops here
143    self.finished = instruction.is_unconditional_jump();
144
145    // Nothing should be done if `displacement` is within the prolog.
146    if (-(self.total_bytes_disassembled as isize)..0).contains(&displacement) {
147      return Ok(Box::new(instruction.as_slice().to_vec()));
148    }
149
150    // These need to be captured by the closure
151    let instruction_address = instruction.address() as isize;
152    let instruction_bytes = instruction.as_slice().to_vec();
153
154    Ok(Box::new(pic::UnsafeThunk::new(
155      move |offset| {
156        let mut bytes = instruction_bytes.clone();
157
158        // Calculate the new relative displacement for the operand. The
159        // instruction is relative so the offset (i.e where the trampoline is
160        // allocated), must be within a range of +/- 2GB.
161        let adjusted_displacement = instruction_address
162          .wrapping_sub(offset as isize)
163          .wrapping_add(displacement);
164        assert!(crate::arch::is_within_range(adjusted_displacement));
165
166        // The displacement value is placed at (instruction - disp32)
167        let index = instruction_bytes.len() - mem::size_of::<u32>();
168
169        // Write the adjusted displacement offset to the operand
170        let as_bytes: [u8; 4] = (adjusted_displacement as u32).to_ne_bytes();
171        bytes[index..instruction_bytes.len()].copy_from_slice(&as_bytes);
172        bytes
173      },
174      instruction.len(),
175    )))
176  }
177
178  /// Processes relative branches (e.g `call`, `loop`, `jne`).
179  unsafe fn handle_relative_branch(
180    &mut self,
181    instruction: &Instruction,
182    displacement: isize,
183  ) -> Result<Box<dyn pic::Thunkable>> {
184    // Calculate the absolute address of the target destination
185    let destination_address_abs = instruction
186      .next_instruction_address()
187      .wrapping_add(displacement as usize);
188
189    if instruction.is_call() {
190      // Calls are not an issue since they return to the original address
191      return Ok(thunk::call(destination_address_abs));
192    }
193
194    let prolog_range = (self.target as usize)..(self.target as usize + self.margin);
195
196    // If the relative jump is internal, and short enough to
197    // fit within the copied function prolog (i.e `margin`),
198    // the jump instruction can be copied indiscriminately.
199    if prolog_range.contains(&destination_address_abs) {
200      // Keep track of the jump's destination address
201      self.branch_address = Some(destination_address_abs);
202      Ok(Box::new(instruction.as_slice().to_vec()))
203    } else if instruction.is_loop() {
204      // Loops (e.g 'loopnz', 'jecxz') to the outside are not supported
205      Err(Error::UnsupportedInstruction)
206    } else if instruction.is_unconditional_jump() {
207      // If the function is not in a branch, and it unconditionally jumps
208      // a distance larger than the prolog, it's the same as if it terminates.
209      self.finished = !self.is_instruction_in_branch(instruction);
210      Ok(thunk::jmp(destination_address_abs))
211    } else {
212      // Conditional jumps (Jcc)
213      // To extract the condition, the primary opcode is required. Short
214      // jumps are only one byte, but long jccs are prefixed with 0x0F.
215      let primary_opcode = instruction
216        .as_slice()
217        .iter()
218        .find(|op| **op != 0x0F)
219        .expect("retrieving conditional jump primary op code");
220
221      // Extract the condition (i.e 0x74 is [jz rel8] ⟶ 0x74 & 0x0F == 4)
222      let condition = primary_opcode & 0x0F;
223      Ok(thunk::jcc(destination_address_abs, condition))
224    }
225  }
226
227  /// Returns whether the current instruction is inside a branch or not.
228  fn is_instruction_in_branch(&self, instruction: &Instruction) -> bool {
229    self
230      .branch_address
231      .map_or(false, |offset| instruction.address() < offset)
232  }
233}