diff options
Diffstat (limited to 'plugins/bntl_utils/src/command')
| -rw-r--r-- | plugins/bntl_utils/src/command/create.rs | 277 | ||||
| -rw-r--r-- | plugins/bntl_utils/src/command/diff.rs | 103 | ||||
| -rw-r--r-- | plugins/bntl_utils/src/command/dump.rs | 52 | ||||
| -rw-r--r-- | plugins/bntl_utils/src/command/validate.rs | 78 |
4 files changed, 510 insertions, 0 deletions
diff --git a/plugins/bntl_utils/src/command/create.rs b/plugins/bntl_utils/src/command/create.rs new file mode 100644 index 00000000..41b5517a --- /dev/null +++ b/plugins/bntl_utils/src/command/create.rs @@ -0,0 +1,277 @@ +use crate::command::{InputDirectoryField, OutputDirectoryField}; +use crate::process::{new_processing_state_background_thread, TypeLibProcessor}; +use crate::validate::TypeLibValidater; +use binaryninja::background_task::BackgroundTask; +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::command::{Command, ProjectCommand}; +use binaryninja::interaction::{Form, FormInputField, MessageBoxButtonSet, MessageBoxIcon}; +use binaryninja::platform::Platform; +use binaryninja::project::Project; +use binaryninja::types::TypeLibrary; +use std::thread; + +pub struct CreateFromCurrentView; + +impl Command for CreateFromCurrentView { + fn action(&self, view: &BinaryView) { + let mut form = Form::new("Create From View"); + // TODO: The choice to select what types to include + form.add_field(OutputDirectoryField::field()); + if !form.prompt() { + return; + } + let output_dir = OutputDirectoryField::from_form(&form).unwrap(); + let Some(default_platform) = view.default_platform() else { + tracing::error!("No default platform set for view"); + return; + }; + + let file_path = view.file().file_path(); + let file_name = file_path.file_name().unwrap_or_default().to_string_lossy(); + let processor = TypeLibProcessor::new(&file_name, &default_platform.name()); + let data = match processor.process_view(file_path, view) { + Ok(data) => data, + Err(err) => { + tracing::error!("Failed to process view: {}", err); + return; + } + } + .prune(); + + let attached_libraries = view + .type_libraries() + .iter() + .map(|t| t.to_owned()) + .chain(data.type_libraries.iter().map(|t| t.to_owned())) + .collect::<Vec<_>>(); + let mut validator = TypeLibValidater::new() + .with_platform(&default_platform) + .with_type_libraries(attached_libraries); + + for type_library in data.type_libraries { + let output_path = output_dir.join(format!("{}.bntl", type_library.name())); + + let validation_result = validator.validate(&type_library); + if !validation_result.issues.is_empty() { + tracing::error!( + "Found {} issues in type library '{}'", + validation_result.issues.len(), + type_library.name() + ); + match validation_result.render_report() { + Ok(rendered) => { + view.show_html_report(&type_library.name(), &rendered, ""); + if let Err(e) = std::fs::write(output_path.with_extension("html"), rendered) + { + tracing::error!( + "Failed to write validation report to {}: {}", + output_path.display(), + e + ); + } + } + Err(err) => tracing::error!("Failed to render validation report: {}", err), + } + } + + if type_library.write_to_file(&output_path) { + tracing::info!( + "Created type library '{}': {}", + type_library.name(), + output_path.display() + ); + } else { + tracing::error!("Failed to write type library to {}", output_path.display()); + } + } + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} + +pub struct NameField; + +impl NameField { + pub fn field() -> FormInputField { + FormInputField::TextLine { + prompt: "Dependency Name".to_string(), + default: Some("foo.dll".to_string()), + value: None, + } + } + + pub fn from_form(form: &Form) -> Option<String> { + let field = form.get_field_with_name("Dependency Name")?; + field.try_value_string() + } +} + +pub struct PlatformField; + +impl PlatformField { + pub fn field() -> FormInputField { + FormInputField::TextLine { + prompt: "Platform Name".to_string(), + default: Some("windows-x86_64".to_string()), + value: None, + } + } + + pub fn from_form(form: &Form) -> Option<String> { + let field = form.get_field_with_name("Platform Name")?; + field.try_value_string() + } +} + +pub struct CreateFromDirectory; + +impl CreateFromDirectory { + pub fn execute() { + let mut form = Form::new("Create From Directory"); + // TODO: The choice to select what types to include + form.add_field(InputDirectoryField::field()); + form.add_field(PlatformField::field()); + form.add_field(NameField::field()); + form.add_field(OutputDirectoryField::field()); + if !form.prompt() { + return; + } + let input_dir = InputDirectoryField::from_form(&form).unwrap(); + let platform_name = PlatformField::from_form(&form).unwrap(); + let default_name = NameField::from_form(&form).unwrap(); + let output_dir = OutputDirectoryField::from_form(&form).unwrap(); + + let Some(default_platform) = Platform::by_name(&platform_name) else { + tracing::error!("Invalid platform name: {}", platform_name); + return; + }; + + let processor = TypeLibProcessor::new(&default_name, &default_platform.name()); + + let background_task = BackgroundTask::new("Processing started...", true); + new_processing_state_background_thread(background_task.clone(), processor.state()); + let data = processor.process_directory(&input_dir); + background_task.finish(); + + let pruned_data = match data { + // Prune off empty type libraries, no need to save them. + Ok(data) => data.prune(), + Err(err) => { + binaryninja::interaction::show_message_box( + "Failed to process directory", + &err.to_string(), + MessageBoxButtonSet::OKButtonSet, + MessageBoxIcon::ErrorIcon, + ); + tracing::error!("Failed to process directory: {}", err); + return; + } + }; + + for type_library in pruned_data.type_libraries { + // Place the type libraries in a folder with the architecture name, as that is necessary + // information for the user to correctly place the following type libraries in the user directory. + let arch_output_path = output_dir.join(type_library.arch().name()); + let _ = std::fs::create_dir_all(&arch_output_path); + let output_path = arch_output_path.join(format!("{}.bntl", type_library.name())); + if type_library.write_to_file(&output_path) { + tracing::info!( + "Created type library '{}': {}", + type_library.name(), + output_path.display() + ); + } else { + tracing::error!("Failed to write type library to {}", output_path.display()); + } + } + } +} + +impl Command for CreateFromDirectory { + fn action(&self, _view: &BinaryView) { + thread::spawn(move || { + CreateFromDirectory::execute(); + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} + +pub struct CreateFromProject; + +impl CreateFromProject { + pub fn execute(project: &Project) { + let mut form = Form::new("Create From Project"); + // TODO: The choice to select what types to include + form.add_field(PlatformField::field()); + form.add_field(NameField::field()); + form.add_field(OutputDirectoryField::field()); + if !form.prompt() { + return; + } + let platform_name = PlatformField::from_form(&form).unwrap(); + let default_name = NameField::from_form(&form).unwrap(); + let output_dir = OutputDirectoryField::from_form(&form).unwrap(); + + let Some(default_platform) = Platform::by_name(&platform_name) else { + tracing::error!("Invalid platform name: {}", platform_name); + return; + }; + + let processor = TypeLibProcessor::new(&default_name, &default_platform.name()); + + let background_task = BackgroundTask::new("Processing started...", true); + new_processing_state_background_thread(background_task.clone(), processor.state()); + let data = processor.process_project(&project); + background_task.finish(); + + let mut finalized_data = match data { + // Prune off empty type libraries, no need to save them. + Ok(data) => data.finalized(&default_name), + Err(err) => { + binaryninja::interaction::show_message_box( + "Failed to process project", + &err.to_string(), + MessageBoxButtonSet::OKButtonSet, + MessageBoxIcon::ErrorIcon, + ); + tracing::error!("Failed to process project: {}", err); + return; + } + }; + + for type_library in finalized_data.type_libraries { + // Place the type libraries in a folder with the architecture name, as that is necessary + // information for the user to correctly place the following type libraries in the user directory. + let arch_output_path = output_dir.join(type_library.arch().name()); + let _ = std::fs::create_dir_all(&arch_output_path); + let output_path = arch_output_path.join(format!("{}.bntl", type_library.name())); + if type_library.write_to_file(&output_path) { + tracing::info!( + "Created type library '{}': {}", + type_library.name(), + output_path.display() + ); + } else { + tracing::error!("Failed to write type library to {}", output_path.display()); + } + } + } +} + +impl ProjectCommand for CreateFromProject { + fn action(&self, project: &Project) { + let owned_project = project.to_owned(); + thread::spawn(move || { + CreateFromProject::execute(&owned_project); + }); + } + + fn valid(&self, _project: &Project) -> bool { + true + } +} diff --git a/plugins/bntl_utils/src/command/diff.rs b/plugins/bntl_utils/src/command/diff.rs new file mode 100644 index 00000000..ae93dafe --- /dev/null +++ b/plugins/bntl_utils/src/command/diff.rs @@ -0,0 +1,103 @@ +use crate::command::OutputDirectoryField; +use crate::diff::TILDiff; +use binaryninja::background_task::BackgroundTask; +use binaryninja::binary_view::BinaryView; +use binaryninja::command::Command; +use binaryninja::interaction::{Form, FormInputField}; +use binaryninja::types::TypeLibrary; +use std::path::PathBuf; +use std::thread; + +pub struct InputFileAField; + +impl InputFileAField { + pub fn field() -> FormInputField { + FormInputField::OpenFileName { + prompt: "Library A".to_string(), + // TODO: This is called extension but is really a filter. + extension: Some("*.bntl".to_string()), + default: None, + value: None, + } + } + + pub fn from_form(form: &Form) -> Option<PathBuf> { + let field = form.get_field_with_name("Library A")?; + let field_value = field.try_value_string()?; + Some(PathBuf::from(field_value)) + } +} + +pub struct InputFileBField; + +impl InputFileBField { + pub fn field() -> FormInputField { + FormInputField::OpenFileName { + prompt: "Library B".to_string(), + // TODO: This is called extension but is really a filter. + extension: Some("*.bntl".to_string()), + default: None, + value: None, + } + } + + pub fn from_form(form: &Form) -> Option<PathBuf> { + let field = form.get_field_with_name("Library B")?; + let field_value = field.try_value_string()?; + Some(PathBuf::from(field_value)) + } +} + +pub struct Diff; + +impl Diff { + pub fn execute() { + let mut form = Form::new("Diff type libraries"); + form.add_field(InputFileAField::field()); + form.add_field(InputFileBField::field()); + form.add_field(OutputDirectoryField::field()); + if !form.prompt() { + return; + } + let a_path = InputFileAField::from_form(&form).unwrap(); + let b_path = InputFileBField::from_form(&form).unwrap(); + let output_dir = OutputDirectoryField::from_form(&form).unwrap(); + + let _bg_task = BackgroundTask::new("Diffing type libraries...", false).enter(); + let Some(type_lib_a) = TypeLibrary::load_from_file(&a_path) else { + tracing::error!("Failed to load type library: {}", a_path.display()); + return; + }; + let Some(type_lib_b) = TypeLibrary::load_from_file(&b_path) else { + tracing::error!("Failed to load type library: {}", b_path.display()); + return; + }; + + let diff_result = match TILDiff::new().diff((&a_path, &type_lib_a), (&b_path, &type_lib_b)) + { + Ok(diff_result) => diff_result, + Err(err) => { + tracing::error!("Failed to diff type libraries: {}", err); + return; + } + }; + tracing::info!("Similarity Ratio: {}", diff_result.ratio); + let output_path = output_dir + .join(type_lib_a.dependency_name()) + .with_extension("diff"); + std::fs::write(&output_path, diff_result.diff).unwrap(); + tracing::info!("Diff written to: {}", output_path.display()); + } +} + +impl Command for Diff { + fn action(&self, _view: &BinaryView) { + thread::spawn(move || { + Diff::execute(); + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/bntl_utils/src/command/dump.rs b/plugins/bntl_utils/src/command/dump.rs new file mode 100644 index 00000000..4b68cd79 --- /dev/null +++ b/plugins/bntl_utils/src/command/dump.rs @@ -0,0 +1,52 @@ +use crate::command::{InputFileField, OutputDirectoryField}; +use crate::dump::TILDump; +use crate::helper::path_to_type_libraries; +use binaryninja::binary_view::BinaryView; +use binaryninja::command::Command; +use binaryninja::interaction::Form; +use binaryninja::types::TypeLibrary; + +pub struct Dump; + +impl Command for Dump { + // TODO: We need a command type that does not require a binary view. + fn action(&self, _view: &BinaryView) { + let mut form = Form::new("Dump to C Header"); + // TODO: The choice to select what to include? + form.add_field(InputFileField::field()); + form.add_field(OutputDirectoryField::field()); + if !form.prompt() { + return; + } + let output_dir = OutputDirectoryField::from_form(&form).unwrap(); + let input_path = InputFileField::from_form(&form).unwrap(); + + let type_lib = match TypeLibrary::load_from_file(&input_path) { + Some(type_lib) => type_lib, + None => { + tracing::error!("Failed to load type library from {}", input_path.display()); + return; + } + }; + + // TODO: Currently we collect input path dependencies from the platform and the parent directory. + let dependencies = path_to_type_libraries(input_path.parent().unwrap()); + let dump = match TILDump::new().with_type_libs(dependencies).dump(&type_lib) { + Ok(dump) => dump, + Err(err) => { + tracing::error!("Failed to dump type library: {}", err); + return; + } + }; + + let output_path = output_dir.join(format!("{}.h", type_lib.name())); + if let Err(e) = std::fs::write(&output_path, dump) { + tracing::error!("Failed to write dump to {}: {}", output_path.display(), e); + } + tracing::info!("Dump written to {}", output_path.display()); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/bntl_utils/src/command/validate.rs b/plugins/bntl_utils/src/command/validate.rs new file mode 100644 index 00000000..8f0095ae --- /dev/null +++ b/plugins/bntl_utils/src/command/validate.rs @@ -0,0 +1,78 @@ +use crate::helper::path_to_type_libraries; +use crate::validate::TypeLibValidater; +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::command::Command; +use binaryninja::interaction::get_open_filename_input; +use binaryninja::platform::Platform; +use binaryninja::types::TypeLibrary; + +pub struct Validate; + +impl Command for Validate { + fn action(&self, _view: &BinaryView) { + let Some(input_path) = + get_open_filename_input("Select a type library to validate", "*.bntl") + else { + return; + }; + + let type_lib = match TypeLibrary::load_from_file(&input_path) { + Some(type_lib) => type_lib, + None => { + tracing::error!("Failed to load type library from {}", input_path.display()); + return; + } + }; + + // Type libraries should always have at least one platform associated with them. + if type_lib.platform_names().is_empty() { + tracing::error!("Type library {} has no platforms!", input_path.display()); + return; + } + + // TODO: Currently we collect input path dependencies from the platform and the parent directory. + let dependencies = path_to_type_libraries(input_path.parent().unwrap()); + + let validator = TypeLibValidater::new().with_type_libraries(dependencies); + // Validate for every platform so that we can find issues in lesser used platforms. + for platform_name in &type_lib.platform_names() { + let Some(platform) = Platform::by_name(platform_name) else { + tracing::error!("Failed to find platform with name {}", platform_name); + continue; + }; + let results = validator + .clone() + .with_platform(&platform) + .validate(&type_lib); + if results.issues.is_empty() { + tracing::info!( + "No issues found for type library {} on platform {}", + type_lib.name(), + platform_name + ); + continue; + } + let rendered = match results.render_report() { + Ok(rendered) => rendered, + Err(err) => { + tracing::error!("Failed to render validation report: {}", err); + continue; + } + }; + let out_path = input_path.with_extension(format!("{}.html", platform_name)); + let out_name = format!("{} ({})", type_lib.name(), platform_name); + _view.show_html_report(&out_name, &rendered, ""); + if let Err(e) = std::fs::write(out_path, rendered) { + tracing::error!( + "Failed to write validation report to {}: {}", + input_path.display(), + e + ); + } + } + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} |
