Merge remote-tracking branch 'origin/GP-719_ryanmkurtz_dyld-filesystem'

(Closes #682, Closes #2934)
This commit is contained in:
ghidra1 2021-05-18 08:50:07 -04:00
commit 147e6b8f13
4 changed files with 314 additions and 194 deletions

View file

@ -132,6 +132,10 @@ public class SegmentCommand extends LoadCommand {
public long getFileOffset() {
return fileoff;
}
public void setFileOffset(long fileOffset) {
fileoff = fileOffset;
}
public long getFileSize() {
return filesize;

View file

@ -0,0 +1,307 @@
/* ###
* 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.file.formats.ios.dyldcache;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import generic.continues.RethrowContinuesFactory;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.macho.*;
import ghidra.app.util.bin.format.macho.commands.*;
import ghidra.util.*;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.TaskMonitor;
/**
* A class for extracting DYLIB files from a {@link DyldCacheFileSystem}
*/
public class DyldCacheDylibExtractor {
/**
* Gets an {@link InputStream} that reads a DYLIB from a {@link DyldCacheFileSystem}. The
* DYLIB's header will be altered to account for its segment bytes being packed down.
*
* @param dylibOffset The offset of the DYLIB in the given provider
* @param provider The DYLD
* @param monitor A cancellable {@link TaskMonitor}
* @return An {@link InputStream} that reads the specified DYLIB from the given DYLD
* {@link ByteProvider}
* @throws IOException If there was an IO-related issue with extracting the DYLIB
* @throws MachException If there was an error parsing the DYLIB headers
*/
public static InputStream extractDylib(long dylibOffset, ByteProvider provider,
TaskMonitor monitor) throws IOException, MachException {
// Make sure Mach-O header is valid
MachHeader header = MachHeader.createMachHeader(RethrowContinuesFactory.INSTANCE, provider,
dylibOffset, false);
header.parse();
// Pack the DYLIB
PackedDylib packedDylib = new PackedDylib(header, dylibOffset, provider);
// Fixup indices, offsets, etc in the packed DYLIB's header
for (LoadCommand cmd : header.getLoadCommands()) {
if (monitor.isCancelled()) {
break;
}
switch (cmd.getCommandType()) {
case LoadCommandTypes.LC_SEGMENT:
fixupSegment((SegmentCommand) cmd, packedDylib, false, monitor);
break;
case LoadCommandTypes.LC_SEGMENT_64:
fixupSegment((SegmentCommand) cmd, packedDylib, true, monitor);
break;
case LoadCommandTypes.LC_SYMTAB:
fixupSymbolTable((SymbolTableCommand) cmd, packedDylib);
break;
case LoadCommandTypes.LC_DYSYMTAB:
fixupDynamicSymbolTable((DynamicSymbolTableCommand) cmd, packedDylib);
break;
case LoadCommandTypes.LC_DYLD_INFO:
case LoadCommandTypes.LC_DYLD_INFO_ONLY:
fixupDyldInfo((DyldInfoCommand) cmd, packedDylib);
break;
}
}
return packedDylib.getInputStream();
}
/**
* Fixes-up the old DYLD file offsets in the given segment so they are correct for the newly
* packed DYLIB
*
* @param cmd The segment to fix-up
* @param packedDylib The packed DYLIB
* @param is64bit True if the segment is 64-bit; false if 32-bit
* @param monitor A cancellable {@link TaskMonitor}
* @throws IOException If there was an IO-related issue performing the fix-up
*/
private static void fixupSegment(SegmentCommand cmd, PackedDylib packedDylib, boolean is64bit,
TaskMonitor monitor) throws IOException {
if (cmd.getFileOffset() > 0 && cmd.getFileSize() > 0) {
packedDylib.fixup(cmd.getStartIndex() + (is64bit ? 0x28 : 0x20), is64bit ? 8 : 4);
}
long sectionStartIndex = cmd.getStartIndex() + (is64bit ? 0x48 : 0x38);
for (Section section : cmd.getSections()) {
if (monitor.isCancelled()) {
break;
}
if (section.getOffset() > 0 && section.getSize() > 0) {
packedDylib.fixup(sectionStartIndex + (is64bit ? 0x30 : 0x28), 4);
}
if (section.getRelocationOffset() > 0) {
packedDylib.fixup(sectionStartIndex + (is64bit ? 0x38 : 0x30), 4);
}
sectionStartIndex += is64bit ? 0x50 : 0x44;
}
}
/**
* Fixes-up the old DYLD file offsets in the given symbol table so they are correct for the
* newly packed DYLIB
*
* @param cmd The symbol table to fix-up
* @param packedDylib The packed DYLIB
* @throws IOException If there was an IO-related issue performing the fix-up
*/
private static void fixupSymbolTable(SymbolTableCommand cmd, PackedDylib packedDylib)
throws IOException {
if (cmd.getSymbolOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x8, 4);
}
if (cmd.getStringTableOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x10, 4);
}
}
/**
* Fixes-up the old DYLD file offsets in the given dynamic symbol table so they are correct for
* the newly packed DYLIB
*
* @param cmd The dynamic symbol table to fix-up
* @param packedDylib The packed DYLIB
* @throws IOException If there was an IO-related issue performing the fix-up
*/
private static void fixupDynamicSymbolTable(DynamicSymbolTableCommand cmd,
PackedDylib packedDylib) throws IOException {
if (cmd.getTableOfContentsOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x20, 4);
}
if (cmd.getModuleTableOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x28, 4);
}
if (cmd.getReferencedSymbolTableOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x30, 4);
}
if (cmd.getIndirectSymbolTableOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x38, 4);
}
if (cmd.getExternalRelocationOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x40, 4);
}
if (cmd.getLocalRelocationOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x48, 4);
}
}
/**
* Fixes-up the old DYLD file offsets in the given DYLD Info command so they are correct for the
* newly packed DYLIB
*
* @param cmd The DYLD Info command to fix-up
* @param packedDylib The packed DYLIB
* @throws IOException If there was an IO-related issue performing the fix-up
*/
private static void fixupDyldInfo(DyldInfoCommand cmd, PackedDylib packedDylib)
throws IOException {
if (cmd.getRebaseOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x8, 4);
}
if (cmd.getBindOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x10, 4);
}
if (cmd.getWeakBindOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x18, 4);
}
if (cmd.getLazyBindOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x20, 4);
}
if (cmd.getExportOffset() > 0) {
packedDylib.fixup(cmd.getStartIndex() + 0x28, 4);
}
}
/**
* A packed DYLIB that was once living inside of a DYLD. The DYLIB is said to be packed
* because its segment file bytes, which were not adjacent in its containing DYLD, are now
* adjacent in its new array.
*/
private static class PackedDylib {
private BinaryReader reader;
private Map<SegmentCommand, Integer> packedStarts;
private byte[] packed;
/**
* Creates a new {@link PackedDylib} object
*
* @param header The DYLD's DYLIB's Mach-O header
* @param dylibOffset The offset of the DYLIB in the given provider
* @param provider The DYLD's bytes
* @throws IOException If there was an IO-related error
*/
public PackedDylib(MachHeader header, long dylibOffset, ByteProvider provider)
throws IOException {
reader = new BinaryReader(provider, true);
packedStarts = new HashMap<>();
int size = 0;
for (SegmentCommand segment : header.getAllSegments()) {
packedStarts.put(segment, size);
size += segment.getFileSize();
// Some older DYLDs use relative file offsets for only their __TEXT segment.
// Adjust these segments to be consistent with all the other segments.
if (segment.getFileOffset() == 0) {
segment.setFileOffset(dylibOffset);
}
}
packed = new byte[size];
for (SegmentCommand segment : header.getAllSegments()) {
long segmentSize = segment.getFileSize();
if (segment.getFileOffset() + segmentSize > provider.length()) {
segmentSize = provider.length() - segment.getFileOffset();
Msg.warn(this, segment.getSegmentName() +
" segment extends beyond end of file. Truncating...");
}
byte[] bytes = provider.readBytes(segment.getFileOffset(), segmentSize);
System.arraycopy(bytes, 0, packed, packedStarts.get(segment), bytes.length);
}
}
/**
* Gets an {@link InputStream} that reads the packed DYLIB
*
* @return An {@link InputStream} that reads the packed DYLIB
*/
public InputStream getInputStream() {
return new ByteArrayInputStream(packed);
}
/**
* Fixes up the bytes at the given DYLD file offset to map to the correct offset in the
* packed DYLIB
*
* @param fileOffset The DYLD file offset to fix-up
* @param size The number of bytes to fix-up (must be 4 or 8)
* @throws IOException If there was an IO-related error
* @throws IllegalArgumentException if size is an unsupported value
*/
public void fixup(long fileOffset, int size) throws IOException {
if (size != 4 && size != 8) {
throw new IllegalArgumentException("Size must be 4 or 8 (got " + size + ")");
}
long orig = reader.readUnsignedValue(fileOffset, size);
try {
byte[] newBytes = toBytes(getPackedOffset(orig), size);
System.arraycopy(newBytes, 0, packed, (int) getPackedOffset(fileOffset),
newBytes.length);
}
catch (NotFoundException e) {
Msg.warn(this, e.getMessage());
}
}
/**
* Converts the given DYLD file offset to an offset into the packed DYLIB
*
* @param fileOffset The DYLD file offset to convert
* @return An offset into the packed DYLIB
* @throws NotFoundException If there was no corresponding DYLIB offset
*/
private long getPackedOffset(long fileOffset) throws NotFoundException {
for (SegmentCommand segment : packedStarts.keySet()) {
if (fileOffset >= segment.getFileOffset() &&
fileOffset < segment.getFileOffset() + segment.getFileSize()) {
return fileOffset - segment.getFileOffset() + packedStarts.get(segment);
}
}
throw new NotFoundException(
"Failed to convert DYLD file offset to packed DYLIB offset: " +
Long.toHexString(fileOffset));
}
/**
* Converts the given value to a byte array
*
* @param value The value to convert to a byte array
* @param size The number of bytes to convert (must be 4 or 8)
* @return The value as a byte array of the given size
* @throws IllegalArgumentException if size is an unsupported value
*/
private byte[] toBytes(long value, int size) throws IllegalArgumentException {
if (size != 4 && size != 8) {
throw new IllegalArgumentException("Size must be 4 or 8 (got " + size + ")");
}
DataConverter converter = LittleEndianDataConverter.INSTANCE;
return size == 8 ? converter.getBytes(value) : converter.getBytes((int) value);
}
}
}

