brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.2 KiB · 2efcf80 Raw
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