#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/0xdea/augur/master/.img/logo.png")]
use std::fs;
use std::ops::Deref;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use anyhow::Context;
use haruspex::{HaruspexError, decompile_to_file};
use idalib::decompiler::HexRaysErrorCode;
use idalib::func::FunctionFlags;
use idalib::idb::IDB;
use idalib::xref::{XRef, XRefQuery};
use idalib::{Address, IDAError};
#[cfg(unix)]
const RESERVED_CHARS: &[char] = &['.', '/'];
#[cfg(windows)]
const RESERVED_CHARS: &[char] = &['.', '/', '<', '>', ':', '"', '\\', '|', '?', '*'];
const MAX_FILENAME_LEN: usize = 64;
static COUNTER: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct IDAString(String);
impl AsRef<str> for IDAString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Deref for IDAString {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<String> for IDAString {
fn from(value: String) -> Self {
Self(value)
}
}
impl IDAString {
fn traverse_xrefs(
&self,
idb: &IDB,
xref: &XRef,
addr: Address,
dirpath: &Path,
) -> Result<(), HaruspexError> {
if let Some(f) = idb.function_at(xref.from()) {
if f.flags().contains(FunctionFlags::THUNK) {
return Ok(());
}
let string_name = self.filter_printable_chars();
let output_dir = format!(
"{addr:X}_{}",
string_name
.replace(RESERVED_CHARS, "_")
.chars()
.take(MAX_FILENAME_LEN)
.collect::<String>()
);
let func_name = f.name().unwrap_or_else(|| "<no name>".into());
let output_file = format!(
"{}@{:X}",
func_name
.replace(RESERVED_CHARS, "_")
.chars()
.take(MAX_FILENAME_LEN)
.collect::<String>(),
f.start_address()
);
let dirpath_sub = dirpath.join(&output_dir);
let output_path = dirpath_sub.join(output_file).with_extension("c");
if !dirpath_sub.exists() {
fs::create_dir(&dirpath_sub)?;
}
decompile_to_file(idb, &f, &output_path)?;
println!(
"{:#X} in {func_name} -> `{}`",
xref.from(),
output_path.display()
);
COUNTER.fetch_add(1, Ordering::Relaxed);
} else {
println!("{:#X} in <unknown>", xref.from());
}
xref.next_to().map_or(Ok(()), |next| {
self.traverse_xrefs(idb, &next, addr, dirpath)
})
}
fn filter_printable_chars(&self) -> String {
self.chars()
.filter(|c| c.is_ascii_graphic() || *c == ' ')
.collect()
}
}
pub fn run(filepath: &Path) -> anyhow::Result<usize> {
println!("[*] Analyzing binary file `{}`", filepath.display());
let idb = IDB::open(filepath)
.with_context(|| format!("Failed to analyze binary file `{}`", filepath.display()))?;
println!("[+] Successfully analyzed binary file");
println!();
println!("[-] Processor: {}", idb.processor().long_name(),);
println!("[-] Compiler: {:?}", idb.meta().cc_id());
println!("[-] File type: {:?}", idb.meta().filetype());
println!();
anyhow::ensure!(idb.decompiler_available(), "Decompiler is not available");
let dirpath = filepath.with_extension("str");
println!("[*] Preparing output directory `{}`", dirpath.display());
if dirpath.exists() {
fs::remove_dir(&dirpath).map_err(|_| anyhow::anyhow!("Output directory already exists"))?;
}
fs::create_dir_all(&dirpath)
.with_context(|| format!("Failed to create directory `{}`", dirpath.display()))?;
println!("[+] Output directory is ready");
println!();
println!("[*] Finding cross-references to strings...");
for i in 0..idb.strings().len() {
let string: IDAString = idb
.strings()
.get_by_index(i)
.context("Failed to get string content")?
.into();
let addr = idb
.strings()
.get_address_by_index(i)
.context("Failed to get string address")?;
println!("\n{addr:#X} {:?} ", string.as_ref());
idb.first_xref_to(addr, XRefQuery::ALL)
.map_or(Ok::<(), HaruspexError>(()), |xref| {
match string.traverse_xrefs(&idb, &xref, addr, &dirpath) {
Err(HaruspexError::DecompileFailed(IDAError::HexRays(e)))
if e.code() == HexRaysErrorCode::License =>
{
fs::remove_dir_all(&dirpath)?;
Err(IDAError::HexRays(e).into())
}
Err(HaruspexError::DecompileFailed(_)) | Ok(()) => Ok(()),
Err(e) => Err(e),
}
})?;
}
if COUNTER.load(Ordering::Relaxed) == 0 {
fs::remove_dir_all(&dirpath)
.with_context(|| format!("Failed to remove directory `{}`", dirpath.display()))?;
anyhow::bail!("No functions were decompiled, check your input file");
}
println!();
println!(
"[+] Found {COUNTER:?} string usages in functions, decompiled into `{}`",
dirpath.display()
);
println!("[+] Done processing binary file `{}`", filepath.display());
Ok(COUNTER.load(Ordering::Relaxed))
}