From 519dbd1ac1ab00f4b9d1ef90f34649c2603cfc81 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Wed, 12 May 2021 13:22:15 -0400 Subject: [PATCH 1/2] GP-719: Improving support of extracting Mach-O DYLIBs from DYLDs --- .../dyldcache/DyldCacheDylibExtractor.java | 296 ++++++++++++++++++ .../ios/dyldcache/DyldCacheFileSystem.java | 15 +- .../dyldcache/FixupMacho32bitArmOffsets.java | 177 ----------- 3 files changed, 298 insertions(+), 190 deletions(-) create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheDylibExtractor.java delete mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/FixupMacho32bitArmOffsets.java 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 new file mode 100644 index 0000000000..21f539dff7 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheDylibExtractor.java @@ -0,0 +1,296 @@ +/* ### + * 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.file.formats.ios.dyldcache; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +import generic.continues.RethrowContinuesFactory; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.format.macho.*; +import ghidra.app.util.bin.format.macho.commands.*; +import ghidra.formats.gfilesystem.GFile; +import ghidra.util.*; +import ghidra.util.exception.NotFoundException; +import ghidra.util.task.TaskMonitor; + +/** + * A class for extracting DYLIB files from a {@link DyldCacheFileSystem} + */ +public class DyldCacheDylibExtractor { + + /** + * Gets an {@link InputStream} that reads a DYLIB from a {@link DyldCacheFileSystem}. The + * DYLIB's header will be altered to account for its segment bytes being packed down. + * + * @param file The {@link GFile DYLIB} + * @param dylibOffset The offset of the DYLIB in the given provider + * @param provider The DYLD + * @param monitor A cancellable {@link TaskMonitor} + * @return An {@link InputStream} that reads the specified DYLIB from the given DYLD + * {@link ByteProvider} + * @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 InputStream extractDylib(GFile file, long dylibOffset, ByteProvider provider, + TaskMonitor monitor) throws IOException, MachException { + + // Make sure Mach-O header is valid + MachHeader header = MachHeader.createMachHeader(RethrowContinuesFactory.INSTANCE, provider, + dylibOffset, false); + header.parse(); + + // Pack the DYLIB + PackedDylib packedDylib = new PackedDylib(header, provider); + + // Fixup indices, offsets, etc in the packed DYLIB's header + for (LoadCommand cmd : header.getLoadCommands()) { + if (monitor.isCancelled()) { + break; + } + switch (cmd.getCommandType()) { + case LoadCommandTypes.LC_SEGMENT: + fixupSegment((SegmentCommand) cmd, packedDylib, false, monitor); + break; + case LoadCommandTypes.LC_SEGMENT_64: + fixupSegment((SegmentCommand) cmd, packedDylib, true, monitor); + break; + case LoadCommandTypes.LC_SYMTAB: + fixupSymbolTable((SymbolTableCommand) cmd, packedDylib); + break; + case LoadCommandTypes.LC_DYSYMTAB: + fixupDynamicSymbolTable((DynamicSymbolTableCommand) cmd, packedDylib); + break; + case LoadCommandTypes.LC_DYLD_INFO: + case LoadCommandTypes.LC_DYLD_INFO_ONLY: + fixupDyldInfo((DyldInfoCommand) cmd, packedDylib); + break; + } + } + + return packedDylib.getInputStream(); + } + + /** + * Fixes-up the old DYLD file offsets in the given segment so they are correct for the newly + * packed DYLIB + * + * @param cmd The segment to fix-up + * @param packedDylib The packed DYLIB + * @param is64bit True if the segment is 64-bit; false if 32-bit + * @param monitor A cancellable {@link TaskMonitor} + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private static void fixupSegment(SegmentCommand cmd, PackedDylib packedDylib, boolean is64bit, + TaskMonitor monitor) throws IOException { + if (cmd.getFileOffset() > 0 && cmd.getFileSize() > 0) { + packedDylib.fixup(cmd.getStartIndex() + (is64bit ? 0x28 : 0x20), is64bit ? 8 : 4); + } + long sectionStartIndex = cmd.getStartIndex() + (is64bit ? 0x48 : 0x38); + for (Section section : cmd.getSections()) { + if (monitor.isCancelled()) { + break; + } + if (section.getOffset() > 0 && section.getSize() > 0) { + packedDylib.fixup(sectionStartIndex + (is64bit ? 0x30 : 0x28), 4); + } + if (section.getRelocationOffset() > 0) { + packedDylib.fixup(sectionStartIndex + (is64bit ? 0x38 : 0x30), 4); + } + 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 + * @param packedDylib The packed DYLIB + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private static void fixupSymbolTable(SymbolTableCommand cmd, PackedDylib packedDylib) + throws IOException { + if (cmd.getSymbolOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x8, 4); + } + if (cmd.getStringTableOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x10, 4); + } + } + + /** + * 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 + * @param packedDylib The packed DYLIB + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private static void fixupDynamicSymbolTable(DynamicSymbolTableCommand cmd, + PackedDylib packedDylib) throws IOException { + if (cmd.getTableOfContentsOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x20, 4); + } + if (cmd.getModuleTableOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x28, 4); + } + if (cmd.getReferencedSymbolTableOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x30, 4); + } + if (cmd.getIndirectSymbolTableOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x38, 4); + } + if (cmd.getExternalRelocationOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x40, 4); + } + if (cmd.getLocalRelocationOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x48, 4); + } + } + + /** + * 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 + * @param packedDylib The packed DYLIB + * @throws IOException If there was an IO-related issue performing the fix-up + */ + private static void fixupDyldInfo(DyldInfoCommand cmd, PackedDylib packedDylib) + throws IOException { + if (cmd.getRebaseOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x8, 4); + } + if (cmd.getBindOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x10, 4); + } + if (cmd.getWeakBindOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x18, 4); + } + if (cmd.getLazyBindOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x20, 4); + } + if (cmd.getExportOffset() > 0) { + packedDylib.fixup(cmd.getStartIndex() + 0x28, 4); + } + } + + /** + * A packed DYLIB that was once living inside of a DYLD. The DYLIB is said to be packed + * because its segment file bytes, which were not adjacent in its containing DYLD, are now + * adjacent in its new array. + */ + private static class PackedDylib { + + private BinaryReader reader; + private Map packedStarts; + private byte[] packed; + + /** + * Creates a new {@link PackedDylib} object + * + * @param header The DYLD's DYLIB's Mach-O header + * @param provider The DYLD's bytes + * @throws IOException If there was an IO-related error + */ + public PackedDylib(MachHeader header, ByteProvider provider) throws IOException { + reader = new BinaryReader(provider, true); + packedStarts = new HashMap<>(); + int size = 0; + for (SegmentCommand segment : header.getAllSegments()) { + packedStarts.put(segment, size); + size += segment.getFileSize(); + } + packed = new byte[size]; + for (SegmentCommand segment : header.getAllSegments()) { + byte[] bytes = provider.readBytes(segment.getFileOffset(), segment.getFileSize()); + System.arraycopy(bytes, 0, packed, packedStarts.get(segment), bytes.length); + } + } + + /** + * Gets an {@link InputStream} that reads the packed DYLIB + * + * @return An {@link InputStream} that reads the packed DYLIB + */ + public InputStream getInputStream() { + return new ByteArrayInputStream(packed); + } + + /** + * Fixes up the bytes at the given DYLD file offset to map to the correct offset in the + * packed DYLIB + * + * @param fileOffset The DYLD file offset to fix-up + * @param size The number of bytes to fix-up (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, int size) throws IOException { + if (size != 4 && size != 8) { + throw new IllegalArgumentException("Size must be 4 or 8 (got " + size + ")"); + } + long orig = size == 8 ? reader.readLong(fileOffset) + : Conv.intToLong(reader.readInt(fileOffset)); + try { + byte[] newBytes = toBytes(getPackedOffset(orig), size); + System.arraycopy(newBytes, 0, packed, (int) getPackedOffset(fileOffset), + newBytes.length); + } + catch (NotFoundException e) { + Msg.warn(this, e.getMessage()); + } + } + + /** + * Converts the given DYLD file offset to an offset into the packed DYLIB + * + * @param fileOffset The DYLD file offset to convert + * @return An offset into the packed DYLIB + * @throws NotFoundException If there was no corresponding DYLIB offset + */ + private long getPackedOffset(long fileOffset) throws NotFoundException { + for (SegmentCommand segment : packedStarts.keySet()) { + if (fileOffset >= segment.getFileOffset() && + fileOffset < segment.getFileOffset() + segment.getFileSize()) { + return fileOffset - segment.getFileOffset() + packedStarts.get(segment); + } + } + throw new NotFoundException( + "Failed to convert DYLD file offset to packed DYLIB offset: " + + Long.toHexString(fileOffset)); + } + + /** + * Converts the given value to a byte array + * + * @param value The value to convert to a byte array + * @param size The number of bytes to convert (must be 4 or 8) + * @return The value as a byte array of the given size + * @throws IllegalArgumentException if size is an unsupported value + */ + private byte[] toBytes(long value, int size) throws IllegalArgumentException { + if (size != 4 && size != 8) { + throw new IllegalArgumentException("Size must be 4 or 8 (got " + size + ")"); + } + DataConverter converter = LittleEndianDataConverter.INSTANCE; + return size == 8 ? converter.getBytes(value) : converter.getBytes((int) value); + } + } +} 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 5d593b7dc4..5a55099cbe 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 @@ -57,19 +57,8 @@ public class DyldCacheFileSystem extends GFileSystemBase { } long machHeaderStartIndexInProvider = data.getAddress() - header.getBaseAddress(); try { - /* - * //check to make sure mach-o header is valid MachHeader header = - * MachHeader.createMachHeader( RethrowContinuesFactory.INSTANCE, - * provider, machHeaderStartIndexInProvider, false ); - * header.parse(); - * - * return new ByteProviderInputStream( provider, - * machHeaderStartIndexInProvider, provider.length() - - * machHeaderStartIndexInProvider ); - */ - - FixupMacho32bitArmOffsets fixer = new FixupMacho32bitArmOffsets(); - return fixer.fix(file, machHeaderStartIndexInProvider, provider, monitor); + return DyldCacheDylibExtractor.extractDylib(file, machHeaderStartIndexInProvider, + provider, monitor); } catch (MachException e) { throw new IOException("Invalid Mach-O header detected at 0x" + diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/FixupMacho32bitArmOffsets.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/FixupMacho32bitArmOffsets.java deleted file mode 100644 index a1f91b1c64..0000000000 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/FixupMacho32bitArmOffsets.java +++ /dev/null @@ -1,177 +0,0 @@ -/* ### - * 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.file.formats.ios.dyldcache; - -import java.io.*; -import java.util.*; - -import generic.continues.RethrowContinuesFactory; -import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.bin.format.macho.*; -import ghidra.app.util.bin.format.macho.commands.*; -import ghidra.formats.gfilesystem.GFile; -import ghidra.util.*; -import ghidra.util.exception.DuplicateNameException; -import ghidra.util.task.TaskMonitor; - -public class FixupMacho32bitArmOffsets { - private DataConverter converter = LittleEndianDataConverter.INSTANCE; - - public InputStream fix(GFile file, long offsetAdjustment, ByteProvider provider, - TaskMonitor monitor) throws IOException, MachException { - Map changeMap = new HashMap(); - - //check to make sure mach-o header is valid - MachHeader header = MachHeader.createMachHeader( RethrowContinuesFactory.INSTANCE, provider, offsetAdjustment, false ); - header.parse(); - - //fix up index, offsets, etc in the header - List commands = header.getLoadCommands(); - for ( LoadCommand loadCommand : commands ) { - if ( monitor.isCancelled() ) { - break; - } - switch ( loadCommand.getCommandType() ) { - case LoadCommandTypes.LC_SEGMENT: { - SegmentCommand segmentCommand = (SegmentCommand) loadCommand; - if ( segmentCommand.getFileOffset() > 0 ) { - long newOffset = segmentCommand.getFileOffset() - offsetAdjustment; - changeMap.put( segmentCommand.getStartIndex() + 0x20 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( segmentCommand.getNumberOfSections() > 0 ) { - long sectionStartIndex = segmentCommand.getStartIndex() + 0x38 - offsetAdjustment; - for ( Section section : segmentCommand.getSections() ) { - if ( monitor.isCancelled() ) { - break; - } - if ( section.getOffset() > 0 && section.getOffset() > offsetAdjustment ) { - long newOffset = Conv.intToLong( section.getOffset() ) - offsetAdjustment; - changeMap.put( sectionStartIndex + 0x28, converter.getBytes( (int)newOffset ) ); - } - if ( section.getRelocationOffset() > 0 && section.getRelocationOffset() > offsetAdjustment ) { - long newOffset = Conv.intToLong( section.getRelocationOffset() ) - offsetAdjustment; - changeMap.put( sectionStartIndex + 0x30, converter.getBytes( (int)newOffset ) ); - } - try { - sectionStartIndex += section.toDataType().getLength(); - } - catch ( DuplicateNameException e ) { - throw new IOException( e ); - } - } - } - break; - } - case LoadCommandTypes.LC_SYMTAB: { - SymbolTableCommand symbolTableCommand = (SymbolTableCommand) loadCommand; - if ( symbolTableCommand.getSymbolOffset() > 0 ) { - long newOffset = Conv.intToLong( symbolTableCommand.getSymbolOffset() ) - offsetAdjustment; - changeMap.put( symbolTableCommand.getStartIndex() + 0x8 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( symbolTableCommand.getStringTableOffset() > 0 ) { - long newOffset = Conv.intToLong( symbolTableCommand.getStringTableOffset() ) - offsetAdjustment; - changeMap.put( symbolTableCommand.getStartIndex() + 0x10 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - break; - } - case LoadCommandTypes.LC_DYSYMTAB: { - DynamicSymbolTableCommand dynamicSymbolTableCommand = (DynamicSymbolTableCommand) loadCommand; - if ( dynamicSymbolTableCommand.getTableOfContentsOffset() > 0 ) { - long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getTableOfContentsOffset() ) - offsetAdjustment; - changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x20 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dynamicSymbolTableCommand.getModuleTableOffset() > 0 ) { - long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getModuleTableOffset() ) - offsetAdjustment; - changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x28 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dynamicSymbolTableCommand.getReferencedSymbolTableOffset() > 0 ) { - long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getReferencedSymbolTableOffset() ) - offsetAdjustment; - changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x30 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dynamicSymbolTableCommand.getIndirectSymbolTableOffset() > 0 ) { - long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getIndirectSymbolTableOffset() ) - offsetAdjustment; - changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x38 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dynamicSymbolTableCommand.getExternalRelocationOffset() > 0 ) { - long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getExternalRelocationOffset() ) - offsetAdjustment; - changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x40 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dynamicSymbolTableCommand.getLocalRelocationOffset() > 0 ) { - long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getLocalRelocationOffset() ) - offsetAdjustment; - changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x48 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - break; - } - case LoadCommandTypes.LC_DYLD_INFO: - case LoadCommandTypes.LC_DYLD_INFO_ONLY: { - DyldInfoCommand dyldInfoCommand = (DyldInfoCommand) loadCommand; - if ( dyldInfoCommand.getRebaseOffset() > 0 ) { - long newOffset = Conv.intToLong( dyldInfoCommand.getRebaseOffset() ) - offsetAdjustment; - changeMap.put( dyldInfoCommand.getStartIndex() + 0x8 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dyldInfoCommand.getBindOffset() > 0 ) { - long newOffset = Conv.intToLong( dyldInfoCommand.getBindOffset() ) - offsetAdjustment; - changeMap.put( dyldInfoCommand.getStartIndex() + 0x10 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dyldInfoCommand.getWeakBindOffset() > 0 ) { - long newOffset = Conv.intToLong( dyldInfoCommand.getWeakBindOffset() ) - offsetAdjustment; - changeMap.put( dyldInfoCommand.getStartIndex() + 0x18 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dyldInfoCommand.getLazyBindOffset() > 0 ) { - long newOffset = Conv.intToLong( dyldInfoCommand.getLazyBindOffset() ) - offsetAdjustment; - changeMap.put( dyldInfoCommand.getStartIndex() + 0x20 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - if ( dyldInfoCommand.getExportOffset() > 0 ) { - long newOffset = Conv.intToLong(dyldInfoCommand.getExportOffset() ) - offsetAdjustment; - changeMap.put( dyldInfoCommand.getStartIndex() + 0x28 - offsetAdjustment, converter.getBytes( (int)newOffset ) ); - } - break; - } - } - } - - List indexList = new ArrayList( changeMap.keySet() ); - Collections.sort( indexList ); - - ByteArrayOutputStream tempOut = new ByteArrayOutputStream(); - try { - long tempIndex = offsetAdjustment; - while ( !monitor.isCancelled() ) { - final int length = 0x10000; - byte [] buffer = provider.readBytes( tempIndex, length ); - - for ( Long index : indexList ) { - if ( index + offsetAdjustment >= tempIndex && index + offsetAdjustment < tempIndex + length ) { - byte [] changedBytes = changeMap.get( index ); - System.arraycopy( changedBytes, 0, buffer, index.intValue(), changedBytes.length ); - } - } - - tempOut.write( buffer ); - tempIndex += buffer.length; - monitor.setMessage( "0x" + Long.toHexString( tempIndex ) ); - if ( tempIndex > provider.length() ) { - break; - } - } - } - finally { - tempOut.close(); - } - - return new ByteArrayInputStream(tempOut.toByteArray()); - } -} From 6737d991e211ebd396adadcb08ea509e1bd6594a Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Mon, 17 May 2021 11:03:57 -0400 Subject: [PATCH 2/2] GP-719: Improvements to the DYLD DYLIB extractor --- .../format/macho/commands/SegmentCommand.java | 4 +++ .../dyldcache/DyldCacheDylibExtractor.java | 27 +++++++++++++------ .../ios/dyldcache/DyldCacheFileSystem.java | 9 +++---- 3 files changed, 26 insertions(+), 14 deletions(-) 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 a8d41a0317..4939373bc6 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 @@ -132,6 +132,10 @@ public class SegmentCommand extends LoadCommand { public long getFileOffset() { return fileoff; } + + public void setFileOffset(long fileOffset) { + fileoff = fileOffset; + } public long getFileSize() { return filesize; 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 21f539dff7..d2c73caf71 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 @@ -24,7 +24,6 @@ import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.format.macho.*; import ghidra.app.util.bin.format.macho.commands.*; -import ghidra.formats.gfilesystem.GFile; import ghidra.util.*; import ghidra.util.exception.NotFoundException; import ghidra.util.task.TaskMonitor; @@ -38,7 +37,6 @@ public class DyldCacheDylibExtractor { * Gets an {@link InputStream} that reads a DYLIB from a {@link DyldCacheFileSystem}. The * DYLIB's header will be altered to account for its segment bytes being packed down. * - * @param file The {@link GFile DYLIB} * @param dylibOffset The offset of the DYLIB in the given provider * @param provider The DYLD * @param monitor A cancellable {@link TaskMonitor} @@ -47,7 +45,7 @@ public class DyldCacheDylibExtractor { * @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 InputStream extractDylib(GFile file, long dylibOffset, ByteProvider provider, + public static InputStream extractDylib(long dylibOffset, ByteProvider provider, TaskMonitor monitor) throws IOException, MachException { // Make sure Mach-O header is valid @@ -56,7 +54,7 @@ public class DyldCacheDylibExtractor { header.parse(); // Pack the DYLIB - PackedDylib packedDylib = new PackedDylib(header, provider); + PackedDylib packedDylib = new PackedDylib(header, dylibOffset, provider); // Fixup indices, offsets, etc in the packed DYLIB's header for (LoadCommand cmd : header.getLoadCommands()) { @@ -206,20 +204,34 @@ public class DyldCacheDylibExtractor { * Creates a new {@link PackedDylib} object * * @param header 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 * @throws IOException If there was an IO-related error */ - public PackedDylib(MachHeader header, ByteProvider provider) throws IOException { + public PackedDylib(MachHeader header, long dylibOffset, ByteProvider provider) + throws IOException { reader = new BinaryReader(provider, true); packedStarts = new HashMap<>(); int size = 0; for (SegmentCommand segment : header.getAllSegments()) { packedStarts.put(segment, size); size += segment.getFileSize(); + + // Some older DYLDs use relative file offsets for only their __TEXT segment. + // Adjust these segments to be consistent with all the other segments. + if (segment.getFileOffset() == 0) { + segment.setFileOffset(dylibOffset); + } } packed = new byte[size]; for (SegmentCommand segment : header.getAllSegments()) { - byte[] bytes = provider.readBytes(segment.getFileOffset(), segment.getFileSize()); + long segmentSize = segment.getFileSize(); + if (segment.getFileOffset() + segmentSize > provider.length()) { + segmentSize = provider.length() - segment.getFileOffset(); + Msg.warn(this, segment.getSegmentName() + + " segment extends beyond end of file. Truncating..."); + } + byte[] bytes = provider.readBytes(segment.getFileOffset(), segmentSize); System.arraycopy(bytes, 0, packed, packedStarts.get(segment), bytes.length); } } @@ -246,8 +258,7 @@ public class DyldCacheDylibExtractor { if (size != 4 && size != 8) { throw new IllegalArgumentException("Size must be 4 or 8 (got " + size + ")"); } - long orig = size == 8 ? reader.readLong(fileOffset) - : Conv.intToLong(reader.readInt(fileOffset)); + long orig = reader.readUnsignedValue(fileOffset, size); try { byte[] newBytes = toBytes(getPackedOffset(orig), size); System.arraycopy(newBytes, 0, packed, (int) getPackedOffset(fileOffset), 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 5a55099cbe..d67eb9ae02 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 @@ -57,8 +57,8 @@ public class DyldCacheFileSystem extends GFileSystemBase { } long machHeaderStartIndexInProvider = data.getAddress() - header.getBaseAddress(); try { - return DyldCacheDylibExtractor.extractDylib(file, machHeaderStartIndexInProvider, - provider, monitor); + return DyldCacheDylibExtractor.extractDylib(machHeaderStartIndexInProvider, provider, + monitor); } catch (MachException e) { throw new IOException("Invalid Mach-O header detected at 0x" + @@ -153,11 +153,8 @@ public class DyldCacheFileSystem extends GFileSystemBase { monitor.incrementProgress(1); - GFileImpl file = GFileImpl.fromPathString(this, root, data.getPath(), null, false, - 0/*TODO compute length?*/ ); + GFileImpl file = GFileImpl.fromPathString(this, root, data.getPath(), null, false, -1); storeFile(file, data); - - file.setLength(provider.length() - (data.getAddress() - header.getBaseAddress())); } }