Revision control
Copy as Markdown
Other Tools
//! Dynamically linked symbolic imports
// table of tuples:
// <seg-index, seg-offset, type, symbol-library-ordinal, symbol-name, addend>
// symbol flags are undocumented
use alloc::vec::Vec;
use core::fmt::{self, Debug};
use core::ops::Range;
use scroll::{Pread, Sleb128, Uleb128};
use crate::container;
use crate::error;
use crate::mach::bind_opcodes;
use crate::mach::load_command;
use crate::mach::segment;
#[derive(Debug)]
/// Import binding information generated by running the Finite State Automaton programmed via `bind_opcodes`
struct BindInformation<'a> {
seg_index: u8,
seg_offset: u64,
bind_type: u8,
symbol_library_ordinal: u8,
symbol_name: &'a str,
symbol_flags: u8,
addend: i64,
special_dylib: u8, // seeing self = 0 assuming this means the symbol is imported from itself, because its... libSystem.B.dylib?
is_lazy: bool,
}
impl<'a> BindInformation<'a> {
pub fn new(is_lazy: bool) -> Self {
let mut bind_info = BindInformation::default();
if is_lazy {
bind_info.is_lazy = true;
bind_info.bind_type = bind_opcodes::BIND_TYPE_POINTER;
}
bind_info
}
pub fn is_weak(&self) -> bool {
self.symbol_flags & bind_opcodes::BIND_SYMBOL_FLAGS_WEAK_IMPORT != 0
}
}
impl<'a> Default for BindInformation<'a> {
fn default() -> Self {
BindInformation {
seg_index: 0,
seg_offset: 0x0,
bind_type: 0x0,
special_dylib: 1,
symbol_library_ordinal: 0,
symbol_name: "",
symbol_flags: 0,
addend: 0,
is_lazy: false,
}
}
}
#[derive(Debug)]
/// An dynamically linked symbolic import
pub struct Import<'a> {
/// The symbol name dyld uses to resolve this import
pub name: &'a str,
/// The library this symbol belongs to (thanks to two-level namespaces)
pub dylib: &'a str,
/// Whether the symbol is lazily resolved or not
pub is_lazy: bool,
/// The offset in the binary this import is found
pub offset: u64,
/// The size of this import
pub size: usize,
/// The virtual memory address at which this import is found
pub address: u64,
/// The addend of this import
pub addend: i64,
/// Whether this import is weak
pub is_weak: bool,
/// The offset in the stream of bind opcodes that caused this import
pub start_of_sequence_offset: u64,
}
impl<'a> Import<'a> {
/// Create a new import from the import binding information in `bi`
fn new(
bi: &BindInformation<'a>,
libs: &[&'a str],
segments: &[segment::Segment],
start_of_sequence_offset: usize,
) -> Import<'a> {
let (offset, address) = {
let segment = &segments[bi.seg_index as usize];
(
segment.fileoff + bi.seg_offset,
segment.vmaddr + bi.seg_offset,
)
};
let size = if bi.is_lazy { 8 } else { 0 };
Import {
name: bi.symbol_name,
dylib: libs[bi.symbol_library_ordinal as usize],
is_lazy: bi.is_lazy,
offset,
size,
address,
addend: bi.addend,
is_weak: bi.is_weak(),
start_of_sequence_offset: start_of_sequence_offset as u64,
}
}
}
/// An interpreter for mach BIND opcodes.
/// Runs on prebound (non lazy) symbols (usually dylib extern consts and extern variables),
/// and lazy symbols (usually dylib functions)
pub struct BindInterpreter<'a> {
data: &'a [u8],
location: Range<usize>,
lazy_location: Range<usize>,
}
impl<'a> Debug for BindInterpreter<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("BindInterpreter")
.field("data", &"<... redacted ...>")
.field(
"location",
&format_args!("{:#x}..{:#x}", self.location.start, self.location.end),
)
.field(
"lazy_location",
&format_args!(
"{:#x}..{:#x}",
self.lazy_location.start, self.lazy_location.end
),
)
.finish()
}
}
impl<'a> BindInterpreter<'a> {
/// Construct a new import binding interpreter from `bytes` and the load `command`
pub fn new(bytes: &'a [u8], command: &load_command::DyldInfoCommand) -> Self {
let get_pos = |off: u32, size: u32| -> Range<usize> {
let start = off as usize;
start..start.saturating_add(size as usize)
};
let location = get_pos(command.bind_off, command.bind_size);
let lazy_location = get_pos(command.lazy_bind_off, command.lazy_bind_size);
BindInterpreter {
data: bytes,
location,
lazy_location,
}
}
/// Return the imports in this binary
pub fn imports(
&self,
libs: &[&'a str],
segments: &[segment::Segment],
ctx: container::Ctx,
) -> error::Result<Vec<Import<'a>>> {
let mut imports = Vec::new();
self.run(false, libs, segments, ctx, &mut imports)?;
self.run(true, libs, segments, ctx, &mut imports)?;
Ok(imports)
}
fn run(
&self,
is_lazy: bool,
libs: &[&'a str],
segments: &[segment::Segment],
ctx: container::Ctx,
imports: &mut Vec<Import<'a>>,
) -> error::Result<()> {
use crate::mach::bind_opcodes::*;
let location = if is_lazy {
&self.lazy_location
} else {
&self.location
};
let mut bind_info = BindInformation::new(is_lazy);
let mut offset = location.start;
let mut start_of_sequence: usize = 0;
while offset < location.end {
let opcode = self.data.gread::<i8>(&mut offset)? as bind_opcodes::Opcode;
// let mut input = String::new();
// ::std::io::stdin().read_line(&mut input).unwrap();
// println!("opcode: {} ({:#x}) offset: {:#x}\n {:?}", opcode_to_str(opcode & BIND_OPCODE_MASK), opcode, offset - location.start - 1, &bind_info);
match opcode & BIND_OPCODE_MASK {
// we do nothing, don't update our records, and add a new, fresh record
BIND_OPCODE_DONE => {
bind_info = BindInformation::new(is_lazy);
start_of_sequence = offset - location.start;
}
BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => {
let symbol_library_ordinal = opcode & BIND_IMMEDIATE_MASK;
bind_info.symbol_library_ordinal = symbol_library_ordinal;
}
BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => {
let symbol_library_ordinal = Uleb128::read(&self.data, &mut offset)?;
bind_info.symbol_library_ordinal = symbol_library_ordinal as u8;
}
BIND_OPCODE_SET_DYLIB_SPECIAL_IMM => {
// dyld puts the immediate into the symbol_library_ordinal field...
let special_dylib = opcode & BIND_IMMEDIATE_MASK;
// Printf.printf "special_dylib: 0x%x\n" special_dylib
bind_info.special_dylib = special_dylib;
}
BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
let symbol_flags = opcode & BIND_IMMEDIATE_MASK;
let symbol_name = self.data.pread::<&str>(offset)?;
offset += symbol_name.len() + 1; // second time this \0 caused debug woes
bind_info.symbol_name = symbol_name;
bind_info.symbol_flags = symbol_flags;
}
BIND_OPCODE_SET_TYPE_IMM => {
let bind_type = opcode & BIND_IMMEDIATE_MASK;
bind_info.bind_type = bind_type;
}
BIND_OPCODE_SET_ADDEND_SLEB => {
let addend = Sleb128::read(&self.data, &mut offset)?;
bind_info.addend = addend;
}
BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
let seg_index = opcode & BIND_IMMEDIATE_MASK;
// dyld sets the address to the segActualLoadAddress(segIndex) + uleb128
// address = segActualLoadAddress(segmentIndex) + read_uleb128(p, end);
let seg_offset = Uleb128::read(&self.data, &mut offset)?;
bind_info.seg_index = seg_index;
bind_info.seg_offset = seg_offset;
}
BIND_OPCODE_ADD_ADDR_ULEB => {
let addr = Uleb128::read(&self.data, &mut offset)?;
let seg_offset = bind_info.seg_offset.wrapping_add(addr);
bind_info.seg_offset = seg_offset;
}
// record the record by placing its value into our list
BIND_OPCODE_DO_BIND => {
// from dyld:
// if ( address >= segmentEndAddress )
// throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p);
// (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last);
// address += sizeof(intptr_t);
imports.push(Import::new(&bind_info, libs, segments, start_of_sequence));
let seg_offset = bind_info.seg_offset.wrapping_add(ctx.size() as u64);
bind_info.seg_offset = seg_offset;
}
BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB => {
// dyld:
// if ( address >= segmentEndAddress )
// throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p);
// (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last);
// address += read_uleb128(p, end) + sizeof(intptr_t);
// we bind the old record, then increment bind info address for the next guy, plus the ptr offset *)
imports.push(Import::new(&bind_info, libs, segments, start_of_sequence));
let addr = Uleb128::read(&self.data, &mut offset)?;
let seg_offset = bind_info
.seg_offset
.wrapping_add(addr)
.wrapping_add(ctx.size() as u64);
bind_info.seg_offset = seg_offset;
}
BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED => {
// dyld:
// if ( address >= segmentEndAddress )
// throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p);
// (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last);
// address += immediate*sizeof(intptr_t) + sizeof(intptr_t);
// break;
// similarly, we bind the old record, then perform address manipulation for the next record
imports.push(Import::new(&bind_info, libs, segments, start_of_sequence));
let scale = opcode & BIND_IMMEDIATE_MASK;
let size = ctx.size() as u64;
let seg_offset = bind_info
.seg_offset
.wrapping_add(u64::from(scale) * size)
.wrapping_add(size);
bind_info.seg_offset = seg_offset;
}
BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB => {
// dyld:
// count = read_uleb128(p, end);
// skip = read_uleb128(p, end);
// for (uint32_t i=0; i < count; ++i) {
// if ( address >= segmentEndAddress )
// throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p);
// (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last);
// address += skip + sizeof(intptr_t);
// }
// break;
let count = Uleb128::read(&self.data, &mut offset)?;
let skip = Uleb128::read(&self.data, &mut offset)?;
let skip_plus_size = skip + ctx.size() as u64;
for _i in 0..count {
imports.push(Import::new(&bind_info, libs, segments, start_of_sequence));
let seg_offset = bind_info.seg_offset.wrapping_add(skip_plus_size);
bind_info.seg_offset = seg_offset;
}
}
_ => {}
}
}
Ok(())
}
}