Skip to content

Commit

Permalink
feature: introduce "binsider as a widget" mechanism. This will help t…
Browse files Browse the repository at this point in the history
…o embedding binsider into another TUI applications.
  • Loading branch information
godzie44 committed Oct 27, 2024
1 parent b764d58 commit dc54752
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 165 deletions.
8 changes: 6 additions & 2 deletions examples/demo.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! A simple demo of how to use the binsider as a library.
use std::{env, fs, path::PathBuf, sync::mpsc, time::Duration};
use std::{fs, path::PathBuf, sync::mpsc, time::Duration};

use binsider::{prelude::*, tui::ui::Tab};

Expand All @@ -11,7 +11,11 @@ use ratatui::{

fn main() -> Result<()> {
// Create an analyzer.
let path = PathBuf::from(env::args().next().expect("no file given"));
let mut path = PathBuf::from("ls");
if !path.exists() {
let resolved_path = which::which(path.to_string_lossy().to_string()).unwrap();
path = resolved_path;
}
let file_data = fs::read(&path)?;
let file_info = FileInfo::new(
path.to_str().unwrap_or_default(),
Expand Down
67 changes: 67 additions & 0 deletions examples/widget.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use binsider::prelude::*;
use ratatui::{crossterm::event::KeyCode, Frame};
use std::path::PathBuf;

fn main() -> Result<()> {
let mut path = PathBuf::from("ls");
if !path.exists() {
let resolved_path = which::which(path.to_string_lossy().to_string())?;
path = resolved_path;
}

let file_data = std::fs::read(&path)?;
let bytes = file_data.as_slice();
let file_info = FileInfo::new(
path.to_str().expect("should be valid string"),
Some(vec![]),
bytes,
)?;
let analyzer = Analyzer::new(file_info, 15, vec![])?;

// Create an application.
let mut state = State::new(analyzer, None)?;
let events = EventHandler::new(250);
state.analyzer.extract_strings(events.sender.clone());

let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| {
render(&mut state, frame);
})
.expect("failed to draw frame");

let event = events.next()?;
match event {
Event::Key(key_event) => {
if key_event.code == KeyCode::Char('q') {
break;
}
handle_event(Event::Key(key_event), &events, &mut state)?;
}
Event::Restart(None) => {
break;
}
Event::Restart(Some(path)) => {
let file_data = std::fs::read(&path)?;
let bytes = file_data.as_slice();
let file_info = FileInfo::new(
path.to_str().expect("should be valid string"),
Some(vec![]),
bytes,
)?;
let analyzer = Analyzer::new(file_info, 15, vec![])?;
state.analyzer = analyzer;
state.handle_tab()?;
state.analyzer.extract_strings(events.sender.clone());
}
_ => {
handle_event(event, &events, &mut state)?;
}
}
}
events.stop();
ratatui::restore();

Ok(())
}
10 changes: 5 additions & 5 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub struct Analyzer<'a> {
/// List of files that are being analyzed.
pub files: Vec<PathBuf>,
/// Current file information.
pub file: FileInfo<'a>,
pub file: FileInfo,
/// Elf properties.
pub elf: Elf,
/// Strings.
Expand Down Expand Up @@ -59,11 +59,11 @@ impl Debug for Analyzer<'_> {
impl<'a> Analyzer<'a> {
/// Constructs a new instance.
pub fn new(
mut file_info: FileInfo<'a>,
mut file_info: FileInfo,
strings_len: usize,
files: Vec<PathBuf>,
) -> Result<Self> {
let elf_bytes = ElfBytes::<AnyEndian>::minimal_parse(file_info.bytes)?;
let elf_bytes = ElfBytes::<AnyEndian>::minimal_parse(file_info.bytes.as_ref())?;
let elf = Elf::try_from(elf_bytes)?;
let heh = Heh::new(file_info.open_file()?, Encoding::Ascii, 0)
.map_err(|e| Error::HexdumpError(e.to_string()))?;
Expand All @@ -81,9 +81,9 @@ impl<'a> Analyzer<'a> {
}

/// Extracts the library dependencies.
pub fn extract_libs(file_info: &FileInfo<'a>) -> Result<Vec<(String, String)>> {
pub fn extract_libs(file_info: &FileInfo) -> Result<Vec<(String, String)>> {
let mut dependencies = DependencyAnalyzer::default()
.analyze(file_info.path)?
.analyze(&file_info.path)?
.libraries
.clone()
.into_iter()
Expand Down
20 changes: 10 additions & 10 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ use std::os::windows::fs::MetadataExt;

/// General file information.
#[derive(Debug)]
pub struct FileInfo<'a> {
pub struct FileInfo {
/// Path of the file.
pub path: &'a str,
pub path: String,
/// Arguments of the file.
pub arguments: Option<Vec<&'a str>>,
pub arguments: Option<Vec<String>>,
/// Bytes of the file.
pub bytes: &'a [u8],
pub bytes: Box<[u8]>,
/// Whether if the file is read only.
pub is_read_only: bool,
/// Name of the file.
Expand Down Expand Up @@ -69,19 +69,19 @@ pub struct FileDateInfo {
pub birth: String,
}

impl<'a> FileInfo<'a> {
impl FileInfo {
/// Constructs a new instance.
#[cfg(not(target_os = "windows"))]
pub fn new(path: &'a str, arguments: Option<Vec<&'a str>>, bytes: &'a [u8]) -> Result<Self> {
pub fn new(path: &str, arguments: Option<Vec<String>>, bytes: impl Into<Box<[u8]>>) -> Result<Self> {
let metadata = fs::metadata(path)?;
let mode = metadata.permissions().mode();

let users = Users::new_with_refreshed_list();
let groups = Groups::new_with_refreshed_list();
Ok(Self {
path,
path: path.to_string(),
arguments,
bytes,
bytes: bytes.into(),
is_read_only: false,
name: PathBuf::from(path)
.file_name()
Expand Down Expand Up @@ -160,11 +160,11 @@ impl<'a> FileInfo<'a> {
/// Opens the file (with R/W if possible) and returns it.
pub fn open_file(&mut self) -> Result<File> {
Ok(
match OpenOptions::new().write(true).read(true).open(self.path) {
match OpenOptions::new().write(true).read(true).open(&self.path) {
Ok(v) => v,
Err(_) => {
self.is_read_only = true;
File::open(self.path)?
File::open(&self.path)?
}
},
)
Expand Down
149 changes: 94 additions & 55 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ pub mod file;
/// Common types that can be glob-imported for convenience.
pub mod prelude;

use crate::tui::{tui_exit, tui_init, ui};
use args::Args;
use file::FileInfo;
use prelude::*;
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use std::sync::atomic::Ordering;
use std::{env, fs, io, path::PathBuf};
use tui::{state::State, ui::Tab, Tui};
use tui::{state::State, ui::Tab};

/// Runs binsider.
pub fn run(mut args: Args) -> Result<()> {
Expand All @@ -48,7 +50,7 @@ pub fn run(mut args: Args) -> Result<()> {
let mut parts = path_str.split_whitespace();
if let Some(bin) = parts.next() {
path = PathBuf::from(bin);
arguments = Some(parts.collect());
arguments = Some(parts.map(|s| s.to_string()).collect());
}
if !path.exists() {
let resolved_path = which::which(path.to_string_lossy().to_string())?;
Expand All @@ -58,12 +60,64 @@ pub fn run(mut args: Args) -> Result<()> {
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 file_info = FileInfo::new(path.to_str().unwrap_or_default(), arguments, file_data)?;
let analyzer = Analyzer::new(file_info, args.min_strings_len, args.files.clone())?;
start_tui(analyzer, args)
}

/// Generic handler for `binsider events.
///
/// Can be used for user defined widgets. Any event handler can be redeclared just by ignore this
/// function.
pub fn handle_event(event: Event, events: &EventHandler, state: &mut State) -> Result<()> {
match event {
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, events.sender.clone())?;
}
Event::Mouse(mouse_event) => {
state.run_command(Command::from(mouse_event), 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;
tracer::trace_syscalls(&state.analyzer.file, events.sender.clone());
}
#[cfg(feature = "dynamic-analysis")]
Event::TraceResult(syscalls) => {
state.analyzer.tracer = syscalls.unwrap_or_else(|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;
state.handle_tab()?;
}
#[cfg(not(feature = "dynamic-analysis"))]
Event::Trace | Event::TraceResult(_) => {}
Event::Restart(_) => {}
}
Ok(())
}

/// Starts the terminal user interface.
pub fn start_tui(analyzer: Analyzer, args: Args) -> Result<()> {
// Create an application.
Expand All @@ -74,65 +128,29 @@ pub fn start_tui(analyzer: Analyzer, args: Args) -> Result<()> {

// Initialize the terminal user interface.
let backend = CrosstermBackend::new(io::stdout());
let terminal = Terminal::new(backend)?;
let mut 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()?;
tui_init(&mut terminal)?;

// Start the main loop.
while state.running {
// Render the user interface.
tui.draw(&mut state)?;
terminal.draw(|frame| ui::render(&mut state, frame))?;
// Handle events.
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()?;
}
}

let event = events.next()?;
match event {
#[cfg(feature = "dynamic-analysis")]
Event::Trace => {
state.system_calls_loaded = false;
tui.toggle_pause()?;
tracer::trace_syscalls(&state.analyzer.file, tui.events.sender.clone());
events.key_input_disabled.store(true, Ordering::Relaxed);
handle_event(event, &events, &mut state)?;
}
#[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()?;
Event::TraceResult(_) => {
events.key_input_disabled.store(false, Ordering::Relaxed);
handle_event(event, &events, &mut state)?;
}
#[cfg(not(feature = "dynamic-analysis"))]
Event::Trace | Event::TraceResult(_) => {}
Event::Restart(path) => {
let mut args = args.clone();
match path {
Expand All @@ -144,15 +162,36 @@ pub fn start_tui(analyzer: Analyzer, args: Args) -> Result<()> {
}
}
if !args.files.is_empty() {
tui.exit()?;
state.running = false;
run(args)?;
if args.files.is_empty() {
args.files.push(env::current_exe()?);
}
let mut path = args.files[args.files.len() - 1].clone();
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 file_info =
FileInfo::new(path.to_str().unwrap_or_default(), Some(vec![]), file_data)?;
let analyzer =
Analyzer::new(file_info, args.min_strings_len, args.files.clone())?;

state.analyzer = analyzer;
state.analyzer.extract_strings(events.sender.clone());
state.handle_tab()?;
}
}
_ => {
handle_event(event, &events, &mut state)?;
}
}
}

// Exit the user interface.
tui.exit()?;
tui_exit(&mut terminal)?;
events.stop();
Ok(())
}
1 change: 1 addition & 0 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub use super::tui::command::*;
pub use super::tui::event::*;
pub use super::tui::state::*;
pub use super::tui::ui::*;
pub use super::handle_event;
Loading

0 comments on commit dc54752

Please sign in to comment.