290 lines · python
1"""Checks the validity of MachO binary signatures2 3MachO binaries sometimes include a LC_CODE_SIGNATURE load command4and corresponding section in the __LINKEDIT segment that together5work to "sign" the binary. This script is used to check the validity6of this signature.7 8Usage:9 ./code-signature-check.py my_binary 800 300 0 80010 11Arguments:12 binary - The MachO binary to be tested13 offset - The offset from the start of the binary to where the code signature section begins14 size - The size of the code signature section in the binary15 code_offset - The point in the binary to begin hashing16 code_size - The length starting from code_offset to hash17"""18 19import argparse20import collections21import hashlib22import itertools23import struct24import sys25import typing26 27 28class CodeDirectoryVersion:29 SUPPORTSSCATTER = 0x2010030 SUPPORTSTEAMID = 0x2020031 SUPPORTSCODELIMIT64 = 0x2030032 SUPPORTSEXECSEG = 0x2040033 34 35class CodeDirectory:36 @staticmethod37 def make(38 buf: memoryview,39 ) -> typing.Union[40 "CodeDirectoryBase",41 "CodeDirectoryV20100",42 "CodeDirectoryV20200",43 "CodeDirectoryV20300",44 "CodeDirectoryV20400",45 ]:46 _magic, _length, version = struct.unpack_from(">III", buf, 0)47 subtype = {48 CodeDirectoryVersion.SUPPORTSSCATTER: CodeDirectoryV20100,49 CodeDirectoryVersion.SUPPORTSTEAMID: CodeDirectoryV20200,50 CodeDirectoryVersion.SUPPORTSCODELIMIT64: CodeDirectoryV20300,51 CodeDirectoryVersion.SUPPORTSEXECSEG: CodeDirectoryV20400,52 }.get(version, CodeDirectoryBase)53 54 return subtype._make(struct.unpack_from(subtype._format(), buf, 0))55 56 57class CodeDirectoryBase(typing.NamedTuple):58 magic: int59 length: int60 version: int61 flags: int62 hashOffset: int63 identOffset: int64 nSpecialSlots: int65 nCodeSlots: int66 codeLimit: int67 hashSize: int68 hashType: int69 platform: int70 pageSize: int71 spare2: int72 73 @staticmethod74 def _format() -> str:75 return ">IIIIIIIIIBBBBI"76 77 78class CodeDirectoryV20100(typing.NamedTuple):79 magic: int80 length: int81 version: int82 flags: int83 hashOffset: int84 identOffset: int85 nSpecialSlots: int86 nCodeSlots: int87 codeLimit: int88 hashSize: int89 hashType: int90 platform: int91 pageSize: int92 spare2: int93 94 scatterOffset: int95 96 @staticmethod97 def _format() -> str:98 return CodeDirectoryBase._format() + "I"99 100 101class CodeDirectoryV20200(typing.NamedTuple):102 magic: int103 length: int104 version: int105 flags: int106 hashOffset: int107 identOffset: int108 nSpecialSlots: int109 nCodeSlots: int110 codeLimit: int111 hashSize: int112 hashType: int113 platform: int114 pageSize: int115 spare2: int116 117 scatterOffset: int118 119 teamOffset: int120 121 @staticmethod122 def _format() -> str:123 return CodeDirectoryV20100._format() + "I"124 125 126class CodeDirectoryV20300(typing.NamedTuple):127 magic: int128 length: int129 version: int130 flags: int131 hashOffset: int132 identOffset: int133 nSpecialSlots: int134 nCodeSlots: int135 codeLimit: int136 hashSize: int137 hashType: int138 platform: int139 pageSize: int140 spare2: int141 142 scatterOffset: int143 144 teamOffset: int145 146 spare3: int147 codeLimit64: int148 149 @staticmethod150 def _format() -> str:151 return CodeDirectoryV20200._format() + "IQ"152 153 154class CodeDirectoryV20400(typing.NamedTuple):155 magic: int156 length: int157 version: int158 flags: int159 hashOffset: int160 identOffset: int161 nSpecialSlots: int162 nCodeSlots: int163 codeLimit: int164 hashSize: int165 hashType: int166 platform: int167 pageSize: int168 spare2: int169 170 scatterOffset: int171 172 teamOffset: int173 174 spare3: int175 codeLimit64: int176 177 execSegBase: int178 execSegLimit: int179 execSegFlags: int180 181 @staticmethod182 def _format() -> str:183 return CodeDirectoryV20300._format() + "QQQ"184 185 186class CodeDirectoryBlobIndex(typing.NamedTuple):187 type_: int188 offset: int189 190 @staticmethod191 def make(buf: memoryview) -> "CodeDirectoryBlobIndex":192 return CodeDirectoryBlobIndex._make(193 struct.unpack_from(CodeDirectoryBlobIndex.__format(), buf, 0)194 )195 196 @staticmethod197 def bytesize() -> int:198 return struct.calcsize(CodeDirectoryBlobIndex.__format())199 200 @staticmethod201 def __format() -> str:202 return ">II"203 204 205class CodeDirectorySuperBlob(typing.NamedTuple):206 magic: int207 length: int208 count: int209 blob_indices: typing.List[CodeDirectoryBlobIndex]210 211 @staticmethod212 def make(buf: memoryview) -> "CodeDirectorySuperBlob":213 super_blob_layout = ">III"214 super_blob = struct.unpack_from(super_blob_layout, buf, 0)215 216 offset = struct.calcsize(super_blob_layout)217 blob_indices = []218 for idx in range(super_blob[2]):219 blob_indices.append(CodeDirectoryBlobIndex.make(buf[offset:]))220 offset += CodeDirectoryBlobIndex.bytesize()221 222 return CodeDirectorySuperBlob(*super_blob, blob_indices)223 224 225def unpack_null_terminated_string(buf: memoryview) -> str:226 b = bytes(itertools.takewhile(lambda b: b != 0, buf))227 return b.decode()228 229 230def main():231 parser = argparse.ArgumentParser()232 parser.add_argument(233 "binary", type=argparse.FileType("rb"), help="The file to analyze"234 )235 parser.add_argument(236 "offset", type=int, help="Offset to start of Code Directory data"237 )238 parser.add_argument("size", type=int, help="Size of Code Directory data")239 parser.add_argument(240 "code_offset", type=int, help="Offset to start of code pages to hash"241 )242 parser.add_argument("code_size", type=int, help="Size of the code pages to hash")243 244 args = parser.parse_args()245 246 args.binary.seek(args.offset)247 super_blob_bytes = args.binary.read(args.size)248 super_blob_mem = memoryview(super_blob_bytes)249 250 super_blob = CodeDirectorySuperBlob.make(super_blob_mem)251 print(super_blob)252 253 for blob_index in super_blob.blob_indices:254 code_directory_offset = blob_index.offset255 code_directory = CodeDirectory.make(super_blob_mem[code_directory_offset:])256 print(code_directory)257 258 ident_offset = code_directory_offset + code_directory.identOffset259 print(260 "Code Directory ID: "261 + unpack_null_terminated_string(super_blob_mem[ident_offset:])262 )263 264 code_offset = args.code_offset265 code_end = code_offset + args.code_size266 page_size = 1 << code_directory.pageSize267 args.binary.seek(code_offset)268 269 hashes_offset = code_directory_offset + code_directory.hashOffset270 for idx in range(code_directory.nCodeSlots):271 hash_bytes = bytes(272 super_blob_mem[hashes_offset : hashes_offset + code_directory.hashSize]273 )274 hashes_offset += code_directory.hashSize275 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_size281 282 print("%s <> %s" % (hash_bytes.hex(), calculated_hash_bytes.hex()))283 284 if hash_bytes != calculated_hash_bytes:285 sys.exit(-1)286 287 288if __name__ == "__main__":289 main()290