mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +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>
|
||||
<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="#gzf">Ghidra Zip File (.gzf)</A></LI>
|
||||
|
||||
<LI><A href="#html">HTML</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>
|
||||
</UL>
|
||||
|
@ -238,17 +240,6 @@
|
|||
</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>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
@ -284,22 +275,8 @@
|
|||
</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>
|
||||
<H3><A name="gzf"/>Ghidra Zip File (.gzf)</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<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>
|
||||
</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>
|
||||
<P>The Intel Hex format, a printable file representing memory images, was originally
|
||||
|
@ -345,18 +322,57 @@
|
|||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="pe"></A>PE</H3>
|
||||
<H3><A name="original_file"/><A name="Options_Original_File"/>Original File</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>Writes a program back to its original file layout. By default, any file-backed bytes
|
||||
that were modified by the user in the program database will be reflected in the new file.
|
||||
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
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
||||
<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
|
||||
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
|
||||
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>
|
||||
|
||||
<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;
|
||||
|
||||
/**
|
||||
* An implementation of exporter that creates
|
||||
* an Binary representation of the program.
|
||||
* An {@link Exporter} that can export memory blocks as raw bytes
|
||||
*/
|
||||
public class BinaryExporter extends Exporter {
|
||||
|
||||
public BinaryExporter() {
|
||||
super("Binary", "bin", new HelpLocation("ExporterPlugin", "binary"));
|
||||
super("Raw Bytes", "bin", new HelpLocation("ExporterPlugin", "binary"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,8 +52,6 @@ public class BinaryExporter extends Exporter {
|
|||
addrSet = memory;
|
||||
}
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
|
||||
AddressSet set = new AddressSet(addrSet);
|
||||
|
||||
//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();
|
||||
while (iter.hasNext()) {
|
||||
AddressRange range = iter.next();
|
||||
|
@ -77,9 +74,6 @@ public class BinaryExporter extends Exporter {
|
|||
catch (MemoryAccessException e) {
|
||||
throw new ExporterException(e);
|
||||
}
|
||||
finally {
|
||||
fos.close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -91,5 +85,6 @@ public class BinaryExporter extends Exporter {
|
|||
|
||||
@Override
|
||||
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