use crate::errors;
use crate::field::{ByteOrder, Field};
pub const LC_SEGMENT: u32 = 0x1;
pub const LC_SYMTAB: u32 = 0x2;
pub const LC_UNIXTHREAD: u32 = 0x5;
pub const LC_DYSYMTAB: u32 = 0xb;
pub const LC_LOAD_DYLIB: u32 = 0xc;
pub const LC_ID_DYLIB: u32 = 0xd;
pub const LC_LOAD_DYLINKER: u32 = 0xe;
pub const LC_SEGMENT_64: u32 = 0x19;
pub const LC_UUID: u32 = 0x1b;
pub const LC_RPATH: u32 = 0x1c;
pub const LC_CODE_SIGNATURE: u32 = 0x1d;
pub const LC_FUNCTION_STARTS: u32 = 0x26;
pub const LC_DATA_IN_CODE: u32 = 0x29;
pub const LC_DYLD_EXPORTS_TRIE: u32 = 0x80000033;
pub const LC_LOAD_WEAK_DYLIB: u32 = 0x80000018;
pub const LC_REEXPORT_DYLIB: u32 = 0x8000001f;
pub const LC_LAZY_LOAD_DYLIB: u32 = 0x20;
pub const LC_DYLD_INFO: u32 = 0x22;
pub const LC_DYLD_INFO_ONLY: u32 = 0x80000022;
pub const LC_MAIN: u32 = 0x80000028;
pub const LC_SOURCE_VERSION: u32 = 0x2a;
pub const LC_VERSION_MIN_MACOSX: u32 = 0x24;
pub const LC_VERSION_MIN_IPHONEOS: u32 = 0x25;
pub const LC_BUILD_VERSION: u32 = 0x32;
pub const LC_LINKER_OPTION: u32 = 0x2d;
pub const LC_LINKER_OPTIMIZATION_HINT: u32 = 0x2e;
pub const LC_VERSION_MIN_TVOS: u32 = 0x2f;
pub const LC_VERSION_MIN_WATCHOS: u32 = 0x30;
pub const LC_FILESET_ENTRY: u32 = 0x35;
pub const LC_REQ_DYLD: u32 = 0x80000000;
#[derive(Debug)]
pub struct LoadCommand {
pub cmd: Field<u32>,
pub cmdsize: Field<u32>,
}
#[derive(Debug)]
pub struct SymtabCommand {
pub symoff: Field<u32>,
pub nsyms: Field<u32>,
pub stroff: Field<u32>,
pub strsize: Field<u32>,
}
#[derive(Debug)]
pub struct DysymtabCommand {
pub ilocalsym: Field<u32>,
pub nlocalsym: Field<u32>,
pub iextdefsym: Field<u32>,
pub nextdefsym: Field<u32>,
pub iundefsym: Field<u32>,
pub nundefsym: Field<u32>,
}
#[derive(Debug)]
pub struct DyldInfoCommand {
pub rebase_off: Field<u32>,
pub rebase_size: Field<u32>,
pub bind_off: Field<u32>,
pub bind_size: Field<u32>,
pub weak_bind_off: Field<u32>,
pub weak_bind_size: Field<u32>,
pub lazy_bind_off: Field<u32>,
pub lazy_bind_size: Field<u32>,
pub export_off: Field<u32>,
pub export_size: Field<u32>,
}
#[derive(Debug)]
pub struct MainCommand {
pub entryoff: Field<u64>,
pub stacksize: Field<u64>,
}
#[derive(Debug)]
pub struct DylibCommand {
pub name_offset: Field<u32>,
pub timestamp: Field<u32>,
pub current_version: Field<u32>,
pub compatibility_version: Field<u32>,
pub name: String,
}
#[derive(Debug)]
pub struct StrCommand {
pub str_offset: Field<u32>,
pub name: String,
}
#[derive(Debug)]
pub struct UuidCommand {
pub uuid: [u8; 16],
}
#[derive(Debug)]
pub struct LinkeditDataCommand {
pub dataoff: Field<u32>,
pub datasize: Field<u32>,
}
#[derive(Debug)]
pub struct BuildVersionCommand {
pub platform: Field<u32>,
pub minos: Field<u32>,
pub sdk: Field<u32>,
pub ntools: Field<u32>,
}
#[derive(Debug)]
pub struct BuildToolVersion {
pub tool: Field<u32>,
pub version: Field<u32>,
}
#[derive(Debug)]
pub struct SourceVersionCommand {
pub version: Field<u64>,
}
#[derive(Debug)]
pub struct VersionMinCommand {
pub version: Field<u32>,
pub sdk: Field<u32>,
}
#[derive(Debug)]
pub struct UnixThreadCommand {
pub flavor: Field<u32>,
pub count: Field<u32>,
pub state_offset: usize,
pub state: Vec<u8>,
}
#[derive(Debug)]
pub struct LinkerOptionCommand {
pub count: Field<u32>,
pub options: Vec<String>,
}
#[derive(Debug)]
pub struct LinkerOptimizationHintCommand {
pub count: Field<u32>,
pub size: Field<u32>,
}
#[derive(Debug)]
pub struct FilesetEntryCommand {
pub vmaddr: Field<u64>,
pub fileoff: Field<u64>,
pub entry_id_offset: Field<u32>,
pub entry_id: String,
}
#[derive(Debug)]
pub enum TypedCommand {
Symtab(SymtabCommand),
Dysymtab(DysymtabCommand),
DyldInfo(DyldInfoCommand),
Main(MainCommand),
Dylib(DylibCommand),
Dylinker(StrCommand),
Rpath(StrCommand),
Uuid(UuidCommand),
LinkeditData(LinkeditDataCommand),
BuildVersion(BuildVersionCommand),
SourceVersion(SourceVersionCommand),
VersionMin(VersionMinCommand),
UnixThread(UnixThreadCommand),
LinkerOption(LinkerOptionCommand),
LinkerOptimizationHint(LinkerOptimizationHintCommand),
FilesetEntry(FilesetEntryCommand),
}
impl LoadCommand {
pub fn offset(&self) -> usize {
self.cmd.offset
}
pub fn typed(
&self,
buffer: &[u8],
order: ByteOrder,
) -> Result<Option<TypedCommand>, errors::FileParseError> {
let off = self.cmd.offset;
let size = self.cmdsize.value as usize;
let read_u32 = |rel: usize| order.read_u32(buffer, off + rel);
let read_u64 = |rel: usize| order.read_u64(buffer, off + rel);
let typed = match self.cmd.value {
LC_SYMTAB => TypedCommand::Symtab(SymtabCommand {
symoff: Field::new(read_u32(8)?, off + 8, 4),
nsyms: Field::new(read_u32(12)?, off + 12, 4),
stroff: Field::new(read_u32(16)?, off + 16, 4),
strsize: Field::new(read_u32(20)?, off + 20, 4),
}),
LC_DYSYMTAB => TypedCommand::Dysymtab(DysymtabCommand {
ilocalsym: Field::new(read_u32(8)?, off + 8, 4),
nlocalsym: Field::new(read_u32(12)?, off + 12, 4),
iextdefsym: Field::new(read_u32(16)?, off + 16, 4),
nextdefsym: Field::new(read_u32(20)?, off + 20, 4),
iundefsym: Field::new(read_u32(24)?, off + 24, 4),
nundefsym: Field::new(read_u32(28)?, off + 28, 4),
}),
LC_DYLD_INFO | LC_DYLD_INFO_ONLY => TypedCommand::DyldInfo(DyldInfoCommand {
rebase_off: Field::new(read_u32(8)?, off + 8, 4),
rebase_size: Field::new(read_u32(12)?, off + 12, 4),
bind_off: Field::new(read_u32(16)?, off + 16, 4),
bind_size: Field::new(read_u32(20)?, off + 20, 4),
weak_bind_off: Field::new(read_u32(24)?, off + 24, 4),
weak_bind_size: Field::new(read_u32(28)?, off + 28, 4),
lazy_bind_off: Field::new(read_u32(32)?, off + 32, 4),
lazy_bind_size: Field::new(read_u32(36)?, off + 36, 4),
export_off: Field::new(read_u32(40)?, off + 40, 4),
export_size: Field::new(read_u32(44)?, off + 44, 4),
}),
LC_MAIN => TypedCommand::Main(MainCommand {
entryoff: Field::new(read_u64(8)?, off + 8, 8),
stacksize: Field::new(read_u64(16)?, off + 16, 8),
}),
LC_LOAD_DYLIB | LC_ID_DYLIB | LC_LOAD_WEAK_DYLIB | LC_REEXPORT_DYLIB
| LC_LAZY_LOAD_DYLIB => {
let name_offset = read_u32(8)?;
let name = read_lc_string(buffer, off, size, name_offset as usize)?;
TypedCommand::Dylib(DylibCommand {
name_offset: Field::new(name_offset, off + 8, 4),
timestamp: Field::new(read_u32(12)?, off + 12, 4),
current_version: Field::new(read_u32(16)?, off + 16, 4),
compatibility_version: Field::new(read_u32(20)?, off + 20, 4),
name,
})
}
LC_LOAD_DYLINKER => {
let str_offset = read_u32(8)?;
let name = read_lc_string(buffer, off, size, str_offset as usize)?;
TypedCommand::Dylinker(StrCommand {
str_offset: Field::new(str_offset, off + 8, 4),
name,
})
}
LC_RPATH => {
let str_offset = read_u32(8)?;
let name = read_lc_string(buffer, off, size, str_offset as usize)?;
TypedCommand::Rpath(StrCommand {
str_offset: Field::new(str_offset, off + 8, 4),
name,
})
}
LC_UUID => {
let bytes = buffer
.get(off + 8..off + 24)
.ok_or(errors::FileParseError::BufferOverflow)?;
let mut uuid = [0u8; 16];
uuid.copy_from_slice(bytes);
TypedCommand::Uuid(UuidCommand { uuid })
}
LC_CODE_SIGNATURE | LC_FUNCTION_STARTS | LC_DATA_IN_CODE | LC_DYLD_EXPORTS_TRIE => {
TypedCommand::LinkeditData(LinkeditDataCommand {
dataoff: Field::new(read_u32(8)?, off + 8, 4),
datasize: Field::new(read_u32(12)?, off + 12, 4),
})
}
LC_BUILD_VERSION => TypedCommand::BuildVersion(BuildVersionCommand {
platform: Field::new(read_u32(8)?, off + 8, 4),
minos: Field::new(read_u32(12)?, off + 12, 4),
sdk: Field::new(read_u32(16)?, off + 16, 4),
ntools: Field::new(read_u32(20)?, off + 20, 4),
}),
LC_SOURCE_VERSION => TypedCommand::SourceVersion(SourceVersionCommand {
version: Field::new(read_u64(8)?, off + 8, 8),
}),
LC_VERSION_MIN_MACOSX
| LC_VERSION_MIN_IPHONEOS
| LC_VERSION_MIN_TVOS
| LC_VERSION_MIN_WATCHOS => TypedCommand::VersionMin(VersionMinCommand {
version: Field::new(read_u32(8)?, off + 8, 4),
sdk: Field::new(read_u32(12)?, off + 12, 4),
}),
LC_UNIXTHREAD => {
let flavor = read_u32(8)?;
let count = read_u32(12)?;
let state_off = off + 16;
let state_len = count as usize * 4;
let state = buffer
.get(state_off..state_off + state_len)
.ok_or(errors::FileParseError::BufferOverflow)?
.to_vec();
TypedCommand::UnixThread(UnixThreadCommand {
flavor: Field::new(flavor, off + 8, 4),
count: Field::new(count, off + 12, 4),
state_offset: state_off,
state,
})
}
LC_LINKER_OPTION => {
let count = read_u32(8)?;
let mut options = Vec::new();
let mut str_pos = off + 12;
let cmd_end = off + size;
for _ in 0..count {
if str_pos >= cmd_end {
return Err(errors::FileParseError::BufferOverflow);
}
let slice = buffer
.get(str_pos..cmd_end)
.ok_or(errors::FileParseError::BufferOverflow)?;
let stop = slice.iter().position(|&b| b == 0).unwrap_or(slice.len());
let opt = String::from_utf8_lossy(&slice[..stop]).into_owned();
str_pos += stop + 1;
options.push(opt);
}
TypedCommand::LinkerOption(LinkerOptionCommand {
count: Field::new(count, off + 8, 4),
options,
})
}
LC_LINKER_OPTIMIZATION_HINT => {
TypedCommand::LinkerOptimizationHint(LinkerOptimizationHintCommand {
count: Field::new(read_u32(8)?, off + 8, 4),
size: Field::new(read_u32(12)?, off + 12, 4),
})
}
LC_FILESET_ENTRY => {
let entry_id_off = read_u32(24)?;
let entry_id = read_lc_string(buffer, off, size, entry_id_off as usize)?;
TypedCommand::FilesetEntry(FilesetEntryCommand {
vmaddr: Field::new(read_u64(8)?, off + 8, 8),
fileoff: Field::new(read_u64(16)?, off + 16, 8),
entry_id_offset: Field::new(entry_id_off, off + 24, 4),
entry_id,
})
}
_ => return Ok(None),
};
Ok(Some(typed))
}
pub(crate) fn parse_load_commands(
buffer: &[u8],
offset: usize,
ncmds: u32,
order: ByteOrder,
) -> Result<Vec<Self>, errors::FileParseError> {
let mut commands = Vec::new();
let mut current_offset = offset;
for _ in 0..ncmds {
if buffer.len() < current_offset + 8 {
return Err(errors::FileParseError::BufferOverflow);
}
let cmd = Field::new(order.read_u32(buffer, current_offset)?, current_offset, 4);
let cmdsize = Field::new(
order.read_u32(buffer, current_offset + 4)?,
current_offset + 4,
4,
);
if cmdsize.value == 0 {
return Err(errors::FileParseError::InvalidFileFormat);
}
commands.push(LoadCommand {
cmd,
cmdsize: cmdsize.clone(),
});
current_offset += cmdsize.value as usize;
}
Ok(commands)
}
}
pub fn parse_build_tools(
buffer: &[u8],
cmd: &BuildVersionCommand,
cmd_offset: usize,
order: ByteOrder,
) -> Result<Vec<BuildToolVersion>, errors::FileParseError> {
let ntools = cmd.ntools.value as usize;
let mut out = Vec::with_capacity(ntools);
let mut off = cmd_offset + 24;
for _ in 0..ntools {
if buffer.len() < off + 8 {
return Err(errors::FileParseError::BufferOverflow);
}
out.push(BuildToolVersion {
tool: Field::new(order.read_u32(buffer, off)?, off, 4),
version: Field::new(order.read_u32(buffer, off + 4)?, off + 4, 4),
});
off += 8;
}
Ok(out)
}
fn read_lc_string(
buffer: &[u8],
cmd_offset: usize,
cmd_size: usize,
str_offset: usize,
) -> Result<String, errors::FileParseError> {
if str_offset >= cmd_size {
return Err(errors::FileParseError::InvalidFileFormat);
}
let start = cmd_offset + str_offset;
let end = cmd_offset + cmd_size;
let slice = buffer
.get(start..end)
.ok_or(errors::FileParseError::BufferOverflow)?;
let stop = slice.iter().position(|&b| b == 0).unwrap_or(slice.len());
Ok(String::from_utf8_lossy(&slice[..stop]).into_owned())
}