GP-5429: Support for Mach-O LC_REEXPORT_DYLIB

This commit is contained in:
Ryan Kurtz 2025-02-27 13:49:43 -05:00
parent 136a944796
commit 17910774cd
10 changed files with 514 additions and 189 deletions

View file

@ -217,6 +217,30 @@ public class MachHeader implements StructConverter {
return segments;
}
/**
* Parses only this {@link MachHeader}'s {@link DynamicLibraryCommand reexport load commands}
*
* @return A {@link List} of this {@link MachHeader}'s
* {@link DynamicLibraryCommand reexport load commands}
* @throws IOException If there was an IO-related error
*/
public List<DynamicLibraryCommand> parseReexports() throws IOException {
List<DynamicLibraryCommand> cmds = new ArrayList<>();
_reader.setPointerIndex(_commandIndex);
for (int i = 0; i < nCmds; ++i) {
int type = _reader.peekNextInt();
if (type == LoadCommandTypes.LC_REEXPORT_DYLIB) {
DynamicLibraryCommand cmd = new DynamicLibraryCommand(_reader);
cmds.add(cmd);
_reader.setPointerIndex(cmd.getStartIndex());
}
type = _reader.readNextInt();
long size = _reader.readNextUnsignedInt();
_reader.setPointerIndex(_reader.getPointerIndex() + size - 8);
}
return cmds;
}
/**
* Parses only this {@link MachHeader}'s {@link LoadCommand}s to check to see if one of the
* given type exists

View file

@ -34,7 +34,7 @@ import ghidra.util.task.TaskMonitor;
public class DynamicLibraryCommand extends LoadCommand {
private DynamicLibrary dylib;
DynamicLibraryCommand(BinaryReader reader) throws IOException {
public DynamicLibraryCommand(BinaryReader reader) throws IOException {
super(reader);
dylib = new DynamicLibrary(reader, this);
}

View file

@ -122,6 +122,7 @@ public class ExportTrie {
if ((flags & EXPORT_SYMBOL_FLAGS_REEXPORT) != 0) {
ulebOffsets.add(reader.getPointerIndex() - base);
other = reader.readNext(LEB128::unsigned); // dylib ordinal
stringOffsets.add(reader.getPointerIndex() - base);
importName = reader.readNextAsciiString();
}
else {

View file

@ -20,6 +20,7 @@ import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -154,28 +155,47 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
log.appendMsg("--------------------------------------------------------------------\n");
}
/**
* {@inheritDoc}
* <p>
* Fix up program's external library entries so that they point to a path in the project.
*/
@Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
if (loadedPrograms.isEmpty()) {
if (loadedPrograms.isEmpty() ||
(!isLinkExistingLibraries(options) && !isLoadLibraries(options))) {
return;
}
if (isLinkExistingLibraries(options) || isLoadLibraries(options)) {
String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
List<DomainFolder> searchFolders = new ArrayList<>();
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder destSearchFolder =
getLibraryDestinationSearchFolder(project, destPath, options);
DomainFolder linkSearchFolder =
getLinkSearchFolder(project, projectFolderPath, options);
if (destSearchFolder != null) {
searchFolders.add(destSearchFolder);
List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options);
List<Loaded<Program>> saveablePrograms =
loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList();
monitor.initialize(saveablePrograms.size());
for (Loaded<Program> loadedProgram : saveablePrograms) {
monitor.increment();
Program program = loadedProgram.getDomainObject();
ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames();
if (extLibNames.length == 0 ||
(extLibNames.length == 1 && Library.UNKNOWN.equals(extLibNames[0]))) {
continue; // skip program if no libraries defined
}
if (linkSearchFolder != null) {
searchFolders.add(linkSearchFolder);
monitor.setMessage("Resolving..." + program.getName());
int id = program.startTransaction("Resolving external references");
try {
resolveExternalLibraries(program, saveablePrograms, searchFolders, monitor,
messageLog);
}
finally {
program.endTransaction(id, true);
}
fixupExternalLibraries(loadedPrograms, searchFolders, messageLog, monitor);
}
}
@ -262,15 +282,16 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* Gets the {@link DomainFolder project folder} to search for existing libraries
*
* @param project The {@link Project}. Could be null if there is no project.
* @param program The {@link Program} being loaded
* @param projectFolderPath The project folder path the program will get saved to. Could be null
* if the program is not getting saved to the project.
* @param options a {@link List} of {@link Option}s
* @return The path of the project folder to search for existing libraries, or null if no
* project folders can be or should be searched
*/
protected DomainFolder getLinkSearchFolder(Project project, String projectFolderPath,
List<Option> options) {
if (!shouldSearchAllPaths(options) && !isLinkExistingLibraries(options)) {
protected DomainFolder getLinkSearchFolder(Project project, Program program,
String projectFolderPath, List<Option> options) {
if (!shouldSearchAllPaths(program, options) && !isLinkExistingLibraries(options)) {
return null;
}
if (project == null) {
@ -373,14 +394,39 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return project.getProjectData().getFolder(libraryDestinationFolderPath);
}
/**
* Gets a {@link List} of library search {@link DomainFolder folders} based on the current
* options
*
* @param loadedPrograms the list of {@link Loaded} {@link Program}s
* @param project The {@link Project} to load into. Could be null if there is no project.
* @param options The {@link List} of {@link Option}s
* @return A {@link List} of library search {@link DomainFolder folders} based on the current
* options
*/
protected List<DomainFolder> getLibrarySearchFolders(List<Loaded<Program>> loadedPrograms,
Project project, List<Option> options) {
List<DomainFolder> searchFolders = new ArrayList<>();
String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder destSearchFolder =
getLibraryDestinationSearchFolder(project, destPath, options);
DomainFolder linkSearchFolder = getLinkSearchFolder(project,
loadedPrograms.getFirst().getDomainObject(), projectFolderPath, options);
Optional.ofNullable(destSearchFolder).ifPresent(searchFolders::add);
Optional.ofNullable(linkSearchFolder).ifPresent(searchFolders::add);
return searchFolders;
}
/**
* Checks whether or not to search for libraries using all possible search paths, regardless
* of what options are set
*
* @param program The {@link Program} being loaded
* @param options a {@link List} of {@link Option}s
* @return True if all possible search paths should be used, regardless of what options are set
*/
protected boolean shouldSearchAllPaths(List<Option> options) {
protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
return false;
}
@ -433,6 +479,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param libraryName The name of the library
* @param libraryFsrl The library {@link FSRL}
* @param provider The library bytes
* @param unprocessed The {@link Queue} of {@link UnprocessedLibrary unprocessed libraries}
* @param depth The load depth of the library to load
* @param loadSpec The {@link LoadSpec} used for the load
* @param options The options
* @param log The log
@ -441,8 +489,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws CancelledException If the user cancelled the action
*/
protected void processLibrary(Program library, String libraryName, FSRL libraryFsrl,
ByteProvider provider, LoadSpec loadSpec, List<Option> options, MessageLog log,
TaskMonitor monitor) throws IOException, CancelledException {
ByteProvider provider, Queue<UnprocessedLibrary> unprocessed, int depth,
LoadSpec loadSpec, List<Option> options, MessageLog log, TaskMonitor monitor)
throws IOException, CancelledException {
// Default behavior is to do nothing
}
@ -478,7 +527,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
getCustomLibrarySearchPaths(provider, options, log, monitor);
List<FileSystemSearchPath> searchPaths =
getLibrarySearchPaths(provider, program, options, log, monitor);
DomainFolder linkSearchFolder = getLinkSearchFolder(project, projectFolderPath, options);
DomainFolder linkSearchFolder =
getLinkSearchFolder(project, program, projectFolderPath, options);
String libraryDestFolderPath =
getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder libraryDestFolder =
@ -490,6 +540,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
monitor.checkCancelled();
UnprocessedLibrary unprocessedLibrary = unprocessed.remove();
String libraryName = unprocessedLibrary.name();
boolean discard = unprocessedLibrary.discard();
int depth = unprocessedLibrary.depth();
if (depth == 0 || processed.contains(libraryName)) {
continue;
@ -519,7 +570,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
provider, customSearchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor);
if (loadedLibrary != null) {
loaded = true;
loaded = loadLibraries && !discard;
loadedLibrary.setDiscard(!loaded);
loadedPrograms.add(loadedLibrary);
}
}
@ -530,16 +582,11 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
provider, searchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor);
if (loadedLibrary != null) {
if (loadLibraries) {
loaded = true;
loaded = loadLibraries && !discard;
loadedLibrary.setDiscard(!loaded);
loadedPrograms.add(loadedLibrary);
}
else {
loadedLibrary.release(consumer);
}
}
}
else {
if (loaded) {
log.appendMsg("Saving library to: " +
loadedPrograms.get(loadedPrograms.size() - 1).toString());
@ -547,7 +594,6 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
else {
log.appendMsg("Library not saved to project.");
}
}
log.appendMsg("------------------------------------------------\n");
}
}
@ -624,13 +670,13 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
library = loadLibrary(simpleLibraryName, candidateLibraryFsrl,
desiredLoadSpec, newLibraryList, options, consumer, log, monitor);
for (String newLibraryName : newLibraryList) {
unprocessed.add(new UnprocessedLibrary(newLibraryName, depth - 1));
unprocessed.add(new UnprocessedLibrary(newLibraryName, depth - 1, false));
}
if (library == null) {
continue;
}
processLibrary(library, libraryName, candidateLibraryFsrl, provider,
desiredLoadSpec, options, log, monitor);
processLibrary(library, libraryName, candidateLibraryFsrl, provider, unprocessed,
depth, desiredLoadSpec, options, log, monitor);
success = true;
return new Loaded<Program>(library, simpleLibraryName, libraryDestFolderPath);
}
@ -664,7 +710,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* If null this method will return null.
* @return The found {@link DomainFile} or null if not found
*/
private DomainFile findLibrary(String libraryPath, DomainFolder folder) {
protected DomainFile findLibrary(String libraryPath, DomainFolder folder) {
if (folder == null) {
return null;
}
@ -897,66 +943,6 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return libraryNames;
}
/**
* For each {@link Loaded} {@link Program} in the given list, fix up its external library
* entries so that they point to a path in the project.
* <p>
* Other {@link Program}s in the given list are matched first, then the given
* {@link DomainFolder search folder} is searched for matches.
*
* @param loadedPrograms the list of {@link Loaded} {@link Program}s
* @param searchFolders an ordered list of {@link DomainFolder}s which imported libraries will
* be searched. These folders will be searched if a library is not found within the list of
* programs supplied.
* @param messageLog log for messages.
* @param monitor the task monitor
* @throws IOException if there was an IO-related problem resolving.
* @throws CancelledException if the user cancelled the load.
*/
private void fixupExternalLibraries(List<Loaded<Program>> loadedPrograms,
List<DomainFolder> searchFolders, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
monitor.initialize(loadedPrograms.size());
for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.increment();
Program program = loadedProgram.getDomainObject();
ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames();
if (extLibNames.length == 0 ||
(extLibNames.length == 1 && Library.UNKNOWN.equals(extLibNames[0]))) {
continue; // skip program if no libraries defined
}
monitor.setMessage("Resolving..." + program.getName());
int id = program.startTransaction("Resolving external references");
try {
resolveExternalLibraries(program, loadedPrograms, searchFolders, monitor,
messageLog);
}
finally {
program.endTransaction(id, true);
}
}
}
/**
* Fix up program's external library entries so that they point to a path in the project.
* <p>
* Other programs in the map are matched first, then the ghidraLibSearchFolders
* are searched for matches.
*
* @param program the program whose Library entries are to be resolved. An open
* transaction on program is required.
* @param loadedPrograms the list of {@link Loaded} {@link Program}s
* @param searchFolders an order list of {@link DomainFolder}s which imported libraries will be
* searched. These folders will be searched if a library is not found within the list of
* programs supplied.
* @param messageLog log for messages.
* @param monitor the task monitor
* @throws CancelledException if the user cancelled the load.
*/
private void resolveExternalLibraries(Program program,
List<Loaded<Program>> loadedPrograms, List<DomainFolder> searchFolders,
TaskMonitor monitor, MessageLog messageLog) throws CancelledException {
@ -1009,8 +995,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param name The name of the library
* @param depth The recursive load depth of the library (based on the original binary being
* loaded)
* @param discard True if the library should be discarded (not saved) after processing
*/
private record UnprocessedLibrary(String name, int depth) {/**/}
protected record UnprocessedLibrary(String name, int depth, boolean discard) {/**/}
/**
* Creates a new {@link Queue} of {@link UnprocessedLibrary}s, initialized filled with the
@ -1022,7 +1009,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*/
private Queue<UnprocessedLibrary> createUnprocessedQueue(List<String> libraryNames, int depth) {
return libraryNames.stream()
.map(name -> new UnprocessedLibrary(name, depth))
.map(name -> new UnprocessedLibrary(name, depth, false))
.collect(Collectors.toCollection(LinkedList::new));
}
@ -1067,7 +1054,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*/
private List<FileSystemSearchPath> getLibrarySearchPaths(ByteProvider provider, Program program,
List<Option> options, MessageLog log, TaskMonitor monitor) throws CancelledException {
if (!isLoadLibraries(options) && !shouldSearchAllPaths(options)) {
if (!isLoadLibraries(options) && !shouldSearchAllPaths(program, options)) {
return List.of();
}
@ -1119,7 +1106,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* be a simple filename or an absolute path.
* @return The found {@link Loaded} {@link Program} or null if not found
*/
private Loaded<Program> findLibrary(List<Loaded<Program>> loadedPrograms, String libraryName) {
protected Loaded<Program> findLibrary(List<Loaded<Program>> loadedPrograms,
String libraryName) {
Comparator<String> comparator = getLibraryNameComparator();
boolean noExtension = FilenameUtils.getExtension(libraryName).equals("");
boolean absolute = libraryName.startsWith("/");

View file

@ -17,8 +17,8 @@ package ghidra.app.util.opinion;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import java.util.function.Predicate;
import generic.jar.ResourceFile;
import ghidra.app.util.Option;
@ -71,13 +71,14 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
@Override
protected boolean shouldSearchAllPaths(List<Option> options) {
protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
return shouldPerformOrdinalLookup(options);
}
@Override
protected void processLibrary(Program lib, String libName, FSRL libFsrl, ByteProvider provider,
LoadSpec loadSpec, List<Option> options, MessageLog log, TaskMonitor monitor)
Queue<UnprocessedLibrary> unprocessed, int depth, LoadSpec loadSpec,
List<Option> options, MessageLog log, TaskMonitor monitor)
throws IOException, CancelledException {
int size = loadSpec.getLanguageCompilerSpec().getLanguageDescription().getSize();
ResourceFile existingExportsFile = LibraryLookupTable.getExistingExportsFile(libName, size);
@ -128,10 +129,12 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
monitor.initialize(loadedPrograms.size());
if (shouldPerformOrdinalLookup(options)) {
for (Loaded<Program> loadedProgram : loadedPrograms) {
List<Loaded<Program>> saveablePrograms =
loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList();
monitor.initialize(saveablePrograms.size());
for (Loaded<Program> loadedProgram : saveablePrograms) {
monitor.checkCancelled();
Program program = loadedProgram.getDomainObject();
int id = program.startTransaction("Ordinal fixups");

View file

@ -17,8 +17,7 @@ package ghidra.app.util.opinion;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import ghidra.app.plugin.processors.generic.MemoryBlockDefinition;
import ghidra.app.util.Option;
@ -141,6 +140,16 @@ public abstract class AbstractProgramLoader implements Loader {
// Subclasses can perform custom post-load fix-ups
postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
// Discard unneeded programs
Iterator<Loaded<Program>> iter = loadedPrograms.iterator();
while (iter.hasNext()) {
Loaded<Program> loaded = iter.next();
if (loaded.shouldDiscard()) {
iter.remove();
loaded.release(consumer);
}
}
success = true;
return new LoadResults<Program>(loadedPrograms);
}
@ -513,7 +522,7 @@ public abstract class AbstractProgramLoader implements Loader {
Namespace namespace = program.getGlobalNamespace();
s = symTable.createLabel(addr, labelname, namespace, SourceType.IMPORTED);
if (comment != null) {
program.getListing().setComment(address, CodeUnit.EOL_COMMENT, comment);
program.getListing().setComment(address, CommentType.EOL, comment);
}
if (isEntry) {
symTable.addExternalEntryPoint(addr);

View file

@ -28,6 +28,9 @@ import ghidra.util.task.TaskMonitor;
* A loaded {@link DomainObject} produced by a {@link Loader}. In addition to storing the loaded
* {@link DomainObject}, it also stores the {@link Loader}'s desired name and project folder path
* for the loaded {@link DomainObject}, should it get saved to a project.
* <p>
* NOTE: If an object of this type is marked as {@link #setDiscard(boolean) discardable}, it should
* be {@link #release(Object) released} and not saved.
*
* @param <T> The type of {@link DomainObject} that was loaded
*/
@ -39,6 +42,7 @@ public class Loaded<T extends DomainObject> {
private DomainFile domainFile;
private boolean ignoreSave;
private boolean discard;
/**
* Creates a new {@link Loaded} object
@ -213,6 +217,27 @@ public class Loaded<T extends DomainObject> {
return domainFile;
}
/**
* Checks to see if this {@link Loaded} {@link DomainObject} should be discarded (not saved)
*
* @return True if this {@link Loaded} {@link DomainObject} should be discarded; otherwise,
* false
*/
public boolean shouldDiscard() {
return discard;
}
/**
* Sets whether or not this {@link Loaded} {@link DomainObject} should be discarded (not saved)
*
* @param discard True if this {@link Loaded} {@link DomainObject} should be discarded;
* otherwise, false
*/
public void setDiscard(boolean discard) {
this.discard = discard;
}
/**
* Deletes the loaded {@link DomainObject}'s associated {@link DomainFile} that was
* {@link #save(Project, MessageLog, TaskMonitor) saved}. This method has no effect if it was

View file

@ -19,22 +19,28 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.ByteProviderWrapper;
import ghidra.app.util.*;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.golang.GoConstants;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.macho.*;
import ghidra.app.util.bin.format.macho.commands.*;
import ghidra.app.util.bin.format.swift.SwiftUtils;
import ghidra.app.util.bin.format.ubi.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.*;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.LittleEndianDataConverter;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import util.CollectionUtils;
/**
* A {@link Loader} for Mach-O files.
@ -44,6 +50,9 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
public final static String MACH_O_NAME = "Mac OS X Mach-O";
private static final long MIN_BYTE_LENGTH = 4;
public static final String REEXPORT_OPTION_NAME = "Perform Reexports";
static final boolean REEXPORT_OPTION_DEFAULT = true;
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
List<LoadSpec> loadSpecs = new ArrayList<>();
@ -121,6 +130,34 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
}
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
if (!loadIntoProgram) {
list.add(new Option(REEXPORT_OPTION_NAME, REEXPORT_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-reexport"));
}
return list;
}
@Override
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program) {
if (options != null) {
for (Option option : options) {
String name = option.getName();
if (name.equals(REEXPORT_OPTION_NAME)) {
if (!Boolean.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
}
}
}
return super.validateOptions(provider, loadSpec, options, program);
}
@Override
public String getName() {
return MACH_O_NAME;
@ -217,4 +254,191 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
return null;
}
/**
* Checks to see if reexports should be performed
*
* @param options a {@link List} of {@link Option}s
* @return True if reexports should be performed; otherwise, false
*/
private boolean shouldPerformReexports(List<Option> options) {
return OptionUtils.getOption(REEXPORT_OPTION_NAME, options, REEXPORT_OPTION_DEFAULT);
}
/**
* {@inheritDoc}
* <p>
* If we aren't loading libraries, we still want to search all paths if the reexport option is
* set and the Mach-O actually has {@code LC_REEXPORT_DYLIB} entries.
*/
@Override
protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
if (super.shouldSearchAllPaths(program, options)) {
return true;
}
if (shouldPerformReexports(options)) {
try {
ByteProvider provider = new MemoryByteProvider(program.getMemory(),
program.getImageBase());
if (new MachHeader(provider).parseAndCheck(LoadCommandTypes.LC_REEXPORT_DYLIB)) {
return true;
}
}
catch (IOException | MachException e) {
Msg.error(this, "Failed to parse Mach-O header for: " + program.getName());
}
}
return false;
}
/**
* {@inheritDoc}
* <p>
* The goal here is to add each reexported library to the {@code unprocessed} list at the
* current {@code depth} to be sure they get loaded. However, if the current depth is 1, we
* need to marked them as "discard" so we know not to save them in the end (since their actual
* depth would have prevented their save as a normal library)
*/
@Override
protected void processLibrary(Program lib, String libName, FSRL libFsrl, ByteProvider provider,
Queue<UnprocessedLibrary> unprocessed, int depth, LoadSpec loadSpec,
List<Option> options, MessageLog log, TaskMonitor monitor)
throws IOException, CancelledException {
if (!shouldPerformReexports(options)) {
return;
}
try {
for (String path : getReexportPaths(lib)) {
unprocessed.add(new UnprocessedLibrary(path, depth, depth == 1));
}
}
catch (MachException e) {
throw new IOException(e);
}
}
/**
* Gets a {@link List} of reexport library paths from the given {@link Program}
*
* @param program The {@link Program}
* @return A {@link List} of reexport library paths from the given {@link Program}
* @throws MachException if there was a problem parsing the Mach-O {@link Program}
* @throws IOException if there was an IO-related error
*/
private List<String> getReexportPaths(Program program) throws MachException, IOException {
ByteProvider p = new MemoryByteProvider(program.getMemory(), program.getImageBase());
return new MachHeader(p).parseReexports()
.stream()
.map(DynamicLibraryCommand::getDynamicLibrary)
.map(DynamicLibrary::getName)
.map(LoadCommandString::getString)
.toList();
}
/**
* {@inheritDoc}
* <p>
* Adds reexported symbols to each {@link Loaded} {@link Program}.
*/
@Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
if (shouldPerformReexports(options)) {
List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options);
monitor.initialize(loadedPrograms.size());
for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.increment();
Program program = loadedProgram.getDomainObject();
int id = program.startTransaction("Reexporting");
try {
reexport(program, loadedPrograms, searchFolders, monitor, messageLog);
}
catch (Exception e) {
messageLog.appendException(e);
}
finally {
program.endTransaction(id, true);
}
}
}
super.postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
}
/**
* "Reexports" symbols from to a {@link Program}
*
* @param program The {@link Program} to receive the reexports
* @param loadedPrograms A {@link List} of {@link Loaded} {@link Program}s to find get the
* reexportable symbols from
* @param searchFolders A {@link List} of project folders that may contain already-loaded
* {@link Program}s with reexportable symbols
* @param monitor A cancelable task monitor
* @param messageLog The log
* @throws CancelledException if the user cancelled the load operation
* @throws IOException if there was an IO-related error during the load
*/
private void reexport(Program program, List<Loaded<Program>> loadedPrograms,
List<DomainFolder> searchFolders, TaskMonitor monitor, MessageLog messageLog)
throws CancelledException, Exception {
for (String path : getReexportPaths(program)) {
Program programToRelease = null;
try {
Loaded<Program> match = findLibrary(loadedPrograms, path);
Program lib = null;
if (match != null) {
lib = match.getDomainObject();
}
if (lib == null) {
for (DomainFolder searchFolder : searchFolders) {
DomainFile df = findLibrary(path, searchFolder);
if (df != null) {
DomainObject obj = df.getDomainObject(this, true, true, monitor);
if (obj instanceof Program p) {
lib = p;
programToRelease = p;
}
break;
}
}
}
if (lib == null) {
continue;
}
List<Symbol> reexportedSymbols = CollectionUtils
.asStream(lib.getSymbolTable().getExternalEntryPointIterator())
.map(lib.getSymbolTable()::getPrimarySymbol)
.filter(Objects::nonNull)
.toList();
Address addr = MachoProgramUtils.addExternalBlock(program,
reexportedSymbols.size() * 8, messageLog);
for (Symbol symbol : reexportedSymbols) {
String name = SymbolUtilities.replaceInvalidChars(symbol.getName(), true);
program.getSymbolTable().addExternalEntryPoint(addr);
program.getSymbolTable().createLabel(addr, name, SourceType.IMPORTED);
Function function = program.getFunctionManager()
.createFunction(name, addr, new AddressSet(addr), SourceType.IMPORTED);
ExternalLocation loc = program.getExternalManager()
.addExtLocation(path, name, null, SourceType.IMPORTED);
function.setThunkedFunction(loc.createFunction());
addr = addr.add(8);
}
}
finally {
if (programToRelease != null) {
programToRelease.release(this);
}
}
}
}
}

View file

@ -66,10 +66,7 @@ import ghidra.util.task.TaskMonitor;
*/
public class MachoProgramBuilder {
public static final String BLOCK_SOURCE_NAME = "Mach-O Loader";
protected MachHeader machoHeader;
protected Program program;
protected ByteProvider provider;
protected FileBytes fileBytes;
@ -738,23 +735,9 @@ public class MachoProgramBuilder {
if (undefinedSymbols.size() == 0) {
return;
}
Address start = getAddress();
try {
MemoryBlock block = memory.createUninitializedBlock(MemoryBlock.EXTERNAL_BLOCK_NAME,
start, undefinedSymbols.size() * machoHeader.getAddressSize(), false);
// assume any value in external is writable.
block.setWrite(true);
// Mark block as an artificial fabrication
block.setArtificial(true);
block.setSourceName(BLOCK_SOURCE_NAME);
block.setComment(
"NOTE: This block is artificial and is used to make relocations work correctly");
}
catch (Exception e) {
log.appendMsg("Unable to create undefined memory block: " + e.getMessage());
}
Address addr = MachoProgramUtils.addExternalBlock(program,
undefinedSymbols.size() * machoHeader.getAddressSize(), log);
for (NList symbol : undefinedSymbols) {
if (monitor.isCancelled()) {
return;
@ -762,15 +745,19 @@ public class MachoProgramBuilder {
try {
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
if (name != null && name.length() > 0) {
program.getSymbolTable().createLabel(start, name, SourceType.IMPORTED);
program.getSymbolTable().createLabel(addr, name, SourceType.IMPORTED);
program.getExternalManager()
.addExtLocation(Library.UNKNOWN, name, start, SourceType.IMPORTED);
.addExtLocation(Library.UNKNOWN, name, addr, SourceType.IMPORTED);
}
}
catch (Exception e) {
log.appendMsg("Unable to create undefined symbol: " + e.getMessage());
}
start = start.add(machoHeader.getAddressSize());
addr = addr.add(machoHeader.getAddressSize());
}
}
catch (Exception e) {
log.appendMsg("Unable to create undefined memory block: " + e.getMessage());
}
}
@ -802,14 +789,10 @@ public class MachoProgramBuilder {
if (absoluteSymbols.size() == 0) {
return;
}
Address start = getAddress();
Address start = MachoProgramUtils.getNextAvailableAddress(program);
try {
memory.createUninitializedBlock("ABSOLUTE", start,
absoluteSymbols.size() * machoHeader.getAddressSize(), false);
}
catch (Exception e) {
log.appendMsg("Unable to create absolute memory block: " + e.getMessage());
}
for (NList symbol : absoluteSymbols) {
try {
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
@ -823,6 +806,10 @@ public class MachoProgramBuilder {
start = start.add(machoHeader.getAddressSize());
}
}
catch (Exception e) {
log.appendMsg("Unable to create absolute memory block: " + e.getMessage());
}
}
public List<Address> processChainedFixups(List<String> libraryPaths) throws Exception {
monitor.setMessage("Fixing up chained pointers...");
@ -1497,24 +1484,6 @@ public class MachoProgramBuilder {
}
}
private Address getAddress() {
Address maxAddress = null;
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (block.isOverlay()) {
continue;
}
if (maxAddress == null || block.getEnd().compareTo(maxAddress) > 0) {
maxAddress = block.getEnd();
}
}
if (maxAddress == null) {
return space.getAddress(0x1000);
}
long maxAddr = maxAddress.getOffset();
long remainder = maxAddr % 0x1000;
return maxAddress.getNewAddress(maxAddr + 0x1000 - remainder);
}
private MemoryBlock getMemoryBlock(Section section) {
Address blockAddress = space.getAddress(section.getAddress());
return memory.getBlock(blockAddress);

View file

@ -0,0 +1,82 @@
/* ###
* 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 ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
public class MachoProgramUtils {
/**
* Gets the next available {@link Address} in the {@link Program}
*
* @param program The {@link Program}
* @return The next available {@link Address} in the {@link Program}
*/
public static Address getNextAvailableAddress(Program program) {
Address maxAddress = null;
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (block.isOverlay()) {
continue;
}
if (maxAddress == null || block.getEnd().compareTo(maxAddress) > 0) {
maxAddress = block.getEnd();
}
}
if (maxAddress == null) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(0x1000);
}
long maxAddr = maxAddress.getOffset();
long remainder = maxAddr % 0x1000;
return maxAddress.getNewAddress(maxAddr + 0x1000 - remainder);
}
/**
* Adds the {@link MemoryBlock#EXTERNAL_BLOCK_NAME EXERNAL block} to memory, or adds to an
* existing one
*
* @param program The {@link Program}
* @param size The desired size of the new EXTERNAL block
* @param log The {@link MessageLog}
* @return The {@link Address} of the new (or new piece) of EXTERNAL block
* @throws Exception if there was an issue creating or adding to the EXTERNAL block
*/
public static Address addExternalBlock(Program program, long size, MessageLog log)
throws Exception {
Memory mem = program.getMemory();
MemoryBlock externalBlock = mem.getBlock(MemoryBlock.EXTERNAL_BLOCK_NAME);
Address ret;
if (externalBlock != null) {
ret = externalBlock.getEnd().add(1);
MemoryBlock newBlock = mem.createBlock(externalBlock, "REEXPORTS", ret, size);
mem.join(externalBlock, newBlock);
//joinedBlock.setName(MemoryBlock.EXTERNAL_BLOCK_NAME);
}
else {
ret = MachoProgramUtils.getNextAvailableAddress(program);
externalBlock =
mem.createUninitializedBlock(MemoryBlock.EXTERNAL_BLOCK_NAME, ret, size, false);
externalBlock.setWrite(true);
externalBlock.setArtificial(true);
externalBlock.setComment(
"NOTE: This block is artificial and is used to make relocations work correctly");
}
return ret;
}
}