GP-786: Some improvements and docs for the PE and ELF exporters

This commit is contained in:
Ryan Kurtz
2021-03-19 10:43:34 -04:00
parent c5044ae99c
commit 6e19466dc6
5 changed files with 216 additions and 114 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

@@ -1,107 +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.app.util.exporter;
import java.io.*;
import java.util.List;
import java.util.Optional;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.DomainObjectService;
import ghidra.app.util.Option;
import ghidra.framework.model.DomainObject;
import ghidra.program.database.mem.AddressSourceInfo;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
import ghidra.program.model.reloc.Relocation;
import ghidra.util.HelpLocation;
import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities;
abstract class AbstractExecutableExporter extends Exporter {
protected AbstractExecutableExporter(String name, String extension, HelpLocation help) {
super(name, extension, help);
}
@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().getName());
return false;
}
Program program = (Program) domainObj;
Memory memory = program.getMemory();
try (OutputStream out = new FileOutputStream(file, false)) {
FileBytes[] fileBytes = memory.getAllFileBytes()
.stream()
.filter((fb) -> fb.getFilename().equals(program.getName()))
.toArray(FileBytes[]::new);
for (FileBytes bytes : fileBytes) {
FileBytesInputStream byteStream = new FileBytesInputStream(bytes);
FileUtilities.copyStreamToStream(byteStream, out, monitor);
}
}
try (RandomAccessFile fout = new RandomAccessFile(file, "rw")) {
Iterable<Relocation> relocs =
() -> program.getRelocationTable().getRelocations();
for (Relocation reloc : relocs) {
AddressSourceInfo info = memory.getAddressSourceInfo(reloc.getAddress());
// some relocations report negative offsets
if (info.getFileOffset() >= 0) {
// seek incase we are larger than an int
fout.seek(info.getFileOffset());
fout.write(reloc.getBytes());
}
}
}
return true;
}
@Override
public List<Option> getOptions(DomainObjectService domainObjectService) {
return EMPTY_OPTIONS;
}
@Override
public void setOptions(List<Option> options) {
}
private static class FileBytesInputStream extends InputStream {
private long pos = 0;
private final FileBytes bytes;
FileBytesInputStream(FileBytes bytes) {
this.bytes = bytes;
}
@Override
public int read() throws IOException {
if (pos < bytes.getSize()) {
return bytes.getModifiedByte(pos++) & 0xff;
}
return -1;
}
}
}

View File

@@ -0,0 +1,161 @@
/* ###
* 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.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}
*/
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 the file
try (OutputStream out = new FileOutputStream(file, 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 file
String error = null;
try (RandomAccessFile fout = new RandomAccessFile(file, "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 clean up the corrupted file
if (error != null) {
log.appendMsg(error);
if (!file.delete()) {
log.appendMsg("Failed to delete corrupted file: " + file);
}
return false;
}
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 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

@@ -15,13 +15,23 @@
*/
package ghidra.app.util.exporter;
import ghidra.app.util.opinion.ElfLoader;
import ghidra.util.HelpLocation;
// TODO export labels and other useful analysis information
public class ElfExporter extends AbstractExecutableExporter {
/**
* 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"));
super("ELF", "", new HelpLocation("ExporterPlugin", "elf"));
}
@Override
protected boolean supportsFileFormat(String fileFormat) {
return ElfLoader.ELF_NAME.equals(fileFormat);
}
}

View File

@@ -15,13 +15,23 @@
*/
package ghidra.app.util.exporter;
import ghidra.app.util.opinion.PeLoader;
import ghidra.util.HelpLocation;
// TODO export labels and other useful analysis information
public class PeExporter extends AbstractExecutableExporter {
/**
* 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);
}
}