Skip to content

Commit

Permalink
Merge pull request #785 from ReFirmLabs/stdin
Browse files Browse the repository at this point in the history
Added support for reading from stdin
  • Loading branch information
devttys0 authored Nov 30, 2024
2 parents 58c3dc3 + 052c318 commit a47c0df
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 69 deletions.
118 changes: 89 additions & 29 deletions src/binwalk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,9 +601,10 @@ impl Binwalk {
pub fn extract(
&self,
file_data: &[u8],
file_path: &String,
file_name: impl Into<String>,
file_map: &Vec<signatures::common::SignatureResult>,
) -> HashMap<String, extractors::common::ExtractionResult> {
let file_path = file_name.into();
let mut extraction_results: HashMap<String, extractors::common::ExtractionResult> =
HashMap::new();

Expand All @@ -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!(
Expand Down Expand Up @@ -653,7 +654,7 @@ impl Binwalk {
// Re-run the extraction
extraction_result = extractors::common::execute(
file_data,
file_path,
&file_path,
&new_signature,
&extractor,
);
Expand All @@ -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<binwalk::Binwalk, binwalk::BinwalkError> {
/// use binwalk::Binwalk;
/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_672_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
/// use binwalk::{Binwalk, common};
///
/// let target_path = std::path::Path::new("tests")
/// .join("inputs")
Expand All @@ -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()),
Expand All @@ -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);
Expand All @@ -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<String>,
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<binwalk::Binwalk, binwalk::BinwalkError> {
/// 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<String>, 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<String, std::io::Error> {
// Create the output directory, equivalent of mkdir -p
match fs::create_dir_all(extraction_directory) {
Expand Down
4 changes: 4 additions & 0 deletions src/cliparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
41 changes: 39 additions & 2 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn std::error::Error>> {
/// 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<String>, stdin: bool) -> Result<Vec<u8>, 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<Vec<u8>, 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<dyn std::error::Error>> {
/// 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<String>) -> Result<Vec<u8>, std::io::Error> {
let mut file_data = Vec::new();
Expand Down
6 changes: 3 additions & 3 deletions src/entropy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::common::read_file;
use crate::common::read_input;
use entropy::shannon_entropy;
use log::error;
use plotters::prelude::*;
Expand Down Expand Up @@ -58,7 +58,7 @@ fn blocks(data: &[u8]) -> Vec<BlockEntropy> {

/// Generate a plot of a file's entropy.
/// Will output a file to the current working directory with the name `<file_name>.png`.
pub fn plot(file_path: impl Into<String>) -> Result<FileEntropy, EntropyError> {
pub fn plot(file_path: impl Into<String>, stdin: bool) -> Result<FileEntropy, EntropyError> {
const FILE_EXTENSION: &str = "png";
const SHANNON_MAX_VALUE: i32 = 8;
const IMAGE_PIXEL_WIDTH: u32 = 2048;
Expand Down Expand Up @@ -87,7 +87,7 @@ pub fn plot(file_path: impl Into<String>) -> Result<FileEntropy, EntropyError> {
}

// 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
Expand Down
Loading

0 comments on commit a47c0df

Please sign in to comment.