Merge remote-tracking branch

'origin/GP-786_ryanmkurtz_PR-1505_astrelsky_exporters' (Closes #19,
Closes #1501, Closes #1505)
This commit is contained in:
ghidra1
2021-03-22 19:22:55 -04:00
4 changed files with 270 additions and 1 deletions

View File

@@ -284,6 +284,20 @@
</LI>
</UL>
</BLOCKQUOTE>
<H3><A name="elf"></A>ELF</H3>
<BLOCKQUOTE>
<P>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.</P>
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Writing back a modified Memory
Map is not supported. </I></P>
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Relocation bytes are always
restored to their original values, even if the user modifies them.</I></P>
</BLOCKQUOTE>
<H3><A name="gzf"></A>Ghidra Zip File (.gzf)</H3>
@@ -320,7 +334,7 @@
<UL>
<LI><B>Address Space</B> - Specifies which address space to export as Intel Hex format
only supports one address space. This option will be intialized to the "default"
only supports one address space. This option will be initialized to the "default"
address space.</LI>
<LI><B>Record Size</B> - Specifies the size (in bytes) of each record in the
output file. The default 16.</LI>
@@ -330,6 +344,20 @@
2 bytes will be dropped.</LI>
</UL>
</BLOCKQUOTE>
<H3><A name="pe"></A>PE</H3>
<BLOCKQUOTE>
<P>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.</P>
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Writing back a modified Memory
Map is not supported. </I></P>
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Relocation bytes are always
restored to their original values, even if the user modifies them.</I></P>
</BLOCKQUOTE>
<H3><A name="xml"/><A name="Options_XML"/>XML</H3>

View File

@@ -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<Relocation> 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<Option> getOptions(DomainObjectService domainObjectService) {
return EMPTY_OPTIONS;
}
@Override
public void setOptions(List<Option> options) {
// No options
}
/**
* An {@link InputStream} that reads a {@link FileBytes} modified bytes
*/
private static class FileBytesInputStream extends InputStream {
private final FileBytes fileBytes;
private final long size;
private long pos;
/**
* Creates a new {@link InputStream} that can read over the modified bytes of the given
* {@link FileBytes} object
*
* @param fileBytes The {@link FileBytes} to use for the {@link InputStream}
*/
FileBytesInputStream(FileBytes fileBytes) {
this.fileBytes = fileBytes;
this.size = fileBytes.getSize();
this.pos = 0;
}
@Override
public int read() throws IOException {
return pos < size ? Conv.byteToInt(fileBytes.getModifiedByte(pos++)) : -1;
}
}
}

View File

@@ -0,0 +1,37 @@
/* ###
* 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 ghidra.app.util.opinion.ElfLoader;
import ghidra.util.HelpLocation;
/**
* An {@link Exporter} that can export programs imported with the {@link ElfLoader}
*/
public class ElfExporter extends AbstractLoaderExporter {
/**
* Create a new {@link ElfExporter}
*/
public ElfExporter() {
super("ELF", "", new HelpLocation("ExporterPlugin", "elf"));
}
@Override
protected boolean supportsFileFormat(String fileFormat) {
return ElfLoader.ELF_NAME.equals(fileFormat);
}
}

View File

@@ -0,0 +1,37 @@
/* ###
* 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 ghidra.app.util.opinion.PeLoader;
import ghidra.util.HelpLocation;
/**
* An {@link Exporter} that can export programs imported with the {@link PeLoader}
*/
public class PeExporter extends AbstractLoaderExporter {
/**
* Creates a new {@link PeExporter}
*/
public PeExporter() {
super("PE", "exe", new HelpLocation("ExporterPlugin", "pe"));
}
@Override
protected boolean supportsFileFormat(String fileFormat) {
return PeLoader.PE_NAME.equals(fileFormat);
}
}