From bc9164b95222b5c696c049996cb3403e1bd0a3df Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 26 Dec 2015 19:16:05 +0300 Subject: [PATCH] core: refactor file loading, add 'aar' support (fix #95) --- .../main/java/jadx/api/JadxDecompiler.java | 7 +- .../src/main/java/jadx/api/ResourceType.java | 6 +- .../main/java/jadx/api/ResourcesLoader.java | 13 +- .../java/jadx/core/clsp/ConvertToClsSet.java | 26 +--- .../java/jadx/core/dex/nodes/DexNode.java | 10 +- .../java/jadx/core/dex/nodes/RootNode.java | 21 +-- .../jadx/core/dex/visitors/RenameVisitor.java | 10 +- .../java/jadx/core/utils/files/DexFile.java | 32 ++++ .../java/jadx/core/utils/files/FileUtils.java | 11 ++ .../java/jadx/core/utils/files/InputFile.java | 140 ++++++++++-------- 10 files changed, 158 insertions(+), 118 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/utils/files/DexFile.java diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index fc8b78d5a..6df86668d 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -116,12 +116,7 @@ public final class JadxDecompiler { inputFiles.clear(); for (File file : files) { try { - InputFile inputFile = new InputFile(file); - inputFiles.add(inputFile); - while (inputFile.nextDexIndex != -1) { - inputFile = new InputFile(file, inputFile.nextDexIndex); - inputFiles.add(inputFile); - } + InputFile.addFilesFrom(file, inputFiles); } catch (IOException e) { throw new JadxException("Error load file: " + file, e); } diff --git a/jadx-core/src/main/java/jadx/api/ResourceType.java b/jadx-core/src/main/java/jadx/api/ResourceType.java index 3399f8ca6..702d72bba 100644 --- a/jadx-core/src/main/java/jadx/api/ResourceType.java +++ b/jadx-core/src/main/java/jadx/api/ResourceType.java @@ -1,10 +1,10 @@ package jadx.api; public enum ResourceType { - CODE(".dex", ".class"), + CODE(".dex", ".jar", ".class"), MANIFEST("AndroidManifest.xml"), - XML(".xml"), // TODO binary or not? - ARSC(".arsc"), // TODO decompile !!! + XML(".xml"), + ARSC(".arsc"), FONT(".ttf"), IMG(".png", ".gif", ".jpg"), LIB(".so"), diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java index eda08ec82..fa3d358f6 100644 --- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java +++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java @@ -85,12 +85,7 @@ public final class ResourcesLoader { return decodeStream(rf, new ResourceDecoder() { @Override public ResContainer decode(long size, InputStream is) throws IOException { - if (size > LOAD_SIZE_LIMIT) { - return ResContainer.singleFile(rf.getName(), - new CodeWriter().add("File too big, size: " - + String.format("%.2f KB", size / 1024.))); - } - return loadContent(jadxRef, rf, is); + return loadContent(jadxRef, rf, is, size); } }); } catch (JadxException e) { @@ -103,7 +98,7 @@ public final class ResourcesLoader { } private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf, - InputStream inputStream) throws IOException { + InputStream inputStream, long size) throws IOException { switch (rf.getType()) { case MANIFEST: case XML: @@ -113,6 +108,10 @@ public final class ResourcesLoader { case ARSC: return new ResTableParser().decodeFiles(inputStream); } + if (size > LOAD_SIZE_LIMIT) { + return ResContainer.singleFile(rf.getName(), + new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.))); + } return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream)); } diff --git a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java index 07cd69fb3..2d4485fd2 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java @@ -36,12 +36,7 @@ public class ConvertToClsSet { if (f.isDirectory()) { addFilesFromDirectory(f, inputFiles); } else { - InputFile inputFile = new InputFile(f); - inputFiles.add(inputFile); - while (inputFile.nextDexIndex != -1) { - inputFile = new InputFile(f, inputFile.nextDexIndex); - inputFiles.add(inputFile); - } + InputFile.addFilesFrom(f, inputFiles); } } for (InputFile inputFile : inputFiles) { @@ -58,8 +53,7 @@ public class ConvertToClsSet { LOG.info("done"); } - private static void addFilesFromDirectory(File dir, - List inputFiles) throws IOException, DecodeException { + private static void addFilesFromDirectory(File dir, List inputFiles) { File[] files = dir.listFiles(); if (files == null) { return; @@ -67,19 +61,13 @@ public class ConvertToClsSet { for (File file : files) { if (file.isDirectory()) { addFilesFromDirectory(file, inputFiles); - } - String fileName = file.getName(); - if (fileName.endsWith(".dex") - || fileName.endsWith(".jar") - || fileName.endsWith(".apk")) { - InputFile inputFile = new InputFile(file); - inputFiles.add(inputFile); - while (inputFile.nextDexIndex != -1) { - inputFile = new InputFile(file, inputFile.nextDexIndex); - inputFiles.add(inputFile); + } else { + try { + InputFile.addFilesFrom(file, inputFiles); + } catch (Exception e) { + LOG.warn("Skip file: {}, load error: {}", file, e.getMessage()); } } } } - } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java index 1e08e5efe..384d786e8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java @@ -6,7 +6,7 @@ import jadx.core.dex.info.InfoStorage; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.utils.exceptions.DecodeException; -import jadx.core.utils.files.InputFile; +import jadx.core.utils.files.DexFile; import java.util.ArrayList; import java.util.Collections; @@ -34,7 +34,7 @@ public class DexNode { private final RootNode root; private final Dex dexBuf; - private final InputFile file; + private final DexFile file; private final List classes = new ArrayList(); private final Map clsMap = new HashMap(); @@ -43,10 +43,10 @@ public class DexNode { private final InfoStorage infoStorage = new InfoStorage(); - public DexNode(RootNode root, InputFile input) { + public DexNode(RootNode root, DexFile input) { this.root = root; this.file = input; - this.dexBuf = input.getDexBuffer(); + this.dexBuf = input.getDexBuf(); } public void loadClasses() throws DecodeException { @@ -163,7 +163,7 @@ public class DexNode { return infoStorage; } - public InputFile getInputFile() { + public DexFile getDexFile() { return file; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 160f08500..8bff12bb0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -9,6 +9,7 @@ import jadx.core.dex.info.ClassInfo; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.files.DexFile; import jadx.core.utils.files.InputFile; import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResTableParser; @@ -42,16 +43,18 @@ public class RootNode { this.args = args; } - public void load(List dexFiles) throws DecodeException { - dexNodes = new ArrayList(dexFiles.size()); - for (InputFile dex : dexFiles) { - DexNode dexNode; - try { - dexNode = new DexNode(this, dex); - } catch (Exception e) { - throw new DecodeException("Error decode file: " + dex, e); + public void load(List inputFiles) throws DecodeException { + dexNodes = new ArrayList(); + for (InputFile input : inputFiles) { + for (DexFile dexFile : input.getDexFiles()) { + try { + LOG.debug("Load: {}", dexFile); + DexNode dexNode = new DexNode(this, dexFile); + dexNodes.add(dexNode); + } catch (Exception e) { + throw new DecodeException("Error decode file: " + dexFile, e); + } } - dexNodes.add(dexNode); } for (DexNode dexNode : dexNodes) { dexNode.loadClasses(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java index 9e3013025..c600c4139 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java @@ -14,11 +14,13 @@ import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.files.InputFile; import java.io.File; import java.util.HashSet; import java.util.Set; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOCase; public class RenameVisitor extends AbstractVisitor { @@ -31,10 +33,10 @@ public class RenameVisitor extends AbstractVisitor { public void init(RootNode root) { IJadxArgs args = root.getArgs(); - final String firstInputFileName = root.getDexNodes().get(0).getInputFile().getFile().getAbsolutePath(); - final String inputPath = org.apache.commons.io.FilenameUtils.getFullPathNoEndSeparator( - firstInputFileName); - final String inputName = org.apache.commons.io.FilenameUtils.getBaseName(firstInputFileName); + InputFile firstInputFile = root.getDexNodes().get(0).getDexFile().getInputFile(); + final String firstInputFileName = firstInputFile.getFile().getAbsolutePath(); + final String inputPath = FilenameUtils.getFullPathNoEndSeparator(firstInputFileName); + final String inputName = FilenameUtils.getBaseName(firstInputFileName); File deobfMapFile = new File(inputPath, inputName + ".jobf"); deobfuscator = new Deobfuscator(args, root.getDexNodes(), deobfMapFile); diff --git a/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java b/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java new file mode 100644 index 000000000..5ef656fb5 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java @@ -0,0 +1,32 @@ +package jadx.core.utils.files; + +import com.android.dex.Dex; + +public class DexFile { + private final InputFile inputFile; + private final String name; + private final Dex dexBuf; + + public DexFile(InputFile inputFile, String name, Dex dexBuf) { + this.inputFile = inputFile; + this.name = name; + this.dexBuf = dexBuf; + } + + public String getName() { + return name; + } + + public Dex getDexBuf() { + return dexBuf; + } + + public InputFile getInputFile() { + return inputFile; + } + + @Override + public String toString() { + return inputFile.toString() + (name.isEmpty() ? "" : ":" + name); + } +} diff --git a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java index c8b7b645e..d7e3826de 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java @@ -48,4 +48,15 @@ public class FileUtils { } } } + + public static File createTempFile(String suffix) { + File temp; + try { + temp = File.createTempFile("jadx-tmp-", System.nanoTime() + "-" + suffix); + temp.deleteOnExit(); + } catch (IOException e) { + throw new JadxRuntimeException("Failed to create temp file with suffix: " + suffix); + } + return temp; + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java index 42edfb065..bd06be0f4 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java @@ -3,16 +3,19 @@ package jadx.core.utils.files; import jadx.core.utils.AsmUtils; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.exceptions.JadxRuntimeException; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,49 +25,95 @@ public class InputFile { private static final Logger LOG = LoggerFactory.getLogger(InputFile.class); private final File file; - private final Dex dexBuf; - public int nextDexIndex = -1; - private final int dexIndex; + private final List dexFiles = new ArrayList(); - public InputFile(File file) throws IOException, DecodeException { - this(file, 0); + public static void addFilesFrom(File file, List list) throws IOException, DecodeException { + InputFile inputFile = new InputFile(file); + inputFile.searchDexFiles(); + list.add(inputFile); } - public InputFile(File file, int dexIndex) throws IOException, DecodeException { + private InputFile(File file) throws IOException, DecodeException { if (!file.exists()) { throw new IOException("File not found: " + file.getAbsolutePath()); } - this.dexIndex = dexIndex; this.file = file; - this.dexBuf = loadDexBuffer(); } - private Dex loadDexBuffer() throws IOException, DecodeException { + private void searchDexFiles() throws IOException, DecodeException { String fileName = file.getName(); if (fileName.endsWith(".dex")) { - return new Dex(file); + addDexFile(new Dex(file)); + return; } if (fileName.endsWith(".class")) { - return loadFromClassFile(file); + addDexFile(loadFromClassFile(file)); + return; } if (fileName.endsWith(".apk") || fileName.endsWith(".zip")) { - Dex dex = loadFromZip(this,file); - if (dex == null) { - throw new IOException("File 'classes.dex' not found in file: " + file); - } - return dex; + loadFromZip(".dex"); + return; } if (fileName.endsWith(".jar")) { - // check if jar contains 'classes.dex' - Dex dex = loadFromZip(this,file); - if (dex != null) { - return dex; + // check if jar contains '.dex' files + if (loadFromZip(".dex")) { + return; } - return loadFromJar(file); + addDexFile(loadFromJar(file)); + return; + } + if (fileName.endsWith(".aar")) { + loadFromZip(".jar"); + return; } throw new DecodeException("Unsupported input file format: " + file); } + private void addDexFile(Dex dexBuf) throws IOException { + addDexFile("", dexBuf); + } + + private void addDexFile(String fileName, Dex dexBuf) throws IOException { + dexFiles.add(new DexFile(this, fileName, dexBuf)); + } + + private boolean loadFromZip(String ext) throws IOException, DecodeException { + ZipFile zf = new ZipFile(file); + int index = 0; + while (true) { + String entryName = "classes" + (index == 0 ? "" : index) + ext; + ZipEntry entry = zf.getEntry(entryName); + if (entry == null) { + break; + } + InputStream inputStream = zf.getInputStream(entry); + try { + if (ext.equals(".dex")) { + addDexFile(entryName, new Dex(inputStream)); + } else if (ext.equals(".jar")) { + File jarFile = FileUtils.createTempFile(entryName); + FileOutputStream fos = new FileOutputStream(jarFile); + try { + IOUtils.copy(inputStream, fos); + } finally { + fos.close(); + } + addDexFile(entryName, loadFromJar(jarFile)); + } else { + throw new JadxRuntimeException("Unexpected extension in zip: " + ext); + } + } finally { + inputStream.close(); + } + index++; + if (index == 1) { + index = 2; + } + } + zf.close(); + return index > 0; + } + private static Dex loadFromJar(File jarFile) throws DecodeException { try { LOG.info("converting to dex: {} ...", jarFile.getName()); @@ -81,47 +130,8 @@ public class InputFile { } } - private static Dex loadFromZip(InputFile ipf, File file) throws IOException { - ZipFile zf = new ZipFile(file); - String dexName = "classes.dex"; - String futureDexName = "classes2.dex"; - if (ipf.dexIndex != 0) { - dexName = "classes" + ipf.dexIndex + ".dex"; - futureDexName = "classes" + (ipf.dexIndex + 1) + ".dex"; - } - ZipEntry dex = zf.getEntry(dexName); - if (dex == null) { - zf.close(); - return null; - } - try { - ZipEntry futureDex = zf.getEntry(futureDexName); - if (futureDex != null) { - ipf.nextDexIndex = ipf.dexIndex == 0 ? 2 : ipf.dexIndex + 1; - } - } catch (Exception ex) { - } - ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); - InputStream in = null; - try { - in = zf.getInputStream(dex); - byte[] buffer = new byte[8192]; - int count; - while ((count = in.read(buffer)) != -1) { - bytesOut.write(buffer, 0, count); - } - } finally { - if (in != null) { - in.close(); - } - zf.close(); - } - return new Dex(bytesOut.toByteArray()); - } - private static Dex loadFromClassFile(File file) throws IOException, DecodeException { - File outFile = File.createTempFile("jadx-tmp-", System.nanoTime() + ".jar"); - outFile.deleteOnExit(); + File outFile = FileUtils.createTempFile("cls.jar"); FileOutputStream out = null; JarOutputStream jo = null; try { @@ -147,12 +157,12 @@ public class InputFile { return file; } - public Dex getDexBuffer() { - return dexBuf; + public List getDexFiles() { + return dexFiles; } @Override public String toString() { - return file.toString(); + return file.getAbsolutePath(); } }