From f8d57d9265545b809328ec0c35cb7e7245ca1e97 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 15 Jul 2014 23:45:25 +0400 Subject: [PATCH] core: decompile '.class' files --- .../src/main/java/jadx/cli/JadxCLIArgs.java | 2 +- jadx-core/build.gradle | 1 + .../main/java/jadx/core/utils/AsmUtils.java | 20 ++++++ .../java/jadx/core/utils/files/FileUtils.java | 38 +++++++++++ .../java/jadx/core/utils/files/InputFile.java | 67 ++++++++++++------- .../test/java/jadx/api/InternalJadxTest.java | 29 +------- 6 files changed, 106 insertions(+), 51 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/utils/AsmUtils.java create mode 100644 jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index d8721b7e5..2a378296b 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -20,7 +20,7 @@ import com.beust.jcommander.ParameterException; public final class JadxCLIArgs implements IJadxArgs { - @Parameter(description = " (.dex, .apk or .jar)") + @Parameter(description = " (.dex, .apk, .jar or .class)") protected List files; @Parameter(names = {"-d", "--output-dir"}, description = "output directory") diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index 5358731e9..98850351b 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -2,6 +2,7 @@ ext.jadxClasspath = 'clsp-data/android-4.3.jar' dependencies { compile files('lib/dx-1.8.jar') + compile 'org.ow2.asm:asm:5.0.3' runtime files(jadxClasspath) } diff --git a/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java b/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java new file mode 100644 index 000000000..840b21d5d --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java @@ -0,0 +1,20 @@ +package jadx.core.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.objectweb.asm.ClassReader; + +public class AsmUtils { + + private AsmUtils() { + } + + public static String getNameFromClassFile(File file) throws IOException { + FileInputStream in = new FileInputStream(file); + ClassReader classReader = new ClassReader(in); + return classReader.getClassName(); + } + +} 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 new file mode 100644 index 000000000..5e607d459 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java @@ -0,0 +1,38 @@ +package jadx.core.utils.files; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +public class FileUtils { + + private FileUtils() { + } + + public static void addFileToJar(JarOutputStream jar, File source, String entryName) throws IOException { + BufferedInputStream in = null; + try { + JarEntry entry = new JarEntry(entryName); + entry.setTime(source.lastModified()); + jar.putNextEntry(entry); + in = new BufferedInputStream(new FileInputStream(source)); + + byte[] buffer = new byte[8192]; + while (true) { + int count = in.read(buffer); + if (count == -1) { + break; + } + jar.write(buffer, 0, count); + } + jar.closeEntry(); + } finally { + if (in != null) { + in.close(); + } + } + } +} 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 8cb22ad19..22e5d719b 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 @@ -1,13 +1,15 @@ 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.jar.JarOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -35,37 +37,44 @@ public class InputFile { if (fileName.endsWith(".dex")) { return new Dex(file); } + if (fileName.endsWith(".class")) { + return loadFromClassFile(file); + } if (fileName.endsWith(".apk")) { - byte[] data = openDexFromZip(file); - if (data == null) { - throw new JadxRuntimeException("File 'classes.dex' not found in file: " + file); + Dex dex = loadFromZip(file); + if (dex == null) { + throw new IOException("File 'classes.dex' not found in file: " + file); } - return new Dex(data); + return dex; } if (fileName.endsWith(".jar")) { // check if jar contains 'classes.dex' - byte[] data = openDexFromZip(file); - if (data != null) { - return new Dex(data); - } - try { - LOG.info("converting to dex: {} ...", fileName); - JavaToDex j2d = new JavaToDex(); - byte[] ba = j2d.convert(file.getAbsolutePath()); - if (ba.length == 0) { - throw new JadxException(j2d.isError() ? j2d.getDxErrors() : "Empty dx output"); - } else if (j2d.isError()) { - LOG.warn("dx message: " + j2d.getDxErrors()); - } - return new Dex(ba); - } catch (Throwable e) { - throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e); + Dex dex = loadFromZip(file); + if (dex != null) { + return dex; } + return loadFromJar(file); } throw new DecodeException("Unsupported input file format: " + file); } - private byte[] openDexFromZip(File file) throws IOException { + private static Dex loadFromJar(File jarFile) throws DecodeException { + try { + LOG.info("converting to dex: {} ...", jarFile.getName()); + JavaToDex j2d = new JavaToDex(); + byte[] ba = j2d.convert(jarFile.getAbsolutePath()); + if (ba.length == 0) { + throw new JadxException(j2d.isError() ? j2d.getDxErrors() : "Empty dx output"); + } else if (j2d.isError()) { + LOG.warn("dx message: " + j2d.getDxErrors()); + } + return new Dex(ba); + } catch (Throwable e) { + throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e); + } + } + + private static Dex loadFromZip(File file) throws IOException { ZipFile zf = new ZipFile(file); ZipEntry dex = zf.getEntry("classes.dex"); if (dex == null) { @@ -87,7 +96,19 @@ public class InputFile { } zf.close(); } - return bytesOut.toByteArray(); + 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(); + FileOutputStream out = new FileOutputStream(outFile); + JarOutputStream jo = new JarOutputStream(out); + String clsName = AsmUtils.getNameFromClassFile(file); + FileUtils.addFileToJar(jo, file, clsName + ".class"); + jo.close(); + out.close(); + return loadFromJar(outFile); } public File getFile() { diff --git a/jadx-core/src/test/java/jadx/api/InternalJadxTest.java b/jadx-core/src/test/java/jadx/api/InternalJadxTest.java index 4a01d40aa..98373cfbd 100644 --- a/jadx-core/src/test/java/jadx/api/InternalJadxTest.java +++ b/jadx-core/src/test/java/jadx/api/InternalJadxTest.java @@ -7,17 +7,15 @@ import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; +import jadx.core.utils.files.FileUtils; -import java.io.BufferedInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; -import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import static org.hamcrest.CoreMatchers.containsString; @@ -94,7 +92,7 @@ public abstract class InternalJadxTest extends TestUtils { File temp = File.createTempFile("jadx-tmp-", System.nanoTime() + ".jar"); JarOutputStream jo = new JarOutputStream(new FileOutputStream(temp)); for (File file : list) { - add(file, path + "/" + file.getName(), jo); + FileUtils.addFileToJar(jo, file, path + "/" + file.getName()); } jo.close(); if (deleteTmpJar) { @@ -127,29 +125,6 @@ public abstract class InternalJadxTest extends TestUtils { return list; } - private void add(File source, String entryName, JarOutputStream target) throws IOException { - BufferedInputStream in = null; - try { - JarEntry entry = new JarEntry(entryName); - entry.setTime(source.lastModified()); - target.putNextEntry(entry); - in = new BufferedInputStream(new FileInputStream(source)); - - byte[] buffer = new byte[1024]; - while (true) { - int count = in.read(buffer); - if (count == -1) { - break; - } - target.write(buffer, 0, count); - } - target.closeEntry(); - } finally { - if (in != null) { - in.close(); - } - } - } // Use only for debug purpose @Deprecated