xref: /llvm-project/llvm/test/tools/llvm-objcopy/MachO/Inputs/code-signature-check.py (revision b71edfaa4ec3c998aadb35255ce2f60bba2940b0)
1"""Checks the validity of MachO binary signatures
2
3MachO binaries sometimes include a LC_CODE_SIGNATURE load command
4and corresponding section in the __LINKEDIT segment that together
5work to "sign" the binary. This script is used to check the validity
6of this signature.
7
8Usage:
9    ./code-signature-check.py my_binary 800 300 0 800
10
11Arguments:
12   binary - The MachO binary to be tested
13   offset - The offset from the start of the binary to where the code signature section begins
14   size - The size of the code signature section in the binary
15   code_offset - The point in the binary to begin hashing
16   code_size - The length starting from code_offset to hash
17"""
18
19import argparse
20import collections
21import hashlib
22import itertools
23import struct
24import sys
25import typing
26
27
28class CodeDirectoryVersion:
29    SUPPORTSSCATTER = 0x20100
30    SUPPORTSTEAMID = 0x20200
31    SUPPORTSCODELIMIT64 = 0x20300
32    SUPPORTSEXECSEG = 0x20400
33
34
35class CodeDirectory:
36    @staticmethod
37    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: int
59    length: int
60    version: int
61    flags: int
62    hashOffset: int
63    identOffset: int
64    nSpecialSlots: int
65    nCodeSlots: int
66    codeLimit: int
67    hashSize: int
68    hashType: int
69    platform: int
70    pageSize: int
71    spare2: int
72
73    @staticmethod
74    def _format() -> str:
75        return ">IIIIIIIIIBBBBI"
76
77
78class CodeDirectoryV20100(typing.NamedTuple):
79    magic: int
80    length: int
81    version: int
82    flags: int
83    hashOffset: int
84    identOffset: int
85    nSpecialSlots: int
86    nCodeSlots: int
87    codeLimit: int
88    hashSize: int
89    hashType: int
90    platform: int
91    pageSize: int
92    spare2: int
93
94    scatterOffset: int
95
96    @staticmethod
97    def _format() -> str:
98        return CodeDirectoryBase._format() + "I"
99
100
101class CodeDirectoryV20200(typing.NamedTuple):
102    magic: int
103    length: int
104    version: int
105    flags: int
106    hashOffset: int
107    identOffset: int
108    nSpecialSlots: int
109    nCodeSlots: int
110    codeLimit: int
111    hashSize: int
112    hashType: int
113    platform: int
114    pageSize: int
115    spare2: int
116
117    scatterOffset: int
118
119    teamOffset: int
120
121    @staticmethod
122    def _format() -> str:
123        return CodeDirectoryV20100._format() + "I"
124
125
126class CodeDirectoryV20300(typing.NamedTuple):
127    magic: int
128    length: int
129    version: int
130    flags: int
131    hashOffset: int
132    identOffset: int
133    nSpecialSlots: int
134    nCodeSlots: int
135    codeLimit: int
136    hashSize: int
137    hashType: int
138    platform: int
139    pageSize: int
140    spare2: int
141
142    scatterOffset: int
143
144    teamOffset: int
145
146    spare3: int
147    codeLimit64: int
148
149    @staticmethod
150    def _format() -> str:
151        return CodeDirectoryV20200._format() + "IQ"
152
153
154class CodeDirectoryV20400(typing.NamedTuple):
155    magic: int
156    length: int
157    version: int
158    flags: int
159    hashOffset: int
160    identOffset: int
161    nSpecialSlots: int
162    nCodeSlots: int
163    codeLimit: int
164    hashSize: int
165    hashType: int
166    platform: int
167    pageSize: int
168    spare2: int
169
170    scatterOffset: int
171
172    teamOffset: int
173
174    spare3: int
175    codeLimit64: int
176
177    execSegBase: int
178    execSegLimit: int
179    execSegFlags: int
180
181    @staticmethod
182    def _format() -> str:
183        return CodeDirectoryV20300._format() + "QQQ"
184
185
186class CodeDirectoryBlobIndex(typing.NamedTuple):
187    type_: int
188    offset: int
189
190    @staticmethod
191    def make(buf: memoryview) -> "CodeDirectoryBlobIndex":
192        return CodeDirectoryBlobIndex._make(
193            struct.unpack_from(CodeDirectoryBlobIndex.__format(), buf, 0)
194        )
195
196    @staticmethod
197    def bytesize() -> int:
198        return struct.calcsize(CodeDirectoryBlobIndex.__format())
199
200    @staticmethod
201    def __format() -> str:
202        return ">II"
203
204
205class CodeDirectorySuperBlob(typing.NamedTuple):
206    magic: int
207    length: int
208    count: int
209    blob_indices: typing.List[CodeDirectoryBlobIndex]
210
211    @staticmethod
212    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.offset
255        code_directory = CodeDirectory.make(super_blob_mem[code_directory_offset:])
256        print(code_directory)
257
258        ident_offset = code_directory_offset + code_directory.identOffset
259        print(
260            "Code Directory ID: "
261            + unpack_null_terminated_string(super_blob_mem[ident_offset:])
262        )
263
264        code_offset = args.code_offset
265        code_end = code_offset + args.code_size
266        page_size = 1 << code_directory.pageSize
267        args.binary.seek(code_offset)
268
269        hashes_offset = code_directory_offset + code_directory.hashOffset
270        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.hashSize
275
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_size
281
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