Skip to content

Commit a47c0df

Browse files
authored
Merge pull request #785 from ReFirmLabs/stdin
Added support for reading from stdin
2 parents 58c3dc3 + 052c318 commit a47c0df

File tree

5 files changed

+185
-69
lines changed

5 files changed

+185
-69
lines changed

src/binwalk.rs

Lines changed: 89 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -601,9 +601,10 @@ impl Binwalk {
601601
pub fn extract(
602602
&self,
603603
file_data: &[u8],
604-
file_path: &String,
604+
file_name: impl Into<String>,
605605
file_map: &Vec<signatures::common::SignatureResult>,
606606
) -> HashMap<String, extractors::common::ExtractionResult> {
607+
let file_path = file_name.into();
607608
let mut extraction_results: HashMap<String, extractors::common::ExtractionResult> =
608609
HashMap::new();
609610

@@ -622,7 +623,7 @@ impl Binwalk {
622623
Some(_) => {
623624
// Run an extraction for this signature
624625
let mut extraction_result =
625-
extractors::common::execute(file_data, file_path, signature, &extractor);
626+
extractors::common::execute(file_data, &file_path, signature, &extractor);
626627

627628
if !extraction_result.success {
628629
debug!(
@@ -653,7 +654,7 @@ impl Binwalk {
653654
// Re-run the extraction
654655
extraction_result = extractors::common::execute(
655656
file_data,
656-
file_path,
657+
&file_path,
657658
&new_signature,
658659
&extractor,
659660
);
@@ -669,13 +670,13 @@ impl Binwalk {
669670
extraction_results
670671
}
671672

672-
/// Analyze a file and optionally extract the file contents.
673+
/// Analyze a data buffer and optionally extract the file contents.
673674
///
674675
/// ## Example
675676
///
676677
/// ```
677-
/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_624_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
678-
/// use binwalk::Binwalk;
678+
/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_672_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
679+
/// use binwalk::{Binwalk, common};
679680
///
680681
/// let target_path = std::path::Path::new("tests")
681682
/// .join("inputs")
@@ -688,6 +689,8 @@ impl Binwalk {
688689
/// .display()
689690
/// .to_string();
690691
///
692+
/// let file_data = common::read_file(&target_path).expect("Failed to read file data");
693+
///
691694
/// # std::fs::remove_dir_all(&extraction_directory);
692695
/// let binwalker = Binwalk::configure(Some(target_path),
693696
/// Some(extraction_directory.clone()),
@@ -696,7 +699,7 @@ impl Binwalk {
696699
/// None,
697700
/// false)?;
698701
///
699-
/// let analysis_results = binwalker.analyze(&binwalker.base_target_file, true);
702+
/// let analysis_results = binwalker.analyze_buf(&file_data, &binwalker.base_target_file, true);
700703
///
701704
/// assert_eq!(analysis_results.file_map.len(), 1);
702705
/// assert_eq!(analysis_results.extractions.len(), 1);
@@ -707,44 +710,101 @@ impl Binwalk {
707710
/// .exists(), true);
708711
/// # std::fs::remove_dir_all(&extraction_directory);
709712
/// # Ok(binwalker)
710-
/// # } _doctest_main_src_binwalk_rs_624_0(); }
713+
/// # } _doctest_main_src_binwalk_rs_672_0(); }
711714
/// ```
712-
pub fn analyze(&self, target_file: &String, do_extraction: bool) -> AnalysisResults {
715+
pub fn analyze_buf(
716+
&self,
717+
file_data: &[u8],
718+
target_file: impl Into<String>,
719+
do_extraction: bool,
720+
) -> AnalysisResults {
721+
let file_path = target_file.into();
722+
713723
// Return value
714724
let mut results: AnalysisResults = AnalysisResults {
715-
file_path: target_file.clone(),
725+
file_path: file_path.clone(),
716726
..Default::default()
717727
};
718728

719-
debug!("Analysis start: {}", target_file);
720-
721-
// Read file into memory
722-
if let Ok(file_data) = read_file(target_file) {
723-
// Scan file data for signatures
724-
info!("Scanning {}", target_file);
725-
results.file_map = self.scan(&file_data);
729+
// Scan file data for signatures
730+
debug!("Analysis start: {}", file_path);
731+
results.file_map = self.scan(file_data);
726732

727-
// Only extract if told to, and if there were some signatures found in this file
728-
if do_extraction && !results.file_map.is_empty() {
729-
// Extract everything we can
730-
debug!(
731-
"Submitting {} signature results to extractor",
732-
results.file_map.len()
733-
);
734-
results.extractions = self.extract(&file_data, target_file, &results.file_map);
735-
}
733+
// Only extract if told to, and if there were some signatures found in this file
734+
if do_extraction && !results.file_map.is_empty() {
735+
// Extract everything we can
736+
debug!(
737+
"Submitting {} signature results to extractor",
738+
results.file_map.len()
739+
);
740+
results.extractions = self.extract(file_data, &file_path, &results.file_map);
736741
}
737742

738-
debug!("Analysis end: {}", target_file);
743+
debug!("Analysis end: {}", file_path);
739744

740745
results
741746
}
747+
748+
/// Analyze a file on disk and optionally extract its contents.
749+
///
750+
/// ## Example
751+
///
752+
/// ```
753+
/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_745_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {
754+
/// use binwalk::Binwalk;
755+
///
756+
/// let target_path = std::path::Path::new("tests")
757+
/// .join("inputs")
758+
/// .join("gzip.bin")
759+
/// .display()
760+
/// .to_string();
761+
///
762+
/// let extraction_directory = std::path::Path::new("tests")
763+
/// .join("extractions")
764+
/// .display()
765+
/// .to_string();
766+
///
767+
/// # std::fs::remove_dir_all(&extraction_directory);
768+
/// let binwalker = Binwalk::configure(Some(target_path),
769+
/// Some(extraction_directory.clone()),
770+
/// None,
771+
/// None,
772+
/// None,
773+
/// false)?;
774+
///
775+
/// let analysis_results = binwalker.analyze(&binwalker.base_target_file, true);
776+
///
777+
/// assert_eq!(analysis_results.file_map.len(), 1);
778+
/// assert_eq!(analysis_results.extractions.len(), 1);
779+
/// assert_eq!(std::path::Path::new(&extraction_directory)
780+
/// .join("gzip.bin.extracted")
781+
/// .join("0")
782+
/// .join("decompressed.bin")
783+
/// .exists(), true);
784+
/// # std::fs::remove_dir_all(&extraction_directory);
785+
/// # Ok(binwalker)
786+
/// # } _doctest_main_src_binwalk_rs_745_0(); }
787+
/// ```
788+
#[allow(dead_code)]
789+
pub fn analyze(&self, target_file: impl Into<String>, do_extraction: bool) -> AnalysisResults {
790+
let file_path = target_file.into();
791+
792+
let file_data = match read_file(&file_path) {
793+
Err(_) => {
794+
error!("Failed to read data from {}", file_path);
795+
b"".to_vec()
796+
}
797+
Ok(data) => data,
798+
};
799+
800+
self.analyze_buf(&file_data, &file_path, do_extraction)
801+
}
742802
}
743803

744804
/// Initializes the extraction output directory
745805
fn init_extraction_directory(
746-
target_file: &String,
747-
extraction_directory: &String,
806+
target_file: &str,
807+
extraction_directory: &str,
748808
) -> Result<String, std::io::Error> {
749809
// Create the output directory, equivalent of mkdir -p
750810
match fs::create_dir_all(extraction_directory) {

src/cliparser.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ pub struct CliArgs {
77
#[arg(short = 'L', long)]
88
pub list: bool,
99

10+
/// Read data from standard input
11+
#[arg(short, long)]
12+
pub stdin: bool,
13+
1014
/// Supress output to stdout
1115
#[arg(short, long)]
1216
pub quiet: bool,

src/common.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,55 @@ use log::{debug, error};
44
use std::fs::File;
55
use std::io::Read;
66

7-
/// Read a file into memory and return its contents.
7+
/// Read a data into memory, either from disk or from stdin, and return its contents.
88
///
99
/// ## Example
1010
///
1111
/// ```
1212
/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_common_rs_11_0() -> Result<(), Box<dyn std::error::Error>> {
13+
/// use binwalk::common::read_input;
14+
///
15+
/// let file_data = read_input("/etc/passwd", false)?;
16+
/// assert!(file_data.len() > 0);
17+
/// # Ok(())
18+
/// # } _doctest_main_src_common_rs_11_0(); }
19+
/// ```
20+
pub fn read_input(file: impl Into<String>, stdin: bool) -> Result<Vec<u8>, std::io::Error> {
21+
if stdin {
22+
read_stdin()
23+
} else {
24+
read_file(file)
25+
}
26+
}
27+
28+
/// Read data from standard input and return its contents.
29+
pub fn read_stdin() -> Result<Vec<u8>, std::io::Error> {
30+
let mut stdin_data = Vec::new();
31+
32+
match std::io::stdin().read_to_end(&mut stdin_data) {
33+
Err(e) => {
34+
error!("Failed to read data from stdin: {}", e);
35+
Err(e)
36+
}
37+
Ok(nbytes) => {
38+
debug!("Loaded {} bytes from stdin", nbytes);
39+
Ok(stdin_data)
40+
}
41+
}
42+
}
43+
44+
/// Read a file data into memory and return its contents.
45+
///
46+
/// ## Example
47+
///
48+
/// ```
49+
/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_common_rs_48_0() -> Result<(), Box<dyn std::error::Error>> {
1350
/// use binwalk::common::read_file;
1451
///
1552
/// let file_data = read_file("/etc/passwd")?;
1653
/// assert!(file_data.len() > 0);
1754
/// # Ok(())
18-
/// # } _doctest_main_src_common_rs_11_0(); }
55+
/// # } _doctest_main_src_common_rs_48_0(); }
1956
/// ```
2057
pub fn read_file(file: impl Into<String>) -> Result<Vec<u8>, std::io::Error> {
2158
let mut file_data = Vec::new();

src/entropy.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::common::read_file;
1+
use crate::common::read_input;
22
use entropy::shannon_entropy;
33
use log::error;
44
use plotters::prelude::*;
@@ -58,7 +58,7 @@ fn blocks(data: &[u8]) -> Vec<BlockEntropy> {
5858

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

8989
// Read in the target file data
90-
if let Ok(file_data) = read_file(&target_file) {
90+
if let Ok(file_data) = read_input(&target_file, stdin) {
9191
let mut points: Vec<(i32, i32)> = vec![];
9292

9393
// Calculate the entropy for each file block

0 commit comments

Comments
 (0)