Merge remote-tracking branch

'origin/GP-3770_ryanmkurtz_fileset-fs--SQUASHED' (Closes #4827)
This commit is contained in:
Ryan Kurtz 2024-03-13 07:33:17 -04:00
commit f13025d426
25 changed files with 1619 additions and 991 deletions

View file

@ -171,7 +171,7 @@ public class MachHeader implements StructConverter {
for (int i = 0; i < nCmds; ++i) {
_reader.setPointerIndex(currentIndex);
int type = _reader.readNextInt();
int size = _reader.readNextInt();
long size = _reader.readNextUnsignedInt();
if (type == LoadCommandTypes.LC_SEGMENT || type == LoadCommandTypes.LC_SEGMENT_64) {
segmentIndexes.add(currentIndex);
}
@ -198,9 +198,8 @@ public class MachHeader implements StructConverter {
*
* @return A {@List} of this {@link MachHeader}'s {@link SegmentCommand segments}
* @throws IOException If there was an IO-related error
* @throws MachException if the load command is invalid
*/
public List<SegmentCommand> parseSegments() throws IOException, MachException {
public List<SegmentCommand> parseSegments() throws IOException {
List<SegmentCommand> segments = new ArrayList<>();
_reader.setPointerIndex(_commandIndex);
for (int i = 0; i < nCmds; ++i) {
@ -210,7 +209,7 @@ public class MachHeader implements StructConverter {
}
else {
type = _reader.readNextInt();
int size = _reader.readNextInt();
long size = _reader.readNextUnsignedInt();
_reader.setPointerIndex(_reader.getPointerIndex() + size - 8);
}
}
@ -218,6 +217,30 @@ public class MachHeader implements StructConverter {
return segments;
}
/**
* Parses only this {@link MachHeader}'s {@link LoadCommand}s to check to see if one of the
* given type exists
*
* @param loadCommandType The type of {@link LoadCommand} to check for
* @return True if this {@link MachHeader} contains the given {@link LoadCommand} type
* @throws IOException If there was an IO-related error
* @see LoadCommandTypes
*/
public boolean parseAndCheck(int loadCommandType) throws IOException {
_reader.setPointerIndex(_commandIndex);
for (int i = 0; i < nCmds; ++i) {
int type = _reader.peekNextInt();
if (type == loadCommandType) {
return true;
}
type = _reader.readNextInt();
long size = _reader.readNextUnsignedInt();
_reader.setPointerIndex(_reader.getPointerIndex() + size - 8);
}
return false;
}
public int getMagic() {
return magic;
}

View file

@ -15,12 +15,16 @@
*/
package ghidra.app.util.bin.format.macho.commands;
import static ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.*;
import java.io.IOException;
import java.util.List;
import java.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.macho.MachHeader;
import ghidra.app.util.bin.format.macho.commands.chained.*;
import ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.DyldChainType;
import ghidra.app.util.bin.format.macho.dyld.DyldFixup;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.address.Address;
@ -28,9 +32,8 @@ import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataUtilities;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.ProgramModule;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
/**
@ -58,6 +61,15 @@ public class DyldChainedFixupsCommand extends LinkEditDataCommand {
chainHeader = new DyldChainedFixupHeader(dataReader);
}
/**
* Gets the {@link DyldChainedFixupHeader}
*
* @return The {@link DyldChainedFixupHeader}
*/
public DyldChainedFixupHeader getChainHeader() {
return chainHeader;
}
@Override
public String getCommandName() {
return "dyld_chained_fixups_command";
@ -97,7 +109,22 @@ public class DyldChainedFixupsCommand extends LinkEditDataCommand {
}
Address dyldChainedHeader = addrs.get(0);
markupChainedFixupHeader(header, api, dyldChainedHeader, parentModule, monitor);
DataType cHeader = chainHeader.toDataType();
api.createData(dyldChainedHeader, cHeader);
Address segsAddr = dyldChainedHeader.add(chainHeader.getStartsOffset());
DyldChainedStartsInImage chainedStartsInImage = chainHeader.getChainedStartsInImage();
int[] segInfoOffset = chainedStartsInImage.getSegInfoOffset();
List<DyldChainedStartsInSegment> chainedStarts =
chainedStartsInImage.getChainedStarts();
for (int i = 0; i < chainedStarts.size(); i++) {
DyldChainedStartsInSegment startsInSeg = chainedStarts.get(i);
DataType dataType = startsInSeg.toDataType();
api.createData(segsAddr.add(segInfoOffset[i]), dataType);
}
}
catch (Exception e) {
log.appendMsg("Unable to create " + getCommandName());
@ -105,27 +132,57 @@ public class DyldChainedFixupsCommand extends LinkEditDataCommand {
}
}
private void markupChainedFixupHeader(MachHeader header, FlatProgramAPI api,
Address baseAddress, ProgramModule parentModule, TaskMonitor monitor)
throws DuplicateNameException, IOException, CodeUnitInsertionException, Exception {
DataType cHeader = chainHeader.toDataType();
api.createData(baseAddress, cHeader);
/**
* Walks this command's chained fixup information and collects a {@link List} of
* {@link DyldFixup}s that will need to be applied to the image
*
* @param reader A {@link BinaryReader} that can read the image
* @param imagebase The image base
* @param symbolTable The {@link SymbolTable}, or null if not available
* @param log The log
* @param monitor A cancellable monitor
* @return A {@link List} of {@link DyldFixup}s
* @throws IOException If there was an IO-related issue
* @throws CancelledException If the user cancelled the operation
*/
public List<DyldFixup> getChainedFixups(BinaryReader reader, long imagebase,
SymbolTable symbolTable, MessageLog log, TaskMonitor monitor)
throws IOException, CancelledException {
List<DyldFixup> result = new ArrayList<>();
Map<DyldChainType, Integer> countMap = new HashMap<>();
for (DyldChainedStartsInSegment chainStart : chainHeader.getChainedStartsInImage()
.getChainedStarts()) {
if (chainStart == null) {
continue;
}
Address segsAddr = baseAddress.add(chainHeader.getStartsOffset());
DyldChainType ptrFormat = DyldChainType.lookupChainPtr(chainStart.getPointerFormat());
monitor.initialize(chainStart.getPageCount(),
"Getting " + ptrFormat.getName() + " chained pointer fixups...");
DyldChainedStartsInImage chainedStartsInImage = chainHeader.getChainedStartsInImage();
int[] segInfoOffset = chainedStartsInImage.getSegInfoOffset();
List<DyldChainedStartsInSegment> chainedStarts = chainedStartsInImage.getChainedStarts();
for (int i = 0; i < chainedStarts.size(); i++) {
DyldChainedStartsInSegment startsInSeg = chainedStarts.get(i);
DataType dataType = startsInSeg.toDataType();
api.createData(segsAddr.add(segInfoOffset[i]), dataType);
try {
for (int index = 0; index < chainStart.getPageCount(); index++) {
monitor.increment();
long page = chainStart.getSegmentOffset() + (chainStart.getPageSize() * index);
int pageEntry = chainStart.getPageStarts()[index] & 0xffff;
if (pageEntry == DYLD_CHAINED_PTR_START_NONE) {
continue;
}
List<DyldFixup> fixups =
DyldChainedFixups.getChainedFixups(reader, chainHeader.getChainedImports(),
ptrFormat, page, pageEntry, 0, imagebase, symbolTable, log, monitor);
result.addAll(fixups);
countMap.put(ptrFormat, countMap.getOrDefault(ptrFormat, 0) + fixups.size());
}
}
catch(IOException e) {
log.appendMsg("Failed to get segment chain fixups at 0x%x"
.formatted(chainStart.getSegmentOffset()));
}
}
}
public DyldChainedFixupHeader getChainHeader() {
return chainHeader;
countMap.forEach((type, count) -> log
.appendMsg("Discovered " + count + " " + type + " chained pointers."));
return result;
}
}

View file

@ -15,26 +15,21 @@
*/
package ghidra.app.util.bin.format.macho.commands.chained;
import static ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.macho.*;
import ghidra.app.util.bin.format.macho.commands.DyldChainedFixupsCommand;
import ghidra.app.util.bin.format.macho.commands.SegmentNames;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr;
import ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.DyldChainType;
import ghidra.app.util.bin.format.macho.dyld.DyldFixup;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Library;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.reloc.Relocation.Status;
import ghidra.program.model.reloc.RelocationResult;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
@ -42,299 +37,154 @@ import ghidra.util.task.TaskMonitor;
public class DyldChainedFixups {
private MachHeader machoHeader;
private Program program;
private List<String> libraryPaths;
private MessageLog log;
private TaskMonitor monitor;
private Memory memory;
private AddressSpace space;
/**
* Creates a new {@link DyldChainedFixups}
*
* @param program The {@link Program}
* @param header The Mach-O header
* @param libraryPaths A {@link List} of the library paths
* @param log The log
* @param monitor A cancelable task monitor.
*/
public DyldChainedFixups(Program program, MachHeader header, List<String> libraryPaths,
MessageLog log, TaskMonitor monitor) {
this.program = program;
this.machoHeader = header;
this.libraryPaths = libraryPaths;
this.log = log;
this.monitor = monitor;
this.memory = program.getMemory();
this.space = program.getAddressFactory().getDefaultAddressSpace();
}
/**
* Fixes up any chained fixups. Relies on the __thread_starts section being present.
*
* @return A list of addresses where chained fixups were performed.
* @throws Exception if there was a problem reading/writing memory.
*/
public List<Address> processChainedFixups() throws Exception {
monitor.setMessage("Fixing up chained pointers...");
List<Address> fixedAddresses = new ArrayList<>();
// First look for a DyldChainedFixupsCommand
List<DyldChainedFixupsCommand> loadCommands =
machoHeader.getLoadCommands(DyldChainedFixupsCommand.class);
for (DyldChainedFixupsCommand loadCommand : loadCommands) {
DyldChainedFixupHeader chainHeader = loadCommand.getChainHeader();
DyldChainedStartsInImage chainedStartsInImage = chainHeader.getChainedStartsInImage();
List<DyldChainedStartsInSegment> chainedStarts =
chainedStartsInImage.getChainedStarts();
for (DyldChainedStartsInSegment chainStart : chainedStarts) {
fixedAddresses.addAll(processSegmentPointerChain(chainHeader, chainStart));
}
log.appendMsg("Fixed up " + fixedAddresses.size() + " chained pointers.");
}
if (!loadCommands.isEmpty()) {
return fixedAddresses;
}
// Didn't find a DyldChainedFixupsCommand, so look for the sections with fixup info
Section chainStartsSection =
machoHeader.getSection(SegmentNames.SEG_TEXT, SectionNames.CHAIN_STARTS);
Section threadStartsSection =
machoHeader.getSection(SegmentNames.SEG_TEXT, SectionNames.THREAD_STARTS);
if (chainStartsSection != null) {
Address sectionStart = space.getAddress(chainStartsSection.getAddress());
ByteProvider provider = new MemoryByteProvider(memory, sectionStart);
BinaryReader reader = new BinaryReader(provider, machoHeader.isLittleEndian());
DyldChainedStartsOffsets chainedStartsOffsets = new DyldChainedStartsOffsets(reader);
for (int offset : chainedStartsOffsets.getChainStartOffsets()) {
processPointerChain(null, fixedAddresses, chainedStartsOffsets.getPointerFormat(),
program.getImageBase().add(offset).getOffset(), 0, 0);
}
log.appendMsg("Fixed up " + fixedAddresses.size() + " chained pointers.");
}
else if (threadStartsSection != null) {
Address threadSectionStart = space.getAddress(threadStartsSection.getAddress());
Address threadSectionEnd = threadSectionStart.add(threadStartsSection.getSize() - 1);
long nextOffSize = (memory.getInt(threadSectionStart) & 1) * 4 + 4;
Address chainHead = threadSectionStart.add(4);
while (chainHead.compareTo(threadSectionEnd) < 0 && !monitor.isCancelled()) {
int headStartOffset = memory.getInt(chainHead);
if (headStartOffset == 0xFFFFFFFF || headStartOffset == 0) {
break;
}
Address chainStart = program.getImageBase().add(headStartOffset & 0xffffffffL);
fixedAddresses.addAll(processPointerChain(chainStart, nextOffSize));
chainHead = chainHead.add(4);
}
log.appendMsg("Fixed up " + fixedAddresses.size() + " chained pointers.");
}
return fixedAddresses;
}
private List<Address> processSegmentPointerChain(DyldChainedFixupHeader chainHeader,
DyldChainedStartsInSegment chainStart)
throws MemoryAccessException, CancelledException {
List<Address> fixedAddresses = new ArrayList<Address>();
long fixedAddressCount = 0;
if (chainStart.getPointerFormat() == 0) {
return fixedAddresses;
}
long dataPageStart = chainStart.getSegmentOffset();
dataPageStart = dataPageStart + program.getImageBase().getOffset();
long pageSize = chainStart.getPageSize();
long pageStartsCount = chainStart.getPageCount();
long authValueAdd = 0;
short[] pageStarts = chainStart.getPageStarts();
short ptrFormatValue = chainStart.getPointerFormat();
DyldChainType ptrFormat = DyldChainType.lookupChainPtr(ptrFormatValue);
monitor.setMessage("Fixing " + ptrFormat.getName() + " chained pointers...");
monitor.setMaximum(pageStartsCount);
for (int index = 0; index < pageStartsCount; index++) {
monitor.checkCancelled();
long page = dataPageStart + (pageSize * index);
monitor.setProgress(index);
int pageEntry = pageStarts[index] & 0xffff;
if (pageEntry == DYLD_CHAINED_PTR_START_NONE) {
continue;
}
List<Address> unchainedLocList = new ArrayList<>(1024);
long pageOffset = pageEntry; // first entry is byte based
switch (ptrFormat) {
case DYLD_CHAINED_PTR_ARM64E:
case DYLD_CHAINED_PTR_ARM64E_KERNEL:
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
processPointerChain(chainHeader.getChainedImports(), unchainedLocList,
ptrFormat, page, pageOffset, authValueAdd);
break;
// These might work, but have not been fully tested!
case DYLD_CHAINED_PTR_64:
case DYLD_CHAINED_PTR_64_OFFSET:
case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
case DYLD_CHAINED_PTR_32:
case DYLD_CHAINED_PTR_32_CACHE:
case DYLD_CHAINED_PTR_32_FIRMWARE:
case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
processPointerChain(chainHeader.getChainedImports(), unchainedLocList,
ptrFormat, page, pageOffset, authValueAdd);
break;
case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
default:
log.appendMsg(
"WARNING: Pointer Chain format " + ptrFormat + " not processed yet!");
break;
}
fixedAddressCount += unchainedLocList.size();
fixedAddresses.addAll(unchainedLocList);
}
log.appendMsg(
"Fixed " + fixedAddressCount + " " + ptrFormat.getName() + " chained pointers.");
return fixedAddresses;
}
/**
* Fixes up any chained pointers, starting at the given address.
* Walks the chained fixup information and collects a {@link List} of {@link DyldFixup}s that
* will need to be applied to the image
*
* @param reader A {@link BinaryReader} that can read the image
* @param chainedImports chained imports (could be null)
* @param unchainedLocList list of locations that were unchained
* @param pointerFormat format of pointers within this chain
* @param page within data pages that has pointers to be unchained
* @param nextOff offset within the page that is the chain start
* @param auth_value_add value to be added to each chain pointer
*
* @throws MemoryAccessException IO problem reading file
* @throws CancelledException user cancels
* @param imagebase The image base
* @param symbolTable The {@link SymbolTable}, or null if not available
* @param log The log
* @param monitor A cancellable monitor
* @return A {@link List} of {@link DyldFixup}s
* @throws IOException If there was an IO-related issue
* @throws CancelledException If the user cancelled the operation
*/
public void processPointerChain(DyldChainedImports chainedImports,
List<Address> unchainedLocList, DyldChainType pointerFormat, long page, long nextOff,
long auth_value_add) throws MemoryAccessException, CancelledException {
long imageBaseOffset = program.getImageBase().getOffset();
Address chainStart = space.getAddress(page);
public static List<DyldFixup> getChainedFixups(BinaryReader reader,
DyldChainedImports chainedImports, DyldChainType pointerFormat, long page, long nextOff,
long auth_value_add, long imagebase, SymbolTable symbolTable, MessageLog log,
TaskMonitor monitor) throws IOException, CancelledException {
List<DyldFixup> fixups = new ArrayList<>();
long next = -1;
boolean start = true;
while (next != 0) {
monitor.checkCancelled();
Address chainLoc = chainStart.add(nextOff);
final long chainValue = DyldChainedPtr.getChainValue(memory, chainLoc, pointerFormat);
long chainLoc = page + nextOff;
final long chainValue = DyldChainedPtr.getChainValue(reader, chainLoc, pointerFormat);
long newChainValue = chainValue;
boolean isAuthenticated = DyldChainedPtr.isAuthenticated(pointerFormat, chainValue);
boolean isBound = DyldChainedPtr.isBound(pointerFormat, chainValue);
Symbol symbol = null;
Integer libOrdinal = null;
if (isBound && chainedImports == null) {
log.appendMsg(
"Error: dyld_chained_import array required to process bound chain fixup at " +
chainLoc);
return;
}
String symName = null;
if (isAuthenticated && !isBound) {
long offsetFromSharedCacheBase =
DyldChainedPtr.getTarget(pointerFormat, chainValue);
//long diversityData = DyldChainedPtr.getDiversity(pointerFormat, chainValue);
//boolean hasAddressDiversity =
// DyldChainedPtr.hasAddrDiversity(pointerFormat, chainValue);
//long key = DyldChainedPtr.getKey(pointerFormat, chainValue);
newChainValue = imageBaseOffset + offsetFromSharedCacheBase + auth_value_add;
}
else if (!isAuthenticated && isBound) {
if (isBound) {
if (chainedImports == null) {
log.appendMsg(
"Error: dyld_chained_import array required to process bound chain fixup at " +
chainLoc);
return List.of();
}
if (symbolTable == null) {
log.appendMsg(
"Error: symbol table required to process bound chain fixup at " + chainLoc);
return List.of();
}
int chainOrdinal = (int) DyldChainedPtr.getOrdinal(pointerFormat, chainValue);
long addend = DyldChainedPtr.getAddend(pointerFormat, chainValue);
DyldChainedImport chainedImport = chainedImports.getChainedImport(chainOrdinal);
symName = chainedImport.getName();
// lookup the symbol, and then add addend
List<Symbol> globalSymbols = program.getSymbolTable().getGlobalSymbols(symName);
List<Symbol> globalSymbols = symbolTable.getGlobalSymbols(chainedImport.getName());
if (globalSymbols.size() > 0) {
Symbol symbol = globalSymbols.get(0);
symbol = globalSymbols.get(0);
newChainValue = symbol.getAddress().getOffset();
fixupExternalLibrary(chainedImport.getLibOrdinal(), symbol);
libOrdinal = chainedImport.getLibOrdinal();
}
newChainValue += addend;
}
else if (isAuthenticated && isBound) {
int chainOrdinal = (int) DyldChainedPtr.getOrdinal(pointerFormat, chainValue);
//long addend = DyldChainedPtr.getAddend(pointerFormat, chainValue);
//long diversityData = DyldChainedPtr.getDiversity(pointerFormat, chainValue);
//boolean hasAddressDiversity =
// DyldChainedPtr.hasAddrDiversity(pointerFormat, chainValue);
//long key = DyldChainedPtr.getKey(pointerFormat, chainValue);
DyldChainedImport chainedImport = chainedImports.getChainedImport(chainOrdinal);
symName = chainedImport.getName();
// lookup the symbol, and then add addend
List<Symbol> globalSymbols = program.getSymbolTable().getGlobalSymbols(symName);
if (globalSymbols.size() > 0) {
Symbol symbol = globalSymbols.get(0);
newChainValue = symbol.getAddress().getOffset();
fixupExternalLibrary(chainedImport.getLibOrdinal(), symbol);
}
newChainValue = newChainValue + auth_value_add;
newChainValue += isAuthenticated ? auth_value_add : addend;
}
else {
newChainValue = DyldChainedPtr.getTarget(pointerFormat, chainValue);
if (DyldChainedPtr.isRelative(pointerFormat)) {
newChainValue += imageBaseOffset;
if (isAuthenticated) {
newChainValue = imagebase +
DyldChainedPtr.getTarget(pointerFormat, chainValue) + auth_value_add;
}
else {
newChainValue = DyldChainedPtr.getTarget(pointerFormat, chainValue);
if (DyldChainedPtr.isRelative(pointerFormat)) {
newChainValue += imagebase;
}
}
}
if (!start || !program.getRelocationTable().hasRelocation(chainLoc)) {
int byteLength = 0;
Status status = Status.FAILURE;
try {
RelocationResult result =
DyldChainedPtr.setChainValue(memory, chainLoc, pointerFormat,
newChainValue);
status = result.status();
byteLength = result.byteLength();
}
finally {
program.getRelocationTable()
.add(chainLoc, status,
(start ? 0x8000 : 0x4000) | (isAuthenticated ? 4 : 0) |
(isBound ? 2 : 0) | 1,
new long[] { newChainValue }, byteLength, symName);
}
}
// delay creating data until after memory has been changed
unchainedLocList.add(chainLoc);
fixups.add(new DyldFixup(chainLoc, newChainValue, DyldChainedPtr.getSize(pointerFormat),
symbol, libOrdinal));
start = false;
next = DyldChainedPtr.getNext(pointerFormat, chainValue);
nextOff += next * DyldChainedPtr.getStride(pointerFormat);
}
return fixups;
}
private void fixupExternalLibrary(int libraryOrdinal, Symbol symbol) {
/**
* Fixes up the program's chained pointers
*
* @param fixups A {@link List} of the fixups
* @param program The {@link Program}
* @param imagebase The image base
* @param libraryPaths A {@link List} of library paths
* @param log The log
* @param monitor A cancellable monitor
* @return A {@link List} of fixed up {@link Address}'s
* @throws MemoryAccessException If there was a problem accessing memory
* @throws CancelledException If the user cancelled the operation
*/
public static List<Address> fixupChainedPointers(List<DyldFixup> fixups, Program program,
Address imagebase, List<String> libraryPaths, MessageLog log, TaskMonitor monitor)
throws MemoryAccessException, CancelledException {
List<Address> fixedAddrs = new ArrayList<>();
if (fixups.isEmpty()) {
return fixedAddrs;
}
Memory memory = program.getMemory();
for (DyldFixup fixup : fixups) {
monitor.checkCancelled();
Status status = Status.FAILURE;
Address addr = imagebase.add(fixup.offset());
try {
if (fixup.size() == 8 || fixup.size() == 4) {
if (fixup.size() == 8) {
memory.setLong(addr, fixup.value());
}
else {
memory.setInt(addr, (int) fixup.value());
}
fixedAddrs.add(addr);
status = Status.APPLIED_OTHER;
}
else {
status = Status.UNSUPPORTED;
}
}
finally {
program.getRelocationTable()
.add(addr, status, 0, new long[] { fixup.value() }, fixup.size(),
fixup.symbol() != null ? fixup.symbol().getName() : null);
}
if (fixup.symbol() != null && fixup.libOrdinal() != null) {
fixupExternalLibrary(program, libraryPaths, fixup.libOrdinal(), fixup.symbol(), log,
monitor);
}
}
log.appendMsg("Fixed up " + fixedAddrs.size() + " chained pointers.");
return fixedAddrs;
}
/**
* Associates the given {@link Symbol} with the correct external {@link Library} (fixing
* the <EXTERNAL> association)
*
* @param program The {@link Program}
* @param libraryPaths A {@link List} of library paths
* @param libraryOrdinal The library ordinal
* @param symbol The {@link Symbol}
* @param log The log
* @param monitor A cancellable monitor
*/
private static void fixupExternalLibrary(Program program, List<String> libraryPaths,
int libraryOrdinal, Symbol symbol, MessageLog log, TaskMonitor monitor) {
ExternalManager extManager = program.getExternalManager();
int libraryIndex = libraryOrdinal - 1;
if (libraryIndex >= 0 && libraryIndex < libraryPaths.size()) {
@ -352,85 +202,63 @@ public class DyldChainedFixups {
}
}
//---------------------Below are used only by handled __thread_starts-------------------------
/**
* Fixes up any chained pointers, starting at the given address.
*
* @param reader A {@link BinaryReader} that can read the image
* @param chainStart The starting of address of the pointer chain to fix.
* @param nextOffSize The size of the next offset.
* @param imagebase The image base
* @param log The log
* @param monitor A cancellable monitor
* @return A list of addresses where pointer fixes were performed.
* @throws MemoryAccessException if there was a problem reading/writing memory.
* @throws IOException If there was an IO-related issue
* @throws CancelledException If the user cancelled the operation
*/
private List<Address> processPointerChain(Address chainStart, long nextOffSize)
throws MemoryAccessException {
List<Address> fixedAddresses = new ArrayList<>();
while (!monitor.isCancelled()) {
long chainValue = memory.getLong(chainStart);
fixupPointer(chainStart, chainValue);
fixedAddresses.add(chainStart);
long nextValueOff = ((chainValue >> 51L) & 0x7ffL) * nextOffSize;
if (nextValueOff == 0) {
break;
}
chainStart = chainStart.add(nextValueOff);
}
return fixedAddresses;
}
/**
* Fixes up the pointer at the given address.
*
* @param pointerAddr The address of the pointer to fix.
* @param pointerValue The value at the address of the pointer to fix.
* @throws MemoryAccessException if there was a problem reading/writing memory.
*/
private void fixupPointer(Address pointerAddr, long pointerValue) throws MemoryAccessException {
public static List<DyldFixup> processPointerChain(BinaryReader reader, long chainStart,
long nextOffSize, long imagebase, MessageLog log, TaskMonitor monitor)
throws IOException, CancelledException {
final long BIT63 = (0x1L << 63);
final long BIT62 = (0x1L << 62);
// Bad chain value
if ((pointerValue & BIT62) != 0) {
// this is a pointer, but is good now
}
List<DyldFixup> fixups = new ArrayList<>();
long fixedPointerValue = 0;
long fixedPointerType = 0;
while (true) {
monitor.checkCancelled();
// Pointer checked value
if ((pointerValue & BIT63) != 0) {
//long tagType = (pointerValue >> 49L) & 0x3L;
long pacMod = ((pointerValue >> 32) & 0xffff);
fixedPointerType = pacMod;
fixedPointerValue = program.getImageBase().getOffset() + (pointerValue & 0xffffffffL);
}
else {
fixedPointerValue =
((pointerValue << 13) & 0xff00000000000000L) | (pointerValue & 0x7ffffffffffL);
if ((pointerValue & 0x40000000000L) != 0) {
fixedPointerValue |= 0xfffc0000000000L;
long chainValue = reader.readLong(chainStart);
long fixedPointerValue = 0;
// Bad chain value
if ((chainValue & BIT62) != 0) {
// this is a pointer, but is good now
}
// Pointer checked value
if ((chainValue & BIT63) != 0) {
//long tagType = (pointerValue >> 49L) & 0x3L;
//long pacMod = ((pointerValue >> 32) & 0xffff);
fixedPointerValue = imagebase + (chainValue & 0xffffffffL);
}
else {
fixedPointerValue =
((chainValue << 13) & 0xff000_0000_0000_000L) | (chainValue & 0x7ff_ffff_ffffL);
if ((chainValue & 0x0400_0000_0000L) != 0) {
fixedPointerValue |= 0x00ff_fc00_0000_0000L;
}
}
fixups.add(new DyldFixup(chainStart, fixedPointerValue, 8, null, null));
long nextValueOff = ((chainValue >> 51L) & 0x7ff) * nextOffSize;
if (nextValueOff == 0) {
break;
}
chainStart = chainStart + nextValueOff;
}
// Add entry to relocation table for the pointer fixup
byte origBytes[] = new byte[8];
memory.getBytes(pointerAddr, origBytes);
boolean success = false;
try {
// Fixup the pointer
memory.setLong(pointerAddr, fixedPointerValue);
success = true;
}
finally {
Status status = success ? Status.APPLIED : Status.FAILURE;
program.getRelocationTable()
.add(pointerAddr, status, (int) fixedPointerType,
new long[] { fixedPointerValue },
origBytes, null);
}
return fixups;
}
}

View file

@ -123,10 +123,10 @@ public class DyldCacheSlideInfo1 extends DyldCacheSlideInfoCommon {
}
@Override
public List<DyldCacheSlideFixup> getSlideFixups(BinaryReader reader, int pointerSize,
MessageLog log, TaskMonitor monitor) throws IOException, CancelledException {
public List<DyldFixup> getSlideFixups(BinaryReader reader, int pointerSize, MessageLog log,
TaskMonitor monitor) throws IOException, CancelledException {
List<DyldCacheSlideFixup> fixups = new ArrayList<>(1024);
List<DyldFixup> fixups = new ArrayList<>(1024);
// V1 pointers currently don't need to be fixed, unless the cache is slid from its preferred
// location.
@ -155,7 +155,7 @@ public class DyldCacheSlideInfo1 extends DyldCacheSlideInfoCommon {
long pageOffset = pageEntriesIndex * 8 * 4 + bitMapIndex * 4;
long value = reader.readLong(segmentOffset + pageOffset) /* + slide */;
fixups.add(
new DyldCacheSlideFixup(segmentOffset + pageOffset, value, 8));
new DyldFixup(segmentOffset + pageOffset, value, 8, null, null));
}
}
}

View file

@ -134,10 +134,10 @@ public class DyldCacheSlideInfo2 extends DyldCacheSlideInfoCommon {
}
@Override
public List<DyldCacheSlideFixup> getSlideFixups(BinaryReader reader, int pointerSize,
MessageLog log, TaskMonitor monitor) throws IOException, CancelledException {
public List<DyldFixup> getSlideFixups(BinaryReader reader, int pointerSize, MessageLog log,
TaskMonitor monitor) throws IOException, CancelledException {
List<DyldCacheSlideFixup> fixups = new ArrayList<>();
List<DyldFixup> fixups = new ArrayList<>();
monitor.initialize(pageStartsCount, "Getting DYLD Cache V2 slide fixups...");
for (int index = 0; index < pageStartsCount; index++) {
@ -172,23 +172,22 @@ public class DyldCacheSlideInfo2 extends DyldCacheSlideInfoCommon {
}
/**
* Walks the pointer chain at the given reader offset to find necessary
* {@link DyldCacheSlideFixup}s
* Walks the pointer chain at the given reader offset to find necessary {@link DyldFixup}s
*
* @param segmentOffset The segment offset
* @param pageOffset The page offset
* @param reader A reader positioned at the start of the segment to fix
* @param pointerSize The size of a pointer in bytes
* @param monitor A cancellable monitor
* @return A {@link List} of {@link DyldCacheSlideFixup}s
* @return A {@link List} of {@link DyldFixup}s
* @throws IOException If an IO-related error occurred
* @throws CancelledException If the user cancelled the operation
*/
private List<DyldCacheSlideFixup> processPointerChain(long segmentOffset, long pageOffset,
private List<DyldFixup> processPointerChain(long segmentOffset, long pageOffset,
BinaryReader reader, int pointerSize, TaskMonitor monitor)
throws IOException, CancelledException {
List<DyldCacheSlideFixup> fixups = new ArrayList<>(1024);
List<DyldFixup> fixups = new ArrayList<>(1024);
long valueMask = ~deltaMask;
long deltaShift = Long.numberOfTrailingZeros(deltaMask);
@ -203,7 +202,7 @@ public class DyldCacheSlideInfo2 extends DyldCacheSlideInfoCommon {
chainValue &= valueMask;
if (chainValue != 0) {
chainValue += valueAdd /* + slide */;
fixups.add(new DyldCacheSlideFixup(dataOffset, chainValue, pointerSize));
fixups.add(new DyldFixup(dataOffset, chainValue, pointerSize, null, null));
}
}

View file

@ -89,9 +89,9 @@ public class DyldCacheSlideInfo3 extends DyldCacheSlideInfoCommon {
}
@Override
public List<DyldCacheSlideFixup> getSlideFixups(BinaryReader reader, int pointerSize,
MessageLog log, TaskMonitor monitor) throws IOException, CancelledException {
List<DyldCacheSlideFixup> fixups = new ArrayList<>();
public List<DyldFixup> getSlideFixups(BinaryReader reader, int pointerSize, MessageLog log,
TaskMonitor monitor) throws IOException, CancelledException {
List<DyldFixup> fixups = new ArrayList<>();
monitor.initialize(pageStartsCount, "Getting DYLD Cache V3 slide fixups...");
for (int index = 0; index < pageStartsCount; index++) {
@ -112,21 +112,20 @@ public class DyldCacheSlideInfo3 extends DyldCacheSlideInfoCommon {
}
/**
* Walks the pointer chain at the given reader offset to find necessary
* {@link DyldCacheSlideFixup}s
* Walks the pointer chain at the given reader offset to find necessary {@link DyldFixup}s
*
* @param segmentOffset The segment offset
* @param pageOffset The page offset
* @param reader A reader positioned at the start of the segment to fix
* @param monitor A cancellable monitor
* @return A {@link List} of {@link DyldCacheSlideFixup}s
* @return A {@link List} of {@link DyldFixup}s
* @throws IOException If an IO-related error occurred
* @throws CancelledException If the user cancelled the operation
*/
private List<DyldCacheSlideFixup> processPointerChain(long segmentOffset, long pageOffset,
private List<DyldFixup> processPointerChain(long segmentOffset, long pageOffset,
BinaryReader reader, TaskMonitor monitor) throws IOException, CancelledException {
List<DyldCacheSlideFixup> fixups = new ArrayList<>(1024);
List<DyldFixup> fixups = new ArrayList<>(1024);
for (long delta = -1; delta != 0; pageOffset += delta * 8) {
monitor.checkCancelled();
@ -151,7 +150,7 @@ public class DyldCacheSlideInfo3 extends DyldCacheSlideInfoCommon {
chainValue = (top8Bits << 13) | bottom43Bits /* + slide */;
}
fixups.add(new DyldCacheSlideFixup(dataOffset, chainValue, 8));
fixups.add(new DyldFixup(dataOffset, chainValue, 8, null, null));
}
return fixups;

View file

@ -138,9 +138,9 @@ public class DyldCacheSlideInfo4 extends DyldCacheSlideInfoCommon {
}
@Override
public List<DyldCacheSlideFixup> getSlideFixups(BinaryReader reader, int pointerSize,
public List<DyldFixup> getSlideFixups(BinaryReader reader, int pointerSize,
MessageLog log, TaskMonitor monitor) throws IOException, CancelledException {
List<DyldCacheSlideFixup> fixups = new ArrayList<>();
List<DyldFixup> fixups = new ArrayList<>();
monitor.initialize(pageStartsCount, "Getting DYLD Cache V4 slide fixups...");
for (int index = 0; index < pageStartsCount; index++) {
@ -175,20 +175,19 @@ public class DyldCacheSlideInfo4 extends DyldCacheSlideInfoCommon {
}
/**
* Walks the pointer chain at the given reader offset to find necessary
* {@link DyldCacheSlideFixup}s
* Walks the pointer chain at the given reader offset to find necessary {@link DyldFixup}s
*
* @param segmentOffset The segment offset
* @param pageOffset The page offset
* @param reader A reader positioned at the start of the segment to fix
* @param monitor A cancellable monitor
* @return A {@link List} of {@link DyldCacheSlideFixup}s
* @return A {@link List} of {@link DyldFixup}s
* @throws IOException If an IO-related error occurred
* @throws CancelledException If the user cancelled the operation
*/
private List<DyldCacheSlideFixup> processPointerChain(long segmentOffset, long pageOffset,
private List<DyldFixup> processPointerChain(long segmentOffset, long pageOffset,
BinaryReader reader, TaskMonitor monitor) throws IOException, CancelledException {
List<DyldCacheSlideFixup> fixups = new ArrayList<>(1024);
List<DyldFixup> fixups = new ArrayList<>(1024);
long valueMask = ~deltaMask;
long deltaShift = Long.numberOfTrailingZeros(deltaMask);
@ -210,7 +209,7 @@ public class DyldCacheSlideInfo4 extends DyldCacheSlideInfoCommon {
chainValue += valueAdd /* + slide */;
}
fixups.add(new DyldCacheSlideFixup(dataOffset, chainValue, 4));
fixups.add(new DyldFixup(dataOffset, chainValue, 4, null, null));
}
return fixups;

View file

@ -108,8 +108,7 @@ public abstract class DyldCacheSlideInfoCommon implements StructConverter {
* @throws IOException if there was an IO-related problem creating the DYLD slide info
*/
public DyldCacheSlideInfoCommon(BinaryReader reader, long mappingAddress, long mappingSize,
long mappingFileOffset)
throws IOException {
long mappingFileOffset) throws IOException {
this.mappingAddress = mappingAddress;
this.mappingSize = mappingSize;
this.mappingFileOffset = mappingFileOffset;
@ -152,22 +151,22 @@ public abstract class DyldCacheSlideInfoCommon implements StructConverter {
}
/**
* Walks the slide fixup information and collects a {@link List} of {@link DyldCacheSlideFixup}s
* that will need to be applied to the image
* Walks the slide fixup information and collects a {@link List} of {@link DyldFixup}s that will
* need to be applied to the image
*
* @param reader A {@link BinaryReader} positioned at the start of the segment to fix up
* @param pointerSize The size of a pointer in bytes
* @param log The log
* @param monitor A cancellable monitor
* @return A {@link List} of {@link DyldCacheSlideFixup}s
* @return A {@link List} of {@link DyldFixup}s
* @throws IOException If there was an IO-related issue
* @throws CancelledException If the user cancelled the operation
*/
public abstract List<DyldCacheSlideFixup> getSlideFixups(BinaryReader reader, int pointerSize,
public abstract List<DyldFixup> getSlideFixups(BinaryReader reader, int pointerSize,
MessageLog log, TaskMonitor monitor) throws IOException, CancelledException;
/**
* Fixes up the programs slide pointers
* Fixes up the program's slide pointers
*
* @param program The {@link Program}
* @param markup True if the slide pointers should be marked up; otherwise, false
@ -188,11 +187,11 @@ public abstract class DyldCacheSlideInfoCommon implements StructConverter {
try (ByteProvider provider = new MemoryByteProvider(memory, dataPageAddr)) {
BinaryReader reader = new BinaryReader(provider, !memory.isBigEndian());
List<DyldCacheSlideFixup> fixups =
List<DyldFixup> fixups =
getSlideFixups(reader, program.getDefaultPointerSize(), log, monitor);
monitor.initialize(fixups.size(), "Fixing DYLD Cache slide pointers...");
for (DyldCacheSlideFixup fixup : fixups) {
for (DyldFixup fixup : fixups) {
monitor.increment();
Address addr = dataPageAddr.add(fixup.offset());
if (fixup.size() == 8) {
@ -205,7 +204,7 @@ public abstract class DyldCacheSlideInfoCommon implements StructConverter {
if (markup) {
monitor.initialize(fixups.size(), "Marking up DYLD Cache slide pointers...");
for (DyldCacheSlideFixup fixup : fixups) {
for (DyldFixup fixup : fixups) {
monitor.increment();
Address addr = dataPageAddr.add(fixup.offset());
if (addRelocations) {

View file

@ -15,6 +15,9 @@
*/
package ghidra.app.util.bin.format.macho.dyld;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.address.Address;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
@ -114,6 +117,29 @@ public class DyldChainedPtr {
}
}
public static int getSize(DyldChainType ptrFormat) {
return switch (ptrFormat) {
case DYLD_CHAINED_PTR_ARM64E:
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
case DYLD_CHAINED_PTR_64:
case DYLD_CHAINED_PTR_64_OFFSET:
case DYLD_CHAINED_PTR_ARM64E_KERNEL:
case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
yield 8;
case DYLD_CHAINED_PTR_32:
case DYLD_CHAINED_PTR_32_CACHE:
case DYLD_CHAINED_PTR_32_FIRMWARE:
yield 4;
default:
yield 8;
};
}
public static RelocationResult setChainValue(Memory memory, Address chainLoc,
DyldChainType ptrFormat,
long value) throws MemoryAccessException {
@ -145,8 +171,8 @@ public class DyldChainedPtr {
return new RelocationResult(Status.APPLIED_OTHER, byteLength);
}
public static long getChainValue(Memory memory, Address chainLoc, DyldChainType ptrFormat)
throws MemoryAccessException {
public static long getChainValue(BinaryReader reader, long chainLoc, DyldChainType ptrFormat)
throws IOException {
switch (ptrFormat) {
case DYLD_CHAINED_PTR_ARM64E:
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
@ -157,12 +183,12 @@ public class DyldChainedPtr {
case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
return memory.getLong(chainLoc);
return reader.readLong(chainLoc);
case DYLD_CHAINED_PTR_32:
case DYLD_CHAINED_PTR_32_CACHE:
case DYLD_CHAINED_PTR_32_FIRMWARE:
return memory.getInt(chainLoc) & 0xFFFFFFFFL;
return reader.readUnsignedInt(chainLoc);
default:
return 0;
}

View file

@ -15,11 +15,15 @@
*/
package ghidra.app.util.bin.format.macho.dyld;
import ghidra.program.model.symbol.Symbol;
/**
* Stores information needed to perform a slide pointer fixup
* Stores information needed to perform a dyld pointer fixup
*
* @param offset The offset of where to perform the fixup (from some base address/index)
* @param value The fixed up value
* @param size The size of the fixup in bytes
* @param symbol The {@link Symbol} associated with the fixup (could be null)
* @param libOrdinal The library ordinal associated with the fixup (could be null)
*/
public record DyldCacheSlideFixup(long offset, long value, int size) {}
public record DyldFixup(long offset, long value, int size, Symbol symbol, Integer libOrdinal) {}

View file

@ -90,7 +90,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
// A Mach-O file may contain PRELINK information. If so, we use a special
// program builder that knows how to deal with it.
if (MachoPrelinkUtils.isMachoPrelink(provider, monitor)) {
if (MachoPrelinkUtils.isMachoPrelink(provider, true, monitor)) {
MachoPrelinkProgramBuilder.buildProgram(program, provider, fileBytes, log, monitor);
}
else {

View file

@ -26,8 +26,7 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.ByteProviderWrapper;
import ghidra.app.util.bin.format.macho.MachException;
import ghidra.app.util.bin.format.macho.MachHeader;
import ghidra.app.util.bin.format.macho.commands.SegmentCommand;
import ghidra.app.util.bin.format.macho.commands.SegmentNames;
import ghidra.app.util.bin.format.macho.commands.*;
import ghidra.app.util.bin.format.macho.prelink.*;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
@ -41,14 +40,20 @@ public class MachoPrelinkUtils {
* Check to see if the given {@link ByteProvider} is a Mach-O PRELINK binary
*
* @param provider The {@link ByteProvider} to check
* @param allowFileset True if Mach-O file sets should be considered a PRELINK binary;
* otherwise, false
* @param monitor A monitor
* @return True if the given {@link ByteProvider} is a Mach-O PRELINK binary; otherwise, false
*/
public static boolean isMachoPrelink(ByteProvider provider, TaskMonitor monitor) {
public static boolean isMachoPrelink(ByteProvider provider, boolean allowFileset,
TaskMonitor monitor) {
try {
return new MachHeader(provider).parseSegments()
MachHeader header = new MachHeader(provider);
boolean hasPrelinkSegment = new MachHeader(provider).parseSegments()
.stream()
.anyMatch(segment -> segment.getSegmentName().startsWith("__PRELINK"));
boolean hasFileSet = header.parseAndCheck(LoadCommandTypes.LC_FILESET_ENTRY);
return hasPrelinkSegment && allowFileset && hasFileSet;
}
catch (MachException | IOException e) {
// Assume it's not a Mach-O PRELINK...fall through

View file

@ -31,6 +31,7 @@ import ghidra.app.util.bin.format.macho.commands.chained.*;
import ghidra.app.util.bin.format.macho.commands.dyld.*;
import ghidra.app.util.bin.format.macho.commands.dyld.BindingTable.Binding;
import ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.DyldChainType;
import ghidra.app.util.bin.format.macho.dyld.DyldFixup;
import ghidra.app.util.bin.format.macho.relocation.*;
import ghidra.app.util.bin.format.macho.threadcommand.ThreadCommand;
import ghidra.app.util.bin.format.objectiveC.ObjectiveC1_Constants;
@ -71,6 +72,7 @@ public class MachoProgramBuilder {
protected Memory memory;
protected Listing listing;
protected AddressSpace space;
protected BinaryReader reader;
/**
* Creates a new {@link MachoProgramBuilder} based on the given information.
@ -91,6 +93,7 @@ public class MachoProgramBuilder {
this.memory = program.getMemory();
this.listing = program.getListing();
this.space = program.getAddressFactory().getDefaultAddressSpace();
this.reader = new BinaryReader(provider, !memory.isBigEndian());
}
/**
@ -119,7 +122,7 @@ public class MachoProgramBuilder {
monitor.setCancelEnabled(true);
// Setup memory
setImageBase();
setProgramImageBase();
processMemoryBlocks(machoHeader, provider.getName(), true, true);
// Process load commands
@ -156,29 +159,34 @@ public class MachoProgramBuilder {
}
/**
* Sets the image base
* Sets the {@link Program} image base
*
* @throws Exception if there was a problem setting the image base
* @throws Exception if there was a problem setting the {@link Program} image base
*/
protected void setImageBase() throws Exception {
Address imageBaseAddr = null;
protected void setProgramImageBase() throws Exception {
program.setImageBase(getMachoBaseAddress(), true);
}
/**
* Gets the base address of this Mach-O. This is the address of the start of the Mach-O, not
* necessary the {@link Program} image base.
*
* @return The base address of this Mach-O
*/
protected Address getMachoBaseAddress() {
Address lowestAddr = null;
for (SegmentCommand segment : machoHeader.getAllSegments()) {
if (segment.getFileSize() > 0) {
Address segmentAddr = space.getAddress(segment.getVMaddress());
if (imageBaseAddr == null) {
imageBaseAddr = segmentAddr;
if (lowestAddr == null) {
lowestAddr = segmentAddr;
}
else if (segmentAddr.compareTo(imageBaseAddr) < 0) {
imageBaseAddr = segmentAddr;
else if (segmentAddr.compareTo(lowestAddr) < 0) {
lowestAddr = segmentAddr;
}
}
}
if (imageBaseAddr != null) {
program.setImageBase(imageBaseAddr, true);
}
else {
program.setImageBase(space.getAddress(0), true);
}
return lowestAddr != null ? lowestAddr : space.getAddress(0);
}
/**
@ -761,9 +769,59 @@ public class MachoProgramBuilder {
}
public List<Address> processChainedFixups(List<String> libraryPaths) throws Exception {
DyldChainedFixups dyldChainedFixups =
new DyldChainedFixups(program, machoHeader, libraryPaths, log, monitor);
return dyldChainedFixups.processChainedFixups();
monitor.setMessage("Fixing up chained pointers...");
SymbolTable symbolTable = program.getSymbolTable();
Address imagebase = getMachoBaseAddress();
List<DyldFixup> fixups = new ArrayList<>();
// First look for a DyldChainedFixupsCommand
List<DyldChainedFixupsCommand> loadCommands =
machoHeader.getLoadCommands(DyldChainedFixupsCommand.class);
if (!loadCommands.isEmpty()) {
for (DyldChainedFixupsCommand loadCommand : loadCommands) {
fixups.addAll(loadCommand.getChainedFixups(reader, imagebase.getOffset(),
symbolTable, log, monitor));
}
}
else {
// Didn't find a DyldChainedFixupsCommand, so look for the sections with fixup info
Section chainStartsSection =
machoHeader.getSection(SegmentNames.SEG_TEXT, SectionNames.CHAIN_STARTS);
Section threadStartsSection =
machoHeader.getSection(SegmentNames.SEG_TEXT, SectionNames.THREAD_STARTS);
if (chainStartsSection != null) {
reader.setPointerIndex(chainStartsSection.getOffset());
DyldChainedStartsOffsets chainedStartsOffsets =
new DyldChainedStartsOffsets(reader);
for (int offset : chainedStartsOffsets.getChainStartOffsets()) {
fixups.addAll(DyldChainedFixups.getChainedFixups(reader, null,
chainedStartsOffsets.getPointerFormat(), offset, 0, 0,
imagebase.getOffset(), symbolTable, log, monitor));
}
}
else if (threadStartsSection != null) {
Address threadSectionStart = space.getAddress(threadStartsSection.getAddress());
Address threadSectionEnd =
threadSectionStart.add(threadStartsSection.getSize() - 1);
long nextOffSize = (memory.getInt(threadSectionStart) & 1) * 4 + 4;
Address chainHead = threadSectionStart.add(4);
while (chainHead.compareTo(threadSectionEnd) < 0 && !monitor.isCancelled()) {
int headStartOffset = memory.getInt(chainHead);
if (headStartOffset == 0xFFFFFFFF || headStartOffset == 0) {
break;
}
long chainStart = Integer.toUnsignedLong(headStartOffset);
fixups.addAll(DyldChainedFixups.processPointerChain(reader, chainStart,
nextOffSize, imagebase.getOffset(), log, monitor));
chainHead = chainHead.add(4);
}
}
}
return DyldChainedFixups.fixupChainedPointers(fixups, program, imagebase, libraryPaths,
log, monitor);
}
protected void processBindings(boolean doClassic, List<String> libraryPaths) throws Exception {
@ -800,24 +858,22 @@ public class MachoProgramBuilder {
throws Exception {
DataConverter converter = DataConverter.getInstance(program.getLanguage().isBigEndian());
SymbolTable symbolTable = program.getSymbolTable();
Address imagebase = getMachoBaseAddress();
List<Binding> bindings = bindingTable.getBindings();
List<Binding> threadedBindings = bindingTable.getThreadedBindings();
List<SegmentCommand> segments = machoHeader.getAllSegments();
if (threadedBindings != null) {
DyldChainedFixups dyldChainedFixups =
new DyldChainedFixups(program, machoHeader, libraryPaths, log, monitor);
DyldChainedImports chainedImports = new DyldChainedImports(bindings);
for (Binding threadedBinding : threadedBindings) {
List<Address> fixedAddresses = new ArrayList<>();
SegmentCommand segment = segments.get(threadedBinding.getSegmentIndex());
dyldChainedFixups.processPointerChain(chainedImports, fixedAddresses,
DyldChainType.DYLD_CHAINED_PTR_ARM64E,
segments.get(threadedBinding.getSegmentIndex()).getVMaddress(),
threadedBinding.getSegmentOffset(), 0);
log.appendMsg("Fixed up %d chained pointers in %s".formatted(fixedAddresses.size(),
segment.getSegmentName()));
List<DyldFixup> fixups = DyldChainedFixups.getChainedFixups(reader,
chainedImports, DyldChainType.DYLD_CHAINED_PTR_ARM64E,
segments.get(threadedBinding.getSegmentIndex()).getFileOffset(),
threadedBinding.getSegmentOffset(), 0, imagebase.getOffset(),
symbolTable, log, monitor);
DyldChainedFixups.fixupChainedPointers(fixups, program, imagebase, libraryPaths,
log, monitor);
}
}
else {
@ -1074,8 +1130,7 @@ public class MachoProgramBuilder {
}
if (section.getSectionName().equals(SectionNames.CHAIN_STARTS)) {
ByteProvider p = new MemoryByteProvider(memory, block.getStart());
BinaryReader reader = new BinaryReader(p, machoHeader.isLittleEndian());
reader.setPointerIndex(section.getOffset());
DyldChainedStartsOffsets chainedStartsOffsets =
new DyldChainedStartsOffsets(reader);
DataUtilities.createData(program, block.getStart(),

View file

@ -55,7 +55,7 @@ public class DyldCacheExtractLoader extends MachoLoader {
try {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
DyldCacheExtractProgramBuilder.buildProgram(program, provider, fileBytes, log, monitor);
MachoExtractProgramBuilder.buildProgram(program, provider, fileBytes, log, monitor);
}
catch (CancelledException e) {
return;

View file

@ -26,13 +26,13 @@ import ghidra.program.model.symbol.*;
import ghidra.util.task.TaskMonitor;
/**
* Builds up a {@link Program} of components extracted from a DYLD Cache by parsing the Mach-O
* Builds up a {@link Program} of components extracted from a Mach-O container by parsing the Mach-O
* headers
*/
public class DyldCacheExtractProgramBuilder extends MachoProgramBuilder {
public class MachoExtractProgramBuilder extends MachoProgramBuilder {
/**
* Creates a new {@link DyldCacheExtractProgramBuilder} based on the given information.
* Creates a new {@link MachoExtractProgramBuilder} based on the given information.
*
* @param program The {@link Program} to build up.
* @param provider The {@link ByteProvider} that contains the Mach-O's bytes.
@ -41,13 +41,13 @@ public class DyldCacheExtractProgramBuilder extends MachoProgramBuilder {
* @param monitor A cancelable task monitor.
* @throws Exception if a problem occurs.
*/
protected DyldCacheExtractProgramBuilder(Program program, ByteProvider provider,
protected MachoExtractProgramBuilder(Program program, ByteProvider provider,
FileBytes fileBytes, MessageLog log, TaskMonitor monitor) throws Exception {
super(program, provider, fileBytes, log, monitor);
}
/**
* Builds up a {@link Program} of components extracted from a DYLD Cache
* Builds up a {@link Program} of Mach-O components extracted from a Mach-O container
*
* @param program The {@link Program} to build up.
* @param provider The {@link ByteProvider} that contains the Mach-O's bytes.
@ -58,13 +58,13 @@ public class DyldCacheExtractProgramBuilder extends MachoProgramBuilder {
*/
public static void buildProgram(Program program, ByteProvider provider, FileBytes fileBytes,
MessageLog log, TaskMonitor monitor) throws Exception {
DyldCacheExtractProgramBuilder programBuilder = new DyldCacheExtractProgramBuilder(program,
MachoExtractProgramBuilder programBuilder = new MachoExtractProgramBuilder(program,
provider, fileBytes, log, monitor);
programBuilder.build();
}
@Override
protected void setImageBase() throws Exception {
protected void setProgramImageBase() throws Exception {
program.setImageBase(space.getAddress(0), true);
}

View file

@ -0,0 +1,115 @@
/* ###
* 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.opinion;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.formats.ios.fileset.MachoFileSetExtractor;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A {@link Loader} for Mach-O file set entries extracted by Ghidra from a Mach-O file set
*/
public class MachoFileSetExtractLoader extends MachoLoader {
public final static String MACHO_FILESET_EXTRACT_NAME = "Extracted Mach-O File Set Entry";
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
if (provider.length() >= MachoFileSetExtractor.FOOTER_V1.length) {
if (Arrays.equals(MachoFileSetExtractor.FOOTER_V1,
provider.readBytes(provider.length() - MachoFileSetExtractor.FOOTER_V1.length,
MachoFileSetExtractor.FOOTER_V1.length))) {
return super.findSupportedLoadSpecs(provider);
}
}
return List.of();
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
try {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
MachoExtractProgramBuilder.buildProgram(program, provider, fileBytes, log, monitor);
}
catch (CancelledException e) {
return;
}
catch (IOException e) {
throw e;
}
catch (Exception e) {
throw new IOException(e);
}
}
@Override
protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Program program, TaskMonitor monitor)
throws IOException, LoadException, CancelledException {
load(provider, loadSpec, options, program, monitor, messageLog);
}
@Override
public boolean supportsLoadIntoProgram(Program program) {
return MACHO_FILESET_EXTRACT_NAME.equals(program.getExecutableFormat());
}
@Override
public String getName() {
return MACHO_FILESET_EXTRACT_NAME;
}
@Override
public int getTierPriority() {
return 49; // Higher priority than MachoLoader
}
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
return List.of();
}
@Override
protected boolean isLoadLocalLibraries(List<Option> options) {
return false;
}
@Override
protected boolean isLoadSystemLibraries(List<Option> options) {
return false;
}
@Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
// Do nothing
}
}

View file

@ -0,0 +1,558 @@
/* ###
* 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;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.macho.MachHeader;
import ghidra.app.util.bin.format.macho.Section;
import ghidra.app.util.bin.format.macho.commands.*;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.TaskMonitor;
/**
* An extracted Mach-O that was once living inside of a Mach-O container file. The Mach-O layout
* is manipulated so all of its segments are adjacent in the resulting binary.
*/
public class ExtractedMacho {
protected ByteProvider provider;
protected long providerOffset;
protected byte[] footer;
protected BinaryReader reader;
protected MachHeader machoHeader;
protected SegmentCommand textSegment;
protected SegmentCommand linkEditSegment;
protected Map<SegmentCommand, Integer> packedSegmentStarts = new HashMap<>();
protected Map<SegmentCommand, Integer> packedSegmentAdjustments = new HashMap<>();
protected Map<LoadCommand, Integer> packedLinkEditDataStarts = new HashMap<>();
protected byte[] packed;
protected TaskMonitor monitor;
/**
* Creates a new {@link ExtractedMacho} object
*
* @param provider The provider with the Mach-O header
* @param providerOffset The offset of the Mach-O in the given provider
* @param machoHeader The parsed {@link MachHeader}
* @param footer A footer that gets appended to the end of every extracted component so Ghidra
* can identify them and treat them special when imported
* @param monitor {@link TaskMonitor}
* @throws IOException If there was an IO-related error
* @throws CancelledException If the user cancelled the operation
*/
public ExtractedMacho(ByteProvider provider, long providerOffset, MachHeader machoHeader,
byte[] footer, TaskMonitor monitor) throws IOException, CancelledException {
this.provider = provider;
this.providerOffset = providerOffset;
this.footer = footer;
this.machoHeader = machoHeader;
this.textSegment = machoHeader.getSegment(SegmentNames.SEG_TEXT);
this.linkEditSegment = machoHeader.getSegment(SegmentNames.SEG_LINKEDIT);
this.reader = new BinaryReader(provider, machoHeader.isLittleEndian());
this.monitor = monitor;
}
/**
* Performs the packing
*
* @throws IOException If there was an IO-related error
* @throws CancelledException If the user cancelled the operation
*/
public void pack() throws IOException, CancelledException {
// Keep track of each segment's file offset in the container.
// 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 : machoHeader.getAllSegments()) {
monitor.checkCancelled();
packedSegmentStarts.put(segment, packedSize);
// The __LINKEDIT segment is shared across all Mach-O's, so it is very large. We
// Want to create a new packed __LINKEDIT segment with only the relevant info for
// the Mach-O we are extracting, resulting in a significantly smaller file.
if (segment == linkEditSegment) {
for (LoadCommand cmd : machoHeader.getLoadCommands()) {
if (cmd instanceof SymbolTableCommand symbolTable) {
symbolTable.addSymbols(getExtraSymbols());
}
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 containers use a file offset of 0 for their __TEXT segment, despite
// being in the middle of the container 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 container file offsets.
if (segment == textSegment && segment.getFileOffset() == 0) {
segment.setFileOffset(providerOffset);
packedSegmentAdjustments.put(segment, (int) providerOffset);
}
}
// Account for the size of the footer
packedSize += footer.length;
packed = new byte[packedSize];
// Copy each segment into the packed array (leaving no gaps)
for (SegmentCommand segment : machoHeader.getAllSegments()) {
monitor.checkCancelled();
long segmentSize = segment.getFileSize();
ByteProvider segmentProvider = getSegmentProvider(segment);
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;
if (segment == linkEditSegment) {
bytes = createPackedLinkEditSegment(segmentProvider, packedLinkEditSize);
adjustLinkEditAddress();
}
else {
bytes = segmentProvider.readBytes(segment.getFileOffset(), segmentSize);
}
System.arraycopy(bytes, 0, packed, packedSegmentStarts.get(segment), bytes.length);
}
// Fixup various fields in the packed array
fixupLoadCommands();
// Add footer
System.arraycopy(footer, 0, packed, packed.length - footer.length, footer.length);
}
/**
* Gets a {@link ByteProvider} for this {@link ExtractedMacho} object
*
* @param fsrl FSRL identity of the file
* @return A {@link ByteProvider} for this {@link ExtractedMacho} object
*/
public ByteProvider getByteProvider(FSRL fsrl) {
return new ByteArrayProvider(packed, fsrl);
}
/**
* Gets the {@link ByteProvider} that contains the given {@link SegmentCommand segment}
*
* @param segment The {@link SegmentCommand segment}
* @return The {@link ByteProvider} that contains the given {@link SegmentCommand segment}
* @throws IOException If a {@link ByteProvider} could not be found
*/
protected ByteProvider getSegmentProvider(SegmentCommand segment) throws IOException {
return provider;
}
/**
* Gets a {@link List} of extra {@link NList symbol}s for the component being extracted
*
* @return A {@link List} of extra {@link NList symbol}s (could be empty)
*/
protected List<NList> getExtraSymbols() {
return List.of();
}
/**
* Converts the given Mach-O file offset to an offset into the packed Mach-O
*
* @param fileOffset The Mach-O file offset to convert
* @param segment The segment that contains the file offset; null if unknown
* @return An offset into the packed Mach-O
* @throws NotFoundException If there was no corresponding Mach-O offset
*/
protected long getPackedOffset(long fileOffset, SegmentCommand segment)
throws NotFoundException {
Integer segmentStart = packedSegmentStarts.get(segment);
if (segmentStart != null) {
return fileOffset - segment.getFileOffset() + segmentStart;
}
throw new NotFoundException(
"Failed to convert Mach-O file offset to packed offset: 0x%x".formatted(fileOffset));
}
/**
* 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
*/
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) {
List<NList> symbols = symbolTable.getSymbols();
byte[] packedSymbolStringTable = new byte[NList.getSize(symbols)];
int nlistIndex = 0;
int stringIndex = symbols.get(0).getSize() * symbols.size();
int stringIndexOrig = stringIndex;
for (NList nlist : symbols) {
byte[] nlistArray = nlistToArray(nlist, stringIndex - stringIndexOrig);
byte[] stringArray = nlist.getString().getBytes(StandardCharsets.US_ASCII);
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);
}
}
return packedLinkEdit;
}
/**
* 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(!machoHeader.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 various fields in the new packed Mach-O's load commands
*
* @throws IOException If there was an IO-related issue performing the fix-up
*/
private void fixupLoadCommands() throws IOException {
for (LoadCommand cmd : machoHeader.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 Mach-O's file offsets and size in the given segment so they are correct
* for the newly packed Mach-O
*
* @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);
set(segment.getStartIndex() + (is64bit ? 0x18 : 0x18), segment.getVMaddress(),
is64bit ? 8 : 4);
set(segment.getStartIndex() + (is64bit ? 0x20 : 0x1c), segment.getVMsize(),
is64bit ? 8 : 4);
fixup(segment.getStartIndex() + (is64bit ? 0x28 : 0x20), adjustment, is64bit ? 8 : 4,
segment);
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 Mach-O's file offsets in the given symbol table so they are correct for
* the newly packed Mach-O
*
* @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);
set(cmd.getStartIndex() + 0xc, cmd.getNumberOfSymbols(), 4);
if (cmd.getStringTableOffset() > 0) {
if (cmd.getNumberOfSymbols() > 0) {
set(cmd.getStartIndex() + 0x10,
symbolOffset + cmd.getNumberOfSymbols() * cmd.getSymbolAt(0).getSize(), 4);
set(cmd.getStartIndex() + 0x14, cmd.getStringTableSize(), 4);
}
else {
set(cmd.getStartIndex() + 0x10, symbolOffset, 4);
set(cmd.getStartIndex() + 0x14, 0, 4);
}
}
}
}
/**
* Fixes-up the old Mach-O's file offsets in the given dynamic symbol table so they are
* correct for the newly packed Mach-O.
* <p>
* 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 Mach-O's file offsets in the given DYLD Info command so they are correct
* for the newly packed Mach-O.
* <p>
* 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 Mach-O file offsets in the given link edit data command so they are
* correct for the newly packed Mach-O
*
* @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 container file offset into the __LINKEDIT
* segment to account for our new __LINKEDIT segment being packed
*
* @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 container file offset to the given value. The provided file
* offset is assumed to map to a field in a load command.
*
* @param fileOffset The Mach-O 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
*/
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 + ")");
}
try {
byte[] newBytes = toBytes(value, size);
System.arraycopy(newBytes, 0, packed, (int) getPackedOffset(fileOffset, textSegment),
newBytes.length);
}
catch (NotFoundException e) {
Msg.warn(this, e.getMessage());
}
}
/**
* Fixes up the bytes at the given container file offset to map to the correct offset in the
* packed Mach-O. The provided file offset is assumed to map to a field in a load command.
*
* @param fileOffset The container file offset to fix-up
* @param adjustment A value to add to the bytes at the given container file offset prior to
* looking them up in the packed Mach-O
* @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
* @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
*/
private long fixup(long fileOffset, long adjustment, int size, SegmentCommand segment)
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);
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;
}
/**
* We don't want our packed __LINKEDIT segment to overlap with other Mach-O's __LINKEDIT
* segments that might get extracted and added to the same program. Rather than computing
* the optimal address it should go at (which will require looking at every other Mach-O
* in the container, which is slow), just make the address very far away from the other
* Mach-O's. This should be safe for 64-bit binaries.
*/
private void adjustLinkEditAddress() {
if (machoHeader.is32bit()) {
return;
}
linkEditSegment.setVMaddress(textSegment.getVMaddress() << 4);
}
/**
* 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
*/
public static 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);
}
}

View file

@ -24,12 +24,13 @@ import com.google.common.collect.RangeSet;
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.commands.NList;
import ghidra.app.util.bin.format.macho.commands.SegmentCommand;
import ghidra.app.util.bin.format.macho.dyld.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache;
import ghidra.file.formats.ios.ExtractedMacho;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.TaskMonitor;
@ -62,7 +63,7 @@ public class DyldCacheExtractor {
* @param dylibOffset The offset of the DYLIB in the given provider
* @param splitDyldCache The {@link SplitDyldCache}
* @param index The DYLIB's {@link SplitDyldCache} index
* @param slideFixupMap A {@link Map} of {@link DyldCacheSlideFixup}s to perform
* @param slideFixupMap A {@link Map} of {@link DyldFixup}s to perform
* @param fsrl {@link FSRL} to assign to the resulting {@link ByteProvider}
* @param monitor {@link TaskMonitor}
* @return {@link ByteProvider} containing the bytes of the DYLIB
@ -71,13 +72,13 @@ public class DyldCacheExtractor {
* @throws CancelledException If the user cancelled the operation
*/
public static ByteProvider extractDylib(long dylibOffset, SplitDyldCache splitDyldCache,
int index, Map<DyldCacheSlideInfoCommon, List<DyldCacheSlideFixup>> slideFixupMap,
int index, Map<DyldCacheSlideInfoCommon, List<DyldFixup>> slideFixupMap,
FSRL fsrl, TaskMonitor monitor) throws IOException, MachException, CancelledException {
PackedSegments packedSegments =
new PackedSegments(dylibOffset, splitDyldCache, index, slideFixupMap, monitor);
return packedSegments.getByteProvider(fsrl);
ExtractedMacho extractedMacho = new DyldPackedSegments(dylibOffset, splitDyldCache, index,
FOOTER_V1, slideFixupMap, monitor);
extractedMacho.pack();
return extractedMacho.getByteProvider(fsrl);
}
/**
@ -87,7 +88,7 @@ public class DyldCacheExtractor {
* @param segmentName The name of the segment in the resulting Mach-O
* @param splitDyldCache The {@link SplitDyldCache}
* @param index The mapping's {@link SplitDyldCache} index
* @param slideFixupMap A {@link Map} of {@link DyldCacheSlideFixup}s to perform
* @param slideFixupMap A {@link Map} of {@link DyldFixup}s to perform
* @param fsrl {@link FSRL} to assign to the resulting {@link ByteProvider}
* @param monitor {@link TaskMonitor}
* @return {@link ByteProvider} containing the bytes of the mapping
@ -97,7 +98,7 @@ public class DyldCacheExtractor {
*/
public static ByteProvider extractMapping(MappingRange mappingRange, String segmentName,
SplitDyldCache splitDyldCache, int index,
Map<DyldCacheSlideInfoCommon, List<DyldCacheSlideFixup>> slideFixupMap, FSRL fsrl,
Map<DyldCacheSlideInfoCommon, List<DyldFixup>> slideFixupMap, FSRL fsrl,
TaskMonitor monitor) throws IOException, MachException, CancelledException {
int magic = MachConstants.MH_MAGIC_64;
@ -114,12 +115,12 @@ public class DyldCacheExtractor {
.findFirst()
.orElse(null);
if (slideInfo != null) {
List<DyldCacheSlideFixup> slideFixups = slideFixupMap.get(slideInfo);
List<DyldFixup> slideFixups = slideFixupMap.get(slideInfo);
monitor.initialize(slideFixups.size(), "Fixing slide pointers...");
for (DyldCacheSlideFixup fixup : slideFixups) {
for (DyldFixup fixup : slideFixups) {
monitor.increment();
long fileOffset = slideInfo.getMappingFileOffset() + fixup.offset();
byte[] newBytes = toBytes(fixup.value(), fixup.size());
byte[] newBytes = ExtractedMacho.toBytes(fixup.value(), fixup.size());
System.arraycopy(newBytes, 0, fixedProviderBytes, (int) fileOffset,
newBytes.length);
}
@ -175,20 +176,19 @@ public class DyldCacheExtractor {
}
/**
* Gets a {@link Map} of {DyldCacheSlideInfoCommon}s to their corresponding
* {@link DyldCacheSlideFixup}s
* Gets a {@link Map} of {DyldCacheSlideInfoCommon}s to their corresponding {@link DyldFixup}s
*
* @param splitDyldCache The {@link SplitDyldCache}
* @param monitor {@link TaskMonitor}
* @return A {@link Map} of {DyldCacheSlideInfoCommon}s to their corresponding
* {@link DyldCacheSlideFixup}s
* {@link DyldFixup}s
* @throws CancelledException If the user cancelled the operation
* @throws IOException If there was an IO-related issue with getting the slide fixups
*/
public static Map<DyldCacheSlideInfoCommon, List<DyldCacheSlideFixup>> getSlideFixups(
public static Map<DyldCacheSlideInfoCommon, List<DyldFixup>> getSlideFixups(
SplitDyldCache splitDyldCache, TaskMonitor monitor)
throws CancelledException, IOException {
Map<DyldCacheSlideInfoCommon, List<DyldCacheSlideFixup>> slideFixupMap = new HashMap<>();
Map<DyldCacheSlideInfoCommon, List<DyldFixup>> slideFixupMap = new HashMap<>();
MessageLog log = new MessageLog();
for (int i = 0; i < splitDyldCache.size(); i++) {
@ -200,7 +200,7 @@ public class DyldCacheExtractor {
slideInfo.getMappingFileOffset(), slideInfo.getMappingSize())) {
BinaryReader wrapperReader =
new BinaryReader(wrapper, !arch.getEndianness().isBigEndian());
List<DyldCacheSlideFixup> fixups = slideInfo.getSlideFixups(wrapperReader,
List<DyldFixup> fixups = slideInfo.getSlideFixups(wrapperReader,
arch.is64bit() ? 8 : 4, log, monitor);
slideFixupMap.put(slideInfo, fixups);
}
@ -210,541 +210,49 @@ public class DyldCacheExtractor {
return slideFixupMap;
}
/**
* 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 static 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);
}
/**
* A packed DYLIB that was once living inside of a DYLD shared cache. 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 PackedSegments {
private static class DyldPackedSegments extends ExtractedMacho {
private ByteProvider provider;
private BinaryReader reader;
private Map<DyldCacheSlideInfoCommon, List<DyldCacheSlideFixup>> slideFixupMap;
private MachHeader machoHeader;
private SegmentCommand textSegment;
private SegmentCommand linkEditSegment;
private Map<SegmentCommand, Integer> packedSegmentStarts = new HashMap<>();
private Map<SegmentCommand, Integer> packedSegmentAdjustments = new HashMap<>();
private Map<LoadCommand, Integer> packedLinkEditDataStarts = new HashMap<>();
private byte[] packed;
private TaskMonitor monitor;
private SplitDyldCache splitDyldCache;
private Map<DyldCacheSlideInfoCommon, List<DyldFixup>> slideFixupMap;
/**
* Creates a new {@link PackedSegments} object
* Creates a new {@link DyldPackedSegments} object
*
* @param dylibOffset The offset of the DYLIB in the given provider
* @param splitDyldCache The {@link SplitDyldCache}
* @param index The DYLIB's {@link SplitDyldCache} index
* @param slideFixupMap A {@link Map} of {@link DyldCacheSlideFixup}s to perform
* @param footer A footer that gets appended to the end of every extracted component so
* Ghidra can identify them and treat them special when imported
* @param slideFixupMap A {@link Map} of {@link DyldFixup}s to perform
* @param monitor {@link TaskMonitor}
* @throws MachException If there was an error parsing the DYLIB headers
* @throws IOException If there was an IO-related error
* @throws CancelledException If the user cancelled the operation
*/
public PackedSegments(long dylibOffset, SplitDyldCache splitDyldCache, int index,
Map<DyldCacheSlideInfoCommon, List<DyldCacheSlideFixup>> slideFixupMap,
public DyldPackedSegments(long dylibOffset, SplitDyldCache splitDyldCache, int index,
byte[] footer, Map<DyldCacheSlideInfoCommon, List<DyldFixup>> slideFixupMap,
TaskMonitor monitor) throws MachException, IOException, CancelledException {
this.provider = splitDyldCache.getProvider(index);
super(splitDyldCache.getProvider(index), dylibOffset,
new MachHeader(splitDyldCache.getProvider(index), dylibOffset, false)
.parse(splitDyldCache),
footer, monitor);
this.splitDyldCache = splitDyldCache;
this.slideFixupMap = slideFixupMap;
this.machoHeader = new MachHeader(provider, dylibOffset, false).parse(splitDyldCache);
this.textSegment = machoHeader.getSegment(SegmentNames.SEG_TEXT);
this.linkEditSegment = machoHeader.getSegment(SegmentNames.SEG_LINKEDIT);
this.reader = new BinaryReader(provider, machoHeader.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 : machoHeader.getAllSegments()) {
packedSegmentStarts.put(segment, packedSize);
// 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 : machoHeader.getLoadCommands()) {
if (cmd instanceof SymbolTableCommand symbolTable) {
symbolTable.addSymbols(getLocalSymbols(splitDyldCache));
}
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 == textSegment && segment.getFileOffset() == 0) {
segment.setFileOffset(dylibOffset);
packedSegmentAdjustments.put(segment, (int) dylibOffset);
}
}
// Account for the size of the footer
packedSize += FOOTER_V1.length;
packed = new byte[packedSize];
// Copy each segment into the packed array (leaving no gaps)
for (SegmentCommand segment : machoHeader.getAllSegments()) {
long segmentSize = segment.getFileSize();
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;
if (segment == linkEditSegment) {
bytes = createPackedLinkEditSegment(segmentProvider, packedLinkEditSize);
// We don't want our packed __LINKEDIT segment to overlap with other DYLIB's
// that might get extracted and added to the same program. Rather than
// computing the optimal address it should go at (which will required looking
// at every other DYLIB in the cache which is slow), just make the address very
// far away from the other DYLIB's. This should be safe for 64-bit binaries.
if (!machoHeader.is32bit()) {
segment.setVMaddress(textSegment.getVMaddress() << 4);
}
}
else {
bytes = segmentProvider.readBytes(segment.getFileOffset(), segmentSize);
}
System.arraycopy(bytes, 0, packed, packedSegmentStarts.get(segment), bytes.length);
}
// Fixup various fields in the packed array
fixupMachHeader();
fixupLoadCommands();
@Override
public void pack() throws IOException, CancelledException {
super.pack();
fixupSlidePointers();
// Add footer
System.arraycopy(FOOTER_V1, 0, packed, packed.length - FOOTER_V1.length,
FOOTER_V1.length);
}
/**
* Gets a {@link ByteProvider} for this {@link PackedSegments} object
*
* @param fsrl FSRL identity of the file
* @return A {@link ByteProvider} for this {@link PackedSegments} object
*/
public ByteProvider getByteProvider(FSRL fsrl) {
return new ByteArrayProvider(packed, fsrl);
}
/**
* Gets a {@link List} of local {@link NList symbol}s for the DYLIB being extracted
*
* @param splitDyldCache The {@link SplitDyldCache}
* @return A {@link List} of local {@link NList symbol}s (could be empty)
*/
private List<NList> getLocalSymbols(SplitDyldCache splitDyldCache) {
long base = splitDyldCache.getBaseAddress();
DyldCacheLocalSymbolsInfo info = splitDyldCache.getLocalSymbolInfo();
return info != null ? info.getNList(textSegment.getVMaddress() - base) : List.of();
}
/**
* 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
*/
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) {
List<NList> symbols = symbolTable.getSymbols();
byte[] packedSymbolStringTable = new byte[NList.getSize(symbols)];
int nlistIndex = 0;
int stringIndex = symbols.get(0).getSize() * symbols.size();
int stringIndexOrig = stringIndex;
for (NList nlist : symbols) {
byte[] nlistArray = nlistToArray(nlist, stringIndex - stringIndexOrig);
byte[] stringArray = nlist.getString().getBytes(StandardCharsets.US_ASCII);
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);
}
}
return packedLinkEdit;
}
/**
* 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(!machoHeader.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(machoHeader.getStartIndexInProvider() + 0x18,
machoHeader.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 : machoHeader.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.getVMaddress() > 0) {
set(segment.getStartIndex() + (is64bit ? 0x18 : 0x18), segment.getVMaddress(),
is64bit ? 8 : 4);
}
if (segment.getVMsize() > 0) {
set(segment.getStartIndex() + (is64bit ? 0x20 : 0x1c), segment.getVMsize(),
is64bit ? 8 : 4);
}
if (segment.getFileOffset() > 0) {
fixup(segment.getStartIndex() + (is64bit ? 0x28 : 0x20), adjustment,
is64bit ? 8 : 4, segment);
}
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);
set(cmd.getStartIndex() + 0xc, cmd.getNumberOfSymbols(), 4);
if (cmd.getStringTableOffset() > 0) {
if (cmd.getNumberOfSymbols() > 0) {
set(cmd.getStartIndex() + 0x10,
symbolOffset + cmd.getNumberOfSymbols() * cmd.getSymbolAt(0).getSize(),
4);
set(cmd.getStartIndex() + 0x14, cmd.getStringTableSize(), 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.
* <p>
* 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.
* <p>
* 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 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
*/
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 + ")");
}
try {
byte[] newBytes = toBytes(value, size);
System.arraycopy(newBytes, 0, packed,
(int) getPackedOffset(fileOffset, textSegment), newBytes.length);
}
catch (NotFoundException e) {
Msg.warn(this, e.getMessage());
}
}
/**
* 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 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 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
*/
private long fixup(long fileOffset, long adjustment, int size, SegmentCommand segment)
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);
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;
}
/**
* Converts the given DYLD file offset to an offset into the packed DYLIB
*
* @param fileOffset The DYLD file offset to convert
* @param segment The segment that contains the file offset; null if unknown
* @return An offset into the packed DYLIB
* @throws NotFoundException If there was no corresponding DYLIB offset
*/
private long getPackedOffset(long fileOffset, SegmentCommand segment)
throws NotFoundException {
Integer segmentStart = packedSegmentStarts.get(segment);
if (segmentStart != null) {
return fileOffset - segment.getFileOffset() + segmentStart;
}
throw new NotFoundException(
"Failed to convert DYLD file offset to packed DYLIB offset: " +
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 {
@Override
protected ByteProvider getSegmentProvider(SegmentCommand segment) throws IOException {
for (int i = 0; i < splitDyldCache.size(); i++) {
DyldCacheHeader dyldCacheheader = splitDyldCache.getDyldCacheHeader(i);
for (DyldCacheMappingInfo mappingInfo : dyldCacheheader.getMappingInfos()) {
@ -757,6 +265,13 @@ public class DyldCacheExtractor {
"Failed to find provider for segment: " + segment.getSegmentName());
}
@Override
protected List<NList> getExtraSymbols() {
long base = splitDyldCache.getBaseAddress();
DyldCacheLocalSymbolsInfo info = splitDyldCache.getLocalSymbolInfo();
return info != null ? info.getNList(textSegment.getVMaddress() - base) : List.of();
}
/**
* Fixes-up the slide pointers
*
@ -768,7 +283,7 @@ public class DyldCacheExtractor {
long total = slideFixupMap.values().stream().flatMap(List::stream).count();
monitor.initialize(total, "Fixing slide pointers...");
for (DyldCacheSlideInfoCommon slideInfo : slideFixupMap.keySet()) {
for (DyldCacheSlideFixup fixup : slideFixupMap.get(slideInfo)) {
for (DyldFixup fixup : slideFixupMap.get(slideInfo)) {
monitor.increment();
long addr = slideInfo.getMappingAddress() + fixup.offset();
long fileOffset = slideInfo.getMappingFileOffset() + fixup.offset();
@ -777,7 +292,7 @@ public class DyldCacheExtractor {
// Fixup is not in this Mach-O
continue;
}
byte[] newBytes = toBytes(fixup.value(), fixup.size());
byte[] newBytes = ExtractedMacho.toBytes(fixup.value(), fixup.size());
try {
System.arraycopy(newBytes, 0, packed,
(int) getPackedOffset(fileOffset, segment), newBytes.length);

View file

@ -49,7 +49,7 @@ public class DyldCacheFileSystem extends GFileSystemBase {
private SplitDyldCache splitDyldCache;
private boolean parsedLocalSymbols = false;
private Map<DyldCacheSlideInfoCommon, List<DyldCacheSlideFixup>> slideFixupMap;
private Map<DyldCacheSlideInfoCommon, List<DyldFixup>> slideFixupMap;
private Map<GFile, Long> addrMap = new HashMap<>();
private Map<GFile, Integer> indexMap = new HashMap<>();
private Map<Long, MappingRange> stubMap = new HashMap<>();

View file

@ -0,0 +1,63 @@
/* ###
* 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;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.macho.MachException;
import ghidra.app.util.bin.format.macho.MachHeader;
import ghidra.file.formats.ios.ExtractedMacho;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A class for extracting components from a {@link MachoFileSetFileSystem}
*/
public class MachoFileSetExtractor {
/**
* A footer that gets appended to the end of every extracted component so Ghidra can identify
* them and treat them special when imported
*/
public static final byte[] FOOTER_V1 =
"Ghidra Mach-O file set extraction v1".getBytes(StandardCharsets.US_ASCII);
/**
* Gets a {@link ByteProvider} that contains a Mach-O file set entry. The Mach-O's header will
* be altered to account for its segment bytes being packed down.
*
* @param provider The Mach-O file set provider
* @param providerOffset The offset of the Mach-O file set entry in the given provider
* @param fsrl {@link FSRL} to assign to the resulting {@link ByteProvider}
* @param monitor {@link TaskMonitor}
* @return {@link ByteProvider} containing the bytes of the extracted Mach-O file set entry
* @throws MachException If there was an error parsing the Mach-O file set header
* @throws IOException If there was an IO-related issue with extracting the Mach-O file set
* entry
* @throws CancelledException If the user cancelled the operation
*/
public static ByteProvider extractFileSetEntry(ByteProvider provider, long providerOffset,
FSRL fsrl, TaskMonitor monitor) throws IOException, MachException, CancelledException {
ExtractedMacho extractedMacho = new ExtractedMacho(provider, providerOffset,
new MachHeader(provider, providerOffset, false).parse(), FOOTER_V1, monitor);
extractedMacho.pack();
return extractedMacho.getByteProvider(fsrl);
}
}

View file

@ -0,0 +1,170 @@
/* ###
* 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;
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.macho.MachException;
import ghidra.app.util.bin.format.macho.MachHeader;
import ghidra.app.util.bin.format.macho.commands.*;
import ghidra.app.util.bin.format.macho.dyld.DyldFixup;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.formats.ios.ExtractedMacho;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
import ghidra.util.exception.CancelledException;
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 static final String MACHO_FILESET_FSTYPE = "machofileset";
private ByteProvider provider;
private ByteProvider fixedUpProvider;
private Map<FileSetEntryCommand, List<SegmentCommand>> entrySegmentMap;
/**
* Creates a new {@link MachoFileSetFileSystem}
*
* @param fsFSRL {@link FSRLRoot} of this file system
* @param provider The {@link ByteProvider} that contains the file system
*/
public MachoFileSetFileSystem(FSRLRoot fsFSRL, ByteProvider provider) {
super(fsFSRL, FileSystemService.getInstance());
this.provider = provider;
this.entrySegmentMap = new HashMap<>();
}
/**
* Mounts this file system
*
* @param monitor {@link TaskMonitor}
* @throws IOException If there was an issue mounting the file system
* @throws CancelledException If the user cancelled the operation
*/
public void mount(TaskMonitor monitor) throws IOException, CancelledException {
MessageLog log = new MessageLog();
try {
monitor.setMessage("Opening Mach-O file set...");
MachHeader header = new MachHeader(provider).parse();
SegmentCommand textSegment = header.getSegment(SegmentNames.SEG_TEXT);
if (textSegment == null) {
throw new MachException(SegmentNames.SEG_TEXT + " not found!");
}
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());
}
monitor.setMessage("Getting chained pointers...");
BinaryReader reader = new BinaryReader(provider, header.isLittleEndian());
List<DyldFixup> fixups = new ArrayList<>();
long imagebase = textSegment.getVMaddress();
for (DyldChainedFixupsCommand loadCommand : header
.getLoadCommands(DyldChainedFixupsCommand.class)) {
fixups.addAll(loadCommand.getChainedFixups(reader, imagebase, null, log,
monitor));
}
monitor.initialize(fixups.size(), "Fixing chained pointers...");
byte[] bytes = provider.readBytes(0, provider.length());
for (DyldFixup fixup : fixups) {
byte[] newBytes = ExtractedMacho.toBytes(fixup.value(), fixup.size());
System.arraycopy(newBytes, 0, bytes, (int) fixup.offset(), newBytes.length);
}
fixedUpProvider = new ByteArrayProvider(bytes);
}
catch (MachException e) {
throw new IOException(e);
}
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
throws IOException, CancelledException {
FileSetEntryCommand cmd = fsIndex.getMetadata(file);
if (cmd == null) {
return null;
}
try {
return MachoFileSetExtractor.extractFileSetEntry(fixedUpProvider, cmd.getFileOffset(),
file.getFSRL(), monitor);
}
catch (MachException e) {
throw new IOException(
"Invalid Mach-O header detected at 0x%x".formatted(cmd.getFileOffset()));
}
}
@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());
}
return result;
}
/**
* Gets the open Mach-O file set {@link ByteProvider}. This is the original
* {@link ByteProvider} that this file system opened.
*
* @return The opened Mach-O file set {@link ByteProvider}, or null if it has is not open
*/
public ByteProvider getMachoFileSetProvider() {
return provider;
}
/**
* {@return the map of file set entry segments}
*/
public Map<FileSetEntryCommand, List<SegmentCommand>> getEntrySegmentMap() {
return entrySegmentMap;
}
@Override
public boolean isClosed() {
return provider == null;
}
@Override
public void close() throws IOException {
refManager.onClose();
if (provider != null) {
provider.close();
provider = null;
}
if (fixedUpProvider != null) {
fixedUpProvider.close();
fixedUpProvider = null;
}
fsIndex.clear();
entrySegmentMap.clear();
}
}

View file

@ -0,0 +1,57 @@
/* ###
* 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;
import java.io.IOException;
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.commands.LoadCommandTypes;
import ghidra.formats.gfilesystem.FSRLRoot;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider;
import ghidra.formats.gfilesystem.factory.GFileSystemProbeByteProvider;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Factory to identify and create instances of a {@link MachoFileSetFileSystem}
*/
public class MachoFileSetFileSystemFactory implements
GFileSystemFactoryByteProvider<MachoFileSetFileSystem>, GFileSystemProbeByteProvider {
@Override
public MachoFileSetFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider,
FileSystemService fsService, TaskMonitor monitor)
throws IOException, CancelledException {
MachoFileSetFileSystem fs = new MachoFileSetFileSystem(targetFSRL, byteProvider);
fs.mount(monitor);
return fs;
}
@Override
public boolean probe(ByteProvider byteProvider, FileSystemService fsService,
TaskMonitor monitor) throws IOException, CancelledException {
try {
return new MachHeader(byteProvider).parseAndCheck(LoadCommandTypes.LC_FILESET_ENTRY);
}
catch (MachException e) {
// Assume it's not a Mach-O...fall through
}
return false;
}
}

View file

@ -75,7 +75,7 @@ public class MachoPrelinkFileSystem extends GFileSystemBase implements GFileSyst
@Override
public boolean isValid(TaskMonitor monitor) throws IOException {
return MachoPrelinkUtils.isMachoPrelink(provider, monitor);
return MachoPrelinkUtils.isMachoPrelink(provider, false, monitor);
}
@Override

View file

@ -31,11 +31,9 @@ import ghidra.app.util.opinion.DyldCacheExtractLoader;
import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache;
import ghidra.file.formats.ios.dyldcache.DyldCacheFileSystem;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.plugin.importer.ImporterUtilities;
import ghidra.plugin.importer.ProgramMappingService;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
@ -148,20 +146,17 @@ public class DyldCacheBuilderPlugin extends Plugin {
*/
private FileSystemRef openDyldCache(Program program, TaskMonitor monitor)
throws IOException, CancelledException {
FileSystemService fsService = FileSystemService.getInstance();
Options props = program.getOptions(Program.PROGRAM_INFO);
String fsrlProp = props.getString(ProgramMappingService.PROGRAM_SOURCE_FSRL, null);
if (fsrlProp == null) {
FSRL fsrl = FSRL.fromProgram(program);
if (fsrl == null) {
throw new IOException("The program does not have an FSRL property");
}
FSRL fsrl = FSRL.fromString(fsrlProp);
String requiredProtocol = DyldCacheFileSystem.DYLD_CACHE_FSTYPE;
if (!fsrl.getFS().getProtocol().equals(requiredProtocol)) {
throw new IOException("The program's FSRL protocol is '%s' but '%s' is required"
.formatted(fsrl.getFS().getProtocol(), requiredProtocol));
}
FSRLRoot fsrlRoot = fsrl.getFS();
return fsService.getFilesystem(fsrlRoot, monitor);
return FileSystemService.getInstance().getFilesystem(fsrlRoot, monitor);
}
/**

View file

@ -0,0 +1,161 @@
/* ###
* 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.macosx.plugins;
import java.io.IOException;
import java.util.List;
import java.util.Map;
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.MachoFileSetFileSystem;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.plugin.importer.ImporterUtilities;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
/**
* A {@link Plugin} that adds an action to build up a Mach-O file set from extracted components
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.COMMON,
shortDescription = "Mach-O LC_FILESET_ENTRY Builder",
description = "This plugin provides actions for adding Mach-O file set entries to the program"
)
//@formatter:on
public class MachoFileSetBuilderPlugin extends Plugin {
/**
* Creates a new {@link MachoFileSetBuilderPlugin}
*
* @param tool The {@link PluginTool} that will host/contain this {@link Plugin}
*/
public MachoFileSetBuilderPlugin(PluginTool tool) {
super(tool);
}
@Override
protected void init() {
super.init();
String actionName = "Add To Program";
new ActionBuilder(actionName, getName()).withContext(ProgramLocationActionContext.class)
.enabledWhen(p -> p.getProgram()
.getExecutableFormat()
.equals(MachoFileSetExtractLoader.MACHO_FILESET_EXTRACT_NAME))
.onAction(plac -> TaskLauncher.launchModal(actionName,
monitor -> addMissingMachoFileSetEntry(plac.getLocation(), monitor)))
.popupMenuPath("References", actionName)
.popupMenuGroup("Add")
.helpLocation(new HelpLocation("ImporterPlugin", "Add_To_Program"))
.buildAndInstall(tool);
}
/**
* Attempts to add the Mach-O file set entry that resides at the given {@link ProgramLocation}'s
* "referred to" address
*
* @param location The {@link ProgramLocation} where the action took place
* @param monitor A {@link TaskMonitor}
*/
private void addMissingMachoFileSetEntry(ProgramLocation location, TaskMonitor monitor) {
Program program = location.getProgram();
Address refAddress = location.getRefAddress();
if (refAddress == null) {
Msg.showInfo(this, null, name, "No referenced address selected");
return;
}
if (refAddress.getAddressSpace().isExternalSpace()) {
Msg.showInfo(this, null, name, "External locations are not currently supported");
return;
}
if (program.getMemory().contains(refAddress)) {
Msg.showInfo(this, null, name, "Referenced address already exists in memory");
return;
}
long refAddr = refAddress.getOffset();
try (FileSystemRef fsRef = openMachoFileSet(program, monitor)) {
MachoFileSetFileSystem fs = (MachoFileSetFileSystem) fsRef.getFilesystem();
Map<FileSetEntryCommand, List<SegmentCommand>> entrySegmentMap =
fs.getEntrySegmentMap();
String fsPath = null;
for (FileSetEntryCommand cmd : entrySegmentMap.keySet()) {
for (SegmentCommand segment : entrySegmentMap.get(cmd)) {
if (segment.contains(refAddr)) {
fsPath = cmd.getFileSetEntryId().getString();
break;
}
}
}
if (fsPath != null) {
ImporterUtilities.showAddToProgramDialog(fs.getFSRL().appendPath(fsPath), program,
tool, monitor);
}
else {
Msg.showInfo(this, null, name,
"Address %s not found in %s".formatted(refAddress, fs.toString()));
}
}
catch (CancelledException e) {
// Do nothing
}
catch (IOException e) {
Msg.showError(this, null, name, e.getMessage(), e);
}
}
/**
* Attempts to open the given {@link Program}'s originating {@link MachoFileSetFileSystem}
*
* @param program The {@link Program}
* @param monitor A {@link TaskMonitor}
* @return A {@link FileSystemRef file system reference} to the open
* {@link MachoFileSetFileSystem}
* @throws IOException if an FSRL or IO-related error occurred
* @throws CancelledException if the user cancelled the operation
*/
private FileSystemRef openMachoFileSet(Program program, TaskMonitor monitor)
throws IOException, CancelledException {
FSRL fsrl = FSRL.fromProgram(program);
if (fsrl == null) {
throw new IOException("The program does not have an FSRL property");
}
String requiredProtocol = MachoFileSetFileSystem.MACHO_FILESET_FSTYPE;
if (!fsrl.getFS().getProtocol().equals(requiredProtocol)) {
throw new IOException("The program's FSRL protocol is '%s' but '%s' is required"
.formatted(fsrl.getFS().getProtocol(), requiredProtocol));
}
FSRLRoot fsrlRoot = fsrl.getFS();
return FileSystemService.getInstance().getFilesystem(fsrlRoot, monitor);
}
}