diff --git a/rust/examples/dwarf/dwarf_import/src/helpers.rs b/rust/examples/dwarf/dwarf_import/src/helpers.rs index ed40d446c..ae338a4be 100644 --- a/rust/examples/dwarf/dwarf_import/src/helpers.rs +++ b/rust/examples/dwarf/dwarf_import/src/helpers.rs @@ -12,8 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::DebugInfoBuilderContext; +use std::{ + collections::HashMap, + ops::Deref, + sync::mpsc, + str::FromStr +}; +use crate::DebugInfoBuilderContext; +use binaryninja::binaryview::BinaryViewBase; +use binaryninja::filemetadata::FileMetadata; +use binaryninja::Endianness; +use binaryninja::{binaryview::{BinaryView, BinaryViewExt}, downloadprovider::{DownloadInstanceInputOutputCallbacks, DownloadProvider}, rc::Ref, settings::Settings}; use gimli::{ constants, Attribute, AttributeValue, AttributeValue::{DebugInfoRef, UnitRef}, @@ -291,3 +301,123 @@ pub(crate) fn get_expr_value>( None } } + + +pub(crate) fn download_debug_info(view: &BinaryView) -> Result, String> { + let settings = Settings::new(""); + + let mut build_id: Option = None; + + if let Ok(raw_view) = view.raw_view() { + if let Ok(build_id_section) = raw_view.section_by_name(".note.gnu.build-id") { + // Name size - 4 bytes + // Desc size - 4 bytes + // Type - 4 bytes + // Name - n bytes + // Desc - n bytes + let build_id_bytes = raw_view.read_vec(build_id_section.start(), build_id_section.len()); + if build_id_bytes.len() < 12 { + return Err("Build id section must be at least 12 bytes".to_string()); + } + + let name_len: u32; + let desc_len: u32; + let note_type: u32; + match raw_view.default_endianness() { + Endianness::LittleEndian => { + name_len = u32::from_le_bytes(build_id_bytes[0..4].try_into().unwrap()); + desc_len = u32::from_le_bytes(build_id_bytes[4..8].try_into().unwrap()); + note_type = u32::from_le_bytes(build_id_bytes[8..12].try_into().unwrap()); + }, + Endianness::BigEndian => { + name_len = u32::from_be_bytes(build_id_bytes[0..4].try_into().unwrap()); + desc_len = u32::from_be_bytes(build_id_bytes[4..8].try_into().unwrap()); + note_type = u32::from_be_bytes(build_id_bytes[8..12].try_into().unwrap()); + } + }; + + if note_type != 3 { + return Err(format!("Build id section has wrong type: {}", note_type)); + } + + let expected_len = (12 + name_len + desc_len) as usize; + + if build_id_bytes.len() < expected_len { + return Err(format!("Build id section not expected length: expected {}, got {}", expected_len, build_id_bytes.len())); + } + + let desc: &[u8] = &build_id_bytes[(12+name_len as usize)..expected_len]; + build_id = Some(desc.iter().map(|b| format!("{:02x}", b)).collect()); + } + } + + if build_id.is_none() { + return Err("Failed to get build id".to_string()); + } + + let debug_server_urls = settings.get_string_list("network.debuginfodServers", Some(view), None); + + for debug_server_url in debug_server_urls.iter() { + let artifact_url = format!("{}/buildid/{}/debuginfo", debug_server_url, build_id.as_ref().unwrap()); + + // Download from remote + let (tx, rx) = mpsc::channel(); + let write = move |data: &[u8]| -> usize { + if let Ok(_) = tx.send(Vec::from(data)) { + data.len() + } else { + 0 + } + }; + + let dp = DownloadProvider::try_default().map_err(|_| "No default download provider")?; + let mut inst = dp + .create_instance() + .map_err(|_| "Couldn't create download instance")?; + let result = inst + .perform_custom_request( + "GET", + artifact_url, + HashMap::::new(), + DownloadInstanceInputOutputCallbacks { + read: None, + write: Some(Box::new(write)), + progress: None, + }, + ) + .map_err(|e| e.to_string())?; + if result.status_code != 200 { + continue; + } + + let mut expected_length = None; + for (k, v) in result.headers.iter() { + if k.to_lowercase() == "content-length" { + expected_length = Some(usize::from_str(v).map_err(|e| e.to_string())?); + } + } + + let mut data = vec![]; + while let Ok(packet) = rx.try_recv() { + data.extend(packet.into_iter()); + } + + if let Some(length) = expected_length { + if data.len() != length { + return Err(format!( + "Bad length: expected {} got {}", + length, + data.len() + )); + } + } + + let options = "{\"analysis.debugInfo.internal\": false}"; + let bv = BinaryView::from_data(FileMetadata::new().deref(), &data) + .map_err(|_| "Unable to create binary view from downloaded data".to_string())?; + + return binaryninja::load_view(bv.deref(), false, Some(options)) + .ok_or("Unable to load binary view from downloaded data".to_string()); + } + return Err("Could not find a server with debug info for this file".to_string()); +} diff --git a/rust/examples/dwarf/dwarf_import/src/lib.rs b/rust/examples/dwarf/dwarf_import/src/lib.rs index 8aeac6587..a752d351f 100644 --- a/rust/examples/dwarf/dwarf_import/src/lib.rs +++ b/rust/examples/dwarf/dwarf_import/src/lib.rs @@ -27,6 +27,7 @@ use binaryninja::{ binaryview::{BinaryView, BinaryViewExt}, debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, logger, + settings::Settings, templatesimplifier::simplify_str_to_str, }; use dwarfreader::{ @@ -213,21 +214,22 @@ fn parse_unit>( } fn parse_dwarf( - view: &BinaryView, + bv: &BinaryView, progress: Box Result<(), ()>>, -) -> DebugInfoBuilder { +) -> Result { // Determine if this is a DWO // TODO : Make this more robust...some DWOs follow non-DWO conventions - let dwo_file = is_dwo_dwarf(view) || is_raw_dwo_dwarf(view); // Figure out if it's the given view or the raw view that has the dwarf info in it - let raw_view = &view.raw_view().unwrap(); - let view = if is_dwo_dwarf(view) || is_non_dwo_dwarf(view) { - view + let raw_view = &bv.raw_view().unwrap(); + let view = if is_dwo_dwarf(bv) || is_non_dwo_dwarf(bv) { + bv } else { raw_view }; + let dwo_file = is_dwo_dwarf(view) || is_raw_dwo_dwarf(view); + // gimli setup let endian = get_endian(view); let mut section_reader = @@ -246,7 +248,7 @@ fn parse_dwarf( if !recover_names(&mut debug_info_builder_context, &progress) || debug_info_builder_context.total_die_count == 0 { - return debug_info_builder; + return Ok(debug_info_builder); } // Parse all the compilation units @@ -261,14 +263,14 @@ fn parse_dwarf( ); } } - debug_info_builder + Ok(debug_info_builder) } struct DWARFParser; impl CustomDebugInfoParser for DWARFParser { fn is_valid(&self, view: &BinaryView) -> bool { - dwarfreader::is_valid(view) + dwarfreader::is_valid(view) || dwarfreader::can_use_debuginfod(view) } fn parse_info( @@ -278,10 +280,28 @@ impl CustomDebugInfoParser for DWARFParser { debug_file: &BinaryView, progress: Box Result<(), ()>>, ) -> bool { - parse_dwarf(debug_file, progress) - .post_process(bv, debug_info) - .commit_info(debug_info); - true + let external_file = if !dwarfreader::is_valid(bv) && dwarfreader::can_use_debuginfod(bv) { + //TODO: try to download from debuginfod if there is no debug info in the binary + if let Ok(debug_view) = helpers::download_debug_info(bv) { + Some(debug_view) + } else { + None + } + } else { + None + }; + + match parse_dwarf(external_file.as_deref().unwrap_or(debug_file), progress) { + Ok(mut builder) => { + builder + .post_process(bv, debug_info) + .commit_info(debug_info); + true + }, + Err(_) => { + false + } + } } } @@ -289,6 +309,31 @@ impl CustomDebugInfoParser for DWARFParser { pub extern "C" fn CorePluginInit() -> bool { logger::init(LevelFilter::Debug).unwrap(); + let settings = Settings::new(""); + + settings.register_setting_json( + "network.enableDebuginfod", + r#"{ + "title" : "Enable Debuginfod Support", + "type" : "boolean", + "default" : false, + "description" : "Enable using debuginfod servers to fetch debug info for files with a .note.gnu.build-id section.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "network.debuginfodServers", + r#"{ + "title" : "Debuginfod Server URLs", + "type" : "array", + "elementType" : "string", + "default" : [], + "description" : "Servers to use for fetching debug info for files with a .note.gnu.build-id section.", + "ignore" : [] + }"#, + ); + DebugInfoParser::register("DWARF", DWARFParser {}); true } diff --git a/rust/examples/dwarf/shared/src/lib.rs b/rust/examples/dwarf/shared/src/lib.rs index 7712ff3b3..76c903e4b 100644 --- a/rust/examples/dwarf/shared/src/lib.rs +++ b/rust/examples/dwarf/shared/src/lib.rs @@ -20,6 +20,7 @@ use binaryninja::{ binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}, databuffer::DataBuffer, Endianness, + settings::Settings, }; use std::{ffi::CString, rc::Rc}; @@ -52,6 +53,15 @@ pub fn is_raw_dwo_dwarf(view: &BinaryView) -> bool { } } +pub fn can_use_debuginfod(view: &BinaryView) -> bool { + if let Ok(raw_view) = view.raw_view() { + if raw_view.section_by_name(".note.gnu.build-id").is_ok() { + return Settings::new("").get_bool("network.enableDebuginfod", Some(view), None); + } + } + false +} + pub fn is_valid(view: &BinaryView) -> bool { is_non_dwo_dwarf(view) || is_raw_non_dwo_dwarf(view) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 1c2dacd0c..e24a3f784 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -269,6 +269,41 @@ pub fn load_with_options( } } +pub fn load_view( + bv: &BinaryView, + update_analysis_and_wait: bool, + options: Option, +) -> Option> { + let options_or_default = if let Some(opt) = options { + opt.get_json_string() + .ok()? + .into_bytes_with_nul() + .as_ref() + .to_vec() + } else { + Metadata::new_of_type(MetadataType::KeyValueDataType) + .get_json_string() + .ok()? + .as_ref() + .to_vec() + }; + + let handle = unsafe { + binaryninjacore_sys::BNLoadBinaryView( + bv.handle as *mut _, + update_analysis_and_wait, + options_or_default.as_ptr() as *mut core::ffi::c_char, + None, + ) + }; + + if handle.is_null() { + None + } else { + Some(unsafe { BinaryView::from_raw(handle) }) + } +} + pub fn install_directory() -> Result { let s: *mut std::os::raw::c_char = unsafe { binaryninjacore_sys::BNGetInstallDirectory() }; if s.is_null() {