diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SegmentNames.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SegmentNames.java index 7a0287d26b..1656d0cdd1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SegmentNames.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/SegmentNames.java @@ -54,4 +54,6 @@ public final class SegmentNames { public final static String SEG_TEXT_EXEC = "__TEXT_EXEC"; public final static String SEG_PRELINK_TEXT = "__PRELINK_TEXT"; + public final static String SEG_BRANCH_STUBS = "__BRANCH_STUBS"; + public final static String SEG_BRANCH_GOTS = "__BRANCH_GOTS"; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetEntry.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetEntry.java new file mode 100644 index 0000000000..d6573b0349 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetEntry.java @@ -0,0 +1,26 @@ +/* ### + * 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.fileset; + +/** + * An entry in the {@link MachoFileSetFileSystem} + * + * @param id The id of the entry + * @param offset The offset of the entry in the provider + * @param isBranchSegment True if this entry represents a branch segment; false if it represents + * an LC_FILESET_ENTRY Mach-O + */ +public record MachoFileSetEntry(String id, long offset, boolean isBranchSegment) {} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetExtractor.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetExtractor.java index fdd115f86d..9cf69fa7ef 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetExtractor.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetExtractor.java @@ -18,9 +18,10 @@ package ghidra.file.formats.ios.fileset; import java.io.IOException; import java.nio.charset.StandardCharsets; +import ghidra.app.util.bin.ByteArrayProvider; import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.bin.format.macho.MachException; -import ghidra.app.util.bin.format.macho.MachHeader; +import ghidra.app.util.bin.format.macho.*; +import ghidra.app.util.bin.format.macho.commands.SegmentCommand; import ghidra.file.formats.ios.ExtractedMacho; import ghidra.formats.gfilesystem.FSRL; import ghidra.util.exception.CancelledException; @@ -60,4 +61,49 @@ public class MachoFileSetExtractor { extractedMacho.pack(); return extractedMacho.getByteProvider(fsrl); } + + /** + * Gets a {@link ByteProvider} that contains a single segment from a Mach-O file set + * + * @param provider The Mach-O file set {@link ByteProvider} + * @param segment The segment to extract + * @param fsrl {@link FSRL} to assign to the resulting {@link ByteProvider} + * @param monitor {@link TaskMonitor} + * @return {@link ByteProvider} containing the bytes of the single-segment Mach-O + * @throws MachException If there was an error creating Mach-O headers + * @throws IOException If there was an IO-related issue with extracting the segment + * @throws CancelledException If the user cancelled the operation + */ + public static ByteProvider extractSegment(ByteProvider provider, SegmentCommand segment, FSRL fsrl, TaskMonitor monitor) throws IOException, MachException, CancelledException { + + int magic = MachConstants.MH_MAGIC_64; + int allSegmentsSize = SegmentCommand.size(magic); + + // Mach-O Header + byte[] header = + MachHeader.create(magic, 0x100000c, 0x80000002, 6, 1, allSegmentsSize, 0x42100085, 0); + + // Segment command + byte[] segmentCommandBytes = + SegmentCommand.create(magic, segment.getSegmentName(), segment.getVMaddress(), + segment.getVMsize(), header.length + allSegmentsSize, segment.getFileSize(), + segment.getMaxProtection(), segment.getInitProtection(), 0, segment.getFlags()); + + // Segment data + byte[] segmentDataBytes = + provider.readBytes(segment.getFileOffset(), segment.getFileSize()); + + // Combine pieces + int totalSize = header.length + allSegmentsSize + segmentDataBytes.length; + byte[] result = new byte[totalSize + FOOTER_V1.length]; + System.arraycopy(header, 0, result, 0, header.length); + System.arraycopy(segmentCommandBytes, 0, result, header.length, segmentCommandBytes.length); + System.arraycopy(segmentDataBytes, 0, result, header.length + segmentCommandBytes.length, + segmentDataBytes.length); + + // Add footer + System.arraycopy(FOOTER_V1, 0, result, result.length - FOOTER_V1.length, FOOTER_V1.length); + + return new ByteArrayProvider(result, fsrl); + } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetFileSystem.java index 66b6f3e00a..b33ea69895 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/fileset/MachoFileSetFileSystem.java @@ -37,13 +37,14 @@ import ghidra.util.task.TaskMonitor; * A {@link GFileSystem} implementation for Mach-O file set entries */ @FileSystemInfo(type = MachoFileSetFileSystem.MACHO_FILESET_FSTYPE, description = "Mach-O file set", factory = MachoFileSetFileSystemFactory.class) -public class MachoFileSetFileSystem extends AbstractFileSystem { +public class MachoFileSetFileSystem extends AbstractFileSystem { public static final String MACHO_FILESET_FSTYPE = "machofileset"; private ByteProvider provider; private ByteProvider fixedUpProvider; - private Map> entrySegmentMap; + private MachHeader header; + private Map> entrySegmentMap; /** * Creates a new {@link MachoFileSetFileSystem} @@ -68,16 +69,35 @@ public class MachoFileSetFileSystem extends AbstractFileSystem> getEntrySegmentMap() { + public Map> getEntrySegmentMap() { return entrySegmentMap; } @@ -164,6 +188,9 @@ public class MachoFileSetFileSystem extends AbstractFileSystem> entrySegmentMap = - fs.getEntrySegmentMap(); + Map> entrySegmentMap = fs.getEntrySegmentMap(); String fsPath = null; - for (FileSetEntryCommand cmd : entrySegmentMap.keySet()) { - for (SegmentCommand segment : entrySegmentMap.get(cmd)) { + for (MachoFileSetEntry entry : entrySegmentMap.keySet()) { + for (SegmentCommand segment : entrySegmentMap.get(entry)) { if (segment.contains(refAddr)) { - fsPath = cmd.getFileSetEntryId().getString(); + fsPath = entry.id(); break; } }