From cd153c76f2c7e25d4eab1e5310b7c4a238e96325 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 12 Oct 2021 16:55:22 +0300 Subject: [PATCH] feat: add raung input plugin, use raung in tests --- jadx-core/build.gradle | 4 +- .../test/java/jadx/tests/api/RaungTest.java | 85 +++++++++++++++++++ .../integration/others/TestJavaSwap.java | 66 +------------- .../src/test/raung/others/TestJavaSwap.raung | 29 +++++++ .../plugins/input/java/JavaInputPlugin.java | 9 +- .../plugins/input/java/JavaLoadResult.java | 13 ++- jadx-plugins/jadx-raung-input/build.gradle | 11 +++ .../plugins/input/raung/RaungConvert.java | 67 +++++++++++++++ .../plugins/input/raung/RaungInputPlugin.java | 30 +++++++ .../services/jadx.api.plugins.JadxPlugin | 1 + settings.gradle | 1 + 11 files changed, 248 insertions(+), 68 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/api/RaungTest.java create mode 100644 jadx-core/src/test/raung/others/TestJavaSwap.raung create mode 100644 jadx-plugins/jadx-raung-input/build.gradle create mode 100644 jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungConvert.java create mode 100644 jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungInputPlugin.java create mode 100644 jadx-plugins/jadx-raung-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index cf7ab5fb..564b285a 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -14,13 +14,11 @@ dependencies { testImplementation 'org.apache.commons:commons-lang3:3.12.0' - testImplementation 'org.ow2.asm:asm:9.2' - testImplementation 'org.ow2.asm:asm-util:9.2' - testRuntimeOnly(project(':jadx-plugins:jadx-dex-input')) testRuntimeOnly(project(':jadx-plugins:jadx-smali-input')) testRuntimeOnly(project(':jadx-plugins:jadx-java-convert')) testRuntimeOnly(project(':jadx-plugins:jadx-java-input')) + testRuntimeOnly(project(':jadx-plugins:jadx-raung-input')) } test { diff --git a/jadx-core/src/test/java/jadx/tests/api/RaungTest.java b/jadx-core/src/test/java/jadx/tests/api/RaungTest.java new file mode 100644 index 00000000..91b79b98 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/RaungTest.java @@ -0,0 +1,85 @@ +package jadx.tests.api; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; + +import jadx.api.JadxInternalAccess; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.RootNode; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +public abstract class RaungTest extends IntegrationTest { + + private static final String RAUNG_TESTS_PROJECT = "jadx-core"; + private static final String RAUNG_TESTS_DIR = "src/test/raung"; + private static final String RAUNG_TESTS_EXT = ".raung"; + + @BeforeEach + public void init() { + super.init(); + this.useJavaInput(); + } + + /** + * Preferred method for one file raung test + */ + protected ClassNode getClassNodeFromRaung() { + String pkg = getTestPkg(); + String clsName = getTestName(); + return getClassNodeFromRaung(pkg + File.separatorChar + clsName, pkg + '.' + clsName); + } + + protected ClassNode getClassNodeFromRaung(String file, String clsName) { + File raungFile = getRaungFile(file); + return getClassNodeFromFiles(Collections.singletonList(raungFile), clsName); + } + + protected List loadFromRaungFiles() { + jadxDecompiler = loadFiles(collectRaungFiles(getTestPkg(), getTestName())); + RootNode root = JadxInternalAccess.getRoot(jadxDecompiler); + List classes = root.getClasses(false); + decompileAndCheck(classes); + return classes; + } + + private List collectRaungFiles(String pkg, String testDir) { + String raungFilesDir = pkg + File.separatorChar + testDir + File.separatorChar; + File raungDir = getRaungDir(raungFilesDir); + String[] raungFileNames = raungDir.list((dir, name) -> name.endsWith(".raung")); + assertThat("Raung files not found in " + raungDir, raungFileNames, notNullValue()); + return Stream.of(raungFileNames) + .map(file -> new File(raungDir, file)) + .collect(Collectors.toList()); + } + + private static File getRaungFile(String baseName) { + File raungFile = new File(RAUNG_TESTS_DIR, baseName + RAUNG_TESTS_EXT); + if (raungFile.exists()) { + return raungFile; + } + File pathFromRoot = new File(RAUNG_TESTS_PROJECT, raungFile.getPath()); + if (pathFromRoot.exists()) { + return pathFromRoot; + } + throw new AssertionError("Raung file not found: " + raungFile.getPath()); + } + + private static File getRaungDir(String baseName) { + File raungDir = new File(RAUNG_TESTS_DIR, baseName); + if (raungDir.exists()) { + return raungDir; + } + File pathFromRoot = new File(RAUNG_TESTS_PROJECT, raungDir.getPath()); + if (pathFromRoot.exists()) { + return pathFromRoot; + } + throw new AssertionError("Raung dir not found: " + raungDir.getPath()); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestJavaSwap.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestJavaSwap.java index de439278..bdd670d0 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestJavaSwap.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestJavaSwap.java @@ -1,27 +1,12 @@ package jadx.tests.integration.others; -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; - import org.junit.jupiter.api.Test; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.util.CheckClassAdapter; -import jadx.core.utils.files.FileUtils; -import jadx.tests.api.IntegrationTest; +import jadx.tests.api.RaungTest; import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; -public class TestJavaSwap extends IntegrationTest { +public class TestJavaSwap extends RaungTest { @SuppressWarnings("StringBufferReplaceableByString") public static class TestCls { @@ -44,52 +29,9 @@ public class TestJavaSwap extends IntegrationTest { } @Test - public void test() throws IOException { - // TODO: find up-to-date assembler/disassembler in java - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - cw.visit(Opcodes.V1_8, 0, "TestCls", null, "java/lang/Object", new String[] {}); - cw.visitField(Opcodes.ACC_PRIVATE, "field", "Ljava/lang/Iterable;", null, null).visitEnd(); - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, new String[] {}); - mv.visitCode(); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitFieldInsn(Opcodes.GETFIELD, "TestCls", "field", "Ljava/lang/Iterable;"); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false); - mv.visitVarInsn(Opcodes.ASTORE, 1); - mv.visitIntInsn(Opcodes.BIPUSH, 8); - mv.visitVarInsn(Opcodes.ALOAD, 1); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); - mv.visitInsn(Opcodes.IADD); - mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); - mv.visitInsn(Opcodes.DUP_X1); - mv.visitInsn(Opcodes.SWAP); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(I)V", false); - mv.visitLdcInsn("concat("); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", - "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); - mv.visitVarInsn(Opcodes.ALOAD, 1); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", - "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); - mv.visitLdcInsn(")"); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", - "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); - mv.visitInsn(Opcodes.ARETURN); - mv.visitMaxs(0, 0); // auto calculated - mv.visitEnd(); - cw.visitEnd(); - byte[] clsBytes = cw.toByteArray(); - - StringWriter results = new StringWriter(); - CheckClassAdapter.verify(new ClassReader(clsBytes), false, new PrintWriter(results)); - assertThat(results.toString()).isEmpty(); - - Path clsFile = FileUtils.createTempFile(".class"); - Files.write(clsFile, clsBytes); - List files = Collections.singletonList(clsFile.toFile()); - + public void test() { useJavaInput(); - assertThat(getClassNodeFromFiles(files, "TestCls")) + assertThat(getClassNodeFromRaung()) .code(); } } diff --git a/jadx-core/src/test/raung/others/TestJavaSwap.raung b/jadx-core/src/test/raung/others/TestJavaSwap.raung new file mode 100644 index 00000000..66846cdb --- /dev/null +++ b/jadx-core/src/test/raung/others/TestJavaSwap.raung @@ -0,0 +1,29 @@ +.version 52 +.class others/TestJavaSwap +.auto frames + +.field private field Ljava/lang/Iterable; + +.method public toString()Ljava/lang/String; + aload 0 + getfield others/TestJavaSwap field Ljava/lang/Iterable; + invokestatic java/lang/String valueOf (Ljava/lang/Object;)Ljava/lang/String; + astore 1 + bipush 8 + aload 1 + invokestatic java/lang/String valueOf (Ljava/lang/Object;)Ljava/lang/String; + invokevirtual java/lang/String length ()I + iadd + new java/lang/StringBuilder + dup_x1 + swap + invokespecial java/lang/StringBuilder (I)V + ldc "concat(" + invokevirtual java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; + aload 1 + invokevirtual java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; + ldc ")" + invokevirtual java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; + invokevirtual java/lang/StringBuilder toString ()Ljava/lang/String; + areturn +.end method diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java index ac7a6ce9..7cc44297 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java @@ -1,8 +1,11 @@ package jadx.plugins.input.java; +import java.io.Closeable; import java.nio.file.Path; import java.util.List; +import org.jetbrains.annotations.Nullable; + import jadx.api.plugins.JadxPluginInfo; import jadx.api.plugins.input.JadxInputPlugin; import jadx.api.plugins.input.data.ILoadResult; @@ -22,10 +25,14 @@ public class JavaInputPlugin implements JadxInputPlugin { @Override public ILoadResult loadFiles(List inputFiles) { + return loadClassFiles(inputFiles, null); + } + + public static ILoadResult loadClassFiles(List inputFiles, @Nullable Closeable closeable) { List readers = new JavaFileLoader().collectFiles(inputFiles); if (readers.isEmpty()) { return EmptyLoadResult.INSTANCE; } - return new JavaLoadResult(readers); + return new JavaLoadResult(readers, closeable); } } diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java index 95800055..e14cd705 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java @@ -1,8 +1,11 @@ package jadx.plugins.input.java; +import java.io.Closeable; +import java.io.IOException; import java.util.List; import java.util.function.Consumer; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,9 +17,12 @@ public class JavaLoadResult implements ILoadResult { private static final Logger LOG = LoggerFactory.getLogger(JavaLoadResult.class); private final List readers; + @Nullable + private final Closeable closeable; - public JavaLoadResult(List readers) { + public JavaLoadResult(List readers, @Nullable Closeable closeable) { this.readers = readers; + this.closeable = closeable; } @Override @@ -40,7 +46,10 @@ public class JavaLoadResult implements ILoadResult { } @Override - public void close() { + public void close() throws IOException { readers.clear(); + if (closeable != null) { + closeable.close(); + } } } diff --git a/jadx-plugins/jadx-raung-input/build.gradle b/jadx-plugins/jadx-raung-input/build.gradle new file mode 100644 index 00000000..2d585c9f --- /dev/null +++ b/jadx-plugins/jadx-raung-input/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java-library' +} + +dependencies { + api(project(":jadx-plugins:jadx-plugins-api")) + + implementation(project(":jadx-plugins:jadx-java-input")) + + implementation('io.github.skylot:raung-asm:0.0.1') +} diff --git a/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungConvert.java b/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungConvert.java new file mode 100644 index 00000000..51ae4522 --- /dev/null +++ b/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungConvert.java @@ -0,0 +1,67 @@ +package jadx.plugins.input.raung; + +import java.io.Closeable; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.github.skylot.raung.asm.RaungAsm; + +public class RaungConvert implements Closeable { + private static final Logger LOG = LoggerFactory.getLogger(RaungConvert.class); + + @Nullable + private Path tmpJar; + + public boolean execute(List input) { + List raungInputs = filterRaungFiles(input); + if (raungInputs.isEmpty()) { + return false; + } + try { + this.tmpJar = Files.createTempFile("jadx-raung-", ".jar"); + RaungAsm.create() + .output(tmpJar) + .inputs(input) + .execute(); + return true; + } catch (Exception e) { + LOG.error("Raung process error", e); + } + close(); + return false; + } + + private List filterRaungFiles(List input) { + PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**.raung"); + return input.stream() + .filter(matcher::matches) + .collect(Collectors.toList()); + } + + public List getFiles() { + if (tmpJar == null) { + return Collections.emptyList(); + } + return Collections.singletonList(tmpJar); + } + + @Override + public void close() { + try { + if (tmpJar != null) { + Files.deleteIfExists(tmpJar); + } + } catch (Exception e) { + LOG.error("Failed to remove tmp jar file: {}", tmpJar, e); + } + } +} diff --git a/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungInputPlugin.java b/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungInputPlugin.java new file mode 100644 index 00000000..c28d5ba9 --- /dev/null +++ b/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungInputPlugin.java @@ -0,0 +1,30 @@ +package jadx.plugins.input.raung; + +import java.nio.file.Path; +import java.util.List; + +import jadx.api.plugins.JadxPluginInfo; +import jadx.api.plugins.input.JadxInputPlugin; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.impl.EmptyLoadResult; +import jadx.plugins.input.java.JavaInputPlugin; + +public class RaungInputPlugin implements JadxInputPlugin { + + @Override + public JadxPluginInfo getPluginInfo() { + return new JadxPluginInfo( + "raung-input", + "RaungInput", + "Load .raung files"); + } + + @Override + public ILoadResult loadFiles(List input) { + RaungConvert convert = new RaungConvert(); + if (!convert.execute(input)) { + return EmptyLoadResult.INSTANCE; + } + return JavaInputPlugin.loadClassFiles(convert.getFiles(), convert); + } +} diff --git a/jadx-plugins/jadx-raung-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-raung-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 00000000..b7303b93 --- /dev/null +++ b/jadx-plugins/jadx-raung-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +jadx.plugins.input.raung.RaungInputPlugin diff --git a/settings.gradle b/settings.gradle index c9ae25cd..1187a16e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,5 +7,6 @@ include 'jadx-plugins' include 'jadx-plugins:jadx-plugins-api' include 'jadx-plugins:jadx-dex-input' include 'jadx-plugins:jadx-java-input' +include 'jadx-plugins:jadx-raung-input' include 'jadx-plugins:jadx-smali-input' include 'jadx-plugins:jadx-java-convert' -- GitLab