forked from OSchip/llvm-project
258 lines
7.0 KiB
Python
258 lines
7.0 KiB
Python
"""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()
|