def readExports(self) -> None: """ Gets export symbols """ self.exports = [] # try to get exports by LC_DYLD_INFO dyldInfo = self.machoFile.getLoadCommand( (MachO.LoadCommands.LC_DYLD_INFO, MachO.LoadCommands.LC_DYLD_INFO_ONLY)) if dyldInfo: self.exports = MachO.TrieParser(dyldInfo.exportData).parse() else: # try to get exports by LC_DYLD_EXPORTS_TRIE exportsTrie = self.machoFile.getLoadCommand( MachO.LoadCommands.LC_DYLD_EXPORTS_TRIE) if exportsTrie: self.exports = MachO.TrieParser( exportsTrie.linkeditData).parse() else: logging.warning("Unable to get export data.") # remove any non ReExport symbols reExportDeps = [] deps = self.machoFile.getLoadCommand( (MachO.LoadCommands.LC_LOAD_DYLIB, MachO.LoadCommands.LC_LOAD_WEAK_DYLIB, MachO.LoadCommands.LC_REEXPORT_DYLIB, MachO.LoadCommands.LC_LOAD_UPWARD_DYLIB), multiple=True) if deps: depIndex = 0 for dep in deps: depIndex += 1 if dep.cmd == MachO.LoadCommands.LC_REEXPORT_DYLIB: reExportDeps.append(depIndex) def isReExport(entry: MachO.TrieEntry) -> bool: if (entry.flags & MachO.Export.EXPORT_SYMBOL_FLAGS_KIND_MASK ) != MachO.Export.EXPORT_SYMBOL_FLAGS_KIND_REGULAR: return True if (entry.flags & MachO.Export.EXPORT_SYMBOL_FLAGS_REEXPORT) == 0: return True if entry.other in reExportDeps: return True return False self.exports = [ export for export in self.exports if isReExport(export) ] pass
def lookupSymbol(self, addr: int) -> bytes: """ Given the VMAddress of an exported function, this method will return its symbol name. """ if addr in self.symbolCache: return self.symbolCache[addr] # find the image with the address and cache its exports. for image in self.imageCache: if image.containsAddr(addr): dyldInfo = image.getLoadCommand( (MachO.LoadCommands.LC_DYLD_INFO, MachO.LoadCommands.LC_DYLD_INFO_ONLY)) dyldInfo.loadData() imageTextSeg = image.getSegment(b"__TEXT\x00") exports = MachO.TrieParser(dyldInfo.exportData).parse() for export in exports: exportAddr = imageTextSeg.vmaddr + export.address self.symbolCache[exportAddr] = export.name break # look for the symbol again if addr in self.symbolCache: return self.symbolCache[addr] else: return None
def processLoadCommands(self) -> None: for command in self.machoFile.loadCommands: if command.cmd == MachO.LoadCommands.LC_SEGMENT_64: self.processSegment(command) pass elif (command.cmd == MachO.LoadCommands.LC_DYLD_INFO or command.cmd == MachO.LoadCommands.LC_DYLD_INFO_ONLY): if len(command.rebaseData): command.rebase_off = self.dataHead self.dataHead += len(command.rebaseData) if len(command.bindData): command.bind_off = self.dataHead self.dataHead += len(command.bindData) if len(command.weak_bindData): command.weak_bind_off = self.dataHead self.dataHead += len(command.weak_bindData) if len(command.lazy_bindData): command.lazy_bind_off = self.dataHead self.dataHead += len(command.lazy_bindData) if len(command.exportData): command.export_off = self.dataHead self.dataHead += len(command.exportData) pass elif (command.cmd == MachO.LoadCommands.LC_FUNCTION_STARTS or command.cmd == MachO.LoadCommands.LC_DATA_IN_CODE): command.dataoff = self.dataHead self.dataHead += command.datasize pass elif (command.cmd == MachO.LoadCommands.LC_SYMTAB or command.cmd == MachO.LoadCommands.LC_DYSYMTAB): # These are located at the end of the file, # we'll fix these later pass elif (command.cmd == MachO.LoadCommands.LC_BUILD_VERSION or command.cmd == MachO.LoadCommands.LC_ID_DYLIB or command.cmd == MachO.LoadCommands.LC_LOAD_DYLIB or command.cmd == MachO.LoadCommands.LC_REEXPORT_DYLIB or command.cmd == MachO.LoadCommands.LC_LOAD_WEAK_DYLIB or command.cmd == MachO.LoadCommands.LC_LOAD_UPWARD_DYLIB or command.cmd == MachO.LoadCommands.LC_UUID or command.cmd == MachO.LoadCommands.LC_SOURCE_VERSION or command.cmd == MachO.LoadCommands.LC_SUB_FRAMEWORK or command.cmd == MachO.LoadCommands.LC_SUB_CLIENT or command.cmd == MachO.LoadCommands.LC_ROUTINES_64 or command.cmd == MachO.LoadCommands.LC_ENCRYPTION_INFO_64 or command.cmd == MachO.LoadCommands.LC_RPATH): # These don't have any data pass else: raise Exception("Unknown load command: " + MachO.LoadCommands(command.cmd).name) pass
def extractImage(dyld: Dyld.DyldFile, image: Dyld.dyld_cache_image_info, outputPath: str) -> None: """Extract and image Args: dyld: The DyldFile to extract from. image: The target image to extract. outputPath: The path to extract to. """ imageOff = dyld.convertAddr(image.address) machoFile = MachO.MachoFile.parse(dyld.file, imageOff) # rebuild the linkedit segment, which includes symbols logging.info("Starting LinkeditConverter") Converter.LinkeditConverter(machoFile, dyld).convert() # remove extra data in pointers and generate rebase data logging.info("Starting RebaseConverter") Converter.RebaseConverter(machoFile, dyld).convert() # fix references to selectors logging.info("Starting SelectorConverter") Converter.SelectorConverter(machoFile, dyld).convert() # fix stubs and references to stubs logging.info("Starting StubConverter") Converter.StubConverter(machoFile, dyld).convert() # fix and decache ObjC info logging.info("Starting ObjCConverter") Converter.ObjCConverter(machoFile, dyld).convert() # changes file offsets so that the final MachO file is not GBs big logging.info("Starting OffsetConverter") Converter.OffsetConverter(machoFile).convert() # save the converted image to a file logging.info("Starting Writer") MachO.Writer(machoFile).writeToPath(outputPath) pass
def runOnce(imageIndex: int, path: str) -> None: """ Decache an image at the imageIndex, and save it to the path. """ with open(DYLD_PATH, "rb") as dyldFile: dyld = Dyld.DyldFile(dyldFile) image = dyld.images[imageIndex] imageOff = dyld.convertAddr(image.address) macho = MachO.MachoFile.parse(dyldFile, imageOff) # comment or uncomment lines below to enable or disable modules. Though some do rely on each other. # rebuild the linkedit segment, which includes symbols print("LinkeditConverter") LinkeditConverter(macho, dyld).convert() # remove extra data in pointers and generate rebase data print("RebaseConverter") RebaseConverter(macho, dyld).convert() # fix references to selectors print("SelectorConverter") SelectorConverter(macho, dyld).convert() # fix stubs and references to stubs print("StubConverter") StubConverter(macho, dyld).convert() # fix and decache ObjC info print("ObjCConverter") ObjCConverter(macho, dyld).convert() # changes file offsets so that the final MachO file is not GBs big print("OffsetConverter") OffsetConverter(macho).convert() # save the converted image to a file print("Writer") MachO.Writer(macho).writeToPath(path)
def readExports(self) -> None: """ Gets export symbols """ exportData = self.machoFile.getLoadCommand( (MachO.LoadCommands.LC_DYLD_INFO, MachO.LoadCommands.LC_DYLD_INFO_ONLY)).exportData self.exports = MachO.TrieParser(exportData).parse() # remove any non ReExport symbols reExportDeps = [] deps = self.machoFile.getLoadCommand( (MachO.LoadCommands.LC_LOAD_DYLIB, MachO.LoadCommands.LC_LOAD_WEAK_DYLIB, MachO.LoadCommands.LC_REEXPORT_DYLIB, MachO.LoadCommands.LC_LOAD_UPWARD_DYLIB), multiple=True) if deps: depIndex = 0 for dep in deps: depIndex += 1 if dep.cmd == MachO.LoadCommands.LC_REEXPORT_DYLIB: reExportDeps.append(depIndex) def isReExport(entry: MachO.TrieEntry) -> bool: if (entry.flags & MachO.Export.EXPORT_SYMBOL_FLAGS_KIND_MASK ) != MachO.Export.EXPORT_SYMBOL_FLAGS_KIND_REGULAR: return True if (entry.flags & MachO.Export.EXPORT_SYMBOL_FLAGS_REEXPORT) == 0: return True if entry.other in reExportDeps: return True return False self.exports = [ export for export in self.exports if isReExport(export) ] pass
def buildSymbolTable(self) -> None: """ Rebuilds the symbol table. """ newStrData = b"\x00" newSymTab = b"" symtabCommand: MachO.symtab_command = self.machoFile.getLoadCommand( MachO.LoadCommands.LC_SYMTAB) # copy original symbols for i in range(0, len(symtabCommand.symbolData), MachO.nlist_64.SIZE): symEntry: MachO.nlist_64 = MachO.nlist_64.parseBytes( symtabCommand.symbolData, i) # skip local symbols for now if (symEntry.n_type & (MachO.NList.N_TYPE | MachO.NList.N_EXT)) == MachO.NList.N_SECT: continue # get the symbol symEnd = symtabCommand.stringData.index(b"\x00", symEntry.n_strx) + 1 symbol = symtabCommand.stringData[symEntry.n_strx:symEnd] # adjust the entry and add it to the new tables symEntry.n_strx = len(newStrData) newSymTab += symEntry.asBytes() newStrData += symbol # add N_INDR symbols for export in self.exports: symEntry = MachO.nlist_64() symEntry.n_strx = len(newStrData) symEntry.n_type = MachO.NList.N_INDR | MachO.NList.N_EXT symEntry.n_sect = 0 symEntry.n_desc = 0 newStrData += export.name importName = export.importName if export.importName else export.name symEntry.n_value = len(newStrData) newStrData += importName newSymTab += symEntry.asBytes() # add the local symbols # but first update the load commands dysymtabCommand: MachO.dysymtab_command = self.machoFile.getLoadCommand( MachO.LoadCommands.LC_DYSYMTAB) dysymtabCommand.ilocalsym = int(len(newSymTab) / MachO.nlist_64.SIZE) dysymtabCommand.nlocalsym = self.localSymEntry.nlistCount # add the indirect symbols indirectSymbolLocalCount = 0 indirectsymsData = bytearray(dysymtabCommand.indirectsymsData) for i in range(0, len(indirectsymsData), 4): entryIndex = struct.unpack_from("<I", indirectsymsData, i)[0] if entryIndex == 0x80000000: indirectSymbolLocalCount += 1 continue entryOff = entryIndex * MachO.nlist_64.SIZE entry = MachO.nlist_64.parseBytes(symtabCommand.symbolData, entryOff) # get the symbol symEnd = symtabCommand.stringData.index(b"\x00", entry.n_strx) + 1 sym = symtabCommand.stringData[entry.n_strx:symEnd] # add the entry newEntryIndex = int(len(newSymTab) / MachO.nlist_64.SIZE) struct.pack_into("<I", indirectsymsData, i, newEntryIndex) entry.n_strx = len(newStrData) newSymTab += entry.asBytes() # add the symbol newStrData += sym dysymtabCommand.indirectsymsData = bytes(indirectsymsData) # copy local symbols for i in range(0, self.localSymEntry.nlistCount): symOff = (i + self.localSymEntry.nlistStartIndex) * MachO.nlist_64.SIZE symEntry = MachO.nlist_64.parseBytes( self.dyldFile.localSymbolInfo.nlistData, symOff) localSymEnd = self.dyldFile.localSymbolInfo.stringData.index( b"\x00", symEntry.n_strx) + 1 localSym = self.dyldFile.localSymbolInfo.stringData[ symEntry.n_strx:localSymEnd] symEntry.n_strx = len(newStrData) newSymTab += symEntry.asBytes() newStrData += localSym if (self.calculateEntryCount() - indirectSymbolLocalCount) != ( len(newSymTab) / MachO.nlist_64.SIZE): raise Exception("symbol count miscalculation") # set the new data symtabCommand.symbolData = newSymTab symtabCommand.nsyms = int(len(newSymTab) / MachO.nlist_64.SIZE) symtabCommand.stringData = newStrData symtabCommand.strsize = len(newStrData) pass