1#![doc = include_str!("../README.md")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/0xdea/haruspex/master/.img/logo.png")]
3
4use std::fs;
5use std::fs::File;
6use std::io::{BufWriter, Write};
7use std::path::Path;
8
9use anyhow::Context;
10use idalib::IDAError;
11use idalib::decompiler::HexRaysErrorCode;
12use idalib::func::{Function, FunctionFlags};
13use idalib::idb::IDB;
14use thiserror::Error;
15
16#[cfg(unix)]
18const RESERVED_CHARS: &[char] = &['.', '/'];
19#[cfg(windows)]
20const RESERVED_CHARS: &[char] = &['.', '/', '<', '>', ':', '"', '\\', '|', '?', '*'];
21
22const MAX_FILENAME_LEN: usize = 64;
24
25#[derive(Error, Debug)]
27pub enum HaruspexError {
28 #[error(transparent)]
30 DecompileFailed(#[from] IDAError),
31 #[error(transparent)]
33 FileWriteFailed(#[from] std::io::Error),
34}
35
36pub fn run(filepath: &Path) -> anyhow::Result<usize> {
42 println!("[*] Analyzing binary file `{}`", filepath.display());
44 let idb = IDB::open(filepath)
45 .with_context(|| format!("Failed to analyze binary file `{}`", filepath.display()))?;
46 println!("[+] Successfully analyzed binary file");
47 println!();
48
49 println!("[-] Processor: {}", idb.processor().long_name());
51 println!("[-] Compiler: {:?}", idb.meta().cc_id());
52 println!("[-] File type: {:?}", idb.meta().filetype());
53 println!();
54
55 anyhow::ensure!(idb.decompiler_available(), "Decompiler is not available");
57
58 let dirpath = filepath.with_extension("dec");
60 println!("[*] Preparing output directory `{}`", dirpath.display());
61 if dirpath.exists() {
62 fs::remove_dir(&dirpath).map_err(|_| anyhow::anyhow!("Output directory already exists"))?;
63 }
64 fs::create_dir_all(&dirpath)
65 .with_context(|| format!("Failed to create directory `{}`", dirpath.display()))?;
66 println!("[+] Output directory is ready");
67
68 let mut decompiled_count: usize = 0;
69
70 println!();
72 println!("[*] Extracting pseudocode of functions...");
73 println!();
74 for (_id, f) in idb.functions() {
75 if f.flags().contains(FunctionFlags::THUNK) {
77 continue;
78 }
79
80 let func_name = f.name().unwrap_or_else(|| "[no name]".into());
82 let output_file = format!(
83 "{}@{:X}",
84 func_name
85 .replace(RESERVED_CHARS, "_")
86 .chars()
87 .take(MAX_FILENAME_LEN)
88 .collect::<String>(),
89 f.start_address()
90 );
91 let output_path = dirpath.join(output_file).with_extension("c");
92
93 match decompile_to_file(&idb, &f, &output_path) {
94 Ok(()) => {
96 println!("{func_name} -> `{}`", output_path.display());
97 decompiled_count += 1;
98 }
99
100 Err(HaruspexError::DecompileFailed(IDAError::HexRays(e)))
102 if e.code() == HexRaysErrorCode::License =>
103 {
104 return Err(e.into());
105 }
106
107 Err(HaruspexError::DecompileFailed(_)) => (),
109
110 Err(e) => return Err(e.into()),
112 }
113 }
114
115 if decompiled_count == 0 {
117 fs::remove_dir(&dirpath)
118 .with_context(|| format!("Failed to remove directory `{}`", dirpath.display()))?;
119 anyhow::bail!("No functions were decompiled, check your input file");
120 }
121
122 println!();
123 println!(
124 "[+] Decompiled {decompiled_count} functions into `{}`",
125 dirpath.display()
126 );
127 println!("[+] Done processing binary file `{}`", filepath.display());
128 Ok(decompiled_count)
129}
130
131pub fn decompile_to_file(
159 idb: &IDB,
160 func: &Function,
161 filepath: impl AsRef<Path>,
162) -> Result<(), HaruspexError> {
163 let decomp = idb.decompile(func)?;
165 let source = decomp.pseudocode();
166
167 let mut writer = BufWriter::new(File::create(&filepath)?);
170 writer.write_all(source.as_bytes())?;
171 writer.flush()?;
172
173 Ok(())
174}