Merge remote-tracking branch 'origin/GP-4561_ryanmkurtz_corrupt-macho'

(Closes #6271)
This commit is contained in:
Ryan Kurtz 2024-04-26 09:52:23 -04:00
commit e6c8f5b53c
5 changed files with 226 additions and 131 deletions

View file

@ -0,0 +1,52 @@
/* ###
* 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.bin.format.macho.commands;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.macho.MachConstants;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class CorruptLoadCommand extends LoadCommand {
private Throwable t;
public CorruptLoadCommand(BinaryReader reader, Throwable t) throws IOException {
super(reader);
this.t = t;
}
public Throwable getProblem() {
return t;
}
@Override
public String getCommandName() {
return "corrupt_command";
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(getCommandName(), 0);
struct.add(DWORD, "cmd", null);
struct.add(DWORD, "cmdsize", null);
struct.setCategoryPath(new CategoryPath(MachConstants.DATA_TYPE_CATEGORY));
return struct;
}
}

View file

@ -26,7 +26,6 @@ import ghidra.app.util.bin.format.macho.dyld.DyldCacheHeader;
import ghidra.app.util.bin.format.macho.dyld.DyldCacheMappingInfo; import ghidra.app.util.bin.format.macho.dyld.DyldCacheMappingInfo;
import ghidra.app.util.bin.format.macho.threadcommand.ThreadCommand; import ghidra.app.util.bin.format.macho.threadcommand.ThreadCommand;
import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache; import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache;
import ghidra.util.Msg;
/** /**
* A factory used to create {@link LoadCommand}s * A factory used to create {@link LoadCommand}s
@ -50,113 +49,119 @@ public class LoadCommandFactory {
*/ */
public static LoadCommand getLoadCommand(BinaryReader reader, MachHeader header, public static LoadCommand getLoadCommand(BinaryReader reader, MachHeader header,
SplitDyldCache splitDyldCache) throws IOException, MachException { SplitDyldCache splitDyldCache) throws IOException, MachException {
long origIndex = reader.getPointerIndex();
int type = reader.peekNextInt(); int type = reader.peekNextInt();
switch (type) { try {
case LC_SEGMENT: return switch (type) {
return new SegmentCommand(reader, header.is32bit()); case LC_SEGMENT:
case LC_SYMTAB: yield new SegmentCommand(reader, header.is32bit());
return new SymbolTableCommand(reader, case LC_SYMTAB:
getLinkerLoadCommandReader(reader, header, splitDyldCache), header); yield new SymbolTableCommand(reader,
case LC_SYMSEG: getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
return new SymbolCommand(reader); case LC_SYMSEG:
case LC_THREAD: yield new SymbolCommand(reader);
case LC_UNIXTHREAD: case LC_THREAD:
return new ThreadCommand(reader, header); case LC_UNIXTHREAD:
case LC_LOADFVMLIB: yield new ThreadCommand(reader, header);
case LC_IDFVMLIB: case LC_LOADFVMLIB:
return new FixedVirtualMemorySharedLibraryCommand(reader); case LC_IDFVMLIB:
case LC_IDENT: yield new FixedVirtualMemorySharedLibraryCommand(reader);
return new IdentCommand(reader); case LC_IDENT:
case LC_FVMFILE: yield new IdentCommand(reader);
return new FixedVirtualMemoryFileCommand(reader); case LC_FVMFILE:
case LC_PREPAGE: yield new FixedVirtualMemoryFileCommand(reader);
return new UnsupportedLoadCommand(reader, type); case LC_PREPAGE:
case LC_DYSYMTAB: yield new UnsupportedLoadCommand(reader);
return new DynamicSymbolTableCommand(reader, case LC_DYSYMTAB:
getLinkerLoadCommandReader(reader, header, splitDyldCache), header); yield new DynamicSymbolTableCommand(reader,
case LC_LOAD_DYLIB: getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
case LC_ID_DYLIB: case LC_LOAD_DYLIB:
case LC_LOAD_UPWARD_DYLIB: case LC_ID_DYLIB:
return new DynamicLibraryCommand(reader); case LC_LOAD_UPWARD_DYLIB:
case LC_LOAD_DYLINKER: yield new DynamicLibraryCommand(reader);
case LC_ID_DYLINKER: case LC_LOAD_DYLINKER:
case LC_DYLD_ENVIRONMENT: case LC_ID_DYLINKER:
return new DynamicLinkerCommand(reader); case LC_DYLD_ENVIRONMENT:
case LC_PREBOUND_DYLIB: yield new DynamicLinkerCommand(reader);
return new PreboundDynamicLibraryCommand(reader); case LC_PREBOUND_DYLIB:
case LC_ROUTINES: yield new PreboundDynamicLibraryCommand(reader);
return new RoutinesCommand(reader, header.is32bit()); case LC_ROUTINES:
case LC_SUB_FRAMEWORK: yield new RoutinesCommand(reader, header.is32bit());
return new SubFrameworkCommand(reader); case LC_SUB_FRAMEWORK:
case LC_SUB_UMBRELLA: yield new SubFrameworkCommand(reader);
return new SubUmbrellaCommand(reader); case LC_SUB_UMBRELLA:
case LC_SUB_CLIENT: yield new SubUmbrellaCommand(reader);
return new SubClientCommand(reader); case LC_SUB_CLIENT:
case LC_SUB_LIBRARY: yield new SubClientCommand(reader);
return new SubLibraryCommand(reader); case LC_SUB_LIBRARY:
case LC_TWOLEVEL_HINTS: yield new SubLibraryCommand(reader);
return new TwoLevelHintsCommand(reader); case LC_TWOLEVEL_HINTS:
case LC_PREBIND_CKSUM: yield new TwoLevelHintsCommand(reader);
return new PrebindChecksumCommand(reader); case LC_PREBIND_CKSUM:
case LC_LOAD_WEAK_DYLIB: yield new PrebindChecksumCommand(reader);
return new DynamicLibraryCommand(reader); case LC_LOAD_WEAK_DYLIB:
case LC_SEGMENT_64: yield new DynamicLibraryCommand(reader);
return new SegmentCommand(reader, header.is32bit()); case LC_SEGMENT_64:
case LC_ROUTINES_64: yield new SegmentCommand(reader, header.is32bit());
return new RoutinesCommand(reader, header.is32bit()); case LC_ROUTINES_64:
case LC_UUID: yield new RoutinesCommand(reader, header.is32bit());
return new UuidCommand(reader); case LC_UUID:
case LC_RPATH: yield new UuidCommand(reader);
return new RunPathCommand(reader); case LC_RPATH:
case LC_CODE_SIGNATURE: yield new RunPathCommand(reader);
return new CodeSignatureCommand(reader, case LC_CODE_SIGNATURE:
getLinkerLoadCommandReader(reader, header, splitDyldCache)); yield new CodeSignatureCommand(reader,
case LC_SEGMENT_SPLIT_INFO: getLinkerLoadCommandReader(reader, header, splitDyldCache));
case LC_OPTIMIZATION_HINT: case LC_SEGMENT_SPLIT_INFO:
case LC_DYLIB_CODE_SIGN_DRS: case LC_OPTIMIZATION_HINT:
return new LinkEditDataCommand(reader, case LC_DYLIB_CODE_SIGN_DRS:
getLinkerLoadCommandReader(reader, header, splitDyldCache)); yield new LinkEditDataCommand(reader,
case LC_REEXPORT_DYLIB: getLinkerLoadCommandReader(reader, header, splitDyldCache));
return new DynamicLibraryCommand(reader); case LC_REEXPORT_DYLIB:
case LC_ENCRYPTION_INFO: yield new DynamicLibraryCommand(reader);
case LC_ENCRYPTION_INFO_64: case LC_ENCRYPTION_INFO:
return new EncryptedInformationCommand(reader, header.is32bit()); case LC_ENCRYPTION_INFO_64:
case LC_DYLD_INFO: yield new EncryptedInformationCommand(reader, header.is32bit());
case LC_DYLD_INFO_ONLY: case LC_DYLD_INFO:
return new DyldInfoCommand(reader, case LC_DYLD_INFO_ONLY:
getLinkerLoadCommandReader(reader, header, splitDyldCache), header); yield new DyldInfoCommand(reader,
case LC_VERSION_MIN_MACOSX: getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
case LC_VERSION_MIN_IPHONEOS: case LC_VERSION_MIN_MACOSX:
case LC_VERSION_MIN_TVOS: case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_WATCHOS: case LC_VERSION_MIN_TVOS:
return new VersionMinCommand(reader); case LC_VERSION_MIN_WATCHOS:
case LC_FUNCTION_STARTS: yield new VersionMinCommand(reader);
return new FunctionStartsCommand(reader, case LC_FUNCTION_STARTS:
getLinkerLoadCommandReader(reader, header, splitDyldCache)); yield new FunctionStartsCommand(reader,
case LC_MAIN: getLinkerLoadCommandReader(reader, header, splitDyldCache));
return new EntryPointCommand(reader); case LC_MAIN:
case LC_DATA_IN_CODE: yield new EntryPointCommand(reader);
return new DataInCodeCommand(reader, case LC_DATA_IN_CODE:
getLinkerLoadCommandReader(reader, header, splitDyldCache)); yield new DataInCodeCommand(reader,
case LC_SOURCE_VERSION: getLinkerLoadCommandReader(reader, header, splitDyldCache));
return new SourceVersionCommand(reader); case LC_SOURCE_VERSION:
case LC_LAZY_LOAD_DYLIB: yield new SourceVersionCommand(reader);
return new DynamicLibraryCommand(reader); case LC_LAZY_LOAD_DYLIB:
case LC_LINKER_OPTIONS: yield new DynamicLibraryCommand(reader);
return new LinkerOptionCommand(reader); case LC_LINKER_OPTIONS:
case LC_BUILD_VERSION: yield new LinkerOptionCommand(reader);
return new BuildVersionCommand(reader); case LC_BUILD_VERSION:
case LC_DYLD_EXPORTS_TRIE: yield new BuildVersionCommand(reader);
return new DyldExportsTrieCommand(reader, case LC_DYLD_EXPORTS_TRIE:
getLinkerLoadCommandReader(reader, header, splitDyldCache)); yield new DyldExportsTrieCommand(reader,
case LC_DYLD_CHAINED_FIXUPS: getLinkerLoadCommandReader(reader, header, splitDyldCache));
return new DyldChainedFixupsCommand(reader, case LC_DYLD_CHAINED_FIXUPS:
getLinkerLoadCommandReader(reader, header, splitDyldCache)); yield new DyldChainedFixupsCommand(reader,
case LC_FILESET_ENTRY: getLinkerLoadCommandReader(reader, header, splitDyldCache));
return new FileSetEntryCommand(reader); case LC_FILESET_ENTRY:
default: yield new FileSetEntryCommand(reader);
Msg.warn(header, "Unsupported load command " + Integer.toHexString(type)); default:
return new UnsupportedLoadCommand(reader, type); yield new UnsupportedLoadCommand(reader);
};
}
catch (Exception e) {
reader.setPointerIndex(origIndex);
return new CorruptLoadCommand(reader, e);
} }
} }

View file

@ -219,6 +219,6 @@ public final class LoadCommandTypes {
break; break;
} }
} }
return "Unknown load command type: " + Integer.toHexString(type); return "LC_UNKNOWN_%X".formatted(type);
} }
} }

