FreezeTool: Place blob in custom segment on macOS

The motivation behind this is in the code's comments. FreezeTool will
only put the blob in a custom segment if the provided deploy-stub
contains a __PANDA segment stub. Otherwise, it keeps the old behavior of
simply putting the blob at the end of the binary.
This commit is contained in:
Donny Lawrence 2020-04-24 18:38:35 -05:00 committed by rdb
parent 23041ec069
commit 0f3948005f
1 changed files with 129 additions and 12 deletions

View File

@ -639,6 +639,51 @@ okMissing = [
'direct.extensions_native.extensions_darwin',
]
# Since around macOS 10.15, Apple's codesigning process has become more strict.
# Appending data to the end of a Mach-O binary is now explicitly forbidden. The
# solution is to embed our own segment into the binary so it can be properly
# signed.
mach_header_64_layout = '<IIIIIIII'
# Each load command is guaranteed to start with the command identifier and
# command size. We'll call this the "lc header".
lc_header_layout = '<II'
# Each Mach-O segment is made up of sections. We need to change both the segment
# and section information, so we'll need to know the layout of a section as
# well.
section64_header_layout = '<16s16sQQIIIIIIII'
# These are all of the load commands we'll need to modify parts of.
LC_SEGMENT_64 = 0x19
LC_DYLD_INFO_ONLY = 0x80000022
LC_SYMTAB = 0x02
LC_DYSYMTAB = 0x0B
LC_FUNCTION_STARTS = 0x26
LC_DATA_IN_CODE = 0x29
lc_layouts = {
LC_SEGMENT_64: '<II16sQQQQIIII',
LC_DYLD_INFO_ONLY: '<IIIIIIIIIIII',
LC_SYMTAB: '<IIIIII',
LC_DYSYMTAB: '<IIIIIIIIIIIIIIIIIIII',
LC_FUNCTION_STARTS: '<IIII',
LC_DATA_IN_CODE: '<IIII',
}
# All of our modifications involve sliding some offsets, since we need to insert
# our data in the middle of the binary (we can't just put the data at the end
# since __LINKEDIT must be the last segment).
lc_indices_to_slide = {
b'__PANDA': [4, 6],
b'__LINKEDIT': [3, 5],
LC_DYLD_INFO_ONLY: [2, 4, 8, 10],
LC_SYMTAB: [2, 4],
LC_DYSYMTAB: [14],
LC_FUNCTION_STARTS: [2],
LC_DATA_IN_CODE: [2],
}
class Freezer:
class ModuleDef:
def __init__(self, moduleName, filename = None,
@ -1799,21 +1844,36 @@ class Freezer:
# Align to page size, so that it can be mmapped.
blob_align = 4096
# Add padding before the blob if necessary.
blob_offset = len(stub_data)
if (blob_offset & (blob_align - 1)) != 0:
pad = (blob_align - (blob_offset & (blob_align - 1)))
stub_data += (b'\0' * pad)
blob_offset += pad
assert (blob_offset % blob_align) == 0
assert blob_offset == len(stub_data)
# Also determine the total blob size now. Add padding to the end.
blob_size = pool_offset + len(pool)
if blob_size & (blob_align - 1) != 0:
pad = (blob_align - (blob_size & (blob_align - 1)))
blob_size += pad
# TODO: Support creating custom sections in universal binaries.
append_blob = True
if self.platform.startswith('macosx') and len(bitnesses) == 1:
# If our deploy-stub has a __PANDA segment, we know we're meant to
# put our blob there rather than attach it to the end.
load_commands = self._parse_macho_load_commands(stub_data)
if b'__PANDA' in load_commands.keys():
append_blob = False
if self.platform.startswith("macosx") and not append_blob:
# Take this time to shift any Mach-O structures around to fit our
# blob. We don't need to worry about aligning the offset since the
# compiler already took care of that when creating the segment.
blob_offset = self._shift_macho_structures(stub_data, load_commands, blob_size)
else:
# Add padding before the blob if necessary.
blob_offset = len(stub_data)
if (blob_offset & (blob_align - 1)) != 0:
pad = (blob_align - (blob_offset & (blob_align - 1)))
stub_data += (b'\0' * pad)
blob_offset += pad
assert (blob_offset % blob_align) == 0
assert blob_offset == len(stub_data)
# Calculate the offsets for the variables. These are pointers,
# relative to the beginning of the blob.
field_offsets = {}
@ -1893,9 +1953,13 @@ class Freezer:
blob += struct.pack('<Q', blob_offset)
with open(target, 'wb') as f:
f.write(stub_data)
assert f.tell() == blob_offset
f.write(blob)
if append_blob:
f.write(stub_data)
assert f.tell() == blob_offset
f.write(blob)
else:
stub_data[blob_offset:blob_offset + blob_size] = blob
f.write(stub_data)
os.chmod(target, 0o755)
return target
@ -2165,6 +2229,59 @@ class Freezer:
return fileoff + rel
print("Could not find memory address for symbol %s" % (symbol_name))
def _parse_macho_load_commands(self, macho_data):
"""Returns the list of load commands from macho_data."""
mach_header_64 = list(
struct.unpack_from(mach_header_64_layout, macho_data, 0))
num_load_commands = mach_header_64[4]
load_commands = {}
curr_lc_offset = struct.calcsize(mach_header_64_layout)
for i in range(num_load_commands):
lc = struct.unpack_from(lc_header_layout, macho_data, curr_lc_offset)
layout = lc_layouts.get(lc[0])
if layout:
# Make it a list since we want to mutate it.
lc = list(struct.unpack_from(layout, macho_data, curr_lc_offset))
if lc[0] == LC_SEGMENT_64:
stripped_name = lc[2].rstrip(b'\0')
if stripped_name in [b'__PANDA', b'__LINKEDIT']:
load_commands[stripped_name] = (curr_lc_offset, lc)
else:
load_commands[lc[0]] = (curr_lc_offset, lc)
curr_lc_offset += lc[1]
return load_commands
def _shift_macho_structures(self, macho_data, load_commands, blob_size):
"""Given the stub and the size of our blob, make room for it and edit
all of the necessary structures to keep the binary valid. Returns the
offset where the blob should be placed."""
for lc_key in load_commands.keys():
for index in lc_indices_to_slide[lc_key]:
load_commands[lc_key][1][index] += blob_size
if lc_key == b'__PANDA':
section_header_offset = load_commands[lc_key][0] + struct.calcsize(lc_layouts[LC_SEGMENT_64])
section_header = list(struct.unpack_from(section64_header_layout, macho_data, section_header_offset))
section_header[3] = blob_size
struct.pack_into(section64_header_layout, macho_data, section_header_offset, *section_header)
layout = LC_SEGMENT_64 if lc_key in [b'__PANDA', b'__LINKEDIT'] else lc_key
struct.pack_into(lc_layouts[layout], macho_data, load_commands[lc_key][0], *load_commands[lc_key][1])
blob_offset = load_commands[b'__PANDA'][1][5]
# Write in some null bytes until we write in the actual blob.
macho_data[blob_offset:blob_offset] = b'\0' * blob_size
return blob_offset
def makeModuleDef(self, mangledName, code):
result = ''
result += 'static unsigned char %s[] = {' % (mangledName)