1a299b247SNuri Amari"""Checks the validity of MachO binary signatures 2a299b247SNuri Amari 3a299b247SNuri AmariMachO binaries sometimes include a LC_CODE_SIGNATURE load command 4a299b247SNuri Amariand corresponding section in the __LINKEDIT segment that together 5a299b247SNuri Amariwork to "sign" the binary. This script is used to check the validity 6a299b247SNuri Amariof this signature. 7a299b247SNuri Amari 8a299b247SNuri AmariUsage: 9a299b247SNuri Amari ./code-signature-check.py my_binary 800 300 0 800 10a299b247SNuri Amari 11a299b247SNuri AmariArguments: 12a299b247SNuri Amari binary - The MachO binary to be tested 13a299b247SNuri Amari offset - The offset from the start of the binary to where the code signature section begins 14a299b247SNuri Amari size - The size of the code signature section in the binary 15a299b247SNuri Amari code_offset - The point in the binary to begin hashing 16a299b247SNuri Amari code_size - The length starting from code_offset to hash 17a299b247SNuri Amari""" 18a299b247SNuri Amari 19a299b247SNuri Amariimport argparse 20a299b247SNuri Amariimport collections 21a299b247SNuri Amariimport hashlib 22a299b247SNuri Amariimport itertools 23a299b247SNuri Amariimport struct 24a299b247SNuri Amariimport sys 25a299b247SNuri Amariimport typing 26a299b247SNuri Amari 27*b71edfaaSTobias Hieta 28a299b247SNuri Amariclass CodeDirectoryVersion: 29a299b247SNuri Amari SUPPORTSSCATTER = 0x20100 30a299b247SNuri Amari SUPPORTSTEAMID = 0x20200 31a299b247SNuri Amari SUPPORTSCODELIMIT64 = 0x20300 32a299b247SNuri Amari SUPPORTSEXECSEG = 0x20400 33a299b247SNuri Amari 34*b71edfaaSTobias Hieta 35a299b247SNuri Amariclass CodeDirectory: 36a299b247SNuri Amari @staticmethod 37*b71edfaaSTobias Hieta def make( 38*b71edfaaSTobias Hieta buf: memoryview, 39*b71edfaaSTobias Hieta ) -> typing.Union[ 40*b71edfaaSTobias Hieta "CodeDirectoryBase", 41*b71edfaaSTobias Hieta "CodeDirectoryV20100", 42*b71edfaaSTobias Hieta "CodeDirectoryV20200", 43*b71edfaaSTobias Hieta "CodeDirectoryV20300", 44*b71edfaaSTobias Hieta "CodeDirectoryV20400", 45*b71edfaaSTobias Hieta ]: 46a299b247SNuri Amari _magic, _length, version = struct.unpack_from(">III", buf, 0) 47a299b247SNuri Amari subtype = { 48a299b247SNuri Amari CodeDirectoryVersion.SUPPORTSSCATTER: CodeDirectoryV20100, 49a299b247SNuri Amari CodeDirectoryVersion.SUPPORTSTEAMID: CodeDirectoryV20200, 50a299b247SNuri Amari CodeDirectoryVersion.SUPPORTSCODELIMIT64: CodeDirectoryV20300, 51a299b247SNuri Amari CodeDirectoryVersion.SUPPORTSEXECSEG: CodeDirectoryV20400, 52a299b247SNuri Amari }.get(version, CodeDirectoryBase) 53a299b247SNuri Amari 54a299b247SNuri Amari return subtype._make(struct.unpack_from(subtype._format(), buf, 0)) 55a299b247SNuri Amari 56*b71edfaaSTobias Hieta 57a299b247SNuri Amariclass CodeDirectoryBase(typing.NamedTuple): 58a299b247SNuri Amari magic: int 59a299b247SNuri Amari length: int 60a299b247SNuri Amari version: int 61a299b247SNuri Amari flags: int 62a299b247SNuri Amari hashOffset: int 63a299b247SNuri Amari identOffset: int 64a299b247SNuri Amari nSpecialSlots: int 65a299b247SNuri Amari nCodeSlots: int 66a299b247SNuri Amari codeLimit: int 67a299b247SNuri Amari hashSize: int 68a299b247SNuri Amari hashType: int 69a299b247SNuri Amari platform: int 70a299b247SNuri Amari pageSize: int 71a299b247SNuri Amari spare2: int 72a299b247SNuri Amari 73a299b247SNuri Amari @staticmethod 74a299b247SNuri Amari def _format() -> str: 75a299b247SNuri Amari return ">IIIIIIIIIBBBBI" 76a299b247SNuri Amari 77*b71edfaaSTobias Hieta 78a299b247SNuri Amariclass CodeDirectoryV20100(typing.NamedTuple): 79a299b247SNuri Amari magic: int 80a299b247SNuri Amari length: int 81a299b247SNuri Amari version: int 82a299b247SNuri Amari flags: int 83a299b247SNuri Amari hashOffset: int 84a299b247SNuri Amari identOffset: int 85a299b247SNuri Amari nSpecialSlots: int 86a299b247SNuri Amari nCodeSlots: int 87a299b247SNuri Amari codeLimit: int 88a299b247SNuri Amari hashSize: int 89a299b247SNuri Amari hashType: int 90a299b247SNuri Amari platform: int 91a299b247SNuri Amari pageSize: int 92a299b247SNuri Amari spare2: int 93a299b247SNuri Amari 94a299b247SNuri Amari scatterOffset: int 95a299b247SNuri Amari 96a299b247SNuri Amari @staticmethod 97a299b247SNuri Amari def _format() -> str: 98a299b247SNuri Amari return CodeDirectoryBase._format() + "I" 99a299b247SNuri Amari 100*b71edfaaSTobias Hieta 101a299b247SNuri Amariclass CodeDirectoryV20200(typing.NamedTuple): 102a299b247SNuri Amari magic: int 103a299b247SNuri Amari length: int 104a299b247SNuri Amari version: int 105a299b247SNuri Amari flags: int 106a299b247SNuri Amari hashOffset: int 107a299b247SNuri Amari identOffset: int 108a299b247SNuri Amari nSpecialSlots: int 109a299b247SNuri Amari nCodeSlots: int 110a299b247SNuri Amari codeLimit: int 111a299b247SNuri Amari hashSize: int 112a299b247SNuri Amari hashType: int 113a299b247SNuri Amari platform: int 114a299b247SNuri Amari pageSize: int 115a299b247SNuri Amari spare2: int 116a299b247SNuri Amari 117a299b247SNuri Amari scatterOffset: int 118a299b247SNuri Amari 119a299b247SNuri Amari teamOffset: int 120a299b247SNuri Amari 121a299b247SNuri Amari @staticmethod 122a299b247SNuri Amari def _format() -> str: 123a299b247SNuri Amari return CodeDirectoryV20100._format() + "I" 124a299b247SNuri Amari 125*b71edfaaSTobias Hieta 126a299b247SNuri Amariclass CodeDirectoryV20300(typing.NamedTuple): 127a299b247SNuri Amari magic: int 128a299b247SNuri Amari length: int 129a299b247SNuri Amari version: int 130a299b247SNuri Amari flags: int 131a299b247SNuri Amari hashOffset: int 132a299b247SNuri Amari identOffset: int 133a299b247SNuri Amari nSpecialSlots: int 134a299b247SNuri Amari nCodeSlots: int 135a299b247SNuri Amari codeLimit: int 136a299b247SNuri Amari hashSize: int 137a299b247SNuri Amari hashType: int 138a299b247SNuri Amari platform: int 139a299b247SNuri Amari pageSize: int 140a299b247SNuri Amari spare2: int 141a299b247SNuri Amari 142a299b247SNuri Amari scatterOffset: int 143a299b247SNuri Amari 144a299b247SNuri Amari teamOffset: int 145a299b247SNuri Amari 146a299b247SNuri Amari spare3: int 147a299b247SNuri Amari codeLimit64: int 148a299b247SNuri Amari 149a299b247SNuri Amari @staticmethod 150a299b247SNuri Amari def _format() -> str: 151a299b247SNuri Amari return CodeDirectoryV20200._format() + "IQ" 152a299b247SNuri Amari 153*b71edfaaSTobias Hieta 154a299b247SNuri Amariclass CodeDirectoryV20400(typing.NamedTuple): 155a299b247SNuri Amari magic: int 156a299b247SNuri Amari length: int 157a299b247SNuri Amari version: int 158a299b247SNuri Amari flags: int 159a299b247SNuri Amari hashOffset: int 160a299b247SNuri Amari identOffset: int 161a299b247SNuri Amari nSpecialSlots: int 162a299b247SNuri Amari nCodeSlots: int 163a299b247SNuri Amari codeLimit: int 164a299b247SNuri Amari hashSize: int 165a299b247SNuri Amari hashType: int 166a299b247SNuri Amari platform: int 167a299b247SNuri Amari pageSize: int 168a299b247SNuri Amari spare2: int 169a299b247SNuri Amari 170a299b247SNuri Amari scatterOffset: int 171a299b247SNuri Amari 172a299b247SNuri Amari teamOffset: int 173a299b247SNuri Amari 174a299b247SNuri Amari spare3: int 175a299b247SNuri Amari codeLimit64: int 176a299b247SNuri Amari 177a299b247SNuri Amari execSegBase: int 178a299b247SNuri Amari execSegLimit: int 179a299b247SNuri Amari execSegFlags: int 180a299b247SNuri Amari 181a299b247SNuri Amari @staticmethod 182a299b247SNuri Amari def _format() -> str: 183a299b247SNuri Amari return CodeDirectoryV20300._format() + "QQQ" 184a299b247SNuri Amari 185*b71edfaaSTobias Hieta 186a299b247SNuri Amariclass CodeDirectoryBlobIndex(typing.NamedTuple): 187a299b247SNuri Amari type_: int 188a299b247SNuri Amari offset: int 189a299b247SNuri Amari 190a299b247SNuri Amari @staticmethod 191*b71edfaaSTobias Hieta def make(buf: memoryview) -> "CodeDirectoryBlobIndex": 192*b71edfaaSTobias Hieta return CodeDirectoryBlobIndex._make( 193*b71edfaaSTobias Hieta struct.unpack_from(CodeDirectoryBlobIndex.__format(), buf, 0) 194*b71edfaaSTobias Hieta ) 195a299b247SNuri Amari 196a299b247SNuri Amari @staticmethod 197a299b247SNuri Amari def bytesize() -> int: 198a299b247SNuri Amari return struct.calcsize(CodeDirectoryBlobIndex.__format()) 199a299b247SNuri Amari 200a299b247SNuri Amari @staticmethod 201a299b247SNuri Amari def __format() -> str: 202a299b247SNuri Amari return ">II" 203a299b247SNuri Amari 204*b71edfaaSTobias Hieta 205a299b247SNuri Amariclass CodeDirectorySuperBlob(typing.NamedTuple): 206a299b247SNuri Amari magic: int 207a299b247SNuri Amari length: int 208a299b247SNuri Amari count: int 209a299b247SNuri Amari blob_indices: typing.List[CodeDirectoryBlobIndex] 210a299b247SNuri Amari 211a299b247SNuri Amari @staticmethod 212*b71edfaaSTobias Hieta def make(buf: memoryview) -> "CodeDirectorySuperBlob": 213a299b247SNuri Amari super_blob_layout = ">III" 214a299b247SNuri Amari super_blob = struct.unpack_from(super_blob_layout, buf, 0) 215a299b247SNuri Amari 216a299b247SNuri Amari offset = struct.calcsize(super_blob_layout) 217a299b247SNuri Amari blob_indices = [] 218a299b247SNuri Amari for idx in range(super_blob[2]): 219a299b247SNuri Amari blob_indices.append(CodeDirectoryBlobIndex.make(buf[offset:])) 220a299b247SNuri Amari offset += CodeDirectoryBlobIndex.bytesize() 221a299b247SNuri Amari 222a299b247SNuri Amari return CodeDirectorySuperBlob(*super_blob, blob_indices) 223a299b247SNuri Amari 224*b71edfaaSTobias Hieta 225a299b247SNuri Amaridef unpack_null_terminated_string(buf: memoryview) -> str: 226a299b247SNuri Amari b = bytes(itertools.takewhile(lambda b: b != 0, buf)) 227a299b247SNuri Amari return b.decode() 228a299b247SNuri Amari 229*b71edfaaSTobias Hieta 230a299b247SNuri Amaridef main(): 231a299b247SNuri Amari parser = argparse.ArgumentParser() 232*b71edfaaSTobias Hieta parser.add_argument( 233*b71edfaaSTobias Hieta "binary", type=argparse.FileType("rb"), help="The file to analyze" 234*b71edfaaSTobias Hieta ) 235*b71edfaaSTobias Hieta parser.add_argument( 236*b71edfaaSTobias Hieta "offset", type=int, help="Offset to start of Code Directory data" 237*b71edfaaSTobias Hieta ) 238*b71edfaaSTobias Hieta parser.add_argument("size", type=int, help="Size of Code Directory data") 239*b71edfaaSTobias Hieta parser.add_argument( 240*b71edfaaSTobias Hieta "code_offset", type=int, help="Offset to start of code pages to hash" 241*b71edfaaSTobias Hieta ) 242*b71edfaaSTobias Hieta parser.add_argument("code_size", type=int, help="Size of the code pages to hash") 243a299b247SNuri Amari 244a299b247SNuri Amari args = parser.parse_args() 245a299b247SNuri Amari 246a299b247SNuri Amari args.binary.seek(args.offset) 247a299b247SNuri Amari super_blob_bytes = args.binary.read(args.size) 248a299b247SNuri Amari super_blob_mem = memoryview(super_blob_bytes) 249a299b247SNuri Amari 250a299b247SNuri Amari super_blob = CodeDirectorySuperBlob.make(super_blob_mem) 251a299b247SNuri Amari print(super_blob) 252a299b247SNuri Amari 253a299b247SNuri Amari for blob_index in super_blob.blob_indices: 254a299b247SNuri Amari code_directory_offset = blob_index.offset 255a299b247SNuri Amari code_directory = CodeDirectory.make(super_blob_mem[code_directory_offset:]) 256a299b247SNuri Amari print(code_directory) 257a299b247SNuri Amari 258a299b247SNuri Amari ident_offset = code_directory_offset + code_directory.identOffset 259*b71edfaaSTobias Hieta print( 260*b71edfaaSTobias Hieta "Code Directory ID: " 261*b71edfaaSTobias Hieta + unpack_null_terminated_string(super_blob_mem[ident_offset:]) 262*b71edfaaSTobias Hieta ) 263a299b247SNuri Amari 264a299b247SNuri Amari code_offset = args.code_offset 265a299b247SNuri Amari code_end = code_offset + args.code_size 266a299b247SNuri Amari page_size = 1 << code_directory.pageSize 267a299b247SNuri Amari args.binary.seek(code_offset) 268a299b247SNuri Amari 269a299b247SNuri Amari hashes_offset = code_directory_offset + code_directory.hashOffset 270a299b247SNuri Amari for idx in range(code_directory.nCodeSlots): 271*b71edfaaSTobias Hieta hash_bytes = bytes( 272*b71edfaaSTobias Hieta super_blob_mem[hashes_offset : hashes_offset + code_directory.hashSize] 273*b71edfaaSTobias Hieta ) 274a299b247SNuri Amari hashes_offset += code_directory.hashSize 275a299b247SNuri Amari 276a299b247SNuri Amari hasher = hashlib.sha256() 277a299b247SNuri Amari read_size = min(page_size, code_end - code_offset) 278a299b247SNuri Amari hasher.update(args.binary.read(read_size)) 279a299b247SNuri Amari calculated_hash_bytes = hasher.digest() 280a299b247SNuri Amari code_offset += read_size 281a299b247SNuri Amari 282a299b247SNuri Amari print("%s <> %s" % (hash_bytes.hex(), calculated_hash_bytes.hex())) 283a299b247SNuri Amari 284a299b247SNuri Amari if hash_bytes != calculated_hash_bytes: 285a299b247SNuri Amari sys.exit(-1) 286a299b247SNuri Amari 287a299b247SNuri Amari 288*b71edfaaSTobias Hietaif __name__ == "__main__": 289a299b247SNuri Amari main() 290