Merge branch 'GP-5396_ryanmkurtz_libobjc'

This commit is contained in:
Ryan Kurtz 2025-02-20 13:10:51 -05:00
commit e2435030bc
7 changed files with 209 additions and 72 deletions

View file

@ -110,7 +110,7 @@ public class DyldChainedFixups {
}
fixups.add(new DyldFixup(chainLoc, newChainValue, DyldChainedPtr.getSize(pointerFormat),
symbol, libOrdinal));
symbol.getName(), libOrdinal));
next = DyldChainedPtr.getNext(pointerFormat, chainValue);
nextOff += next * DyldChainedPtr.getStride(pointerFormat);
@ -161,7 +161,7 @@ public class DyldChainedFixups {
finally {
program.getRelocationTable()
.add(addr, status, 0, new long[] { fixup.value() }, fixup.size(),
fixup.symbol() != null ? fixup.symbol().getName() : null);
fixup.symbol() != null ? fixup.symbol() : null);
}
if (fixup.symbol() != null && fixup.libOrdinal() != null) {
try {
@ -170,7 +170,7 @@ public class DyldChainedFixups {
}
catch (Exception e) {
log.appendMsg("WARNING: Problem fixing up symbol '%s' - %s"
.formatted(fixup.symbol().getName(), e.getMessage()));
.formatted(fixup.symbol(), e.getMessage()));
}
}
}

View file

@ -15,15 +15,13 @@
*/
package ghidra.app.util.bin.format.macho.dyld;
import ghidra.program.model.symbol.Symbol;
/**
* 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 symbol The symbol associated with the fixup (could be null)
* @param libOrdinal The library ordinal associated with the fixup (could be null)
*/
public record DyldFixup(long offset, long value, int size, Symbol symbol, Integer libOrdinal) {}
public record DyldFixup(long offset, long value, int size, String symbol, Integer libOrdinal) {}

View file

