use crate::{Error, Result, SectionMap};
use goblin::mach::{Mach, MachO};
const CPU_TYPE_X86: u32 = 7;
const CPU_TYPE_X86_64: u32 = 7 | 0x0100_0000;
pub fn extract_intel(binary: &[u8]) -> Result<MachO<'_>> {
let mach = goblin::mach::Mach::parse(binary)?;
match mach {
Mach::Binary(m) => {
let ct = m.header.cputype();
if ct == CPU_TYPE_X86_64 || ct == CPU_TYPE_X86 {
Ok(m)
} else {
Err(Error::UnsupportedFormatError)
}
}
Mach::Fat(fat) => {
for want in [CPU_TYPE_X86_64, CPU_TYPE_X86] {
for (i, arch) in fat.iter_arches().enumerate() {
let arch = arch?;
if arch.cputype == want
&& let Ok(goblin::mach::SingleArch::MachO(m)) = fat.get(i)
{
return Ok(m);
}
}
}
Err(Error::UnsupportedFormatError)
}
}
}
#[must_use]
pub fn get_base_address(mach: &MachO) -> u64 {
for seg in &mach.segments {
let name = std::str::from_utf8(&seg.segname).unwrap_or("");
let name = name.trim_end_matches('\0');
if name == "__PAGEZERO" {
continue;
}
return seg.vmaddr;
}
0
}
#[must_use]
pub fn get_bitness(mach: &MachO) -> u32 {
if mach.is_64 { 64 } else { 32 }
}
const VM_PROT_EXECUTE: u32 = 0x04;
const S_ATTR_PURE_INSTRUCTIONS: u32 = 0x8000_0000;
const S_ATTR_SOME_INSTRUCTIONS: u32 = 0x0000_0400;
#[must_use]
pub fn get_code_areas(mach: &MachO) -> Vec<(u64, u64)> {
let mut areas = Vec::new();
for seg in &mach.segments {
let seg_name = std::str::from_utf8(&seg.segname).unwrap_or("");
let seg_name = seg_name.trim_end_matches('\0');
let is_text_seg = seg_name.starts_with("__TEXT");
let is_exec_seg = seg.initprot & VM_PROT_EXECUTE != 0;
if !(is_text_seg || is_exec_seg) {
continue;
}
let mut added_any = false;
if let Ok(sections) = seg.sections() {
for (sect, _data) in sections {
let sect_name = std::str::from_utf8(§.sectname).unwrap_or("");
let sect_name = sect_name.trim_end_matches('\0');
let has_instr_flag =
sect.flags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS) != 0;
let is_code_section = matches!(
sect_name,
"__text" | "__stubs" | "__stub_helper" | "__symbol_stub"
);
if !(has_instr_flag || is_code_section) {
continue;
}
if let Some(end) = sect.addr.checked_add(sect.size) {
areas.push((sect.addr, end));
added_any = true;
}
}
}
if !added_any && let Some(end) = seg.vmaddr.checked_add(seg.vmsize) {
areas.push((seg.vmaddr, end));
}
}
areas
}
pub fn map_binary(binary: &[u8], base_addr: u64) -> Result<Vec<SectionMap>> {
let _ = base_addr;
let mach = extract_intel(binary)?;
let mut section_maps = Vec::with_capacity(mach.segments.len());
for seg in &mach.segments {
let name = std::str::from_utf8(&seg.segname).unwrap_or("");
if name.trim_end_matches('\0') == "__PAGEZERO" {
continue;
}
if seg.vmsize == 0 {
continue;
}
let Some(va_end) = seg.vmaddr.checked_add(seg.vmsize) else {
continue;
};
let Ok(file_offset) = usize::try_from(seg.fileoff) else {
continue;
};
let Ok(declared_size) = usize::try_from(seg.filesize) else {
continue;
};
let file_size = match file_offset.checked_add(declared_size) {
Some(end) if end <= binary.len() => declared_size,
_ => binary.len().saturating_sub(file_offset),
};
section_maps.push(SectionMap {
va_start: seg.vmaddr,
va_end,
file_offset,
file_size,
});
}
Ok(section_maps)
}
#[must_use]
pub fn get_sections(mach: &MachO) -> Vec<(String, u64, usize)> {
let mut out = Vec::new();
for seg in &mach.segments {
let seg_name = std::str::from_utf8(&seg.segname).unwrap_or("");
if seg_name.trim_end_matches('\0') == "__PAGEZERO" {
continue;
}
if let Ok(sections) = seg.sections() {
for (sect, _data) in sections {
let sect_name = std::str::from_utf8(§.sectname).unwrap_or("");
let combined = format!(
"{},{}",
seg_name.trim_end_matches('\0'),
sect_name.trim_end_matches('\0')
);
if let Ok(size) = usize::try_from(sect.size) {
out.push((combined, sect.addr, size));
}
}
}
}
out
}
#[must_use]
pub fn get_imports(mach: &MachO) -> Vec<(String, String, usize)> {
let Ok(imports) = mach.imports() else {
return Vec::new();
};
imports
.iter()
.filter_map(|i| {
usize::try_from(i.offset)
.ok()
.map(|off| (i.dylib.to_string(), i.name.to_string(), off))
})
.collect()
}
#[must_use]
pub fn get_exports(mach: &MachO) -> Vec<(String, usize, Option<String>)> {
let Ok(exports) = mach.exports() else {
return Vec::new();
};
exports
.iter()
.filter_map(|e| {
usize::try_from(e.offset)
.ok()
.map(|off| (e.name.clone(), off, None))
})
.collect()
}
#[must_use]
pub fn get_entry_point(mach: &MachO, base_addr: u64) -> u64 {
match mach.entry {
0 => 0,
off => base_addr.saturating_add(off),
}
}