Micro-execution with Fugue
Fugue supports analysing programs using a number of different paradigms. Micro-execution is a static/dynamic approach that enables a program to be executed from any address. Since program state will often be unknown or missing under such conditions, Fugue provides a means to intercept invalid accesses and recover execution by providing concrete values for missing memory.
Fugue's executor enables different execution events to be hooked, these include:
- Conditional branches
- Memory reads and writes
- Register reads and writes
- Function calls
- Instruction fetches
Hooks attached to these events can manipulate and read the program's internal state, and redirect its execution.
fuguex-core
The fuguex-core family of crates provide the basis of implementing custom
executors based on fugue-core. The fuguex-concrete
crate provides a full
micro-execution-based executor.
Its basic usage requires the two steps:
- Loading a program representation from a database.
- Configuring an initial program state.
The code below demonstrates how to obtain an initial program database for
analysis and build an initial state (via ConcreteContext
):
use either::Either; use fugue::bytes::LE; use fugue::ir::{AddressValue, LanguageDB}; use fuguex::concrete::interpreter::ConcreteContext; use fuguex::loader::{LoaderMapping, MappedDatabase}; use fuguex::machine::{Bound, Machine}; fn main() -> Result<(), Box<dyn std::error::Error>> { let language_db = LanguageDB::from_directory_with("./processors", true)?; let database = if let Either::Left(db) = MappedDatabase::from_path("./path/to/sample", &language_db)? .pcode_state_with::<LE, _>("gcc") { db } else { panic!("invalid calling convention `gcc`") }; let mut ctx = ConcreteContext::<LE, String, 64>::from_loader(database); let mut machine = Machine::new(ctx);
The machine can be executed using the following snippet:
#![allow(unused)] fn main() { let space = machine.interpreter().interpreter_space(); let start = AddressValue::new(space.clone(), 0x1000); let stop = AddressValue::new(space.clone(), 0x1200); machine.step_until(start, Bound::address(stop)); }
The code above creates two address references into the default interpreter
address space and then steps the machine forwards from start
until stop
is reached. For each event kind observed when stepping, the machine will
call-back to its registered hooks.