use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::sync::Once;
use crate::lift::Lifter;
use crate::lift::headless::{Headless, Invocation};
use crate::{Error, Result};
pub const DEFAULT_IDA_SCRIPT: &str =
include_str!("../../assets/lifters/ida/scripts/MicrocodeLifter.py");
static WARNED: Once = Once::new();
pub struct IdaLifter {
home: PathBuf,
script: Option<PathBuf>,
intermediate_dir: Option<PathBuf>,
}
impl IdaLifter {
pub fn new(home: PathBuf, script: Option<PathBuf>, intermediate_dir: Option<PathBuf>) -> Self {
Self {
home,
script,
intermediate_dir,
}
}
}
impl Lifter for IdaLifter {
fn lift(&self, input: &Path, output: &Path) -> Result<()> {
let idat = self.home.join("idat");
if !idat.exists() {
return Err(Error::Parse(format!(
"IDA Pro's headless analyser not found at {:?}",
idat
)));
}
if let Some(message) = crate::ida::cloud::warning(&self.home) {
WARNED.call_once(|| eprintln!("oinkie: {message}"));
}
Headless {
tool: "IDA Pro",
program: &idat,
script: self.script.as_deref(),
default_script: ("MicrocodeLifter.py", DEFAULT_IDA_SCRIPT),
work_dir: self.intermediate_dir.as_deref(),
}
.lift(input, output, command_line)
}
}
fn command_line(i: &Invocation) -> Vec<OsString> {
let script = i.script_dir.join(&i.script_name);
vec![
"-A".into(),
"-c".into(),
{
let mut o = OsString::from("-o");
o.push(i.work_dir.join(&i.name));
o
},
{
let mut s = OsString::from("-S");
s.push(&script);
s.push(" ");
s.push(format!("{}.json", i.name));
s
},
i.input.clone().into_os_string(),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_the_built_in_script_is_compiled_in() {
assert!(
DEFAULT_IDA_SCRIPT.contains("import ida_hexrays"),
"the built-in script is not the lifter"
);
}
#[test]
fn test_idat_is_given_its_arguments_in_the_order_it_reads_them() {
let i = Invocation {
input: PathBuf::from("/bin/sample"),
name: "sample".to_string(),
work_dir: PathBuf::from("/work"),
script_dir: PathBuf::from("/scripts"),
script_name: OsString::from("MicrocodeLifter.py"),
};
let mut database = OsString::from("-o");
database.push(i.work_dir.join(&i.name));
let mut script = OsString::from("-S");
script.push(i.script_dir.join(&i.script_name));
script.push(" sample.json");
assert_eq!(
command_line(&i),
vec![
OsString::from("-A"),
OsString::from("-c"),
database,
script,
i.input.clone().into_os_string(),
]
);
}
#[test]
fn test_a_missing_idat_is_named() {
let lifter = IdaLifter::new(PathBuf::from("/oinkie-no-such-ida"), None, None);
let err = match lifter.lift(Path::new("in"), Path::new("out")) {
Err(e) => e.to_string(),
Ok(()) => panic!("a lift succeeded without IDA"),
};
assert!(err.contains("idat"), "does not name what is missing: {err}");
}
}