diff options
| author | Mason Reed <mason@vector35.com> | 2026-01-20 14:30:34 -0800 |
|---|---|---|
| committer | Mason Reed <35282038+emesare@users.noreply.github.com> | 2026-03-23 21:50:02 -0700 |
| commit | 1e85844d6407db817db25fd43f1dd9756ef0c3ae (patch) | |
| tree | d6d2df2c3ed9403604fecff294be88d70fce5cf7 /plugins/idb_import/src/translate.rs | |
| parent | 1f98e7ca7cce35ae4c2b4741f33e9892dd695534 (diff) | |
IDB Import refactor
Diffstat (limited to 'plugins/idb_import/src/translate.rs')
| -rw-r--r-- | plugins/idb_import/src/translate.rs | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/plugins/idb_import/src/translate.rs b/plugins/idb_import/src/translate.rs new file mode 100644 index 00000000..c3af6fa6 --- /dev/null +++ b/plugins/idb_import/src/translate.rs @@ -0,0 +1,606 @@ +//! Translate type information from IDB to Binary Ninja, this will not discover type information +//! by which we mean pull type information from outside sources, the mapper does that. + +use binaryninja::architecture::{Architecture, ArchitectureExt, CoreArchitecture}; +use binaryninja::calling_convention::CoreCallingConvention; +use binaryninja::confidence::Conf; +use binaryninja::platform::Platform; +use binaryninja::rc::Ref; +use binaryninja::types::{ + EnumerationBuilder, FunctionParameter, MemberAccess, MemberScope, NamedTypeReference, + NamedTypeReferenceClass, StructureBuilder, StructureMember, StructureType, TypeBuilder, + TypeContainer, +}; +use idb_rs::til::function::CallingConvention; +use idb_rs::til::r#enum::EnumMembers; +use idb_rs::til::{Basic, TILTypeInfo, TypeVariant, TyperefType, TyperefValue}; +use std::collections::{HashMap, HashSet}; +use std::rc::Rc; +use std::sync::Mutex; + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct ReferencedType { + pub name: String, + pub ordinal: Option<u64>, + /// The width of the type in bytes, if known. + /// + /// This is required to be able to place NTR's in structures and unions. + pub width: Option<usize>, +} + +impl ReferencedType { + pub fn new(name: String) -> Self { + Self { + ordinal: None, + name, + width: None, + } + } + + pub fn new_with_ordinal(name: String, ordinal: u64) -> Self { + Self { + ordinal: Some(ordinal), + name, + width: None, + } + } +} + +impl From<&TILTypeInfo> for ReferencedType { + fn from(value: &TILTypeInfo) -> Self { + Self { + ordinal: match value.ordinal { + 0 => None, + ord => Some(ord), + }, + name: value.name.to_string(), + width: None, + } + } +} + +pub struct TILTranslator { + /// Default size of addresses. + pub address_size: usize, + /// Default size of enumerations. + pub enum_size: usize, + /// Reference types, for use with typedefs. + /// + /// This is necessary because ordinals do not have names and can't be made into a [`NamedTypeReference`]. + pub reference_types_by_ord: HashMap<u64, ReferencedType>, + pub reference_types_by_name: HashMap<String, ReferencedType>, + /// The types that have been used in the translation process. + /// + /// For a complete analysis, we will take these used types, attempt to find them in type libraries, + /// then add them to the binary view. + /// + /// NOTE: Not to be confused with `reference_types_by_ord`, which is a map of ordinal to reference types. + pub used_types: Rc<Mutex<HashSet<ReferencedType>>>, + // pub referenced_types: Rc<Mutex<Vec<ReferencedType>>>, + pub default_calling_convention: Option<Ref<CoreCallingConvention>>, + pub cdecl_calling_convention: Option<Ref<CoreCallingConvention>>, + pub stdcall_calling_convention: Option<Ref<CoreCallingConvention>>, + pub fastcall_calling_convention: Option<Ref<CoreCallingConvention>>, +} + +impl TILTranslator { + pub fn new(address_size: usize) -> Self { + Self { + address_size, + enum_size: address_size / 2, + reference_types_by_ord: HashMap::new(), + reference_types_by_name: HashMap::new(), + used_types: Rc::new(Mutex::new(HashSet::new())), + default_calling_convention: None, + cdecl_calling_convention: None, + stdcall_calling_convention: None, + fastcall_calling_convention: None, + } + } + + pub fn new_from_platform(platform: &Platform) -> Self { + Self { + address_size: platform.address_size(), + enum_size: platform.arch().default_integer_size(), + reference_types_by_ord: HashMap::new(), + reference_types_by_name: HashMap::new(), + used_types: Rc::new(Mutex::new(HashSet::new())), + default_calling_convention: platform.get_default_calling_convention(), + cdecl_calling_convention: platform.get_cdecl_calling_convention(), + stdcall_calling_convention: platform.get_stdcall_calling_convention(), + fastcall_calling_convention: platform.get_fastcall_calling_convention(), + } + } + + pub fn new_from_arch(arch: &CoreArchitecture) -> Self { + Self { + address_size: arch.address_size(), + enum_size: arch.default_integer_size(), + reference_types_by_ord: HashMap::new(), + reference_types_by_name: HashMap::new(), + used_types: Rc::new(Mutex::new(HashSet::new())), + default_calling_convention: arch.get_default_calling_convention(), + cdecl_calling_convention: arch.get_cdecl_calling_convention(), + stdcall_calling_convention: arch.get_stdcall_calling_convention(), + fastcall_calling_convention: arch.get_fastcall_calling_convention(), + } + } + + pub fn with_til_info(mut self, til: &idb_rs::til::section::TILSection) -> Self { + if let Some(size_enum) = til.header.size_enum { + self.enum_size = size_enum.get() as usize; + } + + // Add referencable types so that type def lookups can occur. + self.reference_types_by_ord.reserve(til.types.len()); + for (_idx, ty) in til.types.iter().enumerate() { + self.add_referenced_type_info(ty); + } + + // TODO: Handle address (pointer) size information? + self + } + + /// Populate referencable types with the ones in a type container. + pub fn with_type_container(mut self, container: &TypeContainer) -> Self { + for (_, (name, ty)) in container.types().unwrap_or_default() { + self.add_referenced_named_type(&name.to_string(), Some(ty.width() as usize)); + } + self + } + + /// Add a type that can be referenced by ordinal or name. + pub fn add_referenced_type_info(&mut self, ty: &TILTypeInfo) { + let mut referenced_type = ReferencedType::from(ty); + referenced_type.width = self.width_of_type(&ty.tinfo).ok(); + self.reference_types_by_ord + .insert(ty.ordinal, referenced_type.clone()); + self.reference_types_by_name + .insert(referenced_type.name.clone(), referenced_type); + } + + /// Add a named type that can be referenced by ONLY name. + /// + /// Useful to populate with types coming from platform or other "system" types in Binary Ninja. + pub fn add_referenced_named_type(&mut self, name: &str, width: Option<usize>) { + let mut referenced_type = ReferencedType::new(name.to_string()); + referenced_type.width = width; + self.reference_types_by_name + .insert(referenced_type.name.clone(), referenced_type); + } + + pub fn translate_type_info( + &self, + til_ty: &idb_rs::til::Type, + ) -> anyhow::Result<Ref<binaryninja::types::Type>> { + let builder = match &til_ty.type_variant { + TypeVariant::Basic(v) => self.build_basic_ty(&v)?, + TypeVariant::Pointer(v) => self.build_pointer_ty(&v)?, + TypeVariant::Function(v) => self.build_function_ty(&v)?, + TypeVariant::Array(v) => self.build_array_ty(&v)?, + TypeVariant::Typeref(v) => self.build_type_ref_ty(&v)?, + TypeVariant::Struct(v) => self.build_udt_ty(&v, false)?, + TypeVariant::Union(v) => self.build_udt_ty(&v, true)?, + TypeVariant::Enum(v) => self.build_enum_ty(&v)?, + TypeVariant::Bitfield(v) => self.build_bitfield_ty(&v)?, + }; + + builder.set_const(til_ty.is_const); + builder.set_volatile(til_ty.is_volatile); + Ok(builder.finalize()) + } + + pub fn build_basic_ty(&self, basic_ty: &idb_rs::til::Basic) -> anyhow::Result<TypeBuilder> { + use idb_rs::til::Basic; + // TODO: Grab the sizing information of these types from the TIL instead of hardcoding. + match basic_ty { + Basic::Void => Ok(TypeBuilder::void()), + Basic::Unknown { bytes } => { + // In the samples provided it appears that unknown can be used to represent a byte, + // so we are going to be liberal and allow unknown basic types to be treated as a sized int. + Ok(TypeBuilder::int(*bytes as usize, false)) + } + Basic::Bool => Ok(TypeBuilder::bool()), + Basic::BoolSized { .. } => { + // TODO: This needs to be resized, if that cannot be done, make a NTR to an int named BOOL? + Ok(TypeBuilder::bool()) + } + Basic::Char => Ok(TypeBuilder::char()), + Basic::SegReg => Err(anyhow::anyhow!("SegReg is not supported")), + Basic::Short { is_signed } => Ok(TypeBuilder::int(2, is_signed.unwrap_or(true))), + Basic::Long { is_signed } => Ok(TypeBuilder::int(4, is_signed.unwrap_or(true))), + Basic::LongLong { is_signed } => Ok(TypeBuilder::int(8, is_signed.unwrap_or(true))), + Basic::Int { is_signed } => Ok(TypeBuilder::int(4, is_signed.unwrap_or(true))), + Basic::IntSized { bytes, is_signed } => { + let bytes: u8 = u8::try_from(*bytes).unwrap_or(4); + Ok(TypeBuilder::int(bytes as usize, is_signed.unwrap_or(true))) + } + Basic::Float { bytes } => { + let bytes: u8 = u8::try_from(*bytes).unwrap_or(4); + Ok(TypeBuilder::float(bytes as usize)) + } + Basic::LongDouble => Ok(TypeBuilder::float(8)), + } + } + + pub fn build_pointer_ty( + &self, + pointer_ty: &idb_rs::til::pointer::Pointer, + ) -> anyhow::Result<TypeBuilder> { + // TODO: Consult pointer_ty.closure (is this how we can get based pointers?) + let inner_ty = self.translate_type_info(&pointer_ty.typ)?; + Ok(TypeBuilder::pointer_of_width( + &inner_ty, + self.address_size, + // NOTE: Set later in `translate_type_info`. + false, + // NOTE: Set later in `translate_type_info`. + false, + None, + )) + } + + pub fn build_function_ty( + &self, + function_ty: &idb_rs::til::function::Function, + ) -> anyhow::Result<TypeBuilder> { + // TODO: Once branch `test_call_layout` lands use function_ty.retloc to recover return location. + let return_ty = self.translate_type_info(&function_ty.ret)?; + let params: Vec<FunctionParameter> = self.build_function_params(&function_ty.args)?; + let has_variable_args = false; + let stack_adjust = Conf::new(0, 0); + + let builder = match function_ty.calling_convention { + Some(CallingConvention::Cdecl) | Some(CallingConvention::Thiscall) + if self.cdecl_calling_convention.is_some() => + { + let cc = self.cdecl_calling_convention.clone().unwrap(); + TypeBuilder::function_with_opts( + &return_ty, + ¶ms, + has_variable_args, + cc, + stack_adjust, + ) + } + Some(CallingConvention::Stdcall) if self.stdcall_calling_convention.is_some() => { + let cc = self.stdcall_calling_convention.clone().unwrap(); + TypeBuilder::function_with_opts( + &return_ty, + ¶ms, + has_variable_args, + cc, + stack_adjust, + ) + } + Some(CallingConvention::Fastcall) if self.fastcall_calling_convention.is_some() => { + let cc = self.fastcall_calling_convention.clone().unwrap(); + TypeBuilder::function_with_opts( + &return_ty, + ¶ms, + has_variable_args, + cc, + stack_adjust, + ) + } + _ => TypeBuilder::function(&return_ty, params, has_variable_args), + }; + + Ok(builder) + } + + pub fn build_function_params( + &self, + args: &[idb_rs::til::function::FunctionArg], + ) -> anyhow::Result<Vec<FunctionParameter>> { + args.iter() + .enumerate() + .map(|(idx, arg)| { + let arg_name = arg + .name + .clone() + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("arg{}", idx)); + self.translate_type_info(&arg.ty) + .map(|ty| FunctionParameter::new(ty, arg_name, None)) + }) + .collect() + } + + pub fn build_array_ty( + &self, + _array_ty: &idb_rs::til::array::Array, + ) -> anyhow::Result<TypeBuilder> { + let elem_ty = self.translate_type_info(&_array_ty.elem_type)?; + // NOTE: IDA seems to allow DST array (optional nelem) we are just going to default zero count + // for those and assume that to be fine, this obviously is a little bit tricky to assume but + // I imagine IDA only allows these at the end of a struct, and makes the structure unsized, + // not exactly sure how to handle this yet. + let count = _array_ty.nelem.map(|n| n.get()).unwrap_or(0); + Ok(TypeBuilder::array(&elem_ty, count as u64)) + } + + pub fn build_type_ref_ty( + &self, + typ_ref_ty: &idb_rs::til::Typeref, + ) -> anyhow::Result<TypeBuilder> { + let type_class = match typ_ref_ty.ref_type { + Some(TyperefType::Struct) => NamedTypeReferenceClass::StructNamedTypeClass, + Some(TyperefType::Union) => NamedTypeReferenceClass::UnionNamedTypeClass, + Some(TyperefType::Enum) => NamedTypeReferenceClass::EnumNamedTypeClass, + None => NamedTypeReferenceClass::UnknownNamedTypeClass, + }; + + // Named type references can be placed directly, otherwise we have to resolve the ordinal + // to get a name for the type reference. Once we get that, we make a NamedTypeReference + // and then place the types ordinal in the list of referenced types, so that we can pull + // them into the binary view later. + match &typ_ref_ty.typeref_value { + TyperefValue::Name(Some(ref_name)) => { + if let Ok(mut used_types) = self.used_types.lock() { + let ty_ref = ReferencedType::new(ref_name.to_string()); + used_types.insert(ty_ref.clone()); + } + let ntr = NamedTypeReference::new(type_class, ref_name.to_string()); + Ok(TypeBuilder::named_type(&ntr)) + } + TyperefValue::Name(None) => { + // IDA will use an unnamed type reference for a struct, union or enum with no definition. + match typ_ref_ty.ref_type { + Some(TyperefType::Struct) => { + let empty_struct = StructureBuilder::new().finalize(); + Ok(TypeBuilder::structure(&empty_struct)) + } + Some(TyperefType::Union) => { + let empty_union = StructureBuilder::new() + .structure_type(StructureType::UnionStructureType) + .finalize(); + Ok(TypeBuilder::structure(&empty_union)) + } + None | Some(TyperefType::Enum) => { + Err(anyhow::anyhow!("Unnamed type references are not supported")) + } + } + } + TyperefValue::Ordinal(ref_ord) => { + if let Some(ty_ref) = self.reference_types_by_ord.get(&(*ref_ord as u64)) { + // The ordinal has an associated reference type, use the name and insert this into + // the list of used types. + if let Ok(mut used_types) = self.used_types.lock() { + used_types.insert(ty_ref.clone()); + } + let ntr = NamedTypeReference::new(type_class, &ty_ref.name); + Ok(TypeBuilder::named_type(&ntr)) + } else { + Err(anyhow::anyhow!( + "Type reference ordinal not found: {}", + ref_ord + )) + } + } + } + } + + pub fn build_udt_ty( + &self, + udt_ty: &idb_rs::til::udt::UDT, + is_union: bool, + ) -> anyhow::Result<TypeBuilder> { + let mut builder = StructureBuilder::new(); + if let Some(align) = udt_ty.alignment { + builder.alignment(align.get().into()); + } + builder.packed(udt_ty.is_unaligned && udt_ty.is_unknown_8); + if is_union { + builder.structure_type(StructureType::UnionStructureType); + } + + let (members, width) = self.build_udt_members(&udt_ty.members)?; + for mut member in members { + if is_union { + member.offset = 0; + } + builder.insert_member(member, false); + } + + builder.width(width); + // TODO: Handle udt_ty.extra_padding (is that tail padding?) + Ok(TypeBuilder::structure(&builder.finalize())) + } + + pub fn build_udt_members( + &self, + udt_members: &[idb_rs::til::udt::UDTMember], + ) -> anyhow::Result<(Vec<StructureMember>, u64)> { + let mut current_offset = 0; + let mut member_iter = udt_members.iter().peekable(); + let mut structure_members = Vec::new(); + while let Some(member) = member_iter.next() { + let current_byte_offset = current_offset / 8; + let member_name = member + .name + .clone() + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("field_{}", current_byte_offset)); + let member_ty = Conf::new(self.translate_type_info(&member.member_type)?, 255); + let bn_member = match member.member_type.type_variant { + TypeVariant::Bitfield(bf) => StructureMember::new_bitfield( + member_ty, + member_name, + current_offset, + bf.width as u8, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ), + _ => { + let member_align = member_ty.contents.alignment().max(1) as u64; + let member_offset = if current_byte_offset % member_align == 0 { + current_byte_offset + } else { + current_byte_offset + (member_align - (current_byte_offset % member_align)) + }; + // NTR will be zero-sized, we need to handle this by computing the width ourselves. + let referenced_width = self.width_of_type(&member.member_type)?; + current_offset += referenced_width as u64 * 8; + StructureMember::new( + member_ty, + member_name, + member_offset, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ) + } + }; + + structure_members.push(bn_member); + } + + // We need to return the width of the structure as NTR resolution will happen after the structure + // width would be computed, so we need to manually set the structures width in `build_udt_ty`. + Ok((structure_members, current_offset / 8)) + } + + pub fn build_enum_ty( + &self, + enum_ty: &idb_rs::til::r#enum::Enum, + ) -> anyhow::Result<TypeBuilder> { + let mut enumeration_builder = EnumerationBuilder::new(); + match &enum_ty.members { + EnumMembers::Regular(members) => { + for (idx, member) in members.iter().enumerate() { + let member_name = member + .name + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("member_{}", idx)); + enumeration_builder.insert(&member_name, member.value); + } + } + EnumMembers::Groups(groups) => { + for (idx, group) in groups.iter().enumerate() { + // TODO: How does this grouping actually impact the enum besides the name? + let group_name = group + .field + .name + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("group_{}", idx)); + for (idx, member) in group.sub_fields.iter().enumerate() { + let member_name = member + .name + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("member_{}", idx)); + let grouped_member_name = format!("{}_{}", group_name, member_name); + enumeration_builder.insert(&grouped_member_name, member.value); + } + } + } + } + + let width = enum_ty + .storage_size + .map(|s| s.get() as usize) + .unwrap_or(self.enum_size); + Ok(TypeBuilder::enumeration( + &enumeration_builder.finalize(), + width.try_into()?, + enum_ty.is_signed, + )) + } + + /// A bitfield is a single member in an udt that plays the role of a bit-aligned integer. + /// + /// NOTE: This does not return the bit-aligned integer, this returns the **byte-aligned** integer, + /// you must constrain the integer yourself when constructing a Binary Ninja structure. + pub fn build_bitfield_ty( + &self, + bitfield_ty: &idb_rs::til::bitfield::Bitfield, + ) -> anyhow::Result<TypeBuilder> { + self.build_basic_ty(&idb_rs::til::Basic::IntSized { + bytes: bitfield_ty.nbytes, + is_signed: Some(!bitfield_ty.unsigned), + }) + } + + /// Computes the width of a type, in bytes. + pub fn width_of_type(&self, ty: &idb_rs::til::Type) -> anyhow::Result<usize> { + match &ty.type_variant { + TypeVariant::Basic(basic) => match basic { + Basic::Void => Ok(0), + Basic::Unknown { bytes } => Ok(*bytes as usize), + Basic::Bool => Ok(1), + Basic::BoolSized { bytes } => Ok(bytes.get() as usize), + Basic::Char => Ok(1), + Basic::SegReg => Ok(8), + Basic::Short { .. } => Ok(2), + Basic::Long { .. } => Ok(4), + Basic::LongLong { .. } => Ok(8), + Basic::Int { .. } => Ok(4), + Basic::IntSized { bytes, .. } => Ok(bytes.get() as usize), + Basic::Float { bytes } => Ok(bytes.get() as usize), + Basic::LongDouble => Ok(8), + }, + TypeVariant::Pointer(_) => Ok(self.address_size), + TypeVariant::Function(_) => Err(anyhow::anyhow!("Function types do not have a width")), + TypeVariant::Array(arr) => { + let elem_width = self.width_of_type(&arr.elem_type)?; + // TODO: A DST array is unsized or what? I think we should error IMO. + let count = arr.nelem.map(|n| n.get()).unwrap_or(0); + Ok(elem_width * count as usize) + } + TypeVariant::Typeref(r) => { + let resolved_ty = self.resolve_type_ref(r).ok_or_else(|| { + anyhow::anyhow!( + "Type reference {:?} could not be resolved to a type", + r.typeref_value + ) + })?; + resolved_ty.width.ok_or_else(|| { + anyhow::anyhow!("Type reference has no width: {:?}", resolved_ty) + }) + } + TypeVariant::Struct(s) => { + let mut total_width = 0; + for member in &s.members { + total_width += self.width_of_type(&member.member_type)?; + } + // TODO: Handle alignment and bitfields. + Ok(total_width) + } + TypeVariant::Union(u) => { + // Size of the largest member + alignment + let mut max_width = 0; + for member in &u.members { + let member_width = self.width_of_type(&member.member_type)?; + max_width = max_width.max(member_width); + } + // TODO: Handle alignment + Ok(max_width) + } + TypeVariant::Enum(e) => Ok(e + .storage_size + .map(|s| s.get() as usize) + .unwrap_or(self.enum_size)), + TypeVariant::Bitfield(b) => { + // NOTE: We return the byte aligned width here if inside a structure you need to + // constrain the width to the storage yourself. + Ok(b.nbytes.get() as usize) + } + } + } + + /// Try and find the [`ReferencedType`] for a given type reference. + pub fn resolve_type_ref(&self, type_ref: &idb_rs::til::Typeref) -> Option<ReferencedType> { + match &type_ref.typeref_value { + TyperefValue::Name(Some(ref_name)) => self + .reference_types_by_name + .get(&ref_name.to_string()) + .cloned(), + TyperefValue::Ordinal(ref_ord) => { + self.reference_types_by_ord.get(&(*ref_ord as u64)).cloned() + } + _ => None, + } + } +} |
