use crate::error::Result;
use crate::macho::{LC_DYLD_CHAINED_FIXUPS, LC_DYLD_EXPORTS_TRIE};
use super::ExtractionContext;
const MH_DYLIB_IN_CACHE: u32 = 0x8000_0000;
const HEADER_FLAGS_OFFSET: usize = 24;
const CHAINED_FIXUPS_DATAOFF_OFFSET: usize = 8;
const CHAINED_FIXUPS_DATASIZE_OFFSET: usize = 12;
pub fn fix_header_and_load_commands(ctx: &mut ExtractionContext) -> Result<()> {
ctx.info("Fixing header and load commands...");
clear_dylib_in_cache_flag(ctx)?;
zero_chained_fixups(ctx)?;
zero_exports_trie(ctx)?;
Ok(())
}
pub fn clear_dylib_in_cache_flag(ctx: &mut ExtractionContext) -> Result<()> {
let flags = ctx.macho.read_u32(HEADER_FLAGS_OFFSET)?;
if (flags & MH_DYLIB_IN_CACHE) != 0 {
let new_flags = flags & !MH_DYLIB_IN_CACHE;
ctx.macho.write_u32(HEADER_FLAGS_OFFSET, new_flags)?;
ctx.macho.header.flags = new_flags;
ctx.info("Cleared MH_DYLIB_IN_CACHE flag");
}
Ok(())
}
pub fn zero_chained_fixups(ctx: &mut ExtractionContext) -> Result<()> {
let mut offset = 32usize; let end_offset = 32 + ctx.macho.header.sizeofcmds as usize;
while offset < end_offset {
if offset + 8 > ctx.macho.data.len() {
break;
}
let cmd = ctx.macho.read_u32(offset)?;
let cmdsize = ctx.macho.read_u32(offset + 4)?;
if cmd == LC_DYLD_CHAINED_FIXUPS {
ctx.macho
.write_u32(offset + CHAINED_FIXUPS_DATAOFF_OFFSET, 0)?;
ctx.macho
.write_u32(offset + CHAINED_FIXUPS_DATASIZE_OFFSET, 0)?;
ctx.info("Zeroed LC_DYLD_CHAINED_FIXUPS offsets");
}
offset += cmdsize as usize;
}
Ok(())
}
pub fn zero_exports_trie(ctx: &mut ExtractionContext) -> Result<()> {
let mut offset = 32usize; let end_offset = 32 + ctx.macho.header.sizeofcmds as usize;
while offset < end_offset {
if offset + 8 > ctx.macho.data.len() {
break;
}
let cmd = ctx.macho.read_u32(offset)?;
let cmdsize = ctx.macho.read_u32(offset + 4)?;
if cmd == LC_DYLD_EXPORTS_TRIE {
ctx.macho.write_u32(offset + 8, 0)?; ctx.macho.write_u32(offset + 12, 0)?;
ctx.info("Zeroed LC_DYLD_EXPORTS_TRIE offsets");
}
offset += cmdsize as usize;
}
Ok(())
}
pub fn remove_load_command(ctx: &mut ExtractionContext, cmd_type: u32) -> Result<usize> {
let mut removed = 0;
let mut read_offset = 32usize;
let mut write_offset = 32usize;
let end_offset = 32 + ctx.macho.header.sizeofcmds as usize;
while read_offset < end_offset {
if read_offset + 8 > ctx.macho.data.len() {
break;
}
let cmd = ctx.macho.read_u32(read_offset)?;
let cmdsize = ctx.macho.read_u32(read_offset + 4)?;
if cmd == cmd_type {
removed += 1;
} else {
if write_offset != read_offset {
let cmd_data: Vec<u8> =
ctx.macho.data[read_offset..read_offset + cmdsize as usize].to_vec();
ctx.macho.data[write_offset..write_offset + cmdsize as usize]
.copy_from_slice(&cmd_data);
}
write_offset += cmdsize as usize;
}
read_offset += cmdsize as usize;
}
if removed > 0 {
ctx.macho.header.ncmds -= removed as u32;
ctx.macho.header.sizeofcmds = (write_offset - 32) as u32;
ctx.macho.write_u32(16, ctx.macho.header.ncmds)?; ctx.macho.write_u32(20, ctx.macho.header.sizeofcmds)?;
for i in write_offset..end_offset {
if i < ctx.macho.data.len() {
ctx.macho.data[i] = 0;
}
}
ctx.info(&format!(
"Removed {} load command(s) of type {:#x}",
removed, cmd_type
));
}
Ok(removed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flag_value() {
assert_eq!(MH_DYLIB_IN_CACHE, 0x80000000);
}
#[test]
fn test_flag_clearing() {
let flags: u32 = 0x80200085; let cleared = flags & !MH_DYLIB_IN_CACHE;
assert_eq!(cleared, 0x00200085);
assert_eq!(cleared & MH_DYLIB_IN_CACHE, 0);
}
#[test]
fn test_header_offset() {
assert_eq!(HEADER_FLAGS_OFFSET, 24);
}
}