Skip to content

Commit

Permalink
mach: support for lossy Macho parsing (#386)
Browse files Browse the repository at this point in the history
  • Loading branch information
h33p authored Apr 1, 2024
1 parent 40d5816 commit 661180f
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 20 deletions.
76 changes: 59 additions & 17 deletions src/mach/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,17 @@ impl<'a> MachO<'a> {
}
}
/// Parses the Mach-o binary from `bytes` at `offset`
pub fn parse(bytes: &'a [u8], mut offset: usize) -> error::Result<MachO<'a>> {
pub fn parse(bytes: &'a [u8], offset: usize) -> error::Result<MachO<'a>> {
Self::parse_impl(bytes, offset, false)
}

/// Parses the Mach-o binary from `bytes` at `offset` in lossy mode
pub fn parse_lossy(bytes: &'a [u8], offset: usize) -> error::Result<MachO<'a>> {
Self::parse_impl(bytes, offset, true)
}

/// Parses the Mach-o binary from `bytes` at `offset` in `lossy` mode
fn parse_impl(bytes: &'a [u8], mut offset: usize, lossy: bool) -> error::Result<MachO<'a>> {
let (magic, maybe_ctx) = parse_magic_and_ctx(bytes, offset)?;
let ctx = if let Some(ctx) = maybe_ctx {
ctx
Expand Down Expand Up @@ -183,27 +193,42 @@ impl<'a> MachO<'a> {
let cmd = load_command::LoadCommand::parse(bytes, offset, ctx.le)?;
debug!("{} - {:?}", i, cmd);
match cmd.command {
load_command::CommandVariant::Segment32(command) => {
// FIXME: we may want to be less strict about failure here, and just return an empty segment to allow parsing to continue?
segments.push(segment::Segment::from_32(bytes, &command, cmd.offset, ctx)?)
}
load_command::CommandVariant::Segment64(command) => {
segments.push(segment::Segment::from_64(bytes, &command, cmd.offset, ctx)?)
}
load_command::CommandVariant::Segment32(command) => segments.push(
segment::Segment::from_32_impl(bytes, &command, cmd.offset, ctx, lossy)?,
),
load_command::CommandVariant::Segment64(command) => segments.push(
segment::Segment::from_64_impl(bytes, &command, cmd.offset, ctx, lossy)?,
),
load_command::CommandVariant::Symtab(command) => {
symbols = Some(symbols::Symbols::parse(bytes, &command, ctx)?);
match symbols::Symbols::parse(bytes, &command, ctx) {
Ok(s) => symbols = Some(s),
Err(e) if lossy => {
debug!("CommandVariant::Symtab failed: {e}");
}
Err(e) => return Err(e),
}
}
load_command::CommandVariant::LoadDylib(command)
| load_command::CommandVariant::LoadUpwardDylib(command)
| load_command::CommandVariant::ReexportDylib(command)
| load_command::CommandVariant::LoadWeakDylib(command)
| load_command::CommandVariant::LazyLoadDylib(command) => {
let lib = bytes.pread::<&str>(cmd.offset + command.dylib.name as usize)?;
libs.push(lib);
match bytes.pread::<&str>(cmd.offset + command.dylib.name as usize) {
Ok(lib) => libs.push(lib),
Err(e) if lossy => {
debug!("CommandVariant::Load/Reexport Dylib failed: {e}");
}
Err(e) => return Err(e.into()),
}
}
load_command::CommandVariant::Rpath(command) => {
let rpath = bytes.pread::<&str>(cmd.offset + command.path as usize)?;
rpaths.push(rpath);
match bytes.pread::<&str>(cmd.offset + command.path as usize) {
Ok(rpath) => rpaths.push(rpath),
Err(e) if lossy => {
debug!("CommandVariant::Rpath failed: {e}");
}
Err(e) => return Err(e.into()),
}
}
load_command::CommandVariant::DyldInfo(command)
| load_command::CommandVariant::DyldInfoOnly(command) => {
Expand All @@ -229,9 +254,16 @@ impl<'a> MachO<'a> {
}
}
load_command::CommandVariant::IdDylib(command) => {
let id = bytes.pread::<&str>(cmd.offset + command.dylib.name as usize)?;
libs[0] = id;
name = Some(id);
match bytes.pread::<&str>(cmd.offset + command.dylib.name as usize) {
Ok(id) => {
libs[0] = id;
name = Some(id);
}
Err(e) if lossy => {
debug!("CommandVariant::IdDylib failed: {e}");
}
Err(e) => return Err(e.into()),
}
}
_ => (),
}
Expand Down Expand Up @@ -502,6 +534,16 @@ pub enum Mach<'a> {
impl<'a> Mach<'a> {
/// Parse from `bytes` either a multi-arch binary or a regular mach-o binary
pub fn parse(bytes: &'a [u8]) -> error::Result<Self> {
Self::parse_impl(bytes, false)
}

/// Parse from `bytes` either a multi-arch binary or a regular mach-o binary in lossy mode
pub fn parse_lossy(bytes: &'a [u8]) -> error::Result<Self> {
Self::parse_impl(bytes, true)
}

/// Parse from `bytes` either a multi-arch binary or a regular mach-o binary
fn parse_impl(bytes: &'a [u8], lossy: bool) -> error::Result<Self> {
let size = bytes.len();
if size < 4 {
let error = error::Error::Malformed("size is smaller than a magical number".into());
Expand All @@ -515,7 +557,7 @@ impl<'a> Mach<'a> {
}
// we might be a regular binary
_ => {
let binary = MachO::parse(bytes, 0)?;
let binary = MachO::parse_impl(bytes, 0, lossy)?;
Ok(Mach::Binary(binary))
}
}
Expand Down
55 changes: 52 additions & 3 deletions src/mach/segment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,26 @@ impl<'a> Segment<'a> {
segment: &SegmentCommand32,
offset: usize,
ctx: container::Ctx,
) -> Result<Self, error::Error> {
Self::from_32_impl(bytes, segment, offset, ctx, false)
}

/// Convert the raw C 32-bit segment command to a generalized version
pub fn from_32_lossy(
bytes: &'a [u8],
segment: &SegmentCommand32,
offset: usize,
ctx: container::Ctx,
) -> Result<Self, error::Error> {
Self::from_32_impl(bytes, segment, offset, ctx, true)
}

pub(crate) fn from_32_impl(
bytes: &'a [u8],
segment: &SegmentCommand32,
offset: usize,
ctx: container::Ctx,
lossy: bool,
) -> Result<Self, error::Error> {
Ok(Segment {
cmd: segment.cmd,
Expand All @@ -479,22 +499,47 @@ impl<'a> Segment<'a> {
initprot: segment.initprot,
nsects: segment.nsects,
flags: segment.flags,
data: segment_data(
data: match segment_data(
bytes,
u64::from(segment.fileoff),
u64::from(segment.filesize),
)?,
) {
Ok(v) => v,
Err(_) if lossy => &[],
Err(e) => return Err(e),
},
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> {
Self::from_64_impl(bytes, segment, offset, ctx, false)
}

/// Convert the raw C 64-bit segment command to a generalized version
pub fn from_64_lossy(
bytes: &'a [u8],
segment: &SegmentCommand64,
offset: usize,
ctx: container::Ctx,
) -> Result<Self, error::Error> {
Self::from_64_impl(bytes, segment, offset, ctx, true)
}

pub(crate) fn from_64_impl(
bytes: &'a [u8],
segment: &SegmentCommand64,
offset: usize,
ctx: container::Ctx,
lossy: bool,
) -> Result<Self, error::Error> {
Ok(Segment {
cmd: segment.cmd,
Expand All @@ -508,7 +553,11 @@ impl<'a> Segment<'a> {
initprot: segment.initprot,
nsects: segment.nsects,
flags: segment.flags,
data: segment_data(bytes, segment.fileoff, segment.filesize)?,
data: match segment_data(bytes, segment.fileoff, segment.filesize) {
Ok(v) => v,
Err(_) if lossy => &[],
Err(e) => return Err(e),
},
offset,
raw_data: bytes,
ctx,
Expand Down

0 comments on commit 661180f

Please sign in to comment.