diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java index 3e34d12f47..f31d772ef9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java @@ -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 parseReexports() throws IOException { + List 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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DynamicLibraryCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DynamicLibraryCommand.java index dc5bb7cd02..663bf65cb1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DynamicLibraryCommand.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DynamicLibraryCommand.java @@ -4,9 +4,9 @@ * 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. @@ -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); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/ExportTrie.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/ExportTrie.java index cf3e08adf7..7d03f5fee5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/ExportTrie.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/ExportTrie.java @@ -4,9 +4,9 @@ * 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. @@ -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 { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java index c6f31fc3a4..4630914e2c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java @@ -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} + *

+ * Fix up program's external library entries so that they point to a path in the project. + */ @Override protected void postLoadProgramFixups(List> loadedPrograms, Project project, List

- * 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> loadedPrograms, - List searchFolders, MessageLog messageLog, TaskMonitor monitor) - throws CancelledException, IOException { - - monitor.initialize(loadedPrograms.size()); - for (Loaded 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. - *

- * 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> loadedPrograms, List 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 createUnprocessedQueue(List 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 getLibrarySearchPaths(ByteProvider provider, Program program, List

+ * 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 The type of {@link DomainObject} that was loaded */ @@ -39,6 +42,7 @@ public class Loaded { private DomainFile domainFile; private boolean ignoreSave; + private boolean discard; /** * Creates a new {@link Loaded} object @@ -213,6 +217,27 @@ public class Loaded { 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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java index 0357733034..68456a33f0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java @@ -4,9 +4,9 @@ * 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. @@ -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 findSupportedLoadSpecs(ByteProvider provider) throws IOException { List loadSpecs = new ArrayList<>(); @@ -121,6 +130,34 @@ public class MachoLoader extends AbstractLibrarySupportLoader { } } + @Override + public List

+ * 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

+ * 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 unprocessed, int depth, LoadSpec loadSpec, + List

+ * Adds reexported symbols to each {@link Loaded} {@link Program}. + */ + @Override + protected void postLoadProgramFixups(List> loadedPrograms, Project project, + List

processChainedFixups(List libraryPaths) throws Exception { @@ -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); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramUtils.java new file mode 100644 index 0000000000..1a918f9e7a --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramUtils.java @@ -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; + } +}