diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java index f5c297514..a9c13065e 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java @@ -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 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 collectDexFromZip(File file) { diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java index 1ccdec70a..e2d40407d 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java @@ -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 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 list) { List 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); } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java index 5df0d1c1b..8771a2d24 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java @@ -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 consumer) { diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeaderV41.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeaderV41.java new file mode 100644 index 000000000..3d5e2a3c9 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeaderV41.java @@ -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 readSubDexOffsets(byte[] content, DexHeaderV41 header) { + int start = 0; + int end = header.getFileSize(); + int limit = Math.min(header.getContainerSize(), content.length); + List 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; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/DataReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/DataReader.java new file mode 100644 index 000000000..c7b653be3 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/DataReader.java @@ -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; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/DexCheckSum.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/DexCheckSum.java index 4f6167778..e287ae489 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/DexCheckSum.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/DexCheckSum.java @@ -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));