def _rebasePage(self, ctx: MachOContext, pageOffset: int, delta: int) -> None: locOff = pageOffset while True: locOff += delta locInfo = dyld_cache_slide_pointer3(self.dyldCtx.file, locOff) # It appears the delta encoded in the pointers are 64bit jumps... delta = locInfo.plain.offsetToNextPointer * 8 if locInfo.auth.authenticated: newValue = locInfo.auth.offsetFromSharedCacheBase newValue += self.slideInfo.auth_value_add else: value51 = locInfo.plain.pointerValue top8Bits = value51 & 0x0007F80000000000 bottom43Bits = value51 & 0x000007FFFFFFFFFF newValue = (top8Bits << 13) | bottom43Bits ctx.writeBytes(locOff, struct.pack("<Q", newValue)) if delta == 0: break pass pass
def _rebasePage(self, ctx: MachOContext, pageStart: int, pageOffset: int) -> None: """Process the slide info for a page. Args: pageStart: the file offset to the page. pageOffset: the offset from the pageStart to the first rebase location. """ deltaMask = self.slideInfo.delta_mask valueMask = ~deltaMask valueAdd = self.slideInfo.value_add # basically __builtin_ctzll(deltaMask) - 2; deltaShift = "{0:b}".format(deltaMask) deltaShift = len(deltaShift) - len(deltaShift.rstrip("0")) deltaShift = deltaShift - 2 delta = 1 while delta != 0: loc = pageStart + pageOffset rawValue = self.dyldCtx.readFormat("<Q", loc)[0] delta = (rawValue & deltaMask) >> deltaShift newValue = rawValue & valueMask if valueMask != 0: newValue += valueAdd ctx.writeBytes(loc, struct.pack("<Q", newValue)) pageOffset += delta pass
def optimizeOffsets(extractionCtx: ExtractionContext) -> None: """Adjusts file offsets. MachO files in the Dyld Shared Cache are split up, which causes decached files to have really weird offsets. This fixes that. Args: machoCtx: A writable MachOContext. Returns: The processed MachO file. """ extractionCtx.statusBar.update(unit="Optimize Offsets") # The data in a MachO are defined by the segment load commands, # This includes the LinkEdit and MachO header machoCtx = extractionCtx.machoCtx # first change all the offset fields and record the shifts shiftProcedures = [] # Essentally a tuple with args to mmap.move dataHead = 0 for segname, segment in machoCtx.segments.items(): shiftDelta = dataHead - segment.seg.fileoff procedure = ( segment.seg.fileoff + shiftDelta, # dest segment.seg.fileoff, # src segment.seg.filesize # count ) shiftProcedures.append(procedure) if segname == b"__LINKEDIT": _updateLinkEdit(machoCtx, shiftDelta) # Change the offsets for the segment and section structures segment.seg.fileoff += shiftDelta for sect in segment.sects.values(): sect.offset += shiftDelta # update the data head to the next page aligned offset dataHead += segment.seg.filesize dataHead += _PAGE_SIZE - (dataHead % _PAGE_SIZE) # Now we need to actually move the segments. # We are moving the segments now because then we # don't have to constantly "re-point" various structures. for procedure in shiftProcedures: extractionCtx.statusBar.update(status="Moving Segments") machoCtx.file.move(procedure[0], procedure[1], procedure[2]) # re-create the MachOContext so it reflects the new offsets extractionCtx.machoCtx = MachOContext(machoCtx.file, 0)
def _extractImage(dyldFile: BufferedReader, dyldCtx: DyldContext, image: dyld_cache_image_info, outputPath: str) -> None: """Extract an image and save it. The order of converters is essentally a reverse of Apple's SharedCacheBuilder """ logger = logging.getLogger() # get a a writable copy of the MachOContext machoFile = mmap.mmap(dyldFile.fileno(), 0, access=mmap.ACCESS_COPY) machoCtx = MachOContext(machoFile, dyldCtx.convertAddr(image.address)) statusBar = progressbar.ProgressBar( prefix="{variables.unit} >> {variables.status} :: [", variables={ "unit": "--", "status": "--" }, widgets=[progressbar.widgets.AnimatedMarker(), "]"], redirect_stdout=True) extractionCtx = ExtractionContext(dyldCtx, machoCtx, statusBar, logger) slide_info.processSlideInfo(extractionCtx) linkedit_optimizer.optimizeLinkedit(extractionCtx) stub_fixer.fixStubs(extractionCtx) objc_fixer.fixObjC(extractionCtx) macho_offset.optimizeOffsets(extractionCtx) # Write the MachO file with open(outputPath, "wb") as outFile: statusBar.update(unit="Extractor", status="Writing file") newMachoCtx = extractionCtx.machoCtx # get the size of the file linkEditSeg = newMachoCtx.segments[b"__LINKEDIT"].seg fileSize = linkEditSeg.fileoff + linkEditSeg.filesize newMachoCtx.file.seek(0) outFile.write(newMachoCtx.file.read(fileSize)) statusBar.update(unit="Extractor", status="Done")
def _imageRunner(dyldPath: str, imageIndex: int) -> None: level = logging.DEBUG loggingStream = io.StringIO() # setup logging logger = logging.getLogger(f"Worker: {imageIndex}") handler = logging.StreamHandler(loggingStream) formatter = logging.Formatter( fmt= "{asctime}:{msecs:03.0f} [{levelname:^9}] {filename}:{lineno:d} : {message}", # noqa datefmt="%H:%M:%S", style="{", ) handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(level) # process the image with open(dyldPath, "rb") as f: dyldFile = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) dyldCtx = DyldContext(dyldFile) imageOffset = dyldCtx.convertAddr(dyldCtx.images[imageIndex].address) machoFile = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_COPY) machoCtx = MachOContext(machoFile, imageOffset) extractionCtx = ExtractionContext(dyldCtx, machoCtx, _DummyProgressBar(), logger) try: linkedit_optimizer.optimizeLinkedit(extractionCtx) stub_fixer.fixStubs(extractionCtx) objc_fixer.fixObjC(extractionCtx) except Exception as e: logger.exception(e) pass # cleanup handler.close() return loggingStream.getvalue()
def runForAllImages(dyldFile: BufferedReader, dyldCtx: DyldContext, statusBar: progressbar.ProgressBar, logger: logging.Logger, startIndex: int = 0, stopIndex: int = -1) -> None: total = dyldCtx.header.imagesCount for index, imageData in enumerate(dyldCtx.images[startIndex:], startIndex): if index == stopIndex: break # TODO: Imp sub caches imageOffset = dyldCtx.convertAddr(imageData.address) imagePath = dyldCtx.readString(imageData.pathFileOffset)[0:-1] imagePath = imagePath.decode("utf-8") imageName = imagePath.split("/")[-1] # Make a writable copy of the dyld file machoFile = mmap.mmap(dyldFile.fileno(), 0, access=mmap.ACCESS_COPY) machoCtx = MachOContext(machoFile, imageOffset) extractionCtx = ExtractionContext(dyldCtx, machoCtx, statusBar, logger) # Test space start slide_info.processSlideInfo(extractionCtx) linkedit_optimizer.optimizeLinkedit(extractionCtx) stub_fixer.fixStubs(extractionCtx) objc_fixer.fixObjC(extractionCtx) macho_offset.optimizeOffsets(extractionCtx) # Test space end logger.info(f"processed: ({index + 1}/{total}): {imageName}") pass statusBar.update(unit="Extractor", status="Done") pass
def _imageRunner(dyldPath: str, imageIndex: int) -> None: level = logging.DEBUG loggingStream = io.StringIO() # setup logging logger = logging.getLogger(f"Worker: {imageIndex}") handler = logging.StreamHandler(loggingStream) formatter = logging.Formatter( fmt= "{asctime}:{msecs:03.0f} [{levelname:^9}] {filename}:{lineno:d} : {message}", # noqa datefmt="%H:%M:%S", style="{", ) handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(level) # process the image with open(dyldPath, "rb") as f: dyldCtx = DyldContext(f) subCacheFiles: List[BinaryIO] = [] try: # add sub caches if there are any if dyldCtx.hasSubCaches(): subCacheFileCtxs, subCacheFiles = _openSubCaches( dyldPath, dyldCtx.header.numSubCaches) dyldCtx.addSubCaches(subCacheFileCtxs) pass machoOffset, context = dyldCtx.convertAddr( dyldCtx.images[imageIndex].address) machoCtx = MachOContext(context.fileObject, machoOffset, True) # Add sub caches if necessary if dyldCtx.hasSubCaches(): mappings = dyldCtx.mappings mainFileMap = next((mapping[0] for mapping in mappings if mapping[1] == context)) machoCtx.addSubfiles(mainFileMap, ((m, ctx.makeCopy(copyMode=True)) for m, ctx in mappings)) pass extractionCtx = ExtractionContext(dyldCtx, machoCtx, _DummyProgressBar(), logger) # TODO: implement a way to select convertors slide_info.processSlideInfo(extractionCtx) linkedit_optimizer.optimizeLinkedit(extractionCtx) stub_fixer.fixStubs(extractionCtx) objc_fixer.fixObjC(extractionCtx) macho_offset.optimizeOffsets(extractionCtx) except Exception as e: logger.exception(e) pass finally: for file in subCacheFiles: file.close() pass pass pass # cleanup handler.close() return loggingStream.getvalue()
def run(self): # check if the optimization flag is set imageInfo = None imageInfoFile = None for seg in self._machoCtx.segmentsI: if b"__objc_imageinfo" in seg.sects: imageInfo = seg.sects[b"__objc_imageinfo"] imageInfoFile = self._machoCtx.ctxForAddr(seg.seg.vmaddr) break pass if not imageInfo: return flagsOff = self._dyldCtx.convertAddr(imageInfo.addr)[0] flags = imageInfoFile.readFormat( "<I", flagsOff + 4, )[0] if not flags & 0x8: self._logger.info("ObjC was not optimized by Dyld, not fixing ObjC.") return # Removed the optimized objc bit flags &= 0xfffffff7 imageInfoFile.writeBytes(flagsOff, struct.pack("<I", flags)) self._createExtraSegment() # Get the libobjc.A.dylib image for image in self._dyldCtx.images: path = self._dyldCtx.readString(image.pathFileOffset) if b"libobjc.A.dylib" in path: offset, ctx = self._dyldCtx.convertAddr(image.address) self._libobjcImage = MachOContext(ctx.fileObject, offset) break pass else: self._logger.error("Unable to find libobjc.A.dylib") return self._checkMethodNameStorage() # caches that map the original definition address # to its new processed address. self._categoryCache: Dict[int, int] = {} self._classCache: Dict[int, int] = {} self._classDataCache: Dict[int, int] = {} self._ivarListCache: Dict[int, int] = {} self._protocolListCache: Dict[int, int] = {} self._protocolCache: Dict[int, int] = {} self._propertyListCache: Dict[int, int] = {} self._methodListCache: Dict[int, int] = {} self._stringCache: Dict[int, int] = {} self._intCache: Dict[int, int] = {} self._methodNameCache: Dict[int, int] = {} # connects a selrefs old target to its pointer address self._selRefCache: Dict[int, int] = {} # A list of class pointers that are being processed. self._classesProcessing: List[int] = [] # A list of pointers that need to be updated at the end # The first int is the address to the pointer that needs # to be changed. The second int is the address of the # target class self._futureClasses: List[Tuple[int, int]] = [] self._processSections() self._finalizeFutureClasses() _ObjCSelectorFixer(self._extractionCtx, self).run() self._checkSpaceConstraints() self._addExtraDataSeg() pass