From 1560284831aec19433043f9b818fe4daf46ef607 Mon Sep 17 00:00:00 2001 From: Skylot Date: Thu, 6 Aug 2020 13:04:45 +0100 Subject: [PATCH] refactor: fix zip security in dex plugin, remove smali deps from jadx-core --- jadx-core/build.gradle | 6 -- .../src/main/java/jadx/api/ResourceFile.java | 2 +- .../main/java/jadx/api/ResourcesLoader.java | 2 +- .../src/main/java/jadx/core/clsp/ClsSet.java | 2 +- .../jadx/core/codegen/json/JsonCodeGen.java | 2 +- .../java/jadx/core/dex/nodes/ClassNode.java | 51 +++++-------- .../java/jadx/core/dex/nodes/FieldNode.java | 5 +- .../java/jadx/core/dex/nodes/IDexNode.java | 4 +- .../java/jadx/core/dex/nodes/MethodNode.java | 5 +- .../java/jadx/core/dex/visitors/SaveCode.java | 2 +- .../java/jadx/core/utils/ErrorsCounter.java | 8 +- .../main/java/jadx/core/utils/SmaliUtils.java | 60 --------------- .../java/jadx/core/xmlgen/ResourcesSaver.java | 2 +- .../jadx/tests/external/BaseExternalTest.java | 2 +- .../main/java/jadx/gui/ui/RenameDialog.java | 16 ++-- jadx-plugins/jadx-dex-input/build.gradle | 6 +- .../jadx/plugins/input/dex/DexFileLoader.java | 74 ++++++++++++------- .../jadx/plugins/input/dex/DexLoadResult.java | 10 +-- .../jadx/plugins/input/dex/DexReader.java | 39 ++-------- .../input/dex/sections/DexClassData.java | 13 +++- .../plugins/input/dex/utils/SmaliUtils.java | 33 +++++++++ .../plugins/input/dex/DexInputPluginTest.java | 7 +- .../{SmaliUtils.java => SmaliTestUtils.java} | 2 +- .../api/plugins/input/data/IClassData.java | 6 +- .../jadx/api/plugins/utils}/ZipSecurity.java | 2 +- 25 files changed, 152 insertions(+), 209 deletions(-) delete mode 100644 jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/SmaliUtils.java rename jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/{SmaliUtils.java => SmaliTestUtils.java} (97%) rename {jadx-core/src/main/java/jadx/core/utils/files => jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils}/ZipSecurity.java (98%) diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index b0d2698da..0d410aaee 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -10,12 +10,6 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.6' - implementation 'org.smali:baksmali:2.4.0' - implementation('org.smali:smali:2.4.0') { - exclude group: 'com.google.guava' - } - implementation 'com.google.guava:guava:29.0-jre' - testImplementation 'org.apache.commons:commons-lang3:3.10' testRuntimeOnly(project(':jadx-plugins:jadx-dex-input')) diff --git a/jadx-core/src/main/java/jadx/api/ResourceFile.java b/jadx-core/src/main/java/jadx/api/ResourceFile.java index c5f56f5df..ff8d0abe2 100644 --- a/jadx-core/src/main/java/jadx/api/ResourceFile.java +++ b/jadx-core/src/main/java/jadx/api/ResourceFile.java @@ -2,7 +2,7 @@ package jadx.api; import java.io.File; -import jadx.core.utils.files.ZipSecurity; +import jadx.api.plugins.utils.ZipSecurity; import jadx.core.xmlgen.ResContainer; public class ResourceFile { diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java index 7ebe2e09d..8c67cd64a 100644 --- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java +++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java @@ -17,12 +17,12 @@ import org.slf4j.LoggerFactory; import jadx.api.ResourceFile.ZipRef; import jadx.api.impl.SimpleCodeInfo; +import jadx.api.plugins.utils.ZipSecurity; import jadx.core.codegen.CodeWriter; import jadx.core.utils.Utils; import jadx.core.utils.android.Res9patchStreamDecoder; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.ZipSecurity; import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResTableParser; diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java index db98c74f1..dd7fdd927 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java @@ -27,6 +27,7 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.utils.ZipSecurity; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.MethodInfo; @@ -37,7 +38,6 @@ import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.ZipSecurity; import static jadx.core.utils.Utils.notEmpty; diff --git a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java index 67ccab5b8..d5cc20519 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java @@ -68,7 +68,7 @@ public class JsonCodeGen { JsonClass jsonCls = new JsonClass(); jsonCls.setPkg(classInfo.getAliasPkg()); - jsonCls.setDex(cls.getInputPath().toString()); + jsonCls.setDex(cls.getInputFileName()); jsonCls.setName(classInfo.getFullName()); if (classInfo.hasAlias()) { jsonCls.setAlias(classInfo.getAliasFullName()); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 770ee5954..5efa57c81 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -1,7 +1,5 @@ package jadx.core.dex.nodes; -import java.io.StringWriter; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -39,7 +37,6 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.visitors.ProcessAnonymous; -import jadx.core.utils.SmaliUtils; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -50,10 +47,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private final RootNode root; - private final IClassData cls; - private final int clsDefOffset; - @Nullable - private final Path inputPath; + private final IClassData clsData; private final ClassInfo clsInfo; private AccessInfo accessFlags; @@ -86,11 +80,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN public ClassNode(RootNode root, IClassData cls) { this.root = root; - this.inputPath = cls.getInputPath(); - this.clsDefOffset = cls.getClassDefOffset(); this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType())); initialLoad(cls); - this.cls = cls.copy(); // TODO: need only for rename feature + this.clsData = cls.copy(); } private void initialLoad(IClassData cls) { @@ -152,9 +144,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN // Create empty class private ClassNode(RootNode root, String name, int accessFlags) { this.root = root; - this.cls = null; - this.inputPath = null; - this.clsDefOffset = 0; + this.clsData = null; this.clsInfo = ClassInfo.fromName(root, name); this.interfaces = new ArrayList<>(); this.methods = new ArrayList<>(); @@ -299,13 +289,13 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } public void deepUnload() { - if (cls == null) { + if (clsData == null) { // manually added class return; } clearAttributes(); root().getConstValues().removeForClass(this); - initialLoad(cls); + initialLoad(clsData); ProcessAnonymous.runForClass(this); for (ClassNode innerClass : innerClasses) { @@ -610,29 +600,28 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN public String getSmali() { if (smali == null) { - StringWriter stringWriter = new StringWriter(4096); - getSmali(this, stringWriter); - stringWriter.append(System.lineSeparator()); + StringBuilder sb = new StringBuilder(); + getSmali(sb); + sb.append(System.lineSeparator()); Set allInlinedClasses = new LinkedHashSet<>(); getInnerAndInlinedClassesRecursive(allInlinedClasses); for (ClassNode innerClass : allInlinedClasses) { - getSmali(innerClass, stringWriter); - stringWriter.append(System.lineSeparator()); + innerClass.getSmali(sb); + sb.append(System.lineSeparator()); } - smali = stringWriter.toString(); + smali = sb.toString(); } return smali; } - protected static boolean getSmali(ClassNode classNode, StringWriter stringWriter) { - Path inputPath = classNode.inputPath; - if (inputPath == null) { - stringWriter.append(String.format("###### Class %s is created by jadx", classNode.getFullName())); - return false; + protected void getSmali(StringBuilder sb) { + if (this.clsData == null) { + sb.append(String.format("###### Class %s is created by jadx", getFullName())); + return; } - stringWriter.append(String.format("###### Class %s (%s)", classNode.getFullName(), classNode.getRawName())); - stringWriter.append(System.lineSeparator()); - return SmaliUtils.getSmaliCode(inputPath, classNode.clsDefOffset, stringWriter); + sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName())); + sb.append(System.lineSeparator()); + sb.append(this.clsData.getDisassembledCode()); } public ProcessState getState() { @@ -668,8 +657,8 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } @Override - public Path getInputPath() { - return inputPath; + public String getInputFileName() { + return clsData == null ? "synthetic" : clsData.getInputFileName(); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index 7a0a60513..ce7d0541c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -1,6 +1,5 @@ package jadx.core.dex.nodes; -import java.nio.file.Path; import java.util.Collections; import java.util.List; @@ -88,8 +87,8 @@ public class FieldNode extends LineAttrNode implements ICodeNode { } @Override - public Path getInputPath() { - return parentClass.getInputPath(); + public String getInputFileName() { + return parentClass.getInputFileName(); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java index c8ec7da4a..730dab096 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java @@ -1,12 +1,10 @@ package jadx.core.dex.nodes; -import java.nio.file.Path; - public interface IDexNode { String typeName(); RootNode root(); - Path getInputPath(); + String getInputFileName(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index c522fd179..dba60cd80 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -1,6 +1,5 @@ package jadx.core.dex.nodes; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -598,8 +597,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, } @Override - public Path getInputPath() { - return parentClass.getInputPath(); + public String getInputFileName() { + return parentClass.getInputFileName(); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java index be0c9340a..1e530c0df 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java @@ -8,11 +8,11 @@ import org.slf4j.LoggerFactory; import jadx.api.ICodeInfo; import jadx.api.JadxArgs; +import jadx.api.plugins.utils.ZipSecurity; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.ZipSecurity; public class SaveCode { private static final Logger LOG = LoggerFactory.getLogger(SaveCode.class); diff --git a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java index 3689a88ba..f456c3abe 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java +++ b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java @@ -1,6 +1,5 @@ package jadx.core.utils; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -37,12 +36,7 @@ public class ErrorsCounter { } public static String formatMsg(IDexNode node, String msg) { - return msg + " in " + node.typeName() + ": " + node + ", file: " + getNodeFile(node); - } - - private static String getNodeFile(IDexNode node) { - Path inputPath = node.getInputPath(); - return inputPath == null ? "synthetic" : inputPath.toString(); + return msg + " in " + node.typeName() + ": " + node + ", file: " + node.getInputFileName(); } private synchronized String addError(N node, String error, @Nullable Throwable e) { diff --git a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java deleted file mode 100644 index 1ff8f2bdf..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java +++ /dev/null @@ -1,60 +0,0 @@ -package jadx.core.utils; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.jf.baksmali.Adaptors.ClassDefinition; -import org.jf.baksmali.BaksmaliOptions; -import org.jf.dexlib2.dexbacked.DexBackedClassDef; -import org.jf.dexlib2.dexbacked.DexBackedDexFile; -import org.jf.smali.Smali; -import org.jf.smali.SmaliOptions; -import org.jf.util.IndentingWriter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.core.utils.exceptions.JadxRuntimeException; - -// TODO: move smali dependencies out from jadx-core -public class SmaliUtils { - - private static final Logger LOG = LoggerFactory.getLogger(SmaliUtils.class); - - public static void assembleDex(String outputDexFile, String inputSmali) { - try { - SmaliOptions options = new SmaliOptions(); - options.outputDexFile = outputDexFile; - Smali.assemble(options, inputSmali); - } catch (Exception e) { - throw new JadxRuntimeException("Smali assemble error", e); - } - } - - public static boolean getSmaliCode(Path path, int clsDefOffset, StringWriter stringWriter) { - if (clsDefOffset == 0) { - return false; - } - try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(path))) { - DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(null, inputStream); - DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset, 0); - getSmaliCode(dexBackedClassDef, stringWriter); - return true; - } catch (Exception e) { - LOG.error("Error generating smali", e); - stringWriter.append("Error generating smali code: "); - stringWriter.append(e.getMessage()); - stringWriter.append(System.lineSeparator()); - stringWriter.append(Utils.getStackTrace(e)); - return false; - } - } - - private static void getSmaliCode(DexBackedClassDef classDef, StringWriter stringWriter) throws IOException { - ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef); - classDefinition.writeTo(new IndentingWriter(stringWriter)); - } -} diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java index 617b17300..965284f78 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java @@ -9,11 +9,11 @@ import org.slf4j.LoggerFactory; import jadx.api.ResourceFile; import jadx.api.ResourcesLoader; +import jadx.api.plugins.utils.ZipSecurity; import jadx.core.dex.visitors.SaveCode; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.ZipSecurity; public class ResourcesSaver implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(ResourcesSaver.class); diff --git a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java index 55ba0b7db..1cd7c843b 100644 --- a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -100,7 +100,7 @@ public abstract class BaseExternalTest extends IntegrationTest { throw new JadxRuntimeException("Class process failed", e); } LOG.info("----------------------------------------------------------------"); - LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.getInputPath()); + LOG.info("Print class: {} from: {}", classNode.getFullName(), classNode.getInputFileName()); if (mthPattern != null) { printMethods(classNode, mthPattern); } else { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index e4fdd0158..1d46585a0 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -132,26 +132,24 @@ public class RenameDialog extends JDialog { return String.format("%s %s = %s", type, id, renameText); } - private boolean writeDeobfMapFile(Path deobfMapPath, List deobfMap) throws IOException { + private void writeDeobfMapFile(Path deobfMapPath, List deobfMap) throws IOException { if (deobfMapPath == null) { LOG.error("updateDeobfMapFile(): deobfMapPath is null!"); - return false; + return; } - File tmpFile = File.createTempFile("deobf_tmp_", ".txt"); - FileOutputStream fileOut = new FileOutputStream(tmpFile); - for (String entry : deobfMap) { - fileOut.write(entry.getBytes()); - fileOut.write(System.lineSeparator().getBytes()); + try (FileOutputStream fileOut = new FileOutputStream(tmpFile)) { + for (String entry : deobfMap) { + fileOut.write(entry.getBytes()); + fileOut.write(System.lineSeparator().getBytes()); + } } - fileOut.close(); File oldMap = File.createTempFile("deobf_bak_", ".txt"); Files.copy(deobfMapPath, oldMap.toPath(), StandardCopyOption.REPLACE_EXISTING); LOG.trace("Copying " + tmpFile.toPath() + " to " + deobfMapPath); Files.copy(tmpFile.toPath(), deobfMapPath, StandardCopyOption.REPLACE_EXISTING); Files.delete(oldMap.toPath()); Files.delete(tmpFile.toPath()); - return true; } @NotNull diff --git a/jadx-plugins/jadx-dex-input/build.gradle b/jadx-plugins/jadx-dex-input/build.gradle index f5d10c75f..b8db80a53 100644 --- a/jadx-plugins/jadx-dex-input/build.gradle +++ b/jadx-plugins/jadx-dex-input/build.gradle @@ -5,5 +5,9 @@ plugins { dependencies { api(project(":jadx-plugins:jadx-plugins-api")) - testImplementation('org.smali:smali:2.4.0') + // TODO: finish own smali printer + implementation 'org.smali:baksmali:2.4.0' + implementation 'com.google.guava:guava:29.0-jre' // force latest version for smali + + testImplementation 'org.smali:smali:2.4.0' // compile smali files in tests } 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 56fc240a3..45215c239 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 @@ -1,24 +1,24 @@ package jadx.plugins.input.dex; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.zip.ZipFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.io.ByteStreams; + +import jadx.api.plugins.utils.ZipSecurity; import jadx.plugins.input.dex.sections.DexConsts; public class DexFileLoader { @@ -26,43 +26,57 @@ public class DexFileLoader { public static List collectDexFiles(List pathsList) { return pathsList.stream() - .map(path -> loadDexFromPath(path, 0)) + .map(Path::toFile) + .map(DexFileLoader::loadDexFromFile) .filter(list -> !list.isEmpty()) .flatMap(Collection::stream) .peek(dr -> LOG.debug("Loading dex: {}", dr)) .collect(Collectors.toList()); } - private static List loadDexFromPath(Path path, int depth) { - try (InputStream inputStream = Files.newInputStream(path, StandardOpenOption.READ)) { + private static List loadDexFromFile(File file) { + try (InputStream inputStream = new FileInputStream(file)) { + return checkFileMagic(file, inputStream, file.getAbsolutePath()); + } catch (Exception e) { + LOG.error("File open error: {}", file.getAbsolutePath(), e); + return Collections.emptyList(); + } + } + + private static List checkFileMagic(File file, InputStream inputStream, String inputFileName) throws IOException { + try (InputStream in = inputStream.markSupported() ? inputStream : new BufferedInputStream(inputStream)) { byte[] magic = new byte[DexConsts.MAX_MAGIC_SIZE]; - if (inputStream.read(magic) != magic.length) { + in.mark(magic.length); + if (in.read(magic) != magic.length) { return Collections.emptyList(); } if (isStartWithBytes(magic, DexConsts.DEX_FILE_MAGIC)) { - return Collections.singletonList(new DexReader(path)); + in.reset(); + DexReader dexReader = new DexReader(inputFileName, readAllBytes(in)); + return Collections.singletonList(dexReader); } - if (depth == 0 && isStartWithBytes(magic, DexConsts.ZIP_FILE_MAGIC)) { - return collectDexFromZip(path, depth); + if (file != null && isStartWithBytes(magic, DexConsts.ZIP_FILE_MAGIC)) { + return collectDexFromZip(file); } - } catch (Exception e) { - LOG.error("File open error: {}", path, e); + return Collections.emptyList(); } - return Collections.emptyList(); } - private static List collectDexFromZip(Path path, int depth) throws IOException { + private static List collectDexFromZip(File file) { List result = new ArrayList<>(); - FileSystem zip = FileSystems.newFileSystem(path, (ClassLoader) null); - for (Path rootDir : zip.getRootDirectories()) { - Files.walkFileTree(rootDir, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - // TODO: add zip security checks - result.addAll(loadDexFromPath(file, depth + 1)); - return FileVisitResult.CONTINUE; - } - }); + try (ZipFile zip = new ZipFile(file)) { + zip.stream() + .filter(entry -> !entry.isDirectory()) + .filter(ZipSecurity::isValidZipEntry) + .forEach(entry -> { + try (InputStream in = zip.getInputStream(entry)) { + result.addAll(checkFileMagic(null, in, entry.getName())); + } catch (Exception e) { + LOG.error("Failed to read zip entry: {}", entry, e); + } + }); + } catch (Exception e) { + LOG.warn("Failed to open zip file: {}", file.getAbsolutePath()); } return result; } @@ -79,4 +93,8 @@ public class DexFileLoader { } return true; } + + private static byte[] readAllBytes(InputStream in) throws IOException { + return ByteStreams.toByteArray(in); + } } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java index f0378a306..39e60aae9 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java @@ -16,11 +16,7 @@ public class DexLoadResult implements ILoadResult { @Nullable private final Closeable closeable; - public DexLoadResult(List dexReaders) { - this(dexReaders, null); - } - - public DexLoadResult(List dexReaders, Closeable closeable) { + public DexLoadResult(List dexReaders, @Nullable Closeable closeable) { this.dexReaders = dexReaders; this.closeable = closeable; } @@ -38,9 +34,7 @@ public class DexLoadResult implements ILoadResult { @Override public void close() throws IOException { - for (DexReader dexReader : dexReaders) { - dexReader.close(); - } + dexReaders.clear(); if (closeable != null) { closeable.close(); } 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 14105047a..89bb86db0 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 @@ -1,11 +1,6 @@ package jadx.plugins.input.dex; -import java.io.Closeable; -import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.function.Consumer; import jadx.api.plugins.input.data.IClassData; @@ -14,22 +9,18 @@ import jadx.plugins.input.dex.sections.DexHeader; import jadx.plugins.input.dex.sections.SectionReader; import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; -public class DexReader implements Closeable { +public class DexReader { - private final Path path; + private final String inputFileName; private final ByteBuffer buf; private final DexHeader header; - public DexReader(Path path) throws IOException { - this.path = path; - this.buf = ByteBuffer.wrap(Files.readAllBytes(path)); + public DexReader(String inputFileName, byte[] content) { + this.inputFileName = inputFileName; + this.buf = ByteBuffer.wrap(content); this.header = new DexHeader(new SectionReader(this, 0)); } - public String getDexVersion() { - return this.header.getVersion(); - } - public void visitClasses(Consumer consumer) { int count = header.getClassDefsSize(); if (count == 0) { @@ -53,26 +44,12 @@ public class DexReader implements Closeable { return header; } - public Path getPath() { - return path; - } - - public String getFullPath() { - StringBuilder sb = new StringBuilder(); - FileSystem fileSystem = path.getFileSystem(); - if (fileSystem.getClass().getName().contains("Zip")) { - sb.append(fileSystem.toString()).append(':'); - } - sb.append(path.toAbsolutePath()); - return sb.toString(); - } - - @Override - public void close() throws IOException { + public String getInputFileName() { + return inputFileName; } @Override public String toString() { - return getFullPath(); + return inputFileName; } } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java index 59c62c526..cbca1fb80 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java @@ -1,6 +1,5 @@ package jadx.plugins.input.dex.sections; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -15,6 +14,7 @@ import jadx.api.plugins.input.data.IMethodData; import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; +import jadx.plugins.input.dex.utils.SmaliUtils; public class DexClassData implements IClassData { public static final int SIZE = 8 * 4; @@ -71,8 +71,8 @@ public class DexClassData implements IClassData { } @Override - public Path getInputPath() { - return in.getDexReader().getPath(); + public String getInputFileName() { + return in.getDexReader().getInputFileName(); } public int getAnnotationsOff() { @@ -186,11 +186,16 @@ public class DexClassData implements IClassData { return annotationsParser.readClassAnnotations(); } - @Override public int getClassDefOffset() { return in.pos(0).getAbsPos(); } + @Override + public String getDisassembledCode() { + byte[] dexBuf = in.getDexReader().getBuf().array(); + return SmaliUtils.getSmaliCode(dexBuf, getClassDefOffset()); + } + @Override public String toString() { return getType(); diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/SmaliUtils.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/SmaliUtils.java new file mode 100644 index 000000000..8ee09599b --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/SmaliUtils.java @@ -0,0 +1,33 @@ +package jadx.plugins.input.dex.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.jf.baksmali.Adaptors.ClassDefinition; +import org.jf.baksmali.BaksmaliOptions; +import org.jf.dexlib2.dexbacked.DexBackedClassDef; +import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.util.IndentingWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SmaliUtils { + private static final Logger LOG = LoggerFactory.getLogger(SmaliUtils.class); + + public static String getSmaliCode(byte[] dexBuf, int clsDefOffset) { + StringWriter stringWriter = new StringWriter(); + try { + DexBackedDexFile dexFile = new DexBackedDexFile(null, dexBuf); + DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset, 0); + ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), dexBackedClassDef); + classDefinition.writeTo(new IndentingWriter(stringWriter)); + } catch (Exception e) { + LOG.error("Error generating smali", e); + stringWriter.append("Error generating smali code: "); + stringWriter.append(e.getMessage()); + stringWriter.append(System.lineSeparator()); + e.printStackTrace(new PrintWriter(stringWriter, true)); + } + return stringWriter.toString(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java index 5896d910e..3ce64ba73 100644 --- a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java +++ b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java @@ -13,7 +13,7 @@ import jadx.api.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlagsScope; import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.ILoadResult; -import jadx.plugins.input.dex.utils.SmaliUtils; +import jadx.plugins.input.dex.utils.SmaliTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +31,7 @@ class DexInputPluginTest { @Test public void loadTestSmali() throws Exception { - processFile(SmaliUtils.compileSmaliFromResource("samples/test.smali")); + processFile(SmaliTestUtils.compileSmaliFromResource("samples/test.smali")); } private static void processFile(Path sample) throws IOException { @@ -65,6 +65,9 @@ class DexInputPluginTest { System.out.println(mth.disassembleMethod()); System.out.println("---"); }); + System.out.println("----"); + System.out.println(cls.getDisassembledCode()); + System.out.println("----"); }); assertThat(count.get()).isGreaterThan(0); } diff --git a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliTestUtils.java similarity index 97% rename from jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java rename to jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliTestUtils.java index cc784d89d..3893532c8 100644 --- a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java +++ b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliTestUtils.java @@ -10,7 +10,7 @@ import java.util.stream.Collectors; import org.jf.smali.Smali; import org.jf.smali.SmaliOptions; -public class SmaliUtils { +public class SmaliTestUtils { public static Path compileSmaliFromResource(String res) { try { diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java index 9fec50b1e..863e18843 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java @@ -1,6 +1,5 @@ package jadx.api.plugins.input.data; -import java.nio.file.Path; import java.util.List; import java.util.function.Consumer; @@ -23,7 +22,7 @@ public interface IClassData { String getSourceFile(); - Path getInputPath(); + String getInputFileName(); void visitFieldsAndMethods(Consumer fieldsConsumer, Consumer mthConsumer); @@ -31,6 +30,5 @@ public interface IClassData { List getAnnotations(); - // TODO: make api methods to get dissembled code - int getClassDefOffset(); + String getDisassembledCode(); } diff --git a/jadx-core/src/main/java/jadx/core/utils/files/ZipSecurity.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/ZipSecurity.java similarity index 98% rename from jadx-core/src/main/java/jadx/core/utils/files/ZipSecurity.java rename to jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/ZipSecurity.java index 39bf20a57..965502ab2 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/ZipSecurity.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/ZipSecurity.java @@ -1,4 +1,4 @@ -package jadx.core.utils.files; +package jadx.api.plugins.utils; import java.io.File; import java.io.IOException;