#!/usr/bin/env python3 """add_dylib.py — Add an LC_LOAD_DYLIB load command to a Mach-O 64-bit binary. Usage: python3 add_dylib.py [--weak] Inserts the new load command right after the existing load commands, before the first section data. Requires enough free space in the __TEXT header region (checked automatically). The binary is modified in-place; a .orig backup is created first. """ import struct, sys, shutil, os LC_LOAD_DYLIB = 0x0c LC_LOAD_WEAK_DYLIB = 0x80000018 # LC_LOAD_WEAK_DYLIB with LC_REQ_DYLD def main(): args = sys.argv[1:] weak = False if '--weak' in args: weak = True args.remove('--weak') if len(args) != 2: sys.exit("Usage: add_dylib.py [--weak]") path, dylib = args with open(path, 'rb') as f: data = bytearray(f.read()) # Parse Mach-O 64-bit header magic = struct.unpack_from(' 0 and sect_fileoff < min_section_off: min_section_off = sect_fileoff off += cmdsize # Build the LC_LOAD_DYLIB command name = dylib.encode() + b'\0' # name_offset = 24 (cmd + cmdsize + 4*4 for dylib struct) name_offset = 24 cmdsize = name_offset + len(name) # align to 8 bytes cmdsize = (cmdsize + 7) & ~7 needed = cmdsize free = min_section_off - hdr_end if free < needed: sys.exit(f"Not enough free space: need {needed}, have {free} " f"(hdr_end={hdr_end}, first_section={min_section_off})") # Build the command bytes cmd_id = LC_LOAD_WEAK_DYLIB if weak else LC_LOAD_DYLIB cmd = struct.pack('{ncmds+1}, " f"sizeofcmds={sizeofcmds}->{sizeofcmds+cmdsize}") print(f" free space was {free} bytes, backup saved as {path}.orig") if __name__ == '__main__': main()