Skip to content

Commit 4347671

Browse files
committed
[WARP] Let BNDB processing optionally skip analysis updates
This helps when you have analyzed large binaries and want to create signatures after-the-fact, skipping analysis. This can speed up the time to create large datasets drastically.
1 parent bbda267 commit 4347671

File tree

2 files changed

+75
-10
lines changed

2 files changed

+75
-10
lines changed

plugins/warp/src/plugin/project.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::processor::{
22
new_processing_state_background_thread, CompressionTypeField, FileDataKindField,
3-
FileFilterField, WarpFileProcessor,
3+
FileFilterField, ProcessingFileState, RequestAnalysisField, WarpFileProcessor,
44
};
55
use crate::report::{ReportGenerator, ReportKindField};
66
use binaryninja::background_task::BackgroundTask;
@@ -30,6 +30,7 @@ impl CreateSignaturesForm {
3030
form.add_field(Self::compression_type_field());
3131
form.add_field(Self::save_individual_files_field());
3232
form.add_field(Self::skip_existing_warp_files_field());
33+
form.add_field(Self::request_analysis_field());
3334
form.add_field(Self::processing_thread_count_field());
3435
Self { form }
3536
}
@@ -46,7 +47,7 @@ impl CreateSignaturesForm {
4647
FileFilterField::to_field()
4748
}
4849

49-
pub fn file_filter(&self) -> Option<Regex> {
50+
pub fn file_filter(&self) -> Option<Result<Regex, regex::Error>> {
5051
FileFilterField::from_form(&self.form)
5152
}
5253

@@ -100,6 +101,14 @@ impl CreateSignaturesForm {
100101
}
101102
}
102103

104+
pub fn request_analysis_field() -> FormInputField {
105+
RequestAnalysisField::default().to_field()
106+
}
107+
108+
pub fn request_analysis(&self) -> RequestAnalysisField {
109+
RequestAnalysisField::from_form(&self.form).unwrap_or_default()
110+
}
111+
103112
pub fn processing_thread_count_field() -> FormInputField {
104113
let default = rayon::current_num_threads();
105114
FormInputField::Integer {
@@ -136,6 +145,7 @@ impl CreateSignatures {
136145
let compression_type = form.compression_type();
137146
let save_individual_files = form.save_individual_files();
138147
let skip_existing_warp_files = form.skip_existing_warp_files();
148+
let request_analysis = form.request_analysis();
139149
let processing_thread_count = form.processing_thread_count();
140150

141151
// Save the warp file to the project.
@@ -189,7 +199,8 @@ impl CreateSignatures {
189199
let mut processor = WarpFileProcessor::new()
190200
.with_file_data(file_data_kind)
191201
.with_compression_type(compression_type)
192-
.with_skip_warp_files(skip_existing_warp_files);
202+
.with_skip_warp_files(skip_existing_warp_files)
203+
.with_request_analysis(request_analysis == RequestAnalysisField::Yes);
193204

194205
if save_individual_files {
195206
processor = processor.with_processed_file_callback(save_individual_files_cb);
@@ -198,7 +209,18 @@ impl CreateSignatures {
198209
// Construct the user-supplied file filter. This will filter files in the project only, files
199210
// in an archive will be considered a part of the archive file.
200211
if let Some(filter) = form.file_filter() {
201-
processor = processor.with_file_filter(filter);
212+
match filter {
213+
Ok(f) => {
214+
processor = processor.with_file_filter(f);
215+
}
216+
Err(err) => {
217+
log::error!("Failed to parse file filter: {}", err);
218+
log::error!(
219+
"Consider using a substring instead of a glob pattern, e.g. *.exe => exe"
220+
);
221+
return;
222+
}
223+
}
202224
}
203225

204226
// This thread will show the state in a background task.
@@ -234,7 +256,15 @@ impl CreateSignatures {
234256
log::error!("Failed to process project: {}", e);
235257
}
236258
});
237-
log::info!("Processing project files took: {:?}", start.elapsed());
259+
260+
let processed_file_count = processor
261+
.state()
262+
.files_with_state(ProcessingFileState::Processed);
263+
log::info!(
264+
"Processing {} project files took: {:?}",
265+
processed_file_count,
266+
start.elapsed()
267+
);
238268
// Reset the worker thread count to the user specified; either way it will not persist.
239269
set_worker_thread_count(previous_worker_thread_count);
240270
// Tells the processing state thread to finish.

plugins/warp/src/processor.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl FileFilterField {
9090
}
9191
}
9292

93-
pub fn from_form(form: &Form) -> Option<Regex> {
93+
pub fn from_form(form: &Form) -> Option<Result<Regex, regex::Error>> {
9494
let field = form.get_field_with_name("File Filter")?;
9595
let field_value = field.try_value_string()?;
9696

@@ -103,7 +103,7 @@ impl FileFilterField {
103103
format!(".*{}.*", regex::escape(&field_value))
104104
};
105105

106-
Regex::new(&pattern).ok()
106+
Some(Regex::new(&pattern))
107107
}
108108
}
109109

@@ -214,6 +214,32 @@ impl SaveReportToDiskField {
214214
}
215215
}
216216

217+
#[derive(Debug, Clone, Copy, PartialEq, Default)]
218+
pub enum RequestAnalysisField {
219+
No,
220+
#[default]
221+
Yes,
222+
}
223+
224+
impl RequestAnalysisField {
225+
pub fn to_field(&self) -> FormInputField {
226+
FormInputField::Checkbox {
227+
prompt: "Request Analysis for BNDB's".to_string(),
228+
default: Some(true),
229+
value: false,
230+
}
231+
}
232+
233+
pub fn from_form(form: &Form) -> Option<Self> {
234+
let field = form.get_field_with_name("Request Analysis for BNDB's")?;
235+
let field_value = field.try_value_int()?;
236+
match field_value {
237+
1 => Some(Self::Yes),
238+
_ => Some(Self::No),
239+
}
240+
}
241+
}
242+
217243
#[derive(Debug, Clone, Copy, PartialEq, Default)]
218244
pub enum CompressionTypeField {
219245
None,
@@ -511,6 +537,10 @@ impl WarpFileProcessor {
511537
.filter_map(|res| match res {
512538
Ok(result) => Some(Ok(result)),
513539
Err(ProcessingError::Cancelled) => Some(Err(ProcessingError::Cancelled)),
540+
Err(ProcessingError::NoPathToProjectFile(path)) => {
541+
log::debug!("Skipping non-pulled project file: {:?}", path);
542+
None
543+
}
514544
Err(ProcessingError::SkippedFile(path)) => {
515545
log::debug!("Skipping project file: {:?}", path);
516546
None
@@ -586,15 +616,19 @@ impl WarpFileProcessor {
586616
if file_cache_path.exists() {
587617
// TODO: Update analysis and wait option
588618
log::debug!("Analysis database found in cache: {:?}", file_cache_path);
589-
binaryninja::load_with_options(&file_cache_path, true, Some(settings_str))
619+
binaryninja::load_with_options(
620+
&file_cache_path,
621+
self.request_analysis,
622+
Some(settings_str),
623+
)
590624
} else {
591625
log::debug!("No database found in cache: {:?}", file_cache_path);
592-
binaryninja::load_with_options(&path, true, Some(settings_str))
626+
binaryninja::load_with_options(&path, self.request_analysis, Some(settings_str))
593627
}
594628
}
595629
None => {
596630
// Processor is not caching analysis
597-
binaryninja::load_with_options(&path, true, Some(settings_str))
631+
binaryninja::load_with_options(&path, self.request_analysis, Some(settings_str))
598632
}
599633
}
600634
.ok_or(ProcessingError::BinaryViewLoad(path.clone()))?;
@@ -904,6 +938,7 @@ impl Debug for WarpFileProcessor {
904938
.field("state", &self.state)
905939
.field("cache_path", &self.cache_path)
906940
.field("analysis_settings", &self.analysis_settings)
941+
.field("request_analysis", &self.request_analysis)
907942
.finish()
908943
}
909944
}

0 commit comments

Comments
 (0)