GP-3770: Adding files __BRANCH_STUBS and __BRANCH_GOTS to

MachoFileSetFileSystem
This commit is contained in:
Ryan Kurtz 2024-03-14 10:48:59 -04:00
parent 43c6f6897a
commit 1d52fdbdc6
5 changed files with 124 additions and 24 deletions

View file

@ -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";
}

View file

@ -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) {}

View file

@ -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);
}
}

View file

@ -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<FileSetEntryCommand> {
public class MachoFileSetFileSystem extends AbstractFileSystem<MachoFileSetEntry> {
public static final String MACHO_FILESET_FSTYPE = "machofileset";
private ByteProvider provider;
private ByteProvider fixedUpProvider;
private Map<FileSetEntryCommand, List<SegmentCommand>> entrySegmentMap;
private MachHeader header;
private Map<MachoFileSetEntry, List<SegmentCommand>> entrySegmentMap;
/**
* Creates a new {@link MachoFileSetFileSystem}
@ -68,16 +69,35 @@ public class MachoFileSetFileSystem extends AbstractFileSystem<FileSetEntryComma
MessageLog log = new MessageLog();
try {
monitor.setMessage("Opening Mach-O file set...");
MachHeader header = new MachHeader(provider).parse();
header = new MachHeader(provider).parse();
SegmentCommand textSegment = header.getSegment(SegmentNames.SEG_TEXT);
if (textSegment == null) {
throw new MachException(SegmentNames.SEG_TEXT + " not found!");
}
// File set entries
for (FileSetEntryCommand cmd : header.getLoadCommands(FileSetEntryCommand.class)) {
fsIndex.storeFile(cmd.getFileSetEntryId().getString(), fsIndex.getFileCount(),
false, -1, cmd);
MachHeader entryHeader = new MachHeader(provider, cmd.getFileOffset());
entrySegmentMap.put(cmd, entryHeader.parseSegments());
MachoFileSetEntry entry = new MachoFileSetEntry(cmd.getFileSetEntryId().getString(),
cmd.getFileOffset(), false);
fsIndex.storeFile(entry.id(), fsIndex.getFileCount(), false, -1, entry);
entrySegmentMap.put(entry,
new MachHeader(provider, entry.offset()).parseSegments());
}
// BRANCH segments, if present
SegmentCommand branchStubs = header.getSegment(SegmentNames.SEG_BRANCH_STUBS);
if (branchStubs != null) {
MachoFileSetEntry entry =
new MachoFileSetEntry(SegmentNames.SEG_BRANCH_STUBS.substring(2), 0, true);
fsIndex.storeFile(entry.id(), fsIndex.getFileCount(), false, -1, entry);
entrySegmentMap.put(entry, List.of(branchStubs));
}
SegmentCommand branchGots = header.getSegment(SegmentNames.SEG_BRANCH_GOTS);
if (branchGots != null) {
MachoFileSetEntry entry =
new MachoFileSetEntry(SegmentNames.SEG_BRANCH_GOTS.substring(2), 0, true);
fsIndex.storeFile(entry.id(), fsIndex.getFileCount(), false, -1, entry);
entrySegmentMap.put(entry, List.of(branchGots));
}
monitor.setMessage("Getting chained pointers...");
@ -106,27 +126,31 @@ public class MachoFileSetFileSystem extends AbstractFileSystem<FileSetEntryComma
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
FileSetEntryCommand cmd = fsIndex.getMetadata(file);
if (cmd == null) {
MachoFileSetEntry entry = fsIndex.getMetadata(file);
if (entry == null) {
return null;
}
try {
return MachoFileSetExtractor.extractFileSetEntry(fixedUpProvider, cmd.getFileOffset(),
if (entry.isBranchSegment()) {
return MachoFileSetExtractor.extractSegment(fixedUpProvider,
header.getSegment("__" + entry.id()), file.getFSRL(), monitor);
}
return MachoFileSetExtractor.extractFileSetEntry(fixedUpProvider, entry.offset(),
file.getFSRL(), monitor);
}
catch (MachException e) {
throw new IOException(
"Invalid Mach-O header detected at 0x%x".formatted(cmd.getFileOffset()));
"Invalid Mach-O header detected at 0x%x".formatted(entry.offset()));
}
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
FileAttributes result = new FileAttributes();
FileSetEntryCommand cmd = fsIndex.getMetadata(file);
if (cmd != null) {
result.add(NAME_ATTR, cmd.getFileSetEntryId().getString());
result.add(PATH_ATTR, cmd.getFileSetEntryId().getString());
MachoFileSetEntry entry = fsIndex.getMetadata(file);
if (entry != null) {
result.add(NAME_ATTR, entry.id());
result.add(PATH_ATTR, entry.id());
}
return result;
}
@ -144,7 +168,7 @@ public class MachoFileSetFileSystem extends AbstractFileSystem<FileSetEntryComma
/**
* {@return the map of file set entry segments}
*/
public Map<FileSetEntryCommand, List<SegmentCommand>> getEntrySegmentMap() {
public Map<MachoFileSetEntry, List<SegmentCommand>> getEntrySegmentMap() {
return entrySegmentMap;
}
@ -164,6 +188,9 @@ public class MachoFileSetFileSystem extends AbstractFileSystem<FileSetEntryComma
fixedUpProvider.close();
fixedUpProvider = null;
}
if (header != null) {
header = null;
}
fsIndex.clear();
entrySegmentMap.clear();
}

View file

@ -23,9 +23,9 @@ import docking.action.builder.ActionBuilder;
import ghidra.app.CorePluginPackage;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.util.bin.format.macho.commands.FileSetEntryCommand;
import ghidra.app.util.bin.format.macho.commands.SegmentCommand;
import ghidra.app.util.opinion.MachoFileSetExtractLoader;
import ghidra.file.formats.ios.fileset.MachoFileSetEntry;
import ghidra.file.formats.ios.fileset.MachoFileSetFileSystem;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.plugintool.*;
@ -106,13 +106,12 @@ public class MachoFileSetBuilderPlugin extends Plugin {
long refAddr = refAddress.getOffset();
try (FileSystemRef fsRef = openMachoFileSet(program, monitor)) {
MachoFileSetFileSystem fs = (MachoFileSetFileSystem) fsRef.getFilesystem();
Map<FileSetEntryCommand, List<SegmentCommand>> entrySegmentMap =
fs.getEntrySegmentMap();
Map<MachoFileSetEntry, List<SegmentCommand>> 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;
}
}