mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-25 17:00:36 -09:00
GP-786: Some improvements and docs for the PE and ELF exporters
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user