Skip to content

Commit 4ef1f3b

Browse files
committed
feat(dex-input): initial support for DEX v41 (#2128)
1 parent 8873038 commit 4ef1f3b

File tree

6 files changed

+107
-14
lines changed

6 files changed

+107
-14
lines changed

jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java

+26-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import jadx.api.plugins.utils.CommonFileUtils;
2121
import jadx.api.plugins.utils.ZipSecurity;
2222
import jadx.plugins.input.dex.sections.DexConsts;
23+
import jadx.plugins.input.dex.sections.DexHeaderV41;
2324
import jadx.plugins.input.dex.utils.DexCheckSum;
2425

2526
public class DexFileLoader {
@@ -63,8 +64,7 @@ private List<DexReader> load(@Nullable File file, InputStream inputStream, Strin
6364
if (isStartWithBytes(magic, DexConsts.DEX_FILE_MAGIC) || fileName.endsWith(".dex")) {
6465
in.reset();
6566
byte[] content = readAllBytes(in);
66-
DexReader dexReader = loadDexReader(fileName, content);
67-
return Collections.singletonList(dexReader);
67+
return loadDexReaders(fileName, content);
6868
}
6969
if (file != null) {
7070
// allow only top level zip files
@@ -76,11 +76,32 @@ private List<DexReader> load(@Nullable File file, InputStream inputStream, Strin
7676
}
7777
}
7878

79-
public DexReader loadDexReader(String fileName, byte[] content) {
79+
public List<DexReader> loadDexReaders(String fileName, byte[] content) {
80+
DexHeaderV41 dexHeaderV41 = DexHeaderV41.readIfPresent(content);
81+
if (dexHeaderV41 != null) {
82+
return DexHeaderV41.readSubDexOffsets(content, dexHeaderV41)
83+
.stream()
84+
.map(offset -> loadSingleDex(fileName, content, offset))
85+
.collect(Collectors.toList());
86+
}
87+
DexReader dexReader = loadSingleDex(fileName, content, 0);
88+
return Collections.singletonList(dexReader);
89+
}
90+
91+
private DexReader loadSingleDex(String fileName, byte[] content, int offset) {
8092
if (options.isVerifyChecksum()) {
81-
DexCheckSum.verify(content, fileName);
93+
DexCheckSum.verify(fileName, content, offset);
8294
}
83-
return new DexReader(getNextUniqId(), fileName, content);
95+
return new DexReader(getNextUniqId(), fileName, content, offset);
96+
}
97+
98+
/**
99+
* Since DEX v41, several sub DEX structures can be stored inside container of a single DEX file
100+
* Use {@link DexFileLoader#loadDexReaders(String, byte[])} instead.
101+
*/
102+
@Deprecated
103+
public DexReader loadDexReader(String fileName, byte[] content) {
104+
return loadSingleDex(fileName, content, 0);
84105
}
85106

86107
private List<DexReader> collectDexFromZip(File file) {

jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.io.Closeable;
44
import java.io.InputStream;
55
import java.nio.file.Path;
6-
import java.util.Collections;
76
import java.util.List;
87
import java.util.stream.Collectors;
98

@@ -48,8 +47,8 @@ public ICodeLoader loadFiles(List<Path> inputFiles, @Nullable Closeable closeabl
4847

4948
public ICodeLoader loadDex(byte[] content, @Nullable String fileName) {
5049
String fileLabel = fileName == null ? "input.dex" : fileName;
51-
DexReader dexReader = loader.loadDexReader(fileLabel, content);
52-
return new DexLoadResult(Collections.singletonList(dexReader), null);
50+
List<DexReader> dexReaders = loader.loadDexReaders(fileLabel, content);
51+
return new DexLoadResult(dexReaders, null);
5352
}
5453

5554
public ICodeLoader loadDexFromInputStream(InputStream in, @Nullable String fileLabel) {
@@ -62,7 +61,7 @@ public ICodeLoader loadDexFromInputStream(InputStream in, @Nullable String fileL
6261

6362
public ICodeLoader loadDexData(List<IDexData> list) {
6463
List<DexReader> readers = list.stream()
65-
.map(data -> loader.loadDexReader(data.getFileName(), data.getContent()))
64+
.flatMap(data -> loader.loadDexReaders(data.getFileName(), data.getContent()).stream())
6665
.collect(Collectors.toList());
6766
return new DexLoadResult(readers, null);
6867
}

jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ public class DexReader {
1515
private final ByteBuffer buf;
1616
private final DexHeader header;
1717

18-
public DexReader(int uniqId, String inputFileName, byte[] content) {
18+
public DexReader(int uniqId, String inputFileName, byte[] content, int offset) {
1919
this.uniqId = uniqId;
2020
this.inputFileName = inputFileName;
2121
this.buf = ByteBuffer.wrap(content);
22-
this.header = new DexHeader(new SectionReader(this, 0));
22+
this.header = new DexHeader(new SectionReader(this, offset));
2323
}
2424

2525
public void visitClasses(Consumer<IClassData> consumer) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package jadx.plugins.input.dex.sections;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.jetbrains.annotations.Nullable;
7+
8+
import static jadx.plugins.input.dex.utils.DataReader.readU4;
9+
10+
public class DexHeaderV41 {
11+
12+
public static @Nullable DexHeaderV41 readIfPresent(byte[] content) {
13+
int headerSize = readU4(content, 36);
14+
if (headerSize < 120) {
15+
return null;
16+
}
17+
int fileSize = readU4(content, 32);
18+
int containerSize = readU4(content, 112);
19+
int headerOffset = readU4(content, 116);
20+
return new DexHeaderV41(fileSize, containerSize, headerOffset);
21+
}
22+
23+
public static List<Integer> readSubDexOffsets(byte[] content, DexHeaderV41 header) {
24+
int start = 0;
25+
int end = header.getFileSize();
26+
int limit = Math.min(header.getContainerSize(), content.length);
27+
List<Integer> list = new ArrayList<>();
28+
while (true) {
29+
list.add(start);
30+
start = end;
31+
if (start >= limit) {
32+
break;
33+
}
34+
int nextFileSize = readU4(content, start + 32);
35+
end = start + nextFileSize;
36+
}
37+
return list;
38+
}
39+
40+
private final int fileSize;
41+
private final int containerSize;
42+
private final int headerOffset;
43+
44+
public DexHeaderV41(int fileSize, int containerSize, int headerOffset) {
45+
this.fileSize = fileSize;
46+
this.containerSize = containerSize;
47+
this.headerOffset = headerOffset;
48+
}
49+
50+
public int getFileSize() {
51+
return fileSize;
52+
}
53+
54+
public int getContainerSize() {
55+
return containerSize;
56+
}
57+
58+
public int getHeaderOffset() {
59+
return headerOffset;
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package jadx.plugins.input.dex.utils;
2+
3+
public class DataReader {
4+
5+
public static int readU4(byte[] data, int pos) {
6+
byte b1 = data[pos++];
7+
byte b2 = data[pos++];
8+
byte b3 = data[pos++];
9+
byte b4 = data[pos];
10+
return (b4 & 0xFF) << 24 | (b3 & 0xFF) << 16 | (b2 & 0xFF) << 8 | b1 & 0xFF;
11+
}
12+
}

jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/DexCheckSum.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99

1010
public class DexCheckSum {
1111

12-
public static void verify(byte[] content, String fileName) {
12+
public static void verify(String fileName, byte[] content, int offset) {
1313
int len = content.length;
1414
if (len < 12) {
1515
throw new DexException("Dex file truncated, length: " + len + ", file: " + fileName);
1616
}
17-
int checksum = ByteBuffer.wrap(content, 8, 4).order(LITTLE_ENDIAN).getInt();
17+
int checksum = ByteBuffer.wrap(content, offset + 8, 4).order(LITTLE_ENDIAN).getInt();
1818
Adler32 adler32 = new Adler32();
1919
adler32.update(content, 12, len - 12);
20-
int fileChecksum = (int) (adler32.getValue());
20+
int fileChecksum = (int) adler32.getValue();
2121
if (checksum != fileChecksum) {
2222
throw new DexException(String.format("Bad dex file checksum: 0x%08x, expected: 0x%08x, file: %s",
2323
fileChecksum, checksum, fileName));

0 commit comments

Comments
 (0)