summaryrefslogtreecommitdiff
path: root/plugins/bntl_utils/src/command
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/bntl_utils/src/command')
-rw-r--r--plugins/bntl_utils/src/command/create.rs277
-rw-r--r--plugins/bntl_utils/src/command/diff.rs103
-rw-r--r--plugins/bntl_utils/src/command/dump.rs52
-rw-r--r--plugins/bntl_utils/src/command/validate.rs78
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
+ }
+}