1use sha2::{Digest, Sha256};
26use zerocopy::byteorder::big_endian;
27use zerocopy::{AsBytes, FromBytes, FromZeroes};
28
29use crate::{Error, Header64, SegmentCommand64, LC_CODE_SIGNATURE, LC_SEGMENT_64};
30use core::mem::size_of;
31
32#[derive(Debug, Clone, FromBytes, FromZeroes, AsBytes)]
33#[repr(C)]
34struct SuperBlob {
35 magic: big_endian::U32,
36 length: big_endian::U32,
37 count: big_endian::U32,
38}
39
40#[derive(Debug, Clone, FromBytes, FromZeroes, AsBytes)]
41#[repr(C)]
42struct Blob {
43 typ: big_endian::U32,
44 offset: big_endian::U32,
45}
46
47#[repr(C)]
48#[derive(Debug, Clone, FromBytes, FromZeroes, AsBytes)]
49struct CodeDirectory {
50 magic: big_endian::U32, length: big_endian::U32, version: big_endian::U32, flags: big_endian::U32, hash_offset: big_endian::U32, ident_offset: big_endian::U32, n_special_slots: big_endian::U32, n_code_slots: big_endian::U32, code_limit: big_endian::U32, hash_size: u8, hash_type: u8, _pad1: u8, page_size: u8, _pad2: big_endian::U32, scatter_offset: big_endian::U32,
65 team_offset: big_endian::U32,
66 _pad3: big_endian::U32,
67 code_limit64: big_endian::U64,
68 exec_seg_base: big_endian::U64,
69 exec_seg_limit: big_endian::U64,
70 exec_seg_flags: big_endian::U64,
71}
72
73#[derive(FromBytes, FromZeroes, AsBytes, Debug)]
74#[repr(C)]
75struct LinkeditDataCommand {
76 cmd: u32,
77 cmdsize: u32,
78 dataoff: u32,
79 datasize: u32,
80}
81
82pub struct MachoSigner {
83 data: Vec<u8>,
84
85 sig_off: usize,
86 sig_sz: usize,
87 cs_cmd_off: usize,
88 linkedit_off: usize,
89
90 linkedit_seg: SegmentCommand64,
91 text_seg: SegmentCommand64,
92}
93
94const CSMAGIC_CODEDIRECTORY: u32 = 0xfade0c02; const CSMAGIC_EMBEDDED_SIGNATURE: u32 = 0xfade0cc0; const CSSLOT_CODEDIRECTORY: u32 = 0; const SEC_CODE_SIGNATURE_HASH_SHA256: u8 = 2;
99
100const CS_EXECSEG_MAIN_BINARY: u64 = 0x1; impl MachoSigner {
103 pub fn new(obj: Vec<u8>) -> Result<Self, Error> {
104 let header = Header64::read_from_prefix(&obj)
105 .ok_or(Error::InvalidObject("Invalid Mach-O header"))?;
106
107 let mut offset = size_of::<Header64>();
108 let mut sig_off = 0;
109 let mut sig_sz = 0;
110 let mut cs_cmd_off = 0;
111 let mut linkedit_off = 0;
112
113 let mut text_seg = SegmentCommand64::new_zeroed();
114 let mut linkedit_seg = SegmentCommand64::new_zeroed();
115
116 for _ in 0..header.ncmds as usize {
117 let cmd = u32::from_le_bytes(
118 obj[offset..offset + 4]
119 .try_into()
120 .map_err(|_| Error::InvalidObject("Failed to read command"))?,
121 );
122 let cmdsize = u32::from_le_bytes(
123 obj[offset + 4..offset + 8]
124 .try_into()
125 .map_err(|_| Error::InvalidObject("Failed to read command size"))?,
126 );
127
128 if cmd == LC_CODE_SIGNATURE {
129 let cmd = LinkeditDataCommand::read_from_prefix(&obj[offset..])
130 .ok_or(Error::InvalidObject("Failed to read linkedit data command"))?;
131 sig_off = cmd.dataoff as usize;
132 sig_sz = cmd.datasize as usize;
133 cs_cmd_off = offset;
134 }
135 if cmd == LC_SEGMENT_64 {
136 let segcmd = SegmentCommand64::read_from_prefix(&obj[offset..])
137 .ok_or(Error::InvalidObject("Failed to read segment command"))?;
138 let segname = String::from_utf8_lossy(&segcmd.segname);
140 let segname = segname.trim_end_matches('\0');
141
142 if segname == "__LINKEDIT" {
143 linkedit_off = offset;
144 linkedit_seg = segcmd;
145 } else if segname == "__TEXT" {
146 text_seg = segcmd;
147 }
148 }
149
150 offset += cmdsize as usize;
151 }
152
153 Ok(Self {
154 data: obj,
155 sig_off,
156 sig_sz,
157 cs_cmd_off,
158 linkedit_off,
159 linkedit_seg,
160 text_seg,
161 })
162 }
163
164 pub fn sign<W: std::io::Write>(mut self, mut writer: W) -> Result<(), Error> {
165 const PAGE_SIZE: usize = 1 << 12;
166
167 let id = b"a.out\0";
168 let n_hashes = self.sig_off.div_ceil(PAGE_SIZE);
169 let id_off = size_of::<CodeDirectory>();
170 let hash_off = id_off + id.len();
171 let c_dir_sz = hash_off + n_hashes * 32;
172 let sz = size_of::<SuperBlob>() + size_of::<Blob>() + c_dir_sz;
173
174 if self.sig_sz != sz {
175 let cs_cmd = LinkeditDataCommand::mut_from_prefix(&mut self.data[self.cs_cmd_off..])
177 .ok_or(Error::InvalidObject("Failed to read linkedit data command"))?;
178 cs_cmd.datasize = sz as u32;
179
180 let seg_sz = self.sig_off + sz - self.linkedit_seg.fileoff as usize;
182 let linkedit_seg =
183 SegmentCommand64::mut_from_prefix(&mut self.data[self.linkedit_off..])
184 .ok_or(Error::InvalidObject("Failed to read linkedit segment"))?;
185 linkedit_seg.filesize = seg_sz as u64;
186 linkedit_seg.vmsize = seg_sz as u64;
187 }
188
189 let sb = SuperBlob {
190 magic: CSMAGIC_EMBEDDED_SIGNATURE.into(),
191 length: (sz as u32).into(),
192 count: 1.into(),
193 };
194 let blob = Blob {
195 typ: CSSLOT_CODEDIRECTORY.into(),
196 offset: (size_of::<SuperBlob>() as u32 + size_of::<Blob>() as u32).into(),
197 };
198 let c_dir = CodeDirectory::new_zeroed();
199 let c_dir = CodeDirectory {
200 magic: CSMAGIC_CODEDIRECTORY.into(),
201 length: (sz as u32 - (size_of::<SuperBlob>() as u32 + size_of::<Blob>() as u32)).into(),
202 version: 0x20400.into(),
203 flags: 0x20002.into(), hash_offset: (hash_off as u32).into(),
205 ident_offset: (id_off as u32).into(),
206 n_code_slots: (n_hashes as u32).into(),
207 code_limit: (self.sig_off as u32).into(),
208 hash_size: sha2::Sha256::output_size() as u8,
209 hash_type: SEC_CODE_SIGNATURE_HASH_SHA256,
210 page_size: 12,
211 exec_seg_base: self.text_seg.fileoff.into(),
212 exec_seg_limit: self.text_seg.filesize.into(),
213 exec_seg_flags: CS_EXECSEG_MAIN_BINARY.into(),
214 ..c_dir
215 };
216
217 let mut out = Vec::with_capacity(sz);
218 out.extend_from_slice(sb.as_bytes());
219 out.extend_from_slice(blob.as_bytes());
220 out.extend_from_slice(c_dir.as_bytes());
221 out.extend_from_slice(id);
222
223 let mut fileoff = 0;
224
225 let mut hasher = Sha256::new();
226 while fileoff < self.sig_off {
227 let mut n = PAGE_SIZE;
228 if fileoff + n > self.sig_off {
229 n = self.sig_off - fileoff;
230 }
231 let chunk = &self.data[fileoff..fileoff + n];
232 hasher.update(chunk);
233 out.extend_from_slice(&hasher.finalize_reset());
234 fileoff += n;
235 }
236
237 if self.data.len() < self.sig_off + sz {
238 self.data.resize(self.sig_off + sz, 0);
239 }
240
241 self.data[self.sig_off..self.sig_off + sz].copy_from_slice(&out);
242 self.data.truncate(self.sig_off + sz);
243
244 writer.write_all(&self.data)?;
245
246 Ok(())
247 }
248}