"""Checks the validity of MachO binary signatures MachO binaries sometimes include a LC_CODE_SIGNATURE load command and corresponding section in the __LINKEDIT segment that together work to "sign" the binary. This script is used to check the validity of this signature. Usage: ./code-signature-check.py my_binary 800 300 0 800 Arguments: binary - The MachO binary to be tested offset - The offset from the start of the binary to where the code signature section begins size - The size of the code signature section in the binary code_offset - The point in the binary to begin hashing code_size - The length starting from code_offset to hash """ import argparse import collections import hashlib import itertools import struct import sys import typing class CodeDirectoryVersion: SUPPORTSSCATTER = 0x20100 SUPPORTSTEAMID = 0x20200 SUPPORTSCODELIMIT64 = 0x20300 SUPPORTSEXECSEG = 0x20400 class CodeDirectory: @staticmethod def make( buf: memoryview, ) -> typing.Union[ "CodeDirectoryBase", "CodeDirectoryV20100", "CodeDirectoryV20200", "CodeDirectoryV20300", "CodeDirectoryV20400", ]: _magic, _length, version = struct.unpack_from(">III", buf, 0) subtype = { CodeDirectoryVersion.SUPPORTSSCATTER: CodeDirectoryV20100, CodeDirectoryVersion.SUPPORTSTEAMID: CodeDirectoryV20200, CodeDirectoryVersion.SUPPORTSCODELIMIT64: CodeDirectoryV20300, CodeDirectoryVersion.SUPPORTSEXECSEG: CodeDirectoryV20400, }.get(version, CodeDirectoryBase) return subtype._make(struct.unpack_from(subtype._format(), buf, 0)) class CodeDirectoryBase(typing.NamedTuple): magic: int length: int version: int flags: int hashOffset: int identOffset: int nSpecialSlots: int nCodeSlots: int codeLimit: int hashSize: int hashType: int platform: int pageSize: int spare2: int @staticmethod def _format() -> str: return ">IIIIIIIIIBBBBI" class CodeDirectoryV20100(typing.NamedTuple): magic: int length: int version: int flags: int hashOffset: int identOffset: int nSpecialSlots: int nCodeSlots: int codeLimit: int hashSize: int hashType: int platform: int pageSize: int spare2: int scatterOffset: int @staticmethod def _format() -> str: return CodeDirectoryBase._format() + "I" class CodeDirectoryV20200(typing.NamedTuple): magic: int length: int version: int flags: int hashOffset: int identOffset: int nSpecialSlots: int nCodeSlots: int codeLimit: int hashSize: int hashType: int platform: int pageSize: int spare2: int scatterOffset: int teamOffset: int @staticmethod def _format() -> str: return CodeDirectoryV20100._format() + "I" class CodeDirectoryV20300(typing.NamedTuple): magic: int length: int version: int flags: int hashOffset: int identOffset: int nSpecialSlots: int nCodeSlots: int codeLimit: int hashSize: int hashType: int platform: int pageSize: int spare2: int scatterOffset: int teamOffset: int spare3: int codeLimit64: int @staticmethod def _format() -> str: return CodeDirectoryV20200._format() + "IQ" class CodeDirectoryV20400(typing.NamedTuple): magic: int length: int version: int flags: int hashOffset: int identOffset: int nSpecialSlots: int nCodeSlots: int codeLimit: int hashSize: int hashType: int platform: int pageSize: int spare2: int scatterOffset: int teamOffset: int spare3: int codeLimit64: int execSegBase: int execSegLimit: int execSegFlags: int @staticmethod def _format() -> str: return CodeDirectoryV20300._format() + "QQQ" class CodeDirectoryBlobIndex(typing.NamedTuple): type_: int offset: int @staticmethod def make(buf: memoryview) -> "CodeDirectoryBlobIndex": return CodeDirectoryBlobIndex._make( struct.unpack_from(CodeDirectoryBlobIndex.__format(), buf, 0) ) @staticmethod def bytesize() -> int: return struct.calcsize(CodeDirectoryBlobIndex.__format()) @staticmethod def __format() -> str: return ">II" class CodeDirectorySuperBlob(typing.NamedTuple): magic: int length: int count: int blob_indices: typing.List[CodeDirectoryBlobIndex] @staticmethod def make(buf: memoryview) -> "CodeDirectorySuperBlob": super_blob_layout = ">III" super_blob = struct.unpack_from(super_blob_layout, buf, 0) offset = struct.calcsize(super_blob_layout) blob_indices = [] for idx in range(super_blob[2]): blob_indices.append(CodeDirectoryBlobIndex.make(buf[offset:])) offset += CodeDirectoryBlobIndex.bytesize() return CodeDirectorySuperBlob(*super_blob, blob_indices) def unpack_null_terminated_string(buf: memoryview) -> str: b = bytes(itertools.takewhile(lambda b: b != 0, buf)) return b.decode() def main(): parser = argparse.ArgumentParser() parser.add_argument( "binary", type=argparse.FileType("rb"), help="The file to analyze" ) parser.add_argument( "offset", type=int, help="Offset to start of Code Directory data" ) parser.add_argument("size", type=int, help="Size of Code Directory data") parser.add_argument( "code_offset", type=int, help="Offset to start of code pages to hash" ) parser.add_argument("code_size", type=int, help="Size of the code pages to hash") args = parser.parse_args() args.binary.seek(args.offset) super_blob_bytes = args.binary.read(args.size) super_blob_mem = memoryview(super_blob_bytes) super_blob = CodeDirectorySuperBlob.make(super_blob_mem) print(super_blob) for blob_index in super_blob.blob_indices: code_directory_offset = blob_index.offset code_directory = CodeDirectory.make(super_blob_mem[code_directory_offset:]) print(code_directory) ident_offset = code_directory_offset + code_directory.identOffset print( "Code Directory ID: " + unpack_null_terminated_string(super_blob_mem[ident_offset:]) ) code_offset = args.code_offset code_end = code_offset + args.code_size page_size = 1 << code_directory.pageSize args.binary.seek(code_offset) hashes_offset = code_directory_offset + code_directory.hashOffset for idx in range(code_directory.nCodeSlots): hash_bytes = bytes( super_blob_mem[hashes_offset : hashes_offset + code_directory.hashSize] ) hashes_offset += code_directory.hashSize hasher = hashlib.sha256() read_size = min(page_size, code_end - code_offset) hasher.update(args.binary.read(read_size)) calculated_hash_bytes = hasher.digest() code_offset += read_size print("%s <> %s" % (hash_bytes.hex(), calculated_hash_bytes.hex())) if hash_bytes != calculated_hash_bytes: sys.exit(-1) if __name__ == "__main__": main()