use std::collections::HashMap;
use crate::{
binary::MachoBinary,
util::{read_cstr_at, read_u32_le_at, read_u64_le_at, vm_to_file_offset_in},
};
mod category;
mod class;
mod conformance;
mod imageinfo;
mod ivar;
mod method;
mod property;
mod protocol;
mod refs;
mod section;
pub use category::{CategoryIter, ObjcCategory};
pub use class::{ClassIter, ClassRo, ObjcClass};
pub use conformance::{ConformanceEdge, ConformanceIter};
pub use imageinfo::{
ImageInfo, OBJC_IMAGE_DYLD_CATEGORIES_OPTIMIZED, OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES,
OBJC_IMAGE_IS_SIMULATED, OBJC_IMAGE_OPTIMIZED_BY_DYLD, OBJC_IMAGE_OPTIMIZED_BY_DYLD_CLOSURE,
OBJC_IMAGE_REQUIRES_GC, OBJC_IMAGE_SIGNED_CLASS_RO, OBJC_IMAGE_SUPPORTS_GC,
OBJC_IMAGE_SWIFT_STABLE_VERSION_MASK, OBJC_IMAGE_SWIFT_UNSTABLE_VERSION_MASK,
};
pub use ivar::{Ivar, IvarIter};
pub use method::{Method, MethodIter, MethodKind};
pub use property::{ParsedAttribute, ParsedAttributes, Property, PropertyIter};
pub use protocol::{ObjcProtocol, ProtocolIter, ProtocolNameIter};
pub use refs::{ClassRefIter, ProtoRefIter, RefTarget, SelRefIter, SuperRefIter};
pub(crate) use section::{ObjcSection, find_section};
#[derive(Debug)]
pub struct ObjcRuntime<'a> {
pub(crate) data: &'a [u8],
pub(crate) segments: Vec<(u64, u64, u64, u64)>,
pub(crate) image_info: ImageInfo,
pub(crate) class_list: Option<ObjcSection<'a>>,
pub(crate) nlclslist: Option<ObjcSection<'a>>,
pub(crate) cat_list: Option<ObjcSection<'a>>,
pub(crate) nlcat_list: Option<ObjcSection<'a>>,
pub(crate) proto_list: Option<ObjcSection<'a>>,
pub(crate) sel_refs: Option<ObjcSection<'a>>,
pub(crate) class_refs: Option<ObjcSection<'a>>,
pub(crate) super_refs: Option<ObjcSection<'a>>,
pub(crate) proto_refs: Option<ObjcSection<'a>>,
pub(crate) binds_by_va: HashMap<u64, (&'a str, &'a str)>,
pub(crate) rebases_by_va: HashMap<u64, u64>,
}
impl<'a> ObjcRuntime<'a> {
pub(crate) fn build(bin: &MachoBinary<'a>) -> Option<Self> {
if !bin.header().is_64() {
#[cfg(feature = "tracing")]
tracing::debug!("darwinscope::objc: 32-bit Mach-O - Obj-C walker is 64-bit only");
return None;
}
let imageinfo_sec = find_section(bin, "__objc_imageinfo")?;
let image_info = ImageInfo::parse(imageinfo_sec.body)?;
let mut binds_by_va: HashMap<u64, (&'a str, &'a str)> = HashMap::new();
let data = bin.raw();
for imp in bin.imports() {
let Some(name) = reborrow_into_data(data, imp.name) else {
continue;
};
let Some(dylib) = reborrow_into_data(data, imp.dylib) else {
continue;
};
binds_by_va.insert(imp.address, (name, dylib));
}
let mut segments: Vec<(u64, u64, u64, u64)> = Vec::new();
for s in bin.segments() {
segments.push((s.vmaddr(), s.vmsize(), s.fileoff(), s.filesize()));
}
let mut rebases_by_va: HashMap<u64, u64> = HashMap::new();
for r in bin.chained_rebases() {
rebases_by_va.insert(r.vm_address(), r.target_vmaddr());
}
Some(Self {
data: bin.raw(),
segments,
image_info,
class_list: find_section(bin, "__objc_classlist"),
nlclslist: find_section(bin, "__objc_nlclslist"),
cat_list: find_section(bin, "__objc_catlist"),
nlcat_list: find_section(bin, "__objc_nlcatlist"),
proto_list: find_section(bin, "__objc_protolist"),
sel_refs: find_section(bin, "__objc_selrefs"),
class_refs: find_section(bin, "__objc_classrefs"),
super_refs: find_section(bin, "__objc_superrefs"),
proto_refs: find_section(bin, "__objc_protorefs"),
binds_by_va,
rebases_by_va,
})
}
pub fn image_info(&self) -> ImageInfo {
self.image_info
}
pub fn classes(&self) -> ClassIter<'a, '_> {
ClassIter::new(self)
}
pub fn protocols(&self) -> ProtocolIter<'a, '_> {
ProtocolIter::new(self)
}
pub fn categories(&self) -> CategoryIter<'a, '_> {
CategoryIter::new(self)
}
pub fn conformances(&self) -> ConformanceIter<'a, '_> {
ConformanceIter::new(self)
}
pub fn selector_refs(&self) -> SelRefIter<'a, '_> {
SelRefIter::new(self)
}
pub fn class_refs(&self) -> ClassRefIter<'a, '_> {
ClassRefIter::new(self)
}
pub fn super_refs(&self) -> SuperRefIter<'a, '_> {
SuperRefIter::new(self)
}
pub fn protocol_refs(&self) -> ProtoRefIter<'a, '_> {
ProtoRefIter::new(self)
}
pub(crate) fn vm_to_file_offset(&self, vmaddr: u64) -> Option<u64> {
vm_to_file_offset_in(self.segments.iter().copied(), vmaddr)
}
pub(crate) fn read_cstr(&self, vmaddr: u64) -> Option<&'a str> {
if vmaddr == 0 {
return None;
}
let off = self.vm_to_file_offset(vmaddr)? as usize;
read_cstr_at(self.data, off)
}
pub(crate) fn read_u32(&self, vmaddr: u64) -> Option<u32> {
let off = self.vm_to_file_offset(vmaddr)? as usize;
read_u32_le_at(self.data, off)
}
pub(crate) fn read_u64(&self, vmaddr: u64) -> Option<u64> {
let off = self.vm_to_file_offset(vmaddr)? as usize;
read_u64_le_at(self.data, off)
}
pub(crate) fn read_bytes(&self, vmaddr: u64, len: usize) -> Option<&'a [u8]> {
let off = self.vm_to_file_offset(vmaddr)? as usize;
let end = off.checked_add(len)?;
self.data.get(off..end)
}
pub(crate) fn resolve_pointer(&self, slot_va: u64) -> Option<u64> {
if let Some(&target) = self.rebases_by_va.get(&slot_va) {
return Some(target);
}
let raw = self.read_u64(slot_va)?;
Some(crate::ptrauth::strip_signature(raw))
}
}
fn reborrow_into_data<'a>(data: &'a [u8], s: &str) -> Option<&'a str> {
let s_ptr = s.as_ptr() as usize;
let s_len = s.len();
let data_start = data.as_ptr() as usize;
let data_end = data_start.checked_add(data.len())?;
if s_ptr < data_start {
return None;
}
let s_end = s_ptr.checked_add(s_len)?;
if s_end > data_end {
return None;
}
let off = s_ptr.checked_sub(data_start)?;
let end = off.checked_add(s_len)?;
let bytes = data.get(off..end)?;
core::str::from_utf8(bytes).ok()
}
pub(crate) fn strip_objc_symbol_prefix(name: &str) -> &str {
const PREFIXES: &[&str] = &[
"_OBJC_CLASS_$_",
"_OBJC_METACLASS_$_",
"_OBJC_PROTOCOL_$_",
"_OBJC_REF_$_",
"_OBJC_LABEL_PROTOCOL_$_",
];
for p in PREFIXES {
if let Some(rest) = name.strip_prefix(p) {
return rest;
}
}
name
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strip_class_prefix() {
assert_eq!(
strip_objc_symbol_prefix("_OBJC_CLASS_$_NSObject"),
"NSObject"
);
assert_eq!(
strip_objc_symbol_prefix("_OBJC_METACLASS_$_NSObject"),
"NSObject"
);
assert_eq!(
strip_objc_symbol_prefix("_OBJC_PROTOCOL_$_NSCoding"),
"NSCoding"
);
assert_eq!(
strip_objc_symbol_prefix("_NSConcreteStackBlock"),
"_NSConcreteStackBlock"
);
}
}