Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add binaryninja::load_view and add debuginfod support to dwarf_import #5482

Merged
merged 2 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 131 additions & 1 deletion rust/examples/dwarf/dwarf_import/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -291,3 +301,123 @@ pub(crate) fn get_expr_value<R: Reader<Offset = usize>>(
None
}
}


pub(crate) fn download_debug_info(view: &BinaryView) -> Result<Ref<BinaryView>, String> {
let settings = Settings::new("");

let mut build_id: Option<String> = 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::<String, String>::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());
}
71 changes: 58 additions & 13 deletions rust/examples/dwarf/dwarf_import/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use binaryninja::{
binaryview::{BinaryView, BinaryViewExt},
debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser},
logger,
settings::Settings,
templatesimplifier::simplify_str_to_str,
};
use dwarfreader::{
Expand Down Expand Up @@ -213,21 +214,22 @@ fn parse_unit<R: Reader<Offset = usize>>(
}

fn parse_dwarf(
view: &BinaryView,
bv: &BinaryView,
progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
) -> DebugInfoBuilder {
) -> Result<DebugInfoBuilder, ()> {
// 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 =
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -278,17 +280,60 @@ impl CustomDebugInfoParser for DWARFParser {
debug_file: &BinaryView,
progress: Box<dyn Fn(usize, usize) -> 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
}
}
}
}

#[no_mangle]
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
}
10 changes: 10 additions & 0 deletions rust/examples/dwarf/shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use binaryninja::{
binaryview::{BinaryView, BinaryViewBase, BinaryViewExt},
databuffer::DataBuffer,
Endianness,
settings::Settings,
};

use std::{ffi::CString, rc::Rc};
Expand Down Expand Up @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,41 @@ pub fn load_with_options<S: BnStrCompatible, O: IntoJson>(
}
}

pub fn load_view<O: IntoJson>(
bv: &BinaryView,
update_analysis_and_wait: bool,
options: Option<O>,
) -> Option<rc::Ref<binaryview::BinaryView>> {
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<PathBuf, ()> {
let s: *mut std::os::raw::c_char = unsafe { binaryninjacore_sys::BNGetInstallDirectory() };
if s.is_null() {
Expand Down
Loading