diff --git a/jadx-cli/src/test/java/jadx/cli/TestInput.java b/jadx-cli/src/test/java/jadx/cli/TestInput.java index 3289dbd1b..de0a439b1 100644 --- a/jadx-cli/src/test/java/jadx/cli/TestInput.java +++ b/jadx-cli/src/test/java/jadx/cli/TestInput.java @@ -33,6 +33,11 @@ public class TestInput { decompile("smali", "samples/HelloWorld.smali"); } + @Test + public void testClassInput() throws Exception { + decompile("class", "samples/HelloWorld.class"); + } + private void decompile(String tmpDirName, String inputSample) throws URISyntaxException, IOException { StringBuilder args = new StringBuilder(); Path tempDir = FileUtils.createTempDir(tmpDirName); diff --git a/jadx-cli/src/test/resources/samples/HelloWorld.class b/jadx-cli/src/test/resources/samples/HelloWorld.class new file mode 100644 index 000000000..30cee9d25 Binary files /dev/null and b/jadx-cli/src/test/resources/samples/HelloWorld.class differ diff --git a/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java b/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java index a1dcdff43..be6ae3dee 100644 --- a/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java +++ b/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java @@ -26,4 +26,9 @@ public class InMemoryCodeCache implements ICodeCache { public @Nullable ICodeInfo get(String clsFullName) { return storage.get(clsFullName); } + + @Override + public String toString() { + return "InMemoryCodeCache"; + } } diff --git a/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java b/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java index 4e3d4811a..f52601f46 100644 --- a/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java +++ b/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java @@ -21,4 +21,9 @@ public class NoOpCodeCache implements ICodeCache { public @Nullable ICodeInfo get(String clsFullName) { return null; } + + @Override + public String toString() { + return "NoOpCodeCache"; + } } diff --git a/jadx-plugins/jadx-java-convert/build.gradle b/jadx-plugins/jadx-java-convert/build.gradle index a5753b0b9..06c50318a 100644 --- a/jadx-plugins/jadx-java-convert/build.gradle +++ b/jadx-plugins/jadx-java-convert/build.gradle @@ -7,4 +7,6 @@ dependencies { implementation(project(":jadx-plugins:jadx-dex-input")) implementation(files('lib/dx-1.16.jar')) + + implementation 'org.ow2.asm:asm:8.0.1' } diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java index e2e29b038..76038191a 100644 --- a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java @@ -1,40 +1,86 @@ package jadx.plugins.input.javaconvert; +import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; +import java.nio.file.PathMatcher; import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.objectweb.asm.ClassReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.utils.ZipSecurity; + public class JavaConvertLoader { private static final Logger LOG = LoggerFactory.getLogger(JavaConvertLoader.class); public static ConvertResult process(List input) { ConvertResult result = new ConvertResult(); - for (Path path : input) { - if (isJavaFile(path)) { - try { - convert(result, path); - } catch (Exception e) { - LOG.error("Failed to convert file: " + path.toAbsolutePath(), e); - } - } - } + processJars(input, result); + processClassFiles(input, result); return result; } - private static boolean isJavaFile(Path path) { - String fileName = path.getFileName().toString(); - return fileName.endsWith(".jar") - || fileName.endsWith(".class"); + private static void processJars(List input, ConvertResult result) { + PathMatcher jarMatcher = FileSystems.getDefault().getPathMatcher("glob:**.jar"); + input.stream() + .filter(path -> Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) + .filter(jarMatcher::matches) + .forEach(path -> { + try { + convertJar(result, path); + } catch (Exception e) { + LOG.error("Failed to convert file: " + path.toAbsolutePath(), e); + } + }); } - private static void convert(ConvertResult result, Path path) throws Exception { + private static void processClassFiles(List input, ConvertResult result) { + PathMatcher jarMatcher = FileSystems.getDefault().getPathMatcher("glob:**.class"); + List clsFiles = input.stream() + .filter(path -> Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) + .filter(jarMatcher::matches) + .collect(Collectors.toList()); + if (clsFiles.isEmpty()) { + return; + } + try { + Path jarFile = Files.createTempFile("jadx-", ".jar"); + try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(jarFile))) { + for (Path file : clsFiles) { + String clsName = getNameFromClassFile(file); + if (clsName == null || !ZipSecurity.isValidZipEntryName(clsName)) { + throw new IOException("Can't read class name from file: " + file); + } + addFileToJar(jo, file, clsName + ".class"); + } + } + result.addTempPath(jarFile); + LOG.debug("Packed class files {} into jar {}", clsFiles, jarFile); + convertJar(result, jarFile); + } catch (Exception e) { + LOG.error("Error process class files", e); + } + } + + public static String getNameFromClassFile(Path file) throws IOException { + try (InputStream in = Files.newInputStream(file)) { + ClassReader classReader = new ClassReader(in); + return classReader.getClassName(); + } + } + + private static void convertJar(ConvertResult result, Path path) throws Exception { Path tempDirectory = Files.createTempDirectory("jadx-"); result.addTempPath(tempDirectory); @@ -45,10 +91,34 @@ public class JavaConvertLoader { } private static List collectFilesInDir(Path tempDirectory) throws IOException { + PathMatcher dexMatcher = FileSystems.getDefault().getPathMatcher("glob:**.dex"); try (Stream pathStream = Files.walk(tempDirectory, 1)) { return pathStream .filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS)) + .filter(dexMatcher::matches) .collect(Collectors.toList()); } } + + public static void addFileToJar(JarOutputStream jar, Path source, String entryName) throws IOException { + try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(source))) { + JarEntry entry = new JarEntry(entryName); + entry.setTime(Files.getLastModifiedTime(source, LinkOption.NOFOLLOW_LINKS).toMillis()); + jar.putNextEntry(entry); + + copyStream(in, jar); + jar.closeEntry(); + } + } + + public static void copyStream(InputStream input, OutputStream output) throws IOException { + byte[] buffer = new byte[8 * 1024]; + while (true) { + int count = input.read(buffer); + if (count == -1) { + break; + } + output.write(buffer, 0, count); + } + } }