rustproof-libsmt 0.1.0

Rust bindings for z3 as utilized by RustProof.
Documentation
//! Example showing bitvector and array usage examples

// Example ASM:
// -----------------
// global _main
// section .text
// main:
//   ; Instructions, not for our case.
//
// write:
//   ; Funtion epilogue and prologue skipped.
//   ; buf is located at rbp - 0xa.
//   ; rdi and rsi are symbolic, user controlled inputs.
//   lea rax, [rbp - 0xa]
//   add rax, rdi
//   mov [rax], rsi
//   ret

extern crate libsmt;

use libsmt::backends::smtlib2::*;
use libsmt::backends::backend::*;
use libsmt::backends::z3;
use libsmt::theories::{array_ex, bitvec, core};
use libsmt::logics::qf_abv::QF_ABV;
use libsmt::logics::qf_abv;

macro_rules! bv_const {
    ($solver: ident, $i: expr, $n: expr) => { $solver.new_const(bitvec::OpCodes::Const($i, $n)) }
}

fn main() {
    let mut z3: z3::Z3 = Default::default();
    let mut solver = SMTLib2::new(Some(QF_ABV));

    // First we declare all the symbolic vars and constants that are needed in order to check if
    // the program is vulnerable.

    // Two symbolic vars corresponding to the user inputs.
    let rdi = solver.new_var(Some("rdi"), qf_abv::bv_sort(64));
    let rsi = solver.new_var(Some("rsi"), qf_abv::bv_sort(64));

    // Memory is defined to be constant of type Array of BitVectors.
    let mem = {
        let bit_vec_arr = qf_abv::array_sort(qf_abv::bv_sort(64),
                                             qf_abv::bv_sort(64));
        solver.new_var(Some("mem"), bit_vec_arr)
    };

    // rbp is a constant in this case (and equal to 0x9000).
    let rbp = bv_const!(solver, 0x9000, 64);

    let const_a = bv_const!(solver, 0xA, 64);
    let const_4 = bv_const!(solver, 0x4, 64);
    let const_badcafe = bv_const!(solver, 0xbadcafe, 64);
    // Constant array (of bitvectors) with all 0's. Used to reset memory.
    let const_mem_0 = {
        let arr_const = qf_abv::array_const(qf_abv::bv_sort(64),
                                            qf_abv::bv_sort(64),
                                            bitvec::OpCodes::Const(0, 64));
        solver.new_const(arr_const)
    };

    // Adding constraints based on the above asm.
    
    // Reset memory to 0
    solver.assert(core::OpCodes::Cmp, &[mem, const_mem_0]);
    // We know that the return address is at rbp + 0x4
    let ret_addr = solver.assert(bitvec::OpCodes::BvAdd, &[rbp, const_4]);

    // buf = rbp - 0xa
    let buf = solver.assert(bitvec::OpCodes::BvSub, &[rbp, const_a]);
    // rax = rax + rdi
    let rax = solver.assert(bitvec::OpCodes::BvAdd, &[buf, rdi]);

    // [rax] = rsi
    let new_mem = solver.assert(array_ex::OpCodes::Store, &[mem, rax, rsi]);

    // Assert for safety constaint i.e. Add a constraint to check if mem[ret_addr] is equal to
    // 0xbadcafe. If there is a satisfying solution, then the return address can be overwritten and
    // the solver returns the value of rdi and rsi that achieves this goal.
    let sel = solver.assert(array_ex::OpCodes::Select, &[new_mem, ret_addr]);
    solver.assert(core::OpCodes::Cmp, &[sel, const_badcafe]);

    // Check if we have a satisfying solution.
    if let Ok(result) = solver.solve(&mut z3) {
        println!("Out-Of-Bounds Write detected!");
        println!("rdi: 0x{:x}; rsi: 0x{:x};", result[&rdi], result[&rsi]);
    } else {
        println!("This program is not vulnerable to Out-Of-Bounds Write.");
    }
}