diff --git a/lld/test/MachO/Inputs/code-signature-check.py b/lld/test/MachO/Inputs/code-signature-check.py new file mode 100644 --- /dev/null +++ b/lld/test/MachO/Inputs/code-signature-check.py @@ -0,0 +1,257 @@ +"""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() diff --git a/lld/test/MachO/adhoc-codesign-hash.s b/lld/test/MachO/adhoc-codesign-hash.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/adhoc-codesign-hash.s @@ -0,0 +1,23 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t; mkdir -p %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/empty-arm64-macos.o %s +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-iossimulator -o %t/empty-arm64-iossimulator.o %s +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/empty-x86_64-macos.o %s + +# RUN: %lld -arch arm64 -dylib -adhoc_codesign -o %t/empty-arm64-macos.dylib %t/empty-arm64-macos.o +# RUN: %lld -arch arm64 -dylib -adhoc_codesign -o %t/empty-arm64-iossimulator.dylib %t/empty-arm64-iossimulator.o +# RUN: %lld -arch x86_64 -dylib -adhoc_codesign -o %t/empty-x86_64-macos.dylib %t/empty-x86_64-macos.o + +# RUN: obj2yaml %t/empty-arm64-macos.dylib | FileCheck %s -D#DATA_OFFSET=16400 -D#DATA_SIZE=304 +# RUN: obj2yaml %t/empty-arm64-iossimulator.dylib | FileCheck %s -D#DATA_OFFSET=16400 -D#DATA_SIZE=304 +# RUN: obj2yaml %t/empty-x86_64-macos.dylib | FileCheck %s -D#DATA_OFFSET=4112 -D#DATA_SIZE=208 + +# CHECK: - cmd: LC_CODE_SIGNATURE +# CHECK-NEXT: cmdsize: 16 +# CHECK-NEXT: dataoff: [[#DATA_OFFSET]] +# CHECK-NEXT: datasize: [[#DATA_SIZE]] + +# RUN: %python %p/Inputs/code-signature-check.py %t/empty-arm64-macos.dylib 16400 304 0 16400 +# RUN: %python %p/Inputs/code-signature-check.py %t/empty-arm64-iossimulator.dylib 16400 304 0 16400 +# RUN: %python %p/Inputs/code-signature-check.py %t/empty-x86_64-macos.dylib 4112 208 0 4112