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

View file

@ -19,6 +19,8 @@ import java.io.IOException;
import java.math.BigInteger;
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.RustUtilities;
import ghidra.app.util.MemoryBlockUtils;
@ -147,6 +149,7 @@ public class MachoProgramBuilder {
processLocalRelocations();
processEncryption();
processUnsupportedLoadCommands();
processCorruptLoadCommands();
// Markup structures
markupHeaders(machoHeader, setupHeaderAddr(machoHeader.getAllSegments()));
@ -455,39 +458,58 @@ public class MachoProgramBuilder {
/**
* 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.
*/
protected void processEntryPoint() throws Exception {
monitor.setMessage("Processing entry point...");
Address entryPointAddr = null;
EntryPointCommand entryPointCommand =
machoHeader.getFirstLoadCommand(EntryPointCommand.class);
if (entryPointCommand != null) {
long offset = entryPointCommand.getEntryOffset();
final int LC_MAIN_PRIORITY = 1;
final int LC_UNIX_THREAD_PRIORITY = 2;
final int LC_THREAD_PRIORITY = 3;
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) {
SegmentCommand segment = machoHeader.getSegment("__TEXT");
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) {
ThreadCommand threadCommand = machoHeader.getFirstLoadCommand(ThreadCommand.class);
if (threadCommand != null) {
for (ThreadCommand threadCommand : machoHeader.getLoadCommands(ThreadCommand.class)) {
int priority = threadCommand.getCommandType() == LoadCommandTypes.LC_UNIXTHREAD
? LC_UNIX_THREAD_PRIORITY
: LC_THREAD_PRIORITY;
long pointer = threadCommand.getInitialInstructionPointer();
if (pointer != -1) {
entryPointAddr = space.getAddress(pointer);
}
priorityMap.get(priority).add(space.getAddress(pointer));
}
}
if (entryPointAddr != null) {
program.getSymbolTable().createLabel(entryPointAddr, "entry", SourceType.IMPORTED);
program.getSymbolTable().addExternalEntryPoint(entryPointAddr);
createOneByteFunction("entry", entryPointAddr);
if (!priorityMap.isEmpty()) {
boolean realEntryFound = false;
for (List<Address> addrs : priorityMap.values()) {
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 {
log.appendMsg("Unable to determine entry point.");
@ -1341,9 +1363,27 @@ public class MachoProgramBuilder {
protected void processUnsupportedLoadCommands() throws CancelledException {
monitor.setMessage("Processing unsupported load commands...");
for (LoadCommand loadCommand : machoHeader.getLoadCommands(UnsupportedLoadCommand.class)) {
for (LoadCommand cmd : machoHeader.getLoadCommands(UnsupportedLoadCommand.class)) {
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()));
}
}