diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackZipParser.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackZipParser.java index 11ecc0b2e..7e41fd224 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackZipParser.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackZipParser.java @@ -17,8 +17,8 @@ import jadx.zip.IZipEntry; import jadx.zip.IZipParser; import jadx.zip.ZipContent; import jadx.zip.ZipReaderOptions; +import jadx.zip.io.LimitedInputStream; import jadx.zip.security.IJadxZipSecurity; -import jadx.zip.security.LimitedInputStream; public class FallbackZipParser implements IZipParser { private static final Logger LOG = LoggerFactory.getLogger(FallbackZipParser.class); diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/ByteBufferBackedInputStream.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/ByteBufferBackedInputStream.java new file mode 100644 index 000000000..8b54d80dd --- /dev/null +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/ByteBufferBackedInputStream.java @@ -0,0 +1,46 @@ +package jadx.zip.io; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +public class ByteBufferBackedInputStream extends InputStream { + private final ByteBuffer buf; + private int markedPosition = 0; + + public ByteBufferBackedInputStream(ByteBuffer buf) { + this.buf = buf; + } + + public int read() throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + return buf.get() & 0xFF; + } + + @SuppressWarnings("NullableProblems") + public int read(byte[] bytes, int off, int len) throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + int readLen = Math.min(len, buf.remaining()); + buf.get(bytes, off, readLen); + return readLen; + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public synchronized void mark(int unused) { + markedPosition = buf.position(); + } + + @Override + public synchronized void reset() { + buf.position(markedPosition); + } +} diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/security/LimitedInputStream.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/LimitedInputStream.java similarity index 81% rename from jadx-commons/jadx-zip/src/main/java/jadx/zip/security/LimitedInputStream.java rename to jadx-commons/jadx-zip/src/main/java/jadx/zip/io/LimitedInputStream.java index 66a9e04f5..543b58203 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/security/LimitedInputStream.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/LimitedInputStream.java @@ -1,4 +1,4 @@ -package jadx.zip.security; +package jadx.zip.io; import java.io.FilterInputStream; import java.io.IOException; @@ -8,6 +8,7 @@ public class LimitedInputStream extends FilterInputStream { private final long maxSize; private long currentPos; + private long markPos; public LimitedInputStream(InputStream in, long maxSize) { super(in); @@ -50,7 +51,14 @@ public class LimitedInputStream extends FilterInputStream { } @Override - public boolean markSupported() { - return false; + public void mark(int readLimit) { + super.mark(readLimit); + markPos = currentPos; + } + + @Override + public void reset() throws IOException { + super.reset(); + currentPos = markPos; } } diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipParser.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipParser.java index 8df43a44e..2d74ef7c2 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipParser.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipParser.java @@ -1,6 +1,5 @@ package jadx.zip.parser; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -23,6 +22,8 @@ import jadx.zip.ZipContent; import jadx.zip.ZipReaderFlags; import jadx.zip.ZipReaderOptions; import jadx.zip.fallback.FallbackZipParser; +import jadx.zip.io.ByteBufferBackedInputStream; +import jadx.zip.io.LimitedInputStream; import jadx.zip.security.IJadxZipSecurity; /** @@ -46,6 +47,7 @@ public final class JadxZipParser implements IZipParser { private final IJadxZipSecurity zipSecurity; private final Set flags; private final boolean verify; + private final boolean useLimitedDataStream; private RandomAccessFile file; private FileChannel fileChannel; @@ -61,6 +63,7 @@ public final class JadxZipParser implements IZipParser { this.zipSecurity = options.getZipSecurity(); this.flags = options.getFlags(); this.verify = options.getFlags().contains(ZipReaderFlags.REPORT_TAMPERING); + this.useLimitedDataStream = zipSecurity.useLimitedDataStream(); } @Override @@ -287,47 +290,74 @@ public final class JadxZipParser implements IZipParser { } } - InputStream getInputStream(JadxZipEntry entry) { - return new ByteArrayInputStream(getBytes(entry)); + synchronized InputStream getInputStream(JadxZipEntry entry) { + if (verify) { + verifyEntry(entry); + } + InputStream stream; + if (entry.getCompressMethod() == 8) { + try { + stream = ZipDeflate.decompressEntryToStream(byteBuffer, entry); + } catch (Exception e) { + entryParseFailed(entry, e); + return useFallbackParser(entry).getInputStream(); + } + } else { + // treat any other compression methods values as UNCOMPRESSED + stream = bufferToStream(byteBuffer, entry.getDataStart(), (int) entry.getUncompressedSize()); + } + if (useLimitedDataStream) { + return new LimitedInputStream(stream, entry.getUncompressedSize()); + } + return stream; } synchronized byte[] getBytes(JadxZipEntry entry) { - int compressMethod = entry.getCompressMethod(); if (verify) { - if (compressMethod == 0) { - if (entry.getCompressedSize() != entry.getUncompressedSize()) { - LOG.warn("Not equal sizes for STORE method: compressed: {}, uncompressed: {}, entry: {}", - entry.getCompressedSize(), entry.getUncompressedSize(), entry); - } - } else if (compressMethod != 8) { - LOG.warn("Unknown compress method: {} in entry: {}", compressMethod, entry); - } + verifyEntry(entry); } - if (compressMethod == 8) { + if (entry.getCompressMethod() == 8) { try { return ZipDeflate.decompressEntryToBytes(byteBuffer, entry); } catch (Exception e) { - if (isEncrypted(entry)) { - throw new RuntimeException("Entry is encrypted, failed to decompress: " + entry, e); - } - if (flags.contains(ZipReaderFlags.DONT_USE_FALLBACK)) { - throw new RuntimeException("Failed to decompress zip entry: " + entry + ", error: " + e.getMessage(), e); - } - LOG.warn("Entry '{}' parse failed, switching to fallback parser", entry, e); - return useFallbackParser(entry); + entryParseFailed(entry, e); + return useFallbackParser(entry).getBytes(); } } // treat any other compression methods values as UNCOMPRESSED - return bufferToBytes(entry.getDataStart(), (int) entry.getUncompressedSize()); + return bufferToBytes(byteBuffer, entry.getDataStart(), (int) entry.getUncompressedSize()); + } + + private static void verifyEntry(JadxZipEntry entry) { + int compressMethod = entry.getCompressMethod(); + if (compressMethod == 0) { + if (entry.getCompressedSize() != entry.getUncompressedSize()) { + LOG.warn("Not equal sizes for STORE method: compressed: {}, uncompressed: {}, entry: {}", + entry.getCompressedSize(), entry.getUncompressedSize(), entry); + } + } else if (compressMethod != 8) { + LOG.warn("Unknown compress method: {} in entry: {}", compressMethod, entry); + } + } + + private void entryParseFailed(JadxZipEntry entry, Exception e) { + if (isEncrypted(entry)) { + throw new RuntimeException("Entry is encrypted, failed to decompress: " + entry, e); + } + if (flags.contains(ZipReaderFlags.DONT_USE_FALLBACK)) { + throw new RuntimeException("Failed to decompress zip entry: " + entry + ", error: " + e.getMessage(), e); + } + LOG.warn("Entry '{}' parse failed, switching to fallback parser", entry, e); } @SuppressWarnings("resource") - private byte[] useFallbackParser(JadxZipEntry entry) { + private IZipEntry useFallbackParser(JadxZipEntry entry) { + LOG.debug("useFallbackParser used for {}", entry); IZipEntry zipEntry = initFallbackParser().searchEntry(entry.getName()); if (zipEntry == null) { throw new RuntimeException("Fallback parser can't find entry: " + entry); } - return zipEntry.getBytes(); + return zipEntry; } @SuppressWarnings("resource") @@ -353,14 +383,20 @@ public final class JadxZipParser implements IZipParser { return readU2(buf); } - byte[] bufferToBytes(int start, int size) { - ByteBuffer buf = byteBuffer; + static byte[] bufferToBytes(ByteBuffer buf, int start, int size) { byte[] data = new byte[size]; buf.position(start); buf.get(data); return data; } + static InputStream bufferToStream(ByteBuffer buf, int start, int size) { + buf.position(start); + ByteBuffer streamBuf = buf.slice(); + streamBuf.limit(size); + return new ByteBufferBackedInputStream(streamBuf); + } + private static int readU2(ByteBuffer buf) { return buf.getShort() & 0xFFFF; } diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/ZipDeflate.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/ZipDeflate.java index 357314671..625d73e9b 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/ZipDeflate.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/ZipDeflate.java @@ -1,10 +1,15 @@ package jadx.zip.parser; +import java.io.InputStream; import java.nio.ByteBuffer; import java.util.zip.DataFormatException; import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import static jadx.zip.parser.JadxZipParser.bufferToStream; final class ZipDeflate { + private static final int BUFFER_SIZE = 4096; static byte[] decompressEntryToBytes(ByteBuffer buf, JadxZipEntry entry) throws DataFormatException { buf.position(entry.getDataStart()); @@ -14,11 +19,17 @@ final class ZipDeflate { Inflater inflater = new Inflater(true); inflater.setInput(entryBuf); int written = inflater.inflate(out); + inflater.end(); if (written != out.length) { throw new DataFormatException("Unexpected size of decompressed entry: " + entry + ", got: " + written + ", expected: " + out.length); } - inflater.end(); return out; } + + static InputStream decompressEntryToStream(ByteBuffer buf, JadxZipEntry entry) { + InputStream stream = bufferToStream(buf, entry.getDataStart(), (int) entry.getCompressedSize()); + Inflater inflater = new Inflater(true); + return new InflaterInputStream(stream, inflater, BUFFER_SIZE); + } } diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/security/JadxZipSecurity.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/security/JadxZipSecurity.java index 7b1404cca..2ff646c5e 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/security/JadxZipSecurity.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/security/JadxZipSecurity.java @@ -27,6 +27,8 @@ public class JadxZipSecurity implements IJadxZipSecurity { private int maxEntriesCount = 100_000; + private boolean useLimitedDataStream = true; + @Override public boolean isValidEntry(IZipEntry entry) { return isValidEntryName(entry.getName()) && !isZipBomb(entry); @@ -34,7 +36,7 @@ public class JadxZipSecurity implements IJadxZipSecurity { @Override public boolean useLimitedDataStream() { - return false; + return useLimitedDataStream; } @Override @@ -115,6 +117,10 @@ public class JadxZipSecurity implements IJadxZipSecurity { this.zipBombMinUncompressedSize = zipBombMinUncompressedSize; } + public void setUseLimitedDataStream(boolean useLimitedDataStream) { + this.useLimitedDataStream = useLimitedDataStream; + } + private static File getCWD() { try { return new File(".").getCanonicalFile(); diff --git a/jadx-core/src/main/java/jadx/api/plugins/utils/ZipSecurity.java b/jadx-core/src/main/java/jadx/api/plugins/utils/ZipSecurity.java index 2d3cf9b74..ddd89bfd8 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/utils/ZipSecurity.java +++ b/jadx-core/src/main/java/jadx/api/plugins/utils/ZipSecurity.java @@ -16,10 +16,10 @@ import jadx.api.plugins.JadxPluginContext; import jadx.core.utils.Utils; import jadx.zip.IZipEntry; import jadx.zip.ZipReader; +import jadx.zip.io.LimitedInputStream; import jadx.zip.security.DisabledZipSecurity; import jadx.zip.security.IJadxZipSecurity; import jadx.zip.security.JadxZipSecurity; -import jadx.zip.security.LimitedInputStream; /** * Deprecated, migrate to {@link ZipReader}.
diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java b/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java index 3346126a5..e84459d32 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java @@ -1,5 +1,6 @@ package jadx.core.xmlgen; +import java.io.BufferedInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -21,7 +22,7 @@ public class ParserStream { private long markPos = 0; public ParserStream(@NotNull InputStream inputStream) { - this.input = inputStream; + this.input = inputStream.markSupported() ? inputStream : new BufferedInputStream(inputStream); } public long getPos() {