1 """Checks the validity of MachO binary signatures
3 MachO binaries sometimes include a LC_CODE_SIGNATURE load command
4 and corresponding section in the __LINKEDIT segment that together
5 work to "sign" the binary. This script is used to check the validity
9 ./code-signature-check.py my_binary 800 300 0 800
12 binary - The MachO binary to be tested
13 offset - The offset from the start of the binary to where the code signature section begins
14 size - The size of the code signature section in the binary
15 code_offset - The point in the binary to begin hashing
16 code_size - The length starting from code_offset to hash
28 class CodeDirectoryVersion
:
29 SUPPORTSSCATTER
= 0x20100
30 SUPPORTSTEAMID
= 0x20200
31 SUPPORTSCODELIMIT64
= 0x20300
32 SUPPORTSEXECSEG
= 0x20400
41 "CodeDirectoryV20100",
42 "CodeDirectoryV20200",
43 "CodeDirectoryV20300",
44 "CodeDirectoryV20400",
46 _magic
, _length
, version
= struct
.unpack_from(">III", buf
, 0)
48 CodeDirectoryVersion
.SUPPORTSSCATTER
: CodeDirectoryV20100
,
49 CodeDirectoryVersion
.SUPPORTSTEAMID
: CodeDirectoryV20200
,
50 CodeDirectoryVersion
.SUPPORTSCODELIMIT64
: CodeDirectoryV20300
,
51 CodeDirectoryVersion
.SUPPORTSEXECSEG
: CodeDirectoryV20400
,
52 }.get(version
, CodeDirectoryBase
)
54 return subtype
._make
(struct
.unpack_from(subtype
._format
(), buf
, 0))
57 class CodeDirectoryBase(typing
.NamedTuple
):
75 return ">IIIIIIIIIBBBBI"
78 class CodeDirectoryV20100(typing
.NamedTuple
):
98 return CodeDirectoryBase
._format
() + "I"
101 class CodeDirectoryV20200(typing
.NamedTuple
):
122 def _format() -> str:
123 return CodeDirectoryV20100
._format() + "I"
126 class CodeDirectoryV20300(typing
.NamedTuple
):
150 def _format() -> str:
151 return CodeDirectoryV20200
._format() + "IQ"
154 class CodeDirectoryV20400(typing
.NamedTuple
):
182 def _format() -> str:
183 return CodeDirectoryV20300
._format() + "QQQ"
186 class CodeDirectoryBlobIndex(typing
.NamedTuple
):
191 def make(buf
: memoryview
) -> "CodeDirectoryBlobIndex":
192 return CodeDirectoryBlobIndex
._make
(
193 struct
.unpack_from(CodeDirectoryBlobIndex
.__format
(), buf
, 0)
197 def bytesize() -> int:
198 return struct
.calcsize(CodeDirectoryBlobIndex
.__format
())
201 def __format() -> str:
205 class CodeDirectorySuperBlob(typing
.NamedTuple
):
209 blob_indices
: typing
.List
[CodeDirectoryBlobIndex
]
212 def make(buf
: memoryview
) -> "CodeDirectorySuperBlob":
213 super_blob_layout
= ">III"
214 super_blob
= struct
.unpack_from(super_blob_layout
, buf
, 0)
216 offset
= struct
.calcsize(super_blob_layout
)
218 for idx
in range(super_blob
[2]):
219 blob_indices
.append(CodeDirectoryBlobIndex
.make(buf
[offset
:]))
220 offset
+= CodeDirectoryBlobIndex
.bytesize()
222 return CodeDirectorySuperBlob(*super_blob
, blob_indices
)
225 def unpack_null_terminated_string(buf
: memoryview
) -> str:
226 b
= bytes(itertools
.takewhile(lambda b
: b
!= 0, buf
))
231 parser
= argparse
.ArgumentParser()
233 "binary", type=argparse
.FileType("rb"), help="The file to analyze"
236 "offset", type=int, help="Offset to start of Code Directory data"
238 parser
.add_argument("size", type=int, help="Size of Code Directory data")
240 "code_offset", type=int, help="Offset to start of code pages to hash"
242 parser
.add_argument("code_size", type=int, help="Size of the code pages to hash")
244 args
= parser
.parse_args()
246 args
.binary
.seek(args
.offset
)
247 super_blob_bytes
= args
.binary
.read(args
.size
)
248 super_blob_mem
= memoryview(super_blob_bytes
)
250 super_blob
= CodeDirectorySuperBlob
.make(super_blob_mem
)
253 for blob_index
in super_blob
.blob_indices
:
254 code_directory_offset
= blob_index
.offset
255 code_directory
= CodeDirectory
.make(super_blob_mem
[code_directory_offset
:])
256 print(code_directory
)
258 ident_offset
= code_directory_offset
+ code_directory
.identOffset
260 "Code Directory ID: "
261 + unpack_null_terminated_string(super_blob_mem
[ident_offset
:])
264 code_offset
= args
.code_offset
265 code_end
= code_offset
+ args
.code_size
266 page_size
= 1 << code_directory
.pageSize
267 args
.binary
.seek(code_offset
)
269 hashes_offset
= code_directory_offset
+ code_directory
.hashOffset
270 for idx
in range(code_directory
.nCodeSlots
):
272 super_blob_mem
[hashes_offset
: hashes_offset
+ code_directory
.hashSize
]
274 hashes_offset
+= code_directory
.hashSize
276 hasher
= hashlib
.sha256()
277 read_size
= min(page_size
, code_end
- code_offset
)
278 hasher
.update(args
.binary
.read(read_size
))
279 calculated_hash_bytes
= hasher
.digest()
280 code_offset
+= read_size
282 print("%s <> %s" % (hash_bytes
.hex(), calculated_hash_bytes
.hex()))
284 if hash_bytes
!= calculated_hash_bytes
:
288 if __name__
== "__main__":