@ -135,7 +135,7 @@ public class MachoProgramBuilder {
processMemoryBlocks(machoHeader, provider.getName(), true, true);
// Process load commands
processEntryPoint();
processEntryPoint(provider.getName());
boolean exportsFound = processExports(machoHeader);
processSymbolTables(machoHeader, !exportsFound);
processStubs();
@ -470,9 +470,10 @@ public class MachoProgramBuilder {
* We will sort the discovered entry points by priorities assigned to each type of load
* command, and only use the one with the highest priority.
*
* @param source A name that represents where the memory blocks came from.
* @throws Exception If there was a problem discovering or setting the entry point.
*/
protected void processEntryPoint() throws Exception {
protected void processEntryPoint(String source) throws Exception {
monitor.setMessage("Processing entry point...");
final int LC_MAIN_PRIORITY = 1;
@ -513,14 +514,11 @@ public class MachoProgramBuilder {
realEntryFound = true;
}
else {
log.appendMsg("Ignoring entry point at: " + addr);
log.appendMsg("Ignoring entry point at " + addr + " in " + source);
}
}
}
}
else {
log.appendMsg("Unable to determine entry point.");
}
}
protected boolean processExports(MachHeader header) throws Exception {
@ -965,7 +963,7 @@ public class MachoProgramBuilder {
try {
fixupExternalLibrary(program, libraryPaths, binding.getLibraryOrdinal(),
symbol);
symbol.getName());
}
catch (Exception e) {
log.appendMsg("WARNING: Problem fixing up symbol '%s' - %s"
@ -1897,11 +1895,11 @@ public class MachoProgramBuilder {
* @param program The {@link Program}
* @param libraryPaths A {@link List} of library paths
* @param libraryOrdinal The library ordinal
* @param symbol The {@link Symbol}
* @param symbol The symbol
* @throws Exception if an unexpected problem occurs
*/
public static void fixupExternalLibrary(Program program, List<String> libraryPaths,
int libraryOrdinal, Symbol symbol) throws Exception {
int libraryOrdinal, String symbol) throws Exception {
ExternalManager extManager = program.getExternalManager();
int libraryIndex = libraryOrdinal - 1;
if (libraryIndex < 0 || libraryIndex >= libraryPaths.size()) {
@ -1915,10 +1913,10 @@ public class MachoProgramBuilder {
"Library '%s' not found in external program list".formatted(libraryName));
}
ExternalLocation loc =
extManager.getUniqueExternalLocation(Library.UNKNOWN, symbol.getName());
extManager.getUniqueExternalLocation(Library.UNKNOWN, symbol);
if (loc != null) {
try {
loc.setName(library, symbol.getName(), SourceType.IMPORTED);
loc.setName(library, symbol, SourceType.IMPORTED);
}
catch (InvalidInputException e) {
throw new Exception("Symbol name contains illegal characters");

View file

@ -91,4 +91,8 @@ public abstract class AbstractFileSystem<METADATATYPE> implements GFileSystem {
return fsIndex.resolveSymlinks(file);
}
@Override
public String toString() {
return getName();
}
}

View file

@ -18,14 +18,17 @@ 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.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.macho.dyld.DyldCacheMappingAndSlideInfo;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.formats.ios.dyldcache.DyldCacheExtractor;
import ghidra.file.formats.ios.dyldcache.DyldCacheFileSystem;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.listing.Group;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -37,6 +40,30 @@ public class DyldCacheExtractLoader extends MachoLoader {
public final static String DYLD_CACHE_EXTRACT_NAME = "Extracted DYLD Component";
static final String LIBOBJC_OPTION_NAME = "Add libobjc.dylib";
static final boolean LIBOBJC_OPTION_DEFAULT = true;
static final String AUTH_DATA_OPTION_NAME = "Add AUTH_DATA";
static final boolean AUTH_DATA_OPTION_DEFAULT = false;
static final String DIRTY_DATA_OPTION_NAME = "Add DIRTY_DATA";
static final boolean DIRTY_DATA_OPTION_DEFAULT = false;
static final String CONST_DATA_OPTION_NAME = "Add CONST_DATA";
static final boolean CONST_DATA_OPTION_DEFAULT = true;
static final String TEXT_STUBS_OPTION_NAME = "Add TEXT_STUBS";
static final boolean TEXT_STUBS_OPTION_DEFAULT = true;
static final String CONFIG_DATA_OPTION_NAME = "Add CONFIG_DATA";
static final boolean CONFIG_DATA_OPTION_DEFAULT = false;
static final String READ_ONLY_DATA_OPTION_NAME = "Add READ_ONLY_DATA";
static final boolean READ_ONLY_DATA_OPTION_DEFAULT = true;
static final String CONST_TPRO_DATA_OPTION_NAME = "Add CONST_TPRO_DATA";
static final boolean CONST_TPRO_DATA_OPTION_DEFAULT = false;
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
if (provider.length() >= DyldCacheExtractor.FOOTER_V1.length) {
@ -56,6 +83,7 @@ public class DyldCacheExtractLoader extends MachoLoader {
try {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
MachoExtractProgramBuilder.buildProgram(program, provider, fileBytes, log, monitor);
addOptionalComponents(program, options, log, monitor);
}
catch (CancelledException e) {
return;
@ -93,7 +121,46 @@ public class DyldCacheExtractLoader extends MachoLoader {
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
return List.of();
List<Option> list = new ArrayList<>();
list.add(new Option(LIBOBJC_OPTION_NAME, !loadIntoProgram && LIBOBJC_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-libobjc"));
list.add(new Option(AUTH_DATA_OPTION_NAME, !loadIntoProgram && AUTH_DATA_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-authData"));
list.add(new Option(DIRTY_DATA_OPTION_NAME, !loadIntoProgram && DIRTY_DATA_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-dirtyData"));
list.add(new Option(CONST_DATA_OPTION_NAME, !loadIntoProgram && CONST_DATA_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-constData"));
list.add(new Option(TEXT_STUBS_OPTION_NAME, !loadIntoProgram && TEXT_STUBS_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-textStubs"));
list.add(new Option(CONFIG_DATA_OPTION_NAME, !loadIntoProgram && CONFIG_DATA_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-configData"));
list.add(new Option(READ_ONLY_DATA_OPTION_NAME,
!loadIntoProgram && READ_ONLY_DATA_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-readOnlyData"));
list.add(new Option(CONST_TPRO_DATA_OPTION_NAME,
!loadIntoProgram && CONST_TPRO_DATA_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-constTproData"));
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(LIBOBJC_OPTION_NAME) || name.equals(AUTH_DATA_OPTION_NAME) ||
name.equals(DIRTY_DATA_OPTION_NAME) || name.equals(CONST_DATA_OPTION_NAME) ||
name.equals(TEXT_STUBS_OPTION_NAME) || name.equals(CONFIG_DATA_OPTION_NAME) ||
name.equals(READ_ONLY_DATA_OPTION_NAME) ||
name.equals(CONST_TPRO_DATA_OPTION_NAME)) {
if (!Boolean.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
}
}
}
return null;
}
@Override
@ -107,4 +174,77 @@ public class DyldCacheExtractLoader extends MachoLoader {
throws CancelledException, IOException {
// Do nothing
}
private void addOptionalComponents(Program program, List<Option> options, MessageLog log,
TaskMonitor monitor) throws Exception {
boolean addLibobjc =
OptionUtils.getOption(LIBOBJC_OPTION_NAME, options, LIBOBJC_OPTION_DEFAULT);
long flags = 0;
if (OptionUtils.getOption(AUTH_DATA_OPTION_NAME, options, AUTH_DATA_OPTION_DEFAULT)) {
flags |= DyldCacheMappingAndSlideInfo.DYLD_CACHE_MAPPING_AUTH_DATA;
}
if (OptionUtils.getOption(DIRTY_DATA_OPTION_NAME, options, DIRTY_DATA_OPTION_DEFAULT)) {
flags |= DyldCacheMappingAndSlideInfo.DYLD_CACHE_MAPPING_DIRTY_DATA;
}
if (OptionUtils.getOption(CONST_DATA_OPTION_NAME, options, CONST_DATA_OPTION_DEFAULT)) {
flags |= DyldCacheMappingAndSlideInfo.DYLD_CACHE_MAPPING_CONST_DATA;
}
if (OptionUtils.getOption(TEXT_STUBS_OPTION_NAME, options, TEXT_STUBS_OPTION_DEFAULT)) {
flags |= DyldCacheMappingAndSlideInfo.DYLD_CACHE_MAPPING_TEXT_STUBS;
}
if (OptionUtils.getOption(CONFIG_DATA_OPTION_NAME, options, CONFIG_DATA_OPTION_DEFAULT)) {
flags |= DyldCacheMappingAndSlideInfo.DYLD_CACHE_DYNAMIC_CONFIG_DATA;
}
if (OptionUtils.getOption(READ_ONLY_DATA_OPTION_NAME, options,
READ_ONLY_DATA_OPTION_DEFAULT)) {
flags |= DyldCacheMappingAndSlideInfo.DYLD_CACHE_READ_ONLY_DATA;
}
if (OptionUtils.getOption(CONST_TPRO_DATA_OPTION_NAME, options,
CONST_TPRO_DATA_OPTION_DEFAULT)) {
flags |= DyldCacheMappingAndSlideInfo.DYLD_CACHE_MAPPING_CONST_TPRO_DATA;
}
if (!addLibobjc && flags == 0) {
return;
}
try (FileSystemRef fsRef = openDyldCache(program, monitor)) {
DyldCacheFileSystem fs = (DyldCacheFileSystem) fsRef.getFilesystem();
Set<GFile> files = new HashSet<>();
if (addLibobjc) {
Optional.ofNullable(fs.lookup("/usr/lib/libobjc.A.dylib")).ifPresent(files::add);
}
files.addAll(fs.getFiles(flags));
for (GFile file : files) {
Group[] children = program.getListing().getDefaultRootModule().getChildren();
if (Arrays.stream(children).noneMatch(e -> e.getName().contains(file.getPath()))) {
ByteProvider p = fs.getByteProvider(file, monitor);
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, p, monitor);
MachoExtractProgramBuilder.buildProgram(program, p, fileBytes, log, monitor);
}
}
}
}
/**
* Attempts to open the given {@link Program}'s originating {@link DyldCacheFileSystem}
*
* @param program The {@link Program}
* @param monitor A {@link TaskMonitor}
* @return A {@link FileSystemRef file system reference} to the open {@link DyldCacheFileSystem}
* @throws IOException if an FSRL or IO-related error occurred
* @throws CancelledException if the user cancelled the operation
*/
public static FileSystemRef openDyldCache(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 = 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 FileSystemService.getInstance().getFilesystem(fsrlRoot, monitor);
}
}

View file

@ -18,8 +18,7 @@ package ghidra.file.formats.ios.dyldcache;
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.*;
import com.google.common.collect.*;
@ -97,8 +96,7 @@ public class DyldCacheFileSystem extends AbstractFileSystem<DyldCacheEntry> {
new DyldCacheEntry(mappedImage.getPath(), i, rangeSet, null, null, -1);
rangeSet.asRanges().forEach(r -> rangeMap.put(r, entry));
allDylibRanges.addAll(rangeSet);
fsIndex.storeFile(mappedImage.getPath(), fsIndex.getFileCount(), false, -1,
entry);
fsIndex.storeFile(mappedImage.getPath(), fsIndex.getFileCount(), false, -1, entry);
}
}
@ -182,6 +180,23 @@ public class DyldCacheFileSystem extends AbstractFileSystem<DyldCacheEntry> {
return entry != null ? entry.path() : null;
}
/**
* Gets a {@link List} of {@link GFile files} that have the given mapping flags
*
* @param flags The desired flags
* @return A {@link List} of {@link GFile files} that have the given mapping flags
*/
public List<GFile> getFiles(long flags) {
List<GFile> files = new ArrayList<>();
for (DyldCacheEntry entry : rangeMap.asMapOfRanges().values()) {
DyldCacheMappingAndSlideInfo mappingAndSlideInfo = entry.mappingAndSlideInfo();
if (mappingAndSlideInfo != null && (flags & mappingAndSlideInfo.getFlags()) != 0) {
Optional.ofNullable(lookup(entry.path())).ifPresent(files::add);
}
}
return files;
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
FileAttributes result = new FileAttributes();
@ -253,6 +268,7 @@ public class DyldCacheFileSystem extends AbstractFileSystem<DyldCacheEntry> {
*
* @param dyldCacheName The name of the DYLD Cache
* @param mappingInfo the mapping info
* @param mappingAndSlideInfo the mapping and slide info (could be null)
* @param mappingIndex The mapping index
* @return The DYLD component path of the given DYLD component
*/

View file

@ -21,9 +21,10 @@ import docking.action.builder.ActionBuilder;
import ghidra.app.CorePluginPackage;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.util.dialog.AskAddrDialog;
import ghidra.app.util.opinion.DyldCacheExtractLoader;
import ghidra.file.formats.ios.dyldcache.DyldCacheFileSystem;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.FileSystemRef;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.plugin.importer.ImporterUtilities;
@ -63,15 +64,15 @@ public class DyldCacheBuilderPlugin extends Plugin {
protected void init() {
super.init();
String actionName = "Add To Program";
new ActionBuilder(actionName, getName())
final String addActionName = "Add To Program";
new ActionBuilder(addActionName, getName())
.withContext(ProgramLocationActionContext.class)
.enabledWhen(p -> p.getProgram()
.enabledWhen(context -> context.getProgram()
.getExecutableFormat()
.equals(DyldCacheExtractLoader.DYLD_CACHE_EXTRACT_NAME))
.onAction(plac -> TaskLauncher.launchModal(actionName,
monitor -> addMissingDyldCacheComponent(plac.getLocation(), monitor)))
.popupMenuPath("References", actionName)
.onAction(context -> TaskLauncher.launchModal(addActionName,
monitor -> addMissingDyldCacheComponent(context.getLocation(), monitor)))
.popupMenuPath("References", addActionName)
.popupMenuGroup("Add")
.helpLocation(new HelpLocation("ImporterPlugin", "Add_To_Program"))
.buildAndInstall(tool);
@ -86,31 +87,35 @@ public class DyldCacheBuilderPlugin extends Plugin {
*/
private void addMissingDyldCacheComponent(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;
Address address = location.getRefAddress();
if (address == null) {
AskAddrDialog dialog = new AskAddrDialog(name, "Enter address", program, null);
if (dialog.isCanceled()) {
return;
}
address = dialog.getValueAsAddress();
}
if (refAddress.getAddressSpace().isExternalSpace()) {
if (address.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");
if (program.getMemory().contains(address)) {
Msg.showInfo(this, null, name,
"Address %s already exists in memory".formatted(address));
return;
}
try (FileSystemRef fsRef = openDyldCache(program, monitor)) {
try (FileSystemRef fsRef = DyldCacheExtractLoader.openDyldCache(program, monitor)) {
DyldCacheFileSystem fs = (DyldCacheFileSystem) fsRef.getFilesystem();
long refAddr = refAddress.getOffset();
String fsPath = fs.findAddress(refAddr);
long offset = address.getOffset();
String fsPath = fs.findAddress(offset);
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()));
"Address %s not found in %s".formatted(address, fs.toString()));
}
}
catch (CancelledException e) {
@ -120,28 +125,4 @@ public class DyldCacheBuilderPlugin extends Plugin {
Msg.showError(this, null, name, e.getMessage(), e);
}
}
/**
* Attempts to open the given {@link Program}'s originating {@link DyldCacheFileSystem}
*
* @param program The {@link Program}
* @param monitor A {@link TaskMonitor}
* @return A {@link FileSystemRef file system reference} to the open {@link DyldCacheFileSystem}
* @throws IOException if an FSRL or IO-related error occurred
* @throws CancelledException if the user cancelled the operation
*/
private FileSystemRef openDyldCache(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 = 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 FileSystemService.getInstance().getFilesystem(fsrlRoot, monitor);
}
}