Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions radius/src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::BTreeMap;
use std::collections::HashSet;
//use ahash::AHashMap;
//type HashMap<P, Q> = AHashMap<P, Q>;

Expand Down Expand Up @@ -40,6 +41,7 @@ pub struct Memory {
pub endian: Endian,
pub segs: Vec<MemorySegment>,
pub blank: bool,
pub write_log: WriteLog,
}

pub enum Permission {
Expand All @@ -59,6 +61,21 @@ pub struct MemorySegment {
pub init: bool,
}

#[derive(Clone)]
pub struct WriteLog {
pub new_epoch: u64,
pub chunks: HashSet<u64>,
}

impl Default for WriteLog {
fn default() -> Self {
WriteLog {
new_epoch: 0,
chunks: HashSet::new(),
}
}
}

impl Memory {
/// Create a new Memory struct to hold memory values for symbolic execution
pub fn new(r2api: &mut R2Api, btor: Solver, blank: bool) -> Memory {
Expand Down Expand Up @@ -99,6 +116,7 @@ impl Memory {
endian: Endian::from_string(endian),
segs,
blank,
write_log: WriteLog::default(),
}
}

Expand Down Expand Up @@ -565,6 +583,23 @@ impl Memory {
prot_str
}

#[inline]
pub fn chunk_base(addr: u64) -> u64 {
let mask = !(READ_CACHE as u64 - 1);
addr & mask
}

#[inline]
pub fn write_epoch(&self) -> u64 {
self.write_log.new_epoch
}

#[inline]
pub fn has_written_chunk(&self, addr: u64) -> bool {
let base = Memory::chunk_base(addr);
self.write_log.chunks.contains(&base)
}

pub fn read_bytes(&mut self, addr: u64, length: usize, solver: &mut Solver) -> Vec<u8> {
let mut data = vec![Value::Concrete(0, 0); length];
self.read(addr, length, &mut data);
Expand Down Expand Up @@ -604,6 +639,9 @@ impl Memory {
for count in 0..chunks {
let caddr = (addr & mask) + size * count;
let mut offset = (addr & not_mask) * (count == 0) as u64;
if self.write_log.chunks.insert(caddr) {
self.write_log.new_epoch += 1;
}

let mem = if let Some(m) = self.mem.get_mut(&caddr) {
m
Expand Down
159 changes: 159 additions & 0 deletions radius/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ pub enum RunMode {
Multiple,
}

#[derive(Clone)]
pub struct WatchdogConfig {
pub enabled: bool,
pub max_steps: u64,
pub pc_repeat_limit: usize,
pub pc_window_size: usize,
pub pc_window_repeat_limit: usize,
pub no_progress_steps_limit: u64,
pub trap_repeat_limit: usize,
pub exec_from_written_streak_limit: usize,
pub exec_from_written_stable_steps: u64,
}

impl Default for WatchdogConfig {
fn default() -> Self {
WatchdogConfig {
enabled: false,
max_steps: 0,
pc_repeat_limit: 0,
pc_window_size: 0,
pc_window_repeat_limit: 0,
no_progress_steps_limit: 0,
trap_repeat_limit: 0,
exec_from_written_streak_limit: 0,
exec_from_written_stable_steps: 0,
}
}
}

pub type HookMethod = fn(&mut State) -> bool;

#[derive(Clone)]
Expand All @@ -67,6 +96,7 @@ pub struct Processor {
pub color: bool,
pub topological: bool, // execute blocks in topological sort order
pub steps: u64, // number of state steps
pub watchdog: WatchdogConfig,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -121,6 +151,7 @@ impl Processor {
automerge,
color,
steps: 0, //states: vec!()
watchdog: WatchdogConfig::default(),
}
}

Expand Down Expand Up @@ -232,6 +263,18 @@ impl Processor {
}
}

fn is_trap_instruction(instr: &Instruction) -> bool {
let dis = instr.disasm.to_ascii_lowercase();
let op = instr.opcode.to_ascii_lowercase();
dis.starts_with("int3")
|| dis.starts_with("int 3")
|| dis.starts_with("ud2")
|| dis.starts_with("hlt")
|| op.starts_with("int3")
|| op.starts_with("ud2")
|| op.starts_with("hlt")
}

// perform an emulated syscall using the definitions in syscall.rs
pub fn do_syscall(&self, state: &mut State) {
let sys_val = state.registers.get_with_alias("SN");
Expand Down Expand Up @@ -559,6 +602,26 @@ impl Processor {
}
}

if self.watchdog.enabled
&& self.watchdog.trap_repeat_limit > 0
&& Processor::is_trap_instruction(instr)
&& !flags.contains(&InstructionFlag::Break)
&& !flags.contains(&InstructionFlag::Hook)
&& !flags.contains(&InstructionFlag::ESILHook)
&& !flags.contains(&InstructionFlag::Sim)
&& !flags.contains(&InstructionFlag::Merge)
&& !flags.contains(&InstructionFlag::Avoid)
{
let hits = state.watchdog.trap_hits.entry(pc).or_insert(0);
*hits += 1;
if *hits >= self.watchdog.trap_repeat_limit {
state.esil.pcs.clear();
state.esil.pcs.push(new_pc);
state.registers.set_pc(vc(new_pc));
return;
}
}

//let mut new_status = status;
let mut new_flags = flags.clone();
if state.status == StateStatus::PostMerge && flags.contains(&InstructionFlag::Merge) {
Expand Down Expand Up @@ -767,6 +830,13 @@ impl Processor {
pub fn step(&mut self, state: &mut State) -> Vec<State> {
self.steps += 1;
state.visit();
if self.watchdog.enabled {
state.watchdog.step_count = state.watchdog.step_count.saturating_add(1);
if self.watchdog.max_steps > 0 && state.watchdog.step_count > self.watchdog.max_steps {
state.status = StateStatus::Break;
return vec![];
}
}

let pc_allocs = 32;
let pc_value = state.registers.get_pc();
Expand All @@ -777,8 +847,97 @@ impl Processor {
panic!("got an unexpected sym PC: {:?}", pc_value);
}

if self.watchdog.enabled {
if let Some(exec_pc) = state.esil.prev_pc.as_u64() {
if state.watchdog.last_pc == Some(exec_pc) {
state.watchdog.same_pc_count += 1;
} else {
state.watchdog.last_pc = Some(exec_pc);
state.watchdog.same_pc_count = 1;
}

if self.watchdog.pc_repeat_limit > 0
&& state.watchdog.same_pc_count >= self.watchdog.pc_repeat_limit
{
state.status = StateStatus::Break;
return vec![];
}

if self.watchdog.pc_window_size > 0 {
state.watchdog.pc_window.push(exec_pc);
if state.watchdog.pc_window.len() > self.watchdog.pc_window_size {
state.watchdog.pc_window.remove(0);
}

if state.watchdog.pc_window.len() == self.watchdog.pc_window_size {
if state.watchdog.last_window == state.watchdog.pc_window {
state.watchdog.window_repeat_count += 1;
} else {
state.watchdog.last_window = state.watchdog.pc_window.clone();
state.watchdog.window_repeat_count = 1;
}

if self.watchdog.pc_window_repeat_limit > 0
&& state.watchdog.window_repeat_count
>= self.watchdog.pc_window_repeat_limit
{
state.status = StateStatus::Break;
return vec![];
}
}
}
}
}

if self.watchdog.enabled {
let epoch = state.memory.write_epoch();
if epoch != state.watchdog.last_write_epoch {
state.watchdog.last_write_epoch = epoch;
state.watchdog.steps_since_progress = 0;
state.watchdog.steps_since_new_write = 0;
} else {
state.watchdog.steps_since_new_write =
state.watchdog.steps_since_new_write.saturating_add(1);
if self.watchdog.no_progress_steps_limit > 0 {
state.watchdog.steps_since_progress =
state.watchdog.steps_since_progress.saturating_add(1);
if state.watchdog.steps_since_progress
>= self.watchdog.no_progress_steps_limit
{
state.status = StateStatus::Break;
return vec![];
}
}
}
}

let new_pc = state.registers.get_pc();

if self.watchdog.enabled
&& self.watchdog.exec_from_written_streak_limit > 0
&& self.watchdog.exec_from_written_stable_steps > 0
{
if let Some(pc) = new_pc.as_u64() {
if state.memory.has_written_chunk(pc) {
state.watchdog.exec_from_written_streak += 1;
} else {
state.watchdog.exec_from_written_streak = 0;
}

if state.watchdog.exec_from_written_streak
>= self.watchdog.exec_from_written_streak_limit
&& state.watchdog.steps_since_new_write
>= self.watchdog.exec_from_written_stable_steps
{
state.watchdog.oep = Some(pc);
state.status = StateStatus::Break;
return vec![];
}
} else {
state.watchdog.exec_from_written_streak = 0;
}
}

if self.force && !state.esil.pcs.is_empty() {
// we just use the pcs in state.esil.pcs
} else if let Some(pc) = new_pc.as_u64() {
Expand Down
7 changes: 6 additions & 1 deletion radius/src/radius.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub use crate::processor::{HookMethod, Processor, RunMode};
pub use crate::processor::{HookMethod, Processor, RunMode, WatchdogConfig};
use crate::r2_api::{BasicBlock, FunctionInfo, Information, Instruction, R2Api, R2Result};
use crate::state::State;
//use crate::value::Value;
Expand Down Expand Up @@ -201,6 +201,11 @@ impl Radius {
}
}

/// Replace the watchdog configuration for the main processor.
pub fn set_watchdog(&mut self, config: WatchdogConfig) {
self.processor.watchdog = config;
}

/// Initialized state at the provided function address with an initialized stack
/// (if applicable)
///
Expand Down
38 changes: 38 additions & 0 deletions radius/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,41 @@ pub enum StateStatus {
Exit,
}

#[derive(Clone)]
pub struct WatchdogState {
pub step_count: u64,
pub last_pc: Option<u64>,
pub same_pc_count: usize,
pub pc_window: Vec<u64>,
pub last_window: Vec<u64>,
pub window_repeat_count: usize,
pub steps_since_progress: u64,
pub steps_since_new_write: u64,
pub last_write_epoch: u64,
pub exec_from_written_streak: usize,
pub trap_hits: HashMap<u64, usize>,
pub oep: Option<u64>,
}

impl Default for WatchdogState {
fn default() -> Self {
WatchdogState {
step_count: 0,
last_pc: None,
same_pc_count: 0,
pc_window: Vec::with_capacity(64),
last_window: Vec::with_capacity(64),
window_repeat_count: 0,
steps_since_progress: 0,
steps_since_new_write: 0,
last_write_epoch: 0,
exec_from_written_streak: 0,
trap_hits: HashMap::new(),
oep: None,
}
}
}

/// A program state, including memory, registers, and solver data
#[derive(Clone)]
pub struct State {
Expand All @@ -121,6 +156,7 @@ pub struct State {
pub visits: HashMap<u64, usize>,
pub pid: u64,
pub backtrace: Vec<(u64, u64)>,
pub watchdog: WatchdogState,
pub blank: bool,
pub debug: bool,
pub check: bool,
Expand Down Expand Up @@ -193,6 +229,7 @@ impl State {
visits: HashMap::with_capacity(512),
backtrace: Vec::with_capacity(128),
pid: 1337, // sup3rh4x0r
watchdog: WatchdogState::default(),
blank,
debug,
check,
Expand Down Expand Up @@ -269,6 +306,7 @@ impl State {
visits: self.visits.clone(),
backtrace: self.backtrace.clone(),
pid: self.pid,
watchdog: self.watchdog.clone(),
blank: self.blank,
debug: self.debug,
check: self.check,
Expand Down