View file

@ -23,16 +23,14 @@ import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
public class UnsupportedLoadCommand extends LoadCommand { public class UnsupportedLoadCommand extends LoadCommand {
private int type;
UnsupportedLoadCommand(BinaryReader reader, int type) throws IOException { UnsupportedLoadCommand(BinaryReader reader) throws IOException {
super(reader); super(reader);
this.type = type;
} }
@Override @Override
public String getCommandName() { public String getCommandName() {
return "Unsupported Load Command Type = 0x" + Integer.toHexString(type); return "unsupported_command";
} }
@Override @Override

View file

@ -19,6 +19,8 @@ import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import org.apache.commons.collections4.map.LazySortedMap;
import ghidra.app.plugin.core.analysis.rust.RustConstants; import ghidra.app.plugin.core.analysis.rust.RustConstants;
import ghidra.app.plugin.core.analysis.rust.RustUtilities; import ghidra.app.plugin.core.analysis.rust.RustUtilities;
import ghidra.app.util.MemoryBlockUtils; import ghidra.app.util.MemoryBlockUtils;
@ -147,6 +149,7 @@ public class MachoProgramBuilder {
processLocalRelocations(); processLocalRelocations();
processEncryption(); processEncryption();
processUnsupportedLoadCommands(); processUnsupportedLoadCommands();
processCorruptLoadCommands();
// Markup structures // Markup structures
markupHeaders(machoHeader, setupHeaderAddr(machoHeader.getAllSegments())); markupHeaders(machoHeader, setupHeaderAddr(machoHeader.getAllSegments()));
@ -455,39 +458,58 @@ public class MachoProgramBuilder {
/** /**
* Attempts to discover and set the entry point. * Attempts to discover and set the entry point.
* <p>
* A program may declare multiple entry points to, for example, confuse static analysis tools.
* 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.
* *
* @throws Exception If there was a problem discovering or setting the entry point. * @throws Exception If there was a problem discovering or setting the entry point.
*/ */
protected void processEntryPoint() throws Exception { protected void processEntryPoint() throws Exception {
monitor.setMessage("Processing entry point..."); monitor.setMessage("Processing entry point...");
Address entryPointAddr = null;
EntryPointCommand entryPointCommand = final int LC_MAIN_PRIORITY = 1;
machoHeader.getFirstLoadCommand(EntryPointCommand.class); final int LC_UNIX_THREAD_PRIORITY = 2;
if (entryPointCommand != null) { final int LC_THREAD_PRIORITY = 3;
long offset = entryPointCommand.getEntryOffset(); SortedMap<Integer, List<Address>> priorityMap =
LazySortedMap.lazySortedMap(new TreeMap<>(), () -> new ArrayList<>());
for (EntryPointCommand cmd : machoHeader.getLoadCommands(EntryPointCommand.class)) {
long offset = cmd.getEntryOffset();
if (offset > 0) { if (offset > 0) {
SegmentCommand segment = machoHeader.getSegment("__TEXT"); SegmentCommand segment = machoHeader.getSegment("__TEXT");
if (segment != null) { if (segment != null) {
entryPointAddr = space.getAddress(segment.getVMaddress()).add(offset); priorityMap.get(LC_MAIN_PRIORITY)
.add(space.getAddress(segment.getVMaddress()).add(offset));
} }
} }
} }
if (entryPointAddr == null) { for (ThreadCommand threadCommand : machoHeader.getLoadCommands(ThreadCommand.class)) {
ThreadCommand threadCommand = machoHeader.getFirstLoadCommand(ThreadCommand.class); int priority = threadCommand.getCommandType() == LoadCommandTypes.LC_UNIXTHREAD
if (threadCommand != null) { ? LC_UNIX_THREAD_PRIORITY
long pointer = threadCommand.getInitialInstructionPointer(); : LC_THREAD_PRIORITY;
if (pointer != -1) { long pointer = threadCommand.getInitialInstructionPointer();
entryPointAddr = space.getAddress(pointer); if (pointer != -1) {
} priorityMap.get(priority).add(space.getAddress(pointer));
} }
} }
if (entryPointAddr != null) { if (!priorityMap.isEmpty()) {
program.getSymbolTable().createLabel(entryPointAddr, "entry", SourceType.IMPORTED); boolean realEntryFound = false;
program.getSymbolTable().addExternalEntryPoint(entryPointAddr); for (List<Address> addrs : priorityMap.values()) {
createOneByteFunction("entry", entryPointAddr); for (Address addr : addrs) {
if (!realEntryFound) {
program.getSymbolTable().createLabel(addr, "entry", SourceType.IMPORTED);
program.getSymbolTable().addExternalEntryPoint(addr);
createOneByteFunction("entry", addr);
realEntryFound = true;
}
else {
log.appendMsg("Ignoring entry point at: " + addr);
}
}
}
} }
else { else {
log.appendMsg("Unable to determine entry point."); log.appendMsg("Unable to determine entry point.");
@ -1341,9 +1363,27 @@ public class MachoProgramBuilder {
protected void processUnsupportedLoadCommands() throws CancelledException { protected void processUnsupportedLoadCommands() throws CancelledException {
monitor.setMessage("Processing unsupported load commands..."); monitor.setMessage("Processing unsupported load commands...");
for (LoadCommand loadCommand : machoHeader.getLoadCommands(UnsupportedLoadCommand.class)) { for (LoadCommand cmd : machoHeader.getLoadCommands(UnsupportedLoadCommand.class)) {
monitor.checkCancelled(); monitor.checkCancelled();
log.appendMsg(loadCommand.getCommandName()); log.appendMsg("Skipping unsupported load command: " +
LoadCommandTypes.getLoadCommandName(cmd.getCommandType()));
}
}
/**
* Processes {@link LoadCommand}s that appear to be corrupt.
*
* @throws CancelledException if the operation was cancelled.
*/
protected void processCorruptLoadCommands() throws CancelledException {
monitor.setMessage("Processing corrupt load commands...");
for (CorruptLoadCommand cmd : machoHeader.getLoadCommands(CorruptLoadCommand.class)) {
monitor.checkCancelled();
log.appendMsg("Skipping corrupt load command: %s (%s: %s)".formatted(
LoadCommandTypes.getLoadCommandName(cmd.getCommandType()),
cmd.getProblem().getClass().getSimpleName(),
cmd.getProblem().getMessage()));
} }
} }