mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-27 21:10:26 +00:00
GP-4561: Allow Mach-O's with corrupted load commands to be imported
This commit is contained in:
parent
9a3fd98d02
commit
84cd4f98f0
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
case LC_SEGMENT:
|
||||
return new SegmentCommand(reader, header.is32bit());
|
||||
case LC_SYMTAB:
|
||||
return new SymbolTableCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
||||
case LC_SYMSEG:
|
||||
return new SymbolCommand(reader);
|
||||
case LC_THREAD:
|
||||
case LC_UNIXTHREAD:
|
||||
return new ThreadCommand(reader, header);
|
||||
case LC_LOADFVMLIB:
|
||||
case LC_IDFVMLIB:
|
||||
return new FixedVirtualMemorySharedLibraryCommand(reader);
|
||||
case LC_IDENT:
|
||||
return new IdentCommand(reader);
|
||||
case LC_FVMFILE:
|
||||
return new FixedVirtualMemoryFileCommand(reader);
|
||||
case LC_PREPAGE:
|
||||
return new UnsupportedLoadCommand(reader, type);
|
||||
case LC_DYSYMTAB:
|
||||
return 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);
|
||||
case LC_LOAD_DYLINKER:
|
||||
case LC_ID_DYLINKER:
|
||||
case LC_DYLD_ENVIRONMENT:
|
||||
return new DynamicLinkerCommand(reader);
|
||||
case LC_PREBOUND_DYLIB:
|
||||
return new PreboundDynamicLibraryCommand(reader);
|
||||
case LC_ROUTINES:
|
||||
return new RoutinesCommand(reader, header.is32bit());
|
||||
case LC_SUB_FRAMEWORK:
|
||||
return new SubFrameworkCommand(reader);
|
||||
case LC_SUB_UMBRELLA:
|
||||
return new SubUmbrellaCommand(reader);
|
||||
case LC_SUB_CLIENT:
|
||||
return new SubClientCommand(reader);
|
||||
case LC_SUB_LIBRARY:
|
||||
return new SubLibraryCommand(reader);
|
||||
case LC_TWOLEVEL_HINTS:
|
||||
return new TwoLevelHintsCommand(reader);
|
||||
case LC_PREBIND_CKSUM:
|
||||
return new PrebindChecksumCommand(reader);
|
||||
case LC_LOAD_WEAK_DYLIB:
|
||||
return new DynamicLibraryCommand(reader);
|
||||
case LC_SEGMENT_64:
|
||||
return new SegmentCommand(reader, header.is32bit());
|
||||
case LC_ROUTINES_64:
|
||||
return new RoutinesCommand(reader, header.is32bit());
|
||||
case LC_UUID:
|
||||
return new UuidCommand(reader);
|
||||
case LC_RPATH:
|
||||
return new RunPathCommand(reader);
|
||||
case LC_CODE_SIGNATURE:
|
||||
return 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,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_REEXPORT_DYLIB:
|
||||
return new DynamicLibraryCommand(reader);
|
||||
case LC_ENCRYPTION_INFO:
|
||||
case LC_ENCRYPTION_INFO_64:
|
||||
return new EncryptedInformationCommand(reader, header.is32bit());
|
||||
case LC_DYLD_INFO:
|
||||
case LC_DYLD_INFO_ONLY:
|
||||
return 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);
|
||||
case LC_FUNCTION_STARTS:
|
||||
return new FunctionStartsCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_MAIN:
|
||||
return new EntryPointCommand(reader);
|
||||
case LC_DATA_IN_CODE:
|
||||
return new DataInCodeCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_SOURCE_VERSION:
|
||||
return new SourceVersionCommand(reader);
|
||||
case LC_LAZY_LOAD_DYLIB:
|
||||
return new DynamicLibraryCommand(reader);
|
||||
case LC_LINKER_OPTIONS:
|
||||
return new LinkerOptionCommand(reader);
|
||||
case LC_BUILD_VERSION:
|
||||
return new BuildVersionCommand(reader);
|
||||
case LC_DYLD_EXPORTS_TRIE:
|
||||
return new DyldExportsTrieCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_DYLD_CHAINED_FIXUPS:
|
||||
return new DyldChainedFixupsCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_FILESET_ENTRY:
|
||||
return new FileSetEntryCommand(reader);
|
||||
default:
|
||||
Msg.warn(header, "Unsupported load command " + Integer.toHexString(type));
|
||||
return new UnsupportedLoadCommand(reader, type);
|
||||
try {
|
||||
return switch (type) {
|
||||
case LC_SEGMENT:
|
||||
yield new SegmentCommand(reader, header.is32bit());
|
||||
case LC_SYMTAB:
|
||||
yield new SymbolTableCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
||||
case LC_SYMSEG:
|
||||
yield new SymbolCommand(reader);
|
||||
case LC_THREAD:
|
||||
case LC_UNIXTHREAD:
|
||||
yield new ThreadCommand(reader, header);
|
||||
case LC_LOADFVMLIB:
|
||||
case LC_IDFVMLIB:
|
||||
yield new FixedVirtualMemorySharedLibraryCommand(reader);
|
||||
case LC_IDENT:
|
||||
yield new IdentCommand(reader);
|
||||
case LC_FVMFILE:
|
||||
yield new FixedVirtualMemoryFileCommand(reader);
|
||||
case LC_PREPAGE:
|
||||
yield new UnsupportedLoadCommand(reader);
|
||||
case LC_DYSYMTAB:
|
||||
yield new DynamicSymbolTableCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
||||
case LC_LOAD_DYLIB:
|
||||
case LC_ID_DYLIB:
|
||||
case LC_LOAD_UPWARD_DYLIB:
|
||||
yield new DynamicLibraryCommand(reader);
|
||||
case LC_LOAD_DYLINKER:
|
||||
case LC_ID_DYLINKER:
|
||||
case LC_DYLD_ENVIRONMENT:
|
||||
yield new DynamicLinkerCommand(reader);
|
||||
case LC_PREBOUND_DYLIB:
|
||||
yield new PreboundDynamicLibraryCommand(reader);
|
||||
case LC_ROUTINES:
|
||||
yield new RoutinesCommand(reader, header.is32bit());
|
||||
case LC_SUB_FRAMEWORK:
|
||||
yield new SubFrameworkCommand(reader);
|
||||
case LC_SUB_UMBRELLA:
|
||||
yield new SubUmbrellaCommand(reader);
|
||||
case LC_SUB_CLIENT:
|
||||
yield new SubClientCommand(reader);
|
||||
case LC_SUB_LIBRARY:
|
||||
yield new SubLibraryCommand(reader);
|
||||
case LC_TWOLEVEL_HINTS:
|
||||
yield new TwoLevelHintsCommand(reader);
|
||||
case LC_PREBIND_CKSUM:
|
||||
yield new PrebindChecksumCommand(reader);
|
||||
case LC_LOAD_WEAK_DYLIB:
|
||||
yield new DynamicLibraryCommand(reader);
|
||||
case LC_SEGMENT_64:
|
||||
yield new SegmentCommand(reader, header.is32bit());
|
||||
case LC_ROUTINES_64:
|
||||
yield new RoutinesCommand(reader, header.is32bit());
|
||||
case LC_UUID:
|
||||
yield new UuidCommand(reader);
|
||||
case LC_RPATH:
|
||||
yield new RunPathCommand(reader);
|
||||
case LC_CODE_SIGNATURE:
|
||||
yield new CodeSignatureCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_SEGMENT_SPLIT_INFO:
|
||||
case LC_OPTIMIZATION_HINT:
|
||||
case LC_DYLIB_CODE_SIGN_DRS:
|
||||
yield new LinkEditDataCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_REEXPORT_DYLIB:
|
||||
yield new DynamicLibraryCommand(reader);
|
||||
case LC_ENCRYPTION_INFO:
|
||||
case LC_ENCRYPTION_INFO_64:
|
||||
yield new EncryptedInformationCommand(reader, header.is32bit());
|
||||
case LC_DYLD_INFO:
|
||||
case LC_DYLD_INFO_ONLY:
|
||||
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:
|
||||
yield new VersionMinCommand(reader);
|
||||
case LC_FUNCTION_STARTS:
|
||||
yield new FunctionStartsCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_MAIN:
|
||||
yield new EntryPointCommand(reader);
|
||||
case LC_DATA_IN_CODE:
|
||||
yield new DataInCodeCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_SOURCE_VERSION:
|
||||
yield new SourceVersionCommand(reader);
|
||||
case LC_LAZY_LOAD_DYLIB:
|
||||
yield new DynamicLibraryCommand(reader);
|
||||
case LC_LINKER_OPTIONS:
|
||||
yield new LinkerOptionCommand(reader);
|
||||
case LC_BUILD_VERSION:
|
||||
yield new BuildVersionCommand(reader);
|
||||
case LC_DYLD_EXPORTS_TRIE:
|
||||
yield new DyldExportsTrieCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_DYLD_CHAINED_FIXUPS:
|
||||
yield new DyldChainedFixupsCommand(reader,
|
||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||
case LC_FILESET_ENTRY:
|
||||
yield new FileSetEntryCommand(reader);
|
||||
default:
|
||||
yield new UnsupportedLoadCommand(reader);
|
||||
};
|
||||
}
|
||||
catch (Exception e) {
|
||||
reader.setPointerIndex(origIndex);
|
||||
return new CorruptLoadCommand(reader, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -219,6 +219,6 @@ public final class LoadCommandTypes {
|
|||
break;
|
||||
}
|
||||
}
|
||||
return "Unknown load command type: " + Integer.toHexString(type);
|
||||
return "LC_UNKNOWN_%X".formatted(type);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
long pointer = threadCommand.getInitialInstructionPointer();
|
||||
if (pointer != -1) {
|
||||
entryPointAddr = space.getAddress(pointer);
|
||||
}
|
||||
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) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue