From c6a241672823de220d3837d046de4e219e7a8c24 Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Wed, 25 Mar 2026 12:09:08 -0700 Subject: [PATCH 1/2] [IDB Import] Fix file dialog filter separating out each extension Caused the resulting dialog to have a drop down that you must select to get to *.i64, which is an extra unneeded step --- plugins/idb_import/src/commands/load_file.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/idb_import/src/commands/load_file.rs b/plugins/idb_import/src/commands/load_file.rs index f7dd92c55..67416e613 100644 --- a/plugins/idb_import/src/commands/load_file.rs +++ b/plugins/idb_import/src/commands/load_file.rs @@ -15,8 +15,10 @@ impl Command for LoadIDBFile { let mut form = Form::new("Load IDB File"); let mut default_path = PathBuf::from(&view.file().file_path()); default_path.set_extension("idb"); - let file_field = - LoadFileField::with_default("*.idb;;*.i64;;*.til", &default_path.to_string_lossy()); + let file_field = LoadFileField::with_default( + "IDA Files (*.idb *.i64 *.til)", + &default_path.to_string_lossy(), + ); form.add_field(file_field.field()); if !form.prompt() { return; From e0679a5eec25a74b3fbccc6dd8409eb0e5ec8b30 Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Wed, 25 Mar 2026 21:02:02 -0700 Subject: [PATCH 2/2] [IDB Import] Fix some misc bugs - In certain IDBs the loading base is zeroed but the info is not relative, in this case we now fallback to rebasing based off the lowest section address specified by `min_ea`. - In certain IDBs the info is relative, we ignore both the loading base and `min_ea` and compute the absolute address using the base address in the binary view - Fixed data exports being recognized as functions - Retrieve post, pre comments from dirtree - Fix mapping in extern section (which is tool specific in how it is setup) - Properly mark exported data as global binding --- plugins/idb_import/src/mapper.rs | 119 ++++++++++++++++++++++++++++--- plugins/idb_import/src/parse.rs | 117 ++++++++++++++++++++++++++---- 2 files changed, 213 insertions(+), 23 deletions(-) diff --git a/plugins/idb_import/src/mapper.rs b/plugins/idb_import/src/mapper.rs index 4301ca9c5..36c980dad 100644 --- a/plugins/idb_import/src/mapper.rs +++ b/plugins/idb_import/src/mapper.rs @@ -1,15 +1,19 @@ //! Map the IDB data we parsed into the [`BinaryView`]. -use crate::parse::{CommentInfo, FunctionInfo, IDBInfo, LabelInfo, NameInfo, SegmentInfo}; +use crate::parse::{ + BaseAddressInfo, CommentInfo, ExportInfo, FunctionInfo, IDBInfo, LabelInfo, NameInfo, + SegmentInfo, +}; use crate::translate::TILTranslator; use binaryninja::architecture::Architecture; use binaryninja::binary_view::{BinaryView, BinaryViewBase, BinaryViewExt}; use binaryninja::qualified_name::QualifiedName; use binaryninja::rc::Ref; use binaryninja::section::{SectionBuilder, Semantics}; -use binaryninja::symbol::{Symbol, SymbolType}; +use binaryninja::symbol::{Binding, Symbol, SymbolType}; use binaryninja::types::Type; use idb_rs::id0::SegmentType; +use idb_rs::til::TypeVariant; use std::collections::HashSet; /// Maps IDB data into a [`BinaryView`]. @@ -36,8 +40,28 @@ impl IDBMapper { // Rebase the address from ida -> binja without this rebased views will fail to map. let bn_base_address = view.start(); - let ida_base_address = id0.base_address.unwrap_or(bn_base_address); - let base_address_delta = bn_base_address.wrapping_sub(ida_base_address); + let base_address_delta = match id0.base_address { + // There is no base address in the IDA file, so we assume everything is relative and rebase. + BaseAddressInfo::None => bn_base_address, + BaseAddressInfo::BaseSegment(start_addr) => bn_base_address.wrapping_sub(start_addr), + BaseAddressInfo::BaseSection(section_addr) => { + let bn_section_addr = view + .sections() + .iter() + .min_by_key(|s| s.start()) + .map(|s| s.start()); + match bn_section_addr { + Some(bn_section) => bn_section.wrapping_sub(section_addr), + None => bn_base_address, + } + } + }; + + tracing::debug!( + "Rebasing for {:0x} with delta {:0x}", + bn_base_address, + base_address_delta + ); let rebase = |addr: u64| -> u64 { addr.wrapping_add(base_address_delta) }; for segment in &id0.segments { @@ -70,10 +94,16 @@ impl IDBMapper { self.map_func_to_view(view, &til_translator, &rebased_func); } + for export in &id0.exports { + let mut rebased_export = export.clone(); + rebased_export.address = rebase(export.address); + self.map_export_to_view(view, &til_translator, &rebased_export); + } + // TODO: The below undo and ignore is not thread safe, this means that the mapper itself // TODO: should be the only thing running at the time of the mapping process. let undo = view.file().begin_undo_actions(true); - for comment in &id0.comments { + for comment in &self.info.merged_comments() { let mut rebased_comment = comment.clone(); rebased_comment.address = rebase(comment.address); self.map_comment_to_view(view, &rebased_comment); @@ -103,11 +133,11 @@ impl IDBMapper { } match til_translator.translate_type_info(&ty.tinfo) { Ok(bn_ty) => { - tracing::debug!("Mapping type: {:?}", ty); + tracing::debug!("Mapping type: {}", ty.name); view.define_auto_type(&ty_name, "IDA", &bn_ty); } Err(err) => { - tracing::warn!("Failed to map type {:?}: {}", ty, err) + tracing::warn!("Failed to map type {}: {}", ty.name, err) } } } @@ -183,6 +213,11 @@ impl IDBMapper { pub fn map_segment_to_view(&self, view: &BinaryView, segment: &SegmentInfo) { let semantics = match segment.ty { SegmentType::Norm => Semantics::DefaultSection, + // One issue is that an IDA section named 'extern' is _actually_ a synthetic section, so we + // should not map it. + SegmentType::Xtrn if segment.name == "extern" => { + return; + } SegmentType::Xtrn => { // IDA definition of extern is an actual section like '.idata' whereas extern in BN // is a synthetic section, do NOT use [`Semantics::External`]. @@ -226,12 +261,62 @@ impl IDBMapper { view.add_section(section); } + pub fn map_export_to_view( + &self, + view: &BinaryView, + til_translator: &TILTranslator, + export: &ExportInfo, + ) { + let within_code_section = view + .sections_at(export.address) + .iter() + .find(|s| s.semantics() == Semantics::ReadOnlyCode) + .is_some(); + let is_func_ty = export + .ty + .as_ref() + .is_some_and(|ty| matches!(ty.type_variant, TypeVariant::Function(_))); + + if within_code_section && is_func_ty { + tracing::debug!("Mapping function export: {:0x}", export.address); + let func_info = FunctionInfo { + name: Some(export.name.clone()), + ty: export.ty.clone(), + address: export.address, + is_library: false, + is_no_return: false, + }; + self.map_func_to_view(view, til_translator, &func_info); + } else { + tracing::debug!("Mapping data export: {:0x}", export.address); + let name_info = NameInfo { + label: Some(export.name.clone()), + ty: export.ty.clone(), + address: export.address, + exported: true, + }; + self.map_name_to_view(view, til_translator, &name_info); + } + } + pub fn map_func_to_view( &self, view: &BinaryView, til_translator: &TILTranslator, func: &FunctionInfo, ) { + // We need to skip things that hit the extern section, since they do not have a bearing in the + // actual context of the binary, and can be derived differently between IDA and Binja. + let within_extern_section = view + .sections_at(func.address) + .iter() + .find(|s| s.semantics() == Semantics::External) + .is_some(); + if within_extern_section { + tracing::debug!("Skipping function in extern section: {:0x}", func.address); + return; + } + let Some(bn_func) = view.add_auto_function(func.address) else { tracing::warn!("Failed to add function for {:0x}", func.address); return; @@ -277,6 +362,18 @@ impl IDBMapper { til_translator: &TILTranslator, name: &NameInfo, ) { + // We need to skip things that hit the extern section, since they do not have a bearing in the + // actual context of the binary, and can be derived differently between IDA and Binja. + let within_extern_section = view + .sections_at(name.address) + .iter() + .find(|s| s.semantics() == Semantics::External) + .is_some(); + if within_extern_section { + tracing::debug!("Skipping name in extern section: {:0x}", name.address); + return; + } + // Currently, we only want to use name info to map data variables, so skip anything in code. let within_code_section = view .sections_at(name.address) @@ -289,7 +386,13 @@ impl IDBMapper { } if let Some(label) = &name.label { - let symbol = Symbol::builder(SymbolType::Data, &label, name.address).create(); + let binding = name + .exported + .then_some(Binding::Global) + .unwrap_or(Binding::None); + let symbol = Symbol::builder(SymbolType::Data, &label, name.address) + .binding(binding) + .create(); tracing::debug!("Mapping name label: {:0x} => {}", name.address, symbol); view.define_auto_symbol(&symbol); } diff --git a/plugins/idb_import/src/parse.rs b/plugins/idb_import/src/parse.rs index 72ac1baa7..95a2fdd0e 100644 --- a/plugins/idb_import/src/parse.rs +++ b/plugins/idb_import/src/parse.rs @@ -1,6 +1,6 @@ //! Parse the provided IDB / TIL file and extract information into a struct for further processing. -use idb_rs::addr_info::AddressInfo; +use idb_rs::addr_info::{all_address_info, AddressInfo}; use idb_rs::id0::function::{FuncIdx, FuncordsIdx, IDBFunctionType}; use idb_rs::id0::{ID0Section, Netdelta, SegmentType}; use idb_rs::id1::ID1Section; @@ -27,7 +27,13 @@ pub struct FunctionInfo { pub address: u64, pub is_library: bool, pub is_no_return: bool, - pub is_entry: bool, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ExportInfo { + pub name: String, + pub address: u64, + pub ty: Option, } #[derive(Debug, Clone, Serialize)] @@ -35,6 +41,7 @@ pub struct NameInfo { pub address: u64, pub ty: Option, pub label: Option, + pub exported: bool, } #[derive(Debug, Clone, Serialize)] @@ -56,13 +63,28 @@ pub struct FunctionCordInfo { labels: Vec, } +#[derive(Debug, Clone, Serialize)] +pub enum BaseAddressInfo { + /// The base address is not specified in the IDB. + None, + /// The base address is the absolute address of the first byte of the executable. + /// + /// To get a delta, calculate the difference between the base address and the address of the lowest segment. + BaseSegment(u64), + /// The base address is the address of the first byte of the lowest section. + /// + /// To get a delta, calculate the difference between the base address and the address of the lowest section. + BaseSection(u64), +} + #[derive(Debug, Clone, Serialize)] pub struct ID0Info { - pub base_address: Option, + pub base_address: BaseAddressInfo, pub segments: Vec, pub functions: Vec, pub comments: Vec, pub labels: Vec, + pub exports: Vec, } #[derive(Debug, Clone, Serialize)] @@ -71,6 +93,7 @@ pub struct DirTreeInfo { pub types: Vec, /// Contains both function and data names (along with their types). pub names: Vec, + pub comments: Vec, } #[derive(Debug, Clone, Serialize, Default)] @@ -124,6 +147,7 @@ impl IDBInfo { address: label.address, ty: None, label: Some(label.label.clone()), + exported: false, }); } @@ -132,6 +156,7 @@ impl IDBInfo { address: func.address, ty: func.ty.clone(), label: func.name.clone(), + exported: false, }); } } @@ -172,6 +197,24 @@ impl IDBInfo { }); types } + + pub fn merged_comments(&self) -> Vec { + let mut comments = Vec::new(); + if let Some(id0) = &self.id0 { + comments.extend(id0.comments.clone()); + } + if let Some(dir_tree) = &self.dir_tree { + comments.extend(dir_tree.comments.clone()); + } + comments.sort_by_key(|c| c.address); + comments.dedup_by(|a, b| { + if a.address != b.address { + return false; + } + a.is_repeatable == b.is_repeatable + }); + comments + } } /// Parsed the IDB data into [`IDBInfo`]. @@ -314,32 +357,32 @@ impl IDBFileParser { address: func_start, is_library: func.flags.is_lib(), is_no_return: func.flags.is_no_return(), - is_entry: false, }); } } } } + let mut exports = Vec::new(); if let Ok(entry_points) = id0.entry_points(&root_info) { for entry in entry_points { - // TODO: What to do with entry.forwarded? - functions.push(FunctionInfo { - name: Some(entry.name), - ty: entry.entry_type, + exports.push(ExportInfo { + name: entry.name, address: entry.address.into_u64(), - is_library: false, - is_no_return: false, - is_entry: true, + ty: entry.entry_type, }); } } - let base_address = match root_info.addresses.loading_base.into_u64() { + let min_ea = root_info.addresses.min_ea.into_raw().into_u64(); + let loading_base = root_info.addresses.loading_base.into_u64(); + let base_address = match (loading_base, min_ea) { + (0, 0) => BaseAddressInfo::None, // An IDB with zero loading base is possibly not loaded there. // For example, see the FlawedGrace.idb in the idb-rs resources directory. - 0 => None, - loading_base => Some(loading_base.into_u64()), + // Instead, we will want to use the lowest section address. + (0, min_ea) => BaseAddressInfo::BaseSection(min_ea), + (loading_base, _) => BaseAddressInfo::BaseSegment(loading_base.into_u64()), }; Ok(ID0Info { @@ -348,6 +391,7 @@ impl IDBFileParser { functions, comments, labels, + exports, }) } @@ -421,10 +465,51 @@ impl IDBFileParser { address: func_addr, is_library: false, is_no_return: false, - is_entry: false, })) }; + let comment_info_from_addr = |addr_info: &AddressInfo| -> Vec { + let mut comments = Vec::new(); + if let Some(comment) = addr_info.comment() { + comments.push(CommentInfo { + address: addr_info.address().into_raw().into_u64(), + comment: comment.to_string(), + is_repeatable: false, + }); + } + if let Some(comment) = addr_info.comment_repeatable() { + comments.push(CommentInfo { + address: addr_info.address().into_raw().into_u64(), + comment: comment.to_string(), + is_repeatable: true, + }) + } + if let Some(pre_comments) = addr_info.comment_pre() { + for comment in pre_comments { + comments.push(CommentInfo { + address: addr_info.address().into_raw().into_u64(), + comment: comment.to_string(), + is_repeatable: false, + }) + } + } + if let Some(post_comments) = addr_info.comment_post() { + for comment in post_comments { + comments.push(CommentInfo { + address: addr_info.address().into_raw().into_u64(), + comment: comment.to_string(), + is_repeatable: false, + }) + } + } + comments + }; + + let mut comments = Vec::new(); + for (addr_info, _) in all_address_info(id0, id1, id2, netdelta) { + comments.extend(comment_info_from_addr(&addr_info)); + } + let mut functions = Vec::new(); if let Some(func_dir_tree) = id0.dirtree_function_address()? { func_dir_tree.visit_leafs(|addr_raw| { @@ -446,6 +531,7 @@ impl IDBFileParser { address: info.address().into_raw().into_u64(), ty: info.tinfo(&root_info).ok().flatten().map(|t| t.clone()), label: info.label().ok().flatten().map(|s| s.to_string()), + exported: false, }); } }); @@ -466,6 +552,7 @@ impl IDBFileParser { functions, types, names, + comments, }) } }