diff options
| author | Cindy Xiao <git@cxiao.net> | 2023-02-11 11:25:38 -0800 |
|---|---|---|
| committer | Kyle Martin <krm504@nyu.edu> | 2023-02-20 10:45:04 -0500 |
| commit | 56996b0617c499178b463eb27973e7b12a056ffc (patch) | |
| tree | 9035cee98b515fa2946d5d115a9eac3768f9d8bc /rust/examples/minidump/src | |
| parent | 9993345001464331697167349803235498218652 (diff) | |
Rust API : Add minidump example
Squashed commit message history:
Initial commit
chore: Add files from cargo new, Binary Ninja Rust API template
chore: Add README
feat: Add basic plugin registration code
feat: Add command to print memory information from minidump
I can't believe this actually worked the first time
feat: Set up registration of Minidump BinaryView type
Also set architecture based on contents of MinidumpSystemInfo stream in
the minidump.
feat: Perform basic segment mapping from MinidumpMemoryList, MinidumpMemory64List
feat: Read and apply memory segment protection info from MinidumpMemoryInfoList
feat: Log action to add segments at info level
docs: Update readme with build instructions, screenshot
style: Minor cleanup of types
refactor: Remove use of unwrap when parsing raw BaseRVA
docs: Add doc comments to view module
feat: More logging in print_memory_information command, remove unwrap
refactor: Add struct for representing memory protection flags
fix: Correct information about difference between MinidumpMemoryList, MinidumpMemory64List
MinidumpMemory64List is always used for "full dumps", i.e. dumps which
include the full process memory. It does not have to do with 64-bit
segments specifically.
docs: Clarify Windows-only support for now in README
fix: First try to find full dump memory in MinidumpMemory64List before looking for partial dump memory in MinidumpMemoryList
docs: Update README with examples of how to generate minidumps
feat: Parse module information in MinidumpModuleList, and add modules as sections
docs: Add information about unsupported features
docs: Update README with screenshots, explanation of memory map
docs: Update TODOs in BinaryViewBase impl
Rust Minidump example : Clippy appeasements; remove network dependency
Diffstat (limited to 'rust/examples/minidump/src')
| -rw-r--r-- | rust/examples/minidump/src/command.rs | 44 | ||||
| -rw-r--r-- | rust/examples/minidump/src/lib.rs | 37 | ||||
| -rw-r--r-- | rust/examples/minidump/src/view.rs | 426 |
3 files changed, 507 insertions, 0 deletions
diff --git a/rust/examples/minidump/src/command.rs b/rust/examples/minidump/src/command.rs new file mode 100644 index 00000000..0b10c65a --- /dev/null +++ b/rust/examples/minidump/src/command.rs @@ -0,0 +1,44 @@ +use std::str; + +use log::{debug, error, info}; +use minidump::{Minidump, MinidumpMemoryInfoList}; + +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; + +use crate::view::DataBufferWrapper; + +pub fn print_memory_information(bv: &BinaryView) { + debug!("Printing memory information"); + if let Ok(minidump_bv) = bv.parent_view() { + if let Ok(read_buffer) = minidump_bv.read_buffer(0, minidump_bv.len()) { + let read_buffer = DataBufferWrapper::new(read_buffer); + if let Ok(minidump_obj) = Minidump::read(read_buffer) { + if let Ok(memory_info_list) = minidump_obj.get_stream::<MinidumpMemoryInfoList>() { + let mut memory_info_list_writer = Vec::new(); + match memory_info_list.print(&mut memory_info_list_writer) { + Ok(_) => { + if let Ok(memory_info_str) = str::from_utf8(&memory_info_list_writer) { + info!("{memory_info_str}"); + } else { + error!("Could not convert the memory information description from minidump into a valid string"); + } + } + Err(_) => { + error!("Could not get memory information from minidump"); + } + } + } else { + error!( + "Could not parse a valid MinidumpMemoryInfoList stream from the minidump" + ); + } + } else { + error!("Could not parse a valid minidump file from the parent binary view's data buffer"); + } + } else { + error!("Could not read data from parent binary view"); + } + } else { + error!("Could not get the parent binary view"); + } +} diff --git a/rust/examples/minidump/src/lib.rs b/rust/examples/minidump/src/lib.rs new file mode 100644 index 00000000..9d8b4054 --- /dev/null +++ b/rust/examples/minidump/src/lib.rs @@ -0,0 +1,37 @@ +use binaryninja::binaryview::BinaryView; +use binaryninja::command::{register, Command}; +use binaryninja::custombinaryview::register_view_type; +use log::{debug, LevelFilter}; + +mod command; +mod view; + +struct PrintMemoryInformationCommand; + +impl Command for PrintMemoryInformationCommand { + fn action(&self, binary_view: &BinaryView) { + command::print_memory_information(binary_view); + } + + fn valid(&self, _binary_view: &BinaryView) -> bool { + true // TODO: Of course, the command will not always be valid! + } +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginInit() -> bool { + binaryninja::logger::init(LevelFilter::Trace).expect("failed to initialize logging"); + + debug!("Registering minidump binary view type"); + register_view_type("Minidump", "Minidump", view::MinidumpBinaryViewType::new); + + debug!("Registering minidump plugin commands"); + register( + "Minidump\\[DEBUG] Print Minidump Memory Information", + "Print a human-readable description of the contents of the MinidumpMemoryInfoList stream in the loaded minidump", + PrintMemoryInformationCommand {}, + ); + + true +} diff --git a/rust/examples/minidump/src/view.rs b/rust/examples/minidump/src/view.rs new file mode 100644 index 00000000..9eee6aa6 --- /dev/null +++ b/rust/examples/minidump/src/view.rs @@ -0,0 +1,426 @@ +use std::collections::HashMap; +use std::ops::{Deref, Range}; +use std::sync::Arc; + +use binaryninja::section::Section; +use binaryninja::segment::Segment; +use log::{debug, error, info, warn}; +use minidump::format::MemoryProtection; +use minidump::{ + Minidump, MinidumpMemory64List, MinidumpMemoryInfoList, MinidumpMemoryList, MinidumpModuleList, + MinidumpStream, MinidumpSystemInfo, Module, +}; + +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +use binaryninja::custombinaryview::{ + BinaryViewType, BinaryViewTypeBase, CustomBinaryView, CustomBinaryViewType, CustomView, + CustomViewBuilder, +}; +use binaryninja::databuffer::DataBuffer; +use binaryninja::platform::Platform; +use binaryninja::Endianness; + +type BinaryViewResult<R> = binaryninja::binaryview::Result<R>; + +/// A wrapper around a `binaryninja::databuffer::DataBuffer`, from which a `[u8]` buffer can be obtained +/// to pass to `minidump::Minidump::read`. +/// +/// This code is taken from [`dwarfdump`](https://github.com/Vector35/binaryninja-api/blob/9d8bc846bd213407fb1a7a19af2a96f17501ac3b/rust/examples/dwarfdump/src/lib.rs#L81) +/// in the Rust API examples. +#[derive(Clone)] +pub struct DataBufferWrapper { + inner: Arc<DataBuffer>, +} + +impl DataBufferWrapper { + pub fn new(buf: DataBuffer) -> Self { + DataBufferWrapper { + inner: Arc::new(buf), + } + } +} + +impl Deref for DataBufferWrapper { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + self.inner.get_data() + } +} + +/// The _Minidump_ binary view type, which the Rust plugin registers with the Binary Ninja core +/// (via `binaryninja::custombinaryview::register_view_type`) as a possible binary view +/// that can be applied to opened binaries. +/// +/// If this view type is valid for an opened binary (determined by `is_valid_for`), +/// the Binary Ninja core then uses this view type to create an actual instance of the _Minidump_ +/// binary view (via `create_custom_view`). +pub struct MinidumpBinaryViewType { + view_type: BinaryViewType, +} + +impl MinidumpBinaryViewType { + pub fn new(view_type: BinaryViewType) -> Self { + MinidumpBinaryViewType { view_type } + } +} + +impl AsRef<BinaryViewType> for MinidumpBinaryViewType { + fn as_ref(&self) -> &BinaryViewType { + &self.view_type + } +} + +impl BinaryViewTypeBase for MinidumpBinaryViewType { + fn is_deprecated(&self) -> bool { + false + } + + fn is_valid_for(&self, data: &BinaryView) -> bool { + let mut magic_number = Vec::<u8>::new(); + data.read_into_vec(&mut magic_number, 0, 4); + + magic_number == b"MDMP" + } +} + +impl CustomBinaryViewType for MinidumpBinaryViewType { + fn create_custom_view<'builder>( + &self, + data: &BinaryView, + builder: CustomViewBuilder<'builder, Self>, + ) -> BinaryViewResult<CustomView<'builder>> { + debug!("Creating MinidumpBinaryView from registered MinidumpBinaryViewType"); + + let binary_view = builder.create::<MinidumpBinaryView>(data, ()); + binary_view + } +} + +#[derive(Debug)] +struct SegmentData { + rva_range: Range<u64>, + mapped_addr_range: Range<u64>, +} + +impl SegmentData { + fn from_addresses_and_size(rva: u64, mapped_addr: u64, size: u64) -> Self { + SegmentData { + rva_range: Range { + start: rva, + end: rva + size, + }, + mapped_addr_range: Range { + start: mapped_addr, + end: mapped_addr + size, + }, + } + } +} + +#[derive(Debug)] +struct SegmentMemoryProtection { + readable: bool, + writable: bool, + executable: bool, +} + +/// An instance of the actual _Minidump_ custom binary view. +/// This contains the main logic to load the memory segments inside a minidump file into the binary view. +pub struct MinidumpBinaryView { + /// The handle to the "real" BinaryView object, in the Binary Ninja core. + inner: binaryninja::rc::Ref<BinaryView>, +} + +impl MinidumpBinaryView { + fn new(view: &BinaryView) -> Self { + MinidumpBinaryView { + inner: view.to_owned(), + } + } + + fn init(&self) -> BinaryViewResult<()> { + let parent_view = self.parent_view()?; + let read_buffer = parent_view.read_buffer(0, parent_view.len())?; + let read_buffer = DataBufferWrapper::new(read_buffer); + + if let Ok(minidump_obj) = Minidump::read(read_buffer) { + // Architecture, platform information + if let Ok(minidump_system_info) = minidump_obj.get_stream::<MinidumpSystemInfo>() { + if let Some(platform) = MinidumpBinaryView::translate_minidump_platform( + minidump_system_info.cpu, + minidump_obj.endian, + minidump_system_info.os, + ) { + self.set_default_platform(&platform); + } else { + error!( + "Could not parse valid system information from minidump: could not map system information in MinidumpSystemInfo stream (arch {:?}, endian {:?}, os {:?}) to a known architecture", + minidump_system_info.cpu, + minidump_obj.endian, + minidump_system_info.os, + ); + return Err(()); + } + } else { + error!("Could not parse system information from minidump: could not find a valid MinidumpSystemInfo stream"); + return Err(()); + } + + // Memory segments + let mut segment_data = Vec::<SegmentData>::new(); + + // Memory segments in a full memory dump (MinidumpMemory64List) + // Grab the shared base RVA for all entries in the MinidumpMemory64List, + // since the minidump crate doesn't expose this to us + if let Ok(raw_stream) = minidump_obj.get_raw_stream(MinidumpMemory64List::STREAM_TYPE) { + if let Ok(base_rva_array) = raw_stream[8..16].try_into() { + let base_rva = u64::from_le_bytes(base_rva_array); + debug!("Found BaseRVA value {:#x}", base_rva); + + if let Ok(minidump_memory_list) = + minidump_obj.get_stream::<MinidumpMemory64List>() + { + let mut current_rva = base_rva; + for memory_segment in minidump_memory_list.iter() { + debug!( + "Found memory segment at RVA {:#x} with virtual address {:#x} and size {:#x}", + current_rva, + memory_segment.base_address, + memory_segment.size, + ); + segment_data.push(SegmentData::from_addresses_and_size( + current_rva, + memory_segment.base_address, + memory_segment.size, + )); + current_rva += memory_segment.size; + } + } + } else { + error!("Could not parse BaseRVA value shared by all entries in the MinidumpMemory64List stream") + } + } else { + warn!("Could not read memory from minidump: could not find a valid MinidumpMemory64List stream. This minidump may not be a full memory dump. Trying to find partial dump memory from a MinidumpMemoryList now..."); + // Memory segments in a regular memory dump (MinidumpMemoryList), + // i.e. one that does not include the full process memory data. + if let Ok(minidump_memory_list) = minidump_obj.get_stream::<MinidumpMemoryList>() { + for memory_segment in minidump_memory_list.by_addr() { + debug!( + "Found memory segment at RVA {:#x} with virtual address {:#x} and size {:#x}", + memory_segment.desc.memory.rva, + memory_segment.base_address, + memory_segment.size + ); + segment_data.push(SegmentData::from_addresses_and_size( + memory_segment.desc.memory.rva as u64, + memory_segment.base_address, + memory_segment.size, + )); + } + } else { + error!("Could not read any memory from minidump: could not find a valid MinidumpMemory64List stream or a valid MinidumpMemoryList stream."); + } + } + + // Memory protection information + let mut segment_protection_data = HashMap::new(); + + if let Ok(minidump_memory_info_list) = + minidump_obj.get_stream::<MinidumpMemoryInfoList>() + { + for memory_info in minidump_memory_info_list.iter() { + if let Some(memory_range) = memory_info.memory_range() { + debug!( + "Found memory protection info for memory segment ranging from virtual address {:#x} to {:#x}: {:#?}", + memory_range.start, + memory_range.end, + memory_info.protection + ); + segment_protection_data.insert( + // The range returned to us by MinidumpMemoryInfoList is an + // end-inclusive range_map::Range; we need to add 1 to + // the end index to make it into an end-exclusive std::ops::Range. + Range { + start: memory_range.start, + end: memory_range.end + 1, + }, + memory_info.protection, + ); + } + } + } + + for segment in segment_data.iter() { + if let Some(segment_protection) = + segment_protection_data.get(&segment.mapped_addr_range) + { + let segment_memory_protection = + MinidumpBinaryView::translate_memory_protection(*segment_protection); + + info!( + "Adding memory segment at virtual address {:#x} to {:#x}, from data range {:#x} to {:#x}, with protections readable {}, writable {}, executable {}", + segment.mapped_addr_range.start, + segment.mapped_addr_range.end, + segment.rva_range.start, + segment.rva_range.end, + segment_memory_protection.readable, + segment_memory_protection.writable, + segment_memory_protection.executable, + ); + + self.add_segment( + Segment::builder(segment.mapped_addr_range.clone()) + .parent_backing(segment.rva_range.clone()) + .is_auto(true) + .readable(segment_memory_protection.readable) + .writable(segment_memory_protection.writable) + .executable(segment_memory_protection.executable), + ); + } else { + error!( + "Could not find memory protection information for memory segment from {:#x} to {:#x}", segment.mapped_addr_range.start, + segment.mapped_addr_range.end, + ); + } + } + + // Module information + // This stretches the concept a bit, but we can add each module as a + // separate "section" of the binary. + // Sections can be named, and can span multiple segments. + if let Ok(minidump_module_list) = minidump_obj.get_stream::<MinidumpModuleList>() { + for module_info in minidump_module_list.by_addr() { + info!( + "Found module with name {} at virtual address {:#x} with size {:#x}", + module_info.name, + module_info.base_address(), + module_info.size(), + ); + let module_address_range = Range { + start: module_info.base_address(), + end: module_info.base_address() + module_info.size(), + }; + self.add_section( + Section::builder(module_info.name.clone(), module_address_range) + .is_auto(true), + ); + } + } else { + warn!("Could not find valid module information in minidump: could not find a valid MinidumpModuleList stream"); + } + } else { + error!("Could not parse data as minidump"); + return Err(()); + } + Ok(()) + } + + fn translate_minidump_platform( + minidump_cpu_arch: minidump::system_info::Cpu, + minidump_endian: minidump::Endian, + minidump_os: minidump::system_info::Os, + ) -> Option<binaryninja::rc::Ref<Platform>> { + match minidump_os { + minidump::system_info::Os::Windows => match minidump_cpu_arch { + minidump::system_info::Cpu::Arm64 => Platform::by_name("windows-aarch64"), + minidump::system_info::Cpu::Arm => Platform::by_name("windows-armv7"), + minidump::system_info::Cpu::X86 => Platform::by_name("windows-x86"), + minidump::system_info::Cpu::X86_64 => Platform::by_name("windows-x86_64"), + _ => None, + }, + minidump::system_info::Os::MacOs => match minidump_cpu_arch { + minidump::system_info::Cpu::Arm64 => Platform::by_name("mac-aarch64"), + minidump::system_info::Cpu::Arm => Platform::by_name("mac-armv7"), + minidump::system_info::Cpu::X86 => Platform::by_name("mac-x86"), + minidump::system_info::Cpu::X86_64 => Platform::by_name("mac-x86_64"), + _ => None, + }, + minidump::system_info::Os::Linux => match minidump_cpu_arch { + minidump::system_info::Cpu::Arm64 => Platform::by_name("linux-aarch64"), + minidump::system_info::Cpu::Arm => Platform::by_name("linux-armv7"), + minidump::system_info::Cpu::X86 => Platform::by_name("linux-x86"), + minidump::system_info::Cpu::X86_64 => Platform::by_name("linux-x86_64"), + minidump::system_info::Cpu::Ppc => match minidump_endian { + minidump::Endian::Little => Platform::by_name("linux-ppc32_le"), + minidump::Endian::Big => Platform::by_name("linux-ppc32"), + }, + minidump::system_info::Cpu::Ppc64 => match minidump_endian { + minidump::Endian::Little => Platform::by_name("linux-ppc64_le"), + minidump::Endian::Big => Platform::by_name("linux-ppc64"), + }, + _ => None, + }, + minidump::system_info::Os::NaCl => None, + minidump::system_info::Os::Android => None, + minidump::system_info::Os::Ios => None, + minidump::system_info::Os::Ps3 => None, + minidump::system_info::Os::Solaris => None, + _ => None, + } + } + + fn translate_memory_protection( + minidump_memory_protection: MemoryProtection, + ) -> SegmentMemoryProtection { + let (readable, writable, executable) = match minidump_memory_protection { + MemoryProtection::PAGE_NOACCESS => (false, false, false), + MemoryProtection::PAGE_READONLY => (true, false, false), + MemoryProtection::PAGE_READWRITE => (true, true, false), + MemoryProtection::PAGE_WRITECOPY => (true, true, false), + MemoryProtection::PAGE_EXECUTE => (false, false, true), + MemoryProtection::PAGE_EXECUTE_READ => (true, false, true), + MemoryProtection::PAGE_EXECUTE_READWRITE => (true, true, true), + MemoryProtection::PAGE_EXECUTE_WRITECOPY => (true, true, true), + MemoryProtection::ACCESS_MASK => (false, false, false), + MemoryProtection::PAGE_GUARD => (false, false, false), + MemoryProtection::PAGE_NOCACHE => (false, false, false), + MemoryProtection::PAGE_WRITECOMBINE => (false, false, false), + _ => (false, false, false), + }; + SegmentMemoryProtection { + readable, + writable, + executable, + } + } +} + +impl AsRef<BinaryView> for MinidumpBinaryView { + fn as_ref(&self) -> &BinaryView { + &self.inner + } +} + +impl BinaryViewBase for MinidumpBinaryView { + // TODO: This should be filled out with the actual address size + // from the platform information in the minidump. + fn address_size(&self) -> usize { + 0 + } + + fn default_endianness(&self) -> Endianness { + // TODO: This should be filled out with the actual endianness + // from the platform information in the minidump. + Endianness::LittleEndian + } + + fn entry_point(&self) -> u64 { + // TODO: We should fill this out with a real entry point. + // This can be done by getting the main module of the minidump + // with MinidumpModuleList::main_module, + // then parsing the PE metadata of the main module to find its entry point(s). + 0 + } +} + +unsafe impl CustomBinaryView for MinidumpBinaryView { + type Args = (); + + fn new(handle: &BinaryView, _args: &Self::Args) -> BinaryViewResult<Self> { + Ok(MinidumpBinaryView::new(handle)) + } + + fn init(&self, _args: Self::Args) -> BinaryViewResult<()> { + self.init() + } +} |
