From cf3c2382a01fa4923d401144a144e4863a0e3bfe Mon Sep 17 00:00:00 2001 From: Valentin Obst Date: Fri, 14 Jun 2024 11:27:40 +0200 Subject: [PATCH] lib/utils/debug: add option to read output of Ghidra plugin from file The output of the Ghidra plugin (and Ghidra in general) is non-deterministic, even on the same Ghidra version. Probably this is due to analysis timeouts. Furthermore, running Ghidra takes a significant portion of the analysis time. Therefore, we can create reproducibility as well as speedups during development by saving the plugin output (using `--debug pcode-raw`) and replaying it later (via `--pcode-raw /path/to/file`). Signed-off-by: Valentin Obst --- src/caller/src/main.rs | 18 ++++++++- src/cwe_checker_lib/src/utils/debug.rs | 51 ++++++++++++++++++++----- src/cwe_checker_lib/src/utils/ghidra.rs | 46 ++++++++++++---------- 3 files changed, 84 insertions(+), 31 deletions(-) diff --git a/src/caller/src/main.rs b/src/caller/src/main.rs index 8ffa3558d..d2243420b 100644 --- a/src/caller/src/main.rs +++ b/src/caller/src/main.rs @@ -100,6 +100,11 @@ struct CmdlineArgs { /// The current behavior of this flag is unstable and subject to change. #[arg(long, hide(true))] debug: Option, + + /// Read the saved output of the Pcode Extractor plugin from a file instead + /// of invoking Ghidra. + #[arg(long, hide(true))] + pcode_raw: Option, } impl From<&CmdlineArgs> for debug::Settings { @@ -108,7 +113,7 @@ impl From<&CmdlineArgs> for debug::Settings { None => debug::Stage::default(), Some(mode) => mode.into(), }; - let verbose = if args.verbose { + let verbosity = if args.verbose { debug::Verbosity::Verbose } else if args.quiet { debug::Verbosity::Quiet @@ -116,7 +121,16 @@ impl From<&CmdlineArgs> for debug::Settings { debug::Verbosity::default() }; - debug::Settings::new(stage, verbose) + let mut builder = debug::SettingsBuilder::default() + .set_stage(stage) + .set_verbosity(verbosity) + .set_termination_policy(debug::TerminationPolicy::EarlyExit); + + if let Some(pcode_raw) = &args.pcode_raw { + builder = builder.set_saved_pcode_raw(PathBuf::from(pcode_raw.clone())); + } + + builder.build() } } diff --git a/src/cwe_checker_lib/src/utils/debug.rs b/src/cwe_checker_lib/src/utils/debug.rs index be15aa3ce..afb76d453 100644 --- a/src/cwe_checker_lib/src/utils/debug.rs +++ b/src/cwe_checker_lib/src/utils/debug.rs @@ -4,6 +4,8 @@ #![allow(dead_code)] #![allow(missing_docs)] +use std::path::PathBuf; + #[derive(PartialEq, Eq, Copy, Clone, Debug, Default)] /// Stages of the analysis that can be debugged separately. #[non_exhaustive] @@ -48,29 +50,60 @@ pub enum Verbosity { /// Selects whether the analysis is aborted after reaching the point of /// interest. #[non_exhaustive] -enum TerminationPolicy { +pub enum TerminationPolicy { KeepRunning, #[default] EarlyExit, Panic, } -#[derive(PartialEq, Eq, Copy, Clone, Default, Debug)] +#[derive(PartialEq, Eq, Clone, Default, Debug)] /// Configuration of the debugging behavior. pub struct Settings { stage: Stage, verbose: Verbosity, terminate: TerminationPolicy, + saved_pcode_raw: Option, +} + +#[derive(PartialEq, Eq, Clone, Default, Debug)] +pub struct SettingsBuilder { + inner: Settings, +} + +impl SettingsBuilder { + pub fn build(self) -> Settings { + self.inner + } + + pub fn set_stage(mut self, stage: Stage) -> Self { + self.inner.stage = stage; + + self + } + + pub fn set_verbosity(mut self, verbosity: Verbosity) -> Self { + self.inner.verbose = verbosity; + + self + } + + pub fn set_termination_policy(mut self, policy: TerminationPolicy) -> Self { + self.inner.terminate = policy; + + self + } + + pub fn set_saved_pcode_raw(mut self, saved_pcode_raw: PathBuf) -> Self { + self.inner.saved_pcode_raw = Some(saved_pcode_raw); + + self + } } impl Settings { - /// Returns a new settings object. - pub fn new(stage: Stage, verbose: Verbosity) -> Self { - Self { - stage, - verbose, - terminate: TerminationPolicy::default(), - } + pub fn get_saved_pcode_raw(&self) -> Option { + self.saved_pcode_raw.clone() } /// Returns true iff the `stage` is being debugged. diff --git a/src/cwe_checker_lib/src/utils/ghidra.rs b/src/cwe_checker_lib/src/utils/ghidra.rs index 877c66eb3..0d5d7a1a3 100644 --- a/src/cwe_checker_lib/src/utils/ghidra.rs +++ b/src/cwe_checker_lib/src/utils/ghidra.rs @@ -26,26 +26,32 @@ pub fn get_project_from_ghidra( bare_metal_config_opt: Option, debug_settings: &debug::Settings, ) -> Result<(Project, Vec), Error> { - let tmp_folder = get_tmp_folder()?; - // We add a timestamp suffix to file names - // so that if two instances of the cwe_checker are running in parallel on the same file - // they do not interfere with each other. - let timestamp_suffix = format!( - "{:?}", - std::time::SystemTime::now() - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis() - ); - // Create a unique name for the pipe - let fifo_path = tmp_folder.join(format!("pcode_{timestamp_suffix}.pipe")); - let ghidra_command = generate_ghidra_call_command( - file_path, - &fifo_path, - ×tamp_suffix, - &bare_metal_config_opt, - )?; - let pcode_project = execute_ghidra(ghidra_command, &fifo_path, debug_settings)?; + let pcode_project = if let Some(saved_pcode_raw) = debug_settings.get_saved_pcode_raw() { + let file = std::fs::File::open(saved_pcode_raw) + .expect("Failed to open saved output of Pcode Extractor plugin."); + serde_json::from_reader(std::io::BufReader::new(file))? + } else { + let tmp_folder = get_tmp_folder()?; + // We add a timestamp suffix to file names + // so that if two instances of the cwe_checker are running in parallel on the same file + // they do not interfere with each other. + let timestamp_suffix = format!( + "{:?}", + std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() + ); + // Create a unique name for the pipe + let fifo_path = tmp_folder.join(format!("pcode_{timestamp_suffix}.pipe")); + let ghidra_command = generate_ghidra_call_command( + file_path, + &fifo_path, + ×tamp_suffix, + &bare_metal_config_opt, + )?; + execute_ghidra(ghidra_command, &fifo_path, debug_settings)? + }; parse_pcode_project_to_ir_project(pcode_project, binary, &bare_metal_config_opt) }