use crate::endian::{BigEndian, Endian};
use crate::macho;
use crate::read::{Bytes, Error, ReadError, ReadRef, Result};
impl<E: Endian> macho::LinkeditDataCommand<E> {
pub fn code_signature<'data, R: ReadRef<'data>>(
&self,
endian: E,
data: R,
) -> Result<CodeSignature<'data>> {
let signature_data = data
.read_bytes_at(
self.dataoff.get(endian).into(),
self.datasize.get(endian).into(),
)
.read_error("Invalid Mach-O code signature offset or size")?;
CodeSignature::parse(signature_data)
}
}
#[derive(Debug, Clone, Copy)]
pub struct CodeSignature<'data> {
data: Bytes<'data>,
header: &'data macho::CsSuperBlob,
index: &'data [macho::CsBlobIndex],
}
impl<'data> CodeSignature<'data> {
pub fn parse(data: &'data [u8]) -> Result<Self> {
let mut bytes = Bytes(data);
let header = bytes
.read::<macho::CsSuperBlob>()
.read_error("Invalid Mach-O code signature super blob")?;
if header.magic.get(BigEndian) != macho::CSMAGIC_EMBEDDED_SIGNATURE {
return Err(Error("Unsupported Mach-O code signature magic"));
}
let index = bytes
.read_slice::<macho::CsBlobIndex>(header.count.get(BigEndian) as usize)
.read_error("Invalid Mach-O code signature blob count")?;
Ok(CodeSignature {
data: Bytes(data),
header,
index,
})
}
pub fn header(&self) -> &'data macho::CsSuperBlob {
self.header
}
pub fn index(&self) -> &'data [macho::CsBlobIndex] {
self.index
}
pub fn blobs(&self) -> CodeSignatureBlobIterator<'data> {
CodeSignatureBlobIterator {
data: self.data,
index: self.index.iter(),
}
}
}
#[derive(Debug, Clone)]
pub struct CodeSignatureBlobIterator<'data> {
data: Bytes<'data>,
index: core::slice::Iter<'data, macho::CsBlobIndex>,
}
impl<'data> CodeSignatureBlobIterator<'data> {
pub fn next(&mut self) -> Result<Option<CodeSignatureBlob<'data>>> {
let Some(index) = self.index.next() else {
return Ok(None);
};
let slot = index.slot.get(BigEndian);
let offset = index.offset.get(BigEndian);
let header = self
.data
.read_at::<macho::CsGenericBlob>(offset as usize)
.read_error("Invalid Mach-O code signature blob offset")?;
let data = self
.data
.read_bytes_at(offset as usize, header.length.get(BigEndian) as usize)
.read_error("Invalid Mach-O code signature blob length")?;
Ok(Some(CodeSignatureBlob {
slot,
offset,
magic: header.magic.get(BigEndian),
data,
}))
}
}
impl<'data> Iterator for CodeSignatureBlobIterator<'data> {
type Item = Result<CodeSignatureBlob<'data>>;
fn next(&mut self) -> Option<Self::Item> {
self.next().transpose()
}
}
#[derive(Debug, Clone, Copy)]
pub struct CodeSignatureBlob<'data> {
slot: macho::CsSlot,
offset: u32,
magic: u32,
data: Bytes<'data>,
}
impl<'data> CodeSignatureBlob<'data> {
pub fn slot(&self) -> macho::CsSlot {
self.slot
}
pub fn offset(&self) -> u32 {
self.offset
}
pub fn magic(&self) -> u32 {
self.magic
}
pub fn data(&self) -> &'data [u8] {
self.data.0
}
pub fn contents(&self) -> &'data [u8] {
self.data
.0
.get(core::mem::size_of::<macho::CsGenericBlob>()..)
.unwrap_or(&[])
}
pub fn code_directory(&self) -> Result<Option<CodeDirectory<'data>>> {
if self.magic != macho::CSMAGIC_CODEDIRECTORY {
return Ok(None);
}
CodeDirectory::parse(self.data.0).map(Some)
}
}
#[derive(Debug, Clone, Copy)]
pub struct CodeDirectory<'data> {
data: Bytes<'data>,
header: &'data macho::CsCodeDirectoryV0,
v1: Option<&'data macho::CsCodeDirectoryV1>,
v2: Option<&'data macho::CsCodeDirectoryV2>,
v3: Option<&'data macho::CsCodeDirectoryV3>,
v4: Option<&'data macho::CsCodeDirectoryV4>,
}
impl<'data> CodeDirectory<'data> {
pub fn parse(data: &'data [u8]) -> Result<Self> {
let mut header_data = Bytes(data);
let header = header_data
.read::<macho::CsCodeDirectoryV0>()
.read_error("Invalid Mach-O code directory")?;
if header.magic.get(BigEndian) != macho::CSMAGIC_CODEDIRECTORY {
return Err(Error("Unsupported Mach-O code directory magic"));
}
let version = header.version.get(BigEndian);
let v1 = if version >= macho::CS_SUPPORTSSCATTER {
Some(
header_data
.read::<macho::CsCodeDirectoryV1>()
.read_error("Invalid Mach-O code directory length")?,
)
} else {
None
};
let v2 = if version >= macho::CS_SUPPORTSTEAMID {
Some(
header_data
.read::<macho::CsCodeDirectoryV2>()
.read_error("Invalid Mach-O code directory length")?,
)
} else {
None
};
let v3 = if version >= macho::CS_SUPPORTSCODELIMIT64 {
Some(
header_data
.read::<macho::CsCodeDirectoryV3>()
.read_error("Invalid Mach-O code directory length")?,
)
} else {
None
};
let v4 = if version >= macho::CS_SUPPORTSEXECSEG {
Some(
header_data
.read::<macho::CsCodeDirectoryV4>()
.read_error("Invalid Mach-O code directory length")?,
)
} else {
None
};
Ok(CodeDirectory {
data: Bytes(data),
header,
v1,
v2,
v3,
v4,
})
}
pub fn header(&self) -> &'data macho::CsCodeDirectoryV0 {
self.header
}
pub fn version(&self) -> macho::CsVersion {
self.header.version.get(BigEndian)
}
pub fn scatter_offset(&self) -> Option<u32> {
Some(self.v1?.scatter_offset.get(BigEndian))
}
pub fn team_offset(&self) -> Option<u32> {
Some(self.v2?.team_offset.get(BigEndian))
}
pub fn code_limit(&self) -> u64 {
match self.code_limit64() {
Some(code_limit64) if code_limit64 != 0 => code_limit64,
_ => self.header.code_limit.get(BigEndian).into(),
}
}
pub fn code_limit64(&self) -> Option<u64> {
Some(self.v3?.code_limit64.get(BigEndian))
}
pub fn exec_seg(&self) -> Option<&'data macho::CsCodeDirectoryV4> {
self.v4
}
pub fn ident(&self) -> Result<&'data [u8]> {
self.data
.read_string_at(self.header.ident_offset.get(BigEndian) as usize)
.read_error("Invalid Mach-O code directory identifier offset")
}
pub fn team_id(&self) -> Result<Option<&'data [u8]>> {
let team_offset = self.team_offset().unwrap_or(0);
if team_offset == 0 {
return Ok(None);
}
let team_id = self
.data
.read_string_at(team_offset as usize)
.read_error("Invalid Mach-O code directory team identifier offset")?;
Ok(Some(team_id))
}
pub fn special_hash(&self, slot: macho::CsSlot) -> Result<&'data [u8]> {
if slot.0 < 1 || slot.0 > self.header.n_special_slots.get(BigEndian) {
return Err(Error("Invalid Mach-O code directory special hash index"));
}
let offset = slot
.0
.checked_mul(u32::from(self.header.hash_size))
.and_then(|offset| self.header.hash_offset.get(BigEndian).checked_sub(offset))
.read_error("Invalid Mach-O code directory hash size")?;
Ok(self
.data
.read_bytes_at(offset as usize, self.header.hash_size as usize)
.read_error("Invalid Mach-O code directory hash offset")?
.0)
}
pub fn code_hash(&self, index: u32) -> Result<&'data [u8]> {
if self.scatter_offset().unwrap_or(0) != 0 {
return Err(Error("Unsupported Mach-O code directory scatter"));
}
if index >= self.header.n_code_slots.get(BigEndian) {
return Err(Error("Invalid Mach-O code directory hash index"));
}
let offset = index
.checked_mul(u32::from(self.header.hash_size))
.and_then(|offset| offset.checked_add(self.header.hash_offset.get(BigEndian)))
.read_error("Invalid Mach-O code directory hash size")?;
Ok(self
.data
.read_bytes_at(offset as usize, self.header.hash_size as usize)
.read_error("Invalid Mach-O code directory hash offset")?
.0)
}
}