#![warn(missing_docs, clippy::unwrap_used)]
pub mod app;
pub mod tui;
pub mod elf;
pub mod args;
pub mod error;
#[cfg(feature = "dynamic-analysis")]
pub mod tracer;
pub mod file;
pub mod prelude;
use args::Args;
use file::FileInfo;
use prelude::*;
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use std::{env, fs, io, path::PathBuf};
use tui::{state::State, ui::Tab, Tui};
pub fn run(mut args: Args) -> Result<()> {
if args.files.is_empty() {
args.files.push(env::current_exe()?);
}
let mut path = args.files[args.files.len() - 1].clone();
let mut arguments = None;
let path_str = path.to_string_lossy().to_string();
let mut parts = path_str.split_whitespace();
if let Some(bin) = parts.next() {
path = PathBuf::from(bin);
arguments = Some(parts.collect());
}
if !path.exists() {
let resolved_path = which::which(path.to_string_lossy().to_string())?;
if let Some(file) = args.files.iter_mut().find(|f| **f == path) {
*file = resolved_path.clone();
}
path = resolved_path;
}
let file_data = fs::read(&path)?;
let bytes = file_data.as_slice();
let file_info = FileInfo::new(path.to_str().unwrap_or_default(), arguments, bytes)?;
let analyzer = Analyzer::new(file_info, args.min_strings_len, args.files.clone())?;
start_tui(analyzer, args)
}
pub fn start_tui(analyzer: Analyzer, args: Args) -> Result<()> {
let mut state = State::new(analyzer, args.accent_color)?;
state.set_tab(args.tab);
let backend = CrosstermBackend::new(io::stdout());
let terminal = Terminal::new(backend)?;
let events = EventHandler::new(250);
state.analyzer.extract_strings(events.sender.clone());
let mut tui = Tui::new(terminal, events);
tui.init()?;
while state.running {
tui.draw(&mut state)?;
match tui.events.next()? {
Event::Tick => {}
Event::Key(key_event) => {
let command = if state.input_mode {
Command::Input(InputCommand::parse(key_event, &state.input))
} else if state.show_heh {
Command::Hexdump(HexdumpCommand::parse(
key_event,
state.analyzer.file.is_read_only,
))
} else {
Command::from(key_event)
};
state.run_command(command, tui.events.sender.clone())?;
}
Event::Mouse(mouse_event) => {
state.run_command(Command::from(mouse_event), tui.events.sender.clone())?;
}
Event::Resize(_, _) => {}
Event::FileStrings(strings) => {
state.strings_loaded = true;
state.analyzer.strings = Some(strings?.into_iter().map(|(v, l)| (l, v)).collect());
if state.tab == Tab::Strings {
state.handle_tab()?;
}
}
#[cfg(feature = "dynamic-analysis")]
Event::Trace => {
state.system_calls_loaded = false;
tui.toggle_pause()?;
tracer::trace_syscalls(&state.analyzer.file, tui.events.sender.clone());
}
#[cfg(feature = "dynamic-analysis")]
Event::TraceResult(syscalls) => {
state.analyzer.tracer = match syscalls {
Ok(v) => v,
Err(e) => TraceData {
syscalls: console::style(e).red().to_string().as_bytes().to_vec(),
..Default::default()
},
};
state.system_calls_loaded = true;
state.dynamic_scroll_index = 0;
tui.toggle_pause()?;
state.handle_tab()?;
}
#[cfg(not(feature = "dynamic-analysis"))]
Event::Trace | Event::TraceResult(_) => {}
Event::Restart(path) => {
let mut args = args.clone();
match path {
Some(path) => {
args.files.push(path);
}
None => {
args.files.pop();
}
}
if !args.files.is_empty() {
tui.exit()?;
state.running = false;
run(args)?;
}
}
}
}
tui.exit()?;
Ok(())
}