feat: custom zip reader implementation to fight tampering

fix(zip): use size info from CD if LFH entry is incorrect

refactor: move custom zip implementation into new module

feat: move ZipSecurity into jadx-zip module
This commit is contained in:
Skylot
2025-02-23 20:51:28 +00:00
parent 5d720dd29c
commit d84f0389ec
54 changed files with 1557 additions and 514 deletions
@@ -5,6 +5,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
@@ -17,7 +18,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.plugins.files.TempFilesGetter;
import jadx.core.utils.files.FileUtils;
import jadx.zip.IZipEntry;
import jadx.zip.ZipContent;
import jadx.zip.ZipReader;
public class JavaInputLoader {
private static final Logger LOG = LoggerFactory.getLogger(JavaInputLoader.class);
@@ -26,8 +31,24 @@ public class JavaInputLoader {
private static final byte[] JAVA_CLASS_FILE_MAGIC = { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE };
private static final byte[] ZIP_FILE_MAGIC = { 0x50, 0x4B, 0x03, 0x04 };
private final ZipReader zipReader;
private final Path tempPath;
private int classUniqId = 1;
public JavaInputLoader(ZipReader zipReader, Path tempPath) {
this.zipReader = zipReader;
this.tempPath = tempPath;
}
/**
* This will use zip reader with default options and ignore provided in jadx args
*/
@Deprecated
public JavaInputLoader() {
this(new ZipReader(), TempFilesGetter.INSTANCE.getTempDir());
}
public List<JavaClassReader> collectFiles(List<Path> inputFiles) {
return inputFiles.stream()
.map(Path::toFile)
@@ -78,6 +99,23 @@ public class JavaInputLoader {
return Collections.emptyList();
}
private List<JavaClassReader> loadReaderFromZipEntry(byte[] content, String name, String parentFileName) throws IOException {
if (isStartWithBytes(content, JAVA_CLASS_FILE_MAGIC) || name.endsWith(".class")) {
String source = concatSource(parentFileName, name);
JavaClassReader reader = new JavaClassReader(getNextUniqId(), source, content);
return Collections.singletonList(reader);
}
if (isStartWithBytes(content, ZIP_FILE_MAGIC) || CommonFileUtils.isZipFileExt(name)) {
Path tempZip = Files.createTempFile(tempPath, "temp", ".zip");
FileUtils.writeFile(tempZip, content);
File zipFile = tempZip.toFile();
List<JavaClassReader> readers = collectFromZip(zipFile, concatSource(parentFileName, name));
CommonFileUtils.safeDeleteFile(zipFile);
return readers;
}
return Collections.emptyList();
}
private static String concatSource(@Nullable String parentFileName, String name) {
if (parentFileName == null) {
return name;
@@ -87,19 +125,28 @@ public class JavaInputLoader {
private List<JavaClassReader> collectFromZip(File file, String name) {
List<JavaClassReader> result = new ArrayList<>();
try {
ZipSecurity.readZipEntries(file, (entry, in) -> {
try (ZipContent zip = zipReader.open(file)) {
for (IZipEntry entry : zip.getEntries()) {
if (entry.isDirectory()) {
continue;
}
String entryName = entry.getName();
if (entryName.startsWith("META-INF/versions/")) {
// skip classes for different java versions
continue;
}
try {
String entryName = entry.getName();
if (entryName.startsWith("META-INF/versions/")) {
// skip classes for different java versions
return;
List<JavaClassReader> readers;
if (entry.preferBytes()) {
readers = loadReaderFromZipEntry(entry.getBytes(), entryName, name);
} else {
readers = loadReader(entry.getInputStream(), entryName, null, name);
}
result.addAll(loadReader(in, entryName, null, name));
result.addAll(readers);
} catch (Exception e) {
LOG.error("Failed to read zip entry: {}", entry, e);
}
});
}
} catch (Exception e) {
LOG.error("Failed to process zip file: {}", name, e);
}
@@ -25,7 +25,14 @@ public class JavaInputPlugin implements JadxPlugin {
@Override
public void init(JadxPluginContext context) {
context.addCodeInput(JavaInputPlugin::loadClassFiles);
context.addCodeInput(inputFiles -> {
JavaInputLoader loader = new JavaInputLoader(context.getZipReader(), context.files().getPluginTempDir());
List<JavaClassReader> readers = loader.collectFiles(inputFiles);
if (readers.isEmpty()) {
return EmptyCodeLoader.INSTANCE;
}
return new JavaLoadResult(readers, null);
});
}
public static ICodeLoader loadClassFiles(List<Path> inputFiles) {