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
Example #2
0
    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
Example #3
0
    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
Example #4
0
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
Example #5
0
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