mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-2770: Replacing PE and ELF exporters with generic Original File exporter. Changing the BinaryExporter's display name to 'Raw Bytes'.
This commit is contained in:
parent
45640e9bc6
commit
93bbe3b883
7 changed files with 340 additions and 287 deletions
|
@ -22,15 +22,17 @@
|
||||||
<UL>
|
<UL>
|
||||||
<LI><A href="#ascii">ASCII</A></LI>
|
<LI><A href="#ascii">ASCII</A></LI>
|
||||||
|
|
||||||
<LI><A href="#binary">Binary</A></LI>
|
|
||||||
|
|
||||||
<LI><A href="#c_cpp">C/C++</A></LI>
|
<LI><A href="#c_cpp">C/C++</A></LI>
|
||||||
|
|
||||||
<LI><A href="#gzf">Ghidra Zip File (.gzf)</A></LI>
|
<LI><A href="#gzf">Ghidra Zip File (.gzf)</A></LI>
|
||||||
|
|
||||||
<LI><A href="#html">HTML</A></LI>
|
<LI><A href="#html">HTML</A></LI>
|
||||||
|
|
||||||
<LI><A href="#intel_hex">Intel Hex</A></LI>
|
<LI><A href="#intel_hex">Intel Hex</A></LI>
|
||||||
|
|
||||||
|
<LI><A href="#original_file">Original File</A></LI>
|
||||||
|
|
||||||
|
<LI><A href="#binary">Raw Bytes</A></LI>
|
||||||
|
|
||||||
<LI><A href="#xml">XML Export Format</A></LI>
|
<LI><A href="#xml">XML Export Format</A></LI>
|
||||||
</UL>
|
</UL>
|
||||||
|
@ -238,17 +240,6 @@
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H3><A name="binary"><A name="Options_Binary"/></A>Binary</H3>
|
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
|
||||||
<P>Creates a binary file containing only the bytes from each memory block in the program.
|
|
||||||
If the program was originally created using the <B>Binary Importer</B>, then this
|
|
||||||
exporter allows recreation of the original file.</P>
|
|
||||||
|
|
||||||
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Only initialized memory blocks
|
|
||||||
are included in the output file.</I></P>
|
|
||||||
</BLOCKQUOTE>
|
|
||||||
|
|
||||||
<H3><A name="c_cpp"/><A name="Options_C_C__""/>C/C++</H3>
|
<H3><A name="c_cpp"/><A name="Options_C_C__""/>C/C++</H3>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
|
@ -284,22 +275,8 @@
|
||||||
</LI>
|
</LI>
|
||||||
</UL>
|
</UL>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H3><A name="elf"></A>ELF</H3>
|
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<H3><A name="gzf"/>Ghidra Zip File (.gzf)</H3>
|
||||||
<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>
|
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<P>Creates a zip file from a program in your project. You may want to create a zip file
|
<P>Creates a zip file from a program in your project. You may want to create a zip file
|
||||||
|
@ -321,7 +298,7 @@
|
||||||
the <A href="#ascii_options">ASCII Options</A>.</I></P>
|
the <A href="#ascii_options">ASCII Options</A>.</I></P>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H3><A name="intel_hex"><A name="Options_Intel_Hex"/></A>Intel Hex</H3>
|
<H3><A name="intel_hex"/><A name="Options_Intel_Hex"/>Intel Hex</H3>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<P>The Intel Hex format, a printable file representing memory images, was originally
|
<P>The Intel Hex format, a printable file representing memory images, was originally
|
||||||
|
@ -345,18 +322,57 @@
|
||||||
</UL>
|
</UL>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H3><A name="pe"></A>PE</H3>
|
<H3><A name="original_file"/><A name="Options_Original_File"/>Original File</H3>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<P>Writes a PE program that was imported with the PE loader back to its original file
|
<P>Writes a program back to its original file layout. By default, any file-backed bytes
|
||||||
layout. Any file-backed bytes that were modified by the user in the program database will
|
that were modified by the user in the program database will be reflected in the new file.
|
||||||
be reflected in the new file.</P>
|
Optionally, the program can be written back to its unmodified file bytes, discarding all
|
||||||
|
user modifications.
|
||||||
|
</P>
|
||||||
|
|
||||||
|
<H4>Original File Options</H4>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
<LI><B>Export User Byte Modifications</B> - If checked, user byte modifications are
|
||||||
|
preserved in the exported file. If unchecked, no user byte modifications are preserved
|
||||||
|
and the exported file will exactly match the file that was originally imported.</LI>
|
||||||
|
</UL>
|
||||||
|
<UL>
|
||||||
|
<LI><B>Save Multiple File Sources To Directory</B> - If checked, the destination file
|
||||||
|
will be treated as a directory. Each file source from the program will be saved to this
|
||||||
|
newly created directory with names of the form <directory>.0, <directory>.1,
|
||||||
|
etc. If the program contains multiple file sources and this option is not checked, only
|
||||||
|
the primary (first) file source will saved to the specified destination file.</LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>This exporter is only
|
||||||
|
operational when the program has at least one file-backed byte source. This will be
|
||||||
|
reflected in the <A href="help/topics/MemoryMapPlugin/Memory_Map.htm">Memory Map's</A>
|
||||||
|
Byte Source column, which entries that begin with <B>File:</B></I></P>
|
||||||
|
|
||||||
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Writing back a modified Memory
|
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Writing back a modified Memory
|
||||||
Map is not supported. </I></P>
|
Map is not supported. </I></P>
|
||||||
|
|
||||||
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Relocation bytes are always
|
<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>
|
restored to their original values, even if the user modifies them.</I></P>
|
||||||
|
|
||||||
|
<P><IMG alt="" border="0" src="../../shared/warning.png"> <I>Programs written to disk with
|
||||||
|
this exporter may be runnable on your native platform. Use caution when exporting
|
||||||
|
potentially malicious programs.</I></P>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
<H3><A name="binary"/><A name="Options_Binary"/></A>Raw Bytes</H3>
|
||||||
|
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>Creates a binary file containing only the raw bytes from each memory block in the
|
||||||
|
program. If there are multiple memory blocks, their bytes will be concatenated in the
|
||||||
|
exported binary file. If the program was originally created using the <B>Binary
|
||||||
|
Importer</B> and there is only one memory block, then this exporter allows recreation of
|
||||||
|
the original file.</P>
|
||||||
|
|
||||||
|
<P><IMG alt="" border="0" src="../../shared/note.png"> <I>Only initialized memory blocks
|
||||||
|
are included in the output file.</I></P>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H3><A name="xml"/><A name="Options_XML"/>XML</H3>
|
<H3><A name="xml"/><A name="Options_XML"/>XML</H3>
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
block. If the bytes were originally imported from a file, then this will indicate which file
|
block. If the bytes were originally imported from a file, then this will indicate which file
|
||||||
and the offset into that file. If the bytes are mapped to another region of memory, it will
|
and the offset into that file. If the bytes are mapped to another region of memory, it will
|
||||||
provide the address for the mapping. Blocks may consist of regions that have different
|
provide the address for the mapping. Blocks may consist of regions that have different
|
||||||
sources. In that case, source information about the first several regions will be d
|
sources. In that case, source information about the first several regions will be
|
||||||
displayed.</P>
|
displayed.</P>
|
||||||
|
|
||||||
<P><I><B>Source -</B></I> Description of block origination.</P>
|
<P><I><B>Source -</B></I> Description of block origination.</P>
|
||||||
|
|
|
@ -1,169 +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.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.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;
|
|
||||||
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 help The {@link HelpLocation} for this exporter
|
|
||||||
*/
|
|
||||||
protected AbstractLoaderExporter(String name, HelpLocation help) {
|
|
||||||
super(name, "", 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FileBytes> 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)) {
|
|
||||||
FileUtilities.copyStreamToStream(new FileBytesInputStream(fileBytes.get(0)), out,
|
|
||||||
monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Undo relocations in the temp file
|
|
||||||
// NOTE: not all relocations are file-backed, and some are only partially file-backed
|
|
||||||
try (RandomAccessFile fout = new RandomAccessFile(tempFile, "rw")) {
|
|
||||||
Iterable<Relocation> relocs = () -> program.getRelocationTable().getRelocations();
|
|
||||||
for (Relocation reloc : relocs) {
|
|
||||||
Address addr = reloc.getAddress();
|
|
||||||
AddressSourceInfo addrSourceInfo = memory.getAddressSourceInfo(addr);
|
|
||||||
if (addrSourceInfo == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
long offset = addrSourceInfo.getFileOffset();
|
|
||||||
if (offset >= 0) {
|
|
||||||
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, 0, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,13 +28,12 @@ import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of exporter that creates
|
* An {@link Exporter} that can export memory blocks as raw bytes
|
||||||
* an Binary representation of the program.
|
|
||||||
*/
|
*/
|
||||||
public class BinaryExporter extends Exporter {
|
public class BinaryExporter extends Exporter {
|
||||||
|
|
||||||
public BinaryExporter() {
|
public BinaryExporter() {
|
||||||
super("Binary", "bin", new HelpLocation("ExporterPlugin", "binary"));
|
super("Raw Bytes", "bin", new HelpLocation("ExporterPlugin", "binary"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -53,8 +52,6 @@ public class BinaryExporter extends Exporter {
|
||||||
addrSet = memory;
|
addrSet = memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOutputStream fos = new FileOutputStream(file);
|
|
||||||
|
|
||||||
AddressSet set = new AddressSet(addrSet);
|
AddressSet set = new AddressSet(addrSet);
|
||||||
|
|
||||||
//skip blocks that are not initialized...
|
//skip blocks that are not initialized...
|
||||||
|
@ -65,7 +62,7 @@ public class BinaryExporter extends Exporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||||
AddressRangeIterator iter = set.getAddressRanges();
|
AddressRangeIterator iter = set.getAddressRanges();
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
AddressRange range = iter.next();
|
AddressRange range = iter.next();
|
||||||
|
@ -77,9 +74,6 @@ public class BinaryExporter extends Exporter {
|
||||||
catch (MemoryAccessException e) {
|
catch (MemoryAccessException e) {
|
||||||
throw new ExporterException(e);
|
throw new ExporterException(e);
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
fos.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -91,5 +85,6 @@ public class BinaryExporter extends Exporter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOptions(List<Option> options) {
|
public void setOptions(List<Option> options) {
|
||||||
|
// No options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +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 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
/* ###
|
||||||
|
* 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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.app.util.*;
|
||||||
|
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.HelpLocation;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link Exporter} that can export {@link FileBytes the originally imported file}.
|
||||||
|
* <p>
|
||||||
|
* WARNING: Programs written to disk with this exporter may be runnable on your native platform.
|
||||||
|
* Use caution when exporting potentially malicious programs.
|
||||||
|
*/
|
||||||
|
public class OriginalFileExporter extends Exporter {
|
||||||
|
|
||||||
|
private static final String USER_MODS_OPTION_NAME = "Export User Byte Modifications";
|
||||||
|
private static final boolean USER_MODS_OPTION_DEFAULT = true;
|
||||||
|
|
||||||
|
private static final String CREATE_DIR_OPTION_NAME = "Save Multiple File Sources To Directory";
|
||||||
|
private static final boolean CREATE_DIR_OPTION_DEFAULT = false;
|
||||||
|
|
||||||
|
private List<Option> options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link OriginalFileExporter}
|
||||||
|
*/
|
||||||
|
public OriginalFileExporter() {
|
||||||
|
super("Original File", "", new HelpLocation("ExporterPlugin", "original_file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsPartialExport() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
List<FileBytes> allFileBytes = program.getMemory().getAllFileBytes();
|
||||||
|
if (allFileBytes.isEmpty()) {
|
||||||
|
log.appendMsg("Exporting a program with no file source bytes is not supported");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unusual Code Alert!
|
||||||
|
// This exporter has an option to save multiple file sources to a newly created directory.
|
||||||
|
// If this happens, we treat the file parameter as the new directory to save to. The newly
|
||||||
|
// exported files will each get a filename based on this directory name. We don't want
|
||||||
|
// to use the original FileBytes file name, as it could be dangerous to save the original
|
||||||
|
// files to disk with their original filenames.
|
||||||
|
File dir = null;
|
||||||
|
if (shouldCreateDir()) {
|
||||||
|
dir = file;
|
||||||
|
if (!FileUtilities.mkdirs(dir)) {
|
||||||
|
log.appendMsg("Failed to create directory: " + dir);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (allFileBytes.size() > 1) {
|
||||||
|
log.appendMsg("WARNING: Program contains more than 1 file source.\n" +
|
||||||
|
"Only bytes from the primary (first) file source will be exported.\n" +
|
||||||
|
"Enable option to export all file sources to a directory if desired.");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean ret = true;
|
||||||
|
for (int i = 0; i < allFileBytes.size(); i++) {
|
||||||
|
FileBytes fileBytes = allFileBytes.get(i);
|
||||||
|
if (dir != null) {
|
||||||
|
file = new File(dir, dir.getName() + "." + i);
|
||||||
|
}
|
||||||
|
boolean success = shouldExportUserModifications()
|
||||||
|
? exportModifiedBytes(file, fileBytes, (Program) domainObj, monitor)
|
||||||
|
: exportUnmodifiedlBytes(file, fileBytes, monitor);
|
||||||
|
ret &= success;
|
||||||
|
if (dir != null) {
|
||||||
|
if (success) {
|
||||||
|
log.appendMsg("Exported " + fileBytes.getFilename() + " to " + file);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.appendMsg("Failed to export " + fileBytes.getFilename() + " to " + file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the unmodified {@link FileBytes}
|
||||||
|
*
|
||||||
|
* @param file The file to export to
|
||||||
|
* @param fileBytes The {@link FileBytes} to export
|
||||||
|
* @param monitor The monitor
|
||||||
|
* @return True if the export succeeded; otherwise, false
|
||||||
|
* @throws IOException If there was IO-related error during the export
|
||||||
|
*/
|
||||||
|
private boolean exportUnmodifiedlBytes(File file, FileBytes fileBytes, TaskMonitor monitor)
|
||||||
|
throws IOException {
|
||||||
|
try (OutputStream out = new FileOutputStream(file, false)) {
|
||||||
|
FileUtilities.copyStreamToStream(new FileBytesInputStream(fileBytes, false), out,
|
||||||
|
monitor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the modified {@link FileBytes}
|
||||||
|
*
|
||||||
|
* @param file The file to export to
|
||||||
|
* @param fileBytes The {@link FileBytes} to export
|
||||||
|
* @param program The program to export
|
||||||
|
* @param monitor The monitor
|
||||||
|
* @return True if the export succeeded; otherwise, false
|
||||||
|
* @throws IOException If there was IO-related error during the export
|
||||||
|
*/
|
||||||
|
private boolean exportModifiedBytes(File file, FileBytes fileBytes, Program program,
|
||||||
|
TaskMonitor monitor) throws IOException {
|
||||||
|
|
||||||
|
// Write source program's file bytes to a temp file.
|
||||||
|
// This is done to ensure a random access write failure doesn't corrupt a file the user
|
||||||
|
// might be overwriting.
|
||||||
|
File tempFile = File.createTempFile("ghidra_export_", null);
|
||||||
|
try (OutputStream out = new FileOutputStream(tempFile, false)) {
|
||||||
|
FileUtilities.copyStreamToStream(new FileBytesInputStream(fileBytes, true), out,
|
||||||
|
monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo relocations in the temp file.
|
||||||
|
// NOTE: not all relocations are file-backed, and some are only partially file-backed.
|
||||||
|
try (RandomAccessFile fout = new RandomAccessFile(tempFile, "rw")) {
|
||||||
|
Iterable<Relocation> relocs = () -> program.getRelocationTable().getRelocations();
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
for (Relocation reloc : relocs) {
|
||||||
|
Address addr = reloc.getAddress();
|
||||||
|
AddressSourceInfo addrSourceInfo = memory.getAddressSourceInfo(addr);
|
||||||
|
if (addrSourceInfo == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long offset = addrSourceInfo.getFileOffset();
|
||||||
|
if (offset >= 0) {
|
||||||
|
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, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
if (!tempFile.delete()) {
|
||||||
|
log.appendMsg("Failed to delete malformed file: " + tempFile);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success...it is safe to move the 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) {
|
||||||
|
if (options == null) {
|
||||||
|
options = new ArrayList<>();
|
||||||
|
options.add(new Option(USER_MODS_OPTION_NAME, USER_MODS_OPTION_DEFAULT));
|
||||||
|
if (domainObjectService.getDomainObject() instanceof Program program &&
|
||||||
|
program.getMemory().getAllFileBytes().size() > 1) {
|
||||||
|
options.add(new Option(CREATE_DIR_OPTION_NAME, CREATE_DIR_OPTION_DEFAULT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOptions(List<Option> opt) {
|
||||||
|
options = opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if user byte modifications should be preserved during the export.
|
||||||
|
* <p>
|
||||||
|
* User byte modifications are any modified byte that does not appear in the relocation table
|
||||||
|
* (relocation table entries are assumed to only be populated by the loader).
|
||||||
|
*
|
||||||
|
* @return True if user byte modifications should be preserved during the export; otherwise,
|
||||||
|
* false
|
||||||
|
*/
|
||||||
|
private boolean shouldExportUserModifications() {
|
||||||
|
return OptionUtils.getOption(USER_MODS_OPTION_NAME, options, USER_MODS_OPTION_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if a directory should be created when there are multiple {@link FileBytes}
|
||||||
|
*
|
||||||
|
* @return True if a directory should be created when there are multiple {@link FileBytes}
|
||||||
|
*/
|
||||||
|
private boolean shouldCreateDir() {
|
||||||
|
return OptionUtils.getOption(CREATE_DIR_OPTION_NAME, options, CREATE_DIR_OPTION_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link InputStream} that reads a {@link FileBytes} modified or unmodified (original) bytes
|
||||||
|
*/
|
||||||
|
private static class FileBytesInputStream extends InputStream {
|
||||||
|
|
||||||
|
private final FileBytes fileBytes;
|
||||||
|
private final long size;
|
||||||
|
private long pos;
|
||||||
|
private boolean useModifiedBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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}
|
||||||
|
* @param useModifiedBytes True if modified bytes should be read; false for unmodified
|
||||||
|
* (original) bytes
|
||||||
|
*/
|
||||||
|
FileBytesInputStream(FileBytes fileBytes, boolean useModifiedBytes) {
|
||||||
|
this.fileBytes = fileBytes;
|
||||||
|
this.size = fileBytes.getSize();
|
||||||
|
this.pos = 0;
|
||||||
|
this.useModifiedBytes = useModifiedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (pos >= size) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
byte b;
|
||||||
|
if (useModifiedBytes) {
|
||||||
|
b = fileBytes.getModifiedByte(pos);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
b = fileBytes.getOriginalByte(pos);
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
return Byte.toUnsignedInt(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +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 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", new HelpLocation("ExporterPlugin", "pe"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean supportsFileFormat(String fileFormat) {
|
|
||||||
return PeLoader.PE_NAME.equals(fileFormat);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue