feat(dex-input): initial support for DEX v41 (#2128)

This commit is contained in:
Skylot
2025-02-12 22:19:08 +00:00
parent 8873038b57
commit 4ef1f3b12b
6 changed files with 108 additions and 15 deletions
@@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.plugins.input.dex.sections.DexConsts;
import jadx.plugins.input.dex.sections.DexHeaderV41;
import jadx.plugins.input.dex.utils.DexCheckSum;
public class DexFileLoader {
@@ -63,8 +64,7 @@ public class DexFileLoader {
if (isStartWithBytes(magic, DexConsts.DEX_FILE_MAGIC) || fileName.endsWith(".dex")) {
in.reset();
byte[] content = readAllBytes(in);
DexReader dexReader = loadDexReader(fileName, content);
return Collections.singletonList(dexReader);
return loadDexReaders(fileName, content);
}
if (file != null) {
// allow only top level zip files
@@ -76,11 +76,32 @@ public class DexFileLoader {
}
}
public DexReader loadDexReader(String fileName, byte[] content) {
if (options.isVerifyChecksum()) {
DexCheckSum.verify(content, fileName);
public List<DexReader> loadDexReaders(String fileName, byte[] content) {
DexHeaderV41 dexHeaderV41 = DexHeaderV41.readIfPresent(content);
if (dexHeaderV41 != null) {
return DexHeaderV41.readSubDexOffsets(content, dexHeaderV41)
.stream()
.map(offset -> loadSingleDex(fileName, content, offset))
.collect(Collectors.toList());
}
return new DexReader(getNextUniqId(), fileName, content);
DexReader dexReader = loadSingleDex(fileName, content, 0);
return Collections.singletonList(dexReader);
}
private DexReader loadSingleDex(String fileName, byte[] content, int offset) {
if (options.isVerifyChecksum()) {
DexCheckSum.verify(fileName, content, offset);
}
return new DexReader(getNextUniqId(), fileName, content, offset);
}
/**
* Since DEX v41, several sub DEX structures can be stored inside container of a single DEX file
* Use {@link DexFileLoader#loadDexReaders(String, byte[])} instead.
*/
@Deprecated
public DexReader loadDexReader(String fileName, byte[] content) {
return loadSingleDex(fileName, content, 0);
}
private List<DexReader> collectDexFromZip(File file) {
@@ -3,7 +3,6 @@ package jadx.plugins.input.dex;
import java.io.Closeable;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -48,8 +47,8 @@ public class DexInputPlugin implements JadxPlugin {
public ICodeLoader loadDex(byte[] content, @Nullable String fileName) {
String fileLabel = fileName == null ? "input.dex" : fileName;
DexReader dexReader = loader.loadDexReader(fileLabel, content);
return new DexLoadResult(Collections.singletonList(dexReader), null);
List<DexReader> dexReaders = loader.loadDexReaders(fileLabel, content);
return new DexLoadResult(dexReaders, null);
}
public ICodeLoader loadDexFromInputStream(InputStream in, @Nullable String fileLabel) {
@@ -62,7 +61,7 @@ public class DexInputPlugin implements JadxPlugin {
public ICodeLoader loadDexData(List<IDexData> list) {
List<DexReader> readers = list.stream()
.map(data -> loader.loadDexReader(data.getFileName(), data.getContent()))
.flatMap(data -> loader.loadDexReaders(data.getFileName(), data.getContent()).stream())
.collect(Collectors.toList());
return new DexLoadResult(readers, null);
}
@@ -15,11 +15,11 @@ public class DexReader {
private final ByteBuffer buf;
private final DexHeader header;
public DexReader(int uniqId, String inputFileName, byte[] content) {
public DexReader(int uniqId, String inputFileName, byte[] content, int offset) {
this.uniqId = uniqId;
this.inputFileName = inputFileName;
this.buf = ByteBuffer.wrap(content);
this.header = new DexHeader(new SectionReader(this, 0));
this.header = new DexHeader(new SectionReader(this, offset));
}
public void visitClasses(Consumer<IClassData> consumer) {
@@ -0,0 +1,61 @@
package jadx.plugins.input.dex.sections;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import static jadx.plugins.input.dex.utils.DataReader.readU4;
public class DexHeaderV41 {
public static @Nullable DexHeaderV41 readIfPresent(byte[] content) {
int headerSize = readU4(content, 36);
if (headerSize < 120) {
return null;
}
int fileSize = readU4(content, 32);
int containerSize = readU4(content, 112);
int headerOffset = readU4(content, 116);
return new DexHeaderV41(fileSize, containerSize, headerOffset);
}
public static List<Integer> readSubDexOffsets(byte[] content, DexHeaderV41 header) {
int start = 0;
int end = header.getFileSize();
int limit = Math.min(header.getContainerSize(), content.length);
List<Integer> list = new ArrayList<>();
while (true) {
list.add(start);
start = end;
if (start >= limit) {
break;
}
int nextFileSize = readU4(content, start + 32);
end = start + nextFileSize;
}
return list;
}
private final int fileSize;
private final int containerSize;
private final int headerOffset;
public DexHeaderV41(int fileSize, int containerSize, int headerOffset) {
this.fileSize = fileSize;
this.containerSize = containerSize;
this.headerOffset = headerOffset;
}
public int getFileSize() {
return fileSize;
}
public int getContainerSize() {
return containerSize;
}
public int getHeaderOffset() {
return headerOffset;
}
}
@@ -0,0 +1,12 @@
package jadx.plugins.input.dex.utils;
public class DataReader {
public static int readU4(byte[] data, int pos) {
byte b1 = data[pos++];
byte b2 = data[pos++];
byte b3 = data[pos++];
byte b4 = data[pos];
return (b4 & 0xFF) << 24 | (b3 & 0xFF) << 16 | (b2 & 0xFF) << 8 | b1 & 0xFF;
}
}
@@ -9,15 +9,15 @@ import static java.nio.ByteOrder.LITTLE_ENDIAN;
public class DexCheckSum {
public static void verify(byte[] content, String fileName) {
public static void verify(String fileName, byte[] content, int offset) {
int len = content.length;
if (len < 12) {
throw new DexException("Dex file truncated, length: " + len + ", file: " + fileName);
}
int checksum = ByteBuffer.wrap(content, 8, 4).order(LITTLE_ENDIAN).getInt();
int checksum = ByteBuffer.wrap(content, offset + 8, 4).order(LITTLE_ENDIAN).getInt();
Adler32 adler32 = new Adler32();
adler32.update(content, 12, len - 12);
int fileChecksum = (int) (adler32.getValue());
int fileChecksum = (int) adler32.getValue();
if (checksum != fileChecksum) {
throw new DexException(String.format("Bad dex file checksum: 0x%08x, expected: 0x%08x, file: %s",
fileChecksum, checksum, fileName));