Source code

Revision control

Copy as Markdown

Other Tools

use scroll::ctx::{self, SizeWith};
use scroll::{Pread, Pwrite};
use log::{debug, warn};
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt;
use core::ops::{Deref, DerefMut};
use crate::container;
use crate::error;
use crate::mach::constants::{SECTION_TYPE, S_GB_ZEROFILL, S_THREAD_LOCAL_ZEROFILL, S_ZEROFILL};
use crate::mach::load_command::{
Section32, Section64, SegmentCommand32, SegmentCommand64, LC_SEGMENT, LC_SEGMENT_64,
SIZEOF_SECTION_32, SIZEOF_SECTION_64, SIZEOF_SEGMENT_COMMAND_32, SIZEOF_SEGMENT_COMMAND_64,
};
use crate::mach::relocation::RelocationInfo;
pub struct RelocationIterator<'a> {
data: &'a [u8],
nrelocs: usize,
offset: usize,
count: usize,
ctx: scroll::Endian,
}
impl<'a> Iterator for RelocationIterator<'a> {
type Item = error::Result<RelocationInfo>;
fn next(&mut self) -> Option<Self::Item> {
if self.count >= self.nrelocs {
None
} else {
self.count += 1;
match self.data.gread_with(&mut self.offset, self.ctx) {
Ok(res) => Some(Ok(res)),
Err(e) => Some(Err(e.into())),
}
}
}
}
/// Generalized 32/64 bit Section
#[derive(Default)]
pub struct Section {
/// name of this section
pub sectname: [u8; 16],
/// segment this section goes in
pub segname: [u8; 16],
/// memory address of this section
pub addr: u64,
/// size in bytes of this section
pub size: u64,
/// file offset of this section
pub offset: u32,
/// section alignment (power of 2)
pub align: u32,
/// file offset of relocation entries
pub reloff: u32,
/// number of relocation entries
pub nreloc: u32,
/// flags (section type and attributes
pub flags: u32,
}
impl Section {
/// The name of this section
pub fn name(&self) -> error::Result<&str> {
Ok(self.sectname.pread::<&str>(0)?)
}
/// The containing segment's name
pub fn segname(&self) -> error::Result<&str> {
Ok(self.segname.pread::<&str>(0)?)
}
/// Iterate this sections relocations given `data`; `data` must be the original binary
pub fn iter_relocations<'b>(
&self,
data: &'b [u8],
ctx: container::Ctx,
) -> RelocationIterator<'b> {
let offset = self.reloff as usize;
debug!(
"Relocations for {} starting at offset: {:#x}",
self.name().unwrap_or("BAD_SECTION_NAME"),
offset
);
RelocationIterator {
offset,
nrelocs: self.nreloc as usize,
count: 0,
data,
ctx: ctx.le,
}
}
}
impl From<Section> for Section64 {
fn from(section: Section) -> Self {
Section64 {
sectname: section.sectname,
segname: section.segname,
addr: section.addr as u64,
size: section.size as u64,
offset: section.offset,
align: section.align,
reloff: section.reloff,
nreloc: section.nreloc,
flags: section.flags,
reserved1: 0,
reserved2: 0,
reserved3: 0,
}
}
}
impl From<Section> for Section32 {
fn from(section: Section) -> Self {
Section32 {
sectname: section.sectname,
segname: section.segname,
addr: section.addr as u32,
size: section.size as u32,
offset: section.offset,
align: section.align,
reloff: section.reloff,
nreloc: section.nreloc,
flags: section.flags,
reserved1: 0,
reserved2: 0,
}
}
}
impl fmt::Debug for Section {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Section")
.field("sectname", &self.name().unwrap())
.field("segname", &self.segname().unwrap())
.field("addr", &self.addr)
.field("size", &self.size)
.field("offset", &self.offset)
.field("align", &self.align)
.field("reloff", &self.reloff)
.field("nreloc", &self.nreloc)
.field("flags", &self.flags)
.finish()
}
}
impl From<Section32> for Section {
fn from(section: Section32) -> Self {
Section {
sectname: section.sectname,
segname: section.segname,
addr: u64::from(section.addr),
size: u64::from(section.size),
offset: section.offset,
align: section.align,
reloff: section.reloff,
nreloc: section.nreloc,
flags: section.flags,
}
}
}
impl From<Section64> for Section {
fn from(section: Section64) -> Self {
Section {
sectname: section.sectname,
segname: section.segname,
addr: section.addr,
size: section.size,
offset: section.offset,
align: section.align,
reloff: section.reloff,
nreloc: section.nreloc,
flags: section.flags,
}
}
}
impl<'a> ctx::TryFromCtx<'a, container::Ctx> for Section {
type Error = crate::error::Error;
fn try_from_ctx(bytes: &'a [u8], ctx: container::Ctx) -> Result<(Self, usize), Self::Error> {
match ctx.container {
container::Container::Little => {
let section = Section::from(bytes.pread_with::<Section32>(0, ctx.le)?);
Ok((section, SIZEOF_SECTION_32))
}
container::Container::Big => {
let section = Section::from(bytes.pread_with::<Section64>(0, ctx.le)?);
Ok((section, SIZEOF_SECTION_64))
}
}
}
}
impl ctx::SizeWith<container::Ctx> for Section {
fn size_with(ctx: &container::Ctx) -> usize {
match ctx.container {
container::Container::Little => SIZEOF_SECTION_32,
container::Container::Big => SIZEOF_SECTION_64,
}
}
}
impl ctx::TryIntoCtx<container::Ctx> for Section {
type Error = crate::error::Error;
fn try_into_ctx(self, bytes: &mut [u8], ctx: container::Ctx) -> Result<usize, Self::Error> {
if ctx.is_big() {
bytes.pwrite_with::<Section64>(self.into(), 0, ctx.le)?;
} else {
bytes.pwrite_with::<Section32>(self.into(), 0, ctx.le)?;
}
Ok(Self::size_with(&ctx))
}
}
impl ctx::IntoCtx<container::Ctx> for Section {
fn into_ctx(self, bytes: &mut [u8], ctx: container::Ctx) {
bytes.pwrite_with(self, 0, ctx).unwrap();
}
}
pub struct SectionIterator<'a> {
data: &'a [u8],
count: usize,
offset: usize,
idx: usize,
ctx: container::Ctx,
}
pub type SectionData<'a> = &'a [u8];
impl<'a> ::core::iter::ExactSizeIterator for SectionIterator<'a> {
fn len(&self) -> usize {
self.count
}
}
impl<'a> Iterator for SectionIterator<'a> {
type Item = error::Result<(Section, SectionData<'a>)>;
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.count {
None
} else {
self.idx += 1;
match self.data.gread_with::<Section>(&mut self.offset, self.ctx) {
Ok(section) => {
let section_type = section.flags & SECTION_TYPE;
let data = if section_type == S_ZEROFILL
|| section_type == S_GB_ZEROFILL
|| section_type == S_THREAD_LOCAL_ZEROFILL
{
&[]
} else {
// it's not uncommon to encounter macho files where files are
// truncated but the sections are still remaining in the header.
// Because of this we want to not panic here but instead just
// slice down to a empty data slice. This way only if code
// actually needs to access those sections it will fall over.
self.data
.get(section.offset as usize..)
.unwrap_or_else(|| {
warn!(
"section #{} offset {} out of bounds",
self.idx, section.offset
);
&[]
})
.get(..section.size as usize)
.unwrap_or_else(|| {
warn!("section #{} size {} out of bounds", self.idx, section.size);
&[]
})
};
Some(Ok((section, data)))
}
Err(e) => Some(Err(e)),
}
}
}
}
impl<'a, 'b> IntoIterator for &'b Segment<'a> {
type Item = error::Result<(Section, SectionData<'a>)>;
type IntoIter = SectionIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
SectionIterator {
data: self.raw_data,
count: self.nsects as usize,
offset: self.offset + Segment::size_with(&self.ctx),
idx: 0,
ctx: self.ctx,
}
}
}
/// Generalized 32/64 bit Segment Command
pub struct Segment<'a> {
pub cmd: u32,
pub cmdsize: u32,
pub segname: [u8; 16],
pub vmaddr: u64,
pub vmsize: u64,
pub fileoff: u64,
pub filesize: u64,
pub maxprot: u32,
pub initprot: u32,
pub nsects: u32,
pub flags: u32,
pub data: &'a [u8],
offset: usize,
raw_data: &'a [u8],
ctx: container::Ctx,
}
impl<'a> From<Segment<'a>> for SegmentCommand64 {
fn from(segment: Segment<'a>) -> Self {
SegmentCommand64 {
cmd: segment.cmd,
cmdsize: segment.cmdsize,
segname: segment.segname,
vmaddr: segment.vmaddr as u64,
vmsize: segment.vmsize as u64,
fileoff: segment.fileoff as u64,
filesize: segment.filesize as u64,
maxprot: segment.maxprot,
initprot: segment.initprot,
nsects: segment.nsects,
flags: segment.flags,
}
}
}
impl<'a> From<Segment<'a>> for SegmentCommand32 {
fn from(segment: Segment<'a>) -> Self {
SegmentCommand32 {
cmd: segment.cmd,
cmdsize: segment.cmdsize,
segname: segment.segname,
vmaddr: segment.vmaddr as u32,
vmsize: segment.vmsize as u32,
fileoff: segment.fileoff as u32,
filesize: segment.filesize as u32,
maxprot: segment.maxprot,
initprot: segment.initprot,
nsects: segment.nsects,
flags: segment.flags,
}
}
}
impl<'a> fmt::Debug for Segment<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Segment")
.field("cmd", &self.cmd)
.field("cmdsize", &self.cmdsize)
.field("segname", &self.segname.pread::<&str>(0).unwrap())
.field("vmaddr", &self.vmaddr)
.field("vmsize", &self.vmsize)
.field("fileoff", &self.fileoff)
.field("filesize", &self.filesize)
.field("maxprot", &self.maxprot)
.field("initprot", &self.initprot)
.field("nsects", &self.nsects)
.field("flags", &self.flags)
.field("data", &self.data.len())
.field(
"sections()",
&self.sections().map(|sections| {
sections
.into_iter()
.map(|(section, _)| section)
.collect::<Vec<_>>()
}),
)
.finish()
}
}
impl<'a> ctx::SizeWith<container::Ctx> for Segment<'a> {
fn size_with(ctx: &container::Ctx) -> usize {
match ctx.container {
container::Container::Little => SIZEOF_SEGMENT_COMMAND_32,
container::Container::Big => SIZEOF_SEGMENT_COMMAND_64,
}
}
}
impl<'a> ctx::TryIntoCtx<container::Ctx> for Segment<'a> {
type Error = crate::error::Error;
fn try_into_ctx(self, bytes: &mut [u8], ctx: container::Ctx) -> Result<usize, Self::Error> {
let segment_size = Self::size_with(&ctx);
// should be able to write the section data inline after this, but not working at the moment
//let section_size = bytes.pwrite(data, segment_size)?;
//debug!("Segment size: {} raw section data size: {}", segment_size, data.len());
if ctx.is_big() {
bytes.pwrite_with::<SegmentCommand64>(self.into(), 0, ctx.le)?;
} else {
bytes.pwrite_with::<SegmentCommand32>(self.into(), 0, ctx.le)?;
}
//debug!("Section size: {}", section_size);
Ok(segment_size)
}
}
impl<'a> ctx::IntoCtx<container::Ctx> for Segment<'a> {
fn into_ctx(self, bytes: &mut [u8], ctx: container::Ctx) {
bytes.pwrite_with(self, 0, ctx).unwrap();
}
}
/// Read data that belongs to a segment if the offset is within the boundaries of bytes.
fn segment_data(bytes: &[u8], fileoff: u64, filesize: u64) -> Result<&[u8], error::Error> {
let data: &[u8] = if filesize != 0 {
bytes.pread_with(fileoff as usize, filesize as usize)?
} else {
&[]
};
Ok(data)
}
impl<'a> Segment<'a> {
/// Create a new, blank segment, with cmd either `LC_SEGMENT_64`, or `LC_SEGMENT`, depending on `ctx`.
/// **NB** You are responsible for providing a correctly marshalled byte array as the sections. You should not use this for anything other than writing.
pub fn new(ctx: container::Ctx, sections: &'a [u8]) -> Self {
Segment {
cmd: if ctx.is_big() {
LC_SEGMENT_64
} else {
LC_SEGMENT
},
cmdsize: (Self::size_with(&ctx) + sections.len()) as u32,
segname: [0; 16],
vmaddr: 0,
vmsize: 0,
fileoff: 0,
filesize: 0,
maxprot: 0,
initprot: 0,
nsects: 0,
flags: 0,
data: sections,
offset: 0,
raw_data: &[],
ctx,
}
}
/// Get the name of this segment
pub fn name(&self) -> error::Result<&str> {
Ok(self.segname.pread::<&str>(0)?)
}
/// Get the sections from this segment, erroring if any section couldn't be retrieved
pub fn sections(&self) -> error::Result<Vec<(Section, SectionData<'a>)>> {
let mut sections = Vec::new();
for section in self.into_iter() {
sections.push(section?);
}
Ok(sections)
}
/// Convert the raw C 32-bit segment command to a generalized version
pub fn from_32(
bytes: &'a [u8],
segment: &SegmentCommand32,
offset: usize,
ctx: container::Ctx,
) -> Result<Self, error::Error> {
Ok(Segment {
cmd: segment.cmd,
cmdsize: segment.cmdsize,
segname: segment.segname,
vmaddr: u64::from(segment.vmaddr),
vmsize: u64::from(segment.vmsize),
fileoff: u64::from(segment.fileoff),
filesize: u64::from(segment.filesize),
maxprot: segment.maxprot,
initprot: segment.initprot,
nsects: segment.nsects,
flags: segment.flags,
data: segment_data(
bytes,
u64::from(segment.fileoff),
u64::from(segment.filesize),
)?,
offset,
raw_data: bytes,
ctx,
})
}
/// Convert the raw C 64-bit segment command to a generalized version
pub fn from_64(
bytes: &'a [u8],
segment: &SegmentCommand64,
offset: usize,
ctx: container::Ctx,
) -> Result<Self, error::Error> {
Ok(Segment {
cmd: segment.cmd,
cmdsize: segment.cmdsize,
segname: segment.segname,
vmaddr: segment.vmaddr,
vmsize: segment.vmsize,
fileoff: segment.fileoff,
filesize: segment.filesize,
maxprot: segment.maxprot,
initprot: segment.initprot,
nsects: segment.nsects,
flags: segment.flags,
data: segment_data(bytes, segment.fileoff, segment.filesize)?,
offset,
raw_data: bytes,
ctx,
})
}
}
#[derive(Debug, Default)]
/// An opaque 32/64-bit container for Mach-o segments
pub struct Segments<'a> {
segments: Vec<Segment<'a>>,
}
impl<'a> Deref for Segments<'a> {
type Target = Vec<Segment<'a>>;
fn deref(&self) -> &Self::Target {
&self.segments
}
}
impl<'a> DerefMut for Segments<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.segments
}
}
impl<'a, 'b> IntoIterator for &'b Segments<'a> {
type Item = &'b Segment<'a>;
type IntoIter = ::core::slice::Iter<'b, Segment<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.segments.iter()
}
}
impl<'a> Segments<'a> {
/// Construct a new generalized segment container from this `ctx`
pub fn new(_ctx: container::Ctx) -> Self {
Segments {
segments: Vec::new(),
}
}
/// Get every section from every segment
// thanks to SpaceManic for figuring out the 'b lifetimes here :)
pub fn sections<'b>(&'b self) -> Box<dyn Iterator<Item = SectionIterator<'a>> + 'b> {
Box::new(self.segments.iter().map(|segment| segment.into_iter()))
}
}