def main(exePath): print 'dyld_shared_cache decache Fix' print print 'During the production of this script,no Mach-O was killed' print print 'Designed by Naville Zhang' rawFile = open(exePath, 'r') rawFile.seek(-4, 2) #if cmp(rawFile.read(),'BOOM') == 0: # print '#Error: Executable has been patched' # exit() macho=MachO.MachO(exePath) for header in macho.headers: IterateLCInHeader(header) FixMachO(header) replaceDATA(header) print '[+]Generating new executable' spliceHeadersAndRawStuff(header,exePath) print '[+]New executable generated' print '[+]Overwriting raw executable' os.system('mv %s_tmp %s' % (exePath, exePath)) print '[+]Giving execute permission to new executable' givex(exePath) print print '[+]All done.' return
def replace_methname(macho_file, methname_json, output_dir): """ Map method names in Mach-O file with the JSON file """ if not os.path.isfile(macho_file): raise("passing not exist file " + macho_file) if not os.path.isfile(methname_json): raise("passing not exist file " + methname_json) if output_dir is not None and not os.path.isdir(output_dir): raise("passing not exist dir " + output_dir) macho = MachO.MachO(macho_file) name_dict = None with open(methname_json) as json_file: name_dict = json.load(json_file) for header in macho.headers: ch_methname_sect(header, name_dict) ch_symtab(header, name_dict) ori_dir, filename = os.path.split(macho_file) if output_dir is None: output_dir = ori_dir output = os.path.join(output_dir, filename) try: copy2(macho_file, output_dir) except SameFileError: pass with open(output, 'r+b') as fp: macho.write(fp) os.chmod(output, 0o755)
def __call__(self, path): package_file = self.package_file path_by_file = self.path_by_file libs_folder = self.libs_folder if path.startswith("@@HOMEBREW_CELLAR@@"): new_path = libs_folder.joinpath("/".join(path.split("/")[3:])) new_path = "@loader_path/" + relative_to(new_path, package_file.parent) logging.debug(f"{package_file}: {path} -> {new_path}") return new_path else: for new_path in path_by_file.get(path.split("/")[-1], [path]): if path == new_path: logging.debug(f"{package_file}: {path} -> {new_path}") return path elif new_path == str(package_file): continue elif not str(new_path).endswith(".dylib") or not str(new_path).endswith( ".so" ): try: m = MachO.MachO(str(new_path)) except Exception: continue if m.headers[0].filetype == "execute": continue new_path = "@loader_path/" + relative_to(new_path, package_file.parent) logging.debug(f"{package_file}: {path} -> {new_path}") return new_path logging.debug(f"{package_file}: {path} -> {path}") return path
def hash_macho0(exe): headers = MachO.MachO(exe).headers if len(headers) > 1: logging.debug('Mach-O binary is FAT') with open(exe, 'rb') as f: data = bytes() for header in headers: f.seek(header.offset, 0) start, end = sys.maxsize, 0 for (lc, segment, sections) in header.commands: if (mach_o.LC_CODE_SIGNATURE == lc.cmd): logging.warning('Mach-O binary has a signature section') # The minimum section offset of all load commands is the start of VMP signing part if (lc.cmd in (mach_o.LC_SEGMENT_64, mach_o.LC_SEGMENT) and segment.segname.startswith( mach_o.SEG_TEXT.encode('utf-8'))): for section in sections: start = min(start, section.offset) # Expect the String Table is at the end of unsigned binary followed by the code # signature, so the end of String Table is the end of VMP signing part if (mach_o.LC_SYMTAB == lc.cmd): end = segment.stroff + segment.strsize if (start >= end): logging.error( 'Failed to assemble VMP/Mach-O signing body: %d-%d', start, end) raise ValueError( 'Failed to assemble VMP/Mach-O signing body: %d-%d' % (start, end)) f.seek(start, 1) data += f.read(end - start) return compute_sha512(data)
def shared_libraries(binary_file): """ Return a list of the shared libraries referenced by a Mach-O binary """ result = list() try: headers = MachO.MachO(binary_file).headers except ValueError: warn("skipping {}, it is probably not a Mach-O file".format( binary_file)) return [] except IOError: warn("skipping {}, it is probably not a regular file".format( binary_file)) return [] for architecture_header in headers: if is_current_architecture(architecture_header.header): result.extend([ ShlibRef(load_path, 'weak' in load_type) for _, load_type, load_path in architecture_header.walkRelocatables() ]) else: warn("skipping header in {} with arch {}".format( binary_file, MachO.CPU_TYPE_NAMES[architecture_header.header.cputype])) return result
def rpath_entries(binary_file): """ Return a list of rpath entries in the specified binary file The mach-O header should list contain a list of rpaths to be tried when attempting to process the actual load commands in the rest of the header This could easily be a list comprehension, but this is a bit more readable The original version was inspired by rpath.list_rpaths @: https://github.com/enthought/machotools/tree/master/machotools """ result = list() for architecture_header in MachO.MachO(binary_file).headers: for _, command, data in architecture_header.commands: if type(command) is not mach_o.rpath_command: continue # the entry is null terminated, so we take that out. entry = data.rstrip(b'\x00') if entry.startswith('@loader_path'): # it is important to expand loader_path now because it refers # to the path of the file that contains the @loader_path entry entry = expand_load_variables(entry, binary_file) result.append(entry) return result
def load_macho(filename): file_data = open(filename).read() macho = MachO.MachO(filename) header = macho.headers[0] cputype = CPU_TYPE_NAMES[header.header.cputype] assert cputype == "ARM" assert header.header.filetype == MH_EXECUTE regions = [] text_base = None entry_point = None segments = [ cmd for _, cmd, _ in header.commands if type(cmd) == segment_command ] for cmd in segments: name = cmd.segname.replace("\x00", "") if name == SEG_PAGEZERO: continue if name == SEG_TEXT: assert cmd.fileoff == 0 text_base = cmd.vmaddr filesize = align(cmd.filesize, 4096) vmsize = align(cmd.vmsize, 4096) regions.append((cmd.vmaddr, filesize, file_data[cmd.fileoff:cmd.fileoff + filesize])) if vmsize != filesize: regions.append((cmd.vmaddr + filesize, vmsize - filesize, None)) assert text_base is not None dyld_info = None symbols = {} for lc, cmd, data in header.commands: if type(cmd) == entry_point_command: # the entry point is given as a file offset. # assume it's in the text segment... entry_point = cmd.entryoff + text_base elif type(cmd) == dyld_info_command: dyld_info = DyldInfo(filename, cmd, segments) elif type(cmd) == symtab_command: #TODO: populate symbols pass #assert entry_point is not None stack_bottom = 0x70000000 # ? stack_size = 0x20000 # 128k ? regions.append((stack_bottom - stack_size, stack_size, None)) # print [(hex(a), hex(b), c[:16] if c else c) for a, b, c in regions] return (regions, entry_point, stack_bottom, symbols, dyld_info)
def test_known_load_command_should_succeed(self): macho_uuid = uuid.UUID("6894C0AE-C8B7-4E0B-A529-30BBEBA3703B") with temporary_macho_file([lc_uuid(macho_uuid)]) as macho_filename: macho = MachO.MachO(macho_filename, allow_unknown_load_commands=True) self.assertEqual(len(macho.headers), 1) self.assertEqual(len(macho.headers[0].commands), 1) load_command, command, _ = macho.headers[0].commands[0] self.assertEqual(load_command.cmd, mach_o.LC_UUID) self.assertEqual(uuid.UUID(bytes=command.uuid), macho_uuid)
def get_macho(fn): # mod to make the header okay # MH_CIGAM_64 is good dat = open(fn, "rb").read() dat = b"\xcf\xfa\xed\xfe"+dat[4:] from tempfile import NamedTemporaryFile with NamedTemporaryFile(delete=False) as f: f.write(dat) f.close() return MachO.MachO(f.name)
def test_unknown_load_command_should_succeed_with_flag(self): with temporary_macho_file([lc_unknown()]) as macho_filename: macho = MachO.MachO(macho_filename, allow_unknown_load_commands=True) self.assertEqual(len(macho.headers), 1) self.assertEqual(len(macho.headers[0].commands), 1) load_command, command, data = macho.headers[0].commands[0] self.assertEqual(load_command.cmd, 0x707A11ED) self.assertIsInstance(command, mach_o.load_command) self.assertEqual(struct.unpack(">I", data), (42, ))
def check_compatibility(cls, spec, main_obj): with stream_or_path(spec) as stream: if not cls.is_compatible(stream): return False # It's definitely a MachO parsed_macho = MachOLoader.MachO(spec) archs = set(MachO.get_arch_from_header(mach_header.header) for mach_header in parsed_macho.headers) if main_obj.arch.name.lower() in archs: return True return False
def analyze_macho(self, data): ''' start analyzing macho logic, add descriptions and get words and wordsstripped from the file ''' macho = MachO.MachO(data["Location"]["File"]) data["MACHO"] = deepcopy(self.datastruct) fbuffer = data["FilesDumps"][data["Location"]["File"]] data["MACHO"]["General"]: {} data["MACHO"]["Sections"] = self.get_sections(macho, fbuffer) data["MACHO"]["Libraries"] = self.get_libs(macho) data["MACHO"]["Symbols"] = self.get_symbols(macho) data["MACHO"]["Undefined Symbols"] = self.get_undef_symbols(macho) data["MACHO"]["External Symbols"] = self.get_extdef_symbols(macho) data["MACHO"]["Local Symbols"] = self.get_local_symbols(macho) add_description("ManHelp", data["MACHO"]["Symbols"], "Symbol") get_words(data, data["Location"]["File"])
def test_mix_of_known_and_unknown_load_commands_should_allow_unknown_with_flag( self, ): macho_uuid = uuid.UUID("6894C0AE-C8B7-4E0B-A529-30BBEBA3703B") with temporary_macho_file([lc_unknown(), lc_uuid(macho_uuid)]) as macho_filename: macho = MachO.MachO(macho_filename, allow_unknown_load_commands=True) self.assertEqual(len(macho.headers), 1) self.assertEqual(len(macho.headers[0].commands), 2) load_command, command, data = macho.headers[0].commands[0] self.assertEqual(load_command.cmd, 0x707A11ED) self.assertIsInstance(command, mach_o.load_command) self.assertEqual(struct.unpack(">I", data), (42, )) load_command, command, _ = macho.headers[0].commands[1] self.assertEqual(load_command.cmd, mach_o.LC_UUID) self.assertEqual(uuid.UUID(bytes=command.uuid), macho_uuid)
def check_architectures(app): ''' info检查是否支持64位 demo:armv7, arm64, armv7s ''' from macholib import MachO, mach_o m = MachO.MachO(app) arcs = [] for header in m.headers: cpu_type = header.header.cputype cpu_subtype = header.header.cpusubtype arch = str(mach_o.CPU_TYPE_NAMES.get(cpu_type, cpu_type)).lower() if cpu_type == 12: if cpu_subtype == 0: arch = 'armall' elif cpu_subtype == 5: arch = 'armv4t' elif cpu_subtype == 6: arch = 'armv6' elif cpu_subtype == 7: arch = 'armv5tej' elif cpu_subtype == 8: arch = 'arm_xscale' elif cpu_subtype == 9: arch = 'armv7' elif cpu_subtype == 10: arch = 'armv7f' elif cpu_subtype == 11: arch = 'armv7s' elif cpu_subtype == 12: arch = 'armv7k' elif cpu_subtype == 13: arch = 'armv8' elif cpu_subtype == 14: arch = 'armv6m' elif cpu_subtype == 15: arch = 'armv7m' elif cpu_subtype == 16: arch = 'armv7em' elif cpu_type == 16777228: arch = 'arm64' arcs.append(arch) return arcs
def main(executableName): print print 'MachO Headers Eraser' print print 'This script may cause some unpredictable issues, try delete things in the safe list if your program doesn\'t work after patched.' print print '[+]Checking if patched' rawFile = open(executableName, 'r') rawFile.seek(-4, 2) if cmp(rawFile.read(), 'BOOM') == 0: print '#Error: Executable has been patched' exit() print '[+]Making backup' os.system('cp %s %s_bak' % (executableName, executableName)) print '[+]Reading raw executable' machoHeader = MachO.MachO(executableName) print '[+]%s readed' % machoHeader.filename for header in machoHeader.headers: eraseLoadCommandInHeader(header) print '[+]Generating new executable' spliceHeadersAndRawStuff(machoHeader, executableName) print '[+]New executable generated' print '[+]Overwriting raw executable' os.system('mv %s_tmp %s' % (executableName, executableName)) #Insert SYMTAB Operations Here pathname = os.path.dirname(sys.argv[0]) Command = pathname + '/SymbolCleaner ' + executableName + " " + str( SYMTAB["symoff"]) + " " + str( SYMTAB["nsyms"]) + " 0 0 " + executableName + "NoSymbol" + " 32" system(str(Command)) print '[+]Giving execute permission to new executable' givex(executableName) givex(executableName + "NoSymbol") print print '[+]All done.' return
def get_archs(path: Path) -> List[str]: """Get architecture(s) of `path`""" m = None archs: List[str] = [] try: m = MachO.MachO(path) except: return [] for header in m.headers: cpu_type = header.header.cputype cpu_subtype = header.header.cpusubtype arch = str(mach_o.CPU_TYPE_NAMES.get(cpu_type, cpu_type)).lower() if cpu_type == 12: if cpu_subtype == 0: arch = 'armall' elif cpu_subtype == 5: arch = 'armv4t' elif cpu_subtype == 6: arch = 'armv6' elif cpu_subtype == 7: arch = 'armv5tej' elif cpu_subtype == 8: arch = 'arm_xscale' elif cpu_subtype == 9: arch = 'armv7' elif cpu_subtype == 10: arch = 'armv7f' elif cpu_subtype == 11: arch = 'armv7s' elif cpu_subtype == 12: arch = 'armv7k' elif cpu_subtype == 13: arch = 'armv8' elif cpu_subtype == 14: arch = 'armv6m' elif cpu_subtype == 15: arch = 'armv7m' elif cpu_subtype == 16: arch = 'armv7em' elif cpu_type == 16777228: arch = 'arm64' archs.append(arch) return archs
def MacCheckBinary(z, binary): with TempUnzip(z, 'Unvanquished.app/Contents/MacOS/' + binary) as path: macho = MachO.MachO(path) header, = macho.headers # Check PIE MH_PIE = 0x200000 if not header.header.flags & MH_PIE: yield f"Mac binary '{binary}' is not PIE" # Check rpath rpaths = { command[2].rstrip(b'\0').rstrip(b'/') for command in header.commands if isinstance(command[1], mach_o.rpath_command) } rpaths.discard(b'@executable_path') if rpaths: yield f"Mac binary '{binary}' has unwanted rpaths {rpaths}"
def get_version_from_mach_binary(filename): from macholib import MachO # pylint: disable=import-outside-toplevel machofile = MachO.MachO(filename) fpc_offset, fpc_size = 0, 0 for (_load_cmd, _cmd, _data) in machofile.headers[0].commands: for data in _data: if data and hasattr(data, "sectname") and data.sectname: sectname = data.sectname.rstrip(b'\0') if sectname == b"fpc.resources": fpc_offset = data.offset fpc_size = data.size if fpc_offset > 0: with open(filename, "rb") as file: file.seek(fpc_offset) return read_fixed_file_info(file.read(fpc_size)) raise ValueError(f"No version information embedded in '{filename}'")
def __init__(self, arch, path, baseAddr): self.arch = arch self.path = path self.baseAddr = baseAddr self.defaultBaseAddr = 0 self.macho = None self.sections = {} self.symbols = {} fullPath = os.path.join(self.arch.root, path) macho = MachO.MachO(fullPath) for h in macho.headers: if h.header.cputype == self.arch.cpuType: self.macho = h # find the endAddr endAddr = 0 for c in self.macho.commands: if c[0].cmd == MachO.LC_SEGMENT or c[ 0].cmd == MachO.LC_SEGMENT_64: if c[1].segname.strip("\0") == "__TEXT": self.defaultBaseAddr = c[1].vmaddr if (c[1].vmaddr + c[1].vmsize) > endAddr: endAddr = c[1].vmaddr + c[1].vmsize if c[1].maxprot & 0x4: # VM_PROT_EXECUTE # add all the sections for section in c[2]: self.sections[section.addr] = section self.endAddr = self.baseAddr + endAddr #print "%s: %x - %x" % (self.path, self.baseAddr, self.endAddr) # get the symbols (not the best way) if self.arch.cpuType & 0x01000000: arch = "x86_64" # out version doesn't have the 64bit support else: arch = MachO.CPU_TYPE_NAMES[self.arch.cpuType] symbolText = subprocess.check_output( ['/usr/bin/otool', '-Iv', '-arch', arch, fullPath]) symbolLines = symbolText.split("\n") for l in symbolLines: parts = l.split() if len(parts) == 3 and len(parts[0]) > 2: if parts[0][:2] == "0x": offset = int(parts[0][2:], 16) - self.defaultBaseAddr self.symbols[offset] = parts[2].strip()
def check_architetures(app): """ 架构检查 arm64 arm7 arm7s i386 x864 以下是支付SDK的 ['ARM_CPU_SUBTYPE_ARM_V7_32-bit', 'ARM64_CPU_SUBTYPE_ARM64_ALL_64-bit'] """ m = MachO.MachO(app) archs = [] for header in m.headers: if header.MH_MAGIC == mach_o.MH_MAGIC_64 or header.MH_MAGIC == mach_o.MH_CIGAM_64: sz = '64-bit' else: sz = '32-bit' arch = mach_o.CPU_TYPE_NAMES.get(header.header.cputype, header.header.cputype) subarch = mach_o.get_cpu_subtype(header.header.cputype, header.header.cpusubtype) archs.append('_'.join((arch, sz))) return archs
def main(executableName): print print 'MachO Headers Eraser' print print 'This script may cause some unpredictable issues, try delete things in the safe list if your program doesn\'t work after patched.' print print '[+]Checking if patched' rawFile = open(executableName, 'r') rawFile.seek(-4, 2) if cmp(rawFile.read(), 'BOOM') == 0: print '#Error: Executable has been patched' exit() print '[+]Making backup' os.system('cp %s %s_bak' % (executableName, executableName)) print '[+]Reading raw executable' machoHeader = MachO.MachO(executableName) print '[+]%s readed' % machoHeader.filename for header in machoHeader.headers: eraseLoadCommandInHeader(header) print '[+]Generating new executable' spliceHeadersAndRawStuff(machoHeader, executableName) print '[+]New executable generated' print '[+]Overwriting raw executable' os.system('mv %s_tmp %s' % (executableName, executableName)) print '[+]Giving execute permission to new executable' givex(executableName) print print '[+]All done.' return
def __init__(self, path): self.path = path self.macho = MachO.MachO(self.path)
def test_unknown_load_command_should_fail(self): with temporary_macho_file([lc_unknown()]) as macho_filename: with self.assertRaises(ValueError) as assert_context: MachO.MachO(macho_filename)
def _processNode(nodes: queue.Queue, node: dict, ignoreSystem: bool) -> list: paragraph = [] path = node["path"] node["exists"] = os.path.exists(path) if node["exists"]: try: macho = MachO.MachO(path) except: macho = None node["parsed"] = macho is not None if node["parsed"]: node["@loader_path"] = os.path.dirname(path) node["arch"] = {} for header in macho.headers: archName = mach_o.CPU_TYPE_NAMES.get(header.header.cputype, header.header.cputype) if "restrictarch" in node and archName != node["restrictarch"]: continue node["arch"][archName] = {} node["arch"][archName]["name"] = archName node["arch"][archName]["rpaths"] = [] node["arch"][archName]["dependencies"] = [] if header.header.filetype == mach_o.MH_EXECUTE: node["@executable_path"] = node["@loader_path"] for command in header.commands: if command[0].cmd == mach_o.LC_RPATH: node["arch"][archName]["rpaths"].append( command[2][0:command[2].find(b'\x00', 0)].decode( sys.getfilesystemencoding())) # @rpath/ # Dyld maintains a current stack of paths called the run path list. When @rpath is encountered it is substituted with each path in the run path list until a loadable dylib if found. The run path stack is built from the LC_RPATH # load commands in the dependency chain that lead to the current dylib load. You can add an LC_RPATH load command to an image with the -rpath option to ld(1). You can even add a LC_RPATH load command path that starts with # @loader_path/, and it will push a path on the run path stack that relative to the image containing the LC_RPATH. The use of @rpath is most useful when you have a complex directory structure of programs and dylibs which can be # installed anywhere, but keep their relative positions. This scenario could be implemented using @loader_path, but every client of a dylib could need a different load path because its relative position in the file system is dif- # ferent. The use of @rpath introduces a level of indirection that simplfies things. You pick a location in your directory structure as an anchor point. Each dylib then gets an install path that starts with @rpath and is the path # to the dylib relative to the anchor point. Each main executable is linked with -rpath @loader_path/zzz, where zzz is the path from the executable to the anchor point. At runtime dyld sets it run path to be the anchor point, then # each dylib is found relative to the anchor point. # TL;DR -- expanded rpaths are cascading down dependency chain rpathStack = [] if "parentRpathStack" in node: rpathStack = list(node["parentRpathStack"]) for rpath in node["arch"][archName]["rpaths"]: exists, resolvedPath = _resolvePath(node, rpath) if resolvedPath not in rpathStack: rpathStack.append(resolvedPath) for index, name, fileName in header.walkRelocatables(): node["arch"][archName]["dependencies"].append( {"name": fileName}) exists = False newNodePath = None if "@rpath" in fileName: for rpath in rpathStack: exists, fullPath = _resolvePath( node, fileName.replace("@rpath", rpath)) if exists: newNodePath = fullPath break else: exists, newNodePath = _resolvePath(node, fileName) if exists: newNode = { "path": newNodePath, "parentRpathStack": rpathStack, "restrictarch": archName, "system": _isSystemLib(newNodePath) } if "@executable_path" in node: newNode["@executable_path"] = node["@executable_path"] node["arch"][archName]["dependencies"][-1].update(newNode) if not ignoreSystem or not newNode["system"]: nodes.put(newNode) else: paragraph.append(Line(LogLevel.WARNING, 4, 1, "not Mach-O")) return paragraph
#!/usr/bin/env python3 from hexdump import hexdump # mod to make the header okay # MH_CIGAM_64 is good from macholib import MachO a = MachO.MachO("model.hwx") # load commands for c in a.headers[0].commands: print(c[0]) if c[0].cmd == 25: print(c[1]) for section in c[2]: print(section.segname.strip(b'\0'), section.sectname.strip(b'\0'), hex(section.addr), hex(section.size), "@", hex(c[1].fileoff)) #print(dir(section)) if c[1].filesize > 0: hexdump(section.section_data) # this parser is wrong (fixed with 64-bit one) from macholib import SymbolTable sym = SymbolTable.SymbolTable(a) syms = {} for l in sym.nlists: print(l) if l[0].n_value != 0: syms[l[1]] = l[0].n_value
def main(): json_fname = pathlib.Path(sys.argv[1]) base_path = json_fname.parent with open(json_fname) as json_input: dict_json = json.load(json_input) app_name = dict_json["app_name"] version = dict_json["version"] identifier = dict_json["identifier"] icon = dict_json["icon"] packages = dict_json["packages"] ignore_packages = dict_json.get("ignore_packages", []) pip_packages = dict_json.get("pip_packages", []) commands = dict_json.get("commands", []) mac_version = dict_json["mac_version"] package_type = dict_json["app_package"]["source"]["type"] if package_type == "file": package_path = base_path.joinpath(dict_json["app_package"]["source"]["path"]) else: package_url = dict_json["app_package"]["source"]["path"] sha = dict_json["app_package"]["source"].get("sha", None) package_path = download_and_check(package_url, sha) patches = dict_json["app_package"]["source"].get("patches", []) start_script = dict_json["app_package"]["start_script"] build_commands = dict_json["app_package"].get("build_commands", []) cleanup_patterns = dict_json.get("cleanup", []) # Creating folders app_folder = base_path.joinpath(app_name + ".app") app_folder.mkdir(parents=True, exist_ok=True) resources_folder = app_folder.joinpath("Contents/Resources") resources_folder.mkdir(parents=True, exist_ok=True) binary_folder = app_folder.joinpath("Contents/MacOS") binary_folder.mkdir(parents=True, exist_ok=True) libs_folder = resources_folder.joinpath("libs") libs_folder.mkdir(parents=True, exist_ok=True) application_folder = resources_folder.joinpath("app") application_folder.mkdir(parents=True, exist_ok=True) start_script_path = application_folder.joinpath(start_script) start_script_folder = start_script_path.parent package_files = download_packages(packages, mac_version, ignore_packages) extracted_files = [] for package in package_files: extracted_files.extend( extract_files(package_files[package]["file"], libs_folder) ) if "python" in package_files: logging.info("Creating symlink") major, minor, _ = package_files["python"]["version"].split(".") site_packages = libs_folder.joinpath(f"lib/python{major}.{minor}/site-packages") link_site_packages = libs_folder.joinpath( f"Frameworks/Python.framework/Versions/{major}.{minor}/lib/python{major}.{minor}/site-packages" ) os.symlink( relative_to(site_packages, link_site_packages.parent), link_site_packages ) path_by_file = {} for extracted_file in extracted_files: # path_by_file["/".join(extracted_file.parts[-2:])] = extracted_file try: path_by_file[str(extracted_file.parts[-1])].append(extracted_file) except: path_by_file[str(extracted_file.parts[-1])] = [extracted_file] # print("writing json file") # with open("/tmp/files.json", "w") as f: # # f.write(JSONEncoder().encode(path_by_file)) # json.dump(path_by_file, f) for package_file in extracted_files: extension = package_file.suffixes if package_file.is_file() and not package_file.is_symlink(): try: # print(package_file) macho = MachO.MachO(str(package_file)) except Exception: # Not lib or executable continue rewrote = False changer = SharedLibraryChanger(package_file, path_by_file, libs_folder) for header in macho.headers: if macho.rewriteLoadCommands(changer): rewrote = True if rewrote: with package_file.open("rb+") as f: f.seek(0) macho.write(f) # Installing python packages using pip PYTHON_EXEC = libs_folder.joinpath("bin/python3") download_and_install_pip(PYTHON_EXEC) for pip_package in pip_packages: pip_install(PYTHON_EXEC, pip_package) logging.info("Running commands") for command in commands: logging.info(f"\t{command}") command = shlex.split(command) run_cmd(command, cwd=str(resources_folder)) # Copying icon in the package shutil.copy2(base_path.joinpath(icon), resources_folder) # Creating Info.plist file create_app_info(app_folder, identifier, app_name, version, icon) # Copying or extracting app files inside the package if package_type == "file": shutil.copy2(package_path, str(application_folder)) else: extract_files(package_path, start_script_folder, 1) # applying patch logging.info("Applying patches") for patch in patches: patch = base_path.joinpath(patch).resolve() logging.info(patch) run_cmd(["patch", "-p", "1", "-i", str(patch)], cwd=str(start_script_folder)) c_libs_folders = [libs_folder.joinpath("lib").resolve()] c_include_folders = [libs_folder.joinpath("include").resolve()] my_env = os.environ.copy() my_env["PATH"] = str(PYTHON_EXEC.parent.resolve()) + ":" + my_env["PATH"] my_env["CFLAGS"] = ( " ".join("-I{}".format(i) for i in c_include_folders) + " " + " ".join("-L{}".format(i) for i in c_libs_folders) ) my_env["CXXFLAGS"] = ( " ".join("-I{}".format(i) for i in c_include_folders) + " " + " ".join("-L{}".format(i) for i in c_libs_folders) ) logging.info("Running scripts") logging.debug("PATH=" + my_env["PATH"]) logging.debug("CFLAGS=" + my_env["CFLAGS"]) logging.debug("CXXFLAGS=" + my_env["CXXFLAGS"]) for build_command in build_commands: logging.info(build_command) build_command = shlex.split(build_command) run_cmd(build_command, env=my_env, cwd=str(start_script_folder)) # Rewriting any shared lib in app folder for package_file in start_script_folder.glob("**/*"): extension = package_file.suffixes if package_file.is_file() and not package_file.is_symlink(): try: # print(package_file) macho = MachO.MachO(str(package_file)) except Exception: # Not lib or executable continue rewrote = False changer = SharedLibraryChanger(package_file, path_by_file, libs_folder) for header in macho.headers: if macho.rewriteLoadCommands(changer): rewrote = True if rewrote: with package_file.open("rb+") as f: f.seek(0) macho.write(f) create_launcher( app_folder, app_name, start_script_path, relative_to(PYTHON_EXEC, start_script_folder), ) if cleanup_patterns: exclude_files(resources_folder, cleanup_patterns)
def __init__(self, binary, target_arch=None, **kwargs): l.warning('The Mach-O backend is not well-supported. Good luck!') super(MachO, self).__init__(binary, **kwargs) parsed_macho = MachOLoader.MachO(self.binary) # First try to see which arch the main binary has # Then try to match it to its dependencies if not self.is_main_bin: # target_arch prefrence goes to main bin... target_arch = self.loader.main_object.arch.name.lower() # If we have a target_arch, try to match it up with one of the FAT slices if target_arch: self._header = self.match_target_arch_to_header(target_arch, parsed_macho.headers) if not self._header: print(self.binary) # Print out all architectures found? raise CLEError("Couldn't find architecture %s" % target_arch) # Otherwise, we'll just pick one.. else: if len(parsed_macho.headers) > 1: l.warning('No target slice specified. Picking one at random.. Good luck!') self._header = parsed_macho.headers[0] arch_ident = self.get_arch_from_header(self._header.header) # TODO: add lsb/msb support here properly. self._header.endian is exactly it self.set_arch(archinfo.arch_from_id(arch_ident, endness="lsb")) self.struct_byteorder = self._header.endian # self._header.filetype == 6 indicates its a dylib too # https://llvm.org/doxygen/Support_2MachO_8h_source.html#36 if self._header.filetype == 'dylib': self.pic = True else: # XXX: Handle other filetypes. i.e. self._header.filetype # This means we can't load multiple `executables` into the same process # .. Seems fine self.pic = bool(self._header.header.flags & 0x200000) if self.is_main_bin else True self.flags = None # binary flags self.imported_libraries = ["Self"] # ordinal 0 = SELF_LIBRARY_ORDINAL # This was what was historically done: self.sections_by_ordinal.extend(seg.sections) self.sections_by_ordinal = [None] # ordinal 0 = None == Self self.exports_by_name = {} # note exports is currently a raw and unprocessed datastructure. # If we intend to use it we must first upgrade it to a class or somesuch self.entryoff = None self.unixthread_pc = None self.os = "Darwin" self.export_blob = None # exports trie self.binding_blob = None # binding information self.lazy_binding_blob = None # lazy binding information self.weak_binding_blob = None # weak binidng information self.binding_done = False # if true binding was already done and do_bind will be a no-op # Module level constructors / destructors self.mod_init_func_pointers = [] self.mod_term_func_ponters = [] # Library dependencies. self.linking = 'dynamic' # static is impossible in macos... kinda # For some analysis the insertion order of the symbols is relevant and needs to be kept. # This is has to be separate from self.symbols because the latter is sorted by address self._ordered_symbols = [] self.segments = [] if self.is_main_bin: self.linked_base = 0x0100000000 # File is read, begin populating internal fields self._parse_load_cmds() #self._parse_symbols(binary_file) self._parse_mod_funcs() self._handle_rebase_info() if self.is_main_bin: # XXX: remove this after binding_helper is fixed. # This is only for helping debug it self._handle_bindings()