diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java index 300122335a..d527f5b8a5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java @@ -189,6 +189,30 @@ public class MachHeader implements StructConverter { return this; } + /** + * Parses only this {@link MachHeader}'s {@link SegmentCommand segments} + * + * @return A {@List} of this {@link MachHeader}'s {@link SegmentCommand segments} + * @throws IOException If there was an IO-related error + * @throws MachException if the load command is invalid + */ + public List parseSegments() throws IOException, MachException { + List segments = new ArrayList<>(); + _reader.setPointerIndex(_commandIndex); + for (int i = 0; i < nCmds; ++i) { + int type = _reader.peekNextInt(); + if (type == LoadCommandTypes.LC_SEGMENT || type == LoadCommandTypes.LC_SEGMENT_64) { + segments.add(new SegmentCommand(_reader, is32bit())); + } + else { + type = _reader.readNextInt(); + int size = _reader.readNextInt(); + _reader.setPointerIndex(_reader.getPointerIndex() + size - 8); + } + } + return segments; + } + public int getMagic() { return magic; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SegmentCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SegmentCommand.java index 0bcddce7b0..cd3149b356 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SegmentCommand.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SegmentCommand.java @@ -112,6 +112,10 @@ public class SegmentCommand extends LoadCommand { return vmaddr; } + public void setVMaddress(long vmaddr) { + this.vmaddr = vmaddr; + } + public long getVMsize() { return vmsize; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java index 6f434a4581..1560ddb9d8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java @@ -768,7 +768,7 @@ public class DyldCacheHeader implements StructConverter { } } - private void parseLocalSymbolsInfo(boolean shouldParse, MessageLog log, TaskMonitor monitor) + public void parseLocalSymbolsInfo(boolean shouldParse, MessageLog log, TaskMonitor monitor) throws CancelledException { if (!shouldParse || localSymbolsOffset == 0) { return; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheLocalSymbolsInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheLocalSymbolsInfo.java index 807f3be7d0..69b8122c6b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheLocalSymbolsInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheLocalSymbolsInfo.java @@ -115,6 +115,15 @@ public class DyldCacheLocalSymbolsInfo implements StructConverter { markupNList(program, localSymbolsInfoAddr, monitor, log); } + /** + * Gets the {@link List} of {@link DyldCacheLocalSymbolsEntry}s. + * + * @return The {@link List} of {@link DyldCacheLocalSymbolsEntry} + */ + public List getLocalSymbolsEntries() { + return localSymbolsEntryList; + } + /** * Gets the {@link List} of {@link NList}. * @@ -125,12 +134,20 @@ public class DyldCacheLocalSymbolsInfo implements StructConverter { } /** - * Gets the {@link List} of {@link DyldCacheLocalSymbolsEntry}s. + * Gets the {@link List} of {@link NList} for the given dylib offset. * - * @return The {@link List} of {@link DyldCacheLocalSymbolsEntry} + * @param dylibOffset The offset of dylib in the DYLD Cache + * @return The {@link List} of {@link NList} for the given dylib offset */ - public List getLocalSymbolsEntries() { - return localSymbolsEntryList; + public List getNList(long dylibOffset) { + for (DyldCacheLocalSymbolsEntry entry : localSymbolsEntryList) { + int index = entry.getNListStartIndex(); + int count = entry.getNListCount(); + if (dylibOffset == entry.getDylibOffset()) { + return nlistList.subList(index, index + count); + } + } + return List.of(); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheProgramBuilder.java index 17a239e762..e112b8440f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheProgramBuilder.java @@ -88,10 +88,10 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder { new SplitDyldCache(provider, options.processLocalSymbols(), log, monitor)) { // Set image base - setDyldCacheImageBase(splitDyldCache.getDyldCacheHeader(0)); + setDyldCacheImageBase(splitDyldCache); // Set entry point - setDyldCacheEntryPoint(splitDyldCache.getDyldCacheHeader(0)); + setDyldCacheEntryPoint(splitDyldCache); // Setup memory // Check if local symbols are present @@ -125,25 +125,25 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder { /** * Sets the program's image base. * - * @param dyldCacheHeader The "base" DYLD Cache header + * @param splitDyldCache The split DYLD cache * @throws Exception if there was problem setting the program's image base */ - private void setDyldCacheImageBase(DyldCacheHeader dyldCacheHeader) throws Exception { + private void setDyldCacheImageBase(SplitDyldCache splitDyldCache) throws Exception { monitor.setMessage("Setting image base..."); monitor.initialize(1); - program.setImageBase(space.getAddress(dyldCacheHeader.getBaseAddress()), true); + program.setImageBase(space.getAddress(splitDyldCache.getBaseAddress()), true); monitor.incrementProgress(1); } /** * Sets the program's entry point (if known). * - * @param dyldCacheHeader The "base" DYLD Cache header + * @param splitDyldCache The split DYLD cache * @throws Exception if there was problem setting the program's entry point */ - private void setDyldCacheEntryPoint(DyldCacheHeader dyldCacheHeader) throws Exception { + private void setDyldCacheEntryPoint(SplitDyldCache splitDyldCache) throws Exception { monitor.initialize(1, "Setting entry pointer base..."); - Long entryPoint = dyldCacheHeader.getEntryPoint(); + Long entryPoint = splitDyldCache.getDyldCacheHeader(0).getEntryPoint(); if (entryPoint != null) { Address entryPointAddr = space.getAddress(entryPoint); program.getSymbolTable().addExternalEntryPoint(entryPointAddr); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheUtils.java index 9fb8ed88c9..5d2b31eccd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheUtils.java @@ -218,6 +218,30 @@ public class DyldCacheUtils { public int size() { return providers.size(); } + + /** + * Gets the base address of the split DYLD cache. This is where the cache should be loaded + * in memory. + * + * @return The base address of the split DYLD cache + */ + public long getBaseAddress() { + return headers.get(0).getBaseAddress(); + } + + /** + * Gets the {@link DyldCacheLocalSymbolsInfo} from the split DYLD Cache files + * + * @return The {@link DyldCacheLocalSymbolsInfo} from the split DYLD Cache files, or null + * if no local symbols are defined + */ + public DyldCacheLocalSymbolsInfo getLocalSymbolInfo() { + return headers.stream() + .map(h -> h.getLocalSymbolsInfo()) + .filter(info -> info != null) + .findAny() + .orElse(null); + } @Override public void close() throws IOException { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoPrelinkUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoPrelinkUtils.java index 38b33d465a..67be44075f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoPrelinkUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoPrelinkUtils.java @@ -22,10 +22,12 @@ import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.bidimap.DualHashBidiMap; import org.jdom.JDOMException; -import ghidra.app.util.bin.*; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.ByteProviderWrapper; import ghidra.app.util.bin.format.macho.MachException; import ghidra.app.util.bin.format.macho.MachHeader; -import ghidra.app.util.bin.format.macho.commands.*; +import ghidra.app.util.bin.format.macho.commands.SegmentCommand; +import ghidra.app.util.bin.format.macho.commands.SegmentNames; import ghidra.app.util.bin.format.macho.prelink.*; import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; @@ -44,26 +46,9 @@ public class MachoPrelinkUtils { */ public static boolean isMachoPrelink(ByteProvider provider, TaskMonitor monitor) { try { - MachHeader header = new MachHeader(provider); - BinaryReader reader = new BinaryReader(provider, header.isLittleEndian()); - reader.setPointerIndex(header.getSize()); - - // Doing a full header parse is too slow...we really just need to see if a segment - // exists that starts with __PRELINK. Parse the minimal amount to do that check. - for (int i = 0; i < header.getNumberOfCommands(); i++) { - int type = reader.peekNextInt(); - if (type == LoadCommandTypes.LC_SEGMENT || type == LoadCommandTypes.LC_SEGMENT_64) { - SegmentCommand segment = new SegmentCommand(reader, header.is32bit()); - if (segment.getSegmentName().startsWith("__PRELINK")) { - return true; - } - } - else { - type = reader.readNextInt(); - int size = reader.readNextInt(); - reader.setPointerIndex(reader.getPointerIndex() + size - 8); - } - } + return new MachHeader(provider).parseSegments() + .stream() + .anyMatch(segment -> segment.getSegmentName().startsWith("__PRELINK")); } catch (MachException | IOException e) { // Assume it's not a Mach-O PRELINK...fall through diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheDylibExtractor.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheDylibExtractor.java index caec7980a8..aa1562bdf7 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheDylibExtractor.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheDylibExtractor.java @@ -195,8 +195,14 @@ public class DyldCacheDylibExtractor { } byte[] bytes; if (segment == linkEditSegment) { - bytes = - createPackedLinkEditSegment(segmentProvider, packedLinkEditSize); + bytes = createPackedLinkEditSegment(segmentProvider, packedLinkEditSize); + + // We don't want our packed __LINKEDIT segment to overlap with other DYLIB's + // that might get extracted and added to the same program. Rather than + // computing the optimal address it should go at (which will required looking + // at every other DYLIB in the cache which is slow), just make the address very + // far away from the other DYLIB's + segment.setVMaddress(textSegment.getVMaddress() << 4); } else { bytes = segmentProvider.readBytes(segment.getFileOffset(), segmentSize); @@ -221,28 +227,15 @@ public class DyldCacheDylibExtractor { } /** - * Gets a {@link List} of local {@link NList symbol}s + * Gets a {@link List} of local {@link NList symbol}s for the DYLIB being extracted * * @param splitDyldCache The {@link SplitDyldCache} * @return A {@link List} of local {@link NList symbol}s (could be empty) */ private List getLocalSymbols(SplitDyldCache splitDyldCache) { - long base = splitDyldCache.getDyldCacheHeader(0).getBaseAddress(); - for (int i = 0; i < splitDyldCache.size(); i++) { - DyldCacheHeader header = splitDyldCache.getDyldCacheHeader(i); - DyldCacheLocalSymbolsInfo info = header.getLocalSymbolsInfo(); - if (info == null) { - continue; - } - for (DyldCacheLocalSymbolsEntry entry : info.getLocalSymbolsEntries()) { - int index = entry.getNListStartIndex(); - int count = entry.getNListCount(); - if (base + entry.getDylibOffset() == textSegment.getVMaddress() && count > 0) { - return info.getNList().subList(index, index + count); - } - } - } - return List.of(); + long base = splitDyldCache.getBaseAddress(); + DyldCacheLocalSymbolsInfo info = splitDyldCache.getLocalSymbolInfo(); + return info != null ? info.getNList(textSegment.getVMaddress() - base) : List.of(); } /** @@ -378,14 +371,18 @@ public class DyldCacheDylibExtractor { */ private void fixupSegment(SegmentCommand segment, boolean is64bit) throws IOException { long adjustment = packedSegmentAdjustments.getOrDefault(segment, 0); - if (segment.getFileOffset() > 0) { - fixup(segment.getStartIndex() + (is64bit ? 0x28 : 0x20), adjustment, - is64bit ? 8 : 4, segment); + if (segment.getVMaddress() > 0) { + set(segment.getStartIndex() + (is64bit ? 0x18 : 0x18), segment.getVMaddress(), + is64bit ? 8 : 4); } if (segment.getVMsize() > 0) { set(segment.getStartIndex() + (is64bit ? 0x20 : 0x1c), segment.getVMsize(), is64bit ? 8 : 4); } + if (segment.getFileOffset() > 0) { + fixup(segment.getStartIndex() + (is64bit ? 0x28 : 0x20), adjustment, + is64bit ? 8 : 4, segment); + } if (segment.getFileSize() > 0) { set(segment.getStartIndex() + (is64bit ? 0x30 : 0x24), segment.getFileSize(), is64bit ? 8 : 4); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheFileSystem.java index b2cfc90afa..a28cb26928 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheFileSystem.java @@ -36,6 +36,7 @@ import ghidra.util.task.TaskMonitor; public class DyldCacheFileSystem extends GFileSystemBase { private SplitDyldCache splitDyldCache; + private boolean parsedLocalSymbols = false; private Map> slideFixupMap; private Map addrMap = new HashMap<>(); private Map indexMap = new HashMap<>(); @@ -47,9 +48,13 @@ public class DyldCacheFileSystem extends GFileSystemBase { @Override public void close() throws IOException { slideFixupMap = null; + parsedLocalSymbols = false; addrMap.clear(); indexMap.clear(); - splitDyldCache.close(); + if (splitDyldCache != null) { + splitDyldCache.close(); + splitDyldCache = null; + } super.close(); } @@ -68,6 +73,14 @@ public class DyldCacheFileSystem extends GFileSystemBase { slideFixupMap = DyldCacheDylibExtractor.getSlideFixups(splitDyldCache, monitor); } + if (!parsedLocalSymbols) { + for (int i = 0; i < splitDyldCache.size(); i++) { + splitDyldCache.getDyldCacheHeader(i) + .parseLocalSymbolsInfo(true, new MessageLog(), monitor); + } + parsedLocalSymbols = true; + } + try { return DyldCacheDylibExtractor.extractDylib(machHeaderStartIndexInProvider, splitDyldCache, index, slideFixupMap, file.getFSRL(), monitor); @@ -120,7 +133,7 @@ public class DyldCacheFileSystem extends GFileSystemBase { MessageLog log = new MessageLog(); monitor.setMessage("Opening DYLD cache..."); - splitDyldCache = new SplitDyldCache(provider, true, log, monitor); + splitDyldCache = new SplitDyldCache(provider, false, log, monitor); for (int i = 0; i < splitDyldCache.size(); i++) { DyldCacheHeader header = splitDyldCache.getDyldCacheHeader(i); monitor.setMessage("Find files...");