summaryrefslogtreecommitdiff
path: root/rust/examples/minidump/src
diff options
context:
space:
mode:
authorCindy Xiao <git@cxiao.net>2023-02-11 11:25:38 -0800
committerKyle Martin <krm504@nyu.edu>2023-02-20 10:45:04 -0500
commit56996b0617c499178b463eb27973e7b12a056ffc (patch)
tree9035cee98b515fa2946d5d115a9eac3768f9d8bc /rust/examples/minidump/src
parent9993345001464331697167349803235498218652 (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.rs44
-rw-r--r--rust/examples/minidump/src/lib.rs37
-rw-r--r--rust/examples/minidump/src/view.rs426
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()
+ }
+}