diff options
| author | Rubens Brandao <git@rubens.io> | 2024-08-14 18:12:52 -0300 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2024-08-30 10:17:29 -0400 |
| commit | fe0d4ae2c3baa26fca84a9a8cb9670955bdfea12 (patch) | |
| tree | 6b502bbc8dcad7e71725afcd2924085c59a54fda | |
| parent | 564f2a065fae5e08990c660926b147bca7c29226 (diff) | |
Initial IDB/TIL import plugin
| -rw-r--r-- | rust/Cargo.lock | 66 | ||||
| -rw-r--r-- | rust/Cargo.toml | 1 | ||||
| -rw-r--r-- | rust/examples/idb_import/CMakeLists.txt | 91 | ||||
| -rw-r--r-- | rust/examples/idb_import/Cargo.toml | 14 | ||||
| -rw-r--r-- | rust/examples/idb_import/src/lib.rs | 863 |
5 files changed, 1023 insertions, 12 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 2b1afe26..7738ecd6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "autocfg" @@ -115,6 +115,15 @@ dependencies = [ ] [[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -398,9 +407,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" dependencies = [ "crc32fast", "miniz_oxide", @@ -469,6 +478,28 @@ dependencies = [ ] [[package]] +name = "idb-rs" +version = "0.1.0" +source = "git+https://github.com/Vector35/idb-rs#5f32597906d1dea26e403def7006c3ecc0370d66" +dependencies = [ + "anyhow", + "bincode", + "flate2", + "serde", + "serde_repr", +] + +[[package]] +name = "idb_import" +version = "0.1.0" +dependencies = [ + "anyhow", + "binaryninja", + "idb-rs", + "log", +] + +[[package]] name = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -535,9 +566,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lzxd" @@ -875,18 +906,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.205" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.205" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", @@ -924,9 +966,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5ffc5ba4..789a4224 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -27,6 +27,7 @@ members = [ "examples/dwarf/dwarf_import", "examples/dwarf/dwarfdump", "examples/dwarf/shared", + "examples/idb_import", "examples/flowgraph", "examples/minidump", "examples/mlil_visitor", diff --git a/rust/examples/idb_import/CMakeLists.txt b/rust/examples/idb_import/CMakeLists.txt new file mode 100644 index 00000000..5ae51716 --- /dev/null +++ b/rust/examples/idb_import/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(idb_import) + +file(GLOB_RECURSE PLUGIN_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs) + +file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/../../../binaryninjacore.h + ${PROJECT_SOURCE_DIR}/../../binaryninjacore-sys/build.rs + ${PROJECT_SOURCE_DIR}/../../binaryninjacore-sys/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../binaryninjacore-sys/src/* + ${PROJECT_SOURCE_DIR}/../../Cargo.toml + ${PROJECT_SOURCE_DIR}/../../src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) +else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release) + set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}idb_import.pdb) +endif() + +set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}idb_import${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE}) + +add_custom_target(idb_import ALL DEPENDS ${PLUGIN_PATH}) +add_dependencies(idb_import binaryninjaapi) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +if(CARGO_API_VERSION) + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build) +else() + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo build) +endif() + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND mkdir -p ${TARGET_DIR} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + else() + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + endif() +elseif(WIN32) + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +else() + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +endif()
\ No newline at end of file diff --git a/rust/examples/idb_import/Cargo.toml b/rust/examples/idb_import/Cargo.toml new file mode 100644 index 00000000..c54ba93b --- /dev/null +++ b/rust/examples/idb_import/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "idb_import" +version = "0.1.0" +authors = ["Rubens Brandao <git@rubens.io>"] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1.0.86" +binaryninja = { path = "../../" } +idb-rs = { git = "https://github.com/Vector35/idb-rs" } +log = "0.4.20" diff --git a/rust/examples/idb_import/src/lib.rs b/rust/examples/idb_import/src/lib.rs new file mode 100644 index 00000000..34fe433a --- /dev/null +++ b/rust/examples/idb_import/src/lib.rs @@ -0,0 +1,863 @@ +use std::collections::HashMap; + +use binaryninja::architecture::CoreArchitecture; +use binaryninja::binaryninjacore_sys::{BNMemberAccess, BNMemberScope}; +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +use binaryninja::debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}; +use binaryninja::logger; +use binaryninja::rc::Ref; +use binaryninja::types::{ + Conf, EnumerationBuilder, FunctionParameter, NamedTypeReferenceClass, StructureBuilder, + StructureType, Type, +}; + +use idb_rs::til::{ + array::Array as TILArray, function::Function as TILFunction, r#enum::Enum as TILEnum, + r#struct::Struct as TILStruct, r#struct::StructMember as TILStructMember, section::TILSection, + union::Union as TILUnion, TILTypeInfo, Type as TILType, Typedef as TILTypedef, +}; + +use log::{error, trace, warn, LevelFilter}; + +use anyhow::Result; + +#[derive(Debug, Clone)] +enum BnTypeError { + // TODO delete this and make this verification during the TIL/IDB parsing, translating the ordinal + // into a kind of type_idx + OrdinalNotFound(u32), + NameNotFound(String), + + Typedef(Box<BnTypeError>), + Function(FunctionError), + Array(Box<BnTypeError>), + Pointer(Box<BnTypeError>), + /// Error for members + Struct(Vec<(usize, BnTypeError)>), + Union(Vec<(usize, BnTypeError)>), +} + +#[derive(Default, Debug, Clone)] +struct FunctionError { + ret: Option<Box<BnTypeError>>, + args: Vec<(usize, BnTypeError)>, +} + +impl std::fmt::Display for BnTypeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BnTypeError::OrdinalNotFound(i) => write!(f, "Reference to non existing Ordinal {i}"), + BnTypeError::NameNotFound(name) => write!(f, "Reference to non existing name {name}"), + BnTypeError::Typedef(error) => write!(f, "Typedef: {error}"), + BnTypeError::Function(FunctionError { ret, args }) => { + if let Some(error) = ret { + write!(f, "Function return: {error} ")?; + } + for (i, error) in args { + write!(f, "Function argument {i}: {error} ")?; + } + Ok(()) + } + BnTypeError::Array(error) => write!(f, "Array: {error}"), + BnTypeError::Struct(errors) => { + for (i, error) in errors { + write!(f, "Struct Member {i}: {error} ")?; + } + Ok(()) + } + BnTypeError::Union(errors) => { + for (i, error) in errors { + write!(f, "Union Member {i}: {error} ")?; + } + Ok(()) + } + BnTypeError::Pointer(error) => write!(f, "Pointer: {error}"), + } + } +} + +struct IDBDebugInfoParser; +impl CustomDebugInfoParser for IDBDebugInfoParser { + fn is_valid(&self, view: &BinaryView) -> bool { + view.file().filename().as_str().ends_with(".i64") + || view.file().filename().as_str().ends_with(".idb") + } + + fn parse_info( + &self, + debug_info: &mut DebugInfo, + _bv: &BinaryView, + debug_file: &BinaryView, + progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, + ) -> bool { + match parse_idb_info(debug_info, debug_file, progress) { + Ok(()) => true, + Err(error) => { + error!("Unable to parse IDB file: {error}"); + false + } + } + } +} + +struct TILDebugInfoParser; +impl CustomDebugInfoParser for TILDebugInfoParser { + fn is_valid(&self, view: &BinaryView) -> bool { + view.file().filename().as_str().ends_with(".til") + } + + fn parse_info( + &self, + debug_info: &mut DebugInfo, + _bv: &BinaryView, + debug_file: &BinaryView, + progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, + ) -> bool { + match parse_til_info(debug_info, debug_file, progress) { + Ok(()) => true, + Err(error) => { + error!("Unable to parse TIL file: {error}"); + false + } + } + } +} + +struct BinaryViewReader<'a> { + bv: &'a BinaryView, + offset: u64, +} +impl std::io::Read for BinaryViewReader<'_> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + if !self.bv.offset_valid(self.offset) { + return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "")); + } + let len = self.bv.read(buf, self.offset); + self.offset += u64::try_from(len).unwrap(); + Ok(len) + } +} + +impl std::io::Seek for BinaryViewReader<'_> { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> { + let new_offset = match pos { + std::io::SeekFrom::Start(offset) => Some(offset), + std::io::SeekFrom::End(end) => u64::try_from(self.bv.len()) + .unwrap() + .checked_add_signed(end), + std::io::SeekFrom::Current(next) => self.offset.checked_add_signed(next), + }; + let new_offset = + new_offset.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))?; + if !self.bv.offset_valid(new_offset) { + return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "")); + } + self.offset = new_offset; + Ok(new_offset) + } +} + +fn parse_idb_info( + debug_info: &mut DebugInfo, + debug_file: &BinaryView, + progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, +) -> Result<()> { + trace!("Opening a IDB file"); + let file = BinaryViewReader { + bv: debug_file, + offset: 0, + }; + trace!("Parsing a IDB file"); + let file = std::io::BufReader::new(file); + let mut parser = idb_rs::IDBParser::new(file)?; + let Some(til_section) = parser.til_section_offset() else { + return Ok(()); + }; + trace!("Parsing the TIL section"); + let til = parser.read_til_section(til_section)?; + parse_til_section_info(debug_info, debug_file, til, progress) +} + +fn translate_enum(members: &[(Option<String>, u64)], bytesize: u64) -> Ref<Type> { + let eb = EnumerationBuilder::new(); + for (i, (name, bytesize)) in members.iter().enumerate() { + let name = name.to_owned().unwrap_or_else(|| format!("member_{i}")); + eb.insert(name, *bytesize); + } + Type::enumeration( + &eb.finalize(), + usize::try_from(bytesize).unwrap(), + Conf::new(false, 0), + ) +} + +fn translate_basic(mdata: &idb_rs::til::Basic) -> Ref<Type> { + match *mdata { + idb_rs::til::Basic::Void => Type::void(), + idb_rs::til::Basic::Unknown { bytes: 0 } => Type::void(), + idb_rs::til::Basic::Unknown { bytes } => Type::array(&Type::char(), bytes.into()), + idb_rs::til::Basic::Bool { bytes } if bytes.get() == 1 => Type::bool(), + // NOTE Binja don't have any representation for bool other then the default + idb_rs::til::Basic::Bool { bytes } => Type::int(bytes.get().into(), false), + idb_rs::til::Basic::Char => Type::char(), + // TODO what exacly is Segment Register? + idb_rs::til::Basic::SegReg => Type::char(), + idb_rs::til::Basic::Int { bytes, is_signed } => { + // default into signed + let is_signed = is_signed.as_ref().copied().unwrap_or(true); + Type::int(bytes.get().into(), is_signed) + } + idb_rs::til::Basic::Float { bytes } => Type::float(bytes.get().into()), + } +} + +fn parse_til_info( + debug_info: &mut DebugInfo, + debug_file: &BinaryView, + progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, +) -> Result<()> { + trace!("Opening a TIL file"); + let file = BinaryViewReader { + bv: debug_file, + offset: 0, + }; + let file = std::io::BufReader::new(file); + trace!("Parsing the TIL section"); + let til = TILSection::parse(file)?; + parse_til_section_info(debug_info, debug_file, til, progress) +} + +#[derive(Default)] +enum TranslateTypeResult { + #[default] + NotYet, + /// Unable to solve type, there is no point in trying again + Error(BnTypeError), + /// a type that is not final, but equivalent to the final type, if error, there is no + /// point in trying again + PartiallyTranslated(Ref<Type>, Option<BnTypeError>), + Translated(Ref<Type>), +} + +impl From<Result<Ref<Type>, BnTypeError>> for TranslateTypeResult { + fn from(value: Result<Ref<Type>, BnTypeError>) -> Self { + match value { + Ok(ty) => Self::Translated(ty), + Err(error) => Self::Error(error), + } + } +} + +struct TranslatesIDBType<'a> { + // sanitized name form IDB + name: String, + // class, just to make easy to create named_type + _class: NamedTypeReferenceClass, + // the result, if converted + ty: TranslateTypeResult, + og_ty: &'a TILTypeInfo, + is_symbol: bool, +} + +struct TranslateIDBTypes<'a> { + arch: CoreArchitecture, + debug_info: &'a mut DebugInfo, + _debug_file: &'a BinaryView, + progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, + til: &'a TILSection, + // note it's mapped 1:1 with the same index from til types.chain(symbols) + types: Vec<TranslatesIDBType<'a>>, + // ordinals with index to types + types_by_ord: HashMap<u64, usize>, + // original names with index to types + types_by_name: HashMap<String, usize>, +} + +impl TranslateIDBTypes<'_> { + fn find_typedef_by_ordinal(&self, ord: u64) -> Option<TranslateTypeResult> { + self.types_by_ord + .get(&ord) + .map(|idx| self.find_typedef(&self.types[*idx])) + } + + fn find_typedef_by_name(&self, name: &str) -> Option<TranslateTypeResult> { + if name.is_empty() { + // TODO this is my assumption, maybe an empty names Typedef means something else. + return Some(TranslateTypeResult::Translated(Type::void())); + } + + if let Some(other_ty) = self + .types_by_name + .get(name) + .map(|idx| self.find_typedef(&self.types[*idx])) + { + return Some(other_ty); + } + + // check for types that ar usually not defined directly + match name { + "Unkown" | "uint8_t" => Some(TranslateTypeResult::Translated(Type::int(1, false))), + "IUnkown" | "int8_t" => Some(TranslateTypeResult::Translated(Type::int(1, true))), + "SHORT" | "USHORT" => Some(TranslateTypeResult::Translated(Type::int( + self.til + .sizes + .map(|x| x.size_short.get()) + .unwrap_or(2) + .into(), + name == "SHORT", + ))), + "int16_t" => Some(TranslateTypeResult::Translated(Type::int(2, true))), + "uint16_t" => Some(TranslateTypeResult::Translated(Type::int(2, false))), + "int32_t" => Some(TranslateTypeResult::Translated(Type::int(4, true))), + "uint32_t" => Some(TranslateTypeResult::Translated(Type::int(4, false))), + "int64_t" => Some(TranslateTypeResult::Translated(Type::int(8, true))), + "uint64_t" => Some(TranslateTypeResult::Translated(Type::int(8, false))), + "int128_t" => Some(TranslateTypeResult::Translated(Type::int(16, true))), + "uint128_t" => Some(TranslateTypeResult::Translated(Type::int(16, false))), + _ => None, + } + } + + fn find_typedef(&self, ty: &TranslatesIDBType) -> TranslateTypeResult { + // only return a typedef, if it's solved, at least partially + match &ty.ty { + TranslateTypeResult::NotYet => TranslateTypeResult::NotYet, + TranslateTypeResult::Error(error) => { + TranslateTypeResult::Error(BnTypeError::Typedef(Box::new(error.to_owned()))) + } + TranslateTypeResult::PartiallyTranslated(og_ty, error) => { + TranslateTypeResult::PartiallyTranslated( + Type::named_type_from_type(ty.name.as_str(), &og_ty), + error + .as_ref() + .map(|x| BnTypeError::Typedef(Box::new(x.clone()))) + .clone(), + ) + } + TranslateTypeResult::Translated(og_ty) => TranslateTypeResult::Translated( + Type::named_type_from_type(ty.name.as_str(), &og_ty), + ), + } + } + + fn translate_pointer(&self, ty: &TILType) -> TranslateTypeResult { + match self.translate_type(ty) { + TranslateTypeResult::Translated(trans) => { + TranslateTypeResult::Translated(Type::pointer(&self.arch, &trans)) + } + TranslateTypeResult::PartiallyTranslated(trans, error) => { + TranslateTypeResult::PartiallyTranslated( + Type::pointer(&self.arch, &trans), + error.map(|e| BnTypeError::Pointer(Box::new(e))), + ) + } + TranslateTypeResult::Error(error) => TranslateTypeResult::PartiallyTranslated( + Type::pointer(&self.arch, &Type::void()), + Some(error), + ), + TranslateTypeResult::NotYet => TranslateTypeResult::PartiallyTranslated( + Type::pointer(&self.arch, &Type::void()), + None, + ), + } + } + + fn translate_function(&self, fun: &TILFunction) -> TranslateTypeResult { + let mut is_partial = false; + let mut errors: FunctionError = Default::default(); + // funtions are always 0 len, so it's translated or partial(void) + let return_ty = match self.translate_type(&fun.ret) { + TranslateTypeResult::Translated(trans) => trans, + TranslateTypeResult::PartiallyTranslated(trans, error) => { + is_partial |= true; + errors.ret = error.map(Box::new); + trans + } + TranslateTypeResult::Error(error) => { + errors.ret = Some(Box::new(error)); + return TranslateTypeResult::PartiallyTranslated( + Type::void(), + Some(BnTypeError::Function(errors)), + ); + } + TranslateTypeResult::NotYet => { + return TranslateTypeResult::PartiallyTranslated(Type::void(), None) + } + }; + let mut partial_error_args = vec![]; + let mut bn_args = Vec::with_capacity(fun.args.len()); + for (i, (arg_name, arg_type, _arg_loc)) in fun.args.iter().enumerate() { + let arg = match self.translate_type(arg_type) { + TranslateTypeResult::Translated(trans) => trans, + TranslateTypeResult::PartiallyTranslated(trans, error) => { + is_partial = true; + if let Some(error) = error { + errors.args.push((i, error)); + } + trans + } + TranslateTypeResult::NotYet => { + return TranslateTypeResult::PartiallyTranslated(Type::void(), None) + } + TranslateTypeResult::Error(error) => { + partial_error_args.push((i, error)); + return TranslateTypeResult::PartiallyTranslated( + Type::void(), + Some(BnTypeError::Function(errors)), + ); + } + }; + // TODO create location from `arg_loc`? + let loc = None; + let name = arg_name.to_owned().unwrap_or_else(|| format!("arg_{i}")); + bn_args.push(FunctionParameter::new(arg, name, loc)); + } + + let ty = Type::function(&return_ty, &bn_args, false); + if is_partial { + let error = (errors.ret.is_some() || !errors.args.is_empty()) + .then(|| BnTypeError::Function(errors)); + TranslateTypeResult::PartiallyTranslated(ty, error) + } else { + assert!(errors.ret.is_none() && errors.args.is_empty()); + TranslateTypeResult::Translated(ty) + } + } + + fn translate_array(&self, array: &TILArray) -> TranslateTypeResult { + match self.translate_type(&*array.elem_type) { + TranslateTypeResult::NotYet => TranslateTypeResult::NotYet, + TranslateTypeResult::Translated(ty) => { + TranslateTypeResult::Translated(Type::array(&ty, array.nelem.into())) + } + TranslateTypeResult::PartiallyTranslated(ty, error) => { + TranslateTypeResult::PartiallyTranslated( + Type::array(&ty, array.nelem.into()), + error.map(Box::new).map(BnTypeError::Array), + ) + } + TranslateTypeResult::Error(error) => { + TranslateTypeResult::Error(BnTypeError::Array(Box::new(error))) + } + } + } + + fn translate_bitfields_into_struct( + &self, + offset: usize, + members_slice: &[TILStructMember], + struct_builder: &StructureBuilder, + ) { + if members_slice.is_empty() { + unreachable!() + } + let mut members = members_slice + .iter() + .map(|ty| match &ty.member_type { + TILType::Bitfield(b) => b, + _ => unreachable!(), + }) + .enumerate(); + let (_, first_field) = members.next().unwrap(); + let mut current_field_bytes = first_field.nbytes; + let mut current_field_bits: u32 = first_field.width.into(); + let mut start_idx = 0; + + let create_field = |start_idx, i, bytes| { + let name = if start_idx == i - 1 { + let member: &TILStructMember = &members_slice[i - 1]; + let name: &Option<String> = &member.name; + name.to_owned() + .unwrap_or_else(|| format!("bitfield_{}", offset + start_idx)) + } else { + format!("bitfield_{}_{}", offset + start_idx, offset + (i - 1)) + }; + let field = field_from_bytes(bytes); + struct_builder.append( + &field, + name, + BNMemberAccess::NoAccess, + BNMemberScope::NoScope, + ); + }; + + for (i, member) in members { + // starting a new field + let max_bits = u32::try_from(current_field_bytes).unwrap() * 8; + // this bitfield start a a new field, or can't contain other bitfields + // finish the previous and start a new + if current_field_bytes != member.nbytes + || max_bits < current_field_bits + u32::from(member.width) + { + create_field(start_idx, i, current_field_bytes); + current_field_bytes = member.nbytes; + current_field_bits = 0; + start_idx = i; + } + + // just add the current bitfield into the field + current_field_bits += u32::from(member.width); + } + + if current_field_bits != 0 { + create_field(start_idx, members_slice.len(), current_field_bytes); + } + } + + fn translate_struct( + &self, + members: &[TILStructMember], + effective_alignment: u16, + ) -> TranslateTypeResult { + if members.is_empty() { + // binary ninja crashes if you create an empty struct, because it divide by 0 + return TranslateTypeResult::Translated(Type::void()); + } + let mut is_partial = false; + let structure = StructureBuilder::new(); + structure.set_alignment(effective_alignment.into()); + + let mut errors = vec![]; + let mut first_bitfield_seq = None; + for (i, member) in members.iter().enumerate() { + match (&member.member_type, first_bitfield_seq) { + // accumulate the bitfield to be condensated + (TILType::Bitfield(_bit), None) => { + first_bitfield_seq = Some(i); + continue; + } + (TILType::Bitfield(_bit), Some(_)) => continue, + + // condensate the bitfields into byte-wide fields + (_, Some(start_idx)) => { + first_bitfield_seq = None; + let members_bitrange = &members[start_idx..i]; + self.translate_bitfields_into_struct(start_idx, members_bitrange, &structure); + } + + (_, None) => {} + } + + let mem = match self.translate_type(&member.member_type) { + TranslateTypeResult::Translated(ty) => ty, + TranslateTypeResult::PartiallyTranslated(partial_ty, error) => { + is_partial |= true; + if let Some(error) = error { + errors.push((i, error)); + } + partial_ty + } + TranslateTypeResult::NotYet => return TranslateTypeResult::NotYet, + TranslateTypeResult::Error(error) => { + errors.push((i, error)); + return TranslateTypeResult::Error(BnTypeError::Struct(errors)); + } + }; + let name = member + .name + .to_owned() + .unwrap_or_else(|| format!("member_{i}")); + structure.append(&mem, name, BNMemberAccess::NoAccess, BNMemberScope::NoScope); + } + if let Some(start_idx) = first_bitfield_seq { + let members_bitrange = &members[start_idx..]; + self.translate_bitfields_into_struct(start_idx, members_bitrange, &structure); + } + let bn_ty = Type::structure(&structure.finalize()); + if is_partial { + let partial_error = (!errors.is_empty()).then_some(BnTypeError::Struct(errors)); + TranslateTypeResult::PartiallyTranslated(bn_ty, partial_error) + } else { + assert!(errors.is_empty()); + TranslateTypeResult::Translated(bn_ty) + } + } + + fn translate_union( + &self, + members: &[(Option<String>, TILType)], + _effective_alignment: u16, + ) -> TranslateTypeResult { + let mut is_partial = false; + let structure = StructureBuilder::new(); + structure.set_structure_type(StructureType::UnionStructureType); + let mut errors = vec![]; + for (i, (member_name, member_type)) in members.iter().enumerate() { + // bitfields can be translated into complete fields + let mem = match member_type { + TILType::Bitfield(field) => field_from_bytes(field.nbytes), + member_type => match self.translate_type(member_type) { + TranslateTypeResult::Translated(ty) => ty, + TranslateTypeResult::Error(error) => { + errors.push((i, error)); + return TranslateTypeResult::Error(BnTypeError::Union(errors)); + } + TranslateTypeResult::NotYet => return TranslateTypeResult::NotYet, + TranslateTypeResult::PartiallyTranslated(partial, error) => { + is_partial |= true; + if let Some(error) = error { + errors.push((i, error)); + } + partial + } + }, + }; + + let name = member_name + .to_owned() + .unwrap_or_else(|| format!("member_{i}")); + structure.append(&mem, name, BNMemberAccess::NoAccess, BNMemberScope::NoScope); + } + let str_ref = structure.finalize(); + + let bn_ty = Type::structure(&str_ref); + if is_partial { + let partial_error = (!errors.is_empty()).then_some(BnTypeError::Struct(errors)); + TranslateTypeResult::PartiallyTranslated(bn_ty, partial_error) + } else { + assert!(errors.is_empty()); + TranslateTypeResult::Translated(bn_ty) + } + } + + fn translate_type(&self, ty: &TILType) -> TranslateTypeResult { + match &ty { + // types that are always translatable + TILType::Basic(meta) => TranslateTypeResult::Translated(translate_basic(meta)), + TILType::Bitfield(bit) => TranslateTypeResult::Translated(field_from_bytes(bit.nbytes)), + TILType::Enum(TILEnum::NonRef { + members, bytesize, .. + }) => TranslateTypeResult::Translated(translate_enum(members, *bytesize)), + TILType::Typedef(TILTypedef::Ordinal(ord)) => self + .find_typedef_by_ordinal((*ord).into()) + .unwrap_or_else(|| TranslateTypeResult::Error(BnTypeError::OrdinalNotFound(*ord))), + TILType::Typedef(TILTypedef::Name(name)) => { + self.find_typedef_by_name(name).unwrap_or_else(|| { + TranslateTypeResult::Error(BnTypeError::NameNotFound(name.to_owned())) + }) + } + + // may not be translatable imediatly, but the size is known and can be + // updated after alBasicers are finished + TILType::Union(TILUnion::Ref { ref_type, .. }) + | TILType::Struct(TILStruct::Ref { ref_type, .. }) + | TILType::Enum(TILEnum::Ref { ref_type, .. }) => self.translate_pointer(&**ref_type), + TILType::Pointer(ty) => self.translate_pointer(&ty.typ), + TILType::Function(fun) => self.translate_function(fun), + + // can only be partially solved if all fields are solved or partially solved + TILType::Array(array) => self.translate_array(array), + TILType::Struct(TILStruct::NonRef { + members, + effective_alignment, + .. + }) => self.translate_struct(members, *effective_alignment), + TILType::Union(TILUnion::NonRef { + members, + effective_alignment, + .. + }) => self.translate_union(members, *effective_alignment), + } + } +} + +fn find_typedef_named_type_class(ty: &TILType) -> NamedTypeReferenceClass { + match ty { + TILType::Typedef(_) => NamedTypeReferenceClass::TypedefNamedTypeClass, + TILType::Struct(_) => NamedTypeReferenceClass::StructNamedTypeClass, + TILType::Union(_) => NamedTypeReferenceClass::UnionNamedTypeClass, + TILType::Enum(_) => NamedTypeReferenceClass::EnumNamedTypeClass, + _ => NamedTypeReferenceClass::UnknownNamedTypeClass, + } +} + +fn field_from_bytes(bytes: i32) -> Ref<Type> { + match bytes { + 0 => unreachable!(), + num @ (1 | 2 | 4 | 8 | 16) => Type::int(num.try_into().unwrap(), false), + nelem => Type::array(&Type::char(), nelem.try_into().unwrap()), + } +} + +fn parse_til_section_info( + debug_info: &mut DebugInfo, + debug_file: &BinaryView, + til: TILSection, + progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, +) -> Result<()> { + let total = til.symbols.len() + til.types.len(); + let mut types = Vec::with_capacity(total); + let mut types_by_ord = HashMap::with_capacity(total); + let mut types_by_name = HashMap::with_capacity(total); + let all_types = til.types.iter().zip(core::iter::repeat(false)); + // TODO: it's unclear how the demangle symbols and types names/ord, for now only parse types + //let all_types = all_types.chain(til.symbols.iter().zip(core::iter::repeat(true))); + for (i, (ty, is_symbol)) in all_types.enumerate() { + // TODO sanitized the input + // TODO find out how the namespaces used by TIL works + let name = ty.name.to_owned(); + types.push(TranslatesIDBType { + name, + is_symbol, + og_ty: ty, + _class: find_typedef_named_type_class(&ty.tinfo), + ty: TranslateTypeResult::NotYet, + }); + if ty.ordinal != 0 && !is_symbol { + let dup1 = types_by_ord.insert(ty.ordinal, i); + if let Some(old) = dup1 { + let old_type = &types[old]; + let new_type = types.last().unwrap(); + // TODO error? + panic!( + "dup ord {}:{} {}:\n{:?}\n{:?}", + old_type.is_symbol, + new_type.is_symbol, + ty.ordinal, + &old_type.og_ty, + &new_type.og_ty, + ) + } + } + if ty.name != "" { + let dup2 = types_by_name.insert(ty.name.to_owned(), i); + if let Some(old) = dup2 { + let old_type = &types[old]; + let new_type = types.last().unwrap(); + // TODO error? + panic!( + "dup name {}:{}: {}:\n{:?}\n{:?}", + old_type.is_symbol, + new_type.is_symbol, + &ty.name, + &old_type.og_ty, + &new_type.og_ty, + ) + } + } + } + + let mut translator = TranslateIDBTypes { + debug_info, + _debug_file: debug_file, + arch: debug_file.default_arch().unwrap(/* TODO */), + progress, + til: &til, + types, + types_by_ord, + types_by_name, + }; + if (translator.progress)(0, total).is_err() { + warn!("IDB import aborted"); + return Ok(()); + } + + // solve types until there is nothing else being solved + loop { + // is something was solved, mark this variable as true + let mut did_something = false; + let mut num_translated = 0usize; + for i in 0..translator.types.len() { + match &translator.types[i].ty { + TranslateTypeResult::NotYet => { + let result = translator.translate_type(&translator.types[i].og_ty.tinfo); + did_something |= !matches!(&result, TranslateTypeResult::NotYet); + translator.types[i].ty = result; + // if originaly NotKnow and now translated, update the result on bn + match &translator.types[i].ty { + // ignore partial errors here, they will be printed below + TranslateTypeResult::PartiallyTranslated(bn_ty, _) + | TranslateTypeResult::Translated(bn_ty) => { + let name = &translator.types[i].name; + let success = + translator.debug_info.add_type(name, &bn_ty, &[/* TODO */]); + if !success { + error!("Unable to add type `{}`", name) + } + } + _ => {} + } + } + TranslateTypeResult::PartiallyTranslated(_, None) => { + let result = translator.translate_type(&translator.types[i].og_ty.tinfo); + assert!(!matches!(&result, TranslateTypeResult::NotYet)); + did_something |= + !matches!(&result, TranslateTypeResult::PartiallyTranslated(_, None)); + translator.types[i].ty = result; + // don't need to add again they will be fixed on the loop below + } + // if an error was produced, there is no point in try again + TranslateTypeResult::PartiallyTranslated(_, Some(_)) => {} + // NOTE for now we are just accumulating errors, just try to translate the max number + // of types as possible + TranslateTypeResult::Error(_) => {} + // already translated, nothing do to here + TranslateTypeResult::Translated(_) => {} + } + + // count the number of finished types + match &translator.types[i].ty { + TranslateTypeResult::Translated(_) => num_translated += 1, + _ => {} + } + } + + if !did_something { + // means we acomplilshed nothing during this loop, there is no point in trying again + break; + } + if (translator.progress)(num_translated, total).is_err() { + // error means the user aborted the progress + break; + } + } + + // print any errors + for ty in &translator.types { + match &ty.ty { + TranslateTypeResult::NotYet => { + panic!( + "type could not be processed `{}`: {:#?}", + &ty.name, &ty.og_ty + ); + } + TranslateTypeResult::Error(error) => { + error!("Unable to parse type `{}`: {error}", &ty.name); + } + TranslateTypeResult::PartiallyTranslated(_, error) => { + if let Some(error) = error { + error!("Unable to parse type `{}` correctly: {error}", &ty.name); + } else { + warn!("Type `{}` maybe not be fully translated", &ty.name); + } + } + TranslateTypeResult::Translated(_) => {} + }; + } + + // add a second time to fix the references LOL + for ty in &translator.types { + match &ty.ty { + TranslateTypeResult::Translated(bn_ty) + | TranslateTypeResult::PartiallyTranslated(bn_ty, _) => { + let success = translator + .debug_info + .add_type(&ty.name, &bn_ty, &[/* TODO */]); + if !success { + error!("Unable to fix type `{}`", &ty.name) + } + } + _ => {} + } + } + + Ok(()) +} + +#[no_mangle] +pub extern "C" fn CorePluginInit() -> bool { + let _logger = logger::init(LevelFilter::Error); + DebugInfoParser::register("IDB Parser", IDBDebugInfoParser); + DebugInfoParser::register("TIL Parser", TILDebugInfoParser); + true +} |
