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:

  1. Loading a program representation from a database.
  2. 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.