diff --git a/src/binwalk.rs b/src/binwalk.rs index 47237fe8a..b7ed48fe2 100644 --- a/src/binwalk.rs +++ b/src/binwalk.rs @@ -601,9 +601,10 @@ impl Binwalk { pub fn extract( &self, file_data: &[u8], - file_path: &String, + file_name: impl Into, file_map: &Vec, ) -> HashMap { + let file_path = file_name.into(); let mut extraction_results: HashMap = HashMap::new(); @@ -622,7 +623,7 @@ impl Binwalk { Some(_) => { // Run an extraction for this signature let mut extraction_result = - extractors::common::execute(file_data, file_path, signature, &extractor); + extractors::common::execute(file_data, &file_path, signature, &extractor); if !extraction_result.success { debug!( @@ -653,7 +654,7 @@ impl Binwalk { // Re-run the extraction extraction_result = extractors::common::execute( file_data, - file_path, + &file_path, &new_signature, &extractor, ); @@ -669,13 +670,13 @@ impl Binwalk { extraction_results } - /// Analyze a file and optionally extract the file contents. + /// Analyze a data buffer and optionally extract the file contents. /// /// ## Example /// /// ``` - /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_624_0() -> Result { - /// use binwalk::Binwalk; + /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_672_0() -> Result { + /// use binwalk::{Binwalk, common}; /// /// let target_path = std::path::Path::new("tests") /// .join("inputs") @@ -688,6 +689,8 @@ impl Binwalk { /// .display() /// .to_string(); /// + /// let file_data = common::read_file(&target_path).expect("Failed to read file data"); + /// /// # std::fs::remove_dir_all(&extraction_directory); /// let binwalker = Binwalk::configure(Some(target_path), /// Some(extraction_directory.clone()), @@ -696,7 +699,7 @@ impl Binwalk { /// None, /// false)?; /// - /// let analysis_results = binwalker.analyze(&binwalker.base_target_file, true); + /// let analysis_results = binwalker.analyze_buf(&file_data, &binwalker.base_target_file, true); /// /// assert_eq!(analysis_results.file_map.len(), 1); /// assert_eq!(analysis_results.extractions.len(), 1); @@ -707,44 +710,101 @@ impl Binwalk { /// .exists(), true); /// # std::fs::remove_dir_all(&extraction_directory); /// # Ok(binwalker) - /// # } _doctest_main_src_binwalk_rs_624_0(); } + /// # } _doctest_main_src_binwalk_rs_672_0(); } /// ``` - pub fn analyze(&self, target_file: &String, do_extraction: bool) -> AnalysisResults { + pub fn analyze_buf( + &self, + file_data: &[u8], + target_file: impl Into, + do_extraction: bool, + ) -> AnalysisResults { + let file_path = target_file.into(); + // Return value let mut results: AnalysisResults = AnalysisResults { - file_path: target_file.clone(), + file_path: file_path.clone(), ..Default::default() }; - debug!("Analysis start: {}", target_file); - - // Read file into memory - if let Ok(file_data) = read_file(target_file) { - // Scan file data for signatures - info!("Scanning {}", target_file); - results.file_map = self.scan(&file_data); + // Scan file data for signatures + debug!("Analysis start: {}", file_path); + results.file_map = self.scan(file_data); - // Only extract if told to, and if there were some signatures found in this file - if do_extraction && !results.file_map.is_empty() { - // Extract everything we can - debug!( - "Submitting {} signature results to extractor", - results.file_map.len() - ); - results.extractions = self.extract(&file_data, target_file, &results.file_map); - } + // Only extract if told to, and if there were some signatures found in this file + if do_extraction && !results.file_map.is_empty() { + // Extract everything we can + debug!( + "Submitting {} signature results to extractor", + results.file_map.len() + ); + results.extractions = self.extract(file_data, &file_path, &results.file_map); } - debug!("Analysis end: {}", target_file); + debug!("Analysis end: {}", file_path); results } + + /// Analyze a file on disk and optionally extract its contents. + /// + /// ## Example + /// + /// ``` + /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_745_0() -> Result { + /// use binwalk::Binwalk; + /// + /// let target_path = std::path::Path::new("tests") + /// .join("inputs") + /// .join("gzip.bin") + /// .display() + /// .to_string(); + /// + /// let extraction_directory = std::path::Path::new("tests") + /// .join("extractions") + /// .display() + /// .to_string(); + /// + /// # std::fs::remove_dir_all(&extraction_directory); + /// let binwalker = Binwalk::configure(Some(target_path), + /// Some(extraction_directory.clone()), + /// None, + /// None, + /// None, + /// false)?; + /// + /// let analysis_results = binwalker.analyze(&binwalker.base_target_file, true); + /// + /// assert_eq!(analysis_results.file_map.len(), 1); + /// assert_eq!(analysis_results.extractions.len(), 1); + /// assert_eq!(std::path::Path::new(&extraction_directory) + /// .join("gzip.bin.extracted") + /// .join("0") + /// .join("decompressed.bin") + /// .exists(), true); + /// # std::fs::remove_dir_all(&extraction_directory); + /// # Ok(binwalker) + /// # } _doctest_main_src_binwalk_rs_745_0(); } + /// ``` + #[allow(dead_code)] + pub fn analyze(&self, target_file: impl Into, do_extraction: bool) -> AnalysisResults { + let file_path = target_file.into(); + + let file_data = match read_file(&file_path) { + Err(_) => { + error!("Failed to read data from {}", file_path); + b"".to_vec() + } + Ok(data) => data, + }; + + self.analyze_buf(&file_data, &file_path, do_extraction) + } } /// Initializes the extraction output directory fn init_extraction_directory( - target_file: &String, - extraction_directory: &String, + target_file: &str, + extraction_directory: &str, ) -> Result { // Create the output directory, equivalent of mkdir -p match fs::create_dir_all(extraction_directory) { diff --git a/src/cliparser.rs b/src/cliparser.rs index 2842b38a0..0e9afe09a 100644 --- a/src/cliparser.rs +++ b/src/cliparser.rs @@ -7,6 +7,10 @@ pub struct CliArgs { #[arg(short = 'L', long)] pub list: bool, + /// Read data from standard input + #[arg(short, long)] + pub stdin: bool, + /// Supress output to stdout #[arg(short, long)] pub quiet: bool, diff --git a/src/common.rs b/src/common.rs index 1f712190b..111e7c1de 100644 --- a/src/common.rs +++ b/src/common.rs @@ -4,18 +4,55 @@ use log::{debug, error}; use std::fs::File; use std::io::Read; -/// Read a file into memory and return its contents. +/// Read a data into memory, either from disk or from stdin, and return its contents. /// /// ## Example /// /// ``` /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_common_rs_11_0() -> Result<(), Box> { +/// use binwalk::common::read_input; +/// +/// let file_data = read_input("/etc/passwd", false)?; +/// assert!(file_data.len() > 0); +/// # Ok(()) +/// # } _doctest_main_src_common_rs_11_0(); } +/// ``` +pub fn read_input(file: impl Into, stdin: bool) -> Result, std::io::Error> { + if stdin { + read_stdin() + } else { + read_file(file) + } +} + +/// Read data from standard input and return its contents. +pub fn read_stdin() -> Result, std::io::Error> { + let mut stdin_data = Vec::new(); + + match std::io::stdin().read_to_end(&mut stdin_data) { + Err(e) => { + error!("Failed to read data from stdin: {}", e); + Err(e) + } + Ok(nbytes) => { + debug!("Loaded {} bytes from stdin", nbytes); + Ok(stdin_data) + } + } +} + +/// Read a file data into memory and return its contents. +/// +/// ## Example +/// +/// ``` +/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_common_rs_48_0() -> Result<(), Box> { /// use binwalk::common::read_file; /// /// let file_data = read_file("/etc/passwd")?; /// assert!(file_data.len() > 0); /// # Ok(()) -/// # } _doctest_main_src_common_rs_11_0(); } +/// # } _doctest_main_src_common_rs_48_0(); } /// ``` pub fn read_file(file: impl Into) -> Result, std::io::Error> { let mut file_data = Vec::new(); diff --git a/src/entropy.rs b/src/entropy.rs index e3455262f..5c2c9a8e2 100644 --- a/src/entropy.rs +++ b/src/entropy.rs @@ -1,4 +1,4 @@ -use crate::common::read_file; +use crate::common::read_input; use entropy::shannon_entropy; use log::error; use plotters::prelude::*; @@ -58,7 +58,7 @@ fn blocks(data: &[u8]) -> Vec { /// Generate a plot of a file's entropy. /// Will output a file to the current working directory with the name `.png`. -pub fn plot(file_path: impl Into) -> Result { +pub fn plot(file_path: impl Into, stdin: bool) -> Result { const FILE_EXTENSION: &str = "png"; const SHANNON_MAX_VALUE: i32 = 8; const IMAGE_PIXEL_WIDTH: u32 = 2048; @@ -87,7 +87,7 @@ pub fn plot(file_path: impl Into) -> Result { } // Read in the target file data - if let Ok(file_data) = read_file(&target_file) { + if let Ok(file_data) = read_input(&target_file, stdin) { let mut points: Vec<(i32, i32)> = vec![]; // Calculate the entropy for each file block diff --git a/src/main.rs b/src/main.rs index 5576736d0..ba903986b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,9 @@ mod signatures; mod structures; fn main() { + // File name used when reading from stdin + const STDIN: &str = "stdin"; + // Only use one thread if unable to auto-detect available core info const DEFAULT_WORKER_COUNT: usize = 1; @@ -50,7 +53,7 @@ fn main() { env_logger::init(); // Process command line aguments - let cliargs = cliparser::parse(); + let mut cliargs = cliparser::parse(); // If --list was specified, just display a list of signatures and return if cliargs.list { @@ -58,6 +61,11 @@ fn main() { return; } + // Set a dummy file name when reading from stdin + if cliargs.stdin { + cliargs.file_name = Some(STDIN.to_string()); + } + // If --list was not specified, a target file must be provided if cliargs.file_name.is_none() { panic!("No target file name specified! Try --help."); @@ -69,7 +77,7 @@ fn main() { if cliargs.entropy { display::print_plain(cliargs.quiet, "Calculating file entropy..."); - if let Ok(entropy_results) = entropy::plot(cliargs.file_name.unwrap()) { + if let Ok(entropy_results) = entropy::plot(cliargs.file_name.unwrap(), cliargs.stdin) { // Log entropy results to JSON file, if requested json_logger.log(json::JSONType::Entropy(entropy_results.clone())); json_logger.close(); @@ -165,6 +173,7 @@ fn main() { &workers, binwalker.clone(), target_file, + cliargs.stdin && file_count == 0, cliargs.extract, cliargs.carve, worker_tx.clone(), @@ -273,17 +282,27 @@ fn spawn_worker( pool: &ThreadPool, bw: binwalk::Binwalk, target_file: String, + stdin: bool, do_extraction: bool, do_carve: bool, worker_tx: mpsc::Sender, ) { pool.execute(move || { + // Read in file data + let file_data = match common::read_input(&target_file, stdin) { + Err(_) => { + error!("Failed to read {} data", target_file); + b"".to_vec() + } + Ok(data) => data, + }; + // Analyze target file, with extraction, if specified - let results = bw.analyze(&target_file, do_extraction); + let results = bw.analyze_buf(&file_data, &target_file, do_extraction); // If data carving was requested as part of extraction, carve analysis results to disk if do_carve { - let carve_count = carve_file_map(&results); + let carve_count = carve_file_map(&file_data, &results); info!( "Carved {} data blocks to disk from {}", carve_count, target_file @@ -303,46 +322,42 @@ fn spawn_worker( /// Returns the number of carved files created. /// Note that unknown blocks of file data are also carved to disk, so the number of files /// created may be larger than the number of results defined in results.file_map. -fn carve_file_map(results: &binwalk::AnalysisResults) -> usize { +fn carve_file_map(file_data: &[u8], results: &binwalk::AnalysisResults) -> usize { let mut carve_count: usize = 0; let mut last_known_offset: usize = 0; let mut unknown_bytes: Vec<(usize, usize)> = Vec::new(); // No results, don't do anything if !results.file_map.is_empty() { - // Read in the source file - if let Ok(file_data) = common::read_file(&results.file_path) { - // Loop through all identified signatures in the file - for signature_result in &results.file_map { - // If there is data between the last signature and this signature, it is some chunk of unknown data - if signature_result.offset > last_known_offset { - unknown_bytes.push(( - last_known_offset, - signature_result.offset - last_known_offset, - )); - } - - // Carve this signature's data to disk - if carve_file_data_to_disk( - &results.file_path, - &file_data, - &signature_result.name, - signature_result.offset, - signature_result.size, - ) { - carve_count += 1; - } + // Loop through all identified signatures in the file + for signature_result in &results.file_map { + // If there is data between the last signature and this signature, it is some chunk of unknown data + if signature_result.offset > last_known_offset { + unknown_bytes.push(( + last_known_offset, + signature_result.offset - last_known_offset, + )); + } - // Update the last known offset to the end of this signature's data - last_known_offset = signature_result.offset + signature_result.size; + // Carve this signature's data to disk + if carve_file_data_to_disk( + &results.file_path, + file_data, + &signature_result.name, + signature_result.offset, + signature_result.size, + ) { + carve_count += 1; } - // All known signature data has been carved to disk, now carve any unknown blocks of data to disk - for (offset, size) in unknown_bytes { - if carve_file_data_to_disk(&results.file_path, &file_data, "unknown", offset, size) - { - carve_count += 1; - } + // Update the last known offset to the end of this signature's data + last_known_offset = signature_result.offset + signature_result.size; + } + + // All known signature data has been carved to disk, now carve any unknown blocks of data to disk + for (offset, size) in unknown_bytes { + if carve_file_data_to_disk(&results.file_path, file_data, "unknown", offset, size) { + carve_count += 1; } } }