1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
use binwalk::AnalysisResults;
use log::{debug, error, info};
use std::collections::VecDeque;
use std::panic;
use std::process;
use std::sync::mpsc;
use std::thread;
use std::time;
use threadpool::ThreadPool;
mod binwalk;
mod cliparser;
mod common;
mod display;
mod entropy;
mod extractors;
mod json;
mod magic;
mod signatures;
mod structures;
fn main() {
// Only use one thread if unable to auto-detect available core info
const DEFAULT_WORKER_COUNT: usize = 1;
// Number of seconds to wait before printing debug progress info
const PROGRESS_INTERVAL: u64 = 30;
// If this env var is set during extraction, the Binwalk.base_target_file symlink will
// be deleted at the end of extraction.
const BINWALK_RM_SYMLINK: &str = "BINWALK_RM_EXTRACTION_SYMLINK";
// Output directory for extracted files
let mut output_directory: Option<String> = None;
/*
* Maintain a queue of files waiting to be analyzed.
* Note that ThreadPool has its own internal queue so this may seem redundant, however,
* queuing a large number of jobs via the ThreadPool queue results in *massive* amounts
* of unecessary memory consumption, especially when recursively analyzing many files.
*/
let mut target_files = VecDeque::new();
// Statistics variables; keeps track of analyzed file count and total analysis run time
let mut file_count: usize = 0;
let run_time = time::Instant::now();
let mut last_progress_interval = time::Instant::now();
// Initialize logging
env_logger::init();
// Process command line aguments
let cliargs = cliparser::parse();
// If --list was specified, just display a list of signatures and return
if cliargs.list {
display::print_signature_list(cliargs.quiet, &magic::patterns());
return;
}
// If --list was not specified, a target file must be provided
if cliargs.file_name.is_none() {
panic!("No target file name specified! Try --help.");
}
// If entropy analysis was requested, generate the entropy graph and return
if cliargs.entropy {
display::print_plain(cliargs.quiet, "Calculating file entropy...");
if let Ok(entropy_results) = entropy::plot(cliargs.file_name.unwrap()) {
// Log entropy results to JSON file, if requested
json::log(
&cliargs.log,
json::JSONType::Entropy(entropy_results.clone()),
);
display::print_plain(cliargs.quiet, "entropy graph saved to: ");
display::println_plain(cliargs.quiet, &entropy_results.file);
} else {
panic!("Entropy analysis failed!");
}
return;
}
// If extraction was requested, we need to initialize the output directory
if cliargs.extract {
output_directory = Some(cliargs.directory);
}
// Initialize binwalk
let binwalker = binwalk::Binwalk::configure(
cliargs.file_name,
output_directory,
cliargs.include,
cliargs.exclude,
None,
cliargs.search_all,
)
.expect("Binwalk initialization failed");
// If the user specified --threads, honor that request; else, auto-detect available parallelism
let available_workers = cliargs.threads.unwrap_or_else(|| {
// Get CPU core info
match thread::available_parallelism() {
// In case of error use the default
Err(e) => {
error!("Failed to retrieve CPU core info: {e}");
DEFAULT_WORKER_COUNT
}
Ok(coreinfo) => coreinfo.get(),
}
});
// Sanity check the number of available worker threads
if available_workers < 1 {
panic!("No available worker threads!");
}
// Initialize thread pool
debug!(
"Initializing thread pool with {} workers",
available_workers
);
let workers = ThreadPool::new(available_workers);
let (worker_tx, worker_rx) = mpsc::channel();
/*
* Set a custom panic handler.
* This ensures that when any thread panics, the default panic handler will be invoked
* _and_ the entire process will exit with an error code.
*/
let default_panic_handler = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
default_panic_handler(panic_info);
process::exit(-1);
}));
debug!(
"Queuing initial target file: {}",
binwalker.base_target_file
);
// Queue the initial file path
target_files.insert(target_files.len(), binwalker.base_target_file.clone());
/*
* Main loop.
* Loop until all pending thread jobs are complete and there are no more files in the queue.
*/
while !target_files.is_empty() || workers.active_count() > 0 {
// If there are files waiting to be analyzed and there is at least one free thread in the pool
if !target_files.is_empty() && workers.active_count() < workers.max_count() {
// Get the next file path from the target_files queue
let target_file = target_files
.pop_front()
.expect("Failed to retrieve next file from the queue");
// Spawn a new worker for the new file
spawn_worker(
&workers,
binwalker.clone(),
target_file,
cliargs.extract,
worker_tx.clone(),
);
}
// Don't spin CPU cycles if there is no backlog of files to analyze
if target_files.is_empty() {
let sleep_time = time::Duration::from_millis(1);
thread::sleep(sleep_time);
}
// Some debug info on analysis progress
if last_progress_interval.elapsed().as_secs() >= PROGRESS_INTERVAL {
info!(
"Status: active worker threads: {}/{}, files waiting in queue: {}",
workers.active_count(),
workers.max_count(),
target_files.len()
);
last_progress_interval = time::Instant::now();
}
// Get response from a worker thread, if any
if let Ok(results) = worker_rx.try_recv() {
// Keep a tally of how many files have been analyzed
file_count += 1;
// Log analysis results to JSON file
json::log(&cliargs.log, json::JSONType::Analysis(results.clone()));
// Nothing found? Nothing else to do for this file.
if results.file_map.is_empty() {
debug!("Found no results for file {}", results.file_path);
continue;
}
// Print analysis results to screen
if should_display(&results, file_count, cliargs.verbose) {
display::print_analysis_results(cliargs.quiet, cliargs.extract, &results);
}
// If running recursively, add extraction results to list of files to analyze
if cliargs.matryoshka {
for (_signature_id, extraction_result) in results.extractions.into_iter() {
if !extraction_result.do_not_recurse {
for file_path in extractors::common::get_extracted_files(
&extraction_result.output_directory,
) {
debug!("Queuing {file_path} for analysis");
target_files.insert(target_files.len(), file_path.clone());
}
}
}
}
}
}
// If BINWALK_RM_SYMLINK env var was set, delete the base_target_file symlink
if 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: {}",
binwalker.base_target_file, e
);
}
}
// All done, show some basic statistics
display::print_stats(
cliargs.quiet,
run_time,
file_count,
binwalker.signature_count,
binwalker.patterns.len(),
);
}
/// Returns true if the specified results should be displayed to screen
fn should_display(results: &AnalysisResults, file_count: usize, verbose: bool) -> bool {
let mut display_results: bool = false;
/*
* For brevity, when analyzing more than one file only display subsequent files whose results
* contain signatures that we always want displayed, or which contain extractable signatures.
* This can be overridden with the --verbose command line flag.
*/
if file_count == 1 || verbose || !results.extractions.is_empty() {
display_results = true;
} else {
for signature in &results.file_map {
if signature.always_display {
display_results = true;
break;
}
}
}
display_results
}
/// Spawn a worker thread to analyze a file
fn spawn_worker(
pool: &ThreadPool,
bw: binwalk::Binwalk,
target_file: String,
do_extraction: bool,
worker_tx: mpsc::Sender<AnalysisResults>,
) {
pool.execute(move || {
// Analyze target file, with extraction, if specified
let results = bw.analyze(&target_file, do_extraction);
// Report file results back to main thread
if let Err(e) = worker_tx.send(results) {
panic!(
"Worker thread for {target_file} failed to send results back to main thread: {e}"
);
}
});
}