use crate::error::Result;
use super::ExtractionContext;
pub const OBJC_IMAGE_IS_SIMULATED: u32 = 1 << 0;
pub const OBJC_IMAGE_IS_REPLACEMENT: u32 = 1 << 1;
pub const OBJC_IMAGE_SUPPORTS_GC: u32 = 1 << 2;
pub const OBJC_IMAGE_OPTIMIZED_BY_DYLD: u32 = 1 << 3;
pub const OBJC_IMAGE_SIGNED_CLASS_RO: u32 = 1 << 4;
pub const OBJC_IMAGE_SUPPORTS_COMPACTION: u32 = 1 << 5;
pub const METHOD_LIST_RELATIVE_FLAG: u32 = 0x8000_0000;
pub const METHOD_LIST_DIRECT_SEL_FLAG: u32 = 0x4000_0000;
pub const METHOD_LIST_UNIQUED_FLAG: u32 = 0x2000_0000;
pub const METHOD_LIST_COUNT_MASK: u32 = 0x00FF_FFFF;
#[allow(dead_code)]
const RELATIVE_METHOD_SIZE: usize = 12;
#[allow(dead_code)]
const OLD_METHOD_SIZE: usize = 24;
pub fn fix_objc(ctx: &mut ExtractionContext) -> Result<()> {
let imageinfo = ctx
.macho
.section("__DATA", "__objc_imageinfo")
.or_else(|| ctx.macho.section("__DATA_CONST", "__objc_imageinfo"));
let imageinfo = match imageinfo {
Some(sect) => sect.clone(),
None => {
ctx.info("No ObjC image info found, skipping ObjC fixing");
return Ok(());
}
};
let offset = imageinfo.section.offset as usize;
if offset + 8 > ctx.macho.data.len() {
return Ok(());
}
let flags = crate::util::read_u32_le(&ctx.macho.data[offset + 4..]);
if (flags & OBJC_IMAGE_OPTIMIZED_BY_DYLD) == 0 {
ctx.info("ObjC not optimized by dyld, skipping");
return Ok(());
}
ctx.info("Fixing ObjC metadata...");
let mut fixed_methods = 0;
fixed_methods += fix_class_method_lists(ctx)?;
fixed_methods += fix_category_method_lists(ctx)?;
if fixed_methods > 0 {
ctx.info(&format!("Fixed {} method lists", fixed_methods));
}
Ok(())
}
fn fix_class_method_lists(ctx: &mut ExtractionContext) -> Result<usize> {
let classlist = ctx
.macho
.section("__DATA", "__objc_classlist")
.or_else(|| ctx.macho.section("__DATA_CONST", "__objc_classlist"));
let classlist = match classlist {
Some(s) => s.clone(),
None => return Ok(0),
};
let count = classlist.section.size as usize / 8;
let mut fixed = 0;
for i in 0..count {
let ptr_offset = classlist.section.offset as usize + i * 8;
if ptr_offset + 8 > ctx.macho.data.len() {
break;
}
let class_addr = crate::util::read_u64_le(&ctx.macho.data[ptr_offset..]);
let class_addr = class_addr & 0x0000_FFFF_FFFF_FFFF;
if let Some(class_offset) = ctx.macho.addr_to_offset(class_addr) {
fixed += fix_class_at(ctx, class_offset)?;
}
}
Ok(fixed)
}
fn fix_class_at(ctx: &mut ExtractionContext, class_offset: usize) -> Result<usize> {
if class_offset + 48 > ctx.macho.data.len() {
return Ok(0);
}
let data_addr = crate::util::read_u64_le(&ctx.macho.data[class_offset + 32..]);
let data_addr = data_addr & 0x0000_FFFF_FFFF_FFF8;
if let Some(data_offset) = ctx.macho.addr_to_offset(data_addr) {
return fix_class_ro(ctx, data_offset);
}
Ok(0)
}
fn fix_class_ro(ctx: &mut ExtractionContext, ro_offset: usize) -> Result<usize> {
if ro_offset + 72 > ctx.macho.data.len() {
return Ok(0);
}
let methods_addr = crate::util::read_u64_le(&ctx.macho.data[ro_offset + 32..]);
if methods_addr == 0 {
return Ok(0);
}
let methods_addr = methods_addr & 0x0000_FFFF_FFFF_FFFF;
if let Some(methods_offset) = ctx.macho.addr_to_offset(methods_addr) {
return fix_method_list(ctx, methods_offset);
}
Ok(0)
}
fn fix_category_method_lists(ctx: &mut ExtractionContext) -> Result<usize> {
let catlist = ctx
.macho
.section("__DATA", "__objc_catlist")
.or_else(|| ctx.macho.section("__DATA_CONST", "__objc_catlist"));
let catlist = match catlist {
Some(s) => s.clone(),
None => return Ok(0),
};
let count = catlist.section.size as usize / 8;
let mut fixed = 0;
for i in 0..count {
let ptr_offset = catlist.section.offset as usize + i * 8;
if ptr_offset + 8 > ctx.macho.data.len() {
break;
}
let cat_addr = crate::util::read_u64_le(&ctx.macho.data[ptr_offset..]);
let cat_addr = cat_addr & 0x0000_FFFF_FFFF_FFFF;
if let Some(cat_offset) = ctx.macho.addr_to_offset(cat_addr) {
fixed += fix_category_at(ctx, cat_offset)?;
}
}
Ok(fixed)
}
fn fix_category_at(ctx: &mut ExtractionContext, cat_offset: usize) -> Result<usize> {
if cat_offset + 48 > ctx.macho.data.len() {
return Ok(0);
}
let mut fixed = 0;
let instance_methods = crate::util::read_u64_le(&ctx.macho.data[cat_offset + 16..]);
if instance_methods != 0 {
let addr = instance_methods & 0x0000_FFFF_FFFF_FFFF;
if let Some(offset) = ctx.macho.addr_to_offset(addr) {
fixed += fix_method_list(ctx, offset)?;
}
}
let class_methods = crate::util::read_u64_le(&ctx.macho.data[cat_offset + 24..]);
if class_methods != 0 {
let addr = class_methods & 0x0000_FFFF_FFFF_FFFF;
if let Some(offset) = ctx.macho.addr_to_offset(addr) {
fixed += fix_method_list(ctx, offset)?;
}
}
Ok(fixed)
}
fn fix_method_list(ctx: &mut ExtractionContext, offset: usize) -> Result<usize> {
if offset + 8 > ctx.macho.data.len() {
return Ok(0);
}
let entsize_and_flags = crate::util::read_u32_le(&ctx.macho.data[offset..]);
let has_direct_sel = (entsize_and_flags & METHOD_LIST_DIRECT_SEL_FLAG) != 0;
let has_uniqued = (entsize_and_flags & METHOD_LIST_UNIQUED_FLAG) != 0;
if !has_direct_sel && !has_uniqued {
return Ok(0);
}
let new_entsize_and_flags =
entsize_and_flags & !(METHOD_LIST_DIRECT_SEL_FLAG | METHOD_LIST_UNIQUED_FLAG);
ctx.macho.data[offset..offset + 4].copy_from_slice(&new_entsize_and_flags.to_le_bytes());
Ok(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flag_values() {
assert_eq!(OBJC_IMAGE_IS_SIMULATED, 0x01);
assert_eq!(OBJC_IMAGE_IS_REPLACEMENT, 0x02);
assert_eq!(OBJC_IMAGE_SUPPORTS_GC, 0x04);
assert_eq!(OBJC_IMAGE_OPTIMIZED_BY_DYLD, 0x08);
assert_eq!(OBJC_IMAGE_SIGNED_CLASS_RO, 0x10);
}
#[test]
fn test_method_list_flags() {
assert_eq!(METHOD_LIST_RELATIVE_FLAG, 0x8000_0000);
assert_eq!(METHOD_LIST_DIRECT_SEL_FLAG, 0x4000_0000);
assert_eq!(METHOD_LIST_UNIQUED_FLAG, 0x2000_0000);
}
#[test]
fn test_flag_clearing() {
let original: u32 = 0x6000_0018; let expected: u32 = 0x0000_0018; let result = original & !(METHOD_LIST_DIRECT_SEL_FLAG | METHOD_LIST_UNIQUED_FLAG);
assert_eq!(result, expected);
}
}