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 dc26406469..f751995705 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
@@ -426,12 +426,35 @@ public class DyldCacheHeader implements StructConverter {
}
/**
- * Gets the {@link List} of {@link DyldCacheImageInfo}s. Requires header to have been parsed.
+ * Generates a {@link List} of {@link DyldCacheImage}s that are mapped in by this
+ * {@link DyldCacheHeader}. Requires header to have been parsed.
+ *
+ * NOTE: A "split" DYLD Cache header may declare an image, but that image may get loaded at an
+ * address defined by the memory map of a different split header. This method will only return
+ * the images that are mapped by "this" header's memory map.
*
- * @return The {@link List} of {@link DyldCacheImageInfo}s
+ *
+ * @return A {@link List} of {@link DyldCacheImage}s mapped by this {@link DyldCacheHeader}
*/
- public List getImageInfos() {
- return imageInfoList;
+ public List getMappedImages() {
+ List images = new ArrayList<>();
+ if (imageInfoList.size() > 0) {
+ // The old, simple way
+ images.addAll(imageInfoList);
+ }
+ else {
+ // The new, split file way. A split file will have an entry for every image, but
+ // not every image will be mapped.
+ for (DyldCacheImageTextInfo imageTextInfo : imageTextInfoList) {
+ for (DyldCacheMappingInfo mappingInfo : mappingInfoList) {
+ if (mappingInfo.contains(imageTextInfo.getAddress())) {
+ images.add(imageTextInfo);
+ break;
+ }
+ }
+ }
+ }
+ return images;
}
/**
@@ -691,8 +714,8 @@ public class DyldCacheHeader implements StructConverter {
monitor.setMessage("Marking up DYLD header...");
monitor.initialize(1);
try {
- DataUtilities.createData(program, program.getImageBase(), toDataType(), -1, false,
- DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
+ DataUtilities.createData(program, space.getAddress(getBaseAddress()), toDataType(), -1,
+ false, DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
monitor.incrementProgress(1);
}
catch (CodeUnitInsertionException | DuplicateNameException | IOException e) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImage.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImage.java
new file mode 100644
index 0000000000..e3077fff01
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImage.java
@@ -0,0 +1,36 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.bin.format.macho.dyld;
+
+/**
+ * A convenience interface for getting the address and path of a DYLD Cache image
+ */
+public interface DyldCacheImage {
+
+ /**
+ * Gets the address the start of the image
+ *
+ * @return The address of the start of the image
+ */
+ public long getAddress();
+
+ /**
+ * Gets the path of the image
+ *
+ * @return The path of the image
+ */
+ public String getPath();
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageInfo.java
index 084ebc952f..2b0dc702e3 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageInfo.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageInfo.java
@@ -29,7 +29,7 @@ import ghidra.util.exception.DuplicateNameException;
* @see dyld3/shared-cache/dyld_cache_format.h
*/
@SuppressWarnings("unused")
-public class DyldCacheImageInfo implements StructConverter {
+public class DyldCacheImageInfo implements DyldCacheImage, StructConverter {
private long address;
private long modTime;
@@ -55,20 +55,12 @@ public class DyldCacheImageInfo implements StructConverter {
path = reader.readAsciiString(pathFileOffset);
}
- /**
- * Gets the address the start of the image.
- *
- * @return The address of the start of the image
- */
+ @Override
public long getAddress() {
return address;
}
- /**
- * Gets the path of the image.
- *
- * @return The path of the image
- */
+ @Override
public String getPath() {
return path;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageTextInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageTextInfo.java
index a6d08dfec5..4203e07b51 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageTextInfo.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageTextInfo.java
@@ -29,7 +29,7 @@ import ghidra.util.exception.DuplicateNameException;
* @see dyld3/shared-cache/dyld_cache_format.h
*/
@SuppressWarnings("unused")
-public class DyldCacheImageTextInfo implements StructConverter {
+public class DyldCacheImageTextInfo implements DyldCacheImage, StructConverter {
private byte[] uuid;
private long loadAddress;
@@ -52,12 +52,13 @@ public class DyldCacheImageTextInfo implements StructConverter {
path = reader.readAsciiString(pathOffset);
}
+
+ @Override
+ public long getAddress() {
+ return loadAddress;
+ }
- /**
- * Gets the path of the image text.
- *
- * @return The path of the image text.
- */
+ @Override
public String getPath() {
return path;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheMappingInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheMappingInfo.java
index 96c62a7731..c9a4184ba4 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheMappingInfo.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheMappingInfo.java
@@ -106,6 +106,17 @@ public class DyldCacheMappingInfo implements StructConverter {
return (initProt & SegmentConstants.PROTECTION_X) != 0;
}
+ /**
+ * Returns true if the mapping contains the given address
+ *
+ * @param addr The address to check
+ * @return True if the mapping contains the given address; otherwise, false
+ */
+ public boolean contains(long addr) {
+ return Long.compareUnsigned(addr, address) >= 0 &&
+ Long.compareUnsigned(addr, address + size) < 0;
+ }
+
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType("dyld_cache_mapping_info", 0);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java
index c773b1cc3a..d382fbf293 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java
@@ -18,9 +18,7 @@ package ghidra.app.util.opinion;
import java.io.IOException;
import java.util.*;
-import ghidra.app.util.MemoryBlockUtils;
-import ghidra.app.util.Option;
-import ghidra.app.util.OptionUtils;
+import ghidra.app.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.macho.dyld.DyldArchitecture;
@@ -51,11 +49,19 @@ public class DyldCacheLoader extends AbstractLibrarySupportLoader {
static final boolean CREATE_DYLIB_SECTIONS_OPTION_DEFAULT = false;
/** Loader option to add relocation entries for each fixed chain pointer */
- static final String ADD_RELOCATION_ENTRIES_OPTION_NAME = "Add relocation entries for fixed chain pointers";
+ static final String ADD_RELOCATION_ENTRIES_OPTION_NAME =
+ "Add relocation entries for fixed chain pointers";
/** Default value for loader option add relocation entries */
static final boolean ADD_RELOCATION_ENTRIES_OPTION_DEFAULT = false;
+ /** Loader option to combine split DYLD Cache files (.1, .2, .symbol, etc) into one program */
+ static final String COMBINE_SPLIT_FILES_OPTION_NAME =
+ "Auto import and combine split DYLD Cache files";
+
+ /** Default value for loader option add relocation entries */
+ static final boolean COMBINE_SPLIT_FILES_OPTION_DEFAULT = true;
+
@Override
public Collection findSupportedLoadSpecs(ByteProvider provider) throws IOException {
List loadSpecs = new ArrayList<>();
@@ -92,7 +98,8 @@ public class DyldCacheLoader extends AbstractLibrarySupportLoader {
DyldCacheProgramBuilder.buildProgram(program, provider,
MemoryBlockUtils.createFileBytes(program, provider, monitor),
shouldProcessSymbols(options), shouldCreateDylibSections(options),
- shouldAddRelocationEntries(options), log, monitor);
+ shouldAddRelocationEntries(options), shouldCombineSplitFiles(options), log,
+ monitor);
}
catch (CancelledException e) {
return;
@@ -113,25 +120,35 @@ public class DyldCacheLoader extends AbstractLibrarySupportLoader {
list.add(
new Option(CREATE_DYLIB_SECTIONS_OPTION_NAME, CREATE_DYLIB_SECTIONS_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-createDylibSections"));
- list.add(
- new Option(ADD_RELOCATION_ENTRIES_OPTION_NAME, ADD_RELOCATION_ENTRIES_OPTION_DEFAULT,
- Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-addRelocationEntries"));
+ list.add(new Option(ADD_RELOCATION_ENTRIES_OPTION_NAME,
+ ADD_RELOCATION_ENTRIES_OPTION_DEFAULT, Boolean.class,
+ Loader.COMMAND_LINE_ARG_PREFIX + "-addRelocationEntries"));
+ list.add(new Option(COMBINE_SPLIT_FILES_OPTION_NAME, COMBINE_SPLIT_FILES_OPTION_DEFAULT,
+ Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-combineSplitFiles"));
}
return list;
}
private boolean shouldProcessSymbols(List options) {
- return OptionUtils.getOption(PROCESS_SYMBOLS_OPTION_NAME, options, PROCESS_SYMBOLS_OPTION_DEFAULT);
+ return OptionUtils.getOption(PROCESS_SYMBOLS_OPTION_NAME, options,
+ PROCESS_SYMBOLS_OPTION_DEFAULT);
}
private boolean shouldCreateDylibSections(List options) {
- return OptionUtils.getOption(CREATE_DYLIB_SECTIONS_OPTION_NAME, options, CREATE_DYLIB_SECTIONS_OPTION_DEFAULT);
+ return OptionUtils.getOption(CREATE_DYLIB_SECTIONS_OPTION_NAME, options,
+ CREATE_DYLIB_SECTIONS_OPTION_DEFAULT);
}
private boolean shouldAddRelocationEntries(List options) {
- return OptionUtils.getOption(ADD_RELOCATION_ENTRIES_OPTION_NAME, options, ADD_RELOCATION_ENTRIES_OPTION_DEFAULT);
+ return OptionUtils.getOption(ADD_RELOCATION_ENTRIES_OPTION_NAME, options,
+ ADD_RELOCATION_ENTRIES_OPTION_DEFAULT);
}
-
+
+ private boolean shouldCombineSplitFiles(List options) {
+ return OptionUtils.getOption(COMBINE_SPLIT_FILES_OPTION_NAME, options,
+ COMBINE_SPLIT_FILES_OPTION_DEFAULT);
+ }
+
@Override
public String getName() {
return DYLD_CACHE_NAME;
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 350da9cec2..47bfe7636a 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
@@ -20,7 +20,6 @@ import java.io.IOException;
import java.util.*;
import ghidra.app.util.MemoryBlockUtils;
-import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.macho.MachException;
import ghidra.app.util.bin.format.macho.MachHeader;
@@ -28,6 +27,7 @@ import ghidra.app.util.bin.format.macho.commands.NList;
import ghidra.app.util.bin.format.macho.dyld.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.MessageLogContinuesFactory;
+import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
@@ -44,31 +44,36 @@ import ghidra.util.task.TaskMonitor;
*/
public class DyldCacheProgramBuilder extends MachoProgramBuilder {
- protected DyldCacheHeader dyldCacheHeader;
private boolean shouldProcessSymbols;
private boolean shouldCreateDylibSections;
private boolean shouldAddRelocationEntries;
+ private boolean shouldCombineSplitFiles;
/**
* Creates a new {@link DyldCacheProgramBuilder} based on the given information.
*
* @param program The {@link Program} to build up
* @param provider The {@link ByteProvider} that contains the DYLD Cache bytes
- * @param fileBytes Where the Mach-O's bytes came from
+ * @param fileBytes Where the DYLD Cache's bytes came from
* @param shouldProcessSymbols True if symbols should be processed; otherwise, false
* @param shouldCreateDylibSections True if memory blocks should be created for DYLIB sections;
* otherwise, false
- * @param shouldAddRelocationEntries True to create a relocation entry for each fixed up pointer in pointer chain
+ * @param shouldAddRelocationEntries True to create a relocation entry for each fixed up pointer
+ * in pointer chain
+ * @param shouldCombineSplitFiles True if split DYLD Cache files should be automatically
+ * imported and combined into 1 program; otherwise, false
* @param log The log
* @param monitor A cancelable task monitor
*/
protected DyldCacheProgramBuilder(Program program, ByteProvider provider, FileBytes fileBytes,
boolean shouldProcessSymbols, boolean shouldCreateDylibSections,
- boolean shouldAddRelocationEntries, MessageLog log, TaskMonitor monitor) {
+ boolean shouldAddRelocationEntries, boolean shouldCombineSplitFiles, MessageLog log,
+ TaskMonitor monitor) {
super(program, provider, fileBytes, log, monitor);
this.shouldProcessSymbols = shouldProcessSymbols;
this.shouldCreateDylibSections = shouldCreateDylibSections;
this.shouldAddRelocationEntries = shouldAddRelocationEntries;
+ this.shouldCombineSplitFiles = shouldCombineSplitFiles;
}
/**
@@ -80,44 +85,62 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
* @param shouldProcessSymbols True if symbols should be processed; otherwise, false
* @param shouldCreateDylibSections True if memory blocks should be created for DYLIB sections;
* otherwise, false
- * @param addRelocationEntries True to create a relocation entry for each fixed up pointer in pointer chain
+ * @param addRelocationEntries True to create a relocation entry for each fixed up pointer in
+ * pointer chain; otherwise, false
+ * @param shouldCombineSplitFiles True if split DYLD Cache files should be automatically
+ * imported and combined into 1 program; otherwise, false
* @param log The log
* @param monitor A cancelable task monitor
* @throws Exception if a problem occurs
*/
public static void buildProgram(Program program, ByteProvider provider, FileBytes fileBytes,
boolean shouldProcessSymbols, boolean shouldCreateDylibSections,
- boolean addRelocationEntries, MessageLog log, TaskMonitor monitor) throws Exception {
- DyldCacheProgramBuilder dyldCacheProgramBuilder =
- new DyldCacheProgramBuilder(program, provider, fileBytes, shouldProcessSymbols,
- shouldCreateDylibSections, addRelocationEntries, log, monitor);
+ boolean addRelocationEntries, boolean shouldCombineSplitFiles, MessageLog log,
+ TaskMonitor monitor) throws Exception {
+ DyldCacheProgramBuilder dyldCacheProgramBuilder = new DyldCacheProgramBuilder(program,
+ provider, fileBytes, shouldProcessSymbols, shouldCreateDylibSections,
+ addRelocationEntries, shouldCombineSplitFiles, log, monitor);
dyldCacheProgramBuilder.build();
}
@Override
protected void build() throws Exception {
- monitor.setMessage("Parsing DYLD Cache header ...");
- monitor.initialize(1);
- dyldCacheHeader = new DyldCacheHeader(new BinaryReader(provider, true));
- dyldCacheHeader.parseFromFile(shouldProcessSymbols, log, monitor);
- monitor.incrementProgress(1);
+ try (SplitDyldCache splitDyldCache = new SplitDyldCache(provider, shouldProcessSymbols,
+ shouldCombineSplitFiles, log, monitor)) {
- setDyldCacheImageBase();
- processDyldCacheMemoryBlocks();
- fixPageChains();
- markupHeaders();
- markupBranchIslands();
- createSymbols();
- processDylibs();
+ // Set image base
+ setDyldCacheImageBase(splitDyldCache.getDyldCacheHeader(0));
+
+ // Setup memory
+ for (int i = 0; i < splitDyldCache.size(); i++) {
+ DyldCacheHeader header = splitDyldCache.getDyldCacheHeader(i);
+ ByteProvider bp = splitDyldCache.getProvider(i);
+
+ processDyldCacheMemoryBlocks(header, bp);
+ }
+
+ // Perform additional DYLD processing
+ for (int i = 0; i < splitDyldCache.size(); i++) {
+ DyldCacheHeader header = splitDyldCache.getDyldCacheHeader(i);
+ ByteProvider bp = splitDyldCache.getProvider(i);
+
+ fixPageChains(header);
+ markupHeaders(header);
+ markupBranchIslands(header, bp);
+ createSymbols(header);
+ processDylibs(header, bp);
+ }
+ }
}
/**
* Sets the program's image base.
*
+ * @param dyldCacheHeader The "base" DYLD Cache header
* @throws Exception if there was problem setting the program's image base
*/
- private void setDyldCacheImageBase() throws Exception {
+ private void setDyldCacheImageBase(DyldCacheHeader dyldCacheHeader) throws Exception {
monitor.setMessage("Setting image base...");
monitor.initialize(1);
program.setImageBase(space.getAddress(dyldCacheHeader.getBaseAddress()), true);
@@ -127,20 +150,24 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
/**
* Processes the DYLD Cache's memory mappings and creates memory blocks for them.
*
+ * @param dyldCacheHeader The {@link DyldCacheHeader}
+ * @param bp The corresponding {@link ByteProvider}
* @throws Exception if there was a problem creating the memory blocks
*/
- private void processDyldCacheMemoryBlocks() throws Exception {
+ private void processDyldCacheMemoryBlocks(DyldCacheHeader dyldCacheHeader, ByteProvider bp)
+ throws Exception {
List mappingInfos = dyldCacheHeader.getMappingInfos();
-
monitor.setMessage("Processing DYLD mapped memory blocks...");
monitor.initialize(mappingInfos.size());
+ FileBytes fb = MemoryBlockUtils.createFileBytes(program, bp, monitor);
long endOfMappedOffset = 0;
for (DyldCacheMappingInfo mappingInfo : mappingInfos) {
long offset = mappingInfo.getFileOffset();
long size = mappingInfo.getSize();
MemoryBlockUtils.createInitializedBlock(program, false, "DYLD",
- space.getAddress(mappingInfo.getAddress()), fileBytes, offset, size, "", "",
+ space.getAddress(mappingInfo.getAddress()), fb, offset, size, "", "",
mappingInfo.isRead(), mappingInfo.isWrite(), mappingInfo.isExecute(), log);
+
if (offset + size > endOfMappedOffset) {
endOfMappedOffset = offset + size;
}
@@ -148,21 +175,22 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
monitor.incrementProgress(1);
}
- if (endOfMappedOffset < provider.length()) {
+ if (endOfMappedOffset < bp.length()) {
monitor.setMessage("Processing DYLD unmapped memory block...");
MemoryBlockUtils.createInitializedBlock(program, true, "FILE",
- AddressSpace.OTHER_SPACE.getAddress(endOfMappedOffset), fileBytes,
- endOfMappedOffset, provider.length() - endOfMappedOffset,
- "Useful bytes that don't get mapped into memory", "", false, false, false, log);
+ AddressSpace.OTHER_SPACE.getAddress(endOfMappedOffset), fb, endOfMappedOffset,
+ bp.length() - endOfMappedOffset, "Useful bytes that don't get mapped into memory",
+ "", false, false, false, log);
}
}
/**
* Marks up the DYLD Cache headers.
*
+ * @param dyldCacheHeader The {@link DyldCacheHeader}
* @throws Exception if there was a problem marking up the headers
*/
- private void markupHeaders() throws Exception {
+ private void markupHeaders(DyldCacheHeader dyldCacheHeader) throws Exception {
monitor.setMessage("Marking up DYLD headers...");
monitor.initialize(1);
dyldCacheHeader.parseFromMemory(program, space, log, monitor);
@@ -173,15 +201,18 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
/**
* Marks up the DYLD Cache branch islands.
*
+ * @param dyldCacheHeader The {@link DyldCacheHeader}
+ * @param bp The corresponding {@link ByteProvider}
* @throws Exception if there was a problem marking up the branch islands.
*/
- private void markupBranchIslands() throws Exception {
+ private void markupBranchIslands(DyldCacheHeader dyldCacheHeader, ByteProvider bp)
+ throws Exception {
monitor.setMessage("Marking up DYLD branch islands...");
monitor.initialize(dyldCacheHeader.getBranchPoolAddresses().size());
for (Long addr : dyldCacheHeader.getBranchPoolAddresses()) {
try {
MachHeader header =
- MachHeader.createMachHeader(MessageLogContinuesFactory.create(log), provider,
+ MachHeader.createMachHeader(MessageLogContinuesFactory.create(log), bp,
addr - dyldCacheHeader.getBaseAddress());
header.parse();
super.markupHeaders(header, space.getAddress(addr));
@@ -197,9 +228,10 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
/**
* Creates the DYLD Cache symbols.
*
+ * @param dyldCacheHeader The {@link DyldCacheHeader}
* @throws Exception if there was a problem creating the symbols
*/
- private void createSymbols() throws Exception {
+ private void createSymbols(DyldCacheHeader dyldCacheHeader) throws Exception {
DyldCacheLocalSymbolsInfo localSymbolsInfo = dyldCacheHeader.getLocalSymbolsInfo();
if (localSymbolsInfo != null) {
monitor.setMessage("Processing DYLD symbols...");
@@ -225,10 +257,12 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
/**
* Fixes any chained pointers within each of the data pages.
*
+ * @param dyldCacheHeader The {@link DyldCacheHeader}
* @throws MemoryAccessException if there was a problem reading/writing memory.
* @throws CancelledException if user cancels
*/
- private void fixPageChains() throws MemoryAccessException, CancelledException {
+ private void fixPageChains(DyldCacheHeader dyldCacheHeader)
+ throws MemoryAccessException, CancelledException {
// locate slide Info
List slideInfos = dyldCacheHeader.getSlideInfos();
for (DyldCacheSlideInfoCommon info : slideInfos) {
@@ -243,19 +277,22 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
* Processes the DYLD Cache's DYLIB files. This will mark up the DYLIB files, added them to the
* program tree, and make memory blocks for them.
*
+ * @param dyldCacheHeader The {@link DyldCacheHeader}
+ * @param bp The corresponding {@link ByteProvider}
* @throws Exception if there was a problem processing the DYLIB files
*/
- private void processDylibs() throws Exception {
+ private void processDylibs(DyldCacheHeader dyldCacheHeader, ByteProvider bp) throws Exception {
// Create an "info" object for each DyldCache DYLIB, which will make processing them
// easier
monitor.setMessage("Parsing DYLIB's...");
- monitor.initialize(dyldCacheHeader.getImageInfos().size());
TreeSet infoSet =
new TreeSet<>((a, b) -> a.headerAddr.compareTo(b.headerAddr));
- for (DyldCacheImageInfo dyldCacheImageInfo : dyldCacheHeader.getImageInfos()) {
- infoSet.add(new DyldCacheMachoInfo(provider,
- dyldCacheImageInfo.getAddress() - dyldCacheHeader.getBaseAddress(),
- space.getAddress(dyldCacheImageInfo.getAddress()), dyldCacheImageInfo.getPath()));
+ List mappedImages = dyldCacheHeader.getMappedImages();
+ monitor.initialize(mappedImages.size());
+ for (DyldCacheImage mappedImage : mappedImages) {
+ infoSet.add(new DyldCacheMachoInfo(bp,
+ mappedImage.getAddress() - dyldCacheHeader.getBaseAddress(),
+ space.getAddress(mappedImage.getAddress()), mappedImage.getPath()));
monitor.checkCanceled();
monitor.incrementProgress(1);
}
@@ -278,7 +315,7 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
do {
DyldCacheMachoInfo next = iter.hasNext() ? iter.next() : null;
try {
- curr.addToProgramTree(next);
+ curr.addToProgramTree(dyldCacheHeader, next);
}
catch (DuplicateNameException exc) {
log.appendException(exc);
@@ -357,11 +394,13 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
/**
* Adds an entry to the program tree for this Mach-O
*
+ * @param dyldCacheHeader The DYLD Cache header
* @param next The Mach-O that comes directly after this one. Could be null if this
* is the last one.
* @throws Exception If there was a problem adding this Mach-O to the program tree
*/
- public void addToProgramTree(DyldCacheMachoInfo next) throws Exception {
+ public void addToProgramTree(DyldCacheHeader dyldCacheHeader, DyldCacheMachoInfo next)
+ throws Exception {
ProgramFragment fragment = listing.getDefaultRootModule().createFragment(path);
if (next != null) {
fragment.move(headerAddr, next.headerAddr.subtract(1));
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 d1f79020c0..75d8b5b521 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
@@ -15,13 +15,19 @@
*/
package ghidra.app.util.opinion;
-import java.io.IOException;
+import java.io.*;
+import java.nio.file.AccessMode;
+import java.util.*;
-import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.macho.dyld.DyldArchitecture;
+import ghidra.app.util.bin.format.macho.dyld.DyldCacheHeader;
+import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
/**
* Utilities methods for working with Mach-O DYLD shared cache binaries.
@@ -89,4 +95,124 @@ public class DyldCacheUtils {
return false;
}
+ /**
+ * Class to store a "split" DYLD Cache, which is split across several files (base file, .1, .2,
+ * .symbols, etc).
+ */
+ public static class SplitDyldCache implements Closeable {
+
+ List providers = new ArrayList<>();
+ List headers = new ArrayList<>();
+
+ /**
+ * Creates a new {@link SplitDyldCache}
+ *
+ * @param baseProvider The {@link ByteProvider} of the "base" DYLD Cache file
+ * @param shouldProcessSymbols True if symbols should be processed; otherwise, false
+ * @param shouldCombineSplitFiles True if split DYLD Cache files should be automatically
+ * @param log The log
+ * @param monitor A cancelable task monitor
+ * @throws IOException If there was an IO-related issue with processing the split DYLD Cache
+ * @throws CancelledException If the user canceled the operation
+ */
+ public SplitDyldCache(ByteProvider baseProvider, boolean shouldProcessSymbols,
+ boolean shouldCombineSplitFiles, MessageLog log, TaskMonitor monitor)
+ throws IOException, CancelledException {
+
+ // Setup "base" DYLD Cache
+ monitor.setMessage("Parsing " + baseProvider.getName() + " headers...");
+ providers.add(baseProvider);
+ DyldCacheHeader baseHeader = new DyldCacheHeader(new BinaryReader(baseProvider, true));
+ baseHeader.parseFromFile(shouldProcessSymbols, log, monitor);
+ headers.add(baseHeader);
+
+ // Setup additional "split" DYLD Caches (if applicable)
+ for (File splitFile : getSplitDyldCacheFiles(baseProvider, shouldCombineSplitFiles)) {
+ monitor.setMessage("Parsing " + splitFile.getName() + " headers...");
+ ByteProvider provider = new FileByteProvider(splitFile, null, AccessMode.READ);
+ if (!DyldCacheUtils.isDyldCache(provider)) {
+ continue;
+ }
+ providers.add(provider);
+ DyldCacheHeader splitHeader = new DyldCacheHeader(new BinaryReader(provider, true));
+ splitHeader.parseFromFile(shouldProcessSymbols, log, monitor);
+ headers.add(splitHeader);
+ log.appendMsg("Including split DYLD: " + splitFile.getName());
+ }
+ }
+
+ /**
+ * Gets the i'th {@link ByteProvider} in the split DYLD Cache
+ *
+ * @param i The index of the {@link ByteProvider} to get
+ * @return The i'th {@link ByteProvider} in the split DYLD Cache
+ */
+ public ByteProvider getProvider(int i) {
+ return providers.get(i);
+ }
+
+ /**
+ * Gets the i'th {@link DyldCacheHeader} in the split DYLD Cache
+ *
+ * @param i The index of the {@link DyldCacheHeader} to get
+ * @return The i'th {@link DyldCacheHeader} in the split DYLD Cache
+ */
+ public DyldCacheHeader getDyldCacheHeader(int i) {
+ return headers.get(i);
+ }
+
+ /**
+ * Gets the number of split DYLD Cache files
+ *
+ * @return The number of split DYLD Cache files
+ */
+ public int size() {
+ return providers.size();
+ }
+
+ @Override
+ public void close() throws IOException {
+ // Assume someone else is responsible for closing the base providers that was passed
+ // in at construction
+ for (int i = 1; i < providers.size(); i++) {
+ providers.get(i).close();
+ }
+ }
+
+ /**
+ * Gets a {@link List} of extra split DYLD Cache files to load, sorted by name (base
+ * DYLD Cache file not included)
+ *
+ * @param baseProvider The base {@link ByteProvider} that contains the DYLD Cache bytes
+ * @param shouldCombineSplitFiles True if split DYLD Cache files should be automatically
+ * combined into one DYLD Cache; false if only the base file should be processed
+ * @return A {@link List} of extra split DYLD Cache files to load, sorted by name (base
+ * DYLD Cache file not included).
+ */
+ private List getSplitDyldCacheFiles(ByteProvider baseProvider,
+ boolean shouldCombineSplitFiles) {
+ File file = baseProvider.getFile();
+ if (file != null && shouldCombineSplitFiles) {
+ String baseName = file.getName();
+ File[] splitFiles = file.getParentFile().listFiles(f -> {
+ if (!f.getName().startsWith(baseName)) {
+ return false;
+ }
+ if (f.getName().equals(baseName)) {
+ return false;
+ }
+ if (f.getName().toLowerCase().endsWith(".map")) {
+ return false;
+ }
+ return true;
+ });
+ if (splitFiles != null) {
+ List list = Arrays.asList(splitFiles);
+ Collections.sort(list);
+ return list;
+ }
+ }
+ return Collections.emptyList();
+ }
+ }
}
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 474d2f8241..6faa7c836f 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
@@ -23,6 +23,9 @@ import generic.continues.RethrowContinuesFactory;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.macho.*;
import ghidra.app.util.bin.format.macho.commands.*;
+import ghidra.app.util.bin.format.macho.dyld.DyldCacheHeader;
+import ghidra.app.util.bin.format.macho.dyld.DyldCacheMappingInfo;
+import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.util.*;
import ghidra.util.exception.NotFoundException;
@@ -38,26 +41,29 @@ public class DyldCacheDylibExtractor {
* DYLIB's header will be altered to account for its segment bytes being packed down.
*
* @param dylibOffset The offset of the DYLIB in the given provider
- * @param provider The DYLD
- * @param fsrl {@link FSRL} to assign to the resulting ByteProvider
+ * @param splitDyldCache The {@link SplitDyldCache}
+ * @param index The DYLIB's {@link SplitDyldCache} index
+ * @param fsrl {@link FSRL} to assign to the resulting {@link ByteProvider}
* @param monitor {@link TaskMonitor}
- * @return {@link ByteProvider} containing the bytes of the dylib
+ * @return {@link ByteProvider} containing the bytes of the DYLIB
* @throws IOException If there was an IO-related issue with extracting the DYLIB
* @throws MachException If there was an error parsing the DYLIB headers
*/
- public static ByteProvider extractDylib(long dylibOffset, ByteProvider provider, FSRL fsrl,
- TaskMonitor monitor) throws IOException, MachException {
+ public static ByteProvider extractDylib(long dylibOffset, SplitDyldCache splitDyldCache,
+ int index, FSRL fsrl, TaskMonitor monitor) throws IOException, MachException {
// Make sure Mach-O header is valid
- MachHeader header = MachHeader.createMachHeader(RethrowContinuesFactory.INSTANCE, provider,
- dylibOffset, false);
- header.parse();
+ MachHeader dylibHeader = MachHeader.createMachHeader(RethrowContinuesFactory.INSTANCE,
+ splitDyldCache.getProvider(index), dylibOffset, false);
+ dylibHeader.parse();
// Pack the DYLIB
- PackedDylib packedDylib = new PackedDylib(header, dylibOffset, provider);
+ PackedDylib packedDylib = new PackedDylib(dylibHeader, dylibOffset, splitDyldCache, index);
+
+ // TODO: Fixup pointer chains
// Fixup indices, offsets, etc in the packed DYLIB's header
- for (LoadCommand cmd : header.getLoadCommands()) {
+ for (LoadCommand cmd : dylibHeader.getLoadCommands()) {
if (monitor.isCancelled()) {
break;
}
@@ -203,17 +209,18 @@ public class DyldCacheDylibExtractor {
/**
* Creates a new {@link PackedDylib} object
*
- * @param header The DYLD's DYLIB's Mach-O header
+ * @param dylibHeader The DYLD's DYLIB's Mach-O header
* @param dylibOffset The offset of the DYLIB in the given provider
- * @param provider The DYLD's bytes
+ * @param splitDyldCache The {@link SplitDyldCache}
+ * @param index The DYLIB's {@link SplitDyldCache} index
* @throws IOException If there was an IO-related error
*/
- public PackedDylib(MachHeader header, long dylibOffset, ByteProvider provider)
- throws IOException {
- reader = new BinaryReader(provider, true);
+ public PackedDylib(MachHeader dylibHeader, long dylibOffset, SplitDyldCache splitDyldCache,
+ int index) throws IOException {
+ reader = new BinaryReader(splitDyldCache.getProvider(index), true);
packedStarts = new HashMap<>();
int size = 0;
- for (SegmentCommand segment : header.getAllSegments()) {
+ for (SegmentCommand segment : dylibHeader.getAllSegments()) {
packedStarts.put(segment, size);
size += segment.getFileSize();
@@ -224,14 +231,15 @@ public class DyldCacheDylibExtractor {
}
}
packed = new byte[size];
- for (SegmentCommand segment : header.getAllSegments()) {
+ for (SegmentCommand segment : dylibHeader.getAllSegments()) {
long segmentSize = segment.getFileSize();
- if (segment.getFileOffset() + segmentSize > provider.length()) {
- segmentSize = provider.length() - segment.getFileOffset();
+ ByteProvider segmentProvider = getSegmentProvider(segment, splitDyldCache);
+ if (segment.getFileOffset() + segmentSize > segmentProvider.length()) {
+ segmentSize = segmentProvider.length() - segment.getFileOffset();
Msg.warn(this, segment.getSegmentName() +
" segment extends beyond end of file. Truncating...");
}
- byte[] bytes = provider.readBytes(segment.getFileOffset(), segmentSize);
+ byte[] bytes = segmentProvider.readBytes(segment.getFileOffset(), segmentSize);
System.arraycopy(bytes, 0, packed, packedStarts.get(segment), bytes.length);
}
}
@@ -283,6 +291,28 @@ public class DyldCacheDylibExtractor {
Long.toHexString(fileOffset));
}
+ /**
+ * Gets the {@link ByteProvider} that contains the given {@link SegmentCommand segment}
+ *
+ * @param segment The {@link SegmentCommand segment}
+ * @param splitDyldCache The {@link SplitDyldCache}
+ * @return The {@link ByteProvider} that contains the given {@link SegmentCommand segment}
+ * @throws IOException If a {@link ByteProvider} could not be found
+ */
+ private ByteProvider getSegmentProvider(SegmentCommand segment,
+ SplitDyldCache splitDyldCache) throws IOException {
+ for (int i = 0; i < splitDyldCache.size(); i++) {
+ DyldCacheHeader header = splitDyldCache.getDyldCacheHeader(i);
+ for (DyldCacheMappingInfo mappingInfo : header.getMappingInfos()) {
+ if (mappingInfo.contains(segment.getVMaddress())) {
+ return splitDyldCache.getProvider(i);
+ }
+ }
+ }
+ throw new IOException(
+ "Failed to find provider for segment: " + segment.getSegmentName());
+ }
+
/**
* Converts the given value to a byte array
*
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 e0c9d84409..3adf5f8ad2 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
@@ -18,13 +18,13 @@ package ghidra.file.formats.ios.dyldcache;
import java.io.IOException;
import java.util.*;
-import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.macho.MachException;
import ghidra.app.util.bin.format.macho.dyld.DyldCacheHeader;
-import ghidra.app.util.bin.format.macho.dyld.DyldCacheImageInfo;
+import ghidra.app.util.bin.format.macho.dyld.DyldCacheImage;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.DyldCacheUtils;
+import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
@@ -35,8 +35,9 @@ import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "dyldcachev1", description = "iOS DYLD Cache Version 1", factory = GFileSystemBaseFactory.class)
public class DyldCacheFileSystem extends GFileSystemBase {
- private DyldCacheHeader header;
- private Map map = new HashMap<>();
+ private SplitDyldCache splitDyldCache;
+ private Map addrMap = new HashMap<>();
+ private Map indexMap = new HashMap<>();
public DyldCacheFileSystem(String fileSystemName, ByteProvider provider) {
super(fileSystemName, provider);
@@ -44,20 +45,24 @@ public class DyldCacheFileSystem extends GFileSystemBase {
@Override
public void close() throws IOException {
- map.clear();
+ addrMap.clear();
+ indexMap.clear();
+ splitDyldCache.close();
super.close();
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException {
- DyldCacheImageInfo data = map.get(file);
- if (data == null) {
+ Long addr = addrMap.get(file);
+ if (addr == null) {
return null;
}
- long machHeaderStartIndexInProvider = data.getAddress() - header.getBaseAddress();
+ int index = indexMap.get(file);
+ long machHeaderStartIndexInProvider =
+ addr - splitDyldCache.getDyldCacheHeader(index).getBaseAddress();
try {
- return DyldCacheDylibExtractor.extractDylib(machHeaderStartIndexInProvider, provider,
- file.getFSRL(), monitor);
+ return DyldCacheDylibExtractor.extractDylib(machHeaderStartIndexInProvider,
+ splitDyldCache, index, file.getFSRL(), monitor);
}
catch (MachException e) {
throw new IOException("Invalid Mach-O header detected at 0x" +
@@ -65,49 +70,11 @@ public class DyldCacheFileSystem extends GFileSystemBase {
}
}
-/*
-// TODO: support GFileSystemProgramProvider interface?
-// Below is commented out implementation of getProgram(), that was present as a comment
-// in the previous code, but formatted here so it can be read.
-// This needs to be researched and the junit test needs to adjusted to test this.
- @Override
- public Program getProgram(GFile file, LanguageService languageService, TaskMonitor monitor,
- Object consumer) throws Exception {
- DyldArchitecture architecture = header.getArchitecture();
- LanguageCompilerSpecPair lcs = architecture.getLanguageCompilerSpecPair(languageService);
- DyldCacheData dyldCacheData = map.get(file);
- long machHeaderStartIndexInProvider =
- dyldCacheData.getLibraryOffset() - header.getBaseAddress();
- ByteProvider wrapper =
- new ByteProviderWrapper(provider, machHeaderStartIndexInProvider, file.getLength());
- MachHeader machHeader =
- MachHeader.createMachHeader(RethrowContinuesFactory.INSTANCE, wrapper);
- Program program =
- new ProgramDB(file.getName(), lcs.getLanguage(), lcs.getCompilerSpec(), consumer);
- int id = program.startTransaction(getName());
- boolean success = false;
- try {
- MachoLoader loader = new MachoLoader();
- loader.load(machHeader, program, new MessageLog(), monitor);
- program.setExecutableFormat(MachoLoader.MACH_O_NAME);
- program.setExecutablePath(file.getAbsolutePath());
- success = true;
- }
- finally {
- program.endTransaction(id, success);
- if (!success) {
- program.release(consumer);
- }
- }
- return program;
- }
-*/
-
@Override
public List getListing(GFile directory) throws IOException {
if (directory == null || directory.equals(root)) {
List roots = new ArrayList<>();
- for (GFile file : map.keySet()) {
+ for (GFile file : addrMap.keySet()) {
if (file.getParentFile() == root || file.getParentFile().equals(root)) {
roots.add(file);
}
@@ -115,7 +82,7 @@ public class DyldCacheFileSystem extends GFileSystemBase {
return roots;
}
List tmp = new ArrayList<>();
- for (GFile file : map.keySet()) {
+ for (GFile file : addrMap.keySet()) {
if (file.getParentFile() == null) {
continue;
}
@@ -133,41 +100,37 @@ public class DyldCacheFileSystem extends GFileSystemBase {
@Override
public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
+ MessageLog log = new MessageLog();
monitor.setMessage("Opening DYLD cache...");
-
- BinaryReader reader = new BinaryReader(provider, true);
-
- header = new DyldCacheHeader(reader);
- header.parseFromFile(false, new MessageLog(), monitor);
-
- List dataList = header.getImageInfos();
-
- monitor.initialize(dataList.size());
-
- for (DyldCacheImageInfo data : dataList) {
-
- if (monitor.isCancelled()) {
- break;
+
+ splitDyldCache = new SplitDyldCache(provider, false, true, log, monitor);
+ for (int i = 0; i < splitDyldCache.size(); i++) {
+ DyldCacheHeader header = splitDyldCache.getDyldCacheHeader(i);
+ monitor.setMessage("Find files...");
+ List mappedImages = header.getMappedImages();
+ monitor.initialize(mappedImages.size());
+ for (DyldCacheImage mappedImage : mappedImages) {
+ GFileImpl file =
+ GFileImpl.fromPathString(this, root, mappedImage.getPath(), null, false, -1);
+ storeFile(file, mappedImage.getAddress(), i);
+ monitor.checkCanceled();
+ monitor.incrementProgress(1);
}
-
- monitor.incrementProgress(1);
-
- GFileImpl file = GFileImpl.fromPathString(this, root, data.getPath(), null, false, -1);
- storeFile(file, data);
}
}
- private void storeFile(GFile file, DyldCacheImageInfo data) {
+ private void storeFile(GFile file, Long addr, Integer index) {
if (file == null) {
return;
}
if (file.equals(root)) {
return;
}
- if (!map.containsKey(file) || map.get(file) == null) {
- map.put(file, data);
+ if (!addrMap.containsKey(file) || addrMap.get(file) == null) {
+ addrMap.put(file, addr);
+ indexMap.put(file, index);
}
GFile parentFile = file.getParentFile();
- storeFile(parentFile, null);
+ storeFile(parentFile, null, null);
}
}