View file

@ -57,19 +57,8 @@ public class DyldCacheFileSystem extends GFileSystemBase {
}
long machHeaderStartIndexInProvider = data.getAddress() - header.getBaseAddress();
try {
/*
* //check to make sure mach-o header is valid MachHeader header =
* MachHeader.createMachHeader( RethrowContinuesFactory.INSTANCE,
* provider, machHeaderStartIndexInProvider, false );
* header.parse();
*
* return new ByteProviderInputStream( provider,
* machHeaderStartIndexInProvider, provider.length() -
* machHeaderStartIndexInProvider );
*/
FixupMacho32bitArmOffsets fixer = new FixupMacho32bitArmOffsets();
return fixer.fix(file, machHeaderStartIndexInProvider, provider, monitor);
return DyldCacheDylibExtractor.extractDylib(machHeaderStartIndexInProvider, provider,
monitor);
}
catch (MachException e) {
throw new IOException("Invalid Mach-O header detected at 0x" +
@ -164,11 +153,8 @@ public class DyldCacheFileSystem extends GFileSystemBase {
monitor.incrementProgress(1);
GFileImpl file = GFileImpl.fromPathString(this, root, data.getPath(), null, false,
0/*TODO compute length?*/ );
GFileImpl file = GFileImpl.fromPathString(this, root, data.getPath(), null, false, -1);
storeFile(file, data);
file.setLength(provider.length() - (data.getAddress() - header.getBaseAddress()));
}
}

View file

@ -1,177 +0,0 @@
/* ###
* 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.file.formats.ios.dyldcache;
import java.io.*;
import java.util.*;
import generic.continues.RethrowContinuesFactory;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.macho.*;
import ghidra.app.util.bin.format.macho.commands.*;
import ghidra.formats.gfilesystem.GFile;
import ghidra.util.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class FixupMacho32bitArmOffsets {
private DataConverter converter = LittleEndianDataConverter.INSTANCE;
public InputStream fix(GFile file, long offsetAdjustment, ByteProvider provider,
TaskMonitor monitor) throws IOException, MachException {
Map<Long, byte []> changeMap = new HashMap<Long, byte []>();
//check to make sure mach-o header is valid
MachHeader header = MachHeader.createMachHeader( RethrowContinuesFactory.INSTANCE, provider, offsetAdjustment, false );
header.parse();
//fix up index, offsets, etc in the header
List<LoadCommand> commands = header.getLoadCommands();
for ( LoadCommand loadCommand : commands ) {
if ( monitor.isCancelled() ) {
break;
}
switch ( loadCommand.getCommandType() ) {
case LoadCommandTypes.LC_SEGMENT: {
SegmentCommand segmentCommand = (SegmentCommand) loadCommand;
if ( segmentCommand.getFileOffset() > 0 ) {
long newOffset = segmentCommand.getFileOffset() - offsetAdjustment;
changeMap.put( segmentCommand.getStartIndex() + 0x20 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( segmentCommand.getNumberOfSections() > 0 ) {
long sectionStartIndex = segmentCommand.getStartIndex() + 0x38 - offsetAdjustment;
for ( Section section : segmentCommand.getSections() ) {
if ( monitor.isCancelled() ) {
break;
}
if ( section.getOffset() > 0 && section.getOffset() > offsetAdjustment ) {
long newOffset = Conv.intToLong( section.getOffset() ) - offsetAdjustment;
changeMap.put( sectionStartIndex + 0x28, converter.getBytes( (int)newOffset ) );
}
if ( section.getRelocationOffset() > 0 && section.getRelocationOffset() > offsetAdjustment ) {
long newOffset = Conv.intToLong( section.getRelocationOffset() ) - offsetAdjustment;
changeMap.put( sectionStartIndex + 0x30, converter.getBytes( (int)newOffset ) );
}
try {
sectionStartIndex += section.toDataType().getLength();
}
catch ( DuplicateNameException e ) {
throw new IOException( e );
}
}
}
break;
}
case LoadCommandTypes.LC_SYMTAB: {
SymbolTableCommand symbolTableCommand = (SymbolTableCommand) loadCommand;
if ( symbolTableCommand.getSymbolOffset() > 0 ) {
long newOffset = Conv.intToLong( symbolTableCommand.getSymbolOffset() ) - offsetAdjustment;
changeMap.put( symbolTableCommand.getStartIndex() + 0x8 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( symbolTableCommand.getStringTableOffset() > 0 ) {
long newOffset = Conv.intToLong( symbolTableCommand.getStringTableOffset() ) - offsetAdjustment;
changeMap.put( symbolTableCommand.getStartIndex() + 0x10 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
break;
}
case LoadCommandTypes.LC_DYSYMTAB: {
DynamicSymbolTableCommand dynamicSymbolTableCommand = (DynamicSymbolTableCommand) loadCommand;
if ( dynamicSymbolTableCommand.getTableOfContentsOffset() > 0 ) {
long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getTableOfContentsOffset() ) - offsetAdjustment;
changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x20 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dynamicSymbolTableCommand.getModuleTableOffset() > 0 ) {
long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getModuleTableOffset() ) - offsetAdjustment;
changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x28 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dynamicSymbolTableCommand.getReferencedSymbolTableOffset() > 0 ) {
long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getReferencedSymbolTableOffset() ) - offsetAdjustment;
changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x30 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dynamicSymbolTableCommand.getIndirectSymbolTableOffset() > 0 ) {
long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getIndirectSymbolTableOffset() ) - offsetAdjustment;
changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x38 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dynamicSymbolTableCommand.getExternalRelocationOffset() > 0 ) {
long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getExternalRelocationOffset() ) - offsetAdjustment;
changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x40 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dynamicSymbolTableCommand.getLocalRelocationOffset() > 0 ) {
long newOffset = Conv.intToLong( dynamicSymbolTableCommand.getLocalRelocationOffset() ) - offsetAdjustment;
changeMap.put( dynamicSymbolTableCommand.getStartIndex() + 0x48 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
break;
}
case LoadCommandTypes.LC_DYLD_INFO:
case LoadCommandTypes.LC_DYLD_INFO_ONLY: {
DyldInfoCommand dyldInfoCommand = (DyldInfoCommand) loadCommand;
if ( dyldInfoCommand.getRebaseOffset() > 0 ) {
long newOffset = Conv.intToLong( dyldInfoCommand.getRebaseOffset() ) - offsetAdjustment;
changeMap.put( dyldInfoCommand.getStartIndex() + 0x8 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dyldInfoCommand.getBindOffset() > 0 ) {
long newOffset = Conv.intToLong( dyldInfoCommand.getBindOffset() ) - offsetAdjustment;
changeMap.put( dyldInfoCommand.getStartIndex() + 0x10 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dyldInfoCommand.getWeakBindOffset() > 0 ) {
long newOffset = Conv.intToLong( dyldInfoCommand.getWeakBindOffset() ) - offsetAdjustment;
changeMap.put( dyldInfoCommand.getStartIndex() + 0x18 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dyldInfoCommand.getLazyBindOffset() > 0 ) {
long newOffset = Conv.intToLong( dyldInfoCommand.getLazyBindOffset() ) - offsetAdjustment;
changeMap.put( dyldInfoCommand.getStartIndex() + 0x20 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
if ( dyldInfoCommand.getExportOffset() > 0 ) {
long newOffset = Conv.intToLong(dyldInfoCommand.getExportOffset() ) - offsetAdjustment;
changeMap.put( dyldInfoCommand.getStartIndex() + 0x28 - offsetAdjustment, converter.getBytes( (int)newOffset ) );
}
break;
}
}
}
List<Long> indexList = new ArrayList<Long>( changeMap.keySet() );
Collections.sort( indexList );
ByteArrayOutputStream tempOut = new ByteArrayOutputStream();
try {
long tempIndex = offsetAdjustment;
while ( !monitor.isCancelled() ) {
final int length = 0x10000;
byte [] buffer = provider.readBytes( tempIndex, length );
for ( Long index : indexList ) {
if ( index + offsetAdjustment >= tempIndex && index + offsetAdjustment < tempIndex + length ) {
byte [] changedBytes = changeMap.get( index );
System.arraycopy( changedBytes, 0, buffer, index.intValue(), changedBytes.length );
}
}
tempOut.write( buffer );
tempIndex += buffer.length;
monitor.setMessage( "0x" + Long.toHexString( tempIndex ) );
if ( tempIndex > provider.length() ) {
break;
}
}
}
finally {
tempOut.close();
}
return new ByteArrayInputStream(tempOut.toByteArray());
}
}