use crate::prelude::*;
use crate::utils::binary::BareMetalConfig;
use crate::utils::{get_ghidra_plugin_path, read_config_file};
use crate::{
intermediate_representation::{Project, RuntimeMemoryImage},
utils::debug,
utils::log::LogMessage,
};
use directories::ProjectDirs;
use nix::{sys::stat, unistd};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::thread;
pub fn get_project_from_ghidra(
file_path: &Path,
binary: &[u8],
bare_metal_config_opt: Option<BareMetalConfig>,
debug_settings: &debug::Settings,
) -> Result<(Project, Vec<LogMessage>), Error> {
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()?;
let timestamp_suffix = format!(
"{:?}",
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis()
);
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)
}
pub fn parse_pcode_project_to_ir_project(
mut pcode_project: crate::pcode::Project,
binary: &[u8],
bare_metal_config_opt: &Option<BareMetalConfig>,
) -> Result<(Project, Vec<LogMessage>), Error> {
let bare_metal_base_address_opt = bare_metal_config_opt
.as_ref()
.map(|config| config.parse_binary_base_address());
let mut log_messages = pcode_project.normalize();
let project: Project = match RuntimeMemoryImage::get_base_address(binary) {
Ok(binary_base_address) => pcode_project.into_ir_project(binary_base_address),
Err(_err) => {
if let Some(binary_base_address) = bare_metal_base_address_opt {
let mut project = pcode_project.into_ir_project(binary_base_address);
project.program.term.address_base_offset = 0;
project
} else {
log_messages.push(LogMessage::new_info("Could not determine binary base address. Using base address of Ghidra output as fallback."));
let mut project = pcode_project.into_ir_project(0);
project.program.term.address_base_offset = 0;
project
}
}
};
Ok((project, log_messages))
}
fn execute_ghidra(
mut ghidra_command: Command,
fifo_path: &PathBuf,
debug_settings: &debug::Settings,
) -> Result<crate::pcode::Project, Error> {
let should_print_ghidra_error = debug_settings.verbose();
unistd::mkfifo(fifo_path, stat::Mode::from_bits(0o600).unwrap())
.context("Error creating FIFO pipe")?;
let ghidra_subprocess = thread::spawn(move || {
let output = match ghidra_command.output() {
Ok(output) => output,
Err(err) => {
eprintln!("Ghidra could not be executed: {err}");
std::process::exit(101);
}
};
if let Ok(stdout) = String::from_utf8(output.stdout.clone()) {
if stdout.contains("Pcode was successfully extracted!") && output.status.success() {
return;
}
}
if should_print_ghidra_error {
eprintln!("{}", String::from_utf8(output.stdout).unwrap());
eprintln!("{}", String::from_utf8(output.stderr).unwrap());
if let Some(code) = output.status.code() {
eprintln!("Ghidra plugin failed with exit code {code}");
}
eprintln!("Execution of Ghidra plugin failed.");
} else {
eprintln!("Execution of Ghidra plugin failed. Use the --verbose flag to print Ghidra output for troubleshooting.");
}
std::process::exit(101)
});
let mut file = std::fs::File::open(fifo_path.clone()).expect("Could not open FIFO.");
let mut buf = String::new();
file.read_to_string(&mut buf)
.expect("Error while reading from FIFO.");
debug_settings.print(&buf, debug::Stage::Pcode(debug::PcodeForm::Raw));
let pcode_parsing_result = serde_json::from_str(&buf);
ghidra_subprocess
.join()
.expect("The Ghidra thread to be joined has panicked!");
std::fs::remove_file(fifo_path).context("Could not clean up FIFO pipe")?;
Ok(pcode_parsing_result?)
}
fn generate_ghidra_call_command(
file_path: &Path,
fifo_path: &Path,
timestamp_suffix: &str,
bare_metal_config_opt: &Option<BareMetalConfig>,
) -> Result<Command, Error> {
let ghidra_path: std::path::PathBuf =
serde_json::from_value(read_config_file("ghidra.json")?["ghidra_path"].clone())
.context("Path to Ghidra not configured.")?;
let headless_path = ghidra_path.join("support/analyzeHeadless");
let tmp_folder = get_tmp_folder()?;
let filename = file_path
.file_name()
.ok_or_else(|| anyhow!("Invalid file name"))?
.to_string_lossy()
.to_string();
let ghidra_plugin_path = get_ghidra_plugin_path("p_code_extractor");
let mut ghidra_command = Command::new(headless_path);
ghidra_command
.arg(&tmp_folder) .arg(format!("PcodeExtractor_{filename}_{timestamp_suffix}")) .arg("-import") .arg(file_path) .arg("-postScript") .arg(ghidra_plugin_path.join("PcodeExtractor.java")) .arg(fifo_path) .arg("-scriptPath") .arg(ghidra_plugin_path) .arg("-deleteProject") .arg("-analysisTimeoutPerFile") .arg("3600"); if let Some(bare_metal_config) = bare_metal_config_opt {
let mut base_address: &str = &bare_metal_config.flash_base_address;
if let Some(stripped_address) = base_address.strip_prefix("0x") {
base_address = stripped_address;
}
ghidra_command
.arg("-loader") .arg("BinaryLoader") .arg("-loader-baseAddr") .arg(base_address)
.arg("-processor") .arg(bare_metal_config.processor_id.clone());
}
Ok(ghidra_command)
}
fn get_tmp_folder() -> Result<PathBuf, Error> {
let project_dirs = ProjectDirs::from("", "", "cwe_checker")
.context("Could not determine path for temporary files")?;
let tmp_folder = if let Some(folder) = project_dirs.runtime_dir() {
folder
} else {
Path::new("/tmp/cwe_checker")
};
if !tmp_folder.exists() {
std::fs::create_dir(tmp_folder).context("Unable to create temporary folder")?;
}
Ok(tmp_folder.to_path_buf())
}