Skip to content
Open
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
16 changes: 16 additions & 0 deletions src/cliparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ pub struct CliArgs {
#[arg(short = 'E', long, conflicts_with = "extract")]
pub entropy: bool,

/// Set entropy block count
#[arg(short = 'N', long, default_value_t = 2048, requires = "entropy")]
pub block_count: usize,

/// Set entropy start offset
#[arg(long, requires = "entropy")]
pub start: Option<String>,

/// Set entropy end offset
#[arg(long, requires = "entropy")]
pub end: Option<String>,

/// Set entropy offset length
#[arg(long, requires = "entropy", conflicts_with = "end")]
pub length: Option<String>,

/// Save entropy graph as a PNG file
#[arg(short, long)]
pub png: Option<String>,
Expand Down
24 changes: 24 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,27 @@ pub fn is_offset_safe(

true
}

/// Parses a hexadecimal string representation into a `u64`.
///
/// Strips optional `0x` or `0X` prefixes before parsing.
///
/// ## Example
///
/// ```
/// use binwalk::common::parse_hex_u64;
///
/// assert_eq!(parse_hex_u64("0x1F4000"), Some(2_048_000));
/// assert_eq!(parse_hex_u64("1F4000"), Some(2_048_000));
/// assert_eq!(parse_hex_u64("0X0"), Some(0));
/// assert_eq!(parse_hex_u64("invalid"), None);
/// ```
pub fn parse_hex_u64(hex_str: &str) -> Option<u64> {
let clean_str = hex_str.trim_start_matches("0x").trim_start_matches("0X");

if clean_str.is_empty() {
return None;
}

u64::from_str_radix(clean_str, 16).ok()
}
49 changes: 42 additions & 7 deletions src/entropy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::common::read_input;
use crate::common::{parse_hex_u64, read_input};
use entropy::shannon_entropy;
use plotly::layout::{Axis, Layout};
use plotly::{ImageFormat, Plot, Scatter};
Expand All @@ -21,18 +21,49 @@ pub struct FileEntropy {
}

/// Splits the supplied data up into blocks and calculates the entropy of each block.
fn blocks(data: &[u8]) -> Vec<BlockEntropy> {
const BLOCK_COUNT: usize = 2048;
fn blocks(data: &[u8], block_count: usize, start: Option<String>, end: Option<String>, length: Option<String>) -> Vec<BlockEntropy> {
const BLOCK_COUNT_MAX: usize = 1;

let block_count = block_count.max(BLOCK_COUNT_MAX);

let mut offset: usize = 0;

let block_size = if data.len() < BLOCK_COUNT {
let start_off: usize = start
.as_deref()
.and_then(parse_hex_u64)
.map(|v| v as usize)
.unwrap_or(0);

let end_off: usize = if let Some(len_str) = length.as_deref() {
parse_hex_u64(len_str)
.map(|len| start_off.saturating_add(len as usize))
.unwrap_or(data.len())
.min(data.len())
} else if let Some(end_str) = end.as_deref() {
parse_hex_u64(end_str)
.map(|v| v as usize)
.unwrap_or(data.len())
.min(data.len())
} else {
data.len()
};

let data_slice: &[u8] = if start_off <= end_off {
data.get(start_off..end_off).unwrap_or(data)
} else {
data.len() / BLOCK_COUNT
data
};

let mut chunker = data.chunks(block_size);
// block_size usize enforces alignment so block_count can be a non-base2 multiple
let mut block_size = if data_slice.len() < block_count {
data_slice.len()
} else {
data_slice.len() / block_count
};

block_size = block_size.max(1);

let mut chunker = data_slice.chunks(block_size);
let mut entropy_blocks: Vec<BlockEntropy> = vec![];

loop {
Expand All @@ -59,6 +90,10 @@ fn blocks(data: &[u8]) -> Vec<BlockEntropy> {
pub fn plot(
file_path: impl Into<String>,
stdin: bool,
block_count: usize,
start: Option<String>,
end: Option<String>,
length: Option<String>,
out_file: Option<String>,
) -> Result<FileEntropy, EntropyError> {
let mut x: Vec<usize> = Vec::new();
Expand All @@ -72,7 +107,7 @@ pub fn plot(
// Read in the target file data
if let Ok(file_data) = read_input(&target_file, stdin) {
// Calculate the entropy of each file block
file_entropy.blocks = blocks(&file_data);
file_entropy.blocks = blocks(&file_data, block_count, start, end, length);

for block in &file_entropy.blocks {
x.push(block.start);
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ fn main() -> ExitCode {
display::print_plain(cliargs.quiet, "Calculating file entropy...");

if let Ok(entropy_results) =
entropy::plot(cliargs.file_name.unwrap(), cliargs.stdin, cliargs.png)
entropy::plot(cliargs.file_name.unwrap(), cliargs.stdin, cliargs.block_count,
cliargs.start, cliargs.end, cliargs.length, cliargs.png)
{
// Log entropy results to JSON file, if requested
json_logger.log(json::JSONType::Entropy(entropy_results.clone()));
Expand Down