blob: 75c01a2174c7e14ebddbdabc82e97a926364e0a5 [file] [log] [blame]
"""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()