diff --git a/src/binwalk.rs b/src/binwalk.rs index 0e991e00f..b7ed48fe2 100644 --- a/src/binwalk.rs +++ b/src/binwalk.rs @@ -14,7 +14,7 @@ use std::os::windows; #[cfg(unix)] use std::os::unix; -use crate::common::is_offset_safe; +use crate::common::{is_offset_safe, read_file}; use crate::extractors; use crate::magic; use crate::signatures; @@ -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,12 +670,12 @@ 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 { + /// # 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") @@ -688,7 +689,7 @@ impl Binwalk { /// .display() /// .to_string(); /// - /// let file_data = common::read_file(&target_path, false).expect("Failed to read file data"); + /// 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), @@ -698,7 +699,7 @@ impl Binwalk { /// None, /// false)?; /// - /// let analysis_results = binwalker.analyze(&file_data, &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); @@ -709,22 +710,24 @@ 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( + pub fn analyze_buf( &self, file_data: &[u8], - target_file: &String, + 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() }; // Scan file data for signatures - debug!("Analysis start: {}", target_file); + 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 @@ -734,19 +737,74 @@ impl Binwalk { "Submitting {} signature results to extractor", results.file_map.len() ); - results.extractions = self.extract(file_data, target_file, &results.file_map); + 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/common.rs b/src/common.rs index 3e919b18d..111e7c1de 100644 --- a/src/common.rs +++ b/src/common.rs @@ -4,51 +4,75 @@ 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_file; +/// use binwalk::common::read_input; /// -/// let file_data = read_file("/etc/passwd", false)?; +/// let file_data = read_input("/etc/passwd", false)?; /// assert!(file_data.len() > 0); /// # Ok(()) /// # } _doctest_main_src_common_rs_11_0(); } /// ``` -pub fn read_file(file: impl Into, stdin: bool) -> Result, std::io::Error> { +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_48_0(); } +/// ``` +pub fn read_file(file: impl Into) -> Result, std::io::Error> { let mut file_data = Vec::new(); let file_path = file.into(); - if stdin { - match std::io::stdin().read_to_end(&mut file_data) { + match File::open(&file_path) { + Err(e) => { + error!("Failed to open file {}: {}", file_path, e); + Err(e) + } + Ok(mut fp) => match fp.read_to_end(&mut file_data) { Err(e) => { - error!("Failed to read data from stdin: {}", e); + error!("Failed to read file {} into memory: {}", file_path, e); Err(e) } - Ok(nbytes) => { - debug!("Loaded {} bytes from stdin", nbytes); + Ok(file_size) => { + debug!("Loaded {} bytes from {}", file_size, file_path); Ok(file_data) } - } - } else { - match File::open(&file_path) { - Err(e) => { - error!("Failed to open file {}: {}", file_path, e); - Err(e) - } - Ok(mut fp) => match fp.read_to_end(&mut file_data) { - Err(e) => { - error!("Failed to read file {} into memory: {}", file_path, e); - Err(e) - } - Ok(file_size) => { - debug!("Loaded {} bytes from {}", file_size, file_path); - Ok(file_data) - } - }, - } + }, } } diff --git a/src/entropy.rs b/src/entropy.rs index b44ffbb8f..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::*; @@ -87,7 +87,7 @@ pub fn plot(file_path: impl Into, stdin: bool) -> Result = vec![]; // Calculate the entropy for each file block diff --git a/src/main.rs b/src/main.rs index 9ab4bfbc5..ba903986b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -234,10 +234,8 @@ fn main() { json_logger.close(); - // If BINWALK_RM_SYMLINK env var was set, or if data was read from stdin, delete the base_target_file symlink - if (cliargs.carve || cliargs.extract) - && (cliargs.stdin || std::env::var(BINWALK_RM_SYMLINK).is_ok()) - { + // If BINWALK_RM_SYMLINK env var was set, delete the base_target_file symlink + if (cliargs.carve || cliargs.extract) && std::env::var(BINWALK_RM_SYMLINK).is_ok() { if let Err(e) = std::fs::remove_file(&binwalker.base_target_file) { error!( "Request to remove extraction symlink file {} failed: {}", @@ -291,7 +289,7 @@ fn spawn_worker( ) { pool.execute(move || { // Read in file data - let file_data = match common::read_file(&target_file, stdin) { + let file_data = match common::read_input(&target_file, stdin) { Err(_) => { error!("Failed to read {} data", target_file); b"".to_vec() @@ -300,7 +298,7 @@ fn spawn_worker( }; // Analyze target file, with extraction, if specified - let results = bw.analyze(&file_data, &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 { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 861771c72..0ff9a99a9 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,4 +1,4 @@ -use binwalk::{common, AnalysisResults, Binwalk}; +use binwalk::{AnalysisResults, Binwalk}; /// Convenience function for running an integration test against the specified file, with the provided signature filter. /// Assumes that there will be one signature result and one extraction result at file offset 0. @@ -55,9 +55,6 @@ pub fn run_binwalk(signature_filter: &str, file_name: &str) -> AnalysisResults { // Delete the output directory, if it exists let _ = std::fs::remove_dir_all(&output_directory); - // Read in file data - let file_data = common::read_file(&file_path, false).expect("Failed to read test file"); - // Configure binwalk let binwalker = Binwalk::configure( Some(file_path), @@ -70,7 +67,7 @@ pub fn run_binwalk(signature_filter: &str, file_name: &str) -> AnalysisResults { .expect("Binwalk initialization failed"); // Run analysis - let results = binwalker.analyze(&file_data, &binwalker.base_target_file, true); + let results = binwalker.analyze(&binwalker.base_target_file, true); // Clean up the output directory let _ = std::fs::remove_dir_all(output_directory);