Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for reading from stdin #785

Merged
merged 3 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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