initial commit

This commit is contained in:
Rairosu
2024-01-24 13:07:27 +01:00
commit 06c785c352
115 changed files with 33162 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
project(test_headless)
file(GLOB PLUGIN_SOURCES
${PROJECT_SOURCE_DIR}/Cargo.toml
${PROJECT_SOURCE_DIR}/src/*.rs)
file(GLOB API_SOURCES
${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)
endif()
set(OUTPUT_FILE basic_script${CMAKE_EXECUTABLE_SUFFIX})
set(OUTPUT_PATH ${CMAKE_BINARY_DIR}/out/bin/${OUTPUT_FILE})
if(NOT BN_API_BUILD_EXAMPLES)
# Out-of-tree build
find_path(
BN_API_PATH
NAMES binaryninjaapi.h
HINTS ../../.. binaryninjaapi
REQUIRED
)
add_subdirectory(${BN_API_PATH} api)
endif()
add_custom_target(test_headless ALL DEPENDS ${OUTPUT_PATH})
add_dependencies(test_headless binaryninjaapi)
# Get API source directory so we can find BinaryNinjaCore
get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR)
message(STATUS "${BN_API_SOURCE_DIR}")
list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake")
# BinaryNinjaCore has the user plugins dir define that we want
find_package(BinaryNinjaCore REQUIRED)
find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin)
set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build)
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 ${OUTPUT_PATH}
COMMAND ${CMAKE_COMMAND} -E env
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_INSTALL_BIN_DIR}
${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS}
COMMAND ${CMAKE_COMMAND} -E env
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_INSTALL_BIN_DIR}
${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS}
COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${OUTPUT_PATH}
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 ${OUTPUT_PATH}
COMMAND ${CMAKE_COMMAND} -E env
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_INSTALL_BIN_DIR}
${RUSTUP_COMMAND} ${CARGO_OPTS}
COMMAND ${CMAKE_COMMAND} -E copy ${LIB_PATH} ${OUTPUT_PATH}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
endif()
elseif(WIN32)
add_custom_command(
OUTPUT ${OUTPUT_PATH}
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_INSTALL_BIN_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS}
COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE} ${OUTPUT_PATH}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
else()
add_custom_command(
OUTPUT ${OUTPUT_PATH}
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_INSTALL_BIN_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS}
COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE} ${OUTPUT_PATH}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
endif()

View File

@@ -0,0 +1,8 @@
[package]
name = "basic_script"
version = "0.1.0"
authors = ["KyleMiles <kyle@vector35.com>"]
edition = "2021"
[dependencies]
binaryninja = {path="../../"}

View File

@@ -0,0 +1,68 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun");
#[cfg(target_os = "linux")]
static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun");
#[cfg(windows)]
static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun");
// Check last run location for path to BinaryNinja; Otherwise check the default install locations
fn link_path() -> PathBuf {
use std::io::prelude::*;
let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap());
let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1);
File::open(lastrun)
.and_then(|f| {
let mut binja_path = String::new();
let mut reader = BufReader::new(f);
reader.read_line(&mut binja_path)?;
Ok(PathBuf::from(binja_path.trim()))
})
.unwrap_or_else(|_| {
#[cfg(target_os = "macos")]
return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS");
#[cfg(target_os = "linux")]
return home.join("binaryninja");
#[cfg(windows)]
return PathBuf::from(env::var("PROGRAMFILES").unwrap())
.join("Vector35\\BinaryNinja\\");
})
}
fn main() {
// Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults
let install_path = env::var("BINARYNINJADIR")
.map(PathBuf::from)
.unwrap_or_else(|_| link_path());
#[cfg(target_os = "linux")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "macos")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=binaryninjacore");
println!("cargo:rustc-link-search={}", install_path.to_str().unwrap());
}
}

View File

@@ -0,0 +1,37 @@
use binaryninja::architecture::Architecture;
use binaryninja::binaryview::{BinaryViewBase, BinaryViewExt};
fn main() {
println!("Loading plugins..."); // This loads all the core architecture, platform, etc plugins
let headless_session = binaryninja::headless::Session::new();
println!("Loading binary...");
let bv = headless_session
.load("/bin/cat")
.expect("Couldn't open `/bin/cat`");
println!("Filename: `{}`", bv.file().filename());
println!("File size: `{:#x}`", bv.len());
println!("Function count: {}", bv.functions().len());
for func in &bv.functions() {
println!(" `{}`:", func.symbol().full_name());
for basic_block in &func.basic_blocks() {
// TODO : This is intended to be refactored to be more nice to work with soon(TM)
for addr in basic_block.as_ref() {
print!(" {} ", addr);
if let Some((_, tokens)) = func.arch().instruction_text(
bv.read_buffer(addr, func.arch().max_instr_len())
.unwrap()
.get_data(),
addr,
) {
tokens
.iter()
.for_each(|token| print!("{}", token.text().as_str()));
println!();
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
[package]
name = "decompile"
version = "0.1.0"
authors = ["Fabian Freyer <mail@fabianfreyer.de>"]
edition = "2021"
[dependencies]
binaryninja = {path="../../"}
clap = { version = "4.3", features = ["derive"] }

View File

@@ -0,0 +1,68 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun");
#[cfg(target_os = "linux")]
static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun");
#[cfg(windows)]
static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun");
// Check last run location for path to BinaryNinja; Otherwise check the default install locations
fn link_path() -> PathBuf {
use std::io::prelude::*;
let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap());
let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1);
File::open(lastrun)
.and_then(|f| {
let mut binja_path = String::new();
let mut reader = BufReader::new(f);
reader.read_line(&mut binja_path)?;
Ok(PathBuf::from(binja_path.trim()))
})
.unwrap_or_else(|_| {
#[cfg(target_os = "macos")]
return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS");
#[cfg(target_os = "linux")]
return home.join("binaryninja");
#[cfg(windows)]
return PathBuf::from(env::var("PROGRAMFILES").unwrap())
.join("Vector35\\BinaryNinja\\");
})
}
fn main() {
// Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults
let install_path = env::var("BINARYNINJADIR")
.map(PathBuf::from)
.unwrap_or_else(|_| link_path());
#[cfg(target_os = "linux")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "macos")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=binaryninjacore");
println!("cargo:rustc-link-search={}", install_path.to_str().unwrap());
}
}

View File

@@ -0,0 +1,54 @@
use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt};
use binaryninja::disassembly::{DisassemblyOption, DisassemblySettings};
use binaryninja::function::Function;
use binaryninja::linearview::{LinearViewCursor, LinearViewObject};
use clap::Parser;
/// Use binaryninja to decompile to C.
#[derive(Parser, Debug)]
#[clap(version, long_about = None)]
struct Args {
/// Path to the file to decompile
filename: String,
}
fn decompile_to_c(view: &BinaryView, func: &Function) {
let settings = DisassemblySettings::new();
settings.set_option(DisassemblyOption::ShowAddress, false);
settings.set_option(DisassemblyOption::WaitForIL, true);
let linearview = LinearViewObject::language_representation(view, &settings);
let mut cursor = LinearViewCursor::new(&linearview);
cursor.seek_to_address(func.highest_address());
let last = view.get_next_linear_disassembly_lines(&mut cursor.duplicate());
let first = view.get_previous_linear_disassembly_lines(&mut cursor);
let lines = first.into_iter().chain(last.into_iter());
for line in lines {
println!("{}", line.as_ref());
}
}
fn main() {
let args = Args::parse();
eprintln!("Loading plugins...");
binaryninja::headless::init();
eprintln!("Loading binary...");
let bv = binaryninja::load(args.filename).expect("Couldn't open file");
eprintln!("Filename: `{}`", bv.file().filename());
eprintln!("File size: `{:#x}`", bv.len());
eprintln!("Function count: {}", bv.functions().len());
for func in &bv.functions() {
decompile_to_c(bv.as_ref(), func.as_ref());
}
binaryninja::headless::shutdown();
}

View File

@@ -0,0 +1,93 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
project(dwarf_export)
file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS
${PROJECT_SOURCE_DIR}/Cargo.toml
${PROJECT_SOURCE_DIR}/src/*.rs
${PROJECT_SOURCE_DIR}/../shared/Cargo.toml
${PROJECT_SOURCE_DIR}/../shared/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}dwarf_export.pdb)
endif()
set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}dwarf_export${CMAKE_SHARED_LIBRARY_SUFFIX})
set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE})
add_custom_target(dwarf_export ALL DEPENDS ${PLUGIN_PATH})
add_dependencies(dwarf_export 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()

View File

@@ -0,0 +1,13 @@
[package]
name = "dwarf_export"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
binaryninja = {path="../../../"}
gimli = "^0.27"
log = "^0.4"
object = { version = "0.30.3", features = ["write"] }

View File

@@ -0,0 +1 @@
# DWARF Export

View File

@@ -0,0 +1,68 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun");
#[cfg(target_os = "linux")]
static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun");
#[cfg(windows)]
static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun");
// Check last run location for path to BinaryNinja; Otherwise check the default install locations
fn link_path() -> PathBuf {
use std::io::prelude::*;
let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap());
let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1);
File::open(lastrun)
.and_then(|f| {
let mut binja_path = String::new();
let mut reader = BufReader::new(f);
reader.read_line(&mut binja_path)?;
Ok(PathBuf::from(binja_path.trim()))
})
.unwrap_or_else(|_| {
#[cfg(target_os = "macos")]
return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS");
#[cfg(target_os = "linux")]
return home.join("binaryninja");
#[cfg(windows)]
return PathBuf::from(env::var("PROGRAMFILES").unwrap())
.join("Vector35\\BinaryNinja\\");
})
}
fn main() {
// Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults
let install_path = env::var("BINARYNINJADIR")
.map(PathBuf::from)
.unwrap_or_else(|_| link_path());
#[cfg(target_os = "linux")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "macos")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=binaryninjacore");
println!("cargo:rustc-link-search={}", install_path.to_str().unwrap());
}
}

View File

@@ -0,0 +1,44 @@
pub(crate) fn distance(a: &str, b: &str) -> usize {
if a == b {
return 0;
}
match (a.chars().count(), b.chars().count()) {
(0, b) => return b,
(a, 0) => return a,
// (a_len, b_len) if a_len < b_len => return distance(b, a),
_ => (),
}
let mut result = 0;
let mut cache: Vec<usize> = (1..a.chars().count() + 1).collect();
for (index_b, char_b) in b.chars().enumerate() {
result = index_b;
let mut distance_a = index_b;
for (index_a, char_a) in a.chars().enumerate() {
let distance_b = if char_a == char_b {
distance_a
} else {
distance_a + 1
};
distance_a = cache[index_a];
result = if distance_a > result {
if distance_b > result {
result + 1
} else {
distance_b
}
} else if distance_b > distance_a {
distance_a + 1
} else {
distance_b
};
cache[index_a] = result;
}
}
result
}

View File

@@ -0,0 +1,795 @@
mod edit_distance;
use gimli::{
constants,
write::{
Address, AttributeValue, DwarfUnit, EndianVec, Expression, Range, RangeList, Sections,
UnitEntryId,
},
};
use object::{write, Architecture, BinaryFormat, SectionKind};
use std::fs;
use binaryninja::{
binaryview::{BinaryView, BinaryViewBase, BinaryViewExt},
command::{register, Command},
interaction,
interaction::{FormResponses, FormResponses::Index},
logger::init,
rc::Ref,
string::BnString,
symbol::SymbolType,
types::{Conf, MemberAccess, StructureType, Type, TypeClass},
};
use log::{error, info, LevelFilter};
fn export_type(
name: String,
t: &Type,
bv: &BinaryView,
defined_types: &mut Vec<(Ref<Type>, UnitEntryId)>,
dwarf: &mut DwarfUnit,
) -> Option<UnitEntryId> {
if let Some((_, die)) = defined_types
.iter()
.find(|(defined_type, _)| defined_type.as_ref() == t)
{
return Some(*die);
}
let root = dwarf.unit.root();
match t.type_class() {
TypeClass::VoidTypeClass => {
let void_die_uid = dwarf.unit.add(root, constants::DW_TAG_unspecified_type);
defined_types.push((t.to_owned(), void_die_uid));
dwarf.unit.get_mut(void_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String("void".as_bytes().to_vec()),
);
Some(void_die_uid)
}
TypeClass::BoolTypeClass => {
let bool_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type);
defined_types.push((t.to_owned(), bool_die_uid));
dwarf.unit.get_mut(bool_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(name.as_bytes().to_vec()),
);
dwarf.unit.get_mut(bool_die_uid).set(
gimli::DW_AT_byte_size,
AttributeValue::Data1(t.width() as u8),
);
dwarf.unit.get_mut(bool_die_uid).set(
gimli::DW_AT_encoding,
AttributeValue::Encoding(constants::DW_ATE_float),
);
Some(bool_die_uid)
}
TypeClass::IntegerTypeClass => {
let int_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type);
defined_types.push((t.to_owned(), int_die_uid));
dwarf.unit.get_mut(int_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(name.as_bytes().to_vec()),
);
dwarf.unit.get_mut(int_die_uid).set(
gimli::DW_AT_byte_size,
AttributeValue::Data1(t.width() as u8),
);
dwarf.unit.get_mut(int_die_uid).set(
gimli::DW_AT_encoding,
if t.is_signed().contents {
AttributeValue::Encoding(constants::DW_ATE_signed)
} else {
AttributeValue::Encoding(constants::DW_ATE_unsigned)
},
);
Some(int_die_uid)
}
TypeClass::FloatTypeClass => {
let float_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type);
defined_types.push((t.to_owned(), float_die_uid));
dwarf.unit.get_mut(float_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(name.as_bytes().to_vec()),
);
dwarf.unit.get_mut(float_die_uid).set(
gimli::DW_AT_byte_size,
AttributeValue::Data1(t.width() as u8),
);
dwarf.unit.get_mut(float_die_uid).set(
gimli::DW_AT_encoding,
AttributeValue::Encoding(constants::DW_ATE_float),
);
Some(float_die_uid)
}
TypeClass::StructureTypeClass => {
let structure_die_uid = match t.get_structure().unwrap().structure_type() {
StructureType::ClassStructureType => {
dwarf.unit.add(root, constants::DW_TAG_class_type)
}
StructureType::StructStructureType => {
dwarf.unit.add(root, constants::DW_TAG_structure_type)
}
StructureType::UnionStructureType => {
dwarf.unit.add(root, constants::DW_TAG_union_type)
}
};
defined_types.push((t.to_owned(), structure_die_uid));
dwarf.unit.get_mut(structure_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(name.as_bytes().to_vec()),
);
dwarf.unit.get_mut(structure_die_uid).set(
gimli::DW_AT_byte_size,
AttributeValue::Data2(t.width() as u16),
);
for struct_member in t.get_structure().unwrap().members().unwrap() {
let struct_member_die_uid =
dwarf.unit.add(structure_die_uid, constants::DW_TAG_member);
dwarf.unit.get_mut(struct_member_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(struct_member.name.as_bytes().to_vec()),
);
match struct_member.access {
MemberAccess::PrivateAccess => {
dwarf.unit.get_mut(struct_member_die_uid).set(
gimli::DW_AT_accessibility,
AttributeValue::Accessibility(gimli::DW_ACCESS_private),
);
}
MemberAccess::ProtectedAccess => {
dwarf.unit.get_mut(struct_member_die_uid).set(
gimli::DW_AT_accessibility,
AttributeValue::Accessibility(gimli::DW_ACCESS_protected),
);
}
MemberAccess::PublicAccess => {
dwarf.unit.get_mut(struct_member_die_uid).set(
gimli::DW_AT_accessibility,
AttributeValue::Accessibility(gimli::DW_ACCESS_public),
);
}
_ => (),
};
dwarf.unit.get_mut(struct_member_die_uid).set(
gimli::DW_AT_data_member_location,
AttributeValue::Data8(struct_member.offset),
);
if let Some(target_die_uid) = export_type(
format!("{}", struct_member.ty.contents),
struct_member.ty.contents.as_ref(),
bv,
defined_types,
dwarf,
) {
dwarf
.unit
.get_mut(struct_member_die_uid)
.set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid));
}
}
Some(structure_die_uid)
}
TypeClass::EnumerationTypeClass => {
let enum_die_uid = dwarf.unit.add(root, constants::DW_TAG_enumeration_type);
defined_types.push((t.to_owned(), enum_die_uid));
dwarf.unit.get_mut(enum_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(name.as_bytes().to_vec()),
);
dwarf.unit.get_mut(enum_die_uid).set(
gimli::DW_AT_byte_size,
AttributeValue::Data1(t.width() as u8),
);
for enum_field in t.get_enumeration().unwrap().members() {
let enum_field_die_uid = dwarf.unit.add(enum_die_uid, constants::DW_TAG_enumerator);
dwarf.unit.get_mut(enum_field_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(enum_field.name.as_bytes().to_vec()),
);
dwarf.unit.get_mut(enum_field_die_uid).set(
gimli::DW_AT_const_value,
AttributeValue::Data4(enum_field.value as u32),
);
}
Some(enum_die_uid)
}
TypeClass::PointerTypeClass => {
let pointer_die_uid = dwarf.unit.add(root, constants::DW_TAG_pointer_type);
defined_types.push((t.to_owned(), pointer_die_uid));
dwarf.unit.get_mut(pointer_die_uid).set(
gimli::DW_AT_byte_size,
AttributeValue::Data1(t.width() as u8),
);
if let Ok(Conf {
contents: target_type,
..
}) = t.target()
{
// TODO : Passing through the name here might be wrong
if let Some(target_die_uid) =
export_type(name, &target_type, bv, defined_types, dwarf)
{
dwarf
.unit
.get_mut(pointer_die_uid)
.set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid));
}
}
Some(pointer_die_uid)
}
TypeClass::ArrayTypeClass => {
let array_die_uid = dwarf.unit.add(root, constants::DW_TAG_array_type);
defined_types.push((t.to_owned(), array_die_uid));
// Name
dwarf.unit.get_mut(array_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(name.as_bytes().to_vec()),
);
// Element type
if let Ok(Conf {
contents: element_type,
..
}) = t.element_type()
{
// TODO : Passing through the name here might be wrong
if let Some(target_die_uid) =
export_type(name, &element_type, bv, defined_types, dwarf)
{
dwarf
.unit
.get_mut(array_die_uid)
.set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid));
}
}
// For some reason subrange types have a 'type' field that is just "some type" that'll work to index this array
// We're hardcoding this to a uint64_t. This could be unsound.
let array_accessor_type = export_type(
"uint64_t".to_string(),
&Type::named_int(8, false, "uint64_t"),
bv,
defined_types,
dwarf,
)
.unwrap();
// Array length and multidimensional arrays
let mut current_t = t.to_owned();
while let Ok(Conf {
contents: element_type,
..
}) = current_t.element_type()
{
let array_dimension_die_uid = dwarf
.unit
.add(array_die_uid, constants::DW_TAG_subrange_type);
dwarf.unit.get_mut(array_dimension_die_uid).set(
gimli::DW_AT_type,
AttributeValue::UnitRef(array_accessor_type),
);
dwarf.unit.get_mut(array_dimension_die_uid).set(
gimli::DW_AT_upper_bound,
AttributeValue::Data8((current_t.width() / element_type.width()) - 1),
);
if element_type.type_class() != TypeClass::ArrayTypeClass {
break;
} else {
current_t = element_type;
}
}
Some(array_die_uid)
}
TypeClass::FunctionTypeClass => {
Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type))
}
TypeClass::VarArgsTypeClass => {
Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type))
}
TypeClass::ValueTypeClass => Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type)),
TypeClass::NamedTypeReferenceClass => {
let ntr = t.get_named_type_reference().unwrap();
if let Some(target_type) = ntr.target(bv) {
if target_type.type_class() == TypeClass::StructureTypeClass {
export_type(
ntr.name().to_string(),
&target_type,
bv,
defined_types,
dwarf,
)
} else {
let typedef_die_uid = dwarf.unit.add(root, constants::DW_TAG_typedef);
defined_types.push((t.to_owned(), typedef_die_uid));
dwarf.unit.get_mut(typedef_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(ntr.name().to_string().as_bytes().to_vec()),
);
if let Some(target_die_uid) = export_type(
ntr.name().to_string(),
&target_type,
bv,
defined_types,
dwarf,
) {
dwarf
.unit
.get_mut(typedef_die_uid)
.set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid));
}
Some(typedef_die_uid)
}
} else {
error!("Could not get target of typedef `{}`", ntr.name());
None
}
}
TypeClass::WideCharTypeClass => {
let wide_char_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type);
defined_types.push((t.to_owned(), wide_char_die_uid));
dwarf.unit.get_mut(wide_char_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(name.as_bytes().to_vec()),
);
dwarf.unit.get_mut(wide_char_die_uid).set(
gimli::DW_AT_byte_size,
AttributeValue::Data1(t.width() as u8),
);
dwarf.unit.get_mut(wide_char_die_uid).set(
gimli::DW_AT_encoding,
if t.is_signed().contents {
AttributeValue::Encoding(constants::DW_ATE_signed_char)
} else {
AttributeValue::Encoding(constants::DW_ATE_unsigned_char)
},
);
Some(wide_char_die_uid)
}
}
}
fn export_types(
bv: &BinaryView,
dwarf: &mut DwarfUnit,
defined_types: &mut Vec<(Ref<Type>, UnitEntryId)>,
) {
for t in &bv.types() {
export_type(
t.name().to_string(),
&t.type_object(),
bv,
defined_types,
dwarf,
);
}
}
fn export_functions(
bv: &BinaryView,
dwarf: &mut DwarfUnit,
defined_types: &mut Vec<(Ref<Type>, UnitEntryId)>,
) {
let entry_point = bv.entry_point_function();
for function in &bv.functions() {
// Create function DIE as child of the compilation unit DIE
let root = dwarf.unit.root();
let function_die_uid = dwarf.unit.add(root, constants::DW_TAG_subprogram);
// let function_die = dwarf.unit.get_mut(function_die_uid);
// Set subprogram DIE attributes
dwarf.unit.get_mut(function_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(function.symbol().short_name().as_bytes().to_vec()),
);
// TODO : (DW_AT_main_subprogram VS DW_TAG_entry_point)
// TODO : This attribute seems maybe usually unused?
if let Ok(entry_point_function) = &entry_point {
if entry_point_function.as_ref() == function.as_ref() {
dwarf
.unit
.get_mut(function_die_uid)
.set(gimli::DW_AT_main_subprogram, AttributeValue::Flag(true));
dwarf.unit.get_mut(function_die_uid).set(
gimli::DW_AT_low_pc,
AttributeValue::Address(Address::Constant(function.start())), // TODO: Relocations
);
}
}
let address_ranges = function.address_ranges();
if address_ranges.len() == 1 {
let address_range = address_ranges.get(0);
dwarf.unit.get_mut(function_die_uid).set(
gimli::DW_AT_low_pc,
AttributeValue::Address(Address::Constant(address_range.start())), // TODO: Relocations
);
dwarf.unit.get_mut(function_die_uid).set(
gimli::DW_AT_high_pc,
AttributeValue::Address(Address::Constant(address_range.end())),
);
} else {
let range_list = RangeList(
address_ranges
.into_iter()
.map(|range| Range::StartLength {
begin: Address::Constant(range.start()), // TODO: Relocations?
length: range.end() - range.start(),
})
.collect(),
);
let range_list_id = dwarf.unit.ranges.add(range_list);
dwarf.unit.get_mut(function_die_uid).set(
gimli::DW_AT_ranges,
AttributeValue::RangeListRef(range_list_id),
);
}
// DWARFv4 2.18: " If no DW_AT_entry_pc attribute is present, then the entry address is assumed to be the same as the value of the DW_AT_low_pc attribute"
if address_ranges.get(0).start() != function.start() {
dwarf.unit.get_mut(function_die_uid).set(
gimli::DW_AT_entry_pc,
AttributeValue::Address(Address::Constant(function.start())),
);
}
if function.return_type().contents.type_class() != TypeClass::VoidTypeClass {
if let Some(return_type_die_uid) = export_type(
format!("{}", function.return_type().contents),
function.return_type().contents.as_ref(),
bv,
defined_types,
dwarf,
) {
dwarf.unit.get_mut(function_die_uid).set(
gimli::DW_AT_type,
AttributeValue::UnitRef(return_type_die_uid),
);
}
}
for parameter in function.function_type().parameters().unwrap() {
let param_die_uid = dwarf
.unit
.add(function_die_uid, constants::DW_TAG_formal_parameter);
dwarf.unit.get_mut(param_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(parameter.name.as_bytes().to_vec()),
);
if let Some(target_die_uid) = export_type(
format!("{}", parameter.t.contents),
&parameter.t.contents,
bv,
defined_types,
dwarf,
) {
dwarf
.unit
.get_mut(param_die_uid)
.set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid));
}
}
if function.function_type().has_variable_arguments().contents {
dwarf
.unit
.add(function_die_uid, constants::DW_TAG_unspecified_parameters);
}
if function.symbol().external() {
dwarf
.unit
.get_mut(function_die_uid)
.set(gimli::DW_AT_external, AttributeValue::Flag(true));
}
// TODO : calling convention attr
// TODO : local vars
}
}
fn export_data_vars(
bv: &BinaryView,
dwarf: &mut DwarfUnit,
defined_types: &mut Vec<(Ref<Type>, UnitEntryId)>,
) {
let root = dwarf.unit.root();
for data_variable in &bv.data_variables() {
if let Some(symbol) = data_variable.symbol(bv) {
if symbol.sym_type() == SymbolType::External {
continue;
} else if symbol.sym_type() == SymbolType::Function {
continue;
} else if symbol.sym_type() == SymbolType::ImportedFunction {
continue;
} else if symbol.sym_type() == SymbolType::LibraryFunction {
continue;
}
}
let var_die_uid = dwarf.unit.add(root, constants::DW_TAG_variable);
if let Some(symbol) = data_variable.symbol(bv) {
dwarf.unit.get_mut(var_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(symbol.full_name().as_bytes().to_vec()),
);
if symbol.external() {
dwarf
.unit
.get_mut(var_die_uid)
.set(gimli::DW_AT_external, AttributeValue::Flag(true));
}
} else {
dwarf.unit.get_mut(var_die_uid).set(
gimli::DW_AT_name,
AttributeValue::String(
format!("data_{:x}", data_variable.address)
.as_bytes()
.to_vec(),
),
);
}
let mut variable_location = Expression::new();
variable_location.op_addr(Address::Constant(data_variable.address));
dwarf.unit.get_mut(var_die_uid).set(
gimli::DW_AT_location,
AttributeValue::Exprloc(variable_location),
);
if let Some(target_die_uid) = export_type(
format!("{}", data_variable.t.contents),
data_variable.t.contents.as_ref(),
bv,
defined_types,
dwarf,
) {
dwarf
.unit
.get_mut(var_die_uid)
.set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid));
}
}
}
fn present_form(bv_arch: &str) -> Vec<FormResponses> {
// TODO : Verify inputs (like save location) so that we can fail early
// TODO : Add Language field
// TODO : Choose to export types/functions/etc
let archs = [
"Unknown",
"Aarch64",
"Aarch64_Ilp32",
"Arm",
"Avr",
"Bpf",
"I386",
"X86_64",
"X86_64_X32",
"Hexagon",
"LoongArch64",
"Mips",
"Mips64",
"Msp430",
"PowerPc",
"PowerPc64",
"Riscv32",
"Riscv64",
"S390x",
"Sbf",
"Sparc64",
"Wasm32",
"Xtensa",
];
interaction::FormInputBuilder::new()
.save_file_field(
"Save Location",
Some("Debug Files (*.dwo *.debug);;All Files (*)"),
None,
None,
)
.choice_field(
"Architecture",
&archs,
archs
.iter()
.enumerate()
.min_by(|&(_, arch_name_1), &(_, arch_name_2)| {
edit_distance::distance(bv_arch, arch_name_1)
.cmp(&edit_distance::distance(bv_arch, arch_name_2))
})
.map(|(index, _)| index),
)
// Add actual / better support for formats other than elf?
// .choice_field(
// "Container Format",
// &["Coff", "Elf", "MachO", "Pe", "Wasm", "Xcoff"],
// None,
// )
.get_form_input("Export as DWARF")
}
fn write_dwarf<T: gimli::Endianity>(
responses: Vec<FormResponses>,
endian: T,
dwarf: &mut DwarfUnit,
) {
if responses.len() < 2 {
return;
}
let arch = match responses[1] {
Index(0) => Architecture::Unknown,
Index(1) => Architecture::Aarch64,
Index(2) => Architecture::Aarch64_Ilp32,
Index(3) => Architecture::Arm,
Index(4) => Architecture::Avr,
Index(5) => Architecture::Bpf,
Index(6) => Architecture::I386,
Index(7) => Architecture::X86_64,
Index(8) => Architecture::X86_64_X32,
Index(9) => Architecture::Hexagon,
Index(10) => Architecture::LoongArch64,
Index(11) => Architecture::Mips,
Index(12) => Architecture::Mips64,
Index(13) => Architecture::Msp430,
Index(14) => Architecture::PowerPc,
Index(15) => Architecture::PowerPc64,
Index(16) => Architecture::Riscv32,
Index(17) => Architecture::Riscv64,
Index(18) => Architecture::S390x,
Index(19) => Architecture::Sbf,
Index(20) => Architecture::Sparc64,
Index(21) => Architecture::Wasm32,
Index(22) => Architecture::Xtensa,
_ => Architecture::Unknown,
};
// let format = match responses[2] {
// Index(0) => BinaryFormat::Coff,
// Index(1) => BinaryFormat::Elf,
// Index(2) => BinaryFormat::MachO,
// Index(3) => BinaryFormat::Pe,
// Index(4) => BinaryFormat::Wasm,
// Index(5) => BinaryFormat::Xcoff,
// _ => BinaryFormat::Elf,
// };
// TODO : Look in to other options (mangling, flags, etc (see Object::new))
let mut out_object = write::Object::new(
BinaryFormat::Elf,
arch,
if endian.is_little_endian() {
object::Endianness::Little
} else {
object::Endianness::Big
},
);
// Finally, write the DWARF data to the sections.
let mut sections = Sections::new(EndianVec::new(endian));
dwarf.write(&mut sections).unwrap();
sections
.for_each(|input_id, input_data| {
// Create section in output object
let output_id = out_object.add_section(
vec![], // Only machos have segment names? see object::write::Object::segment_name
input_id.name().as_bytes().to_vec(),
SectionKind::Debug, // TODO: Might be wrong
);
// Write data to section in output object
let out_section = out_object.section_mut(output_id);
if out_section.is_bss() {
panic!("Please report this as a bug: output section is bss");
} else {
out_section.set_data(input_data.clone().into_vec(), 1);
}
// out_section.flags = in_section.flags(); // TODO
Ok::<(), ()>(())
})
.unwrap();
if let interaction::FormResponses::String(filename) = &responses[0] {
if let Ok(out_data) = out_object.write() {
if let Err(err) = fs::write(filename, out_data) {
error!("Failed to write DWARF file: {}", err);
} else {
info!("Successfully saved as DWARF to `{}`", filename);
}
} else {
error!("Failed to write DWARF with requested settings");
}
}
}
fn export_dwarf(bv: &BinaryView) {
let arch_name = if let Some(arch) = bv.default_arch() {
arch.name()
} else {
BnString::new("Unknown")
};
let responses = present_form(&arch_name);
let encoding = gimli::Encoding {
format: gimli::Format::Dwarf32,
version: 4,
address_size: bv.address_size() as u8,
};
// Create a container for a single compilation unit.
// TODO : Add attributes to the compilation unit DIE?
let mut dwarf = DwarfUnit::new(encoding);
dwarf.unit.get_mut(dwarf.unit.root()).set(
gimli::DW_AT_producer,
AttributeValue::String("Binary Ninja DWARF Export Plugin".as_bytes().to_vec()),
);
// Everything has types, so we need to track what is already defined globally as to not duplicate type entries
let mut defined_types: Vec<(Ref<Type>, UnitEntryId)> = vec![];
export_types(bv, &mut dwarf, &mut defined_types);
export_functions(bv, &mut dwarf, &mut defined_types);
export_data_vars(bv, &mut dwarf, &mut defined_types);
// TODO: Export all symbols instead of just data vars?
// TODO: Sections? Segments?
if bv.default_endianness() == binaryninja::Endianness::LittleEndian {
write_dwarf(responses, gimli::LittleEndian, &mut dwarf);
} else {
write_dwarf(responses, gimli::BigEndian, &mut dwarf);
};
}
struct MyCommand;
impl Command for MyCommand {
fn action(&self, view: &BinaryView) {
export_dwarf(view)
}
fn valid(&self, _view: &BinaryView) -> bool {
true
}
}
#[no_mangle]
pub extern "C" fn CorePluginInit() -> bool {
init(LevelFilter::Debug).expect("Unable to initialize logger");
register(
"Export as DWARF",
"Export current analysis state and annotations as DWARF for import into other tools",
MyCommand {},
);
true
}

View File

@@ -0,0 +1,93 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
project(dwarf_import)
file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS
${PROJECT_SOURCE_DIR}/Cargo.toml
${PROJECT_SOURCE_DIR}/src/*.rs
${PROJECT_SOURCE_DIR}/../shared/Cargo.toml
${PROJECT_SOURCE_DIR}/../shared/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}dwarf_import.pdb)
endif()
set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}dwarf_import${CMAKE_SHARED_LIBRARY_SUFFIX})
set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE})
add_custom_target(dwarf_import ALL DEPENDS ${PLUGIN_PATH})
add_dependencies(dwarf_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()

View File

@@ -0,0 +1,14 @@
[package]
name = "dwarf_import"
version = "0.1.0"
authors = ["KyleMiles <kyle@vector35.com>"]
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
dwarfreader = { path = "../shared/" }
binaryninja = { path = "../../../" }
gimli = "0.27"
log = "0.4.17"

View File

@@ -0,0 +1,390 @@
// Copyright 2021-2024 Vector 35 Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID};
use crate::helpers::*;
use crate::types::get_type;
use binaryninja::{
rc::*,
types::{EnumerationBuilder, FunctionParameter, ReferenceType, Type, TypeBuilder},
};
use gimli::{constants, AttributeValue::Encoding, DebuggingInformationEntry, Reader, Unit};
pub(crate) fn handle_base_type<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
) -> Option<Ref<Type>> {
// All base types have:
// DW_AT_encoding (our concept of type_class)
// DW_AT_byte_size and/or DW_AT_bit_size
// *DW_AT_name
// *DW_AT_endianity (assumed default for arch)
// *DW_AT_data_bit_offset (assumed 0)
// *Some indication of signedness?
// * = Optional
let name = debug_info_builder_context.get_name(unit, entry)?;
let size = get_size_as_usize(entry)?;
match entry.attr_value(constants::DW_AT_encoding) {
Ok(Some(Encoding(encoding))) => {
match encoding {
constants::DW_ATE_address => None,
constants::DW_ATE_boolean => Some(Type::bool()),
constants::DW_ATE_complex_float => None,
constants::DW_ATE_float => Some(Type::named_float(size, name)),
constants::DW_ATE_signed => Some(Type::named_int(size, true, name)),
constants::DW_ATE_signed_char => Some(Type::named_int(size, true, name)),
constants::DW_ATE_unsigned => Some(Type::named_int(size, false, name)),
constants::DW_ATE_unsigned_char => Some(Type::named_int(size, false, name)),
constants::DW_ATE_imaginary_float => None,
constants::DW_ATE_packed_decimal => None,
constants::DW_ATE_numeric_string => None,
constants::DW_ATE_edited => None,
constants::DW_ATE_signed_fixed => None,
constants::DW_ATE_unsigned_fixed => None,
constants::DW_ATE_decimal_float => Some(Type::named_float(size, name)),
constants::DW_ATE_UTF => Some(Type::named_int(size, false, name)), // TODO : Verify
constants::DW_ATE_UCS => None,
constants::DW_ATE_ASCII => None, // Some sort of array?
constants::DW_ATE_lo_user => None,
constants::DW_ATE_hi_user => None,
_ => None, // Anything else is invalid at time of writing (gimli v0.23.0)
}
}
_ => None,
}
}
pub(crate) fn handle_enum<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
) -> Option<Ref<Type>> {
// All base types have:
// DW_AT_byte_size
// *DW_AT_name
// *DW_AT_enum_class
// *DW_AT_type
// ?DW_AT_abstract_origin
// ?DW_AT_accessibility
// ?DW_AT_allocated
// ?DW_AT_associated
// ?DW_AT_bit_size
// ?DW_AT_bit_stride
// ?DW_AT_byte_stride
// ?DW_AT_data_location
// ?DW_AT_declaration
// ?DW_AT_description
// ?DW_AT_sibling
// ?DW_AT_signature
// ?DW_AT_specification
// ?DW_AT_start_scope
// ?DW_AT_visibility
// * = Optional
// Children of enumeration_types are enumerators which contain:
// DW_AT_name
// DW_AT_const_value
// *DW_AT_description
let enumeration_builder = EnumerationBuilder::new();
let mut tree = unit.entries_tree(Some(entry.offset())).unwrap();
let mut children = tree.root().unwrap().children();
while let Ok(Some(child)) = children.next() {
if child.entry().tag() == constants::DW_TAG_enumerator {
let name = debug_info_builder_context.get_name(unit, child.entry())?;
let value = get_attr_as_u64(
&child
.entry()
.attr(constants::DW_AT_const_value)
.unwrap()
.unwrap(),
)
.unwrap();
enumeration_builder.insert(name, value);
}
}
Some(Type::enumeration(
&enumeration_builder.finalize(),
get_size_as_usize(entry).unwrap_or(8),
false,
))
}
pub(crate) fn handle_typedef(
debug_info_builder: &mut DebugInfoBuilder,
entry_type: Option<TypeUID>,
typedef_name: String,
) -> (Option<Ref<Type>>, bool) {
// All base types have:
// DW_AT_name
// *DW_AT_type
// * = Optional
// This will fail in the case where we have a typedef to a type that doesn't exist (failed to parse, incomplete, etc)
if let Some(entry_type_offset) = entry_type {
if let Some((name, t)) = debug_info_builder.get_type(entry_type_offset) {
if typedef_name == name {
return (Some(t), false);
} else if typedef_name != name {
return (Some(t), true);
}
}
}
// 5.3: "typedef represents a declaration of the type that is not also a definition"
(None, false)
}
pub(crate) fn handle_pointer<R: Reader<Offset = usize>>(
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
debug_info_builder: &mut DebugInfoBuilder,
entry_type: Option<TypeUID>,
reference_type: ReferenceType,
) -> Option<Ref<Type>> {
// All pointer types have:
// DW_AT_type
// *DW_AT_byte_size
// ?DW_AT_name
// ?DW_AT_address
// ?DW_AT_allocated
// ?DW_AT_associated
// ?DW_AT_data_location
// * = Optional
if let Some(pointer_size) = get_size_as_usize(entry) {
if let Some(entry_type_offset) = entry_type {
let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1;
Some(Type::pointer_of_width(
parent_type.as_ref(),
pointer_size,
false,
false,
Some(reference_type),
))
} else {
Some(Type::pointer_of_width(
Type::void().as_ref(),
pointer_size,
false,
false,
Some(reference_type),
))
}
} else if let Some(entry_type_offset) = entry_type {
let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1;
Some(Type::pointer_of_width(
parent_type.as_ref(),
debug_info_builder_context.default_address_size(),
false,
false,
Some(reference_type),
))
} else {
Some(Type::pointer_of_width(
Type::void().as_ref(),
debug_info_builder_context.default_address_size(),
false,
false,
Some(reference_type),
))
}
}
pub(crate) fn handle_array<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder: &mut DebugInfoBuilder,
entry_type: Option<TypeUID>,
) -> Option<Ref<Type>> {
// All array types have:
// DW_AT_type
// *DW_AT_name
// *DW_AT_ordering
// *DW_AT_byte_stride or DW_AT_bit_stride
// *DW_AT_byte_size or DW_AT_bit_size
// *DW_AT_allocated
// *DW_AT_associated and
// *DW_AT_data_location
// * = Optional
// For multidimensional arrays, DW_TAG_subrange_type or DW_TAG_enumeration_type
if let Some(entry_type_offset) = entry_type {
let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1;
let mut tree = unit.entries_tree(Some(entry.offset())).unwrap();
let mut children = tree.root().unwrap().children();
// TODO : This is currently applying the size in reverse order
let mut result_type: Option<Ref<Type>> = None;
while let Ok(Some(child)) = children.next() {
if let Some(inner_type) = result_type {
result_type = Some(Type::array(
inner_type.as_ref(),
get_subrange_size(child.entry()),
));
} else {
result_type = Some(Type::array(
parent_type.as_ref(),
get_subrange_size(child.entry()),
));
}
}
result_type.map_or(Some(Type::array(parent_type.as_ref(), 0)), Some)
} else {
None
}
}
pub(crate) fn handle_function<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
debug_info_builder: &mut DebugInfoBuilder,
entry_type: Option<TypeUID>,
) -> Option<Ref<Type>> {
// All subroutine types have:
// *DW_AT_name
// *DW_AT_type (if not provided, void)
// *DW_AT_prototyped
// ?DW_AT_abstract_origin
// ?DW_AT_accessibility
// ?DW_AT_address_class
// ?DW_AT_allocated
// ?DW_AT_associated
// ?DW_AT_data_location
// ?DW_AT_declaration
// ?DW_AT_description
// ?DW_AT_sibling
// ?DW_AT_start_scope
// ?DW_AT_visibility
// * = Optional
// May have children, including DW_TAG_formal_parameters, which all have:
// *DW_AT_type
// * = Optional
// or is otherwise DW_TAG_unspecified_parameters
let return_type = match entry_type {
Some(entry_type_offset) => {
debug_info_builder
.get_type(entry_type_offset)
.expect("Subroutine return type was not processed")
.1
}
None => Type::void(),
};
// Alias function type in the case that it contains itself
if let Some(name) = debug_info_builder_context.get_name(unit, entry) {
debug_info_builder.add_type(
get_uid(unit, entry),
name.clone(),
Type::named_type_from_type(
name,
&Type::function::<String, &binaryninja::types::Type>(
return_type.as_ref(),
&[],
false,
),
),
false,
);
}
let mut parameters: Vec<FunctionParameter<String>> = vec![];
let mut variable_arguments = false;
// Get all the children and populate
let mut tree = unit.entries_tree(Some(entry.offset())).unwrap();
let mut children = tree.root().unwrap().children();
while let Ok(Some(child)) = children.next() {
if child.entry().tag() == constants::DW_TAG_formal_parameter {
if let (Some(child_uid), Some(name)) = {
(
get_type(
unit,
child.entry(),
debug_info_builder_context,
debug_info_builder,
),
debug_info_builder_context.get_name(unit, child.entry()),
)
} {
let child_type = debug_info_builder.get_type(child_uid).unwrap().1;
parameters.push(FunctionParameter::new(child_type, name, None));
}
} else if child.entry().tag() == constants::DW_TAG_unspecified_parameters {
variable_arguments = true;
}
}
if debug_info_builder_context.get_name(unit, entry).is_some() {
debug_info_builder.remove_type(get_uid(unit, entry));
}
Some(Type::function(
return_type.as_ref(),
&parameters,
variable_arguments,
))
}
pub(crate) fn handle_const(
debug_info_builder: &mut DebugInfoBuilder,
entry_type: Option<TypeUID>,
) -> Option<Ref<Type>> {
// All const types have:
// ?DW_AT_allocated
// ?DW_AT_associated
// ?DW_AT_data_location
// ?DW_AT_name
// ?DW_AT_sibling
// ?DW_AT_type
if let Some(entry_type_offset) = entry_type {
let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1;
Some((*parent_type).to_builder().set_const(true).finalize())
} else {
Some(TypeBuilder::void().set_const(true).finalize())
}
}
pub(crate) fn handle_volatile(
debug_info_builder: &mut DebugInfoBuilder,
entry_type: Option<TypeUID>,
) -> Option<Ref<Type>> {
// All const types have:
// ?DW_AT_allocated
// ?DW_AT_associated
// ?DW_AT_data_location
// ?DW_AT_name
// ?DW_AT_sibling
// ?DW_AT_type
if let Some(entry_type_offset) = entry_type {
let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1;
Some((*parent_type).to_builder().set_volatile(true).finalize())
} else {
Some(TypeBuilder::void().set_volatile(true).finalize())
}
}

View File

@@ -0,0 +1,407 @@
// Copyright 2021-2024 Vector 35 Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::helpers::{get_uid, resolve_specification, DieReference};
use binaryninja::{
binaryview::{BinaryView, BinaryViewBase, BinaryViewExt},
debuginfo::{DebugFunctionInfo, DebugInfo},
platform::Platform,
rc::*,
symbol::SymbolType,
templatesimplifier::simplify_str_to_fqn,
types::{Conf, FunctionParameter, Type},
};
use gimli::{DebuggingInformationEntry, Dwarf, Reader, Unit};
use log::{error, warn};
use std::{
collections::{hash_map::Values, HashMap},
hash::Hash,
};
pub(crate) type TypeUID = usize;
/////////////////////////
// FunctionInfoBuilder
// TODO : Function local variables
#[derive(PartialEq, Eq, Hash)]
pub(crate) struct FunctionInfoBuilder {
pub(crate) full_name: Option<String>,
pub(crate) raw_name: Option<String>,
pub(crate) return_type: Option<TypeUID>,
pub(crate) address: Option<u64>,
pub(crate) parameters: Vec<Option<(String, TypeUID)>>,
pub(crate) platform: Option<Ref<Platform>>,
}
impl FunctionInfoBuilder {
pub(crate) fn update(
&mut self,
full_name: Option<String>,
raw_name: Option<String>,
return_type: Option<TypeUID>,
address: Option<u64>,
parameters: Vec<Option<(String, TypeUID)>>,
) {
if full_name.is_some() {
self.full_name = full_name;
}
if raw_name.is_some() {
self.raw_name = raw_name;
}
if return_type.is_some() {
self.return_type = return_type;
}
if address.is_some() {
self.address = address;
}
for (i, new_parameter) in parameters.into_iter().enumerate() {
match self.parameters.get(i) {
Some(None) => self.parameters[i] = new_parameter,
Some(Some(_)) => (),
// Some(Some((name, _))) if name.as_bytes().is_empty() => {
// self.parameters[i] = new_parameter
// }
// Some(Some((_, uid))) if *uid == 0 => self.parameters[i] = new_parameter, // TODO : This is a placebo....void types aren't actually UID 0
_ => self.parameters.push(new_parameter),
}
}
}
}
//////////////////////
// DebugInfoBuilder
// TODO : Don't make this pub...fix the value thing
pub(crate) struct DebugType {
name: String,
t: Ref<Type>,
commit: bool,
}
pub(crate) struct DebugInfoBuilderContext<R: Reader<Offset = usize>> {
dwarf: Dwarf<R>,
units: Vec<Unit<R>>,
names: HashMap<TypeUID, String>,
default_address_size: usize,
pub(crate) total_die_count: usize,
}
impl<R: Reader<Offset = usize>> DebugInfoBuilderContext<R> {
pub(crate) fn new(view: &BinaryView, dwarf: Dwarf<R>) -> Option<Self> {
let mut units = vec![];
let mut iter = dwarf.units();
while let Ok(Some(header)) = iter.next() {
if let Ok(unit) = dwarf.unit(header) {
units.push(unit);
} else {
error!("Unable to read DWARF information. File may be malformed or corrupted. Not applying debug info.");
return None;
}
}
Some(Self {
dwarf,
units,
names: HashMap::new(),
default_address_size: view.address_size(),
total_die_count: 0,
})
}
pub(crate) fn dwarf(&self) -> &Dwarf<R> {
&self.dwarf
}
pub(crate) fn units(&self) -> &[Unit<R>] {
&self.units
}
pub(crate) fn default_address_size(&self) -> usize {
self.default_address_size
}
pub(crate) fn set_name(&mut self, die_uid: TypeUID, name: String) {
assert!(self.names.insert(die_uid, name).is_none());
}
pub(crate) fn get_name(
&self,
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
) -> Option<String> {
match resolve_specification(unit, entry, self) {
DieReference::UnitAndOffset((entry_unit, entry_offset)) => self
.names
.get(&get_uid(
entry_unit,
&entry_unit.entry(entry_offset).unwrap(),
))
.cloned(),
DieReference::Err => None,
}
}
}
// DWARF info is stored and displayed in a tree, but is really a graph
// The purpose of this builder is to help resolve those graph edges by mapping partial function
// info and types to one DIE's UID (T) before adding the completed info to BN's debug info
pub(crate) struct DebugInfoBuilder {
functions: Vec<FunctionInfoBuilder>,
types: HashMap<TypeUID, DebugType>,
data_variables: HashMap<u64, (Option<String>, TypeUID)>,
}
impl DebugInfoBuilder {
pub(crate) fn new() -> Self {
Self {
functions: vec![],
types: HashMap::new(),
data_variables: HashMap::new(),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn insert_function(
&mut self,
full_name: Option<String>,
raw_name: Option<String>,
return_type: Option<TypeUID>,
address: Option<u64>,
parameters: Vec<Option<(String, TypeUID)>>,
) {
// Raw names should be the primary key, but if they don't exist, use the full name
// TODO : Consider further falling back on address/architecture
if let Some(function) = self
.functions
.iter_mut()
.find(|func| func.raw_name.is_some() && func.raw_name == raw_name)
{
function.update(full_name, raw_name, return_type, address, parameters);
} else if let Some(function) = self.functions.iter_mut().find(|func| {
(func.raw_name.is_none() || raw_name.is_none())
&& func.full_name.is_some()
&& func.full_name == full_name
}) {
function.update(full_name, raw_name, return_type, address, parameters);
} else {
self.functions.push(FunctionInfoBuilder {
full_name,
raw_name,
return_type,
address,
parameters,
platform: None,
});
}
}
pub(crate) fn functions(&self) -> &[FunctionInfoBuilder] {
&self.functions
}
pub(crate) fn types(&self) -> Values<'_, TypeUID, DebugType> {
self.types.values()
}
pub(crate) fn add_type(
&mut self,
type_uid: TypeUID,
name: String,
t: Ref<Type>,
commit: bool,
) {
if let Some(DebugType {
name: existing_name,
t: existing_type,
commit: _,
}) = self.types.insert(
type_uid,
DebugType {
name: name.clone(),
t: t.clone(),
commit,
},
) {
if existing_type != t && commit {
error!("DWARF info contains duplicate type definition. Overwriting type `{}` (named `{:?}`) with `{}` (named `{:?}`)",
existing_type,
existing_name,
t,
name
);
}
}
}
pub(crate) fn remove_type(&mut self, type_uid: TypeUID) {
self.types.remove(&type_uid);
}
// TODO : Non-copy?
pub(crate) fn get_type(&self, type_uid: TypeUID) -> Option<(String, Ref<Type>)> {
self.types
.get(&type_uid)
.map(|type_ref_ref| (type_ref_ref.name.clone(), type_ref_ref.t.clone()))
}
pub(crate) fn contains_type(&self, type_uid: TypeUID) -> bool {
self.types.get(&type_uid).is_some()
}
pub(crate) fn add_data_variable(
&mut self,
address: u64,
name: Option<String>,
type_uid: TypeUID,
) {
if let Some((_existing_name, existing_type_uid)) =
self.data_variables.insert(address, (name, type_uid))
{
let existing_type = self.get_type(existing_type_uid).unwrap().1;
let new_type = self.get_type(type_uid).unwrap().1;
if existing_type_uid != type_uid || existing_type != new_type {
error!("DWARF info contains duplicate data variable definition. Overwriting data variable at 0x{:08x} (`{}`) with `{}`",
address,
self.get_type(existing_type_uid).unwrap().1,
self.get_type(type_uid).unwrap().1
);
}
}
}
fn commit_types(&self, debug_info: &mut DebugInfo) {
for debug_type in self.types() {
if debug_type.commit {
debug_info.add_type(debug_type.name.clone(), debug_type.t.as_ref(), &[]);
// TODO : Components
}
}
}
// TODO : Consume data?
fn commit_data_variables(&self, debug_info: &mut DebugInfo) {
for (&address, (name, type_uid)) in &self.data_variables {
assert!(debug_info.add_data_variable(
address,
&self.get_type(*type_uid).unwrap().1,
name.clone(),
&[] // TODO : Components
));
}
}
fn get_function_type(&self, function: &FunctionInfoBuilder) -> Ref<Type> {
let return_type = match function.return_type {
Some(return_type_id) => Conf::new(self.get_type(return_type_id).unwrap().1.clone(), 0),
_ => Conf::new(binaryninja::types::Type::void(), 0),
};
let parameters: Vec<FunctionParameter<String>> = function
.parameters
.iter()
.filter_map(|parameter| match parameter {
Some((name, 0)) => Some(FunctionParameter::new(Type::void(), name.clone(), None)),
Some((name, uid)) => Some(FunctionParameter::new(
self.get_type(*uid).unwrap().1,
name.clone(),
None,
)),
_ => None,
})
.collect();
// TODO : Handle
let variable_parameters = false;
binaryninja::types::Type::function(&return_type, &parameters, variable_parameters)
}
fn commit_functions(&self, debug_info: &mut DebugInfo) {
for function in self.functions() {
// let calling_convention: Option<Ref<CallingConvention<CoreArchitecture>>> = None;
debug_info.add_function(DebugFunctionInfo::new(
function.full_name.clone(),
function.full_name.clone(), // TODO : This should eventually be changed, but the "full_name" should probably be the unsimplified version, and the "short_name" should be the simplified version...currently the symbols view shows the full version, so changing it here too makes it look bad in the UI
function.raw_name.clone(),
Some(self.get_function_type(function)),
function.address,
function.platform.clone(),
vec![], // TODO : Components
));
}
}
pub(crate) fn post_process(&mut self, bv: &BinaryView, _debug_info: &mut DebugInfo) -> &Self {
// TODO : We don't need post-processing if we process correctly the first time....
// When originally resolving names, we need to check:
// If there's already a name from binja that's "more correct" than what we found (has more namespaces)
// If there's no name for the DIE, but there's a linkage name that's resolved in binja to a usable name
// This is no longer true, because DWARF doesn't provide platform information for functions, so we at least need to post-process thumb functions
for func in &mut self.functions {
// If the function's raw name already exists in the binary...
if let Some(raw_name) = &func.raw_name {
if let Ok(symbol) = bv.symbol_by_raw_name(raw_name) {
// Link mangled names without addresses to existing symbols in the binary
if func.address.is_none() && func.raw_name.is_some() {
// DWARF doesn't contain GOT info, so remove any entries there...they will be wrong (relying on Binja's mechanisms for the GOT is good )
if symbol.sym_type() != SymbolType::ImportAddress {
func.address = Some(symbol.address());
}
}
if let Some(full_name) = &func.full_name {
let func_full_name = full_name;
let symbol_full_name = symbol.full_name();
// If our name has fewer namespaces than the existing name, assume we lost the namespace info
if simplify_str_to_fqn(func_full_name, true).len()
< simplify_str_to_fqn(symbol_full_name.clone(), true).len()
{
func.full_name =
Some(symbol_full_name.to_string());
}
}
}
}
if let Some(address) = func.address {
let existing_functions = bv.functions_at(address);
if existing_functions.len() > 1 {
warn!("Multiple existing functions at address {address:08x}. One or more functions at this address may have the wrong platform information. Please report this binary.");
} else if existing_functions.len() == 1 {
func.platform = Some(existing_functions.get(0).platform());
}
}
}
self
}
pub(crate) fn commit_info(&self, debug_info: &mut DebugInfo) {
self.commit_types(debug_info);
self.commit_data_variables(debug_info);
self.commit_functions(debug_info);
}
}

View File

@@ -0,0 +1,78 @@
// Copyright 2021-2024 Vector 35 Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID};
use crate::helpers::*;
use crate::types::get_type;
use gimli::{constants, DebuggingInformationEntry, Reader, Unit};
fn get_parameters<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
debug_info_builder: &mut DebugInfoBuilder,
) -> Vec<Option<(String, TypeUID)>> {
if !entry.has_children() {
vec![]
} else {
// We make a new tree from the current entry to iterate over its children
let mut sub_die_tree = unit.entries_tree(Some(entry.offset())).unwrap();
let root = sub_die_tree.root().unwrap();
let mut result = vec![];
let mut children = root.children();
while let Some(child) = children.next().unwrap() {
match child.entry().tag() {
constants::DW_TAG_formal_parameter => {
let name = debug_info_builder_context.get_name(unit, child.entry());
let type_ = get_type(
unit,
child.entry(),
debug_info_builder_context,
debug_info_builder,
);
if let Some(parameter_name) = name {
if let Some(parameter_type) = type_ {
result.push(Some((parameter_name, parameter_type)));
} else {
result.push(Some((parameter_name, 0)))
}
} else {
result.push(None)
}
}
constants::DW_TAG_unspecified_parameters => (),
_ => (),
}
}
result
}
}
pub(crate) fn parse_function_entry<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
debug_info_builder: &mut DebugInfoBuilder,
) {
// Collect function properties (if they exist in this DIE)
let full_name = debug_info_builder_context.get_name(unit, entry);
let raw_name = get_raw_name(unit, entry, debug_info_builder_context);
let return_type = get_type(unit, entry, debug_info_builder_context, debug_info_builder);
let address = get_start_address(unit, entry, debug_info_builder_context);
let parameters = get_parameters(unit, entry, debug_info_builder_context, debug_info_builder);
debug_info_builder.insert_function(full_name, raw_name, return_type, address, parameters);
}

View File

@@ -0,0 +1,293 @@
// Copyright 2021-2024 Vector 35 Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::DebugInfoBuilderContext;
use gimli::{
constants, Attribute, AttributeValue,
AttributeValue::{DebugInfoRef, UnitRef},
DebuggingInformationEntry, Operation, Reader, Unit, UnitOffset, UnitSectionOffset,
};
use log::warn;
pub(crate) fn get_uid<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
) -> usize {
match entry.offset().to_unit_section_offset(unit) {
UnitSectionOffset::DebugInfoOffset(o) => o.0,
UnitSectionOffset::DebugTypesOffset(o) => o.0,
}
}
////////////////////////////////////
// DIE attr convenience functions
pub(crate) enum DieReference<'a, R: Reader<Offset = usize>> {
UnitAndOffset((&'a Unit<R>, UnitOffset)),
Err,
}
pub(crate) fn get_attr_die<'a, R: Reader<Offset = usize>>(
unit: &'a Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &'a DebugInfoBuilderContext<R>,
attr: constants::DwAt,
) -> Option<DieReference<'a, R>> {
match entry.attr_value(attr) {
Ok(Some(UnitRef(offset))) => Some(DieReference::UnitAndOffset((unit, offset))),
Ok(Some(DebugInfoRef(offset))) => {
for source_unit in debug_info_builder_context.units() {
if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) {
return Some(DieReference::UnitAndOffset((source_unit, new_offset)));
}
}
warn!("Failed to fetch DIE. Debug information may be incomplete.");
None
}
// Ok(Some(DebugInfoRefSup(offset))) TODO - dwarf 5 stuff
_ => None,
}
}
pub(crate) fn resolve_specification<'a, R: Reader<Offset = usize>>(
unit: &'a Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &'a DebugInfoBuilderContext<R>,
) -> DieReference<'a, R> {
if let Some(die_reference) = get_attr_die(
unit,
entry,
debug_info_builder_context,
constants::DW_AT_specification,
) {
match die_reference {
DieReference::UnitAndOffset((entry_unit, entry_offset)) => {
if let Ok(entry) = entry_unit.entry(entry_offset) {
resolve_specification(entry_unit, &entry, debug_info_builder_context)
} else {
warn!("Failed to fetch DIE. Debug information may be incomplete.");
DieReference::Err
}
}
DieReference::Err => DieReference::Err,
}
} else if let Some(die_reference) = get_attr_die(
unit,
entry,
debug_info_builder_context,
constants::DW_AT_abstract_origin,
) {
match die_reference {
DieReference::UnitAndOffset((entry_unit, entry_offset)) => {
if entry_offset == entry.offset() {
warn!("DWARF information is invalid (infinite abstract origin reference cycle). Debug information may be incomplete.");
DieReference::Err
} else if let Ok(new_entry) = entry_unit.entry(entry_offset) {
resolve_specification(entry_unit, &new_entry, debug_info_builder_context)
} else {
warn!("Failed to fetch DIE. Debug information may be incomplete.");
DieReference::Err
}
}
DieReference::Err => DieReference::Err,
}
} else {
DieReference::UnitAndOffset((unit, entry.offset()))
}
}
// Get name from DIE, or referenced dependencies
pub(crate) fn get_name<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
) -> Option<String> {
match resolve_specification(unit, entry, debug_info_builder_context) {
DieReference::UnitAndOffset((entry_unit, entry_offset)) => {
if let Ok(Some(attr_val)) = entry_unit
.entry(entry_offset)
.unwrap()
.attr_value(constants::DW_AT_name)
{
if let Ok(attr_string) = debug_info_builder_context
.dwarf()
.attr_string(entry_unit, attr_val)
{
if let Ok(attr_string) = attr_string.to_string() {
return Some(attr_string.to_string());
}
}
}
// if let Some(raw_name) = get_raw_name(unit, entry, debug_info_builder_context) {
// if let Some(arch) = debug_info_builder_context.default_architecture() {
// if let Ok((_, names)) = demangle_gnu3(&arch, raw_name, true) {
// return Some(names.join("::"));
// }
// }
// }
None
}
DieReference::Err => None,
}
}
// Get raw name from DIE, or referenced dependencies
pub(crate) fn get_raw_name<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
) -> Option<String> {
if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_linkage_name) {
if let Ok(attr_string) = debug_info_builder_context
.dwarf()
.attr_string(unit, attr_val)
{
if let Ok(attr_string) = attr_string.to_string() {
return Some(attr_string.to_string());
}
}
}
None
}
// Get the size of an object as a usize
pub(crate) fn get_size_as_usize<R: Reader<Offset = usize>>(
entry: &DebuggingInformationEntry<R>,
) -> Option<usize> {
if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) {
get_attr_as_usize(attr)
} else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_bit_size) {
get_attr_as_usize(attr).map(|attr_value| attr_value / 8)
} else {
None
}
}
// Get the size of an object as a u64
pub(crate) fn get_size_as_u64<R: Reader<Offset = usize>>(
entry: &DebuggingInformationEntry<R>,
) -> Option<u64> {
if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) {
get_attr_as_u64(&attr)
} else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_bit_size) {
get_attr_as_u64(&attr).map(|attr_value| attr_value / 8)
} else {
None
}
}
// Get the size of a subrange as a u64
pub(crate) fn get_subrange_size<R: Reader<Offset = usize>>(
entry: &DebuggingInformationEntry<R>,
) -> u64 {
if let Ok(Some(attr)) = entry.attr(constants::DW_AT_upper_bound) {
get_attr_as_u64(&attr).map_or(0, |v| v + 1)
} else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_count) {
get_attr_as_u64(&attr).unwrap_or(0)
} else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_lower_bound) {
get_attr_as_u64(&attr).map_or(0, |v| v + 1)
} else {
0
}
}
// Get the start address of a function
pub(crate) fn get_start_address<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
) -> Option<u64> {
if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_low_pc) {
match debug_info_builder_context
.dwarf()
.attr_address(unit, attr_val)
{
Ok(Some(val)) => Some(val),
_ => None,
}
} else if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_entry_pc) {
match debug_info_builder_context
.dwarf()
.attr_address(unit, attr_val)
{
Ok(Some(val)) => Some(val),
_ => None,
}
} else if let Ok(Some(attr_value)) = entry.attr_value(constants::DW_AT_ranges) {
if let Ok(Some(ranges_offset)) = debug_info_builder_context
.dwarf()
.attr_ranges_offset(unit, attr_value)
{
if let Ok(mut ranges) = debug_info_builder_context
.dwarf()
.ranges(unit, ranges_offset)
{
if let Ok(Some(range)) = ranges.next() {
return Some(range.begin);
}
}
}
return None;
} else {
None
}
}
// Get an attribute value as a u64 if it can be coerced
pub(crate) fn get_attr_as_u64<R: Reader<Offset = usize>>(attr: &Attribute<R>) -> Option<u64> {
if let Some(value) = attr.u8_value() {
Some(value.into())
} else if let Some(value) = attr.u16_value() {
Some(value.into())
} else if let Some(value) = attr.udata_value() {
Some(value)
} else {
attr.sdata_value().map(|value| value as u64)
}
}
// Get an attribute value as a usize if it can be coerced
pub(crate) fn get_attr_as_usize<R: Reader<Offset = usize>>(attr: Attribute<R>) -> Option<usize> {
if let Some(value) = attr.u8_value() {
Some(value.into())
} else if let Some(value) = attr.u16_value() {
Some(value.into())
} else if let Some(value) = attr.udata_value() {
Some(value as usize)
} else {
attr.sdata_value().map(|value| value as usize)
}
}
// Get an attribute value as a usize if it can be coerced
// Parses DW_OP_address, DW_OP_const
pub(crate) fn get_expr_value<R: Reader<Offset = usize>>(
unit: &Unit<R>,
attr: Attribute<R>,
) -> Option<u64> {
if let AttributeValue::Exprloc(mut expression) = attr.value() {
match Operation::parse(&mut expression.0, unit.encoding()) {
Ok(Operation::PlusConstant { value }) => Some(value),
Ok(Operation::UnsignedConstant { value }) => Some(value),
Ok(Operation::Address { address: 0 }) => None,
Ok(Operation::Address { address }) => Some(address),
_ => None,
}
} else {
None
}
}

View File

@@ -0,0 +1,295 @@
// Copyright 2021-2024 Vector 35 Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod die_handlers;
mod dwarfdebuginfo;
mod functions;
mod helpers;
mod types;
use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext};
use crate::functions::parse_function_entry;
use crate::helpers::{get_attr_die, get_name, get_uid, DieReference};
use crate::types::parse_data_variable;
use binaryninja::{
binaryview::{BinaryView, BinaryViewExt},
debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser},
logger,
templatesimplifier::simplify_str_to_str,
};
use dwarfreader::{
create_section_reader, get_endian, is_dwo_dwarf, is_non_dwo_dwarf, is_raw_dwo_dwarf,
};
use gimli::{constants, DebuggingInformationEntry, Dwarf, DwarfFileType, Reader, SectionId, Unit};
use log::{error, warn, LevelFilter};
fn recover_names<R: Reader<Offset = usize>>(
debug_info_builder_context: &mut DebugInfoBuilderContext<R>,
progress: &dyn Fn(usize, usize) -> Result<(), ()>,
) -> bool {
let mut iter = debug_info_builder_context.dwarf().units();
while let Ok(Some(header)) = iter.next() {
let unit = debug_info_builder_context.dwarf().unit(header).unwrap();
let mut namespace_qualifiers: Vec<(isize, String)> = vec![];
let mut entries = unit.entries();
let mut depth = 0;
// The first entry in the unit is the header for the unit
if let Ok(Some((delta_depth, _))) = entries.next_dfs() {
depth += delta_depth;
debug_info_builder_context.total_die_count += 1;
}
while let Ok(Some((delta_depth, entry))) = entries.next_dfs() {
debug_info_builder_context.total_die_count += 1;
if (*progress)(0, debug_info_builder_context.total_die_count).is_err() {
return false; // Parsing canceled
};
depth += delta_depth;
if depth < 0 {
error!("DWARF information is seriously malformed. Aborting parsing.");
return false;
}
// TODO : Better module/component support
namespace_qualifiers.retain(|&(entry_depth, _)| entry_depth < depth);
match entry.tag() {
constants::DW_TAG_namespace => {
fn resolve_namespace_name<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
namespace_qualifiers: &mut Vec<(isize, String)>,
depth: isize,
) {
if let Some(namespace_qualifier) =
get_name(unit, entry, debug_info_builder_context)
{
namespace_qualifiers.push((depth, namespace_qualifier));
} else if let Some(die_reference) = get_attr_die(
unit,
entry,
debug_info_builder_context,
constants::DW_AT_extension,
) {
match die_reference {
DieReference::UnitAndOffset((entry_unit, entry_offset)) => {
resolve_namespace_name(
entry_unit,
&entry_unit.entry(entry_offset).unwrap(),
debug_info_builder_context,
namespace_qualifiers,
depth,
)
}
DieReference::Err => {
warn!(
"Failed to fetch DIE. Debug information may be incomplete."
);
}
}
} else {
namespace_qualifiers
.push((depth, "anonymous_namespace".to_string()));
}
}
resolve_namespace_name(
&unit,
entry,
debug_info_builder_context,
&mut namespace_qualifiers,
depth,
);
}
constants::DW_TAG_class_type
| constants::DW_TAG_structure_type
| constants::DW_TAG_union_type => {
if let Some(name) = get_name(&unit, entry, debug_info_builder_context) {
namespace_qualifiers.push((depth, name))
} else {
namespace_qualifiers.push((
depth,
match entry.tag() {
constants::DW_TAG_class_type => "anonymous_class".to_string(),
constants::DW_TAG_structure_type => "anonymous_structure".to_string(),
constants::DW_TAG_union_type => "anonymous_union".to_string(),
_ => unreachable!(),
}
))
}
debug_info_builder_context.set_name(
get_uid(&unit, entry),
simplify_str_to_str(
namespace_qualifiers
.iter()
.map(|(_, namespace)| namespace.to_owned())
.collect::<Vec<String>>()
.join("::"),
)
.to_string(),
);
}
constants::DW_TAG_typedef
| constants::DW_TAG_subprogram
| constants::DW_TAG_enumeration_type => {
if let Some(name) = get_name(&unit, entry, debug_info_builder_context) {
debug_info_builder_context.set_name(
get_uid(&unit, entry),
simplify_str_to_str(
namespace_qualifiers
.iter()
.chain(vec![&(-1, name)].into_iter())
.map(|(_, namespace)| {
namespace.to_owned()
})
.collect::<Vec<String>>()
.join("::"),
)
.to_string(),
);
}
}
_ => {
if let Some(name) = get_name(&unit, entry, debug_info_builder_context) {
debug_info_builder_context.set_name(get_uid(&unit, entry), name);
}
}
}
}
}
true
}
fn parse_unit<R: Reader<Offset = usize>>(
unit: &Unit<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
debug_info_builder: &mut DebugInfoBuilder,
progress: &dyn Fn(usize, usize) -> Result<(), ()>,
current_die_number: &mut usize,
) {
let mut entries = unit.entries();
// Really all we care about as we iterate the entries in a given unit is how they modify state (our perception of the file)
// There's a lot of junk we don't care about in DWARF info, so we choose a couple DIEs and mutate state (add functions (which adds the types it uses) and keep track of what namespace we're in)
while let Ok(Some((_, entry))) = entries.next_dfs() {
*current_die_number += 1;
if (*progress)(
*current_die_number,
debug_info_builder_context.total_die_count,
)
.is_err()
{
return; // Parsing canceled
}
match entry.tag() {
constants::DW_TAG_subprogram => {
parse_function_entry(unit, entry, debug_info_builder_context, debug_info_builder)
}
constants::DW_TAG_variable => {
parse_data_variable(unit, entry, debug_info_builder_context, debug_info_builder)
}
_ => (),
}
}
}
fn parse_dwarf(
view: &BinaryView,
progress: Box<dyn Fn(usize, usize) -> 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
} else {
raw_view
};
// gimli setup
let endian = get_endian(view);
let mut section_reader =
|section_id: SectionId| -> _ { create_section_reader(section_id, view, endian, dwo_file) };
let mut dwarf = Dwarf::load(&mut section_reader).unwrap();
if dwo_file {
dwarf.file_type = DwarfFileType::Dwo;
}
// Create debug info builder and recover name mapping first
// Since DWARF is stored as a tree with arbitrary implicit edges among leaves,
// it is not possible to correctly track namespaces while you're parsing "in order" without backtracking,
// so we just do it up front
let mut debug_info_builder = DebugInfoBuilder::new();
if let Some(mut debug_info_builder_context) = DebugInfoBuilderContext::new(view, dwarf) {
if !recover_names(&mut debug_info_builder_context, &progress)
|| debug_info_builder_context.total_die_count == 0
{
return debug_info_builder;
}
// Parse all the compilation units
let mut current_die_number = 0;
for unit in debug_info_builder_context.units() {
parse_unit(
unit,
&debug_info_builder_context,
&mut debug_info_builder,
&progress,
&mut current_die_number,
);
}
}
debug_info_builder
}
struct DWARFParser;
impl CustomDebugInfoParser for DWARFParser {
fn is_valid(&self, view: &BinaryView) -> bool {
dwarfreader::is_valid(view)
}
fn parse_info(
&self,
debug_info: &mut DebugInfo,
bv: &BinaryView,
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
}
}
#[no_mangle]
pub extern "C" fn CorePluginInit() -> bool {
logger::init(LevelFilter::Debug).unwrap();
DebugInfoParser::register("DWARF", DWARFParser {});
true
}

View File

@@ -0,0 +1,393 @@
// Copyright 2021-2024 Vector 35 Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::die_handlers::*;
use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID};
use crate::helpers::*;
use binaryninja::{
rc::*,
types::{
MemberAccess, MemberScope, ReferenceType, StructureBuilder, StructureType, Type, TypeClass,
},
};
use gimli::{constants, DebuggingInformationEntry, Reader, Unit};
use log::warn;
pub(crate) fn parse_data_variable<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
debug_info_builder: &mut DebugInfoBuilder,
) {
let full_name = debug_info_builder_context.get_name(unit, entry);
let type_uid = get_type(unit, entry, debug_info_builder_context, debug_info_builder);
let address = if let Ok(Some(attr)) = entry.attr(constants::DW_AT_location) {
get_expr_value(unit, attr)
} else {
None
};
if let (Some(address), Some(type_uid)) = (address, type_uid) {
debug_info_builder.add_data_variable(address, full_name, type_uid);
}
}
fn do_structure_parse<R: Reader<Offset = usize>>(
structure_type: StructureType,
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
debug_info_builder: &mut DebugInfoBuilder,
) -> Option<usize> {
// All struct, union, and class types will have:
// *DW_AT_name
// *DW_AT_byte_size or *DW_AT_bit_size
// *DW_AT_declaration
// *DW_AT_signature
// *DW_AT_specification
// ?DW_AT_abstract_origin
// ?DW_AT_accessibility
// ?DW_AT_allocated
// ?DW_AT_associated
// ?DW_AT_data_location
// ?DW_AT_description
// ?DW_AT_start_scope
// ?DW_AT_visibility
// * = Optional
// Structure/Class/Union _Children_ consist of:
// Data members:
// DW_AT_type
// *DW_AT_name
// *DW_AT_accessibility (default private for classes, public for everything else)
// *DW_AT_mutable
// *DW_AT_data_member_location xor *DW_AT_data_bit_offset (otherwise assume zero) <- there are some deprecations for DWARF 4
// *DW_AT_byte_size xor DW_AT_bit_size, iff the storage size is different than it usually would be for the given member type
// Function members:
// *DW_AT_accessibility (default private for classes, public for everything else)
// *DW_AT_virtuality (assume false)
// If true: DW_AT_vtable_elem_location
// *DW_AT_explicit (assume false)
// *DW_AT_object_pointer (assume false; for non-static member function; references the formal parameter that has "DW_AT_artificial = true" and represents "self" or "this" (language specified))
// *DW_AT_specification
// * = Optional
if let Ok(Some(_)) = entry.attr(constants::DW_AT_declaration) {
return None;
}
let full_name = if get_name(unit, entry, debug_info_builder_context).is_some() {
debug_info_builder_context.get_name(unit, entry)
} else {
None
};
// Create structure with proper size
let size = get_size_as_u64(entry).unwrap_or(0);
let structure_builder: StructureBuilder = StructureBuilder::new();
structure_builder
.set_packed(true)
.set_width(size)
.set_structure_type(structure_type);
// This reference type will be used by any children to grab while we're still building this type
// it will also be how any other types refer to this struct
if let Some(full_name) = &full_name {
debug_info_builder.add_type(
get_uid(unit, entry),
full_name.clone(),
Type::named_type_from_type(
full_name.clone(),
&Type::structure(&structure_builder.finalize()),
),
false,
);
} else {
// We _need_ to have initial typedefs or else we can enter infinite parsing loops
// These get overwritten in the last step with the actual type, however, so this
// is either perfectly fine or breaking a bunch of NTRs
let full_name = format!("anonymous_structure_{:x}", get_uid(unit, entry));
debug_info_builder.add_type(
get_uid(unit, entry),
full_name.clone(),
Type::named_type_from_type(full_name, &Type::structure(&structure_builder.finalize())),
false,
);
}
// Get all the children and populate
let mut tree = unit.entries_tree(Some(entry.offset())).unwrap();
let mut children = tree.root().unwrap().children();
while let Ok(Some(child)) = children.next() {
if child.entry().tag() == constants::DW_TAG_member {
if let Some(child_type_id) = get_type(
unit,
child.entry(),
debug_info_builder_context,
debug_info_builder,
) {
if let Some((_, child_type)) = debug_info_builder.get_type(child_type_id) {
if let Some(child_name) = debug_info_builder_context
.get_name(unit, child.entry())
.map_or(
if child_type.type_class() == TypeClass::StructureTypeClass {
Some("".to_string())
} else {
None
},
Some,
)
{
// TODO : support DW_AT_data_bit_offset for offset as well
if let Ok(Some(raw_struct_offset)) =
child.entry().attr(constants::DW_AT_data_member_location)
{
// TODO : Let this fail; don't unwrap_or_default get_expr_value
let struct_offset =
get_attr_as_u64(&raw_struct_offset).unwrap_or_else(|| {
get_expr_value(unit, raw_struct_offset).unwrap_or_default()
});
structure_builder.insert(
child_type.as_ref(),
child_name,
struct_offset,
false,
MemberAccess::NoAccess, // TODO : Resolve actual scopes, if possible
MemberScope::NoScope,
);
} else {
structure_builder.append(
child_type.as_ref(),
child_name,
MemberAccess::NoAccess,
MemberScope::NoScope,
);
}
}
}
}
}
}
let finalized_structure = Type::structure(&structure_builder.finalize());
if let Some(full_name) = full_name {
debug_info_builder.add_type(
get_uid(unit, entry) + 1, // TODO : This is super broke (uid + 1 is not guaranteed to be unique)
full_name,
finalized_structure,
true,
);
} else {
debug_info_builder.add_type(
get_uid(unit, entry),
format!("{}", finalized_structure),
finalized_structure,
false, // Don't commit anonymous unions (because I think it'll break things)
);
}
Some(get_uid(unit, entry))
}
// This function iterates up through the dependency references, adding all the types along the way until there are no more or stopping at the first one already tracked, then returns the UID of the type of the given DIE
pub(crate) fn get_type<R: Reader<Offset = usize>>(
unit: &Unit<R>,
entry: &DebuggingInformationEntry<R>,
debug_info_builder_context: &DebugInfoBuilderContext<R>,
debug_info_builder: &mut DebugInfoBuilder,
) -> Option<TypeUID> {
// If this node (and thus all its referenced nodes) has already been processed, just return the offset
if debug_info_builder.contains_type(get_uid(unit, entry)) {
return Some(get_uid(unit, entry));
}
// Don't parse types that are just declarations and not definitions
if let Ok(Some(_)) = entry.attr(constants::DW_AT_declaration) {
return None;
}
let entry_type = if let Some(die_reference) = get_attr_die(
unit,
entry,
debug_info_builder_context,
constants::DW_AT_type,
) {
// This needs to recurse first (before the early return below) to ensure all sub-types have been parsed
match die_reference {
DieReference::UnitAndOffset((entry_unit, entry_offset)) => get_type(
entry_unit,
&entry_unit.entry(entry_offset).unwrap(),
debug_info_builder_context,
debug_info_builder,
),
DieReference::Err => {
warn!("Failed to fetch DIE. Debug information may be incomplete.");
None
}
}
} else {
// This needs to recurse first (before the early return below) to ensure all sub-types have been parsed
match resolve_specification(unit, entry, debug_info_builder_context) {
DieReference::UnitAndOffset((entry_unit, entry_offset))
if entry_unit.header.offset() != unit.header.offset()
&& entry_offset != entry.offset() =>
{
get_type(
entry_unit,
&entry_unit.entry(entry_offset).unwrap(),
debug_info_builder_context,
debug_info_builder,
)
}
DieReference::UnitAndOffset(_) => None,
DieReference::Err => {
warn!("Failed to fetch DIE. Debug information may be incomplete.");
None
}
}
};
// If this node (and thus all its referenced nodes) has already been processed, just return the offset
// This check is not redundant because this type might have been processes in the recursive calls above
if debug_info_builder.contains_type(get_uid(unit, entry)) {
return Some(get_uid(unit, entry));
}
// Collect the required information to create a type and add it to the type map. Also, add the dependencies of this type to the type's typeinfo
// Create the type, make a TypeInfo for it, and add it to the debug info
let (type_def, mut commit): (Option<Ref<Type>>, bool) = match entry.tag() {
constants::DW_TAG_base_type => (
handle_base_type(unit, entry, debug_info_builder_context),
false,
),
constants::DW_TAG_structure_type => {
return do_structure_parse(
StructureType::StructStructureType,
unit,
entry,
debug_info_builder_context,
debug_info_builder,
)
}
constants::DW_TAG_class_type => {
return do_structure_parse(
StructureType::ClassStructureType,
unit,
entry,
debug_info_builder_context,
debug_info_builder,
)
}
constants::DW_TAG_union_type => {
return do_structure_parse(
StructureType::UnionStructureType,
unit,
entry,
debug_info_builder_context,
debug_info_builder,
)
}
// Enum
constants::DW_TAG_enumeration_type => {
(handle_enum(unit, entry, debug_info_builder_context), true)
}
// Basic types
constants::DW_TAG_typedef => {
if let Some(name) = debug_info_builder_context.get_name(unit, entry) {
handle_typedef(debug_info_builder, entry_type, name)
} else {
(None, false)
}
}
constants::DW_TAG_pointer_type => (
handle_pointer(
entry,
debug_info_builder_context,
debug_info_builder,
entry_type,
ReferenceType::PointerReferenceType,
),
false,
),
constants::DW_TAG_reference_type => (
handle_pointer(
entry,
debug_info_builder_context,
debug_info_builder,
entry_type,
ReferenceType::ReferenceReferenceType,
),
false,
),
constants::DW_TAG_rvalue_reference_type => (
handle_pointer(
entry,
debug_info_builder_context,
debug_info_builder,
entry_type,
ReferenceType::RValueReferenceType,
),
false,
),
constants::DW_TAG_array_type => (
handle_array(unit, entry, debug_info_builder, entry_type),
false,
),
// Strange Types
constants::DW_TAG_unspecified_type => (Some(Type::void()), false),
constants::DW_TAG_subroutine_type => (
handle_function(
unit,
entry,
debug_info_builder_context,
debug_info_builder,
entry_type,
),
false,
),
// Weird types
constants::DW_TAG_const_type => (handle_const(debug_info_builder, entry_type), false),
constants::DW_TAG_volatile_type => (handle_volatile(debug_info_builder, entry_type), true), // TODO : Maybe false here
// Pass-through everything else!
_ => return entry_type,
};
// Wrap our resultant type in a TypeInfo so that the internal DebugInfo class can manage it
if let Some(type_def) = type_def {
let name = if get_name(unit, entry, debug_info_builder_context).is_some() {
debug_info_builder_context.get_name(unit, entry)
} else {
None
}
.unwrap_or_else(|| {
commit = false;
format!("{}", type_def)
});
debug_info_builder.add_type(get_uid(unit, entry), name, type_def, commit);
Some(get_uid(unit, entry))
} else {
None
}
}

View File

@@ -0,0 +1,13 @@
[package]
name = "dwarfdump"
version = "0.1.0"
authors = ["Kyle Martin <kyle@vector35.com>"]
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
dwarfreader = { path = "../shared/" }
binaryninja = {path="../../../"}
gimli = "0.27"

View File

@@ -0,0 +1,17 @@
# DWARF Dump Example
This is actually a fully-developed plugin, rather than a measly example.
Two features this does not support are: files in big endian, and .dwo files
## How to use
Simply `cargo build --release` in this directory, and copy the `.so` from the target directory to your plugin directory
### Attribution
This example makes use of:
- [gimli] ([gimli license] - MIT)
[gimli license]: https://github.com/gimli-rs/gimli/blob/master/LICENSE-MIT
[gimli]: https://github.com/gimli-rs/gimli

View File

@@ -0,0 +1,308 @@
// Copyright 2021-2024 Vector 35 Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use binaryninja::{
binaryview::{BinaryView, BinaryViewExt},
command::{register, Command},
disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenContents},
flowgraph::{BranchType, EdgeStyle, FlowGraph, FlowGraphNode, FlowGraphOption},
string::BnString,
};
use dwarfreader::is_valid;
use gimli::{
AttributeValue::{Encoding, Flag, UnitRef},
// BigEndian,
DebuggingInformationEntry,
Dwarf,
EntriesTreeNode,
Reader,
ReaderOffset,
SectionId,
Unit,
UnitSectionOffset,
};
static PADDING: [&'static str; 23] = [
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
];
// TODO : This is very much not comprehensive: see https://github.com/gimli-rs/gimli/blob/master/examples/dwarfdump.rs
fn get_info_string<R: Reader>(
view: &BinaryView,
dwarf: &Dwarf<R>,
unit: &Unit<R>,
die_node: &DebuggingInformationEntry<R>,
) -> Vec<DisassemblyTextLine> {
let mut disassembly_lines: Vec<DisassemblyTextLine> = Vec::with_capacity(10); // This is an estimate so "most" things won't need to resize
let label_value = match die_node.offset().to_unit_section_offset(unit) {
UnitSectionOffset::DebugInfoOffset(o) => o.0,
UnitSectionOffset::DebugTypesOffset(o) => o.0,
}
.into_u64();
let label_string = format!("#0x{:08x}", label_value);
disassembly_lines.push(DisassemblyTextLine::from(vec![
InstructionTextToken::new(
BnString::new(label_string),
InstructionTextTokenContents::GotoLabel(label_value),
),
InstructionTextToken::new(BnString::new(":"), InstructionTextTokenContents::Text),
]));
disassembly_lines.push(DisassemblyTextLine::from(vec![InstructionTextToken::new(
BnString::new(die_node.tag().static_string().unwrap()),
InstructionTextTokenContents::TypeName, // TODO : KeywordToken?
)]));
let mut attrs = die_node.attrs();
while let Some(attr) = attrs.next().unwrap() {
let mut attr_line: Vec<InstructionTextToken> = Vec::with_capacity(5);
attr_line.push(InstructionTextToken::new(
BnString::new(" "),
InstructionTextTokenContents::Indentation,
));
let len;
if let Some(n) = attr.name().static_string() {
len = n.len();
attr_line.push(InstructionTextToken::new(
BnString::new(n),
InstructionTextTokenContents::FieldName,
));
} else {
// This is rather unlikely, I think
len = 1;
attr_line.push(InstructionTextToken::new(
BnString::new("?"),
InstructionTextTokenContents::FieldName,
));
}
// On command line the magic number that looks good is 22, but that's too much whitespace in a basic block, so I chose 18 (22 is the max with the current padding provided)
if len < 18 {
attr_line.push(InstructionTextToken::new(
BnString::new(PADDING[18 - len]),
InstructionTextTokenContents::Text,
));
}
attr_line.push(InstructionTextToken::new(
BnString::new(" = "),
InstructionTextTokenContents::Text,
));
if let Ok(Some(addr)) = dwarf.attr_address(unit, attr.value()) {
let addr_string = format!("0x{:08x}", addr);
attr_line.push(InstructionTextToken::new(
BnString::new(addr_string),
InstructionTextTokenContents::Integer(addr),
));
} else if let Ok(attr_reader) = dwarf.attr_string(unit, attr.value()) {
if let Ok(attr_string) = attr_reader.to_string() {
attr_line.push(InstructionTextToken::new(
BnString::new(attr_string.as_ref()),
InstructionTextTokenContents::String({
let (_, id, offset) =
dwarf.lookup_offset_id(attr_reader.offset_id()).unwrap();
offset.into_u64() + view.section_by_name(id.name()).unwrap().start()
}),
));
} else {
attr_line.push(InstructionTextToken::new(
BnString::new("??"),
InstructionTextTokenContents::Text,
));
}
} else if let Encoding(type_class) = attr.value() {
attr_line.push(InstructionTextToken::new(
BnString::new(type_class.static_string().unwrap()),
InstructionTextTokenContents::TypeName,
));
} else if let UnitRef(offset) = attr.value() {
let addr = match offset.to_unit_section_offset(unit) {
UnitSectionOffset::DebugInfoOffset(o) => o.0,
UnitSectionOffset::DebugTypesOffset(o) => o.0,
}
.into_u64();
let addr_string = format!("#0x{:08x}", addr);
attr_line.push(InstructionTextToken::new(
BnString::new(addr_string),
InstructionTextTokenContents::GotoLabel(addr),
));
} else if let Flag(true) = attr.value() {
attr_line.push(InstructionTextToken::new(
BnString::new("true"),
InstructionTextTokenContents::Integer(1),
));
} else if let Flag(false) = attr.value() {
attr_line.push(InstructionTextToken::new(
BnString::new("false"),
InstructionTextTokenContents::Integer(1),
));
// Fall-back cases
} else if let Some(value) = attr.u8_value() {
let value_string = format!("{}", value);
attr_line.push(InstructionTextToken::new(
BnString::new(value_string),
InstructionTextTokenContents::Integer(value.into()),
));
} else if let Some(value) = attr.u16_value() {
let value_string = format!("{}", value);
attr_line.push(InstructionTextToken::new(
BnString::new(value_string),
InstructionTextTokenContents::Integer(value.into()),
));
} else if let Some(value) = attr.udata_value() {
let value_string = format!("{}", value);
attr_line.push(InstructionTextToken::new(
BnString::new(value_string),
InstructionTextTokenContents::Integer(value.into()),
));
} else if let Some(value) = attr.sdata_value() {
let value_string = format!("{}", value);
attr_line.push(InstructionTextToken::new(
BnString::new(value_string),
InstructionTextTokenContents::Integer(value as u64),
));
} else {
let attr_string = format!("{:?}", attr.value());
attr_line.push(InstructionTextToken::new(
BnString::new(attr_string),
InstructionTextTokenContents::Text,
));
}
disassembly_lines.push(DisassemblyTextLine::from(attr_line));
}
disassembly_lines
}
fn process_tree<R: Reader>(
view: &BinaryView,
dwarf: &Dwarf<R>,
unit: &Unit<R>,
graph: &FlowGraph,
graph_parent: &FlowGraphNode,
die_node: EntriesTreeNode<R>,
) {
// Namespaces only - really interesting to look at!
// if (die_node.entry().tag() == constants::DW_TAG_namespace)
// || (die_node.entry().tag() == constants::DW_TAG_class_type)
// || (die_node.entry().tag() == constants::DW_TAG_compile_unit)
// || (die_node.entry().tag() == constants::DW_TAG_subprogram)
// {
let new_node = FlowGraphNode::new(graph);
let attr_string = get_info_string(view, dwarf, unit, die_node.entry());
new_node.set_disassembly_lines(&attr_string);
graph.append(&new_node);
graph_parent.add_outgoing_edge(
BranchType::UnconditionalBranch,
&new_node,
&EdgeStyle::default(),
);
let mut children = die_node.children();
while let Some(child) = children.next().unwrap() {
process_tree(view, dwarf, unit, graph, &new_node, child);
}
// }
}
fn dump_dwarf(bv: &BinaryView) {
let view = if bv.section_by_name(".debug_info").is_ok() {
bv.to_owned()
} else {
bv.parent_view().unwrap()
};
let graph = FlowGraph::new();
graph.set_option(FlowGraphOption::FlowGraphUsesBlockHighlights, true);
graph.set_option(FlowGraphOption::FlowGraphUsesInstructionHighlights, true);
let graph_root = FlowGraphNode::new(&graph);
graph_root.set_lines(vec!["Graph Root"]);
graph.append(&graph_root);
let endian = dwarfreader::get_endian(bv);
let section_reader = |section_id: SectionId| -> _ {
dwarfreader::create_section_reader(section_id, bv, endian, false)
};
let dwarf = Dwarf::load(&section_reader).unwrap();
let mut iter = dwarf.units();
while let Some(header) = iter.next().unwrap() {
let unit = dwarf.unit(header).unwrap();
let mut entries = unit.entries();
let mut depth = 0;
if let Some((delta_depth, entry)) = entries.next_dfs().unwrap() {
depth += delta_depth;
assert!(depth >= 0);
let mut tree = unit.entries_tree(Some(entry.offset())).unwrap();
let root = tree.root().unwrap();
process_tree(&view, &dwarf, &unit, &graph, &graph_root, root);
}
}
view.show_graph_report("DWARF", graph);
}
struct DWARFDump;
impl Command for DWARFDump {
fn action(&self, view: &BinaryView) {
dump_dwarf(view);
}
fn valid(&self, view: &BinaryView) -> bool {
is_valid(view)
}
}
#[no_mangle]
pub extern "C" fn UIPluginInit() -> bool {
register(
"DWARF Dump",
"Show embedded DWARF info as a tree structure for you to navigate",
DWARFDump {},
);
true
}

View File

@@ -0,0 +1,9 @@
[package]
name = "dwarfreader"
version = "0.1.0"
authors = ["Kyle Martin <kyle@vector35.com>"]
edition = "2021"
[dependencies]
binaryninja = {path="../../../"}
gimli = "0.27"

View File

@@ -0,0 +1,159 @@
// Copyright 2021-2024 Vector 35 Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use gimli::{EndianRcSlice, Endianity, Error, RunTimeEndian, SectionId};
use binaryninja::binaryninjacore_sys::*;
use binaryninja::{
binaryview::{BinaryView, BinaryViewBase, BinaryViewExt},
databuffer::DataBuffer,
Endianness,
};
use std::{ffi::CString, rc::Rc};
//////////////////////
// Dwarf Validation
pub fn is_non_dwo_dwarf(view: &BinaryView) -> bool {
view.section_by_name(".debug_info").is_ok() || view.section_by_name("__debug_info").is_ok()
}
pub fn is_dwo_dwarf(view: &BinaryView) -> bool {
view.section_by_name(".debug_info.dwo").is_ok()
}
pub fn is_raw_non_dwo_dwarf(view: &BinaryView) -> bool {
if let Ok(raw_view) = view.raw_view() {
raw_view.section_by_name(".debug_info").is_ok()
|| view.section_by_name("__debug_info").is_ok()
} else {
false
}
}
pub fn is_raw_dwo_dwarf(view: &BinaryView) -> bool {
if let Ok(raw_view) = view.raw_view() {
raw_view.section_by_name(".debug_info.dwo").is_ok()
} else {
false
}
}
pub fn is_valid(view: &BinaryView) -> bool {
is_non_dwo_dwarf(view)
|| is_raw_non_dwo_dwarf(view)
|| is_dwo_dwarf(view)
|| is_raw_dwo_dwarf(view)
}
pub fn get_endian(view: &BinaryView) -> RunTimeEndian {
match view.default_endianness() {
Endianness::LittleEndian => RunTimeEndian::Little,
Endianness::BigEndian => RunTimeEndian::Big,
}
}
pub fn create_section_reader<'a, Endian: 'a + Endianity>(
section_id: SectionId,
view: &'a BinaryView,
endian: Endian,
dwo_file: bool,
) -> Result<EndianRcSlice<Endian>, Error> {
let section_name = if dwo_file && section_id.dwo_name().is_some() {
section_id.dwo_name().unwrap()
} else {
section_id.name()
};
if let Ok(section) = view.section_by_name(section_name) {
// TODO : This is kinda broke....should add rust wrappers for some of this
if let Some(symbol) = view
.symbols()
.iter()
.find(|symbol| symbol.full_name().as_str() == "__elf_section_headers")
{
if let Some(data_var) = view
.data_variables()
.iter()
.find(|var| var.address == symbol.address())
{
// TODO : This should eventually be wrapped by some DataView sorta thingy thing, like how python does it
let data_type = data_var.type_with_confidence().contents;
let data = view.read_vec(data_var.address, data_type.width() as usize);
let element_type = data_type.element_type().unwrap().contents;
if let Some(current_section_header) = data
.chunks(element_type.width() as usize)
.find(|section_header| {
endian.read_u64(&section_header[24..32]) == section.start()
})
{
if (endian.read_u64(&current_section_header[8..16]) & 2048) != 0 {
// Get section, trim header, decompress, return
let offset = section.start() + 24;
let len = section.len() - 24;
if let Ok(buffer) = view.read_buffer(offset, len) {
use std::ptr;
let transform_name =
CString::new("Zlib").unwrap().into_bytes_with_nul();
let transform =
unsafe { BNGetTransformByName(transform_name.as_ptr() as *mut _) };
// Omega broke
let raw_buf: *mut BNDataBuffer =
unsafe { BNCreateDataBuffer(ptr::null_mut(), 0) };
if unsafe {
BNDecode(
transform,
std::mem::transmute(buffer),
raw_buf,
ptr::null_mut(),
0,
)
} {
let output_buffer: DataBuffer =
unsafe { std::mem::transmute(raw_buf) };
return Ok(EndianRcSlice::new(
output_buffer.get_data().into(),
endian,
));
}
}
}
}
}
}
let offset = section.start();
let len = section.len();
if len == 0 {
Ok(EndianRcSlice::new(Rc::from([]), endian))
} else {
Ok(EndianRcSlice::new(
Rc::from(view.read_vec(offset, len).as_slice()),
endian,
))
}
} else if let Ok(section) = view.section_by_name("__".to_string() + &section_name[1..]) {
Ok(EndianRcSlice::new(
Rc::from(view.read_vec(section.start(), section.len()).as_slice()),
endian,
))
} else {
Ok(EndianRcSlice::new(Rc::from([]), endian))
}
}

353
examples/flowgraph/Cargo.lock generated Normal file
View File

@@ -0,0 +1,353 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "binaryninja"
version = "0.1.0"
dependencies = [
"binaryninjacore-sys",
"libc",
"log",
]
[[package]]
name = "binaryninjacore-sys"
version = "0.1.0"
dependencies = [
"bindgen",
]
[[package]]
name = "bindgen"
version = "0.58.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "flowgraph"
version = "0.1.0"
dependencies = [
"binaryninja",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]]
name = "libloading"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "shlex"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -0,0 +1,11 @@
[package]
name = "flowgraph"
version = "0.1.0"
authors = ["Kyle Martin <kyle@vector35.com>"]
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
binaryninja = {path="../../"}

View File

@@ -0,0 +1,51 @@
use binaryninja::{
binaryview::{BinaryView, BinaryViewExt},
command::register,
disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenContents},
flowgraph::{BranchType, EdgePenStyle, EdgeStyle, FlowGraph, FlowGraphNode, ThemeColor},
string::BnString,
};
fn test_graph(view: &BinaryView) {
let graph = FlowGraph::new();
let disassembly_lines_a = vec![DisassemblyTextLine::from(vec![
InstructionTextToken::new(BnString::new("Li"), InstructionTextTokenContents::Text),
InstructionTextToken::new(BnString::new("ne"), InstructionTextTokenContents::Text),
InstructionTextToken::new(BnString::new(" 1"), InstructionTextTokenContents::Text),
])];
let node_a = FlowGraphNode::new(&graph);
node_a.set_disassembly_lines(&disassembly_lines_a);
let node_b = FlowGraphNode::new(&graph);
let disassembly_lines_b = vec![DisassemblyTextLine::from(&vec!["Li", "ne", " 2"])];
node_b.set_disassembly_lines(&disassembly_lines_b);
let node_c = FlowGraphNode::new(&graph);
node_c.set_lines(vec!["Line 3", "Line 4", "Line 5"]);
graph.append(&node_a);
graph.append(&node_b);
graph.append(&node_c);
let edge = EdgeStyle::new(EdgePenStyle::DashDotDotLine, 2, ThemeColor::AddressColor);
node_a.add_outgoing_edge(BranchType::UserDefinedBranch, &node_b, &edge);
node_a.add_outgoing_edge(
BranchType::UnconditionalBranch,
&node_c,
&EdgeStyle::default(),
);
view.show_graph_report("Rust Graph Title", graph);
}
#[no_mangle]
pub extern "C" fn UIPluginInit() -> bool {
register(
"Rust Graph Test Title",
"Rust Graph Test Description",
test_graph,
);
true
}

2
examples/minidump/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
/Cargo.lock

View File

@@ -0,0 +1,13 @@
[package]
name = "minidump_bn"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
binaryninja = {path="../../"}
log = "0.4.17"
minidump = "0.15.2"
time = "=0.3.16" # Remove this when we update from stable-2022-12-15 - it's pinning a dependency that requires a more recent version of rustc

View File

@@ -0,0 +1,65 @@
# Binary Ninja Minidump Loader
A Minidump memory dump loader plugin for Binary Ninja.
![Screenshot of Binary Ninja using the "Minidump" Binary View, with a minidump loaded and the virtual addresses of the memory segments of the minidump showing in the Memory Map window](images/loaded-minidump-screenshot-border.png)
This plugin adds a new _Minidump_ binary view type. When a binary with the magic number `MDMP` is opened, this plugin will automatically try to load in the binary as a minidump, and create a new _Minidump_ binary view to view the contents.
The architecture is determined automatically from the platform information embedded in the minidump.
![Screenshot showing the Minidump binary view type in the dropdown list of available binary views for an open binary](images/minidump-binary-view-type-screenshot-border.png)
The loaded minidump's memory regions and modules can be navigated via the _Memory Map_ window. In the _Minidump_ binary view, the meanings of "Segments" and "Sections" in the Memory Map window are modified to mean the following:
- The memory regions in the minidump are loaded as _Segments_. The _Data Offset_ and _Data Length_ fields of each segment are the corresponding addresses in the minidump file where the data for that memory region is located.
- The modules in the minidump are loaded as _Sections_, with the name of each section being the path to the module.
![Screenshot showing the Memory Map window with the loaded minidump's memory segments and modules (i.e. "sections")](images/minidump-segments-sections-screenshot-border.png)
## Supported Minidump Types
This plugin currently only supports loading minidump files generated by the Windows [`MiniDumpWriteDump` API](https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump).
This includes dumps generated from:
- The [`.dump` command](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/-dump--create-dump-file-) in WinDbg.
- The `.dump` command in Binary Ninja's debugger for Windows targets (which uses the same debugging engine as WinDbg).
For both of the above, it's recommended to generate a full dump:
```
.dump /ma dumpfile.dmp
```
- The [`minidump` command](https://help.x64dbg.com/en/latest/commands/memory-operations/minidump.html) in x64dbg.
```
minidump dumpfile.dmp
```
- Right clicking on a listed process and then clicking "Create dump file" / "Create full dump" from Windows Task Manager, Process Hacker, Sysinternals Process Explorer, etc...
## Unsupported Features (for now)
- Loading Minidump files from platforms or APIs other than Windows' `MinidumpWriteDump`, such as those generated by [Google Breakpad](https://chromium.googlesource.com/breakpad/breakpad/).
- Loading and applyng debug information from the minidump file. In Windows minidump files, `MinidumpModuleList` streams contain information about the PDB file which contains the debug information for the module; this isn't currently read or applied, however.
- Integration with Binary Ninja's built-in debugger. Minidump files can contain information about threads, register values, and stack frames, and it would be nice in the future for minidump files to be loadable back into the debugger in order to resume a debugging session. This isn't currently done, however.
## Building and Installing
This plugin currently needs to be built from source, then copied into your user plugin folder.
```
cargo build --release
cp target/release/libminidump_bn.so ~/.binaryninja/plugins/
```
The code in this plugin targets the `dev` branch of the [Binary Ninja Rust API](https://github.com/Vector35/binaryninja-api/tree/dev/rust).
To update the Binary Ninja Rust API dependency:
```
cargo update -p binaryninja
cargo build --release
```

View File

@@ -0,0 +1,68 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun");
#[cfg(target_os = "linux")]
static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun");
#[cfg(windows)]
static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun");
// Check last run location for path to BinaryNinja; Otherwise check the default install locations
fn link_path() -> PathBuf {
use std::io::prelude::*;
let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap());
let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1);
File::open(lastrun)
.and_then(|f| {
let mut binja_path = String::new();
let mut reader = BufReader::new(f);
reader.read_line(&mut binja_path)?;
Ok(PathBuf::from(binja_path.trim()))
})
.unwrap_or_else(|_| {
#[cfg(target_os = "macos")]
return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS");
#[cfg(target_os = "linux")]
return home.join("binaryninja");
#[cfg(windows)]
return PathBuf::from(env::var("PROGRAMFILES").unwrap())
.join("Vector35\\BinaryNinja\\");
})
}
fn main() {
// Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults
let install_path = env::var("BINARYNINJADIR")
.map(PathBuf::from)
.unwrap_or_else(|_| link_path());
#[cfg(target_os = "linux")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "macos")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=binaryninjacore");
println!("cargo:rustc-link-search={}", install_path.to_str().unwrap());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@@ -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");
}
}

View File

@@ -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
}

View File

@@ -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()
}
}

View File

@@ -0,0 +1,16 @@
[package]
name = "mlil_lifter"
version = "0.1.0"
edition = "2021"
# Uncomment this if you're writing a plugin (plugins are shared objects loaded by the core):
# [lib]
# crate-type = ["cdylib"]
# You can point at the BinaryNinja dependency in one of two ways, via path:
[dependencies]
binaryninja = {path="../../"}
# Or directly at the git repo:
# [dependencies]
# binaryninja = {git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"}

View File

@@ -0,0 +1,68 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun");
#[cfg(target_os = "linux")]
static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun");
#[cfg(windows)]
static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun");
// Check last run location for path to BinaryNinja; Otherwise check the default install locations
fn link_path() -> PathBuf {
use std::io::prelude::*;
let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap());
let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1);
File::open(lastrun)
.and_then(|f| {
let mut binja_path = String::new();
let mut reader = BufReader::new(f);
reader.read_line(&mut binja_path)?;
Ok(PathBuf::from(binja_path.trim()))
})
.unwrap_or_else(|_| {
#[cfg(target_os = "macos")]
return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS");
#[cfg(target_os = "linux")]
return home.join("binaryninja");
#[cfg(windows)]
return PathBuf::from(env::var("PROGRAMFILES").unwrap())
.join("Vector35\\BinaryNinja\\");
})
}
fn main() {
// Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults
let install_path = env::var("BINARYNINJADIR")
.map(PathBuf::from)
.unwrap_or_else(|_| link_path());
#[cfg(target_os = "linux")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "macos")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=binaryninjacore");
println!("cargo:rustc-link-search={}", install_path.to_str().unwrap());
}
}

View File

@@ -0,0 +1,49 @@
use std::env;
use binaryninja::binaryview::BinaryViewExt;
// Standalone executables need to provide a main function for rustc
// Plugins should refer to `binaryninja::command::*` for the various registration callbacks.
fn main() {
let mut args = env::args();
let _ = args.next().unwrap();
let Some(filename) = args.next() else {
panic!("Expected input filename\n");
};
// This loads all the core architecture, platform, etc plugins
// Standalone executables probably need to call this, but plugins do not
println!("Loading plugins...");
let _headless_session = binaryninja::headless::Session::new();
// Your code here...
println!("Loading binary...");
let bv = binaryninja::load(filename).expect("Couldn't open binary file");
// Go through all functions in the binary
for func in bv.functions().iter() {
let sym = func.symbol();
println!("Function {}:", sym.full_name());
let Ok(il) = func.medium_level_il() else {
println!(" Does not have MLIL\n");
continue;
};
// Get the SSA form for this function
let il = il.ssa_form();
// Loop through all blocks in the function
for block in il.basic_blocks().iter() {
// Loop though each instruction in the block
for instr in block.iter() {
// Uplift the instruction into a native rust format
let lifted = instr.lift();
let address = instr.address();
// print the lifted instruction
println!("{address:08x}: {lifted:x?}");
}
}
println!();
}
}

View File

@@ -0,0 +1,16 @@
[package]
name = "mlil_visitor"
version = "0.1.0"
edition = "2021"
# Uncomment this if you're writing a plugin (plugins are shared objects loaded by the core):
# [lib]
# crate-type = ["cdylib"]
# You can point at the BinaryNinja dependency in one of two ways, via path:
[dependencies]
binaryninja = {path="../../"}
# Or directly at the git repo:
# [dependencies]
# binaryninja = {git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"}

View File

@@ -0,0 +1,68 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun");
#[cfg(target_os = "linux")]
static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun");
#[cfg(windows)]
static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun");
// Check last run location for path to BinaryNinja; Otherwise check the default install locations
fn link_path() -> PathBuf {
use std::io::prelude::*;
let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap());
let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1);
File::open(lastrun)
.and_then(|f| {
let mut binja_path = String::new();
let mut reader = BufReader::new(f);
reader.read_line(&mut binja_path)?;
Ok(PathBuf::from(binja_path.trim()))
})
.unwrap_or_else(|_| {
#[cfg(target_os = "macos")]
return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS");
#[cfg(target_os = "linux")]
return home.join("binaryninja");
#[cfg(windows)]
return PathBuf::from(env::var("PROGRAMFILES").unwrap())
.join("Vector35\\BinaryNinja\\");
})
}
fn main() {
// Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults
let install_path = env::var("BINARYNINJADIR")
.map(PathBuf::from)
.unwrap_or_else(|_| link_path());
#[cfg(target_os = "linux")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "macos")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=binaryninjacore");
println!("cargo:rustc-link-search={}", install_path.to_str().unwrap());
}
}

View File

@@ -0,0 +1,266 @@
use std::env;
use binaryninja::binaryview::BinaryViewExt;
use binaryninja::mlil::operation::MediumLevelILOperand;
use binaryninja::mlil::{MediumLevelILFunction, MediumLevelILInstruction, MediumLevelILOperation};
use binaryninja::types::Variable;
fn print_indent(indent: usize) {
print!("{:<indent$}", "")
}
fn print_operation(operation: &MediumLevelILOperation) {
use MediumLevelILOperation::*;
match operation {
Nop(_) => print!("Nop"),
Noret(_) => print!("Noret"),
Bp(_) => print!("Bp"),
Undef(_) => print!("Undef"),
Unimpl(_) => print!("Unimpl"),
If(_) => print!("If"),
FloatConst(_) => print!("FloatConst"),
Const(_) => print!("Const"),
ConstPtr(_) => print!("ConstPtr"),
Import(_) => print!("Import"),
ExternPtr(_) => print!("ExternPtr"),
ConstData(_) => print!("ConstData"),
Jump(_) => print!("Jump"),
RetHint(_) => print!("RetHint"),
StoreSsa(_) => print!("StoreSsa"),
StoreStructSsa(_) => print!("StoreStructSsa"),
StoreStruct(_) => print!("StoreStruct"),
Store(_) => print!("Store"),
JumpTo(_) => print!("JumpTo"),
Goto(_) => print!("Goto"),
FreeVarSlot(_) => print!("FreeVarSlot"),
SetVarField(_) => print!("SetVarField"),
SetVar(_) => print!("SetVar"),
FreeVarSlotSsa(_) => print!("FreeVarSlotSsa"),
SetVarSsaField(_) => print!("SetVarSsaField"),
SetVarAliasedField(_) => print!("SetVarAliasedField"),
SetVarAliased(_) => print!("SetVarAliased"),
SetVarSsa(_) => print!("SetVarSsa"),
VarPhi(_) => print!("VarPhi"),
MemPhi(_) => print!("MemPhi"),
VarSplit(_) => print!("VarSplit"),
SetVarSplit(_) => print!("SetVarSplit"),
VarSplitSsa(_) => print!("VarSplitSsa"),
SetVarSplitSsa(_) => print!("SetVarSplitSsa"),
Add(_) => print!("Add"),
Sub(_) => print!("Sub"),
And(_) => print!("And"),
Or(_) => print!("Or"),
Xor(_) => print!("Xor"),
Lsl(_) => print!("Lsl"),
Lsr(_) => print!("Lsr"),
Asr(_) => print!("Asr"),
Rol(_) => print!("Rol"),
Ror(_) => print!("Ror"),
Mul(_) => print!("Mul"),
MuluDp(_) => print!("MuluDp"),
MulsDp(_) => print!("MulsDp"),
Divu(_) => print!("Divu"),
DivuDp(_) => print!("DivuDp"),
Divs(_) => print!("Divs"),
DivsDp(_) => print!("DivsDp"),
Modu(_) => print!("Modu"),
ModuDp(_) => print!("ModuDp"),
Mods(_) => print!("Mods"),
ModsDp(_) => print!("ModsDp"),
CmpE(_) => print!("CmpE"),
CmpNe(_) => print!("CmpNe"),
CmpSlt(_) => print!("CmpSlt"),
CmpUlt(_) => print!("CmpUlt"),
CmpSle(_) => print!("CmpSle"),
CmpUle(_) => print!("CmpUle"),
CmpSge(_) => print!("CmpSge"),
CmpUge(_) => print!("CmpUge"),
CmpSgt(_) => print!("CmpSgt"),
CmpUgt(_) => print!("CmpUgt"),
TestBit(_) => print!("TestBit"),
AddOverflow(_) => print!("AddOverflow"),
FcmpE(_) => print!("FcmpE"),
FcmpNe(_) => print!("FcmpNe"),
FcmpLt(_) => print!("FcmpLt"),
FcmpLe(_) => print!("FcmpLe"),
FcmpGe(_) => print!("FcmpGe"),
FcmpGt(_) => print!("FcmpGt"),
FcmpO(_) => print!("FcmpO"),
FcmpUo(_) => print!("FcmpUo"),
Fadd(_) => print!("Fadd"),
Fsub(_) => print!("Fsub"),
Fmul(_) => print!("Fmul"),
Fdiv(_) => print!("Fdiv"),
Adc(_) => print!("Adc"),
Sbb(_) => print!("Sbb"),
Rlc(_) => print!("Rlc"),
Rrc(_) => print!("Rrc"),
Call(_) => print!("Call"),
Tailcall(_) => print!("Tailcall"),
Syscall(_) => print!("Syscall"),
Intrinsic(_) => print!("Intrinsic"),
IntrinsicSsa(_) => print!("IntrinsicSsa"),
CallSsa(_) => print!("CallSsa"),
TailcallSsa(_) => print!("TailcallSsa"),
CallUntypedSsa(_) => print!("CallUntypedSsa"),
TailcallUntypedSsa(_) => print!("TailcallUntypedSsa"),
SyscallSsa(_) => print!("SyscallSsa"),
SyscallUntypedSsa(_) => print!("SyscallUntypedSsa"),
CallUntyped(_) => print!("CallUntyped"),
TailcallUntyped(_) => print!("TailcallUntyped"),
SyscallUntyped(_) => print!("SyscallUntyped"),
SeparateParamList(_) => print!("SeparateParamList"),
SharedParamSlot(_) => print!("SharedParamSlot"),
Neg(_) => print!("Neg"),
Not(_) => print!("Not"),
Sx(_) => print!("Sx"),
Zx(_) => print!("Zx"),
LowPart(_) => print!("LowPart"),
BoolToInt(_) => print!("BoolToInt"),
UnimplMem(_) => print!("UnimplMem"),
Fsqrt(_) => print!("Fsqrt"),
Fneg(_) => print!("Fneg"),
Fabs(_) => print!("Fabs"),
FloatToInt(_) => print!("FloatToInt"),
IntToFloat(_) => print!("IntToFloat"),
FloatConv(_) => print!("FloatConv"),
RoundToInt(_) => print!("RoundToInt"),
Floor(_) => print!("Floor"),
Ceil(_) => print!("Ceil"),
Ftrunc(_) => print!("Ftrunc"),
Load(_) => print!("Load"),
LoadStruct(_) => print!("LoadStruct"),
LoadStructSsa(_) => print!("LoadStructSsa"),
LoadSsa(_) => print!("LoadSsa"),
Ret(_) => print!("Ret"),
Var(_) => print!("Var"),
AddressOf(_) => print!("AddressOf"),
VarField(_) => print!("VarField"),
AddressOfField(_) => print!("AddressOfField"),
VarSsa(_) => print!("VarSsa"),
VarAliased(_) => print!("VarAliased"),
VarSsaField(_) => print!("VarSsaField"),
VarAliasedField(_) => print!("VarAliasedField"),
Trap(_) => print!("Trap"),
}
}
fn print_variable(func: &MediumLevelILFunction, var: &Variable) {
print!("{}", func.get_function().get_variable_name(var));
}
fn print_il_expr(instr: &MediumLevelILInstruction, mut indent: usize) {
print_indent(indent);
print_operation(instr.operation());
println!("");
indent += 1;
use MediumLevelILOperand::*;
for (_name, operand) in instr.operands() {
match operand {
Int(int) => {
print_indent(indent);
println!("int 0x{:x}", int);
}
Float(float) => {
print_indent(indent);
println!("int {:e}", float);
}
Expr(expr) => print_il_expr(&expr, indent),
Var(var) => {
print_indent(indent);
print!("var ");
print_variable(instr.function(), &var);
println!();
}
VarSsa(var) => {
print_indent(indent);
print!("ssa var ");
print_variable(instr.function(), &var.variable);
println!("#{}", var.version);
}
IntList(list) => {
print_indent(indent);
print!("index list ");
for i in list {
print!("{i} ");
}
println!();
}
VarList(list) => {
print_indent(indent);
print!("var list ");
for i in list {
print_variable(instr.function(), &i);
print!(" ");
}
println!();
}
VarSsaList(list) => {
print_indent(indent);
print!("ssa var list ");
for i in list {
print_variable(instr.function(), &i.variable);
print!("#{} ", i.version);
}
println!();
}
ExprList(list) => {
print_indent(indent);
println!("expr list");
for i in list {
print_il_expr(&i, indent + 1);
}
}
TargetMap(list) => {
print_indent(indent);
print!("target map ");
for (i, f) in list {
print!("({i}, {f}) ");
}
println!();
}
}
}
}
// Standalone executables need to provide a main function for rustc
// Plugins should refer to `binaryninja::command::*` for the various registration callbacks.
fn main() {
let mut args = env::args();
let _ = args.next().unwrap();
let Some(filename) = args.next() else {
panic!("Expected input filename\n");
};
// This loads all the core architecture, platform, etc plugins
// Standalone executables probably need to call this, but plugins do not
println!("Loading plugins...");
let _headless_session = binaryninja::headless::Session::new();
// Your code here...
println!("Loading binary...");
let bv = binaryninja::load(filename).expect("Couldn't open binary file");
// Go through all functions in the binary
for func in bv.functions().iter() {
let sym = func.symbol();
println!("Function {}:", sym.full_name());
let Ok(il) = func.medium_level_il() else {
println!(" Does not have MLIL\n");
continue;
};
// Loop through all blocks in the function
for block in il.basic_blocks().iter() {
// Loop though each instruction in the block
for instr in block.iter() {
// Generically parse the IL tree and display the parts
print_il_expr(&instr, 2);
}
}
println!();
}
}

355
examples/template/Cargo.lock generated Normal file
View File

@@ -0,0 +1,355 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "binaryninja"
version = "0.1.0"
source = "git+https://github.com/Vector35/binaryninja-api.git?branch=dev#83d7a7800ac4a970d618ad125cdbb18e3b29b53e"
dependencies = [
"binaryninjacore-sys",
"libc",
"log",
]
[[package]]
name = "binaryninjacore-sys"
version = "0.1.0"
source = "git+https://github.com/Vector35/binaryninja-api.git?branch=dev#83d7a7800ac4a970d618ad125cdbb18e3b29b53e"
dependencies = [
"bindgen",
]
[[package]]
name = "bindgen"
version = "0.58.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[package]]
name = "libloading"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "template"
version = "0.1.0"
dependencies = [
"binaryninja",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -0,0 +1,16 @@
[package]
name = "template"
version = "0.1.0"
edition = "2021"
# Uncomment this if you're writing a plugin (plugins are shared objects loaded by the core):
# [lib]
# crate-type = ["cdylib"]
# You can point at the BinaryNinja dependency in one of two ways, via path:
[dependencies]
binaryninja = {path="../../"}
# Or directly at the git repo:
# [dependencies]
# binaryninja = {git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"}

View File

@@ -0,0 +1,19 @@
# Template
[The only official method of providing linker arguments to a crate is through that crate's `build.rs`](https://github.com/rust-lang/cargo/issues/9554), thus this template.
Please see `Cargo.toml` for further configuration options.
## Plugins
Enable
```
[lib]
crate-type = ["cdylib"]
```
in `Cargo.toml`.
## Standalone executables
All standalone executables should call both `binaryninja::headless::init()` and `binaryninja::headless::shutdown()` (see [`src/main.rs`](src/main.rs)).
Standalone executables will fail to link if you do not provide a `build.rs`. The one provided here should work.

View File

@@ -0,0 +1,68 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun");
#[cfg(target_os = "linux")]
static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun");
#[cfg(windows)]
static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun");
// Check last run location for path to BinaryNinja; Otherwise check the default install locations
fn link_path() -> PathBuf {
use std::io::prelude::*;
let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap());
let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1);
File::open(lastrun)
.and_then(|f| {
let mut binja_path = String::new();
let mut reader = BufReader::new(f);
reader.read_line(&mut binja_path)?;
Ok(PathBuf::from(binja_path.trim()))
})
.unwrap_or_else(|_| {
#[cfg(target_os = "macos")]
return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS");
#[cfg(target_os = "linux")]
return home.join("binaryninja");
#[cfg(windows)]
return PathBuf::from(env::var("PROGRAMFILES").unwrap())
.join("Vector35\\BinaryNinja\\");
})
}
fn main() {
// Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults
let install_path = env::var("BINARYNINJADIR")
.map(PathBuf::from)
.unwrap_or_else(|_| link_path());
#[cfg(target_os = "linux")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "macos")]
println!(
"cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore",
install_path.to_str().unwrap(),
install_path.to_str().unwrap(),
);
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=binaryninjacore");
println!("cargo:rustc-link-search={}", install_path.to_str().unwrap());
}
}

View File

@@ -0,0 +1,42 @@
use binaryninja::architecture::Architecture;
use binaryninja::binaryview::{BinaryViewBase, BinaryViewExt};
// Standalone executables need to provide a main function for rustc
// Plugins should refer to `binaryninja::command::*` for the various registration callbacks.
fn main() {
// This loads all the core architecture, platform, etc plugins
// Standalone executables probably need to call this, but plugins do not
println!("Loading plugins...");
let headless_session = binaryninja::headless::Session::new();
// Your code here...
println!("Loading binary...");
let bv = headless_session
.load("/bin/cat")
.expect("Couldn't open `/bin/cat`");
println!("Filename: `{}`", bv.file().filename());
println!("File size: `{:#x}`", bv.len());
println!("Function count: {}", bv.functions().len());
for func in &bv.functions() {
println!(" `{}`:", func.symbol().full_name());
for basic_block in &func.basic_blocks() {
// TODO : This is intended to be refactored to be more nice to work with soon(TM)
for addr in basic_block.as_ref() {
print!(" {} ", addr);
if let Some((_, tokens)) = func.arch().instruction_text(
bv.read_buffer(addr, func.arch().max_instr_len())
.unwrap()
.get_data(),
addr,
) {
tokens
.iter()
.for_each(|token| print!("{}", token.text().as_str()));
println!();
}
}
}
}
}