-
-
Notifications
You must be signed in to change notification settings - Fork 332
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
253 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
//! The Nyx `CmpLog` Observer | ||
//! | ||
//! Reads and parses the redqueen results written by QEMU-Nyx and adds them to the state as `CmpValuesMetadata`. | ||
use std::borrow::Cow; | ||
|
||
use lazy_static::lazy_static; | ||
use libafl::{ | ||
executors::ExitKind, | ||
observers::{CmpValues, CmpValuesMetadata, Observer}, | ||
state::HasExecutions, | ||
Error, HasMetadata, | ||
}; | ||
use libafl_bolts::Named; | ||
pub use libafl_targets::{ | ||
cmps::{ | ||
__libafl_targets_cmplog_instructions, __libafl_targets_cmplog_routines, CMPLOG_ENABLED, | ||
}, | ||
CmpLogMap, CmpLogObserver, CMPLOG_MAP_H, CMPLOG_MAP_PTR, CMPLOG_MAP_SIZE, CMPLOG_MAP_W, | ||
}; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
/// A [`CmpObserver`] observer for Nyx | ||
#[derive(Serialize, Deserialize, Debug)] | ||
pub struct NyxCmpObserver { | ||
/// Observer name | ||
name: Cow<'static, str>, | ||
/// Path to redqueen results file | ||
path: Cow<'static, str>, | ||
add_meta: bool, | ||
} | ||
|
||
impl NyxCmpObserver { | ||
/// Creates a new [`struct@NyxCmpObserver`] with the given filepath. | ||
#[must_use] | ||
pub fn new(name: &'static str, path: String, add_meta: bool) -> Self { | ||
Self { | ||
name: Cow::from(name), | ||
path: Cow::from(path), | ||
add_meta, | ||
} | ||
} | ||
} | ||
|
||
impl<I, S> Observer<I, S> for NyxCmpObserver | ||
where | ||
S: HasMetadata + HasExecutions, | ||
I: std::fmt::Debug, | ||
{ | ||
fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { | ||
unsafe { | ||
CMPLOG_ENABLED = 1; | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn post_exec(&mut self, state: &mut S, _input: &I, _exit_kind: &ExitKind) -> Result<(), Error> { | ||
unsafe { | ||
CMPLOG_ENABLED = 0; | ||
} | ||
if self.add_meta { | ||
let meta = state.metadata_or_insert_with(CmpValuesMetadata::new); | ||
let rq_data = parse_redqueen_data(&std::fs::read_to_string(self.path.as_ref())?); | ||
for event in rq_data.bps { | ||
if let Ok(cmp_value) = event.try_into() { | ||
meta.list.push(cmp_value); | ||
} | ||
} | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl Named for NyxCmpObserver { | ||
fn name(&self) -> &Cow<'static, str> { | ||
&self.name | ||
} | ||
} | ||
|
||
// Based on https://github.com/nyx-fuzz/spec-fuzzer/blob/main/rust_fuzzer/src/runner.rs | ||
#[derive(Debug, Clone, Eq, PartialEq, Hash)] | ||
pub enum RedqueenBPType { | ||
Str, | ||
Cmp, | ||
Sub, | ||
} | ||
|
||
impl RedqueenBPType { | ||
fn new(data: &str) -> Result<RedqueenBPType, String> { | ||
match data { | ||
"STR" => return Ok(Self::Str), | ||
"CMP" => return Ok(Self::Cmp), | ||
"SUB" => return Ok(Self::Sub), | ||
_ => Err("Unknown redqueen type".to_string()), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, Eq, PartialEq, Hash)] | ||
struct RedqueenEvent { | ||
pub addr: u64, | ||
pub bp_type: RedqueenBPType, | ||
pub size: usize, | ||
pub lhs: Vec<u8>, | ||
pub rhs: Vec<u8>, | ||
pub imm: bool, | ||
} | ||
|
||
impl RedqueenEvent { | ||
fn new(line: &str) -> Result<Self, String> { | ||
lazy_static! { | ||
static ref RE: regex::Regex = regex::Regex::new( | ||
r"([0-9a-fA-F]+)\s+(CMP|SUB|STR)\s+(\d+)\s+([0-9a-fA-F]+)-([0-9a-fA-F]+)(\sIMM)?" | ||
) | ||
.expect("Invalid regex pattern"); | ||
} | ||
|
||
let captures = RE | ||
.captures(line) | ||
.ok_or_else(|| format!("Failed to parse Redqueen line: '{}'", line))?; | ||
|
||
let addr_s = captures.get(1).ok_or("Missing address field")?.as_str(); | ||
let type_s = captures.get(2).ok_or("Missing type field")?.as_str(); | ||
let size_s = captures.get(3).ok_or("Missing size field")?.as_str(); | ||
let lhs_s = captures.get(4).ok_or("Missing LHS field")?.as_str(); | ||
let rhs_s = captures.get(5).ok_or("Missing RHS field")?.as_str(); | ||
let imm = captures.get(6).map(|_x| true).unwrap_or(false); | ||
|
||
let addr = u64::from_str_radix(addr_s, 16) | ||
.map_err(|_| format!("Invalid address: '{}'", addr_s))?; | ||
let bp_type = RedqueenBPType::new(type_s) | ||
.map_err(|e| format!("Invalid redqueen type: '{}' - {}", type_s, e))?; | ||
let size = size_s | ||
.parse::<usize>() | ||
.map_err(|_| format!("Invalid size: '{}'", size_s))?; | ||
let lhs = | ||
hex::decode(lhs_s).map_err(|e| format!("Failed to decode LHS: '{}' - {}", lhs_s, e))?; | ||
let rhs = | ||
hex::decode(rhs_s).map_err(|e| format!("Failed to decode RHS: '{}' - {}", rhs_s, e))?; | ||
|
||
Ok(Self { | ||
addr, | ||
bp_type, | ||
size, | ||
lhs, | ||
rhs, | ||
imm, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, Eq, PartialEq, Hash)] | ||
struct RedqueenInfo { | ||
bps: Vec<RedqueenEvent>, | ||
} | ||
|
||
fn parse_redqueen_data(data: &str) -> RedqueenInfo { | ||
let bps = data | ||
.lines() | ||
.filter_map(|line| RedqueenEvent::new(line).ok()) | ||
.collect::<Vec<_>>(); | ||
return RedqueenInfo { bps }; | ||
} | ||
|
||
impl TryInto<CmpValues> for RedqueenEvent { | ||
type Error = String; | ||
|
||
fn try_into(self) -> Result<CmpValues, Self::Error> { | ||
match self.bp_type { | ||
RedqueenBPType::Cmp => { | ||
return match self.size { | ||
8 => Ok(CmpValues::U8(( | ||
*self.rhs.first().ok_or("Invalid RHS length for U8")?, | ||
*self.lhs.first().ok_or("Invalid LHS length for U8")?, | ||
self.imm, | ||
))), | ||
16 => Ok(CmpValues::U16(( | ||
u16::from_be_bytes( | ||
self.rhs | ||
.try_into() | ||
.map_err(|_| "Invalid RHS length for U16")?, | ||
), | ||
u16::from_be_bytes( | ||
self.lhs | ||
.try_into() | ||
.map_err(|_| "Invalid LHS length for U16")?, | ||
), | ||
self.imm, | ||
))), | ||
32 => Ok(CmpValues::U32(( | ||
u32::from_be_bytes( | ||
self.rhs | ||
.try_into() | ||
.map_err(|_| "Invalid RHS length for U32")?, | ||
), | ||
u32::from_be_bytes( | ||
self.lhs | ||
.try_into() | ||
.map_err(|_| "Invalid LHS length for U32")?, | ||
), | ||
self.imm, | ||
))), | ||
64 => Ok(CmpValues::U64(( | ||
u64::from_be_bytes( | ||
self.rhs | ||
.try_into() | ||
.map_err(|_| "Invalid RHS length for U64")?, | ||
), | ||
u64::from_be_bytes( | ||
self.lhs | ||
.try_into() | ||
.map_err(|_| "Invalid LHS length for U64")?, | ||
), | ||
self.imm, | ||
))), | ||
_ => Err("Invalid size".to_string()), | ||
} | ||
} | ||
// TODO: Add encoding for `STR` and `SUB` | ||
_ => Err("Redqueen type not implemented".to_string()), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters