basic macho parsing

This commit is contained in:
nganhkhoa 2021-08-26 04:47:32 +00:00
parent 4f09f3b8aa
commit 65278fbb33
2 changed files with 333 additions and 0 deletions

2
osx/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
mod macho;
pub use macho::Macho;

331
osx/src/macho.rs Normal file
View File

@ -0,0 +1,331 @@
use std::io::{Read, Write, Seek, SeekFrom};
use std::fmt;
use std::error::Error;
use byteorder::{ByteOrder, LittleEndian, BigEndian, ReadBytesExt};
trait ReadString: Read {
fn read_utf8(self: &mut Self, len: usize) -> Result<String, Box<dyn Error>> {
let mut buf = vec![0u8; len];
self.read_exact(&mut buf)?;
Ok(String::from_utf8(buf.split(|&b| b == 0).next().unwrap().to_vec())?)
}
}
impl<R: Read> ReadString for R {}
const MH_MAGIC: u32 = 0xfeedface;
const MH_CIGAM: u32 = MH_MAGIC.swap_bytes();
const MH_MAGIC_64: u32 = 0xfeedfacf;
const MH_CIGAM_64: u32 = MH_MAGIC_64.swap_bytes();
const LC_SEGMENT: u32 = 0x1;
const LC_SEGMENT_64: u32 = 0x19;
const LC_CODE_SIGNATURE: u32 = 0x1d;
pub struct Header {
magic: u32,
cputype: u32,
cpusubtype: u32,
filetype: u32,
ncmds: u32,
sizeofcmds: u32,
flags: u32,
reserved: u32,
}
impl Header {
fn parse<O: ByteOrder, R: Read>(magic: u32, cursor: &mut R) -> Option<Header> {
let cputype: u32 = cursor.read_u32::<O>().ok()?;
let cpusubtype: u32 = cursor.read_u32::<O>().ok()?;
let filetype: u32 = cursor.read_u32::<O>().ok()?;
let ncmds: u32 = cursor.read_u32::<O>().ok()?;
let sizeofcmds: u32 = cursor.read_u32::<O>().ok()?;
let flags: u32 = cursor.read_u32::<O>().ok()?;
let reserved: u32 = {
if magic == MH_MAGIC_64 || magic == MH_CIGAM_64 {
cursor.read_u32::<O>().ok()?
} else {
0
}
};
Some(Header {
magic,
cputype,
cpusubtype,
filetype,
ncmds,
sizeofcmds,
flags,
reserved,
})
}
}
pub struct Section {
sectname: String,
segname: String,
addr: u64,
size: u64,
offset: u32,
align: u32,
reloff: u32,
nreloc: u32,
flags: u32,
reserved1: u32,
reserved2: u32,
reserved3: u32,
}
impl Section {
fn parse_32<O: ByteOrder, R: Read>(cursor: &mut R) -> Option<Section> {
let sectname = cursor.read_utf8(16).ok()?;
let segname = cursor.read_utf8(16).ok()?;
let addr = cursor.read_u32::<O>().ok()? as u64;
let size = cursor.read_u32::<O>().ok()? as u64;
let offset = cursor.read_u32::<O>().ok()?;
let align = cursor.read_u32::<O>().ok()?;
let reloff = cursor.read_u32::<O>().ok()?;
let nreloc = cursor.read_u32::<O>().ok()?;
let flags = cursor.read_u32::<O>().ok()?;
let reserved1 = cursor.read_u32::<O>().ok()?;
let reserved2 = cursor.read_u32::<O>().ok()?;
Some(Section {
sectname,
segname,
addr,
size,
offset,
align,
reloff,
nreloc,
flags,
reserved1,
reserved2,
reserved3: 0,
})
}
fn parse_64<O: ByteOrder, R: Read>(cursor: &mut R) -> Option<Section> {
let sectname = cursor.read_utf8(16).ok()?;
let segname = cursor.read_utf8(16).ok()?;
let addr = cursor.read_u64::<O>().ok()? as u64;
let size = cursor.read_u64::<O>().ok()? as u64;
let offset = cursor.read_u32::<O>().ok()?;
let align = cursor.read_u32::<O>().ok()?;
let reloff = cursor.read_u32::<O>().ok()?;
let nreloc = cursor.read_u32::<O>().ok()?;
let flags = cursor.read_u32::<O>().ok()?;
let reserved1 = cursor.read_u32::<O>().ok()?;
let reserved2 = cursor.read_u32::<O>().ok()?;
let reserved3 = cursor.read_u32::<O>().ok()?;
Some(Section {
sectname,
segname,
addr,
size,
offset,
align,
reloff,
nreloc,
flags,
reserved1,
reserved2,
reserved3,
})
}
}
pub struct Segment {
segname: String,
vmaddr: u64,
vmsize: u64,
fileoff: u64,
filesize: u64,
maxprot: u32,
initprot: u32,
flags: u32,
sections: Vec<Section>,
}
pub struct Linkedit {
pub dataoff: u32,
pub datasize: u32,
}
pub enum LoadCommand {
Segment(Segment),
Codesignature(Linkedit),
Cmd(u32, u32),
}
impl LoadCommand {
fn parse<O: ByteOrder, R: Read>(cursor: &mut R) -> Option<LoadCommand> {
let cmd = cursor.read_u32::<O>().ok()?;
let cmdsize = cursor.read_u32::<O>().ok()?;
if cmdsize <= 8 {
// impossible
return None
}
match cmd {
LC_SEGMENT => {
let segname = cursor.read_utf8(16).ok()?;
let vmaddr = cursor.read_u32::<O>().ok()? as u64;
let vmsize = cursor.read_u32::<O>().ok()? as u64;
let fileoff = cursor.read_u32::<O>().ok()? as u64;
let filesize = cursor.read_u32::<O>().ok()? as u64;
let maxprot = cursor.read_u32::<O>().ok()?;
let initprot = cursor.read_u32::<O>().ok()?;
let nsects = cursor.read_u32::<O>().ok()?;
let flags = cursor.read_u32::<O>().ok()?;
let sections = std::iter::repeat_with(|| Section::parse_32::<O, R>(cursor))
.take_while(|x| x.is_some())
.take(nsects as usize)
.filter_map(|x| x)
.collect::<Vec<Section>>();
Some(LoadCommand::Segment(Segment {
segname,
vmaddr,
vmsize,
fileoff,
filesize,
maxprot,
initprot,
flags,
sections,
}))
},
LC_SEGMENT_64 => {
let segname = cursor.read_utf8(16).ok()?;
let vmaddr = cursor.read_u64::<O>().ok()?;
let vmsize = cursor.read_u64::<O>().ok()?;
let fileoff = cursor.read_u64::<O>().ok()?;
let filesize = cursor.read_u64::<O>().ok()?;
let maxprot = cursor.read_u32::<O>().ok()?;
let initprot = cursor.read_u32::<O>().ok()?;
let nsects = cursor.read_u32::<O>().ok()?;
let flags = cursor.read_u32::<O>().ok()?;
let sections = std::iter::repeat_with(|| Section::parse_64::<O, R>(cursor))
.take_while(|x| x.is_some())
.take(nsects as usize)
.filter_map(|x| x)
.collect::<Vec<Section>>();
Some(LoadCommand::Segment(Segment {
segname,
vmaddr,
vmsize,
fileoff,
filesize,
maxprot,
initprot,
flags,
sections,
}))
},
LC_CODE_SIGNATURE => {
let dataoff = cursor.read_u32::<O>().ok()?;
let datasize = cursor.read_u32::<O>().ok()?;
Some(LoadCommand::Codesignature(Linkedit {
dataoff, datasize
}))
},
_ => {
let mut buf = vec![0u8; cmdsize as usize - 4*2];
cursor.read_exact(&mut buf).ok()?;
Some(LoadCommand::Cmd(cmd, cmdsize))
}
}
}
}
impl fmt::Display for LoadCommand {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LoadCommand::Segment(segment) => {
writeln!(f, "{}", segment.segname)?;
segment.sections.iter().for_each(|section| {
write!(f, " {}.{}\n", section.segname, section.sectname).ok();
});
Ok(())
},
LoadCommand::Codesignature(linkedit) => {
write!(f, "Codesignature(dataoff=0x{:x}, datasize={})", linkedit.dataoff, linkedit.datasize)
},
LoadCommand::Cmd(cmd, cmdsize) => {
write!(f, "cmd=0x{:x} cmdsize={}", cmd, cmdsize)
}
}
}
}
pub struct Macho {
pub header: Header,
pub commands: Vec<LoadCommand>,
}
impl Macho {
pub fn codesignature(self: &Self) -> Option<&Linkedit> {
self.commands
.iter()
.find_map(|cmd| match cmd {
LoadCommand::Codesignature(linkedit) => Some(linkedit),
_ => None
})
}
}
#[derive(Debug)]
pub struct MachoParseError {
message: String
}
impl fmt::Display for MachoParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MachoParseError(message={})", self.message)
}
}
impl Error for MachoParseError {}
impl MachoParseError {
fn new(msg: &str) -> Box<MachoParseError> {
Box::new(MachoParseError {
message: String::from(msg),
})
}
}
impl Macho {
pub fn from<R: Read>(cursor: &mut R) -> Result<Macho, Box<MachoParseError>> {
let magic = cursor.read_u32::<LittleEndian>()
.or(Err(MachoParseError::new("Cannot read magic")))?;
if magic == MH_MAGIC || magic == MH_MAGIC_64 {
Self::parse::<LittleEndian, R>(magic, cursor)
} else {
Self::parse::<BigEndian, R>(magic, cursor)
}
}
pub fn parse<O: ByteOrder, R: Read>(magic: u32, cursor: &mut R) -> Result<Macho, Box<MachoParseError>> {
let header = Header::parse::<O, R>(magic, cursor)
.ok_or(MachoParseError::new("Cannot parse macho header"))?;
let commands = std::iter::repeat_with(|| LoadCommand::parse::<O, R>(cursor))
.take_while(|x| x.is_some())
.take(header.ncmds as usize)
.filter_map(|x| x)
.collect::<Vec<LoadCommand>>();
Ok(Macho {
header,
commands
})
}
}