diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/AbstractDwarfEHDecoder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/AbstractDwarfEHDecoder.java index 741ca0dba2..1d0298a2e5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/AbstractDwarfEHDecoder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/AbstractDwarfEHDecoder.java @@ -333,7 +333,13 @@ abstract class AbstractDwarfEHDecoder implements DwarfEHDecoder { switch (appMode) { case DW_EH_PE_absptr: - // just pass this through + // if the program has been re-based, need to add in the image base difference. + // but only if there are no relocations at this location + if (prog.getRelocationTable().getRelocation(addr) == null) { + long programBaseAddressFixup = context.getOriginalImageBaseOffset(); + + val = val + programBaseAddressFixup; + } break; case DW_EH_PE_aligned: diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/DwarfDecodeContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/DwarfDecodeContext.java index e4177210af..6357323392 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/DwarfDecodeContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/DwarfDecodeContext.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.exceptionhandlers.gcc; +import ghidra.app.util.opinion.ElfLoader; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; @@ -34,6 +35,7 @@ public class DwarfDecodeContext { private Object decodedValue; private int encodedLength; private MemBuffer buffer; + private long originalImageBaseOffset; // offset from image base used in original dwarf, and the actual load image base /** * Constructs a Dwarf decode context. @@ -96,6 +98,8 @@ public class DwarfDecodeContext { this.ehBlock = ehBlock; this.functionEntryPoint = entryPoint; + Long oib = ElfLoader.getElfOriginalImageBase(program); + this.originalImageBaseOffset = program.getImageBase().getOffset() - oib.longValue(); } /** @@ -180,4 +184,12 @@ public class DwarfDecodeContext { public Address getFunctionEntryPoint() { return functionEntryPoint; } + + /** + * Gets the offset from the programs image base and the dwarf original image base + * @return offset that if added to the current image base would be the original dwarf image base + */ + public long getOriginalImageBaseOffset() { + return originalImageBaseOffset; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java index 421ef74c2a..d05ea769c0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import ghidra.app.cmd.comments.AppendCommentCmd; import ghidra.app.cmd.label.SetLabelPrimaryCmd; import ghidra.app.util.bin.format.dwarf4.*; +import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute; import ghidra.app.util.bin.format.dwarf4.encoding.*; import ghidra.app.util.bin.format.dwarf4.expression.*; import ghidra.program.database.data.DataTypeUtilities; @@ -176,7 +177,11 @@ public class DWARFFunctionImporter { return true; } - if (diea.getLowPC(-1) == 0) { + // fetch the low_pc attribute directly instead of calling diea.getLowPc() to avoid + // any fixups applied by lower level code + DWARFNumericAttribute attr = + diea.getAttribute(DWARFAttribute.DW_AT_low_pc, DWARFNumericAttribute.class); + if (attr != null && attr.getUnsignedValue() == 0) { return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java index dc26406469..f751995705 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java @@ -426,12 +426,35 @@ public class DyldCacheHeader implements StructConverter { } /** - * Gets the {@link List} of {@link DyldCacheImageInfo}s. Requires header to have been parsed. + * Generates a {@link List} of {@link DyldCacheImage}s that are mapped in by this + * {@link DyldCacheHeader}. Requires header to have been parsed. + *

+ * NOTE: A "split" DYLD Cache header may declare an image, but that image may get loaded at an + * address defined by the memory map of a different split header. This method will only return + * the images that are mapped by "this" header's memory map. * - * @return The {@link List} of {@link DyldCacheImageInfo}s + * + * @return A {@link List} of {@link DyldCacheImage}s mapped by this {@link DyldCacheHeader} */ - public List getImageInfos() { - return imageInfoList; + public List getMappedImages() { + List images = new ArrayList<>(); + if (imageInfoList.size() > 0) { + // The old, simple way + images.addAll(imageInfoList); + } + else { + // The new, split file way. A split file will have an entry for every image, but + // not every image will be mapped. + for (DyldCacheImageTextInfo imageTextInfo : imageTextInfoList) { + for (DyldCacheMappingInfo mappingInfo : mappingInfoList) { + if (mappingInfo.contains(imageTextInfo.getAddress())) { + images.add(imageTextInfo); + break; + } + } + } + } + return images; } /** @@ -691,8 +714,8 @@ public class DyldCacheHeader implements StructConverter { monitor.setMessage("Marking up DYLD header..."); monitor.initialize(1); try { - DataUtilities.createData(program, program.getImageBase(), toDataType(), -1, false, - DataUtilities.ClearDataMode.CHECK_FOR_SPACE); + DataUtilities.createData(program, space.getAddress(getBaseAddress()), toDataType(), -1, + false, DataUtilities.ClearDataMode.CHECK_FOR_SPACE); monitor.incrementProgress(1); } catch (CodeUnitInsertionException | DuplicateNameException | IOException e) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImage.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImage.java new file mode 100644 index 0000000000..e3077fff01 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImage.java @@ -0,0 +1,36 @@ +/* ### + * 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.dyld; + +/** + * A convenience interface for getting the address and path of a DYLD Cache image + */ +public interface DyldCacheImage { + + /** + * Gets the address the start of the image + * + * @return The address of the start of the image + */ + public long getAddress(); + + /** + * Gets the path of the image + * + * @return The path of the image + */ + public String getPath(); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageInfo.java index 084ebc952f..2b0dc702e3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageInfo.java @@ -29,7 +29,7 @@ import ghidra.util.exception.DuplicateNameException; * @see dyld3/shared-cache/dyld_cache_format.h */ @SuppressWarnings("unused") -public class DyldCacheImageInfo implements StructConverter { +public class DyldCacheImageInfo implements DyldCacheImage, StructConverter { private long address; private long modTime; @@ -55,20 +55,12 @@ public class DyldCacheImageInfo implements StructConverter { path = reader.readAsciiString(pathFileOffset); } - /** - * Gets the address the start of the image. - * - * @return The address of the start of the image - */ + @Override public long getAddress() { return address; } - /** - * Gets the path of the image. - * - * @return The path of the image - */ + @Override public String getPath() { return path; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageTextInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageTextInfo.java index a6d08dfec5..4203e07b51 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageTextInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheImageTextInfo.java @@ -29,7 +29,7 @@ import ghidra.util.exception.DuplicateNameException; * @see dyld3/shared-cache/dyld_cache_format.h */ @SuppressWarnings("unused") -public class DyldCacheImageTextInfo implements StructConverter { +public class DyldCacheImageTextInfo implements DyldCacheImage, StructConverter { private byte[] uuid; private long loadAddress; @@ -52,12 +52,13 @@ public class DyldCacheImageTextInfo implements StructConverter { path = reader.readAsciiString(pathOffset); } + + @Override + public long getAddress() { + return loadAddress; + } - /** - * Gets the path of the image text. - * - * @return The path of the image text. - */ + @Override public String getPath() { return path; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheMappingInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheMappingInfo.java index 96c62a7731..c9a4184ba4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheMappingInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheMappingInfo.java @@ -106,6 +106,17 @@ public class DyldCacheMappingInfo implements StructConverter { return (initProt & SegmentConstants.PROTECTION_X) != 0; } + /** + * Returns true if the mapping contains the given address + * + * @param addr The address to check + * @return True if the mapping contains the given address; otherwise, false + */ + public boolean contains(long addr) { + return Long.compareUnsigned(addr, address) >= 0 && + Long.compareUnsigned(addr, address + size) < 0; + } + @Override public DataType toDataType() throws DuplicateNameException, IOException { StructureDataType struct = new StructureDataType("dyld_cache_mapping_info", 0); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java index e2e51654f2..3bb1c77102 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java @@ -25,9 +25,11 @@ import ghidra.app.util.opinion.Loader; import ghidra.framework.model.DomainObject; import ghidra.program.database.mem.AddressSourceInfo; import ghidra.program.database.mem.FileBytes; +import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlockSourceInfo; import ghidra.program.model.reloc.Relocation; import ghidra.util.Conv; import ghidra.util.HelpLocation; @@ -75,44 +77,45 @@ public abstract class AbstractLoaderExporter extends Exporter { return false; } + List fileBytes = memory.getAllFileBytes(); + if (fileBytes.isEmpty()) { + log.appendMsg("Exporting a program with no file source bytes is not supported"); + return false; + } + if (fileBytes.size() > 1) { + log.appendMsg("Exporting a program with more than 1 file source is not supported"); + return false; + } + // Write source program's file bytes to a temp file File tempFile = File.createTempFile("ghidra_export_", null); try (OutputStream out = new FileOutputStream(tempFile, false)) { - FileBytes[] fileBytes = memory.getAllFileBytes() - .stream() - .filter(fb -> program.getExecutablePath().endsWith(fb.getFilename())) - .toArray(FileBytes[]::new); - for (FileBytes bytes : fileBytes) { - FileUtilities.copyStreamToStream(new FileBytesInputStream(bytes), out, monitor); - } + FileUtilities.copyStreamToStream(new FileBytesInputStream(fileBytes.get(0)), out, + monitor); } // Undo relocations in the temp file - // NOTE: not all relocations are file-backed - String error = null; + // NOTE: not all relocations are file-backed, and some are only partially file-backed try (RandomAccessFile fout = new RandomAccessFile(tempFile, "rw")) { Iterable relocs = () -> program.getRelocationTable().getRelocations(); for (Relocation reloc : relocs) { - AddressSourceInfo info = memory.getAddressSourceInfo(reloc.getAddress()); - if (info == null) { + Address addr = reloc.getAddress(); + AddressSourceInfo addrSourceInfo = memory.getAddressSourceInfo(addr); + if (addrSourceInfo == null) { continue; } - long offset = info.getFileOffset(); - byte[] bytes = reloc.getBytes(); + long offset = addrSourceInfo.getFileOffset(); if (offset >= 0) { - if (offset + bytes.length > fout.length()) { - error = "Relocation at " + reloc.getAddress() + " exceeds file length"; - break; - } + MemoryBlockSourceInfo memSourceInfo = addrSourceInfo.getMemoryBlockSourceInfo(); + byte[] bytes = reloc.getBytes(); + int len = Math.min(bytes.length, + (int) memSourceInfo.getMaxAddress().subtract(addr) + 1); fout.seek(offset); - fout.write(bytes); + fout.write(bytes, 0, len); } } } - - // If errors occurred, log them and delete the malformed temp file - if (error != null) { - log.appendMsg(error); + catch (Exception e) { if (!tempFile.delete()) { log.appendMsg("Failed to delete malformed file: " + tempFile); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java index c773b1cc3a..d382fbf293 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java @@ -18,9 +18,7 @@ package ghidra.app.util.opinion; import java.io.IOException; import java.util.*; -import ghidra.app.util.MemoryBlockUtils; -import ghidra.app.util.Option; -import ghidra.app.util.OptionUtils; +import ghidra.app.util.*; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.format.macho.dyld.DyldArchitecture; @@ -51,11 +49,19 @@ public class DyldCacheLoader extends AbstractLibrarySupportLoader { static final boolean CREATE_DYLIB_SECTIONS_OPTION_DEFAULT = false; /** Loader option to add relocation entries for each fixed chain pointer */ - static final String ADD_RELOCATION_ENTRIES_OPTION_NAME = "Add relocation entries for fixed chain pointers"; + static final String ADD_RELOCATION_ENTRIES_OPTION_NAME = + "Add relocation entries for fixed chain pointers"; /** Default value for loader option add relocation entries */ static final boolean ADD_RELOCATION_ENTRIES_OPTION_DEFAULT = false; + /** Loader option to combine split DYLD Cache files (.1, .2, .symbol, etc) into one program */ + static final String COMBINE_SPLIT_FILES_OPTION_NAME = + "Auto import and combine split DYLD Cache files"; + + /** Default value for loader option add relocation entries */ + static final boolean COMBINE_SPLIT_FILES_OPTION_DEFAULT = true; + @Override public Collection findSupportedLoadSpecs(ByteProvider provider) throws IOException { List loadSpecs = new ArrayList<>(); @@ -92,7 +98,8 @@ public class DyldCacheLoader extends AbstractLibrarySupportLoader { DyldCacheProgramBuilder.buildProgram(program, provider, MemoryBlockUtils.createFileBytes(program, provider, monitor), shouldProcessSymbols(options), shouldCreateDylibSections(options), - shouldAddRelocationEntries(options), log, monitor); + shouldAddRelocationEntries(options), shouldCombineSplitFiles(options), log, + monitor); } catch (CancelledException e) { return; @@ -113,25 +120,35 @@ public class DyldCacheLoader extends AbstractLibrarySupportLoader { list.add( new Option(CREATE_DYLIB_SECTIONS_OPTION_NAME, CREATE_DYLIB_SECTIONS_OPTION_DEFAULT, Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-createDylibSections")); - list.add( - new Option(ADD_RELOCATION_ENTRIES_OPTION_NAME, ADD_RELOCATION_ENTRIES_OPTION_DEFAULT, - Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-addRelocationEntries")); + list.add(new Option(ADD_RELOCATION_ENTRIES_OPTION_NAME, + ADD_RELOCATION_ENTRIES_OPTION_DEFAULT, Boolean.class, + Loader.COMMAND_LINE_ARG_PREFIX + "-addRelocationEntries")); + list.add(new Option(COMBINE_SPLIT_FILES_OPTION_NAME, COMBINE_SPLIT_FILES_OPTION_DEFAULT, + Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-combineSplitFiles")); } return list; } private boolean shouldProcessSymbols(List