1#![allow(clippy::cast_possible_truncation)]
20
21use std::ops::Range;
22
23pub const MH_MAGIC_64: u32 = 0xfeed_facf;
25
26pub const MH_MAGIC: u32 = 0xfeed_face;
28
29pub const FAT_MAGIC: u32 = 0xcafe_babe;
32pub const FAT_MAGIC_64: u32 = 0xcafe_babf;
33
34pub const CPU_TYPE_X86_64: u32 = 0x0100_0007;
36pub const CPU_TYPE_ARM64: u32 = 0x0100_000c;
37
38pub const LC_SEGMENT_64: u32 = 0x19;
41
42pub const LC_REQ_DYLD: u32 = 0x8000_0000;
46
47pub const LC_SYMTAB: u32 = 0x2;
50
51pub const LC_DYSYMTAB: u32 = 0xb;
55
56pub const LC_LOAD_DYLINKER: u32 = 0xe;
59
60pub const LC_UUID: u32 = 0x1b;
63
64pub const LC_LOAD_DYLIB: u32 = 0xc;
67
68pub const LC_LOAD_WEAK_DYLIB: u32 = 0x18 | LC_REQ_DYLD;
70
71pub const LC_REEXPORT_DYLIB: u32 = 0x1f | LC_REQ_DYLD;
73
74pub const LC_ID_DYLIB: u32 = 0xd;
76
77pub const LC_BUILD_VERSION: u32 = 0x32;
79
80pub const LC_SOURCE_VERSION: u32 = 0x2a;
82
83pub const LC_MAIN: u32 = 0x28 | LC_REQ_DYLD;
86
87pub const LC_CODE_SIGNATURE: u32 = 0x1d;
91pub const LC_FUNCTION_STARTS: u32 = 0x26;
92pub const LC_DATA_IN_CODE: u32 = 0x29;
93pub const LC_DYLIB_CODE_SIGN_DRS: u32 = 0x2b;
94pub const LC_LINKER_OPTIMIZATION_HINT: u32 = 0x2e;
95pub const LC_DYLD_EXPORTS_TRIE: u32 = 0x33 | LC_REQ_DYLD;
96pub const LC_DYLD_CHAINED_FIXUPS: u32 = 0x34 | LC_REQ_DYLD;
97
98const MACH_HEADER_64_SIZE: u64 = 32;
100
101const SEGMENT_64_PREFIX_SIZE: usize = 72;
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum MachoCpu {
111 X86_64,
112 Arm64,
113}
114
115#[derive(Debug, thiserror::Error)]
117pub enum Error {
118 #[error("file too short: needed {needed} bytes at offset {offset}, have {have}")]
119 Truncated { offset: u64, needed: u64, have: u64 },
120
121 #[error("not a Mach-O file: bad magic {0:#x}")]
122 BadMagic(u32),
123
124 #[error(
125 "fat (universal) Mach-O wrappers are not supported in v1; demux into thin slices first"
126 )]
127 FatNotSupported,
128
129 #[error("32-bit Mach-O is not supported in v1 (magic {0:#x}); thin 64-bit only")]
130 Macho32NotSupported(u32),
131
132 #[error("unsupported cputype {0:#x}: v1 covers x86-64 (0x01000007) and arm64 (0x0100000c)")]
133 UnsupportedCpu(u32),
134
135 #[error(
136 "load command at offset {offset}: declared cmdsize {cmdsize} is too small (minimum 8)"
137 )]
138 BadLoadCmdSize { offset: u64, cmdsize: u32 },
139
140 #[error("load-command table runs past sizeofcmds: cursor {cursor}, end {end}")]
141 LoadCmdOverrun { cursor: u64, end: u64 },
142
143 #[error(
144 "structured regions overlap: {a_label} at {a_start}..{a_end} vs {b_label} at {b_start}..{b_end}"
145 )]
146 OverlappingRegions {
147 a_label: String,
148 a_start: u64,
149 a_end: u64,
150 b_label: String,
151 b_start: u64,
152 b_end: u64,
153 },
154
155 #[error("integer overflow computing region end for {label} at offset {offset} size {size}")]
156 RegionOverflow {
157 label: String,
158 offset: u64,
159 size: u64,
160 },
161}
162
163pub type Result<T, E = Error> = std::result::Result<T, E>;
164
165#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct MachHeader64 {
173 pub magic: u32,
174 pub cputype: u32,
175 pub cpusubtype: u32,
176 pub filetype: u32,
177 pub ncmds: u32,
178 pub sizeofcmds: u32,
179 pub flags: u32,
180 pub reserved: u32,
181}
182
183impl MachHeader64 {
184 fn parse(bytes: &[u8]) -> Result<Self> {
185 ensure_len(bytes, 0, MACH_HEADER_64_SIZE)?;
186 Ok(Self {
187 magic: read_u32(bytes, 0),
188 cputype: read_u32(bytes, 4),
189 cpusubtype: read_u32(bytes, 8),
190 filetype: read_u32(bytes, 12),
191 ncmds: read_u32(bytes, 16),
192 sizeofcmds: read_u32(bytes, 20),
193 flags: read_u32(bytes, 24),
194 reserved: read_u32(bytes, 28),
195 })
196 }
197
198 fn write(&self, out: &mut [u8]) {
199 write_u32(out, 0, self.magic);
200 write_u32(out, 4, self.cputype);
201 write_u32(out, 8, self.cpusubtype);
202 write_u32(out, 12, self.filetype);
203 write_u32(out, 16, self.ncmds);
204 write_u32(out, 20, self.sizeofcmds);
205 write_u32(out, 24, self.flags);
206 write_u32(out, 28, self.reserved);
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
218pub struct LoadCommand {
219 pub cmd: u32,
220 pub cmdsize: u32,
221 pub body: Vec<u8>,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
229pub struct Segment64 {
230 pub cmd_index: usize,
233 pub segname: [u8; 16],
236 pub vmaddr: u64,
237 pub vmsize: u64,
238 pub fileoff: u64,
239 pub filesize: u64,
240 pub maxprot: u32,
241 pub initprot: u32,
242 pub nsects: u32,
243 pub flags: u32,
244 pub sections: Vec<Section64>,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
250pub struct Section64 {
251 pub sectname: [u8; 16],
253 pub segname: [u8; 16],
255 pub addr: u64,
256 pub size: u64,
257 pub offset: u32,
258 pub align: u32,
259 pub reloff: u32,
260 pub nreloc: u32,
261 pub flags: u32,
262 pub reserved1: u32,
263 pub reserved2: u32,
264 pub reserved3: u32,
265}
266
267impl Segment64 {
268 #[must_use]
272 pub fn name(&self) -> String {
273 cstr_name(&self.segname)
274 }
275}
276
277impl Section64 {
278 #[must_use]
280 pub fn name(&self) -> String {
281 cstr_name(&self.sectname)
282 }
283
284 #[must_use]
286 pub fn segment_name(&self) -> String {
287 cstr_name(&self.segname)
288 }
289}
290
291fn cstr_name(buf: &[u8]) -> String {
292 let nul = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
293 String::from_utf8_lossy(&buf[..nul]).into_owned()
294}
295
296#[derive(Debug, Clone)]
306pub struct MachoFile {
307 pub header: MachHeader64,
308 pub commands: Vec<LoadCommand>,
309 segment_data: Vec<Vec<u8>>,
314 segment_cmd_indices: Vec<usize>,
317 padding: Vec<(u64, Vec<u8>)>,
320 file_size: u64,
321}
322
323#[must_use]
328pub fn is_macho(bytes: &[u8]) -> bool {
329 is_macho64(bytes) || is_fat(bytes) || is_macho32(bytes) || is_macho_be(bytes)
330}
331
332#[must_use]
335pub fn is_macho32(bytes: &[u8]) -> bool {
336 bytes.len() >= 4 && read_u32(bytes, 0) == MH_MAGIC
337}
338
339#[must_use]
343fn is_macho_be(bytes: &[u8]) -> bool {
344 if bytes.len() < 4 {
345 return false;
346 }
347 let be = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
348 be == MH_MAGIC_64 || be == MH_MAGIC
349}
350
351#[must_use]
357pub fn is_macho64(bytes: &[u8]) -> bool {
358 bytes.len() >= 4 && read_u32(bytes, 0) == MH_MAGIC_64
359}
360
361#[must_use]
365pub fn is_fat(bytes: &[u8]) -> bool {
366 if bytes.len() < 4 {
367 return false;
368 }
369 let magic_be = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
371 magic_be == FAT_MAGIC || magic_be == FAT_MAGIC_64
372}
373
374impl MachoFile {
375 pub fn parse(bytes: &[u8]) -> Result<Self> {
377 if bytes.len() < 4 {
378 return Err(Error::Truncated {
379 offset: 0,
380 needed: 4,
381 have: bytes.len() as u64,
382 });
383 }
384 let magic = read_u32(bytes, 0);
385 if magic == FAT_MAGIC || magic == FAT_MAGIC_64 {
386 return Err(Error::FatNotSupported);
387 }
388 let magic_be = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
391 if magic_be == FAT_MAGIC || magic_be == FAT_MAGIC_64 {
392 return Err(Error::FatNotSupported);
393 }
394 if magic == MH_MAGIC || magic == 0xcefa_edfe {
395 return Err(Error::Macho32NotSupported(magic));
396 }
397 if magic != MH_MAGIC_64 {
398 return Err(Error::BadMagic(magic));
399 }
400 let header = MachHeader64::parse(bytes)?;
401 match header.cputype {
402 CPU_TYPE_X86_64 | CPU_TYPE_ARM64 => {}
403 other => return Err(Error::UnsupportedCpu(other)),
404 }
405
406 let commands = parse_load_commands(bytes, &header)?;
407 let (segment_data, segment_cmd_indices) = collect_segment_data(bytes, &commands)?;
408 let regions = build_regions(&header, &commands)?;
409 let padding = compute_padding(bytes, ®ions);
410
411 Ok(Self {
412 header,
413 commands,
414 segment_data,
415 segment_cmd_indices,
416 padding,
417 file_size: bytes.len() as u64,
418 })
419 }
420
421 #[must_use]
424 pub fn from_parts(
425 header: MachHeader64,
426 commands: Vec<LoadCommand>,
427 segment_data: Vec<Vec<u8>>,
428 segment_cmd_indices: Vec<usize>,
429 padding: Vec<(u64, Vec<u8>)>,
430 file_size: u64,
431 ) -> Self {
432 Self {
433 header,
434 commands,
435 segment_data,
436 segment_cmd_indices,
437 padding,
438 file_size,
439 }
440 }
441
442 #[must_use]
447 pub fn cpu(&self) -> Option<MachoCpu> {
448 match self.header.cputype {
449 CPU_TYPE_X86_64 => Some(MachoCpu::X86_64),
450 CPU_TYPE_ARM64 => Some(MachoCpu::Arm64),
451 _ => None,
452 }
453 }
454
455 #[must_use]
461 pub fn segments(&self) -> Vec<Segment64> {
462 let mut out = Vec::new();
463 for (idx, cmd) in self.commands.iter().enumerate() {
464 if cmd.cmd != LC_SEGMENT_64 {
465 continue;
466 }
467 if let Some(seg) = Segment64::parse(idx, cmd) {
468 out.push(seg);
469 }
470 }
471 out
472 }
473
474 #[must_use]
479 pub fn segment_data(&self) -> &[Vec<u8>] {
480 &self.segment_data
481 }
482
483 #[must_use]
485 pub fn segment_command_indices(&self) -> &[usize] {
486 &self.segment_cmd_indices
487 }
488
489 #[must_use]
492 pub fn padding(&self) -> &[(u64, Vec<u8>)] {
493 &self.padding
494 }
495
496 #[must_use]
498 pub fn file_size(&self) -> u64 {
499 self.file_size
500 }
501
502 #[must_use]
505 pub fn write_to_vec(&self) -> Vec<u8> {
506 let mut out = vec![0u8; self.file_size as usize];
507 for (i, data) in self.segment_data.iter().enumerate() {
515 let cmd_idx = self.segment_cmd_indices[i];
516 let seg = self
517 .commands
518 .get(cmd_idx)
519 .and_then(|c| Segment64::parse(cmd_idx, c));
520 if let Some(seg) = seg {
521 if seg.filesize > 0 && !data.is_empty() {
522 let off = seg.fileoff as usize;
523 out[off..off + data.len()].copy_from_slice(data);
524 }
525 }
526 }
527
528 self.header.write(&mut out[..MACH_HEADER_64_SIZE as usize]);
530
531 let mut cursor = MACH_HEADER_64_SIZE as usize;
533 for cmd in &self.commands {
534 write_u32(&mut out, cursor, cmd.cmd);
535 write_u32(&mut out, cursor + 4, cmd.cmdsize);
536 out[cursor + 8..cursor + 8 + cmd.body.len()].copy_from_slice(&cmd.body);
537 cursor += cmd.cmdsize as usize;
538 }
539
540 for (offset, bytes) in &self.padding {
543 let off = *offset as usize;
544 out[off..off + bytes.len()].copy_from_slice(bytes);
545 }
546
547 out
548 }
549}
550
551impl Segment64 {
552 fn parse(cmd_index: usize, cmd: &LoadCommand) -> Option<Self> {
553 if cmd.cmd != LC_SEGMENT_64 {
554 return None;
555 }
556 let body = &cmd.body;
561 if body.len() < SEGMENT_64_PREFIX_SIZE - 8 {
562 return None;
563 }
564 let mut segname = [0u8; 16];
565 segname.copy_from_slice(&body[0..16]);
566 let vmaddr = read_u64(body, 16);
567 let vmsize = read_u64(body, 24);
568 let fileoff = read_u64(body, 32);
569 let filesize = read_u64(body, 40);
570 let maxprot = read_u32(body, 48);
571 let initprot = read_u32(body, 52);
572 let nsects = read_u32(body, 56);
573 let flags = read_u32(body, 60);
574
575 let mut sections = Vec::with_capacity(nsects as usize);
576 let sect_start = SEGMENT_64_PREFIX_SIZE - 8; let sect_size = 80;
578 for i in 0..nsects as usize {
579 let off = sect_start + i * sect_size;
580 if body.len() < off + sect_size {
581 return None;
582 }
583 let s = &body[off..off + sect_size];
584 let mut sectname = [0u8; 16];
585 sectname.copy_from_slice(&s[0..16]);
586 let mut sn = [0u8; 16];
587 sn.copy_from_slice(&s[16..32]);
588 sections.push(Section64 {
589 sectname,
590 segname: sn,
591 addr: read_u64(s, 32),
592 size: read_u64(s, 40),
593 offset: read_u32(s, 48),
594 align: read_u32(s, 52),
595 reloff: read_u32(s, 56),
596 nreloc: read_u32(s, 60),
597 flags: read_u32(s, 64),
598 reserved1: read_u32(s, 68),
599 reserved2: read_u32(s, 72),
600 reserved3: read_u32(s, 76),
601 });
602 }
603
604 Some(Self {
605 cmd_index,
606 segname,
607 vmaddr,
608 vmsize,
609 fileoff,
610 filesize,
611 maxprot,
612 initprot,
613 nsects,
614 flags,
615 sections,
616 })
617 }
618
619 #[must_use]
625 pub fn write_to_body(&self) -> Vec<u8> {
626 let mut out = vec![0u8; 64 + 80 * self.sections.len()];
627 out[0..16].copy_from_slice(&self.segname);
628 out[16..24].copy_from_slice(&self.vmaddr.to_le_bytes());
629 out[24..32].copy_from_slice(&self.vmsize.to_le_bytes());
630 out[32..40].copy_from_slice(&self.fileoff.to_le_bytes());
631 out[40..48].copy_from_slice(&self.filesize.to_le_bytes());
632 out[48..52].copy_from_slice(&self.maxprot.to_le_bytes());
633 out[52..56].copy_from_slice(&self.initprot.to_le_bytes());
634 out[56..60].copy_from_slice(&self.nsects.to_le_bytes());
635 out[60..64].copy_from_slice(&self.flags.to_le_bytes());
636 for (i, s) in self.sections.iter().enumerate() {
637 let off = 64 + i * 80;
638 out[off..off + 16].copy_from_slice(&s.sectname);
639 out[off + 16..off + 32].copy_from_slice(&s.segname);
640 out[off + 32..off + 40].copy_from_slice(&s.addr.to_le_bytes());
641 out[off + 40..off + 48].copy_from_slice(&s.size.to_le_bytes());
642 out[off + 48..off + 52].copy_from_slice(&s.offset.to_le_bytes());
643 out[off + 52..off + 56].copy_from_slice(&s.align.to_le_bytes());
644 out[off + 56..off + 60].copy_from_slice(&s.reloff.to_le_bytes());
645 out[off + 60..off + 64].copy_from_slice(&s.nreloc.to_le_bytes());
646 out[off + 64..off + 68].copy_from_slice(&s.flags.to_le_bytes());
647 out[off + 68..off + 72].copy_from_slice(&s.reserved1.to_le_bytes());
648 out[off + 72..off + 76].copy_from_slice(&s.reserved2.to_le_bytes());
649 out[off + 76..off + 80].copy_from_slice(&s.reserved3.to_le_bytes());
650 }
651 out
652 }
653}
654
655#[derive(Debug, Clone, Copy, PartialEq, Eq)]
660pub struct LcSymtab {
661 pub symoff: u32,
663 pub nsyms: u32,
665 pub stroff: u32,
667 pub strsize: u32,
669}
670
671impl LcSymtab {
672 #[must_use]
676 pub fn decode(body: &[u8]) -> Option<Self> {
677 if body.len() != 16 {
678 return None;
679 }
680 Some(Self {
681 symoff: read_u32(body, 0),
682 nsyms: read_u32(body, 4),
683 stroff: read_u32(body, 8),
684 strsize: read_u32(body, 12),
685 })
686 }
687
688 #[must_use]
691 pub fn encode(&self) -> Vec<u8> {
692 let mut out = vec![0u8; 16];
693 out[0..4].copy_from_slice(&self.symoff.to_le_bytes());
694 out[4..8].copy_from_slice(&self.nsyms.to_le_bytes());
695 out[8..12].copy_from_slice(&self.stroff.to_le_bytes());
696 out[12..16].copy_from_slice(&self.strsize.to_le_bytes());
697 out
698 }
699}
700
701#[allow(clippy::struct_field_names)]
705#[derive(Debug, Clone, Copy, PartialEq, Eq)]
706pub struct LcDysymtab {
707 pub ilocalsym: u32,
708 pub nlocalsym: u32,
709 pub iextdefsym: u32,
710 pub nextdefsym: u32,
711 pub iundefsym: u32,
712 pub nundefsym: u32,
713 pub tocoff: u32,
714 pub ntoc: u32,
715 pub modtaboff: u32,
716 pub nmodtab: u32,
717 pub extrefsymoff: u32,
718 pub nextrefsyms: u32,
719 pub indirectsymoff: u32,
720 pub nindirectsyms: u32,
721 pub extreloff: u32,
722 pub nextrel: u32,
723 pub locreloff: u32,
724 pub nlocrel: u32,
725}
726
727impl LcDysymtab {
728 #[must_use]
729 pub fn decode(body: &[u8]) -> Option<Self> {
730 if body.len() != 72 {
731 return None;
732 }
733 Some(Self {
734 ilocalsym: read_u32(body, 0),
735 nlocalsym: read_u32(body, 4),
736 iextdefsym: read_u32(body, 8),
737 nextdefsym: read_u32(body, 12),
738 iundefsym: read_u32(body, 16),
739 nundefsym: read_u32(body, 20),
740 tocoff: read_u32(body, 24),
741 ntoc: read_u32(body, 28),
742 modtaboff: read_u32(body, 32),
743 nmodtab: read_u32(body, 36),
744 extrefsymoff: read_u32(body, 40),
745 nextrefsyms: read_u32(body, 44),
746 indirectsymoff: read_u32(body, 48),
747 nindirectsyms: read_u32(body, 52),
748 extreloff: read_u32(body, 56),
749 nextrel: read_u32(body, 60),
750 locreloff: read_u32(body, 64),
751 nlocrel: read_u32(body, 68),
752 })
753 }
754
755 #[must_use]
756 pub fn encode(&self) -> Vec<u8> {
757 let mut out = vec![0u8; 72];
758 for (i, v) in [
759 self.ilocalsym,
760 self.nlocalsym,
761 self.iextdefsym,
762 self.nextdefsym,
763 self.iundefsym,
764 self.nundefsym,
765 self.tocoff,
766 self.ntoc,
767 self.modtaboff,
768 self.nmodtab,
769 self.extrefsymoff,
770 self.nextrefsyms,
771 self.indirectsymoff,
772 self.nindirectsyms,
773 self.extreloff,
774 self.nextrel,
775 self.locreloff,
776 self.nlocrel,
777 ]
778 .iter()
779 .enumerate()
780 {
781 out[i * 4..i * 4 + 4].copy_from_slice(&v.to_le_bytes());
782 }
783 out
784 }
785}
786
787#[derive(Debug, Clone, PartialEq, Eq)]
796pub struct LcDylinker {
797 pub offset: u32,
798 pub name: Vec<u8>,
799 pub tail_padding: Vec<u8>,
800}
801
802impl LcDylinker {
803 #[must_use]
804 pub fn decode(body: &[u8]) -> Option<Self> {
805 if body.len() < 4 {
806 return None;
807 }
808 let offset = read_u32(body, 0);
809 let name_off = offset.checked_sub(8)? as usize;
812 if name_off > body.len() {
813 return None;
814 }
815 let tail = &body[name_off..];
816 let nul = tail.iter().position(|&b| b == 0).unwrap_or(tail.len());
817 let name = tail[..nul].to_vec();
818 let tail_padding = tail[nul..].to_vec();
819 if body[4..name_off].iter().any(|&b| b != 0) {
824 return None;
825 }
826 Some(Self {
827 offset,
828 name,
829 tail_padding,
830 })
831 }
832
833 #[must_use]
834 pub fn encode(&self) -> Vec<u8> {
835 let name_off = (self.offset as usize).saturating_sub(8);
836 let mut out = vec![0u8; name_off + self.name.len() + self.tail_padding.len()];
837 out[0..4].copy_from_slice(&self.offset.to_le_bytes());
838 out[name_off..name_off + self.name.len()].copy_from_slice(&self.name);
840 out[name_off + self.name.len()..].copy_from_slice(&self.tail_padding);
841 out
842 }
843}
844
845#[derive(Debug, Clone, PartialEq, Eq)]
849pub struct LcDylib {
850 pub offset: u32,
851 pub timestamp: u32,
852 pub current_version: u32,
853 pub compatibility_version: u32,
854 pub name: Vec<u8>,
855 pub tail_padding: Vec<u8>,
856}
857
858impl LcDylib {
859 #[must_use]
860 pub fn decode(body: &[u8]) -> Option<Self> {
861 if body.len() < 16 {
862 return None;
863 }
864 let offset = read_u32(body, 0);
865 let timestamp = read_u32(body, 4);
866 let current_version = read_u32(body, 8);
867 let compatibility_version = read_u32(body, 12);
868 let name_off = offset.checked_sub(8)? as usize;
869 if name_off > body.len() || name_off < 16 {
870 return None;
871 }
872 if body[16..name_off].iter().any(|&b| b != 0) {
873 return None;
874 }
875 let tail = &body[name_off..];
876 let nul = tail.iter().position(|&b| b == 0).unwrap_or(tail.len());
877 Some(Self {
878 offset,
879 timestamp,
880 current_version,
881 compatibility_version,
882 name: tail[..nul].to_vec(),
883 tail_padding: tail[nul..].to_vec(),
884 })
885 }
886
887 #[must_use]
888 pub fn encode(&self) -> Vec<u8> {
889 let name_off = (self.offset as usize).saturating_sub(8);
890 let mut out = vec![0u8; name_off + self.name.len() + self.tail_padding.len()];
891 out[0..4].copy_from_slice(&self.offset.to_le_bytes());
892 out[4..8].copy_from_slice(&self.timestamp.to_le_bytes());
893 out[8..12].copy_from_slice(&self.current_version.to_le_bytes());
894 out[12..16].copy_from_slice(&self.compatibility_version.to_le_bytes());
895 out[name_off..name_off + self.name.len()].copy_from_slice(&self.name);
896 out[name_off + self.name.len()..].copy_from_slice(&self.tail_padding);
897 out
898 }
899}
900
901#[derive(Debug, Clone, PartialEq, Eq)]
905pub struct LcBuildVersion {
906 pub platform: u32,
907 pub minos: u32,
908 pub sdk: u32,
909 pub tools: Vec<BuildVersionTool>,
910}
911
912#[derive(Debug, Clone, Copy, PartialEq, Eq)]
913pub struct BuildVersionTool {
914 pub tool: u32,
915 pub version: u32,
916}
917
918impl LcBuildVersion {
919 #[must_use]
920 pub fn decode(body: &[u8]) -> Option<Self> {
921 if body.len() < 16 {
922 return None;
923 }
924 let platform = read_u32(body, 0);
925 let minos = read_u32(body, 4);
926 let sdk = read_u32(body, 8);
927 let ntools = read_u32(body, 12) as usize;
928 if body.len() != 16 + 8 * ntools {
929 return None;
930 }
931 let mut tools = Vec::with_capacity(ntools);
932 for i in 0..ntools {
933 let off = 16 + 8 * i;
934 tools.push(BuildVersionTool {
935 tool: read_u32(body, off),
936 version: read_u32(body, off + 4),
937 });
938 }
939 Some(Self {
940 platform,
941 minos,
942 sdk,
943 tools,
944 })
945 }
946
947 #[must_use]
948 pub fn encode(&self) -> Vec<u8> {
949 let mut out = vec![0u8; 16 + 8 * self.tools.len()];
950 out[0..4].copy_from_slice(&self.platform.to_le_bytes());
951 out[4..8].copy_from_slice(&self.minos.to_le_bytes());
952 out[8..12].copy_from_slice(&self.sdk.to_le_bytes());
953 out[12..16].copy_from_slice(&(self.tools.len() as u32).to_le_bytes());
954 for (i, t) in self.tools.iter().enumerate() {
955 let off = 16 + 8 * i;
956 out[off..off + 4].copy_from_slice(&t.tool.to_le_bytes());
957 out[off + 4..off + 8].copy_from_slice(&t.version.to_le_bytes());
958 }
959 out
960 }
961}
962
963#[derive(Debug, Clone, Copy, PartialEq, Eq)]
965pub struct LcMain {
966 pub entryoff: u64,
967 pub stacksize: u64,
968}
969
970impl LcMain {
971 #[must_use]
972 pub fn decode(body: &[u8]) -> Option<Self> {
973 if body.len() != 16 {
974 return None;
975 }
976 Some(Self {
977 entryoff: read_u64(body, 0),
978 stacksize: read_u64(body, 8),
979 })
980 }
981
982 #[must_use]
983 pub fn encode(&self) -> Vec<u8> {
984 let mut out = vec![0u8; 16];
985 out[0..8].copy_from_slice(&self.entryoff.to_le_bytes());
986 out[8..16].copy_from_slice(&self.stacksize.to_le_bytes());
987 out
988 }
989}
990
991#[derive(Debug, Clone, Copy, PartialEq, Eq)]
995pub struct LcLinkeditData {
996 pub dataoff: u32,
997 pub datasize: u32,
998}
999
1000impl LcLinkeditData {
1001 #[must_use]
1002 pub fn decode(body: &[u8]) -> Option<Self> {
1003 if body.len() != 8 {
1004 return None;
1005 }
1006 Some(Self {
1007 dataoff: read_u32(body, 0),
1008 datasize: read_u32(body, 4),
1009 })
1010 }
1011
1012 #[must_use]
1013 pub fn encode(&self) -> Vec<u8> {
1014 let mut out = vec![0u8; 8];
1015 out[0..4].copy_from_slice(&self.dataoff.to_le_bytes());
1016 out[4..8].copy_from_slice(&self.datasize.to_le_bytes());
1017 out
1018 }
1019}
1020
1021#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1023pub struct LcSourceVersion(pub u64);
1024
1025impl LcSourceVersion {
1026 #[must_use]
1027 pub fn decode(body: &[u8]) -> Option<Self> {
1028 if body.len() != 8 {
1029 return None;
1030 }
1031 Some(Self(read_u64(body, 0)))
1032 }
1033
1034 #[must_use]
1035 pub fn encode(&self) -> Vec<u8> {
1036 self.0.to_le_bytes().to_vec()
1037 }
1038}
1039
1040#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1042pub struct LcUuid(pub [u8; 16]);
1043
1044impl LcUuid {
1045 #[must_use]
1046 pub fn decode(body: &[u8]) -> Option<Self> {
1047 if body.len() != 16 {
1048 return None;
1049 }
1050 let mut u = [0u8; 16];
1051 u.copy_from_slice(body);
1052 Some(Self(u))
1053 }
1054
1055 #[must_use]
1056 pub fn encode(&self) -> Vec<u8> {
1057 self.0.to_vec()
1058 }
1059}
1060
1061#[must_use]
1064pub fn is_linkedit_data_cmd(cmd: u32) -> bool {
1065 matches!(
1066 cmd,
1067 LC_CODE_SIGNATURE
1068 | LC_FUNCTION_STARTS
1069 | LC_DATA_IN_CODE
1070 | LC_DYLD_EXPORTS_TRIE
1071 | LC_DYLD_CHAINED_FIXUPS
1072 | LC_DYLIB_CODE_SIGN_DRS
1073 | LC_LINKER_OPTIMIZATION_HINT
1074 )
1075}
1076
1077#[must_use]
1079pub fn is_dylib_cmd(cmd: u32) -> bool {
1080 matches!(
1081 cmd,
1082 LC_LOAD_DYLIB | LC_LOAD_WEAK_DYLIB | LC_REEXPORT_DYLIB | LC_ID_DYLIB
1083 )
1084}
1085
1086fn ensure_len(bytes: &[u8], offset: u64, needed: u64) -> Result<()> {
1089 let end = offset.saturating_add(needed);
1090 if (bytes.len() as u64) < end {
1091 return Err(Error::Truncated {
1092 offset,
1093 needed,
1094 have: bytes.len() as u64,
1095 });
1096 }
1097 Ok(())
1098}
1099
1100fn read_u32(bytes: &[u8], offset: usize) -> u32 {
1101 u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap())
1102}
1103
1104fn read_u64(bytes: &[u8], offset: usize) -> u64 {
1105 u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap())
1106}
1107
1108fn write_u32(bytes: &mut [u8], offset: usize, value: u32) {
1109 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
1110}
1111
1112fn parse_load_commands(bytes: &[u8], header: &MachHeader64) -> Result<Vec<LoadCommand>> {
1113 let table_start = MACH_HEADER_64_SIZE;
1114 let table_end = table_start + u64::from(header.sizeofcmds);
1115 ensure_len(bytes, table_start, u64::from(header.sizeofcmds))?;
1116
1117 let mut commands = Vec::with_capacity(header.ncmds as usize);
1118 let mut cursor = table_start;
1119 for _ in 0..header.ncmds {
1120 if cursor + 8 > table_end {
1121 return Err(Error::LoadCmdOverrun {
1122 cursor,
1123 end: table_end,
1124 });
1125 }
1126 let cmd = read_u32(bytes, cursor as usize);
1127 let cmdsize = read_u32(bytes, cursor as usize + 4);
1128 if cmdsize < 8 {
1129 return Err(Error::BadLoadCmdSize {
1130 offset: cursor,
1131 cmdsize,
1132 });
1133 }
1134 let next = cursor + u64::from(cmdsize);
1135 if next > table_end {
1136 return Err(Error::LoadCmdOverrun {
1137 cursor: next,
1138 end: table_end,
1139 });
1140 }
1141 let body_start = cursor as usize + 8;
1142 let body_end = next as usize;
1143 let body = bytes[body_start..body_end].to_vec();
1144 commands.push(LoadCommand { cmd, cmdsize, body });
1145 cursor = next;
1146 }
1147 if cursor != table_end {
1148 return Err(Error::LoadCmdOverrun {
1149 cursor,
1150 end: table_end,
1151 });
1152 }
1153 Ok(commands)
1154}
1155
1156fn collect_segment_data(
1157 bytes: &[u8],
1158 commands: &[LoadCommand],
1159) -> Result<(Vec<Vec<u8>>, Vec<usize>)> {
1160 let mut data = Vec::new();
1161 let mut indices = Vec::new();
1162 for (idx, cmd) in commands.iter().enumerate() {
1163 if cmd.cmd != LC_SEGMENT_64 {
1164 continue;
1165 }
1166 let Some(seg) = Segment64::parse(idx, cmd) else {
1167 continue;
1168 };
1169 indices.push(idx);
1170 if seg.filesize == 0 {
1171 data.push(Vec::new());
1172 continue;
1173 }
1174 let end = seg
1175 .fileoff
1176 .checked_add(seg.filesize)
1177 .ok_or_else(|| Error::RegionOverflow {
1178 label: format!("segment #{idx} ({:?})", seg.name()),
1179 offset: seg.fileoff,
1180 size: seg.filesize,
1181 })?;
1182 ensure_len(bytes, seg.fileoff, seg.filesize)?;
1183 data.push(bytes[seg.fileoff as usize..end as usize].to_vec());
1184 }
1185 Ok((data, indices))
1186}
1187
1188struct Region {
1189 #[allow(dead_code)]
1194 label: String,
1195 range: Range<u64>,
1196}
1197
1198fn build_regions(header: &MachHeader64, commands: &[LoadCommand]) -> Result<Vec<Region>> {
1199 let mut regions = Vec::new();
1200
1201 regions.push(Region {
1203 label: "Mach-O header".into(),
1204 range: 0..MACH_HEADER_64_SIZE,
1205 });
1206
1207 if header.sizeofcmds > 0 {
1209 regions.push(Region {
1210 label: "load-command table".into(),
1211 range: MACH_HEADER_64_SIZE..MACH_HEADER_64_SIZE + u64::from(header.sizeofcmds),
1212 });
1213 }
1214
1215 for (idx, cmd) in commands.iter().enumerate() {
1222 if cmd.cmd != LC_SEGMENT_64 {
1223 continue;
1224 }
1225 let Some(seg) = Segment64::parse(idx, cmd) else {
1226 continue;
1227 };
1228 if seg.filesize == 0 {
1229 continue;
1230 }
1231 let end = seg
1232 .fileoff
1233 .checked_add(seg.filesize)
1234 .ok_or_else(|| Error::RegionOverflow {
1235 label: format!("segment #{idx} ({:?})", seg.name()),
1236 offset: seg.fileoff,
1237 size: seg.filesize,
1238 })?;
1239 regions.push(Region {
1240 label: format!("segment #{idx} ({:?})", seg.name()),
1241 range: seg.fileoff..end,
1242 });
1243 }
1244
1245 regions.sort_by_key(|r| r.range.start);
1246 Ok(regions)
1247}
1248
1249fn compute_padding(bytes: &[u8], regions: &[Region]) -> Vec<(u64, Vec<u8>)> {
1250 let mut padding = Vec::new();
1251 let file_end = bytes.len() as u64;
1252 let mut cursor = 0u64;
1253 for region in regions {
1254 if region.range.start > cursor {
1255 let start = cursor as usize;
1256 let end = region.range.start as usize;
1257 padding.push((cursor, bytes[start..end].to_vec()));
1258 }
1259 cursor = cursor.max(region.range.end);
1260 }
1261 if cursor < file_end {
1262 let start = cursor as usize;
1263 let end = file_end as usize;
1264 padding.push((cursor, bytes[start..end].to_vec()));
1265 }
1266 padding
1267}
1268
1269#[cfg(test)]
1270mod tests {
1271 use super::*;
1272
1273 #[test]
1274 fn magic_detection() {
1275 let buf = [0xcf, 0xfa, 0xed, 0xfe];
1276 assert!(is_macho(&buf));
1277 assert!(is_macho64(&buf));
1278 let fat = [0xca, 0xfe, 0xba, 0xbe];
1279 assert!(is_macho(&fat));
1280 assert!(is_fat(&fat));
1281 assert!(!is_macho64(&fat));
1282 }
1283
1284 #[test]
1285 fn rejects_short_input() {
1286 let err = MachoFile::parse(b"\x7fELF").unwrap_err();
1287 assert!(matches!(err, Error::BadMagic(_)));
1288 }
1289}