diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteProviderSeekableByteChannel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteProviderSeekableByteChannel.java new file mode 100644 index 0000000000..9124636c0b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ByteProviderSeekableByteChannel.java @@ -0,0 +1,103 @@ +/* ### + * 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.bin; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SeekableByteChannel; + +/** + * Adapter between Ghidra {@link ByteProvider} and java nio {@link SeekableByteChannel} + */ +public class ByteProviderSeekableByteChannel implements SeekableByteChannel { + + protected ByteProvider bp; + protected long position; + + public ByteProviderSeekableByteChannel(ByteProvider bp) { + this.bp = bp; + } + + @Override + public boolean isOpen() { + return bp != null; + } + + @Override + public void close() throws IOException { + if (bp != null) { + bp.close(); + bp = null; + } + } + + @Override + public int read(ByteBuffer dst) throws IOException { + ensureOpen(); + + long bpRemaining = bp.length() - position; + if (bpRemaining <= 0) { + return -1; + } + + int bufRemaining = dst.remaining(); + int bytesToRead = (int) Math.min(bufRemaining, bpRemaining); + + byte[] tmp = bp.readBytes(position, bytesToRead); + + dst.put(tmp, 0, tmp.length); + + position += tmp.length; + return tmp.length; + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new IOException("unimplemented"); + } + + @Override + public long position() throws IOException { + ensureOpen(); + return position; + } + + @Override + public SeekableByteChannel position(long newPosition) throws IOException { + ensureOpen(); + this.position = newPosition; + return this; + } + + @Override + public long size() throws IOException { + ensureOpen(); + return bp.length(); + } + + @Override + public SeekableByteChannel truncate(long size) throws IOException { + throw new IOException("unimplemented"); + } + + private void ensureOpen() throws IOException { + if (bp == null) { + throw new ClosedChannelException(); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java index 879c31fc6e..add4505674 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java @@ -38,10 +38,10 @@ public class FileSystemIndexHelper { private FileData rootDir; static class FileData { + final long fileIndex; GFile file; METADATATYPE metaData; - final long fileIndex; - final String symlinkPath; + String symlinkPath; FileData(GFile file, METADATATYPE metaData, long fileIndex) { this(file, metaData, fileIndex, null); @@ -137,6 +137,15 @@ public class FileSystemIndexHelper { return (fileData != null) ? fileData.file : null; } + /** + * {@return the index of the specified file} + * @param file GFile + */ + public synchronized long getFileIndex(GFile file) { + FileData fd = file == null ? rootDir : fileToEntryMap.get(file); + return fd != null ? fd.fileIndex : -1; + } + /** * Mirrors {@link GFileSystem#getListing(GFile)} interface. * @@ -270,11 +279,28 @@ public class FileSystemIndexHelper { return fd != null ? fd.file : null; } + /** + * {@return the symlink path that the specified file contains} + * @param file GFile + */ public synchronized String getSymlinkPath(GFile file) { FileData fd = file == null ? rootDir : fileToEntryMap.get(file); return fd != null ? fd.symlinkPath : null; } + /** + * Update the symlink path of the specified file + * + * @param file GFile + * @param newSymlinkPath string path + */ + public synchronized void updateSymlinkPath(GFile file, String newSymlinkPath) { + FileData fd = fileToEntryMap.get(file); + if (fd != null) { + fd.symlinkPath = newSymlinkPath; + } + } + private FileData getFileData(GFile f) throws IOException { if (f == null) { return rootDir; diff --git a/Ghidra/Features/FileFormats/Module.manifest b/Ghidra/Features/FileFormats/Module.manifest index d3b4445614..434fc3e6ac 100644 --- a/Ghidra/Features/FileFormats/Module.manifest +++ b/Ghidra/Features/FileFormats/Module.manifest @@ -5,8 +5,7 @@ MODULE FILE LICENSE: lib/dex-translator-2.4.24.jar Apache License 2.0 MODULE FILE LICENSE: lib/baksmali-2.5.2.jar BSD-3-GRUVER MODULE FILE LICENSE: lib/dexlib2-2.5.2.jar BSD-3-GRUVER MODULE FILE LICENSE: lib/util-2.5.2.jar BSD-3-GRUVER -MODULE FILE LICENSE: lib/sevenzipjbinding-16.02-2.01.jar LGPL 2.1 -MODULE FILE LICENSE: lib/sevenzipjbinding-all-platforms-16.02-2.01.jar LGPL 2.1 +MODULE FILE LICENSE: lib/zip4j-2.11.6.jar Apache License 2.0 MODULE FILE LICENSE: lib/AXMLPrinter2.jar Apache License 2.0 MODULE FILE LICENSE: os/win_86_64/lzfse.exe BSD-3-APPLE MODULE FILE LICENSE: os/mac_86_64/lzfse BSD-3-APPLE diff --git a/Ghidra/Features/FileFormats/build.gradle b/Ghidra/Features/FileFormats/build.gradle index e70ae01188..91c10c5c5d 100644 --- a/Ghidra/Features/FileFormats/build.gradle +++ b/Ghidra/Features/FileFormats/build.gradle @@ -45,9 +45,8 @@ dependencies { // Used by "Android XML" file system api ':AXMLPrinter2' - // Used by "ZIP" and "7zip" file systems - api 'net.sf.sevenzipjbinding:sevenzipjbinding:16.02-2.01' - runtimeOnly 'net.sf.sevenzipjbinding:sevenzipjbinding-all-platforms:16.02-2.01' + // Used by Zip file system + api 'net.lingala.zip4j:zip4j:2.11.6' // include code from src/test/slow in Base testImplementation project(path: ':Base', configuration: 'integrationTestArtifacts') @@ -61,41 +60,4 @@ rootProject.assembleDistribution { } } -// *********************************************************************************************** -// Sevenzip native library extract task -// *********************************************************************************************** -// The following extracts native libraries from upstream's sevenzipjbinding allPlatform jar -// and places the uncompressed native libraries into a ghidra folder so that they can be used to -// initialize the sevenzipjbinding library without needing to extract the native libraries at run time. -// This is necessary due to bugs in upstream's initSevenZipFromPlatformJAR() that can cause core -// dumps in java processes that have previously loaded the native library. See comments -// at the top of ghidra.file.formats.sevenzip.SevenZipCustomInitializer. -// This gradle task can be removed when SevenZipCustomInitializer is no longer needed. -String getSevenZipJarPath() { - List libs = getExternalRuntimeDependencies(project); - for(String lib: libs) { - if (lib.contains("sevenzipjbinding-all-platforms")) { - return lib; - } - } - return null -} - -task extractSevenZipNativeLibs(type: Copy) { - gradle.taskGraph.whenReady { - String jarPath = getSevenZipJarPath(); - from zipTree(jarPath) - include "Linux-amd64/*.so" - include "Windows-amd64/*.dll" - include "Mac-x86_64/*.dylib" - exclude "META-INF" - exclude "Linux-i386" - exclude "Windows-x86" - into ("build/data/sevenzipnativelibs") - } -} - -rootProject.prepDev.dependsOn extractSevenZipNativeLibs -jar.dependsOn extractSevenZipNativeLibs -// end of sevenzip native library extract task code diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/Bzip2FileSystem.java similarity index 52% rename from Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystem.java rename to Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/Bzip2FileSystem.java index d17b44147f..f3de563fe4 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/Bzip2FileSystem.java @@ -4,25 +4,36 @@ * 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.ios.hfs; +package ghidra.file.formats.bzip2; -import ghidra.file.formats.sevenzip.SevenZipFileSystem; +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.AbstractSinglePayloadFileSystem; import ghidra.formats.gfilesystem.FSRLRoot; -import ghidra.formats.gfilesystem.FileSystemService; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; +import ghidra.formats.gfilesystem.fileinfo.FileAttributes; -@FileSystemInfo(type = "hfsplus", description = "Apple HFS+ Disk Volume", factory = HFSPlusFileSystemFactory.class) -public class HFSPlusFileSystem extends SevenZipFileSystem { - public HFSPlusFileSystem(FSRLRoot fsrl, FileSystemService fsService) { - super(fsrl, fsService); +//@formatter:off +@FileSystemInfo( + type = "bzip2", + description = "BZip2", + factory = Bzip2FileSystemFactory.class, + extensions = { "bz2" } +) +//@formatter:on +public class Bzip2FileSystem extends AbstractSinglePayloadFileSystem { + + public Bzip2FileSystem(FSRLRoot fsFSRL, ByteProvider payloadProvider, String payloadFilename, + FileAttributes attrs) { + super(fsFSRL, payloadProvider, payloadFilename, attrs); } + } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/Bzip2FileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/Bzip2FileSystemFactory.java new file mode 100644 index 0000000000..fd564c584f --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/Bzip2FileSystemFactory.java @@ -0,0 +1,69 @@ +/* ### + * 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.bzip2; + +import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*; + +import java.io.IOException; + +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider; +import ghidra.formats.gfilesystem.factory.GFileSystemProbeBytesOnly; +import ghidra.formats.gfilesystem.fileinfo.FileAttribute; +import ghidra.formats.gfilesystem.fileinfo.FileAttributes; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class Bzip2FileSystemFactory + implements GFileSystemFactoryByteProvider, GFileSystemProbeBytesOnly { + + @Override + public Bzip2FileSystem create(FSRLRoot targetFSRL, ByteProvider provider, + FileSystemService fsService, TaskMonitor monitor) + throws IOException, CancelledException { + + String containerName = targetFSRL.getContainer().getName(); + String payloadFilename = containerName.endsWith(".bz2") + ? containerName.substring(0, containerName.length() - ".bz2".length()) + : containerName + ".uncompressed"; + + ByteProvider payloadProvider = + fsService.getDerivedByteProvider(provider.getFSRL(), null, payloadFilename, -1, + () -> new BZip2CompressorInputStream(provider.getInputStream(0)), monitor); + + FileAttributes fileAttrs = FileAttributes.of( // attrs + FileAttribute.create(COMPRESSED_SIZE_ATTR, provider.length()), + FileAttribute.create(SIZE_ATTR, payloadProvider.length())); + + Bzip2FileSystem fs = + new Bzip2FileSystem(targetFSRL, payloadProvider, payloadFilename, fileAttrs); + + return fs; + } + + @Override + public int getBytesRequired() { + return 3 /* copied from from BZip2CompressorInputStream's matches() logic */; + } + + @Override + public boolean probeStartBytes(FSRL containerFSRL, byte[] startBytes) { + return BZip2CompressorInputStream.matches(startBytes, startBytes.length); + } +} 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/bzip2/XzFileSystem.java similarity index 53% rename from Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java rename to Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/XzFileSystem.java index d58d71d808..74477ebfa1 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/XzFileSystem.java @@ -4,26 +4,36 @@ * 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.iso9660; +package ghidra.file.formats.bzip2; -import ghidra.file.formats.sevenzip.SevenZipFileSystem; +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.AbstractSinglePayloadFileSystem; import ghidra.formats.gfilesystem.FSRLRoot; -import ghidra.formats.gfilesystem.FileSystemService; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; +import ghidra.formats.gfilesystem.fileinfo.FileAttributes; -@FileSystemInfo(type = "iso9660", description = "ISO 9660", factory = ISO9660FileSystemFactory.class) -public class ISO9660FileSystem extends SevenZipFileSystem { +//@formatter:off +@FileSystemInfo( + type = "xz", + description = "XZ", + factory = XzFileSystemFactory.class, + extensions = { "xz" } +) +//@formatter:on +public class XzFileSystem extends AbstractSinglePayloadFileSystem { - public ISO9660FileSystem(FSRLRoot fsrl, FileSystemService fsService) { - super(fsrl, fsService); + public XzFileSystem(FSRLRoot fsFSRL, ByteProvider payloadProvider, String payloadFilename, + FileAttributes attrs) { + super(fsFSRL, payloadProvider, payloadFilename, attrs); } + } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/XzFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/XzFileSystemFactory.java new file mode 100644 index 0000000000..2d86adf63a --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/bzip2/XzFileSystemFactory.java @@ -0,0 +1,69 @@ +/* ### + * 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.bzip2; + +import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*; + +import java.io.IOException; + +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; +import org.tukaani.xz.XZ; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider; +import ghidra.formats.gfilesystem.factory.GFileSystemProbeBytesOnly; +import ghidra.formats.gfilesystem.fileinfo.FileAttribute; +import ghidra.formats.gfilesystem.fileinfo.FileAttributes; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class XzFileSystemFactory + implements GFileSystemFactoryByteProvider, GFileSystemProbeBytesOnly { + + @Override + public XzFileSystem create(FSRLRoot targetFSRL, ByteProvider provider, + FileSystemService fsService, TaskMonitor monitor) + throws IOException, CancelledException { + + String containerName = targetFSRL.getContainer().getName(); + String payloadFilename = containerName.endsWith(".xz") + ? containerName.substring(0, containerName.length() - ".xz".length()) + : containerName + ".uncompressed"; + + ByteProvider payloadProvider = + fsService.getDerivedByteProvider(provider.getFSRL(), null, payloadFilename, -1, + () -> new XZCompressorInputStream(provider.getInputStream(0)), monitor); + + FileAttributes fileAttrs = FileAttributes.of( // attrs + FileAttribute.create(COMPRESSED_SIZE_ATTR, provider.length()), + FileAttribute.create(SIZE_ATTR, payloadProvider.length())); + + XzFileSystem fs = new XzFileSystem(targetFSRL, payloadProvider, payloadFilename, fileAttrs); + + return fs; + } + + @Override + public int getBytesRequired() { + return XZ.HEADER_MAGIC.length; + } + + @Override + public boolean probeStartBytes(FSRL containerFSRL, byte[] startBytes) { + return XZCompressorInputStream.matches(startBytes, startBytes.length); + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cabarc/CabarcFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cabarc/CabarcFileSystem.java new file mode 100644 index 0000000000..19d5e81437 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cabarc/CabarcFileSystem.java @@ -0,0 +1,109 @@ +/* ### + * 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.cabarc; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.file.cliwrapper.ArchiverCliToolWrapper.Entry; +import ghidra.file.formats.sevenzip.SevenZipCliToolWrapper; +import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.annotations.FileSystemInfo; +import ghidra.formats.gfilesystem.fileinfo.FileType; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** +* File system that handles Microsoft CAB archive files (including compressed symbol server files). +*

+* Requires the user having installed a command-line 7z tool somewhere in their PATH. +*

+* If the container file isn't a plain file, the container will be exported to a temp file so that +* the 7z cli tool can access it. +*/ +//@formatter:off +@FileSystemInfo( + type = "cabarc", + description = "Microsoft CAB Archive", + factory = CabarcFileSystemFactory.class, + extensions = { "cab" } +) +//@formatter:on +public class CabarcFileSystem extends AbstractFileSystem { + private SevenZipCliToolWrapper cliTool; + + private File archiveFile; + private File tmpFile; + + public CabarcFileSystem(FSRLRoot fsrl, FileSystemService fsService, + SevenZipCliToolWrapper cliTool, File archiveFile, File tmpFile) { + super(fsrl, fsService); + + this.cliTool = cliTool; + this.archiveFile = archiveFile; + this.tmpFile = tmpFile; + } + + @Override + public void close() throws IOException { + refManager.onClose(); + + if (tmpFile != null && + tmpFile.getName().startsWith(CabarcFileSystemFactory.CABARC_TMPFILE_PREFIX)) { + tmpFile.delete(); + tmpFile = null; + } + archiveFile = null; + + fsIndex.clear(); + } + + public void mount(TaskMonitor monitor) throws IOException { + if (!cliTool.isValid(monitor)) { + throw new IOException("Bad cli tool"); + } + List listing = cliTool.getListing(archiveFile, monitor); + int fileIndex = 0; + for (Entry entry : listing) { + fsIndex.storeFile(entry.name(), fileIndex++, entry.fileType() == FileType.DIRECTORY, + entry.size(), entry); + } + } + + @Override + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) + throws IOException, CancelledException { + GFile resolvedFile = fsIndex.resolveSymlinks(file); // probably not needed with sz + + Entry entry = fsIndex.getMetadata(resolvedFile); + if (entry == null) { + throw new IOException("Unknown file " + file); + } + + ByteProvider fileBP = fsService.getDerivedByteProviderPush(fsFSRL.getContainer(), null, + file.getPath(), -1, (os) -> cliTool.extract(archiveFile, entry, os, monitor), monitor); + + return fileBP; + } + + @Override + public boolean isClosed() { + return archiveFile == null; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cabarc/CabarcFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cabarc/CabarcFileSystemFactory.java new file mode 100644 index 0000000000..e49e17c5e3 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cabarc/CabarcFileSystemFactory.java @@ -0,0 +1,91 @@ +/* ### + * 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.cabarc; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.file.formats.sevenzip.SevenZipCliToolWrapper; +import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.factory.*; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class CabarcFileSystemFactory + implements GFileSystemFactoryByteProvider, GFileSystemProbeBytesOnly { + + static final String CABARC_TMPFILE_PREFIX = "cabarc_tmp_"; + private static final byte[] CABARC_SIGNATURE_MAGIC = + NumericUtilities.convertStringToBytes("4d53434600000000"); // "MSCF", 00000000 + + private SevenZipCliToolWrapper cliTool; + + @Override + public int getBytesRequired() { + return CABARC_SIGNATURE_MAGIC.length; + } + + @Override + public boolean probeStartBytes(FSRL containerFSRL, byte[] startBytes) { + return Arrays.equals(startBytes, 0, CABARC_SIGNATURE_MAGIC.length, CABARC_SIGNATURE_MAGIC, + 0, CABARC_SIGNATURE_MAGIC.length); + } + + @Override + public GFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider, + FileSystemService fsService, TaskMonitor monitor) + throws IOException, CancelledException { + + try { + ensureTool(monitor); + + File archiveFile = fsService.getFileIfAvailable(byteProvider); + File tmpFile = null; + if (archiveFile == null) { + archiveFile = tmpFile = + fsService.createPlaintextTempFile(byteProvider, CABARC_TMPFILE_PREFIX, monitor); + } + + CabarcFileSystem fs = + new CabarcFileSystem(targetFSRL, fsService, cliTool, archiveFile, tmpFile); + + try { + fs.mount(monitor); + return fs; + } + catch (IOException e) { + FSUtilities.uncheckedClose(fs, null); + throw e; + } + } + finally { + FSUtilities.uncheckedClose(byteProvider, null); + } + } + + private void ensureTool(TaskMonitor monitor) throws IOException { + if (cliTool == null) { + cliTool = SevenZipCliToolWrapper.findTool(monitor); + } + if (cliTool == null) { + throw new FileSystemFactoryDependencyException("No 7z cli tool found in PATH"); + } + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystemFactory.java deleted file mode 100644 index 5decfd182d..0000000000 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystemFactory.java +++ /dev/null @@ -1,53 +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.file.formats.ios.hfs; - -import java.io.IOException; - -import ghidra.app.util.bin.ByteProvider; -import ghidra.formats.gfilesystem.FSRLRoot; -import ghidra.formats.gfilesystem.FileSystemService; -import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider; -import ghidra.formats.gfilesystem.factory.GFileSystemProbeByteProvider; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; - -public class HFSPlusFileSystemFactory - implements GFileSystemFactoryByteProvider, GFileSystemProbeByteProvider { - - @Override - public boolean probe(ByteProvider byteProvider, FileSystemService fsService, - TaskMonitor monitor) throws IOException, CancelledException { - return HFSPlusVolumeHeader.probe(byteProvider); - } - - @Override - public HFSPlusFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider, - FileSystemService fsService, TaskMonitor monitor) - throws IOException, CancelledException { - - HFSPlusFileSystem fs = new HFSPlusFileSystem(targetFSRL, fsService); - try { - fs.mount(byteProvider, monitor); - return fs; - } - catch (IOException ioe) { - fs.close(); - throw ioe; - } - } - -} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusVolumeHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusVolumeHeader.java deleted file mode 100644 index 3acad38e4e..0000000000 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusVolumeHeader.java +++ /dev/null @@ -1,145 +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.file.formats.ios.hfs; - -import java.io.IOException; - -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.ByteProvider; - -/** - * Apple HFS+ volume header. - *

- * See https://developer.apple.com/library/archive/technotes/tn/tn1150.html#VolumeHeader - *

- * Fields are BigEndian - */ -public class HFSPlusVolumeHeader { - //@formatter:off - // Offset (hex) Length Comment - private short signature; // 0 2 48 2b, "H+" - private short version; // 2 2 4=HFS+, 5=HFSX - private int attributes; // 4 4 - private int lastMountedVersion; // 8 4 '10.0' string - private int journalInfoBlock; // C 4 - - private int createDate; // 10 4 - private int modifyDate; // 14 4 - private int backupDate; // 18 4 - private int checkedDate; // 1C 4 - - private int fileCount; // 20 4 - private int folderCount; // 24 4 - - private int blockSize; // 28 4 0x1000=4096 - private int totalBlocks; // 2C 4 blockSize*totalBlocks should equal vol size - private int freeBlocks; // 30 4 - - private int nextAllocation; // 34 4 - private int rsrcClumpSize; // 38 4 - private int dataClumpSize; // 3C 4 - private int nextCatalogID; // 40 4 - - private int writeCount; // 44 4 - private long encodingsBitmap; // 48 8 - - private int[] finderInfo; // 50 32 uint32[8] - - private byte[] rawForkData; // 70 400 - //HFSPlusForkData allocationFile; // 70 80 - //HFSPlusForkData extentsFile; // C0 80 - //HFSPlusForkData catalogFile; // 110 80 - //HFSPlusForkData attributesFile; // 160 80 - //HFSPlusForkData startupFile; // 1B0 80 - //@formatter:on - - private static final int HFSPLUS_SIGNATURE_MAGIC = 0x482b; // "H+" - private static final int HFSX_SIGNATURE_MAGIC = 0x4858; // "HX" - private static final int HFSPLUS_VERSION = 4; - private static final int HFSX_VERSION = 5; - private static final int SIZEOF_HEADER = 512; - private static final int DEFAULT_OFFSET = 1024; - - public static boolean probe(ByteProvider provider) { - try { - if (provider.length() < DEFAULT_OFFSET + SIZEOF_HEADER) { - return false; - } - HFSPlusVolumeHeader header = read(provider); - return header.isValid() && header.hasGoodVolumeInfo(provider); - } - catch (IOException e) { - return false; - } - } - - public static HFSPlusVolumeHeader read(ByteProvider provider) throws IOException { - return read(provider, DEFAULT_OFFSET); - } - - public static HFSPlusVolumeHeader read(ByteProvider provider, long offset) throws IOException { - BinaryReader reader = new BinaryReader(provider, false /*BE*/); - reader.setPointerIndex(offset); - HFSPlusVolumeHeader result = new HFSPlusVolumeHeader(); - result.signature = reader.readNextShort(); - result.version = reader.readNextShort(); - result.attributes = reader.readNextInt(); - result.lastMountedVersion = reader.readNextInt(); - result.journalInfoBlock = reader.readNextInt(); - - result.createDate = reader.readNextInt(); - result.modifyDate = reader.readNextInt(); - result.backupDate = reader.readNextInt(); - result.checkedDate = reader.readNextInt(); - - result.fileCount = reader.readNextInt(); - result.folderCount = reader.readNextInt(); - - result.blockSize = reader.readNextInt(); - result.totalBlocks = reader.readNextInt(); - result.freeBlocks = reader.readNextInt(); - - result.nextAllocation = reader.readNextInt(); - result.rsrcClumpSize = reader.readNextInt(); - result.dataClumpSize = reader.readNextInt(); - result.nextCatalogID = reader.readNextInt(); - - result.writeCount = reader.readNextInt(); - result.encodingsBitmap = reader.readNextLong(); - - result.finderInfo = reader.readNextIntArray(8); - - result.rawForkData = reader.readNextByteArray(400); - - return result; - } - - public boolean isValid() { - return signature == HFSPLUS_SIGNATURE_MAGIC && version == HFSPLUS_VERSION && - isGoodBlockSize(blockSize); - } - - private static boolean isGoodBlockSize(int bs) { - return bs > 0 && bs % 512 == 0; - } - - public boolean hasGoodVolumeInfo(ByteProvider bp) throws IOException { - long calculatedSize = blockSize * totalBlocks; - // NOTE: can't compare with exact equals-to provider size because an extra 16 bytes - // are present in examples extracted from firmware images - return bp.length() >= calculatedSize; - } -} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystemFactory.java deleted file mode 100644 index 5c09a8aa49..0000000000 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystemFactory.java +++ /dev/null @@ -1,71 +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.file.formats.iso9660; - -import java.util.Arrays; - -import java.io.IOException; - -import ghidra.app.util.bin.ByteProvider; -import ghidra.formats.gfilesystem.FSRLRoot; -import ghidra.formats.gfilesystem.FileSystemService; -import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider; -import ghidra.formats.gfilesystem.factory.GFileSystemProbeByteProvider; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; - -public class ISO9660FileSystemFactory - implements GFileSystemFactoryByteProvider, GFileSystemProbeByteProvider { - private static final byte[] MAGIC_BYTES = { 0x43, 0x44, 0x30, 0x30, 0x31 }; // "CD001" - - private static final long[] SIGNATURE_PROBE_OFFSETS = new long[] { 0x8000L, 0x8800L, 0x9000L }; - - @Override - public boolean probe(ByteProvider byteProvider, FileSystemService fsService, - TaskMonitor monitor) throws IOException, CancelledException { - - for (long probeOffset : SIGNATURE_PROBE_OFFSETS) { - if (isMagicSignatureAt(byteProvider, probeOffset + 1)) { - return true; - } - } - return false; - } - - private boolean isMagicSignatureAt(ByteProvider provider, long offset) throws IOException { - int magicLen = MAGIC_BYTES.length; - long providerLen = provider.length(); - return (providerLen > offset + magicLen) && - Arrays.equals(provider.readBytes(offset, magicLen), MAGIC_BYTES); - } - - @Override - public ISO9660FileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider, - FileSystemService fsService, TaskMonitor monitor) - throws IOException, CancelledException { - - ISO9660FileSystem fs = new ISO9660FileSystem(targetFSRL, fsService); - try { - fs.mount(byteProvider, monitor); - return fs; - } - catch (IOException ioe) { - fs.close(); - throw ioe; - } - } - -} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SZByteProviderStream.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SZByteProviderStream.java deleted file mode 100644 index ef7efea9a3..0000000000 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SZByteProviderStream.java +++ /dev/null @@ -1,93 +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.file.formats.sevenzip; - -import java.io.IOException; - -import ghidra.app.util.bin.ByteProvider; -import net.sf.sevenzipjbinding.IInStream; -import net.sf.sevenzipjbinding.SevenZipException; - -/** - * Adapter from Ghidra's {@link ByteProvider} to SZ's {@link IInStream} - */ -class SZByteProviderStream implements IInStream { - - private ByteProvider bp; - private long position; - - SZByteProviderStream(ByteProvider bp) { - this.bp = bp; - } - - @Override - public synchronized long seek(long offset, int seekOrigin) throws SevenZipException { - try { - switch (seekOrigin) { - case SEEK_SET: - setPos(offset); - break; - - case SEEK_CUR: - setPos(position + offset); - break; - - case SEEK_END: - setPos(bp.length() + offset); - break; - - default: - throw new RuntimeException("Seek: unknown origin: " + seekOrigin); - } - } - catch (IOException e) { - throw new SevenZipException(e); - } - - return position; - } - - private void setPos(long newPos) throws SevenZipException { - if (newPos < 0) { - throw new SevenZipException("Invalid offset: " + newPos); - } - position = newPos; - } - - @Override - public synchronized int read(byte[] data) throws SevenZipException { - try { - int bytesToRead = (int)Math.min(data.length, bp.length() - position); - if (bytesToRead <= 0) { - return 0; - } - byte[] bytes = bp.readBytes(position, bytesToRead); - System.arraycopy(bytes, 0, data, 0, bytes.length); - - position += bytes.length; - return bytes.length; - } - catch (IOException e) { - throw new SevenZipException("Error reading random access file", e); - } - } - - @Override - public void close() throws IOException { - bp.close(); - } - -} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipCustomInitializer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipCustomInitializer.java deleted file mode 100644 index 793aeaafae..0000000000 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipCustomInitializer.java +++ /dev/null @@ -1,136 +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.file.formats.sevenzip; - -import java.io.*; -import java.util.*; -import java.util.stream.Collectors; - -import ghidra.framework.Application; -import net.sf.sevenzipjbinding.SevenZip; -import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; - -/** - * - * Custom logic to initialize Sevenzip's native libraries that have been placed into - * Ghidra's module data directory by a gradle build task (see extractSevenZipNativeLibs in build.gradle). - *

- * It is necessary to use this method instead of Sevenzip.initSevenZipFromPlatformJAR() - * because it has a logic error when extracting the native libraries and always overwrites - * the existing libraries, causing other java processes that have already loaded those - * libraries to core-dump. - *

- * See https://github.com/borisbrodski/sevenzipjbinding/issues/50 - *

- * This class (and the tasks in build.gradle) can be removed if/when upstream - * fixes the problem with native libraries always being over-written during initialization (and - * thereby causing earlier-loaded java vms to core dump) and also does not produce errors when - * multiple vms are simultaneously started. - */ -public class SevenZipCustomInitializer { - - /** - * Call this before using any SevenzipJBinding classes. Calling multiple times - * is okay. - *

- * Most likely cause of failure is running on an unsupported platform. - * - * @throws SevenZipNativeInitializationException - */ - public static synchronized void initSevenZip() throws SevenZipNativeInitializationException { - if (SevenZip.isInitializedSuccessfully()) { - return; - } - - try { - String platform = SevenZip.getPlatformBestMatch(); - - // This depends on the sevenzip native libraries being extracted from the sevenzip jar - // and being placed in the data/sevenzipnativelibs/ directory by a gradle task. - // Sevenzip's platform designator will be used to pick the appropriate native library, - // for example "/data/sevenzipnativelibs/Linux-amd64/lib7-Zip-JBinding.so". - File libDir = Application.getModuleDataSubDirectory("sevenzipnativelibs/" + platform) - .getFile(false); - - Properties properties = loadProperties(platform); - - // libName -> hash. hash not used at the moment - Map nativeLibraryInfo = getNativeLibraryInfo(properties); - List libFiles = nativeLibraryInfo.keySet() - .stream() - .map(libName -> new File(libDir, libName)) - .collect(Collectors.toList()); - loadNativeLibraries(libFiles); - SevenZip.initLoadedLibraries(); - } - catch (IOException e) { - throw new SevenZipNativeInitializationException("Error initializing SevenzipJbinding", - e); - } - } - - private static Properties loadProperties(String platform) - throws SevenZipNativeInitializationException, IOException { - // prop file contains lib.##.name and lib.##.hash values - String propFilename = "/" + platform + "/sevenzipjbinding-lib.properties"; - try (InputStream propFileStream = SevenZip.class.getResourceAsStream(propFilename)) { - if (propFileStream == null) { - throw new IOException("Error loading property file stream " + propFilename); - } - - Properties properties = new Properties(); - properties.load(propFileStream); - return properties; - } - } - - private static Map getNativeLibraryInfo(Properties properties) - throws IOException { - // LinkedHashMap to preserve order - LinkedHashMap libraryInfo = new LinkedHashMap<>(); - int libNum = 1; - String libName; - while ((libName = properties.getProperty(String.format("lib.%d.name", libNum))) != null) { - String libHash = properties.getProperty(String.format("lib.%d.hash", libNum)); - if (libHash == null) { - throw new IOException( - "Missing library hash value in property file for library lib." + libNum + - ".name=" + libName); - } - libraryInfo.put(libName, libHash); - libNum++; - } - if (libraryInfo.isEmpty()) { - throw new IOException("Missing library hash values in property file"); - } - return libraryInfo; - } - - private static void loadNativeLibraries(List libFiles) - throws SevenZipNativeInitializationException { - // Load native libraries in reverse order (per the logic in upstream's initialization code) - for (int i = libFiles.size() - 1; i >= 0; i--) { - File libFile = libFiles.get(i); - try { - System.load(libFile.getPath()); - } - catch (Throwable t) { - throw new SevenZipNativeInitializationException( - "Error loading native library: " + libFile, t); - } - } - } -} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java index 17e880ed66..abbbf53ddc 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java @@ -15,610 +15,173 @@ */ package ghidra.file.formats.sevenzip; - -import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*; - -import java.io.Closeable; import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.SeekableByteChannel; import java.util.*; +import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry; +import org.apache.commons.compress.archivers.sevenz.SevenZFile; import org.apache.commons.io.FilenameUtils; -import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.*; import ghidra.formats.gfilesystem.*; -import ghidra.formats.gfilesystem.FileCache.FileCacheEntry; -import ghidra.formats.gfilesystem.FileCache.FileCacheEntryBuilder; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; import ghidra.formats.gfilesystem.crypto.CryptoSession; -import ghidra.formats.gfilesystem.fileinfo.FileAttributes; -import ghidra.formats.gfilesystem.fileinfo.FileType; import ghidra.framework.generic.auth.Password; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; -import ghidra.util.exception.CryptoException; import ghidra.util.task.TaskMonitor; -import net.sf.sevenzipjbinding.*; -import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; -/** - * GFilesystem that uses 7zip native libraries to open archives and extract files. - *

- * WARNING: care is taken to synchronize access to the underlying sevenzip library methods as - * some race conditions have been encountered that cause the entire jdk to core dump. - */ +//@formatter:off @FileSystemInfo( type = "7zip", description = "7Zip", factory = SevenZipFileSystemFactory.class, - extensions = { "7z", "rpm" } + extensions = { "7z" } ) -public class SevenZipFileSystem extends AbstractFileSystem { - private Map passwords = new HashMap<>(); +//@formatter:on +/** +* File system that handles 7z files. +*/ +public class SevenZipFileSystem extends AbstractFileSystem { - private IInArchive archive; - private SZByteProviderStream szBPStream; - private ISimpleInArchiveItem[] items; - private ArchiveFormat archiveFormat; + private SevenZFile szFile; + private ByteProvider bp; public SevenZipFileSystem(FSRLRoot fsrl, FileSystemService fsService) { super(fsrl, fsService); } - /** - * Opens the specified sevenzip container file and initializes this file system with the - * contents. - * - * @param byteProvider container file - * @param monitor {@link TaskMonitor} to allow the user to monitor and cancel - * @throws CancelledException if user cancels - * @throws IOException if error when reading data - */ - public void mount(ByteProvider byteProvider, TaskMonitor monitor) - throws CancelledException, IOException { - if (!SevenZipFileSystemFactory.initNativeLibraries()) { - throw new IOException("Could not initialize 7zip native libraries"); - } - try { - szBPStream = new SZByteProviderStream(byteProvider); - archive = SevenZip.openInArchive(null, szBPStream); - archiveFormat = archive.getArchiveFormat(); - items = archive.getSimpleInterface().getArchiveItems(); - - indexFiles(monitor); - ensurePasswords(monitor); - } - catch (SevenZipException e) { - throw new IOException("Failed to open archive: " + fsFSRL, e); - } - } - @Override public void close() throws IOException { - synchronized (fsIndex) { - refManager.onClose(); + refManager.onClose(); - FSUtilities.uncheckedClose(archive, "Problem closing 7-Zip archive"); - archive = null; - - FSUtilities.uncheckedClose(szBPStream, null); - szBPStream = null; - - fsIndex.clear(); - items = null; + if (szFile != null) { + szFile.close(); + szFile = null; } - } - - private void indexFiles(TaskMonitor monitor) throws CancelledException, SevenZipException { - monitor.initialize(items.length, "Indexing files"); - for (ISimpleInArchiveItem item : items) { - if (monitor.isCancelled()) { - throw new CancelledException(); - } - - long itemSize = Objects.requireNonNullElse(item.getSize(), -1L); - fsIndex.storeFile(fixupItemPath(item), item.getItemIndex(), item.isFolder(), - itemSize, item); + if (bp != null) { + bp.close(); + bp = null; } - } - private String fixupItemPath(ISimpleInArchiveItem item) throws SevenZipException { - String itemPath = item.getPath(); - if (items.length == 1 && itemPath.isBlank()) { - // special case when there is a single unnamed file. - // use the name of the 7zip file itself, minus the extension - itemPath = FilenameUtils.getBaseName(fsFSRL.getContainer().getName()); - } - if (itemPath.isEmpty()) { - itemPath = ""; - } - return itemPath; - } - - private String getPasswordForFile(GFile file, ISimpleInArchiveItem encryptedItem, - TaskMonitor monitor) { - FSRL containerFSRL = fsFSRL.getContainer(); - int itemIndex = encryptedItem.getItemIndex(); - if (!passwords.containsKey(itemIndex)) { - try (CryptoSession cryptoSession = fsService.newCryptoSession()) { - String prompt = passwords.isEmpty() - ? containerFSRL.getName() - : "%s in %s".formatted(file.getName(), containerFSRL.getName()); - for (Iterator pwIt = - cryptoSession.getPasswordsFor(containerFSRL, prompt); pwIt.hasNext();) { - try (Password passwordValue = pwIt.next()) { - monitor.setMessage("Testing password for " + file.getName()); - - String password = String.valueOf(passwordValue.getPasswordChars()); // we are forced to use strings by 7zip's api - int[] encryptedItemIndexes = getEncryptedItemIndexes(); - TestPasswordsCallback testCB = - new TestPasswordsCallback(password, encryptedItemIndexes[0], monitor); - - // call the SZ extract method using "TEST" mode (ie. no bytes are extracted) - // on any files that don't have a password yet - archive.extract(encryptedItemIndexes, true /* test mode */, testCB); - List successFileIndexes = testCB.getSuccessFileIndexes(); - for (Integer unlockedFileIndex : successFileIndexes) { - passwords.put(unlockedFileIndex, password); - } - if (!successFileIndexes.isEmpty()) { - cryptoSession.addSuccessfulPassword(containerFSRL, passwordValue); - } - if (passwords.containsKey(itemIndex)) { - break; - } - } - catch (SevenZipException e) { - Msg.error(this, "Error when testing password for " + file.getFSRL(), e); - return null; - } - } - } - } - return passwords.get(itemIndex); - - } - - private int[] getEncryptedItemIndexes() throws SevenZipException { - List result = new ArrayList<>(); - for (ISimpleInArchiveItem item : items) { - if (item.isEncrypted() && !passwords.containsKey(item.getItemIndex())) { - result.add(item.getItemIndex()); - } - } - int[] arrayResult = new int[result.size()]; - int arrayResultIndex = 0; - for (Integer i : result) { - arrayResult[arrayResultIndex++] = i; - } - return arrayResult; - } - - private void ensurePasswords(TaskMonitor monitor) throws IOException { - // Alert! Unusual code! - // Background: contrary to normal expectations, zip container files can have a - // unique password per-embedded-file. - // Other archive formats may not have that feature, but the SevenZip jbinding - // API is designed to allow a per-embedded-file password. - // The following loop tests passwords against the file, first trying a - // common password against all the embedded files (this is the most likely - // scenario), and then when a password has been found that successfully unlocks - // the first subset of files, each remaining subsequent encrypted file's name is used to - // prompt for the next password. - // If the loop ends without finding a password for an encrypted file, - // that file will not be readable unless a password is found for it (see - // getPasswordForFile()). - - try (CryptoSession _ = fsService.newCryptoSession()) { - List encryptedItems = getEncryptedItemsWithoutPasswords(); - ISimpleInArchiveItem encryptedItem = null; - while ((encryptedItem = getFirstItemWithoutPassword(encryptedItems)) != null && - !monitor.isCancelled()) { - GFile gFile = fsIndex.getFileByIndex(encryptedItem.getItemIndex()); - if (gFile == null) { - throw new IOException("Unable to retrieve file " + encryptedItem.getPath()); - } - getPasswordForFile(gFile, encryptedItem, monitor); - if (passwords.isEmpty()) { - // we didn't find any password for any file in the archive. Abort the loop - // instead of badgering the user by using other files as prompts - break; - } - encryptedItems.remove(encryptedItem); - } - List noPasswordFoundList = getEncryptedItemsWithoutPasswords(); - if (!noPasswordFoundList.isEmpty()) { - Msg.warn(this, - "Unable to find password for " + noPasswordFoundList.size() + " file(s) in " + - fsFSRL.getContainer().getName()); - } - } - } - - private ISimpleInArchiveItem getFirstItemWithoutPassword( - List encryptedItems) { - for (ISimpleInArchiveItem item : encryptedItems) { - if (!passwords.containsKey(item.getItemIndex())) { - return item; - } - } - return null; - } - - private List getEncryptedItemsWithoutPasswords() - throws SevenZipException { - List result = new LinkedList<>(); - for (ISimpleInArchiveItem item : items) { - if (item.isEncrypted() && !passwords.containsKey(item.getItemIndex())) { - result.add(item); - } - } - return result; + fsIndex.clear(); } @Override public boolean isClosed() { - return szBPStream == null; + return bp == null; } - @Override - public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) { - synchronized (fsIndex) { - FileAttributes result = new FileAttributes(); - if (fsIndex.getRootDir().equals(file)) { - result.add(NAME_ATTR, "/"); - result.add("Archive Format", archiveFormat.toString()); - } - else { - ISimpleInArchiveItem item = fsIndex.getMetadata(file); - if (item == null) { - return result; - } + public void mount(ByteProvider bp, TaskMonitor monitor) throws CancelledException, IOException { + this.bp = bp; - result.add(NAME_ATTR, - FilenameUtils.getName(uncheckedGet(item::getPath, "unknown"))); - result.add(FILE_TYPE_ATTR, - uncheckedGet(item::isFolder, false) ? FileType.DIRECTORY : FileType.FILE); - boolean encrypted = uncheckedGet(item::isEncrypted, false); - result.add(IS_ENCRYPTED_ATTR, encrypted); - if (encrypted) { - result.add(HAS_GOOD_PASSWORD_ATTR, passwords.get(item.getItemIndex()) != null); - } - String comment = uncheckedGet(item::getComment, null); - result.add(COMMENT_ATTR, !comment.isBlank() ? comment : null); - result.add(COMPRESSED_SIZE_ATTR, uncheckedGet(item::getPackedSize, null)); - result.add(SIZE_ATTR, uncheckedGet(item::getSize, null)); + szFile = tryMount(null, monitor); - Integer crc = uncheckedGet(item::getCRC, null); - result.add("CRC", crc != null ? "%08X".formatted(crc) : null); - result.add("Compression Method", uncheckedGet(item::getMethod, null)); - result.add(CREATE_DATE_ATTR, uncheckedGet(item::getCreationTime, null)); - result.add(MODIFIED_DATE_ATTR, uncheckedGet(item::getLastWriteTime, null)); + if (szFile == null) { + FSRL containerFSRL = fsFSRL.getContainer(); + try (CryptoSession cryptoSession = fsService.newCryptoSession()) { + for (Iterator pwIt = cryptoSession.getPasswordsFor(containerFSRL, + containerFSRL.getName()); szFile == null && pwIt.hasNext();) { + + try (Password passwordValue = pwIt.next()) { + szFile = tryMount(passwordValue, monitor); + } + } } - return result; + } + + if (szFile == null) { + throw new IOException("Failed to mount " + bp.getFSRL()); } } - @Override - public FileType getFileType(GFile f, TaskMonitor monitor) { - synchronized (fsIndex) { - ISimpleInArchiveItem item = fsIndex.getMetadata(f); - try { - return item.isFolder() ? FileType.DIRECTORY : FileType.FILE; + private SevenZFile tryMount(Password password, TaskMonitor monitor) throws CancelledException { + + try { + SeekableByteChannel sbc = + new ByteProviderSeekableByteChannel(new ByteProviderWrapper(bp, null)); // wrap bp to prevent it from being closed + + // Entire file may be encrypted, or only individual file payloads may be encrypted. + // If entire file is encrypted, we need the password to just list the contents of the + // archive, otherwise we can open the file and list it without password until we try + // to read a file's content. + SevenZFile newSzFile = SevenZFile.builder() + .setSeekableByteChannel(sbc) + .setPassword(password != null ? password.getPasswordChars() : null) // SevenZFile makes a copy of the supplied char[] + .get(); + + List entries = new ArrayList<>(); + + // There may be independent passwords for individual files. + // The SevenZFile impl only supports a single password for the entire file. + // When attempting to read an encrypted file that has a non-matching password, we will + // get some form of IOException, for example CorruptedInputException from the xz compressor + int successCount = 0; + int failCount = 0; + SevenZArchiveEntry szEntry; + while ((szEntry = newSzFile.getNextEntry()) != null) { + monitor.checkCancelled(); + + entries.add(szEntry); + try (InputStream entryIs = newSzFile.getInputStream(szEntry);) { + entryIs.read(); // read a byte to test if we have the correct password if this is an encrypted file + successCount++; + } + catch (IOException e) { + failCount++; + } } - catch (SevenZipException e) { - return FileType.UNKNOWN; + + if (successCount == 0 && failCount > 0) { + // unable to read from any of the files in the archive. (probably wrong password) + return null; } + + if (password != null && failCount > 0) { + Msg.warn(this, "Failed to apply supplied password to %d of %d files in %s" + .formatted(failCount, entries.size(), fsFSRL.getContainer().getName())); + } + + for (int entryIndex = 0; entryIndex < entries.size(); entryIndex++) { + szEntry = entries.get(entryIndex); + String name = szEntry.getName(); + if (name == null || name.isEmpty()) { + if (entryIndex == 0) { + name = FilenameUtils.getBaseName(fsFSRL.getContainer().getName()); + } + else { + name = ""; + } + } + + fsIndex.storeFile(name, entryIndex, szEntry.isDirectory(), szEntry.getSize(), + szEntry); + } + + return newSzFile; + } + catch (IOException e) { + return null; } } @Override public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException, CancelledException { - try { - ISimpleInArchiveItem item = fsIndex.getMetadata(file); + GFile resolvedFile = fsIndex.resolveSymlinks(file); // probably not needed with sz - if (item == null) { - return null; - } - - int itemIndex = item.getItemIndex(); - - if (item.isFolder()) { - throw new IOException("Not a file: " + file.getName()); - } - if (item.isEncrypted()) { - String password = getPasswordForFile(file, item, monitor); - if (password == null) { - throw new CryptoException( - "Unable to extract encrypted file, missing password: " + item.getPath()); - } - } - try (SZExtractCallback szCallback = new SZExtractCallback(monitor, itemIndex, true)) { - synchronized (fsIndex) { - archive.extract(new int[] { itemIndex }, false /* extract mode */, szCallback); - } - FileCacheEntry result = szCallback.getExtractResult(itemIndex); - if (result == null) { - throw new IOException("Unable to extract " + file.getFSRL()); - } - return result.asByteProvider(file.getFSRL()); - } - } - catch (SevenZipException e) { - throw unwrapSZException(e); + SevenZArchiveEntry szEntry = fsIndex.getMetadata(resolvedFile); + if (szEntry == null) { + throw new IOException("Unknown file " + file); } + return fsService.getDerivedByteProvider(fsFSRL.getContainer(), file.getFSRL(), + file.getFSRL().getPath(), szEntry.getSize(), () -> { + + return szFile.getInputStream(szEntry); + + }, monitor); } - - //---------------------------------------------------------------------------------------------- - /** - * Implements SevenZip bulk extract callback. - *

- * For each file in the archive, SZ will call this class's 1) getStream(), 2) prepare(), - * 3) lots of write()s, and then 4) setOperationResult(). - *

- * This class writes the extracted bytes to the FileCache. - */ - private class SZExtractCallback - implements IArchiveExtractCallback, ISequentialOutStream, ICryptoGetTextPassword, - Closeable { - - private TaskMonitor monitor; - private int currentIndex; - private ISimpleInArchiveItem currentItem; - private String currentName = "unknown"; - private FileCacheEntryBuilder currentCacheEntryBuilder; - private boolean saveResults; - private Map extractResults = new HashMap<>(); - - public SZExtractCallback(TaskMonitor monitor, int initalIndex, boolean saveResults) { - this.monitor = monitor; - this.currentIndex = initalIndex; - this.saveResults = saveResults; - } - - FileCacheEntry getExtractResult(int itemIndex) { - return extractResults.get(itemIndex); - } - - @Override - public void close() throws IOException { - if (currentCacheEntryBuilder != null) { - currentCacheEntryBuilder.close(); - } - } - - @Override - public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) - throws SevenZipException { - currentIndex = index; - - // STEP 1: SevenZip calls this method to get a object it can use to write the bytes to. - // If we return null, SZ treats it as a skip. (except for folders) - currentItem = items[currentIndex]; - currentName = currentItem.getPath(); - - if (currentItem.isFolder() || extractAskMode != ExtractAskMode.EXTRACT) { - return null; - } - - if (currentItem.isEncrypted() && !passwords.containsKey(currentIndex)) { - // if we lack a password for this item, don't try to extract it - Msg.debug(SevenZipFileSystem.this, - "No password for file[%d] %s of %s, unable to extract.".formatted(currentIndex, - currentName, fsFSRL.getContainer().getName())); - return null; - } - - return this; - } - - @Override - public void prepareOperation(ExtractAskMode extractAskMode) throws SevenZipException { - // STEP 2: SevenZip calls this method to further prepare to operate on the file. - // In our case, we only handle extract operations. - if (!currentItem.isFolder() && extractAskMode == ExtractAskMode.EXTRACT) { - try { - long size = Objects.requireNonNullElse(currentItem.getSize(), -1L); - currentCacheEntryBuilder = fsService.createTempFile(size); - monitor.initialize(size, "Extracting " + currentName); - } - catch (IOException e) { - throw new SevenZipException(e); - } - } - } - - @Override - public String cryptoGetTextPassword() throws SevenZipException { - // STEP 2.5 or 0: SevenZip calls this method to get the password of the file (if encrypted). - // Sometimes after prepareOperation(), sometimes before getStream(). - String password = passwords.get(currentIndex); - if (password == null) { - - Msg.debug(SevenZipFileSystem.this, "No password for file[%d] %s of %s" - .formatted(currentIndex, currentName, fsFSRL.getContainer().getName())); - // hack, return a non-null bad password. normally shouldn't get here as - // encrypted files w/missing password are skipped by getStream() - password = ""; - } - return password; - } - - @Override - public int write(byte[] data) throws SevenZipException { - // STEP 3: SevenZip calls this multiple times for all the bytes in the file. - // We write them to our temp file. - if (currentCacheEntryBuilder == null) { - throw new SevenZipException("Bad Sevenzip Extract Callback state, %d, %s" - .formatted(currentIndex, currentName)); - } - try { - currentCacheEntryBuilder.write(data); - monitor.incrementProgress(data.length); - return data.length; - } - catch (IOException e) { - throw new SevenZipException(e); - } - } - - @Override - public void setOperationResult(ExtractOperationResult extractOperationResult) - throws SevenZipException { - // STEP 4: SevenZip calls this to signal that the extract is done for this file. - if (currentCacheEntryBuilder == null) { - return; - } - try { - FileCacheEntry fce = currentCacheEntryBuilder.finish(); - if (extractOperationResult == ExtractOperationResult.OK) { - GFile gFile = fsIndex.getFileByIndex(currentIndex); - if (gFile != null && gFile.getFSRL().getMD5() == null) { - fsIndex.updateFSRL(gFile, gFile.getFSRL().withMD5(fce.getMD5())); - } - if (saveResults) { - extractResults.put(currentIndex, fce); - } - Msg.debug(SevenZipFileSystem.this, "Wrote file to cache: " + gFile + ", " + - FSUtilities.formatSize(fce.length())); - } - else { - Msg.warn(SevenZipFileSystem.this, "Failed to push file[%d] %s to cache: %s" - .formatted(currentIndex, currentName, extractOperationResult)); - extractOperationResultToException(extractOperationResult); - } - } - catch (IOException e) { - throw new SevenZipException(e); - } - finally { - FSUtilities.uncheckedClose(currentCacheEntryBuilder, null); - currentCacheEntryBuilder = null; - - // hack to advance the currentIndex for the next file so cryptoGetTextPassword - // will have a correct currentIndex value if it is called before getStream(), - // which does happen depending on the phase of the moon or the 7zip - // library's mood. - currentIndex++; - } - } - - //@formatter:off - @Override public void setTotal(long total) throws SevenZipException { /* nada */ } - @Override public void setCompleted(long complete) throws SevenZipException {/* nada */ } - //@formatter:on - - private void extractOperationResultToException(ExtractOperationResult operationResult) - throws IOException { - if (operationResult == null) { - throw new IOException("7-Zip returned null operation result"); - } - switch (operationResult) { - case CRCERROR: - throw new IOException("7-Zip returned CRC error"); - case DATAERROR: - throw new IOException("7-Zip returned data error"); - case UNSUPPORTEDMETHOD: - throw new IOException("Unexpected: 7-Zip returned unsupported method"); - case UNKNOWN_OPERATION_RESULT: - throw new IOException("Unexpected: 7-Zip returned unknown operation result"); - case WRONG_PASSWORD: - throw new IOException("7-Zip wrong password"); - default: - throw new IOException("7-Zip unknown error " + operationResult); - case OK: - // it's all ok! - } - } - - } - - /** - * This class is has the same layout and hacks re: setting currentIndex as {@link SZExtractCallback}, - * but is specialized to test passwords against the encrypted entries in the file. - */ - private class TestPasswordsCallback implements IArchiveExtractCallback, ICryptoGetTextPassword { - - private int currentIndex; - private String currentPassword; - private List successFileIndexes = new ArrayList<>(); - private TaskMonitor monitor; - - TestPasswordsCallback(String currentPassword, int initialIndex, TaskMonitor monitor) { - this.currentPassword = currentPassword; - this.currentIndex = initialIndex; - this.monitor = monitor; - } - - List getSuccessFileIndexes() { - return successFileIndexes; - } - - @Override - public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) - throws SevenZipException { - currentIndex = index; - ISimpleInArchiveItem item = items[currentIndex]; - monitor.setMessage("Testing password for " + item.getPath()); - return null; - } - - @Override - public void prepareOperation(ExtractAskMode extractAskMode) throws SevenZipException { - // nothing - } - - @Override - public String cryptoGetTextPassword() throws SevenZipException { - return currentPassword; - } - - @Override - public void setOperationResult(ExtractOperationResult extractOperationResult) - throws SevenZipException { - ISimpleInArchiveItem item = items[currentIndex]; - if (item.isEncrypted() && extractOperationResult == ExtractOperationResult.OK && - !passwords.containsKey(currentIndex)) { - successFileIndexes.add(currentIndex); - } - currentIndex++; - } - - @Override - public void setTotal(long total) throws SevenZipException { - monitor.initialize(total); - } - - @Override - public void setCompleted(long complete) throws SevenZipException { - monitor.setProgress(complete); - } - - } - - interface SZGetter { - T get() throws SevenZipException; - } - - private static T uncheckedGet(SZGetter getter, T defaultValue) { - try { - return getter.get(); - } - catch (SevenZipException e) { - // don't care - return defaultValue; - } - } - - private IOException unwrapSZException(SevenZipException e) { - SevenZipException tmp = e; - while (tmp != null && tmp.getCause() instanceof SevenZipException) { - tmp = (SevenZipException) tmp.getCause(); - } - return (tmp != null && tmp.getCause() instanceof IOException) - ? (IOException) tmp.getCause() - : new IOException(e); - } - } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java index 01111d74a9..bcf953d4db 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java @@ -16,56 +16,33 @@ package ghidra.file.formats.sevenzip; import java.io.IOException; -import java.util.List; + +import org.apache.commons.compress.archivers.sevenz.SevenZFile; import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.recognizer.*; import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider; import ghidra.formats.gfilesystem.factory.GFileSystemProbeBytesOnly; -import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; public class SevenZipFileSystemFactory implements GFileSystemFactoryByteProvider, GFileSystemProbeBytesOnly { - private List recognizers = List.of(new SevenZipRecognizer(), new XZRecognizer(), - new Bzip2Recognizer(), new MSWIMRecognizer(), new ArjRecognizer(), new CabarcRecognizer(), - new CHMRecognizer(), new CramFSRecognizer(), new DebRecognizer(), new LhaRecognizer(), - new RarRecognizer(), new RPMRecognizer(), new VHDRecognizer(), new XarRecognizer(), - new UnixCompressRecognizer()); - - private static boolean initFailed; - private final int recognizerBytesRequired; - - public SevenZipFileSystemFactory() { - int max = 0; - for (Recognizer recognizer : recognizers) { - max = Math.max(max, recognizer.numberOfBytesRequired()); - } - recognizerBytesRequired = max; - } + private static final int START_BYTES_REQUIRED = 6; // copied from SevenZFile's private sevenZSignature @Override public int getBytesRequired() { - return recognizerBytesRequired; + return START_BYTES_REQUIRED; } @Override public boolean probeStartBytes(FSRL containerFSRL, byte[] startBytes) { - for (Recognizer recognizer : recognizers) { - String recognized = recognizer.recognize(startBytes); - if (recognized != null) { - return true; - } - } - return false; + return SevenZFile.matches(startBytes, START_BYTES_REQUIRED); } @Override - public SevenZipFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider, + public GFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider, FileSystemService fsService, TaskMonitor monitor) throws IOException, CancelledException { @@ -80,25 +57,4 @@ public class SevenZipFileSystemFactory } } - /** - * Returns true if the native libraries for 7zip were initialized. - * - * @return boolean true if 7zip dlls/libs/etc were successfully initialized - */ - public static boolean initNativeLibraries() { - try { - SevenZipCustomInitializer.initSevenZip(); - return true; - } - catch (SevenZipNativeInitializationException e) { - if (!initFailed) { - Msg.warn(SevenZipFileSystemFactory.class, - "Sevenzip native libraries failed to initialize: " + e.getMessage()); - initFailed = true; - } - return false; - } - - } - } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/tar/TarFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/tar/TarFileSystem.java index 344a2993bc..1162fb866d 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/tar/TarFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/tar/TarFileSystem.java @@ -33,7 +33,7 @@ import ghidra.util.task.TaskMonitor; /** * TAR file system implementation. *

- * The factory supports detecting both compressed (gz) and uncompressed tar files, + * The factory supports detecting both compressed (gz/bz2/xz) and uncompressed tar files, * and keys both on the tar filename extension as well as the data in the file. */ @FileSystemInfo( @@ -41,7 +41,7 @@ import ghidra.util.task.TaskMonitor; description = "TAR", priority = FileSystemInfo.PRIORITY_HIGH, factory = TarFileSystemFactory.class, - extensions = { "tar", "tgz" } + extensions = { "tar", "tgz", "tbz2" } ) public class TarFileSystem extends AbstractFileSystem { diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/tar/TarFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/tar/TarFileSystemFactory.java index cefee0cbf2..e6d9833bab 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/tar/TarFileSystemFactory.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/tar/TarFileSystemFactory.java @@ -4,9 +4,9 @@ * 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. @@ -17,12 +17,14 @@ package ghidra.file.formats.tar; import java.io.IOException; import java.io.InputStream; +import java.util.Set; import java.util.zip.GZIPInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarConstants; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; -import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; +import org.tukaani.xz.XZ; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.ByteProvider; @@ -40,7 +42,9 @@ public class TarFileSystemFactory implements GFileSystemFactoryByteProvider TAR_EXTS = + Set.of(".tar", ".tgz", ".tar.gz", ".tbz2", ".tar.bz2", ".tar.xz"); + @Override public TarFileSystem create(FSRLRoot targetFSRL, ByteProvider provider, @@ -49,13 +53,14 @@ public class TarFileSystemFactory implements GFileSystemFactoryByteProvider { Msg.info(TarFileSystem.class, "Uncompressing tar file " + containerFSRL); - return newFileInputStreamAutoDetectCompressed(provider); + return getTarInputStream(provider, compressor); }, upwtm); provider.close(); } @@ -79,34 +84,17 @@ public class TarFileSystemFactory implements GFileSystemFactoryByteProvider * Note: if a compressed TAR file doesn't have a well-known extension, the * other {@link #probeStartBytes(FSRL, byte[]) probe method} will detect the TAR file after - * the {@link GZipFileSystem} has exposed the uncompressed data. + * a {@link GZipFileSystem} (and friends) has exposed the uncompressed data. * */ @Override public boolean probe(ByteProvider provider, FileSystemService fsService, TaskMonitor taskMonitor) throws IOException, CancelledException { - String filename = provider.getFSRL().getName(); - String ext = FSUtilities.getExtension(filename, 1); - if (ext == null) { - return false; - } - ext = ext.toLowerCase(); - - // special case hack to get 2-part ext for tar.gz or tar.bz2 - ext = - (".gz".equals(ext) || ".bz2".equals(ext)) ? FSUtilities.getExtension(filename, 2) : ext; - - // Only continue with probe (which requires us to open the file) if it has the - // correct file extension.' - if (!ArrayUtils.contains(TAR_EXTS, ext)) { + if (!hasTarExt(provider.getFSRL().getName().toLowerCase())) { return false; } - if (!isCompressedMagicBytes(provider)) { - return false; - } - - try (InputStream is = newFileInputStreamAutoDetectCompressed(provider)) { + try (InputStream is = getTarInputStream(provider, detectCompressor(provider))) { byte[] startBytes = new byte[TAR_MAGIC_BYTES_REQUIRED]; if (is.read(startBytes) != TAR_MAGIC_BYTES_REQUIRED) { return false; @@ -116,35 +104,51 @@ public class TarFileSystemFactory implements GFileSystemFactoryByteProvider is; + case GZIP -> new GZIPInputStream(is); + case BZIP2 -> new BZip2CompressorInputStream(is); + case XZ -> new XZCompressorInputStream(is); + }; } - private static boolean isCompressedMagicBytes(ByteProvider bp) throws IOException { - int magicBytes = readMagicBytes(bp); + + private static TarCompressors detectCompressor(ByteProvider bp) throws IOException { + int magicBytes = readMagicShort(bp); switch (magicBytes) { case GZIPInputStream.GZIP_MAGIC: + return TarCompressors.GZIP; case Bzip2Recognizer.MAGIC_BYTES: - return true; - default: - return false; + return TarCompressors.BZIP2; } + + byte[] startBytes = bp.readBytes(0, XZ.HEADER_MAGIC.length); + if (XZCompressorInputStream.matches(startBytes, startBytes.length)) { + return TarCompressors.XZ; + } + + return null; } - private static int readMagicBytes(ByteProvider bp) throws IOException { + private static int readMagicShort(ByteProvider bp) throws IOException { BinaryReader br = new BinaryReader(bp, true /* LE */); int magicBytes = br.readUnsignedShort(0); return magicBytes; } + + private static boolean hasTarExt(String filename) { + for (String ext : TAR_EXTS) { + if (filename.endsWith(ext)) { + return true; + } + } + return false; + } + + enum TarCompressors { GZIP, BZIP2, XZ } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/Zip4jFileHandler.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/Zip4jFileHandler.java new file mode 100644 index 0000000000..0d87fc52ae --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/Zip4jFileHandler.java @@ -0,0 +1,76 @@ +/* ### + * 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.zip; + +import java.io.*; +import java.util.List; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.FileSystemService; +import ghidra.framework.generic.auth.Password; +import ghidra.util.task.TaskMonitor; +import net.lingala.zip4j.ZipFile; +import net.lingala.zip4j.model.FileHeader; + +/** + * Wrapper around zip4j's ZipFile impl, mainly used to access password protected entries in a + * zip file. + *

+ * If the supplied ByteProvider isn't a simple wrapper around a real local file, the contents of + * the ByteProvider will be written out to a temp file for the duration of this object so that + * zip4j can access it. + */ +public class Zip4jFileHandler implements Closeable { + private static final String ZIP_TEMPFILE_PREFIX = "ghidra_tmp_zip"; + + private ZipFile zipFile; + private List zipEntries; + + private File file; + private File tmpZipFile; + private boolean deleteZipFileWhenDone; + + public Zip4jFileHandler(ByteProvider bp, FileSystemService fsService, TaskMonitor monitor) + throws IOException { + + file = fsService.getFileIfAvailable(bp); + if (file == null) { + tmpZipFile = file = fsService.createPlaintextTempFile(bp, ZIP_TEMPFILE_PREFIX, monitor); + deleteZipFileWhenDone = true; + } + + this.zipFile = new ZipFile(file); + this.zipEntries = zipFile.getFileHeaders(); + } + + @Override + public void close() throws IOException { + if (zipFile != null) { + zipFile.close(); + zipFile = null; + } + if (tmpZipFile != null && deleteZipFileWhenDone) { + tmpZipFile.delete(); + tmpZipFile = null; + } + } + + public InputStream getInputStream(int fileIndex, Password password) throws IOException { + zipFile.setPassword(password.getPasswordChars()); + return zipFile.getInputStream(zipEntries.get(fileIndex)); + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystem.java index d4a850418f..01a510f194 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystem.java @@ -15,27 +15,325 @@ */ package ghidra.file.formats.zip; -import ghidra.file.formats.sevenzip.SevenZipFileSystem; -import ghidra.formats.gfilesystem.FSRLRoot; -import ghidra.formats.gfilesystem.FileSystemService; +import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.sql.Date; +import java.util.*; +import java.util.Map.Entry; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.io.FilenameUtils; + +import ghidra.app.util.bin.*; +import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; +import ghidra.formats.gfilesystem.crypto.CryptoSession; +import ghidra.formats.gfilesystem.fileinfo.FileAttributeType; +import ghidra.formats.gfilesystem.fileinfo.FileAttributes; +import ghidra.framework.generic.auth.Password; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.CryptoException; +import ghidra.util.task.TaskMonitor; /** - * Derivative of 7zip file system to provide "zip" flavored FSRLs. - *

- * 7Zip's features are superior to the native java zip handling (ie. passwords) - */ +* File system that handles .zip files using Apache commons compress, and Zip4j for password +* protected files. +*/ +//@formatter:off @FileSystemInfo( - type = "zip", - description = "ZIP", + type = "zip", + description = "ZIP", factory = ZipFileSystemFactory.class, priority = FileSystemInfo.PRIORITY_HIGH, extensions = { "zip", "jar" } ) -public class ZipFileSystem extends SevenZipFileSystem { +//@formatter:on +public class ZipFileSystem extends AbstractFileSystem { + private static final String INVALID_SYMLINK_PATH = ""; + + private ZipFile zipFile; + private ByteProvider bp; + private Zip4jFileHandler zip4j; // only needed when password protected entries are present + + private List entries = List.of(); + private Map passwords = new HashMap<>(); + private Set uniquePasswords = new HashSet<>(); public ZipFileSystem(FSRLRoot fsrl, FileSystemService fsService) { super(fsrl, fsService); } + @Override + public void close() throws IOException { + refManager.onClose(); + + if (zipFile != null) { + zipFile.close(); + zipFile = null; + } + entries.clear(); + if (zip4j != null) { + zip4j.close(); + zip4j = null; + } + if (bp != null) { + bp.close(); + bp = null; + } + + uniquePasswords.forEach(password -> FSUtilities.uncheckedClose(password, null)); + uniquePasswords.clear(); + passwords.clear(); + + fsIndex.clear(); + } + + @Override + public boolean isClosed() { + return zipFile == null; + } + + public void mount(ByteProvider bp, TaskMonitor monitor) throws CancelledException, IOException { + + this.bp = bp; + + zipFile = ZipFile.builder() + .setSeekableByteChannel( + new ByteProviderSeekableByteChannel(new ByteProviderWrapper(bp, null))) + .get(); + + entries = Collections.list(zipFile.getEntriesInPhysicalOrder()); + + try (CryptoSession _ = fsService.newCryptoSession()) { + ensurePasswords(monitor); + indexFiles(monitor); + } + } + + @Override + public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) { + ZipArchiveEntry zipEntry = fsIndex.getMetadata(file); + if (zipEntry == null) { + return FileAttributes.EMPTY; + } + int fileIndex = (int) fsIndex.getFileIndex(file); + FileAttributes result = new FileAttributes(); + + String rawPath = zipEntry.getName(); + result.add(FileAttributeType.NAME_ATTR, FilenameUtils.getName(rawPath)); + result.add(FileAttributeType.PATH_ATTR, FilenameUtils.getFullPath(rawPath)); + result.add(FileAttributeType.COMMENT_ATTR, zipEntry.getComment()); + result.add(FileAttributeType.SIZE_ATTR, zipEntry.getSize()); + Date date = new Date(zipEntry.getLastModifiedTime().toMillis()); + result.add(FileAttributeType.MODIFIED_DATE_ATTR, date); + result.add(FileAttributeType.COMPRESSED_SIZE_ATTR, zipEntry.getCompressedSize()); + result.add("CRC", Long.toHexString(zipEntry.getCrc())); + result.add("Compression Method", "0x" + Integer.toHexString(zipEntry.getMethod())); + if (zipEntry.getGeneralPurposeBit().usesEncryption()) { + result.add(FileAttributeType.IS_ENCRYPTED_ATTR, true); + result.add(HAS_GOOD_PASSWORD_ATTR, passwords.get(fileIndex) != null); + } + if (zipEntry.isUnixSymlink()) { + String symlinkDest = fsIndex.getSymlinkPath(file); + result.add(FileAttributeType.SYMLINK_DEST_ATTR, symlinkDest); + + } + return result; + } + + @Override + public String toString() { + return "ZipFilesystem [ fsrl=" + fsFSRL + " ]"; + } + + @Override + public InputStream getInputStream(GFile file, TaskMonitor monitor) + throws IOException, CancelledException { + ZipArchiveEntry zipEntry = fsIndex.getMetadata(file); + return (zipEntry != null) ? zipFile.getInputStream(zipEntry) : null; + } + + @Override + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) + throws IOException, CancelledException { + + GFile resolvedFile = fsIndex.resolveSymlinks(file); + ZipArchiveEntry zipEntry = fsIndex.getMetadata(resolvedFile); + if (zipEntry == null) { + throw new IOException("Unknown file " + file); + } + + int fileIndex = (int) fsIndex.getFileIndex(resolvedFile); + + return fsService.getDerivedByteProvider(fsFSRL.getContainer(), file.getFSRL(), + file.getFSRL().getPath(), zipEntry.getSize(), () -> { + + if (zipEntry.getGeneralPurposeBit().usesEncryption()) { + Password password = getPasswordForEntry(fileIndex, monitor); + if (password == null) { + throw new CryptoException( + "Unable to extract encrypted file, missing password: " + + zipEntry.getName()); + } + return zip4j.getInputStream(fileIndex, password); + } + return zipFile.getInputStream(zipEntry); + + }, monitor); + } + + private void indexFiles(TaskMonitor monitor) throws CancelledException { + int zipIndex = 0; + for (ZipArchiveEntry zipEntry : entries) { + monitor.checkCancelled(); + if (zipEntry.isUnixSymlink()) { + String symlinkDest = safeGetSymlinkDest(zipIndex, zipEntry, monitor); + fsIndex.storeSymlink(zipEntry.getName(), zipIndex++, symlinkDest, + zipEntry.getSize(), zipEntry); + } + else { + fsIndex.storeFile(zipEntry.getName(), zipIndex++, zipEntry.isDirectory(), + zipEntry.getSize(), zipEntry); + } + } + } + + private String safeGetSymlinkDest(int fileIndex, ZipArchiveEntry zipEntry, + TaskMonitor monitor) { + try { + if (zipEntry.getGeneralPurposeBit().usesEncryption()) { + Password password = getPasswordForEntry(fileIndex, monitor); + if (password != null) { + try (InputStream is = zip4j.getInputStream(fileIndex, password)) { + byte[] symlinkBytes = is.readAllBytes(); + return new String(symlinkBytes, StandardCharsets.UTF_8); + } + } + return INVALID_SYMLINK_PATH; + } + return zipFile.getUnixSymlink(zipEntry); + } + catch (IOException e) { + return INVALID_SYMLINK_PATH; + } + } + + private Password getPasswordForEntry(int fileIndex, TaskMonitor monitor) { + ZipArchiveEntry zipEntry = entries.get(fileIndex); + String fileName = zipEntry.getName(); + + Password result = passwords.get(fileIndex); + if (result != null) { + return result; + } + + for (Password previousPassword : uniquePasswords) { + if (testPassword(fileIndex, previousPassword)) { + passwords.put(fileIndex, previousPassword); + return previousPassword; + } + } + + FSRL containerFSRL = fsFSRL.getContainer(); + try (CryptoSession cryptoSession = fsService.newCryptoSession()) { + String prompt = uniquePasswords.isEmpty() + ? containerFSRL.getName() + : "%s in %s".formatted(fileName, containerFSRL.getName()); + + for (Iterator pwIt = + cryptoSession.getPasswordsFor(containerFSRL, prompt); pwIt.hasNext();) { + + try (Password passwordValue = pwIt.next()) { + monitor.setMessage("Testing password for " + fileName); + + // Even though the caller requested a password for a specific zipEntry, + // we need to test the candidate password against all entries that are missing + // passwords to avoid situations where the user entered a correct password + // for a later file, but the caller has specified a different file. + // This will also include the file the caller specified. + List successIndexes = new ArrayList<>(); + for (Entry entry : passwords.entrySet()) { + if (entry.getValue() == null) { + if (testPassword(entry.getKey(), passwordValue)) { + successIndexes.add(entry.getKey()); + } + } + } + + if (!successIndexes.isEmpty()) { + cryptoSession.addSuccessfulPassword(containerFSRL, passwordValue); + Password successPassword = passwordValue.clone(); + uniquePasswords.add(successPassword); + + for (Integer successIndex : successIndexes) { + passwords.put(successIndex, successPassword); + } + + // Even though there was a successful password found, it might not have + // been the password for the zipEntry the caller requested + result = passwords.get(fileIndex); + if (result != null) { + return result; + } + } + } + } + } + + return null; + } + + private boolean testPassword(int fileIndex, Password password) { + try (InputStream _ = zip4j.getInputStream(fileIndex, password)) { + return true; + } + catch (IOException e) { + return false; + } + } + + private void ensurePasswords(TaskMonitor monitor) throws IOException { + + for (int i = 0; i < entries.size(); i++) { + ZipArchiveEntry zipEntry = entries.get(i); + if (zipEntry.getGeneralPurposeBit().usesEncryption()) { + passwords.put(i, null); + } + } + + if (passwords.isEmpty()) { + // nothing to do + return; + } + + this.zip4j = new Zip4jFileHandler(bp, fsService, monitor); + + // Alert! Unusual code! + // Background: contrary to normal expectations, zip container files can have a + // unique password per-embedded-file. + // The following loop tests passwords against each file, first trying a + // common password against all the embedded files (this is the most likely + // scenario), and then when a password has been found that successfully unlocks + // the first subset of files, each remaining subsequent encrypted file's name is used to + // prompt for the next password. + // If the loop ends without finding a password for an encrypted file, + // that file will not be readable unless a password is found for it. + for (Entry entry : passwords.entrySet()) { + int fileIndex = entry.getKey(); + getPasswordForEntry(fileIndex, monitor); + } + + long missingPasswordCount = + passwords.entrySet().stream().filter(e -> e.getValue() == null).count(); + if (missingPasswordCount > 0) { + Msg.warn(this, "Unable to find password(s) for %d file(s) in %s" + .formatted(missingPasswordCount, fsFSRL.getContainer().getName())); + } + } + } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystemBuiltin.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystemBuiltin.java deleted file mode 100644 index d7d46ba87f..0000000000 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystemBuiltin.java +++ /dev/null @@ -1,139 +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.file.formats.zip; - -import java.io.*; -import java.sql.Date; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import ghidra.app.util.bin.ByteProvider; -import ghidra.formats.gfilesystem.*; -import ghidra.formats.gfilesystem.annotations.FileSystemInfo; -import ghidra.formats.gfilesystem.fileinfo.FileAttributeType; -import ghidra.formats.gfilesystem.fileinfo.FileAttributes; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; - -/** - * An alternative implementation of the zip file system, using java's built-in support - * for zip files. - *

- * This class is used when the 7zip libraries fail to load, typically on a non-supported - * platform. - *

- * The user will lose the ability to open password protected zip files when operating in this - * mode, as well as the ability to use obfuscated temp files, which may allow a virusscanner - * to mangle our temp files. - *

- * This class's name does not end in "FileSystem", so that it will not be found by the class searcher. - *

- * It also needs to duplicate the FileSystemInfo annotation to allow {@link GFileSystem#getType()} - * and {@link GFileSystem#getDescription()} to operate in the default manner. - */ -@FileSystemInfo(type = "zip", description = "ZIP", factory = ZipFileSystemFactory.class, priority = FileSystemInfo.PRIORITY_HIGH) -public class ZipFileSystemBuiltin extends AbstractFileSystem { - static final String TEMPFILE_PREFIX = "ghidra_tmp_zipfile"; - - private ZipFile zipFile; - - public ZipFileSystemBuiltin(FSRLRoot fsFSRL, FileSystemService fsService) { - super(fsFSRL, fsService); - } - - @Override - public void close() throws IOException { - refManager.onClose(); - if (zipFile != null) { - zipFile.close(); - zipFile = null; - } - fsIndex.clear(); - } - - @Override - public boolean isClosed() { - return zipFile == null; - } - - public void mount(File f, boolean deleteFileWhenDone, TaskMonitor monitor) - throws CancelledException, IOException { - - // Just a little paranoia to ensure we don't delete a user's file. - // The ZipFileSystemFactory knows to create temp files with this prefix. - deleteFileWhenDone = deleteFileWhenDone && f.getName().startsWith(TEMPFILE_PREFIX); - - int openMode = deleteFileWhenDone - ? ZipFile.OPEN_READ | ZipFile.OPEN_DELETE - : ZipFile.OPEN_READ; - this.zipFile = new ZipFile(f, openMode); - - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - monitor.checkCancelled(); - ZipEntry currentEntry = entries.nextElement(); - fsIndex.storeFile(currentEntry.getName(), -1, currentEntry.isDirectory(), - currentEntry.getSize(), currentEntry); - } - } - - @Override - public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) { - ZipEntry zipEntry = fsIndex.getMetadata(file); - if (zipEntry == null) { - return FileAttributes.EMPTY; - } - FileAttributes result = new FileAttributes(); - - result.add(FileAttributeType.COMMENT_ATTR, zipEntry.getComment()); - result.add(FileAttributeType.SIZE_ATTR, zipEntry.getSize()); - Date date = new Date(zipEntry.getLastModifiedTime().toMillis()); - result.add(FileAttributeType.MODIFIED_DATE_ATTR, date); - result.add(FileAttributeType.COMPRESSED_SIZE_ATTR, zipEntry.getCompressedSize()); - result.add("CRC", Long.toHexString(zipEntry.getCrc())); - result.add("Compression Method", "0x" + Integer.toHexString(zipEntry.getMethod())); - return result; - } - - @Override - public String toString() { - return "ZipFilesystemBuiltin [ fsrl=" + fsFSRL + ", filename=" + zipFile.getName() + " ]"; - } - - @Override - public InputStream getInputStream(GFile file, TaskMonitor monitor) - throws IOException, CancelledException { - ZipEntry zipEntry = fsIndex.getMetadata(file); - return (zipEntry != null) ? zipFile.getInputStream(zipEntry) : null; - } - - @Override - public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) - throws IOException, CancelledException { - ZipEntry zipEntry = fsIndex.getMetadata(file); - if (zipEntry == null) { - return null; - } - return fsService.getDerivedByteProvider( - fsFSRL.getContainer(), - file.getFSRL(), - zipEntry.getName(), - zipEntry.getSize(), - () -> zipFile.getInputStream(zipEntry), - monitor); - } -} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystemFactory.java index b9613d5c7b..4a23416505 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystemFactory.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystemFactory.java @@ -15,11 +15,11 @@ */ package ghidra.file.formats.zip; -import java.io.File; import java.io.IOException; +import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; + import ghidra.app.util.bin.ByteProvider; -import ghidra.file.formats.sevenzip.SevenZipFileSystemFactory; import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider; import ghidra.formats.gfilesystem.factory.GFileSystemProbeBytesOnly; @@ -28,25 +28,8 @@ import ghidra.util.task.TaskMonitor; public class ZipFileSystemFactory implements GFileSystemFactoryByteProvider, GFileSystemProbeBytesOnly { - /** - * Use "-Dghidra.file.formats.zip.ZipFileSystemFactory.USE_BUILTIN_ZIP_SUPPORT=true" jvm - * startup option to disable use of 7zip libraries when opening zip filesystems. - */ - private static boolean USE_BUILTIN_ZIP_SUPPORT = - Boolean.getBoolean("ghidra.file.formats.zip.ZipFileSystemFactory.USE_BUILTIN_ZIP_SUPPORT"); - private static final int START_BYTES_REQUIRED = 2; - - /** - * Sets the static flag controlling which zip file implementation will used when opening - * a zip file system. - * - * @param b boolean true forces use of the built-in java zip library (in other words, disables - * use of 7zip), false will allow the 7zip libraries to be attempted to be used - */ - public static void setUseBuiltinZipSupport(boolean b) { - USE_BUILTIN_ZIP_SUPPORT = b; - } + private static final int START_BYTES_REQUIRED = 4; @Override public int getBytesRequired() { @@ -55,45 +38,22 @@ public class ZipFileSystemFactory @Override public boolean probeStartBytes(FSRL containerFSRL, byte[] startBytes) { - return startBytes[0] == 'P' && startBytes[1] == 'K'; + return ZipArchiveInputStream.matches(startBytes, START_BYTES_REQUIRED); } @Override public GFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider, FileSystemService fsService, TaskMonitor monitor) throws IOException, CancelledException { - - // Try to use 7zip to handle .zip files, or fall back to using the less feature rich - // built-in java zip file support. - if (!USE_BUILTIN_ZIP_SUPPORT && SevenZipFileSystemFactory.initNativeLibraries()) { - ZipFileSystem fs = new ZipFileSystem(targetFSRL, fsService); - try { - fs.mount(byteProvider, monitor); - return fs; - } - catch (IOException ioe) { - fs.close(); - throw ioe; - } + + ZipFileSystem fs = new ZipFileSystem(targetFSRL, fsService); + try { + fs.mount(byteProvider, monitor); + return fs; } - else { - File zipFile = fsService.getFileIfAvailable(byteProvider); - boolean deleteZipFileWhenDone = false; - if (zipFile == null) { - zipFile = fsService.createPlaintextTempFile(byteProvider, - ZipFileSystemBuiltin.TEMPFILE_PREFIX, monitor); - deleteZipFileWhenDone = true; - } - FSUtilities.uncheckedClose(byteProvider, null); - ZipFileSystemBuiltin fs = new ZipFileSystemBuiltin(targetFSRL, fsService); - try { - fs.mount(zipFile, deleteZipFileWhenDone, monitor); - return fs; - } - catch (IOException ioe) { - fs.close(); - throw ioe; - } + catch (IOException ioe) { + fs.close(); + throw ioe; } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/generic/auth/Password.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/generic/auth/Password.java index aa1b661d0c..0f07ed805a 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/generic/auth/Password.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/generic/auth/Password.java @@ -4,9 +4,9 @@ * 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. @@ -69,7 +69,9 @@ public class Password implements Closeable { */ @Override public void close() { - Arrays.fill(password, '\0'); + if (password != null) { + Arrays.fill(password, '\0'); + } password = null; }