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:
parent
23041ec069
commit
0f3948005f
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue