mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-25 03:56:41 +00:00
Merge remote-tracking branch
'origin/GP-3770_ryanmkurtz_fileset-fs--SQUASHED' (Closes #4827)
This commit is contained in:
commit
f13025d426
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue