diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteArrayProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteArrayProvider.java index a812f64397..37abf30fa2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteArrayProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteArrayProvider.java @@ -17,6 +17,8 @@ package ghidra.app.util.bin; import java.io.*; +import ghidra.formats.gfilesystem.FSRL; + /** * An implementation of {@link ByteProvider} where the underlying bytes are supplied by a static * byte array. @@ -26,6 +28,7 @@ import java.io.*; public class ByteArrayProvider implements ByteProvider { private byte[] srcBytes; private String name; + private FSRL fsrl; /** * Constructs a {@link ByteArrayProvider} using the specified byte array @@ -33,7 +36,18 @@ public class ByteArrayProvider implements ByteProvider { * @param bytes the underlying byte array */ public ByteArrayProvider(byte[] bytes) { + this(bytes, null); + } + + /** + * Constructs a {@link ByteArrayProvider} using the specified byte array + * + * @param bytes the underlying byte array + * @param fsrl FSRL identity of the file + */ + public ByteArrayProvider(byte[] bytes, FSRL fsrl) { this.srcBytes = bytes; + this.fsrl = fsrl; } /** @@ -52,6 +66,11 @@ public class ByteArrayProvider implements ByteProvider { // don't do anything for now } + @Override + public FSRL getFSRL() { + return fsrl; + } + @Override public File getFile() { return null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteProviderWrapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteProviderWrapper.java index 3497f0cd02..0358c096b5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteProviderWrapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteProviderWrapper.java @@ -17,6 +17,8 @@ package ghidra.app.util.bin; import java.io.*; +import ghidra.formats.gfilesystem.FSRL; + /** * Creates a {@link ByteProvider} constrained to a sub-section of an existing {@link ByteProvider}. */ @@ -24,6 +26,7 @@ public class ByteProviderWrapper implements ByteProvider { private ByteProvider provider; private long subOffset; private long subLength; + private FSRL fsrl; /** * Constructs a {@link ByteProviderWrapper} around the specified {@link ByteProvider} @@ -34,9 +37,23 @@ public class ByteProviderWrapper implements ByteProvider { * @param subLength the length of the new {@link ByteProviderWrapper} */ public ByteProviderWrapper(ByteProvider provider, long subOffset, long subLength) { + this(provider, subOffset, subLength, null); + } + + /** + * Constructs a {@link ByteProviderWrapper} around the specified {@link ByteProvider} + * + * @param provider the {@link ByteProvider} to wrap + * @param subOffset the offset in the {@link ByteProvider} of where to start the new + * {@link ByteProviderWrapper} + * @param subLength the length of the new {@link ByteProviderWrapper} + * @param fsrl FSRL identity of the file this ByteProvider represents + */ + public ByteProviderWrapper(ByteProvider provider, long subOffset, long subLength, FSRL fsrl) { this.provider = provider; this.subOffset = subOffset; this.subLength = subLength; + this.fsrl = fsrl; } @Override @@ -44,6 +61,11 @@ public class ByteProviderWrapper implements ByteProvider { // don't do anything for now } + @Override + public FSRL getFSRL() { + return fsrl; + } + @Override public File getFile() { return provider.getFile(); @@ -51,7 +73,7 @@ public class ByteProviderWrapper implements ByteProvider { @Override public InputStream getInputStream(long index) throws IOException { - return provider.getInputStream(subOffset + index); + return new ByteProviderInputStream(this, index, subLength - index); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/SingleFileSystemIndexHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/SingleFileSystemIndexHelper.java index 7919c815d8..ef7c5b413e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/SingleFileSystemIndexHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/SingleFileSystemIndexHelper.java @@ -59,6 +59,16 @@ public class SingleFileSystemIndexHelper { payloadFile = null; } + /** + * Returns true if the specified file is the payload file. + * + * @param file GFile to test + * @return boolean true if it is the payload file + */ + public boolean isPayloadFile(GFile file) { + return payloadFile.equals(file); + } + /** * Returns true if this object has been {@link #clear()}'ed. * diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/coff/CoffArchiveFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/coff/CoffArchiveFileSystem.java index 41ba600f87..023bf90269 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/coff/CoffArchiveFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/coff/CoffArchiveFileSystem.java @@ -20,7 +20,7 @@ import java.io.InputStream; import java.util.*; import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.bin.ByteProviderInputStream; +import ghidra.app.util.bin.ByteProviderWrapper; import ghidra.app.util.bin.format.coff.CoffException; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; @@ -77,9 +77,15 @@ public class CoffArchiveFileSystem implements GFileSystem { public InputStream getInputStream(GFile file, TaskMonitor monitor) throws IOException, CancelledException { + ByteProvider bp = getByteProvider(file, monitor); + return bp != null ? bp.getInputStream(0) : null; + } + + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) { CoffArchiveMemberHeader entry = fsih.getMetadata(file); return (entry != null && entry.isCOFF()) - ? new ByteProviderInputStream(provider, entry.getPayloadOffset(), entry.getSize()) + ? new ByteProviderWrapper(provider, entry.getPayloadOffset(), entry.getSize(), + file.getFSRL()) : null; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystem.java index efe61a99ef..2f1837706b 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystem.java @@ -16,71 +16,91 @@ package ghidra.file.formats.complzss; import java.io.*; -import java.util.*; +import java.util.List; +import ghidra.app.util.bin.ByteArrayProvider; import ghidra.app.util.bin.ByteProvider; import ghidra.file.formats.lzss.LzssCodec; import ghidra.file.formats.lzss.LzssConstants; import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; -import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory; +import ghidra.util.HashUtilities; import ghidra.util.exception.CancelledException; -import ghidra.util.exception.CryptoException; import ghidra.util.task.TaskMonitor; -@FileSystemInfo(type = "lzss", description = "LZSS Compression", factory = GFileSystemBaseFactory.class) -public class CompLzssFileSystem extends GFileSystemBase { +@FileSystemInfo(type = "lzss", description = "LZSS Compression", factory = CompLzssFileSystemFactory.class) +public class CompLzssFileSystem implements GFileSystem { + private FSRLRoot fsFSRL; + private SingleFileSystemIndexHelper fsIndex; + private FileSystemRefManager fsRefManager = new FileSystemRefManager(this); + private ByteProvider payloadProvider; - private static final String NAME = "lzss_decompressed"; - - private GFileImpl decompressedFile; - private byte[] decompressedBytes; - - public CompLzssFileSystem(String fileSystemName, ByteProvider provider) { - super(fileSystemName, provider); - } - - @Override - public void close() throws IOException { - decompressedFile = null; - decompressedBytes = null; - } - - @Override - protected InputStream getData(GFile file, TaskMonitor monitor) - throws IOException, CancelledException, CryptoException { - if (file != null && file.equals(decompressedFile)) { - return new ByteArrayInputStream(decompressedBytes); - } - return null; - } - - @Override - public List getListing(GFile directory) throws IOException { - return (directory == null || directory.equals(root)) ? Arrays.asList(decompressedFile) - : Collections.emptyList(); - } - - @Override - public boolean isValid(TaskMonitor monitor) throws IOException { - byte[] compressionBytes = provider.readBytes(0, 4); - byte[] lzssBytes = provider.readBytes(4, 4); - - return Arrays.equals(compressionBytes, LzssConstants.SIGNATURE_COMPRESSION_BYTES) && - Arrays.equals(lzssBytes, LzssConstants.SIGNATURE_LZSS_BYTES); - } - - @Override - public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException { + public CompLzssFileSystem(FSRLRoot fsrl, ByteProvider provider, TaskMonitor monitor) + throws IOException { monitor.setMessage("Decompressing LZSS..."); + byte[] compressedBytes = provider.readBytes(LzssConstants.HEADER_LENGTH, provider.length() - LzssConstants.HEADER_LENGTH); ByteArrayOutputStream decompressedBOS = new ByteArrayOutputStream(); LzssCodec.decompress(decompressedBOS, new ByteArrayInputStream(compressedBytes)); - decompressedBytes = decompressedBOS.toByteArray(); - decompressedFile = - GFileImpl.fromFilename(this, root, NAME, false, decompressedBytes.length, null); - debug(decompressedBytes, NAME); + byte[] decompressedBytes = decompressedBOS.toByteArray(); + + String md5 = HashUtilities.getHash(HashUtilities.MD5_ALGORITHM, decompressedBytes); + this.fsIndex = new SingleFileSystemIndexHelper(this, fsFSRL, "lzss_decompressed", + decompressedBytes.length, md5); + this.payloadProvider = + new ByteArrayProvider(decompressedBytes, fsIndex.getPayloadFile().getFSRL()); + } + + @Override + public FSRLRoot getFSRL() { + return fsFSRL; + } + + @Override + public String getName() { + return fsFSRL.getContainer().getName(); + } + + @Override + public FileSystemRefManager getRefManager() { + return fsRefManager; + } + + @Override + public boolean isClosed() { + return payloadProvider == null; + } + + @Override + public void close() throws IOException { + fsRefManager.onClose(); + if (payloadProvider != null) { + payloadProvider.close(); + payloadProvider = null; + } + fsIndex.clear(); + } + + @Override + public InputStream getInputStream(GFile file, TaskMonitor monitor) + throws IOException, CancelledException { + ByteProvider bp = getByteProvider(file, monitor); + return bp != null ? bp.getInputStream(0) : null; + } + + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) { + return fsIndex.isPayloadFile(file) ? payloadProvider : null; + } + + @Override + public List getListing(GFile directory) throws IOException { + return fsIndex.getListing(directory); + } + + @Override + public GFile lookup(String path) throws IOException { + return fsIndex.lookup(path); } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystemFactory.java new file mode 100644 index 0000000000..4c3237e87a --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/complzss/CompLzssFileSystemFactory.java @@ -0,0 +1,50 @@ +/* ### + * 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.file.formats.complzss; + +import java.io.File; +import java.io.IOException; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.file.formats.lzss.LzssCompressionHeader; +import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.factory.GFileSystemFactoryFull; +import ghidra.formats.gfilesystem.factory.GFileSystemProbeBytesOnly; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class CompLzssFileSystemFactory + implements GFileSystemFactoryFull, GFileSystemProbeBytesOnly { + + @Override + public CompLzssFileSystem create(FSRL containerFSRL, FSRLRoot targetFSRL, + ByteProvider byteProvider, File containerFile, FileSystemService fsService, + TaskMonitor monitor) throws IOException, CancelledException { + + CompLzssFileSystem fs = new CompLzssFileSystem(targetFSRL, byteProvider, monitor); + return fs; + } + + @Override + public int getBytesRequired() { + return LzssCompressionHeader.PROBE_BYTES_NEEDED; + } + + @Override + public boolean probeStartBytes(FSRL containerFSRL, byte[] startBytes) { + return LzssCompressionHeader.probe(startBytes); + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cpio/CpioFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cpio/CpioFileSystem.java index 382fed01ca..165b15ff98 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cpio/CpioFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cpio/CpioFileSystem.java @@ -16,86 +16,107 @@ package ghidra.file.formats.cpio; import java.io.*; -import java.util.*; +import java.util.Date; +import java.util.List; import org.apache.commons.compress.archivers.cpio.CpioArchiveEntry; import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream; +import ghidra.app.util.bin.ByteArrayProvider; import ghidra.app.util.bin.ByteProvider; import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.FSUtilities.StreamCopyResult; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; -import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory; +import ghidra.util.NumericUtilities; import ghidra.util.exception.CancelledException; -import ghidra.util.exception.CryptoException; import ghidra.util.task.TaskMonitor; +import utilities.util.FileUtilities; -@FileSystemInfo(type = "cpio", description = "CPIO", factory = GFileSystemBaseFactory.class) -public class CpioFileSystem extends GFileSystemBase { +@FileSystemInfo(type = "cpio", description = "CPIO", factory = CpioFileSystemFactory.class) +public class CpioFileSystem implements GFileSystem { + private FSRLRoot fsFSRL; + private FileSystemIndexHelper fsIndex; + private FileSystemRefManager fsRefManager = new FileSystemRefManager(this); + private ByteProvider provider; - private Map map = new HashMap<>(); - public CpioFileSystem(String fileSystemName, ByteProvider provider) { - super(fileSystemName, provider); - } - - @Override - public boolean isValid(TaskMonitor monitor) throws IOException { - byte[] signature = provider.readBytes(0, 0x10); - return CpioArchiveInputStream.matches(signature, 0x10); - } - - @Override - public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException { + public CpioFileSystem(FSRLRoot fsFSRL, ByteProvider provider, TaskMonitor monitor) + throws IOException { monitor.setMessage("Opening CPIO..."); + this.fsFSRL = fsFSRL; + this.provider = provider; + this.fsIndex = new FileSystemIndexHelper<>(this, fsFSRL); try (CpioArchiveInputStream cpioInputStream = new CpioArchiveInputStream(provider.getInputStream(0))) { CpioArchiveEntry entry; + int fileNum = 0; while ((entry = cpioInputStream.getNextCPIOEntry()) != null) { - skipEntryContents(cpioInputStream, monitor); - storeEntry(entry, monitor); + FileUtilities.copyStreamToStream(cpioInputStream, OutputStream.nullOutputStream(), + monitor); + + monitor.setMessage(entry.getName()); + fsIndex.storeFile(entry.getName(), fileNum++, entry.isDirectory(), + entry.getSize(), entry); } } catch (EOFException e) { // silently ignore EOFExceptions } + catch (IOException e) { + throw e; + } catch (Exception e) { - FSUtilities.displayException(this, null, "Error While Opening CPIO", e.getMessage(), e); + throw new IOException(e); } } @Override public void close() throws IOException { - super.close(); - map.clear(); + fsRefManager.onClose(); + fsIndex.clear(); + if (provider != null) { + provider.close(); + provider = null; + } + } + + @Override + public FSRLRoot getFSRL() { + return fsFSRL; + } + + @Override + public String getName() { + return fsFSRL.getContainer().getName(); + } + + @Override + public boolean isClosed() { + return provider == null; + } + + @Override + public FileSystemRefManager getRefManager() { + return fsRefManager; } @Override public List getListing(GFile directory) throws IOException { - if (directory == null || directory.equals(root)) { - List roots = new ArrayList<>(); - for (GFile file : map.keySet()) { - if (file.getParentFile() == root || file.getParentFile().equals(root)) { - roots.add(file); - } - } - return roots; - } - List tmp = new ArrayList<>(); - for (GFile file : map.keySet()) { - if (file.getParentFile() == null) { - continue; - } - if (file.getParentFile().equals(directory)) { - tmp.add(file); - } - } - return tmp; + return fsIndex.getListing(directory); + } + + @Override + public GFile lookup(String path) throws IOException { + return fsIndex.lookup(path); } @Override public String getInfo(GFile file, TaskMonitor monitor) { - CpioArchiveEntry entry = map.get(file); + CpioArchiveEntry entry = fsIndex.getMetadata(file); + if (entry == null) { + return null; + } StringBuilder buffer = new StringBuilder(); try { buffer.append("Name: " + entry.getName() + "\n"); @@ -129,84 +150,46 @@ public class CpioFileSystem extends GFileSystemBase { } @Override - protected InputStream getData(GFile file, TaskMonitor monitor) - throws IOException, CancelledException, CryptoException { - CpioArchiveEntry fileEntry = map.get(file); - if (!fileEntry.isRegularFile()) { + public InputStream getInputStream(GFile file, TaskMonitor monitor) + throws IOException, CancelledException { + ByteProvider bp = getByteProvider(file, monitor); + return bp != null ? bp.getInputStream(0) : null; + } + + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) + throws IOException, CancelledException { + CpioArchiveEntry targetEntry = fsIndex.getMetadata(file); + if (targetEntry == null) { + return null; + } + if (!targetEntry.isRegularFile()) { throw new IOException("CPIO entry " + file.getName() + " is not a regular file."); } try (CpioArchiveInputStream cpioInputStream = - new CpioArchiveInputStream(provider.getInputStream(0));) { + new CpioArchiveInputStream(provider.getInputStream(0))) { - CpioArchiveEntry entry; - while ((entry = cpioInputStream.getNextCPIOEntry()) != null) { - if (!entry.equals(fileEntry)) { - skipEntryContents(cpioInputStream, monitor); - } - else { - byte[] entryBytes = readEntryContents(cpioInputStream, monitor); - return new ByteArrayInputStream(entryBytes); + CpioArchiveEntry currentEntry; + while ((currentEntry = cpioInputStream.getNextCPIOEntry()) != null) { + if (currentEntry.equals(targetEntry)) { + return getByteProviderForEntry(cpioInputStream, file.getFSRL(), monitor); } + FileUtilities.copyStreamToStream(cpioInputStream, OutputStream.nullOutputStream(), + monitor); } } catch (IllegalArgumentException e) { - //unknown MODES.. + throw new IOException(e); } - return null; + throw new IOException("Unable to seek to entry: " + file.getName()); } - private void storeEntry(CpioArchiveEntry entry, TaskMonitor monitor) { - monitor.setMessage(entry.getName()); - GFileImpl file = GFileImpl.fromPathString(this, root, entry.getName(), null, - entry.isDirectory(), entry.getSize()); - storeFile(file, entry, monitor); - } - - private void storeFile(GFile file, CpioArchiveEntry entry, TaskMonitor monitor) { - if (monitor.isCancelled()) { - return; - } - if (file == null) { - return; - } - if (file.equals(root)) { - return; - } - if (!map.containsKey(file) || map.get(file) == null) { - map.put(file, entry); - } - GFile parentFile = file.getParentFile(); - storeFile(parentFile, null, monitor); - } - - private byte[] readEntryContents(CpioArchiveInputStream cpioInputStream, TaskMonitor monitor) - throws IOException, CancelledException { + private ByteProvider getByteProviderForEntry(CpioArchiveInputStream cpioInputStream, FSRL fsrl, + TaskMonitor monitor) throws CancelledException, IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buffer = new byte[64 * 1024]; - while (true) { - if (monitor.isCancelled()) { - throw new CancelledException(); - } - int bytesRead = cpioInputStream.read(buffer); - if (bytesRead <= 0) { - break; - } - out.write(buffer, 0, bytesRead); - } - return out.toByteArray(); - } - - private void skipEntryContents(CpioArchiveInputStream cpioInputStream, TaskMonitor monitor) - throws IOException, CancelledException { - byte[] buffer = new byte[64 * 1024]; - while (true) { - if (monitor.isCancelled()) { - throw new CancelledException(); - } - int bytesRead = cpioInputStream.read(buffer); - if (bytesRead <= 0) { - break; - } + StreamCopyResult copyResult = FSUtilities.streamCopy(cpioInputStream, out, monitor); + if (fsrl.getMD5() == null) { + fsrl = fsrl.withMD5(NumericUtilities.convertBytesToString(copyResult.md5)); } + return new ByteArrayProvider(out.toByteArray(), fsrl); } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cpio/CpioFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cpio/CpioFileSystemFactory.java new file mode 100644 index 0000000000..ecccf07380 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cpio/CpioFileSystemFactory.java @@ -0,0 +1,51 @@ +/* ### + * 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.file.formats.cpio; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.factory.GFileSystemFactoryFull; +import ghidra.formats.gfilesystem.factory.GFileSystemProbeBytesOnly; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class CpioFileSystemFactory + implements GFileSystemFactoryFull, GFileSystemProbeBytesOnly { + + @Override + public CpioFileSystem create(FSRL containerFSRL, FSRLRoot targetFSRL, + ByteProvider byteProvider, File containerFile, FileSystemService fsService, + TaskMonitor monitor) throws IOException, CancelledException { + + CpioFileSystem fs = new CpioFileSystem(targetFSRL, byteProvider, monitor); + return fs; + } + + @Override + public int getBytesRequired() { + return 6; // CpioArchiveInputStream doesn't have a value we can use, so we hard code + } + + @Override + public boolean probeStartBytes(FSRL containerFSRL, byte[] startBytes) { + return CpioArchiveInputStream.matches(startBytes, startBytes.length); + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/gzip/GZipFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/gzip/GZipFileSystem.java index 80957271c3..78bccd2390 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/gzip/GZipFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/gzip/GZipFileSystem.java @@ -46,7 +46,6 @@ public class GZipFileSystem implements GFileSystem { private final SingleFileSystemIndexHelper fsIndex; private final FileSystemService fsService; - private GFile payload; private String origFilename; private String payloadKey; private String origComment; @@ -64,7 +63,6 @@ public class GZipFileSystem implements GFileSystem { FileCacheEntry fce = getPayloadFileCacheEntry(monitor); this.fsIndex = new SingleFileSystemIndexHelper(this, fsFSRL, origFilename, fce.file.length(), fce.md5); - this.payload = fsIndex.getPayloadFile(); } private void readGzipMetadata(File containerFile, TaskMonitor monitor) throws IOException { @@ -101,7 +99,7 @@ public class GZipFileSystem implements GFileSystem { } public GFile getPayloadFile() { - return payload; + return fsIndex.getPayloadFile(); } @Override @@ -118,7 +116,6 @@ public class GZipFileSystem implements GFileSystem { public void close() throws IOException { refManager.onClose(); fsIndex.clear(); - payload = null; } @Override @@ -134,7 +131,7 @@ public class GZipFileSystem implements GFileSystem { @Override public InputStream getInputStream(GFile file, TaskMonitor monitor) throws IOException, CancelledException { - if (payload.equals(file)) { + if (fsIndex.isPayloadFile(file)) { FileCacheEntry fce = getPayloadFileCacheEntry(monitor); return new FileInputStream(fce.file); } @@ -148,13 +145,14 @@ public class GZipFileSystem implements GFileSystem { @Override public String getInfo(GFile file, TaskMonitor monitor) { - if (payload.equals(file)) { + if (fsIndex.isPayloadFile(file)) { return FSUtilities.infoMapToString(getInfoMap()); } return null; } public Map getInfoMap() { + GFile payload = fsIndex.getPayloadFile(); Map info = new LinkedHashMap<>(); info.put("Name", payload.getName()); info.put("Size", Long.toString(payload.getLength())); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660Directory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660Directory.java index 72bbc1f397..e33764dde6 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660Directory.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660Directory.java @@ -15,14 +15,15 @@ */ package ghidra.file.formats.iso9660; -import ghidra.app.util.bin.*; -import ghidra.program.model.data.*; -import ghidra.util.exception.DuplicateNameException; - -import java.io.*; +import java.io.IOException; import java.time.DateTimeException; import java.time.LocalDateTime; +import ghidra.app.util.bin.*; +import ghidra.formats.gfilesystem.FSRL; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + public class ISO9660Directory implements StructConverter { private int directoryRecordLength; @@ -175,21 +176,11 @@ public class ISO9660Directory implements StructConverter { return false; } - /** - * Returns the bytes of the file which this directory points to - * @param provider The ByteProvider - * @param logicalBlockSize - * @return An InputStream of the data bytes - * @throws IOException - */ - public InputStream getDataBytes(ByteProvider provider, long logicalBlockSize) - throws IOException { + ByteProvider getByteProvider(ByteProvider provider, long logicalBlockSize, FSRL fsrl) { - if (!(this.isDirectoryFlagSet())) { + if (!this.isDirectoryFlagSet()) { long index = locationOfExtentLE * logicalBlockSize; - byte[] dataBytes = provider.readBytes(index, dataLengthLE); - InputStream inputStream = new ByteArrayInputStream(dataBytes); - return inputStream; + return new ByteProviderWrapper(provider, index, dataLengthLE, fsrl); } return null; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java index 4978ba06b9..312dac9f50 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java @@ -15,10 +15,9 @@ */ package ghidra.file.formats.iso9660; -import java.util.*; - import java.io.IOException; import java.io.InputStream; +import java.util.*; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.ByteProvider; @@ -148,10 +147,14 @@ public class ISO9660FileSystem extends GFileSystemBase { protected InputStream getData(GFile file, TaskMonitor monitor) throws IOException, CancelledException, CryptoException { - ISO9660Directory dir = fileToDirectoryMap.get(file); - InputStream inputStream = dir.getDataBytes(provider, logicalBlockSize); + ByteProvider bp = getByteProvider(file, monitor); + return bp != null ? bp.getInputStream(0) : null; + } - return inputStream; + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) + throws IOException, CancelledException { + ISO9660Directory dir = fileToDirectoryMap.get(file); + return dir.getByteProvider(provider, logicalBlockSize, file.getFSRL()); } /* diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java index c727f5fd9e..fedbe1c25f 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java @@ -126,7 +126,7 @@ public class JavaClassDecompilerFileSystem implements GFileSystem { @Override public InputStream getInputStream(GFile file, TaskMonitor monitor) throws IOException, CancelledException { - if (fsIndexHelper.getPayloadFile().equals(file)) { + if (fsIndexHelper.isPayloadFile(file)) { FileCacheEntry fce = getDecompiledJavaSrcFileEntry(monitor); return new FileInputStream(fce.file); } @@ -140,7 +140,7 @@ public class JavaClassDecompilerFileSystem implements GFileSystem { @Override public String getInfo(GFile file, TaskMonitor monitor) { - if (fsIndexHelper.getPayloadFile().equals(file)) { + if (fsIndexHelper.isPayloadFile(file)) { Map info = new HashMap<>(); info.put("Class name", className); return FSUtilities.infoMapToString(info); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/lzss/LzssCompressionHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/lzss/LzssCompressionHeader.java index 4ff5d44dcc..37eb52b9bd 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/lzss/LzssCompressionHeader.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/lzss/LzssCompressionHeader.java @@ -15,20 +15,46 @@ */ package ghidra.file.formats.lzss; +import java.io.IOException; + import ghidra.app.util.bin.*; import ghidra.program.model.data.DataType; import ghidra.util.exception.DuplicateNameException; -import java.io.IOException; - public class LzssCompressionHeader implements StructConverter { - private int signature; - private int compressionType; - private int checksum; - private int decompressedLength; - private int compressedLength; - private byte [] padding; + public static final int PROBE_BYTES_NEEDED = 8; // sizeof(signature) + sizeof(compressionType) + + /** + * Returns true if the bytes have the magic signature of a LzssCompressionHeader. + * + * @param startBytes byte array + * @return boolean true if the signature of a LzssCompressionHeader appears at the beginning + * the byte array + */ + public static boolean probe(byte[] startBytes) { + try { + if (startBytes.length < PROBE_BYTES_NEEDED) { + return false; + } + BinaryReader reader = new BinaryReader(new ByteArrayProvider(startBytes), false); + + int signature = reader.readNextInt(); + int compressionType = reader.readNextInt(); + return signature == LzssConstants.SIGNATURE_LZSS && + compressionType == LzssConstants.SIGNATURE_COMPRESSION; + } + catch (IOException e) { + return false; + } + } + + private int signature; + private int compressionType; + private int checksum; + private int decompressedLength; + private int compressedLength; + private byte[] padding; public LzssCompressionHeader(ByteProvider provider) throws IOException { BinaryReader reader = new BinaryReader(provider, false); @@ -60,6 +86,7 @@ public class LzssCompressionHeader implements StructConverter { return padding; } + @Override public DataType toDataType() throws DuplicateNameException, IOException { return StructConverterUtil.toDataType(this); } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/omf/OmfArchiveFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/omf/OmfArchiveFileSystem.java index 365b13fc1f..11173f1928 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/omf/OmfArchiveFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/omf/OmfArchiveFileSystem.java @@ -98,9 +98,15 @@ public class OmfArchiveFileSystem implements GFileSystem { public InputStream getInputStream(GFile file, TaskMonitor monitor) throws IOException, CancelledException { + ByteProvider bp = getByteProvider(file, monitor); + return bp != null ? bp.getInputStream(0) : null; + } + + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) { OmfLibraryRecord.MemberHeader member = fsih.getMetadata(file); return (member != null) - ? new ByteProviderInputStream(provider, member.payloadOffset, member.size) + ? new ByteProviderWrapper(provider, member.payloadOffset, member.size, + file.getFSRL()) : null; } diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/HashUtilities.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/HashUtilities.java index ba24057897..ed6900fe04 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/HashUtilities.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/util/HashUtilities.java @@ -170,6 +170,21 @@ public class HashUtilities { return NumericUtilities.convertBytesToString(messageDigest.digest()); } + /** + * Generate combined message digest hash for the bytes in the specified array. + * + * @param algorithm message digest algorithm + * @param values array of bytes to hash + * @return message digest hash + * @throws IllegalArgumentException if specified algorithm is not supported + * @see MessageDigest for supported hash algorithms + */ + public static String getHash(String algorithm, byte[] values) { + MessageDigest messageDigest = getMessageDigest(algorithm); + messageDigest.update(values); + return NumericUtilities.convertBytesToString(messageDigest.digest()); + } + private static MessageDigest getMessageDigest(String algorithm) { try { return MessageDigest.getInstance(algorithm);