diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DyldChainedFixupsCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DyldChainedFixupsCommand.java index 3487de4698..856eb89317 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DyldChainedFixupsCommand.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DyldChainedFixupsCommand.java @@ -112,8 +112,9 @@ public class DyldChainedFixupsCommand extends LinkEditDataCommand { try { super.markupRawBinary(header, api, baseAddress, parentModule, monitor, log); - List
addrs = - api.getCurrentProgram().getMemory().locateAddressesForFileOffset(getDataOffset()); + List
addrs = api.getCurrentProgram() + .getMemory() + .locateAddressesForFileOffset(getLinkerDataOffset()); if (addrs.size() <= 0) { throw new Exception("Chain Header does not exist in program"); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DynamicSymbolTableCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DynamicSymbolTableCommand.java index 0a24a2e396..a86a9158a3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DynamicSymbolTableCommand.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DynamicSymbolTableCommand.java @@ -304,6 +304,16 @@ public class DynamicSymbolTableCommand extends LoadCommand { return localRelocations; } + @Override + public int getLinkerDataOffset() { + return indirectsymoff; + } + + @Override + public int getLinkerDataSize() { + return nindirectsyms * Integer.BYTES; + } + @Override public DataType toDataType() throws DuplicateNameException, IOException { StructureDataType struct = new StructureDataType(getCommandName(), 0); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/LinkEditDataCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/LinkEditDataCommand.java index ef3246abc4..3ab82117cf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/LinkEditDataCommand.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/LinkEditDataCommand.java @@ -57,11 +57,13 @@ public class LinkEditDataCommand extends LoadCommand { this.dataReader.setPointerIndex(dataoff); } - public int getDataOffset() { + @Override + public int getLinkerDataOffset() { return dataoff; } - public int getDataSize() { + @Override + public int getLinkerDataSize() { return datasize; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/LoadCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/LoadCommand.java index 1047dbd915..521932eff7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/LoadCommand.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/LoadCommand.java @@ -85,6 +85,26 @@ public abstract class LoadCommand implements StructConverter { */ public abstract String getCommandName(); + /** + * Gets the file offset of this load command's "linker data". Not all load commands with data + * will have linker data. Linker data typically resides in the __LINKEDIT segment. + * + * @return The file offset of this load command's "linker data", or 0 if it has no linker data + */ + public int getLinkerDataOffset() { + return 0; + } + + /** + * Gets the file size of this load command's "linker data". Not all load commands with data + * will have linker data. Linker data typically resides in the __LINKEDIT segment. + * + * @return The file size of this load command's "linker data", or 0 if it has no linker data + */ + public int getLinkerDataSize() { + return 0; + } + /** * Gets the {@link Address} of this load command's "data" * 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 c2e97d397e..0bcddce7b0 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 @@ -116,6 +116,10 @@ public class SegmentCommand extends LoadCommand { return vmsize; } + public void setVMsize(long vmSize) { + vmsize = vmSize; + } + public long getFileOffset() { return fileoff; } @@ -128,6 +132,10 @@ public class SegmentCommand extends LoadCommand { return filesize; } + public void setFileSize(long fileSize) { + filesize = fileSize; + } + /** * Returns a octal model value reflecting the * segment's maximum protection value allowed. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SymbolTableCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SymbolTableCommand.java index 52561fe172..baa33afa63 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SymbolTableCommand.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SymbolTableCommand.java @@ -74,12 +74,12 @@ public class SymbolTableCommand extends LoadCommand { List sortedList = nlistList.stream().sorted((o1, o2) -> Integer.compare(o1.getStringTableIndex(), o2.getStringTableIndex())).collect(Collectors.toList()); - + // initialize the sorted NList strings from string table for (NList nList : sortedList) { nList.initString(dataReader, stroff); } - + // the symbol table should be in the original order. // The table is indexed by other tables in the MachO headers symbols = nlistList; @@ -153,6 +153,23 @@ public class SymbolTableCommand extends LoadCommand { return "symtab_command"; } + @Override + public int getLinkerDataOffset() { + return symoff; + } + + @Override + public int getLinkerDataSize() { + if (!symbols.isEmpty()) { + int totalStringSize = 0; + for (NList nlist : symbols) { + totalStringSize += nlist.getString().length() + 1; // Add 1 for null terminator + } + return nsyms * symbols.get(0).getSize() + totalStringSize; + } + return 0; + } + @Override public Address getDataAddress(MachHeader header, AddressSpace space) { if (symoff != 0 && nsyms != 0) { 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 ae81661b22..816cbb5dc7 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 @@ -16,8 +16,8 @@ package ghidra.file.formats.ios.dyldcache; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; +import java.nio.charset.StandardCharsets; +import java.util.*; import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.macho.*; @@ -66,8 +66,11 @@ public class DyldCacheDylibExtractor { private BinaryReader reader; private MachHeader header; + private SegmentCommand textSegment; + private SegmentCommand linkEditSegment; private Map packedSegmentStarts = new HashMap<>(); private Map packedSegmentAdjustments = new HashMap<>(); + private Map packedLinkEditDataStarts = new HashMap<>(); private byte[] packed; private TaskMonitor monitor; @@ -84,30 +87,52 @@ public class DyldCacheDylibExtractor { public PackedSegments(long dylibOffset, SplitDyldCache splitDyldCache, int index, TaskMonitor monitor) throws MachException, IOException { ByteProvider provider = splitDyldCache.getProvider(index); - this.reader = new BinaryReader(provider, true); this.header = new MachHeader(provider, dylibOffset, false).parse(splitDyldCache); + this.textSegment = header.getSegment(SegmentNames.SEG_TEXT); + this.linkEditSegment = header.getSegment(SegmentNames.SEG_LINKEDIT); + this.reader = new BinaryReader(provider, header.isLittleEndian()); this.monitor = monitor; // Keep track of each segment's file offset in the DYLD cache. // Also keep a running total of each segment's size so we know how big to make our // packed array. int packedSize = 0; + int packedLinkEditSize = 0; for (SegmentCommand segment : header.getAllSegments()) { packedSegmentStarts.put(segment, packedSize); - packedSize += segment.getFileSize(); + + // The __LINKEDIT segment is shared across all DYLIB's, so it is very large. We + // Want to create a new packed __LINKEDIT segment with only the relevant info for + // the DYLIB we are extracting, resulting in a significantly smaller file. + if (segment == linkEditSegment) { + for (LoadCommand cmd : header.getLoadCommands()) { + int offset = cmd.getLinkerDataOffset(); + int size = cmd.getLinkerDataSize(); + if (offset == 0 || size == 0) { + continue; + } + packedLinkEditDataStarts.put(cmd, packedLinkEditSize); + packedLinkEditSize += size; + } + packedSize += packedLinkEditSize; + segment.setFileSize(packedLinkEditSize); + segment.setVMsize(packedLinkEditSize); + } + else { + packedSize += segment.getFileSize(); + } // Some older DYLDs use a file offset of 0 for their __TEXT segment, despite being // in the middle of the cache and despite the other segments using absolute cache // file offsets. Adjust these segments to be consistent with all the other segments, // and store their adjustment values so we can later work with them as absolute // cache file offsets. - if (segment.getSegmentName().equals(SegmentNames.SEG_TEXT) && - segment.getFileOffset() == 0) { + if (segment == textSegment && segment.getFileOffset() == 0) { segment.setFileOffset(dylibOffset); packedSegmentAdjustments.put(segment, (int) dylibOffset); } } - + packed = new byte[packedSize]; // Copy each segment into the packed array (leaving no gaps) @@ -119,7 +144,14 @@ public class DyldCacheDylibExtractor { Msg.warn(this, segment.getSegmentName() + " segment extends beyond end of file. Truncating..."); } - byte[] bytes = segmentProvider.readBytes(segment.getFileOffset(), segmentSize); + byte[] bytes; + if (segment == linkEditSegment) { + bytes = + createPackedLinkEditSegment(segmentProvider, packedLinkEditSize); + } + else { + bytes = segmentProvider.readBytes(segment.getFileOffset(), segmentSize); + } System.arraycopy(bytes, 0, packed, packedSegmentStarts.get(segment), bytes.length); } @@ -141,45 +173,298 @@ public class DyldCacheDylibExtractor { } /** - * Sets the bytes at the given packed DYLIB offset to the given value - * - * @param packedOffset The packed DYLIB offset to fix-up - * @param value The new value - * @param size The number of bytes to set (must be 4 or 8) - * @throws IllegalArgumentException if size is an unsupported value + * Creates a packed __LINKEDIT segment array + * + * @param linkEditSegmentProvider The {@link ByteProvider} that contains the __LINKEDIT + * segment + * @param packedLinkEditSize The size in bytes of the packed __LINKEDIT segment + * @return A packed __LINKEDIT segment array + * @throws IOException If there was an IO-related error */ - public void set(int packedOffset, long value, int size) throws IllegalArgumentException { - if (size != 4 && size != 8) { - throw new IllegalArgumentException("Size must be 4 or 8 (got " + size + ")"); + private byte[] createPackedLinkEditSegment(ByteProvider linkEditSegmentProvider, + int packedLinkEditSize) throws IOException { + byte[] packedLinkEdit = new byte[packedLinkEditSize]; + + for (LoadCommand cmd : packedLinkEditDataStarts.keySet()) { + if (cmd instanceof SymbolTableCommand symbolTable && + symbolTable.getNumberOfSymbols() > 0) { + byte[] packedSymbolStringTable = new byte[cmd.getLinkerDataSize()]; + List symbols = symbolTable.getSymbols(); + int nlistIndex = 0; + int stringIndex = symbols.get(0).getSize() * symbols.size(); + int stringIndexOrig = stringIndex; + for (NList nlist : symbols) { + byte[] nlistArray = nlistToArray(nlist, stringIndex); + byte[] stringArray = nlist.getString().getBytes(StandardCharsets.US_ASCII); + System.arraycopy(toBytes(stringIndex - stringIndexOrig, 4), 0, nlistArray, + 0, 4); + System.arraycopy(nlistArray, 0, packedSymbolStringTable, nlistIndex, + nlistArray.length); + System.arraycopy(stringArray, 0, packedSymbolStringTable, stringIndex, + stringArray.length); + nlistIndex += nlistArray.length; + stringIndex += stringArray.length + 1; // null terminate + } + System.arraycopy(packedSymbolStringTable, 0, packedLinkEdit, + packedLinkEditDataStarts.get(cmd), + packedSymbolStringTable.length); + } + else { + byte[] bytes = + linkEditSegmentProvider.readBytes(cmd.getLinkerDataOffset(), cmd.getLinkerDataSize()); + System.arraycopy(bytes, 0, packedLinkEdit, packedLinkEditDataStarts.get(cmd), + bytes.length); + } } - byte[] newBytes = toBytes(value, size); - System.arraycopy(newBytes, 0, packed, packedOffset, newBytes.length); + + return packedLinkEdit; } /** - * Fixes up the bytes at the given DYLD file offset to map to the correct offset in the - * packed DYLIB. The provided file offset is assumed to map to a field in in a load - * command. + * Converts the given {@link NList} to a byte array. The given {@link NList}'s string + * index field will be replaced with the given string index parameter. + * + * @param nlist The {@link NList} to convert + * @param stringIndex The new string index + * @return A new {@link NList} in byte array form + */ + private byte[] nlistToArray(NList nlist, int stringIndex) { + byte[] ret = new byte[nlist.getSize()]; + DataConverter conv = DataConverter.getInstance(!header.isLittleEndian()); + conv.putInt(ret, 0, stringIndex); + ret[4] = nlist.getType(); + ret[5] = nlist.getSection(); + conv.putShort(ret, 6, nlist.getDescription()); + if (nlist.is32bit()) { + conv.putInt(ret, 8, (int) nlist.getValue()); + } + else { + conv.putLong(ret, 8, nlist.getValue()); + } + return ret; + } + + /** + * Fixes-up the {@link MachHeader} in the newly packed DYLIB + * + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private void fixupMachHeader() throws IOException { + // Indicate that the new packed DYLIB is no longer in the cache + set(header.getStartIndexInProvider() + 0x18, + header.getFlags() & ~MachHeaderFlags.MH_DYLIB_IN_CACHE, 4); + } + + /** + * Fixes-up various fields in the new packed DYLIB's load commands + * + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private void fixupLoadCommands() throws IOException { + // Fixup indices, offsets, etc in the packed DYLIB's load commands + for (LoadCommand cmd : header.getLoadCommands()) { + if (monitor.isCancelled()) { + break; + } + switch (cmd.getCommandType()) { + case LoadCommandTypes.LC_SEGMENT: + fixupSegment((SegmentCommand) cmd, false); + break; + case LoadCommandTypes.LC_SEGMENT_64: + fixupSegment((SegmentCommand) cmd, true); + break; + case LoadCommandTypes.LC_SYMTAB: + fixupSymbolTable((SymbolTableCommand) cmd); + break; + case LoadCommandTypes.LC_DYSYMTAB: + fixupDynamicSymbolTable((DynamicSymbolTableCommand) cmd); + break; + case LoadCommandTypes.LC_DYLD_INFO: + case LoadCommandTypes.LC_DYLD_INFO_ONLY: + fixupDyldInfo((DyldInfoCommand) cmd); + break; + case LoadCommandTypes.LC_CODE_SIGNATURE: + case LoadCommandTypes.LC_SEGMENT_SPLIT_INFO: + case LoadCommandTypes.LC_FUNCTION_STARTS: + case LoadCommandTypes.LC_DATA_IN_CODE: + case LoadCommandTypes.LC_DYLIB_CODE_SIGN_DRS: + case LoadCommandTypes.LC_OPTIMIZATION_HINT: + case LoadCommandTypes.LC_DYLD_EXPORTS_TRIE: + case LoadCommandTypes.LC_DYLD_CHAINED_FIXUPS: + fixupLinkEditData((LinkEditDataCommand) cmd); + break; + } + } + } + + /** + * Fixes-up the old DYLD file offsets and size in the given segment so they are correct for + * the newly packed DYLIB + * + * @param segment The segment to fix-up + * @param is64bit True if the segment is 64-bit; false if 32-bit + * @throws IOException If there was an IO-related issue performing the fix-up + */ + 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.getVMsize() > 0) { + set(segment.getStartIndex() + (is64bit ? 0x20 : 0x1c), segment.getVMsize(), + is64bit ? 8 : 4); + } + if (segment.getFileSize() > 0) { + set(segment.getStartIndex() + (is64bit ? 0x30 : 0x24), segment.getFileSize(), + is64bit ? 8 : 4); + } + long sectionStartIndex = segment.getStartIndex() + (is64bit ? 0x48 : 0x38); + for (Section section : segment.getSections()) { + if (monitor.isCancelled()) { + break; + } + + // For some reason the section file offsets in the iOS 10 DYLD cache do not want + // the adjustment despite the segment needed it. We can expect to see warnings + // in that particular version. + if (section.getOffset() > 0 && section.getSize() > 0) { + fixup(sectionStartIndex + (is64bit ? 0x30 : 0x28), adjustment, 4, segment); + } + if (section.getRelocationOffset() > 0) { + fixup(sectionStartIndex + (is64bit ? 0x38 : 0x30), adjustment, 4, segment); + } + sectionStartIndex += is64bit ? 0x50 : 0x44; + } + } + + /** + * Fixes-up the old DYLD file offsets in the given symbol table so they are correct for the + * newly packed DYLIB + * + * @param cmd The symbol table to fix-up + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private void fixupSymbolTable(SymbolTableCommand cmd) throws IOException { + if (cmd.getSymbolOffset() > 0) { + long symbolOffset = fixup(cmd.getStartIndex() + 0x8, getLinkEditAdjustment(cmd), 4, + linkEditSegment); + if (cmd.getStringTableOffset() > 0) { + if (cmd.getNumberOfSymbols() > 0) { + set(cmd.getStartIndex() + 0x10, + symbolOffset + cmd.getNumberOfSymbols() * cmd.getSymbolAt(0).getSize(), + 4); + } + else { + set(cmd.getStartIndex() + 0x10, symbolOffset, 4); + set(cmd.getStartIndex() + 0x14, 0, 4); + } + } + } + } + + /** + * Fixes-up the old DYLD file offsets in the given dynamic symbol table so they are correct + * for the newly packed DYLIB. + *

+ * NOTE: We are currently only extracting the Indirect Symbol Table, so zero-out the other + * fields that might point to data. + * + * @param cmd The dynamic symbol table to fix-up + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private void fixupDynamicSymbolTable(DynamicSymbolTableCommand cmd) throws IOException { + long adjustment = getLinkEditAdjustment(cmd); + if (cmd.getTableOfContentsOffset() > 0) { + set(cmd.getStartIndex() + 0x20, 0, 8); + } + if (cmd.getModuleTableOffset() > 0) { + set(cmd.getStartIndex() + 0x28, 0, 8); + } + if (cmd.getReferencedSymbolTableOffset() > 0) { + set(cmd.getStartIndex() + 0x30, 0, 8); + } + if (cmd.getIndirectSymbolTableOffset() > 0) { + fixup(cmd.getStartIndex() + 0x38, adjustment, 4, linkEditSegment); + } + if (cmd.getExternalRelocationOffset() > 0) { + set(cmd.getStartIndex() + 0x40, 0, 8); + } + if (cmd.getLocalRelocationOffset() > 0) { + set(cmd.getStartIndex() + 0x48, 0, 8); + } + } + + /** + * Fixes-up the old DYLD file offsets in the given DYLD Info command so they are correct for + * the newly packed DYLIB. + *

+ * NOTE: We are currently not extracting this load command, so zero-out all the fields. + * + * @param cmd The DYLD Info command to fix-up + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private void fixupDyldInfo(DyldInfoCommand cmd) throws IOException { + if (cmd.getRebaseOffset() > 0) { + set(cmd.getStartIndex() + 0x8, 0, 8); + } + if (cmd.getBindOffset() > 0) { + set(cmd.getStartIndex() + 0x10, 0, 8); + } + if (cmd.getWeakBindOffset() > 0) { + set(cmd.getStartIndex() + 0x18, 0, 8); + } + if (cmd.getLazyBindOffset() > 0) { + set(cmd.getStartIndex() + 0x20, 0, 8); + } + if (cmd.getExportOffset() > 0) { + set(cmd.getStartIndex() + 0x28, 0, 8); + } + } + + /** + * Fixes-up the old DYLD file offsets in the given link edit data command so they are correct + * for the newly packed DYLIB + * + * @param cmd The link edit data command to fix-up + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private void fixupLinkEditData(LinkEditDataCommand cmd) throws IOException { + if (cmd.getLinkerDataOffset() > 0) { + fixup(cmd.getStartIndex() + 0x8, getLinkEditAdjustment(cmd), 4, linkEditSegment); + } + } + + /** + * Gets a value that will need to be added to a DYLD file offset into the __LINKEDIT segment + * to account for our new __LINKEDIT segment being packed * - * @param fileOffset The DYLD file offset to fix-up - * @param adjustment An value to add to the bytes at the given DYLD file offset prior to - * looking them up in the packed DYLIB - * @param size The number of bytes to fix-up (must be 4 or 8) - * @param segment The segment that the value at the file offset is associated with + * @param cmd The target __LINKEDIT {@link LoadCommand} + * @return The adjustment value + */ + private long getLinkEditAdjustment(LoadCommand cmd) { + return packedLinkEditDataStarts.getOrDefault(cmd, 0) - + (cmd.getLinkerDataOffset() - linkEditSegment.getFileOffset()); + } + + /** + * Sets the bytes at the given DYLD file offset to the given value. The provided file + * offset is assumed to map to a field in a load command. + * + * @param fileOffset The DYLD file offset to set + * @param value The new value + * @param size The number of bytes to set (must be 4 or 8) * @throws IOException If there was an IO-related error * @throws IllegalArgumentException if size is an unsupported value */ - public void fixup(long fileOffset, long adjustment, int size, SegmentCommand segment) + private void set(long fileOffset, long value, int size) throws IOException, IllegalArgumentException { if (size != 4 && size != 8) { throw new IllegalArgumentException("Size must be 4 or 8 (got " + size + ")"); } - long value = reader.readUnsignedValue(fileOffset, size); - value += adjustment; try { - byte[] newBytes = toBytes(getPackedOffset(value, segment), size); - SegmentCommand textSegment = header.getSegment(SegmentNames.SEG_TEXT); + byte[] newBytes = toBytes(value, size); System.arraycopy(newBytes, 0, packed, (int) getPackedOffset(fileOffset, textSegment), newBytes.length); } @@ -190,18 +475,37 @@ public class DyldCacheDylibExtractor { /** * Fixes up the bytes at the given DYLD file offset to map to the correct offset in the - * packed DYLIB. The provided file offset is assumed to map to a field in in a load - * command. + * packed DYLIB. The provided file offset is assumed to map to a field in a load command. * * @param fileOffset The DYLD file offset to fix-up + * @param adjustment A value to add to the bytes at the given DYLD file offset prior to + * looking them up in the packed DYLIB * @param size The number of bytes to fix-up (must be 4 or 8) - * @param segment The segment that the file offset is associated with + * @param segment The segment that the value at the file offset is associated with + * @return The newly fixed up value (or the original value if there was a graceful failure) * @throws IOException If there was an IO-related error * @throws IllegalArgumentException if size is an unsupported value */ - public void fixup(long fileOffset, int size, SegmentCommand segment) + private long fixup(long fileOffset, long adjustment, int size, SegmentCommand segment) throws IOException, IllegalArgumentException { - fixup(fileOffset, 0, size, segment); + if (size != 4 && size != 8) { + throw new IllegalArgumentException("Size must be 4 or 8 (got " + size + ")"); + } + long value = reader.readUnsignedValue(fileOffset, size); + long ret = value; + value += adjustment; + + try { + ret = getPackedOffset(value, segment); + byte[] newBytes = toBytes(ret, size); + System.arraycopy(newBytes, 0, packed, + (int) getPackedOffset(fileOffset, textSegment), newBytes.length); + } + catch (NotFoundException e) { + Msg.warn(this, e.getMessage()); + } + + return ret; } /** @@ -260,174 +564,5 @@ public class DyldCacheDylibExtractor { DataConverter converter = LittleEndianDataConverter.INSTANCE; return size == 8 ? converter.getBytes(value) : converter.getBytes((int) value); } - - /** - * Fixes-up the {@link MachHeader} in the newly packed DYLIB - */ - private void fixupMachHeader() { - // Indicate that the new packed DYLIB is no longer in the cache - set(0x18, header.getFlags() & ~MachHeaderFlags.MH_DYLIB_IN_CACHE, 4); - } - - /** - * Fixes-up various fields in the new packed DYLIB's load commands - * - * @throws IOException If there was an IO-related issue performing the fix-up - */ - private void fixupLoadCommands() throws IOException { - // Fixup indices, offsets, etc in the packed DYLIB's load commands - for (LoadCommand cmd : header.getLoadCommands()) { - if (monitor.isCancelled()) { - break; - } - switch (cmd.getCommandType()) { - case LoadCommandTypes.LC_SEGMENT: - fixupSegment((SegmentCommand) cmd, false); - break; - case LoadCommandTypes.LC_SEGMENT_64: - fixupSegment((SegmentCommand) cmd, true); - break; - case LoadCommandTypes.LC_SYMTAB: - fixupSymbolTable((SymbolTableCommand) cmd); - break; - case LoadCommandTypes.LC_DYSYMTAB: - fixupDynamicSymbolTable((DynamicSymbolTableCommand) cmd); - break; - case LoadCommandTypes.LC_DYLD_INFO: - case LoadCommandTypes.LC_DYLD_INFO_ONLY: - fixupDyldInfo((DyldInfoCommand) cmd); - break; - case LoadCommandTypes.LC_CODE_SIGNATURE: - case LoadCommandTypes.LC_SEGMENT_SPLIT_INFO: - case LoadCommandTypes.LC_FUNCTION_STARTS: - case LoadCommandTypes.LC_DATA_IN_CODE: - case LoadCommandTypes.LC_DYLIB_CODE_SIGN_DRS: - case LoadCommandTypes.LC_OPTIMIZATION_HINT: - case LoadCommandTypes.LC_DYLD_EXPORTS_TRIE: - case LoadCommandTypes.LC_DYLD_CHAINED_FIXUPS: - fixupLinkEditData((LinkEditDataCommand) cmd); - break; - } - } - } - - /** - * Fixes-up the old DYLD file offsets in the given segment so they are correct for the newly - * packed DYLIB - * - * @param segment The segment to fix-up - * @param is64bit True if the segment is 64-bit; false if 32-bit - * @throws IOException If there was an IO-related issue performing the fix-up - */ - 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); - } - long sectionStartIndex = segment.getStartIndex() + (is64bit ? 0x48 : 0x38); - for (Section section : segment.getSections()) { - if (monitor.isCancelled()) { - break; - } - - // For some reason the section file offsets in the iOS 10 DYLD cache do not want - // the adjustment despite the segment needed it. We can expect to see warnings - // in that particular version. - if (section.getOffset() > 0 && section.getSize() > 0) { - fixup(sectionStartIndex + (is64bit ? 0x30 : 0x28), adjustment, 4, segment); - } - if (section.getRelocationOffset() > 0) { - fixup(sectionStartIndex + (is64bit ? 0x38 : 0x30), adjustment, 4, segment); - } - sectionStartIndex += is64bit ? 0x50 : 0x44; - } - } - - /** - * Fixes-up the old DYLD file offsets in the given symbol table so they are correct for the - * newly packed DYLIB - * - * @param cmd The symbol table to fix-up - * @throws IOException If there was an IO-related issue performing the fix-up - */ - private void fixupSymbolTable(SymbolTableCommand cmd) throws IOException { - SegmentCommand segment = header.getSegment(SegmentNames.SEG_LINKEDIT); - if (cmd.getSymbolOffset() > 0) { - fixup(cmd.getStartIndex() + 0x8, 4, segment); - } - if (cmd.getStringTableOffset() > 0) { - fixup(cmd.getStartIndex() + 0x10, 4, segment); - } - } - - /** - * Fixes-up the old DYLD file offsets in the given dynamic symbol table so they are correct for - * the newly packed DYLIB - * - * @param cmd The dynamic symbol table to fix-up - * @throws IOException If there was an IO-related issue performing the fix-up - */ - private void fixupDynamicSymbolTable(DynamicSymbolTableCommand cmd) throws IOException { - SegmentCommand segment = header.getSegment(SegmentNames.SEG_LINKEDIT); - if (cmd.getTableOfContentsOffset() > 0) { - fixup(cmd.getStartIndex() + 0x20, 4, segment); - } - if (cmd.getModuleTableOffset() > 0) { - fixup(cmd.getStartIndex() + 0x28, 4, segment); - } - if (cmd.getReferencedSymbolTableOffset() > 0) { - fixup(cmd.getStartIndex() + 0x30, 4, segment); - } - if (cmd.getIndirectSymbolTableOffset() > 0) { - fixup(cmd.getStartIndex() + 0x38, 4, segment); - } - if (cmd.getExternalRelocationOffset() > 0) { - fixup(cmd.getStartIndex() + 0x40, 4, segment); - } - if (cmd.getLocalRelocationOffset() > 0) { - fixup(cmd.getStartIndex() + 0x48, 4, segment); - } - } - - /** - * Fixes-up the old DYLD file offsets in the given DYLD Info command so they are correct for the - * newly packed DYLIB - * - * @param cmd The DYLD Info command to fix-up - * @throws IOException If there was an IO-related issue performing the fix-up - */ - private void fixupDyldInfo(DyldInfoCommand cmd) throws IOException { - SegmentCommand segment = header.getSegment(SegmentNames.SEG_LINKEDIT); - if (cmd.getRebaseOffset() > 0) { - fixup(cmd.getStartIndex() + 0x8, 4, segment); - } - if (cmd.getBindOffset() > 0) { - fixup(cmd.getStartIndex() + 0x10, 4, segment); - } - if (cmd.getWeakBindOffset() > 0) { - fixup(cmd.getStartIndex() + 0x18, 4, segment); - } - if (cmd.getLazyBindOffset() > 0) { - fixup(cmd.getStartIndex() + 0x20, 4, segment); - } - if (cmd.getExportOffset() > 0) { - fixup(cmd.getStartIndex() + 0x28, 4, segment); - } - } - - /** - * Fixes-up the old DYLD file offsets in the given link edit data command so they are correct - * for the newly packed DYLIB - * - * @param cmd The link edit data command to fix-up - * @throws IOException If there was an IO-related issue performing the fix-up - */ - private void fixupLinkEditData(LinkEditDataCommand cmd) throws IOException { - SegmentCommand segment = header.getSegment(SegmentNames.SEG_LINKEDIT); - if (cmd.getDataOffset() > 0) { - fixup(cmd.getStartIndex() + 0x8, 4, segment); - } - } } }