diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm b/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm index 1509a09ba1..4a8ff89bdd 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm @@ -284,6 +284,20 @@ + +

ELF

+ +
+

Writes an ELF program that was imported with the ELF loader back to its original file + layout. Any file-backed bytes that were modified by the user in the program database will + be reflected in the new file.

+ +

Writing back a modified Memory + Map is not supported.

+ +

Relocation bytes are always + restored to their original values, even if the user modifies them.

+

Ghidra Zip File (.gzf)

@@ -320,7 +334,7 @@ + +

PE

+ +
+

Writes a PE program that was imported with the PE loader back to its original file + layout. Any file-backed bytes that were modified by the user in the program database will + be reflected in the new file.

+ +

Writing back a modified Memory + Map is not supported.

+ +

Relocation bytes are always + restored to their original values, even if the user modifies them.

+

XML

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 new file mode 100644 index 0000000000..4645369886 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java @@ -0,0 +1,167 @@ +/* ### + * 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.exporter; + +import java.io.*; +import java.nio.file.*; +import java.util.List; + +import ghidra.app.util.DomainObjectService; +import ghidra.app.util.Option; +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.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.reloc.Relocation; +import ghidra.util.Conv; +import ghidra.util.HelpLocation; +import ghidra.util.task.TaskMonitor; +import utilities.util.FileUtilities; + +/** + * An {@link Exporter} that can export programs imported with a particular {@link Loader} + */ +public abstract class AbstractLoaderExporter extends Exporter { + + /** + * Creates a new {@link AbstractLoaderExporter} + * + * @param name The display name of this exporter + * @param extension The default extension for this exporter + * @param help The {@link HelpLocation} for this exporter + */ + protected AbstractLoaderExporter(String name, String extension, HelpLocation help) { + super(name, extension, help); + } + + /** + * Checks to see if the given file format is supported by this exporter + * + * @param fileFormat The file format (loader name) of the program to export + * @return True if the given file format is supported by this exporter; otherwise, false + */ + protected abstract boolean supportsFileFormat(String fileFormat); + + @Override + public boolean export(File file, DomainObject domainObj, AddressSetView addrSet, + TaskMonitor monitor) throws IOException, ExporterException { + + if (!(domainObj instanceof Program)) { + log.appendMsg("Unsupported type: " + domainObj.getClass().getSimpleName()); + return false; + } + + Program program = (Program) domainObj; + Memory memory = program.getMemory(); + + String fileFormat = program.getExecutableFormat(); + if (!supportsFileFormat(fileFormat)) { + log.appendMsg("Unsupported file format: " + fileFormat); + 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); + } + } + + // Undo relocations in the temp file + String error = null; + 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) { + error = "Failed to get relocation address source"; + break; + } + if (info.getFileOffset() < 0) { + error = "Failed to get relocation file offset"; + break; + } + if (info.getFileOffset() >= fout.length()) { + error = "Relocation file offset exceeds file length"; + break; + } + fout.seek(info.getFileOffset()); + fout.write(reloc.getBytes()); + } + } + + // If errors occurred, log them and delete the malformed temp file + if (error != null) { + log.appendMsg(error); + if (!tempFile.delete()) { + log.appendMsg("Failed to delete malformed file: " + tempFile); + } + return false; + } + + // Move temp file to desired output file + Path from = Paths.get(tempFile.toURI()); + Path to = Paths.get(file.toURI()); + Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); + return true; + } + + @Override + public List