Unverified Commit a1c7cd23 by van den Bosch Committed by GitHub

Added installer which automatically searches for Ghidra path if it is not provided (#278)

If no Ghidra path is provided on install, then the installer searches for it and offers all found locations to the user to choose from. Also fixes issue #142.
parent 8d32cfe1
......@@ -144,6 +144,18 @@ dependencies = [
]
[[package]]
name = "cwe_checker_install"
version = "0.1.0"
dependencies = [
"anyhow",
"directories",
"serde",
"serde_json",
"structopt",
"walkdir",
]
[[package]]
name = "cwe_checker_lib"
version = "0.6.0-dev"
dependencies = [
......@@ -522,6 +534,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scroll"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
......@@ -698,6 +719,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
......@@ -720,6 +752,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
......
[workspace]
members = ["src/cwe_checker_lib", "src/caller", "test"]
members = ["src/cwe_checker_lib", "src/caller", "test", "src/installer"]
......@@ -2,18 +2,8 @@ GHIDRA_PATH =
.PHONY: all clean test uninstall docker
all:
cargo build --release
ifdef GHIDRA_PATH
mkdir -p ${HOME}/.config/cwe_checker
cp src/config.json ${HOME}/.config/cwe_checker/config.json
cargo install --path src/caller --locked
echo "{ \"ghidra_path\": \"${GHIDRA_PATH}\" }" > ${HOME}/.config/cwe_checker/ghidra.json
mkdir -p ${HOME}/.local/share/cwe_checker
cp -r src/ghidra ${HOME}/.local/share/cwe_checker/ghidra
else
echo "GHIDRA_PATH not specified. Please set it to the path to your local Ghidra installation."
false
endif
cargo build -p cwe_checker_install --release
./target/release/cwe_checker_install ${GHIDRA_PATH}
test:
cargo test
......@@ -37,9 +27,8 @@ clean:
rm -f -r doc/html
uninstall:
rm -f -r ${HOME}/.config/cwe_checker
rm -f -r ${HOME}/.local/share/cwe_checker
cargo uninstall cwe_checker
cargo build -p cwe_checker_install --release
./target/release/cwe_checker_install --uninstall
documentation:
cargo doc --open --no-deps
......
......@@ -378,13 +378,12 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
is_true: bool,
) -> Option<State> {
let mut specialized_state = state.clone();
if specialized_state
match specialized_state
.specialize_by_expression_result(condition, Bitvector::from_u8(is_true as u8).into())
.is_err()
{
Ok(_) => Some(specialized_state),
// State is unsatisfiable
return None;
Err(_) => None,
}
Some(specialized_state)
}
}
[package]
name = "cwe_checker_install"
version = "0.1.0"
edition = "2021"
[dependencies]
directories = "4.0.1"
walkdir = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0" # for easy error types
structopt = "0.3"
//! This crate automates the installation of cwe_checker.
//! It creates config files, copies the Ghida-Plugin and can search for a Ghidra installation at commonly used locations.
use anyhow::{anyhow, Result};
use directories::{BaseDirs, ProjectDirs, UserDirs};
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use walkdir::WalkDir;
#[derive(Debug, StructOpt)]
/// Installs cwe_checker
struct CmdlineArgs {
#[structopt()]
/// Path to a ghidra installation.
///
/// If this option is set then the installation will use ghidra at this location.
ghidra_path: Option<String>,
#[structopt(long, short)]
/// If true, cwe_checker will be uninstalled.
uninstall: bool,
}
#[derive(Serialize, Deserialize, Debug)]
/// Structure for ghidra.json file
struct GhidraConfig {
/// Path to a ghidra installation
ghidra_path: PathBuf,
}
/// Copies src/config.json to specified location
fn copy_config_json(location: &Path) -> Result<()> {
let repo_dir = env::current_dir().unwrap();
std::fs::copy(
&repo_dir.join("src/config.json"),
location.join("config.json"),
)?;
Ok(())
}
/// Returns vector of os-specific locations
fn get_search_locations() -> Vec<PathBuf> {
let mut locations: Vec<PathBuf> = Vec::new();
let base_dirs = BaseDirs::new().unwrap();
let user_dirs = UserDirs::new().unwrap();
locations.push(base_dirs.data_dir().to_path_buf());
locations.push(base_dirs.data_local_dir().to_path_buf());
locations.push(base_dirs.home_dir().to_path_buf());
if let Some(path) = base_dirs.executable_dir() {
locations.push(path.to_path_buf());
}
if let Some(path) = user_dirs.desktop_dir() {
locations.push(path.to_path_buf());
}
if let Some(path) = user_dirs.download_dir() {
locations.push(path.to_path_buf());
}
if let Some(path) = user_dirs.document_dir() {
locations.push(path.to_path_buf());
}
if let Some(path) = user_dirs.public_dir() {
locations.push(path.to_path_buf());
}
let candidate = Path::new("/opt");
if candidate.exists() {
locations.push(candidate.to_path_buf());
}
let candidate = Path::new("/usr");
if candidate.exists() {
locations.push(candidate.to_path_buf());
}
locations
}
/// Returns None if Ghidra were not found. Else returns path to Ghidra, which might be user selected.
fn find_ghidra() -> Result<PathBuf> {
let mut ghidra_locations: Vec<PathBuf> = get_search_locations()
.into_iter()
.filter_map(|x| search_for_ghidrarun(&x))
.collect();
ghidra_locations.sort();
ghidra_locations.dedup_by(|a, b| a == b);
match ghidra_locations.len() {
0 => Err(anyhow!("Ghidra not found.")),
1 => Ok(ghidra_locations.pop().unwrap()),
_ => select_ghidra_version(ghidra_locations),
}
}
/// Searches for a file containing "ghidraRun" at provided path recursively.
fn search_for_ghidrarun(entry_path: &Path) -> Option<PathBuf> {
for entry in WalkDir::new(entry_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.metadata().unwrap().is_file())
{
if entry.file_name().to_str().unwrap() == "ghidraRun" {
let mut hit = entry.into_path();
hit.pop();
return Some(hit);
}
}
None
}
/// Determines if a path is a ghidra installation
fn is_good_ghidra_location(loc: &Path) -> bool {
loc.to_path_buf().push("Ghidra/application.properties");
loc.exists()
}
/// Determines Ghidra versions and provides selection interface for the user.
fn select_ghidra_version(ghidra_locations: Vec<PathBuf>) -> Result<PathBuf> {
let good_ghidra_locations: Vec<&PathBuf> = ghidra_locations
.iter()
.filter(|x| is_good_ghidra_location(x))
.collect();
if good_ghidra_locations.is_empty() {
return Err(anyhow!("Ghidra not found"));
}
for (i, loc) in good_ghidra_locations.iter().enumerate() {
let mut app_prob_file = (*loc).clone();
app_prob_file.push("Ghidra/application.properties");
let version = match std::fs::read_to_string(app_prob_file) {
Ok(app_prop) => app_prop
.lines()
.filter_map(|x| x.strip_prefix("application.version="))
.next()
.unwrap_or("?")
.to_string(),
Err(_) => "?".to_string(),
};
println!("Use Ghidra at: {} [v{}]? ({})", loc.display(), version, i);
}
println!("Abort ({})", good_ghidra_locations.len());
get_user_choice(good_ghidra_locations)
}
/// Determines Ghidra versions and provides selection interface for the user.
fn get_user_choice(ghidra_locations: Vec<&PathBuf>) -> Result<PathBuf> {
println!("Please select (0-{}): ", ghidra_locations.len());
let mut choice = String::new();
std::io::stdin().read_line(&mut choice)?;
match choice.trim().parse::<usize>() {
Ok(i) if i == ghidra_locations.len() => Err(anyhow!("Installation canceled by user")),
Ok(i) if i < ghidra_locations.len() => Ok(ghidra_locations[i].clone()),
Ok(_) => Err(anyhow!("Invalid user input")),
Err(error) => Err(error.into()),
}
}
/// Creates ghidra.json for a Ghidra location at provided locaton.
fn create_ghidra_json(location: &Path, ghidra_location: PathBuf) -> Result<()> {
let conf = GhidraConfig {
ghidra_path: ghidra_location,
};
println!("creating ghidra.json at: {}", location.display());
std::fs::create_dir_all(location)?;
std::fs::write(location.join("ghidra.json"), serde_json::to_string(&conf)?)?;
Ok(())
}
/// Runs Cargo install to install cwe_checker.
fn install_cwe_checker() -> Result<()> {
match std::process::Command::new("cargo")
.args(["install", "--path", "src/caller", "--locked"])
.status()
{
Ok(exit_status) if exit_status.success() => Ok(()),
Ok(_) => Err(anyhow!("Installaton failed")),
Err(error) => Err(error.into()),
}
}
/// Recursive copy of files and directories.
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
fs::create_dir_all(&dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
/// Copy src/ghidra to provided location.
fn copy_ghidra_plugin(target: &Path) -> Result<()> {
let target = &target;
let mut source = env::current_dir()?;
source.push("src/ghidra");
copy_dir_all(source, target.join("ghidra"))?;
Ok(())
}
/// Removes provided locations and uninstalls cwe_checker via cargo
fn uninstall(conf_dir: &Path, data_dir: &Path) -> Result<()> {
if std::fs::remove_dir_all(conf_dir).is_ok() {
println!("Removing {}", conf_dir.display())
}
if std::fs::remove_dir_all(data_dir).is_ok() {
println!("Removing {}", data_dir.display())
}
let _ = std::process::Command::new("cargo")
.args(["uninstall", "cwe_checker"])
.status();
Ok(())
}
fn main() -> Result<()> {
let cwe_checker_proj_dir = ProjectDirs::from("", "", "cwe_checker").unwrap();
let cmdline_args = CmdlineArgs::from_args();
match cmdline_args.uninstall {
true => {
uninstall(
cwe_checker_proj_dir.config_dir(),
cwe_checker_proj_dir.data_dir(),
)?;
return Ok(());
}
false => match cmdline_args.ghidra_path {
Some(ghidra_input_location) => create_ghidra_json(
cwe_checker_proj_dir.config_dir(),
PathBuf::from(ghidra_input_location),
)?,
None if cwe_checker_proj_dir
.config_dir()
.join("ghidra.json")
.exists() =>
{
println!(
"found ghidra.json at {}, keeping it.",
cwe_checker_proj_dir.config_dir().display()
)
}
None => {
println!("searching for ghidra...");
match find_ghidra() {
Ok(ghidra_location) => {
create_ghidra_json(cwe_checker_proj_dir.config_dir(), ghidra_location)?;
}
Err(err) => return Err(err),
}
}
},
}
println!("installing cwe_checker...");
install_cwe_checker()?;
println!(
"creating config.json at: {}",
cwe_checker_proj_dir.config_dir().display()
);
copy_config_json(cwe_checker_proj_dir.config_dir())?;
println!(
"copy Ghidra Plugin to: {}",
cwe_checker_proj_dir.data_dir().display()
);
copy_ghidra_plugin(cwe_checker_proj_dir.data_dir())?;
Ok(())
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment