feat(dex-input): initial support for DEX v41 (#2128)
This commit is contained in:
+27
-6
@@ -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
-4
@@ -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) {
|
||||
|
||||
+61
@@ -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;
|
||||
}
|
||||
}
|
||||
+12
@@ -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;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user