mirror of
https://github.com/skylot/jadx.git
synced 2025-02-23 14:31:19 +00:00
fix: bring back smali files support (#961)
This commit is contained in:
parent
bfd60b733a
commit
558a86739f
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,6 +26,7 @@ node_modules/
|
||||
jadx-output/
|
||||
*-tmp/
|
||||
**/tmp/
|
||||
*.jobf
|
||||
|
||||
*.class
|
||||
*.dump
|
||||
|
@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'org.sonarqube' version '3.0'
|
||||
id 'com.github.ben-manes.versions' version '0.28.0'
|
||||
id 'com.github.ben-manes.versions' version '0.29.0'
|
||||
id "com.diffplug.gradle.spotless" version "4.5.1"
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ dependencies {
|
||||
implementation(project(':jadx-core'))
|
||||
|
||||
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
|
||||
implementation 'com.beust:jcommander:1.78'
|
||||
|
@ -15,10 +15,7 @@ public class JadxCLI {
|
||||
public static void main(String[] args) {
|
||||
int result = 0;
|
||||
try {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
result = processAndSave(jadxArgs.toJadxArgs());
|
||||
}
|
||||
result = execute(args);
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
result = 1;
|
||||
@ -31,7 +28,15 @@ public class JadxCLI {
|
||||
}
|
||||
}
|
||||
|
||||
static int processAndSave(JadxArgs jadxArgs) {
|
||||
public static int execute(String[] args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
return processAndSave(jadxArgs.toJadxArgs());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int processAndSave(JadxArgs jadxArgs) {
|
||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||
jadx.load();
|
||||
|
68
jadx-cli/src/test/java/jadx/cli/TestInput.java
Normal file
68
jadx-cli/src/test/java/jadx/cli/TestInput.java
Normal file
@ -0,0 +1,68 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
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.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestInput {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
|
||||
|
||||
@Test
|
||||
public void testDexInput() throws Exception {
|
||||
decompile("dex", "samples/hello.dex");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmaliInput() throws Exception {
|
||||
decompile("smali", "samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
private void decompile(String tmpDirName, String inputSample) throws URISyntaxException, IOException {
|
||||
StringBuilder args = new StringBuilder();
|
||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||
args.append("-v ");
|
||||
args.append("-d ").append(tempDir.toAbsolutePath()).append(' ');
|
||||
|
||||
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||
assertThat(resource).isNotNull();
|
||||
String sampleFile = resource.toURI().getRawPath();
|
||||
args.append(sampleFile);
|
||||
|
||||
int result = JadxCLI.execute(args.toString().split(" "));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
|
||||
assertThat(resultJavaFiles).isNotEmpty();
|
||||
}
|
||||
|
||||
private static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
|
||||
PathMatcher matcher = dir.getFileSystem().getPathMatcher("glob:**.java");
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
return pathStream
|
||||
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
||||
.peek(f -> LOG.debug("File in result dir: {}", f))
|
||||
.filter(matcher::matches)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
}
|
26
jadx-cli/src/test/resources/samples/HelloWorld.smali
Normal file
26
jadx-cli/src/test/resources/samples/HelloWorld.smali
Normal file
@ -0,0 +1,26 @@
|
||||
.class LHelloWorld;
|
||||
.super Ljava/lang/Object;
|
||||
.source "HelloWorld.java"
|
||||
|
||||
.method constructor <init>()V
|
||||
.registers 1
|
||||
|
||||
.line 1
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public static main([Ljava/lang/String;)V
|
||||
.registers 2
|
||||
|
||||
.line 3
|
||||
sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
|
||||
const-string v0, "Hello, World"
|
||||
|
||||
invoke-virtual {p0, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
|
||||
.line 4
|
||||
return-void
|
||||
.end method
|
BIN
jadx-cli/src/test/resources/samples/hello.dex
Normal file
BIN
jadx-cli/src/test/resources/samples/hello.dex
Normal file
Binary file not shown.
@ -19,6 +19,7 @@ dependencies {
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.10'
|
||||
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
}
|
||||
|
||||
|
@ -27,15 +27,12 @@ public class JadxArgsValidator {
|
||||
if (inputFiles.isEmpty()) {
|
||||
throw new JadxArgsValidateException("Please specify input file");
|
||||
}
|
||||
if (inputFiles.size() > 1) {
|
||||
for (File inputFile : inputFiles) {
|
||||
String fileName = inputFile.getName();
|
||||
if (fileName.startsWith("--")) {
|
||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
||||
}
|
||||
}
|
||||
throw new JadxArgsValidateException("Only one input file supported");
|
||||
}
|
||||
for (File file : inputFiles) {
|
||||
checkFile(file);
|
||||
}
|
||||
|
@ -108,7 +108,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
loadedInputs.clear();
|
||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
||||
ILoadResult loadResult = inputPlugin.loadFiles(inputPaths);
|
||||
if (loadResult != null && !loadResult.isEmpty()) {
|
||||
loadedInputs.add(loadResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ 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;
|
||||
@ -127,6 +128,7 @@ public final class ResourcesLoader {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
if (FileUtils.isZipFile(file)) {
|
||||
try (ZipFile zip = new ZipFile(file)) {
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
@ -136,7 +138,9 @@ public final class ResourcesLoader {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
||||
LOG.warn("Failed to open zip file: {}", file.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
addResourceFile(list, file);
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ public class ClsSet {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||
LOG.debug("Load class set in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,7 @@ public class RootNode {
|
||||
// sort classes by name, expect top classes before inner
|
||||
classes.sort(Comparator.comparing(ClassNode::getFullName));
|
||||
initInnerClasses();
|
||||
LOG.debug("Classes loaded: {}", classes.size());
|
||||
}
|
||||
|
||||
private void addDummyClass(IClassData classData, Exception exc) {
|
||||
|
@ -222,7 +222,7 @@ public class FileUtils {
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
private static boolean isZipFile(File file) {
|
||||
public static boolean isZipFile(File file) {
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
byte[] headers = new byte[4];
|
||||
int read = is.read(headers, 0, 4);
|
||||
|
@ -104,7 +104,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
DebugChecks.checksEnabled = true;
|
||||
}
|
||||
|
||||
private JadxDecompiler jadxDecompiler;
|
||||
protected JadxDecompiler jadxDecompiler;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
@ -142,7 +142,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
public ClassNode getClassNode(Class<?> clazz) {
|
||||
try {
|
||||
File jar = getJarForClass(clazz);
|
||||
return getClassNodeFromFile(jar, clazz.getName());
|
||||
return getClassNodeFromFiles(Collections.singletonList(jar), clazz.getName());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage());
|
||||
@ -150,8 +150,8 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ClassNode getClassNodeFromFile(File file, String clsName) {
|
||||
jadxDecompiler = loadFiles(Collections.singletonList(file));
|
||||
public ClassNode getClassNodeFromFiles(List<File> files, String clsName) {
|
||||
jadxDecompiler = loadFiles(files);
|
||||
RootNode root = JadxInternalAccess.getRoot(jadxDecompiler);
|
||||
|
||||
ClassNode cls = root.resolveClass(clsName);
|
||||
@ -173,9 +173,8 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
}
|
||||
|
||||
protected JadxDecompiler loadFiles(List<File> inputFiles) {
|
||||
JadxDecompiler d;
|
||||
args.setInputFiles(inputFiles);
|
||||
d = new JadxDecompiler(args);
|
||||
JadxDecompiler d = new JadxDecompiler(args);
|
||||
try {
|
||||
d.load();
|
||||
} catch (Exception e) {
|
||||
|
@ -7,10 +7,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jf.smali.Smali;
|
||||
import org.jf.smali.SmaliOptions;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
@ -26,9 +23,7 @@ public abstract class SmaliTest extends IntegrationTest {
|
||||
|
||||
protected ClassNode getClassNodeFromSmali(String file, String clsName) {
|
||||
File smaliFile = getSmaliFile(file);
|
||||
File outDex = createTempFile(".dex");
|
||||
compileSmali(outDex, Collections.singletonList(smaliFile));
|
||||
return getClassNodeFromFile(outDex, clsName);
|
||||
return getClassNodeFromFiles(Collections.singletonList(smaliFile), clsName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,9 +46,7 @@ public abstract class SmaliTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
protected ClassNode getClassNodeFromSmaliFiles(String pkg, String testName, String clsName) {
|
||||
File outDex = createTempFile(".dex");
|
||||
compileSmali(outDex, collectSmaliFiles(pkg, testName));
|
||||
return getClassNodeFromFile(outDex, pkg + '.' + clsName);
|
||||
return getClassNodeFromFiles(collectSmaliFiles(pkg, testName), pkg + '.' + clsName);
|
||||
}
|
||||
|
||||
protected ClassNode getClassNodeFromSmaliFiles(String clsName) {
|
||||
@ -61,13 +54,10 @@ public abstract class SmaliTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
protected List<ClassNode> loadFromSmaliFiles() {
|
||||
File outDex = createTempFile(".dex");
|
||||
compileSmali(outDex, collectSmaliFiles(getTestPkg(), getTestName()));
|
||||
|
||||
JadxDecompiler d = loadFiles(Collections.singletonList(outDex));
|
||||
RootNode root = JadxInternalAccess.getRoot(d);
|
||||
jadxDecompiler = loadFiles(collectSmaliFiles(getTestPkg(), getTestName()));
|
||||
RootNode root = JadxInternalAccess.getRoot(jadxDecompiler);
|
||||
List<ClassNode> classes = root.getClasses(false);
|
||||
decompileAndCheck(d, classes);
|
||||
decompileAndCheck(jadxDecompiler, classes);
|
||||
return classes;
|
||||
}
|
||||
|
||||
@ -97,17 +87,4 @@ public abstract class SmaliTest extends IntegrationTest {
|
||||
}
|
||||
throw new AssertionError("Smali file not found: " + smaliFile.getPath());
|
||||
}
|
||||
|
||||
private static boolean compileSmali(File output, List<File> inputFiles) {
|
||||
try {
|
||||
SmaliOptions options = new SmaliOptions();
|
||||
options.outputDexFile = output.getAbsolutePath();
|
||||
options.verboseErrors = true;
|
||||
List<String> inputFileNames = inputFiles.stream().map(File::getAbsolutePath).collect(Collectors.toList());
|
||||
Smali.assemble(options, inputFileNames);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Smali assemble error", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ public class DexFileLoader {
|
||||
|
||||
public static List<DexReader> collectDexFiles(List<Path> pathsList) {
|
||||
return pathsList.stream()
|
||||
.map((Path path) -> loadDexFromPath(path, 0))
|
||||
.map(path -> loadDexFromPath(path, 0))
|
||||
.filter(list -> !list.isEmpty())
|
||||
.flatMap(Collection::stream)
|
||||
.peek(dr -> LOG.debug("Loading dex: {}", dr))
|
||||
.collect(Collectors.toList());
|
||||
|
@ -1,11 +1,13 @@
|
||||
package jadx.plugins.input.dex;
|
||||
|
||||
import java.io.Closeable;
|
||||
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;
|
||||
|
||||
public class DexInputPlugin implements JadxInputPlugin {
|
||||
|
||||
@ -16,6 +18,14 @@ public class DexInputPlugin implements JadxInputPlugin {
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> input) {
|
||||
return new DexLoadResult(DexFileLoader.collectDexFiles(input));
|
||||
return loadDexFiles(input, null);
|
||||
}
|
||||
|
||||
public static ILoadResult loadDexFiles(List<Path> inputFiles, Closeable closeable) {
|
||||
List<DexReader> dexReaders = DexFileLoader.collectDexFiles(inputFiles);
|
||||
if (dexReaders.isEmpty()) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
return new DexLoadResult(dexReaders, closeable);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,28 @@
|
||||
package jadx.plugins.input.dex;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.data.IResourceData;
|
||||
|
||||
public class DexLoadResult implements ILoadResult {
|
||||
private final List<DexReader> dexReaders;
|
||||
@Nullable
|
||||
private final Closeable closeable;
|
||||
|
||||
public DexLoadResult(List<DexReader> dexReaders) {
|
||||
this(dexReaders, null);
|
||||
}
|
||||
|
||||
public DexLoadResult(List<DexReader> dexReaders, Closeable closeable) {
|
||||
this.dexReaders = dexReaders;
|
||||
this.closeable = closeable;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -31,5 +41,13 @@ public class DexLoadResult implements ILoadResult {
|
||||
for (DexReader dexReader : dexReaders) {
|
||||
dexReader.close();
|
||||
}
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return dexReaders.isEmpty();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package jadx.plugins.input.javaconvert;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -12,7 +13,7 @@ import java.util.stream.Stream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ConvertResult {
|
||||
public class ConvertResult implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertResult.class);
|
||||
|
||||
private final List<Path> converted = new ArrayList<>();
|
||||
@ -34,7 +35,8 @@ public class ConvertResult {
|
||||
return converted.isEmpty();
|
||||
}
|
||||
|
||||
public void deleteTemp() {
|
||||
@Override
|
||||
public void close() {
|
||||
for (Path tmpPath : tmpPaths) {
|
||||
try {
|
||||
delete(tmpPath);
|
||||
|
@ -1,6 +1,5 @@
|
||||
package jadx.plugins.input.javaconvert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@ -8,9 +7,7 @@ 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.dex.DexFileLoader;
|
||||
import jadx.plugins.input.dex.DexLoadResult;
|
||||
import jadx.plugins.input.dex.DexReader;
|
||||
import jadx.plugins.input.dex.DexInputPlugin;
|
||||
|
||||
public class JavaConvertPlugin implements JadxInputPlugin {
|
||||
|
||||
@ -23,16 +20,9 @@ public class JavaConvertPlugin implements JadxInputPlugin {
|
||||
public ILoadResult loadFiles(List<Path> input) {
|
||||
ConvertResult result = JavaConvertLoader.process(input);
|
||||
if (result.isEmpty()) {
|
||||
result.deleteTemp();
|
||||
result.close();
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
List<DexReader> dexReaders = DexFileLoader.collectDexFiles(result.getConverted());
|
||||
return new DexLoadResult(dexReaders) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
result.deleteTemp();
|
||||
}
|
||||
};
|
||||
return DexInputPlugin.loadDexFiles(result.getConverted(), result);
|
||||
}
|
||||
}
|
||||
|
@ -7,4 +7,6 @@ public interface ILoadResult extends Closeable {
|
||||
void visitClasses(Consumer<IClassData> consumer);
|
||||
|
||||
void visitResources(Consumer<IResourceData> consumer);
|
||||
|
||||
boolean isEmpty();
|
||||
}
|
||||
|
@ -11,6 +11,11 @@ public class EmptyLoadResult implements ILoadResult {
|
||||
|
||||
public static final EmptyLoadResult INSTANCE = new EmptyLoadResult();
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClasses(Consumer<IClassData> consumer) {
|
||||
}
|
||||
|
12
jadx-plugins/jadx-smali-input/build.gradle
Normal file
12
jadx-plugins/jadx-smali-input/build.gradle
Normal file
@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":jadx-plugins:jadx-plugins-api"))
|
||||
|
||||
implementation(project(":jadx-plugins:jadx-dex-input"))
|
||||
|
||||
implementation 'org.smali:smali:2.4.0'
|
||||
implementation 'com.google.guava:guava:29.0-jre' // force latest version for smali
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package jadx.plugins.input.smali;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
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.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jf.smali.Smali;
|
||||
import org.jf.smali.SmaliOptions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SmaliConvert implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SmaliConvert.class);
|
||||
|
||||
@Nullable
|
||||
private Path tmpDex;
|
||||
|
||||
public boolean execute(List<Path> input) {
|
||||
List<Path> smaliFiles = filterSmaliFiles(input);
|
||||
if (smaliFiles.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
this.tmpDex = Files.createTempFile("jadx-", ".dex");
|
||||
boolean result = compileSmali(tmpDex, smaliFiles);
|
||||
if (result) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Smali process error", e);
|
||||
}
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean compileSmali(Path output, List<Path> inputFiles) throws IOException {
|
||||
SmaliOptions options = new SmaliOptions();
|
||||
options.outputDexFile = output.toAbsolutePath().toString();
|
||||
options.verboseErrors = true;
|
||||
|
||||
List<String> inputFileNames = inputFiles.stream()
|
||||
.map(p -> p.toAbsolutePath().toString())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
boolean result = collectSystemErrors(out, () -> Smali.assemble(options, inputFileNames));
|
||||
if (!result) {
|
||||
LOG.error("Smali compilation error:\n{}", out);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean collectSystemErrors(OutputStream out, Callable<Boolean> exec) {
|
||||
PrintStream systemErr = System.err;
|
||||
try (PrintStream err = new PrintStream(out)) {
|
||||
System.setErr(err);
|
||||
try {
|
||||
return exec.call();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(err);
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
System.setErr(systemErr);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Path> filterSmaliFiles(List<Path> input) {
|
||||
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**.smali");
|
||||
return input.stream()
|
||||
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
||||
.filter(matcher::matches)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Path> getDexFiles() {
|
||||
if (tmpDex == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(tmpDex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
if (tmpDex != null) {
|
||||
Files.deleteIfExists(tmpDex);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to remove tmp dex file: {}", tmpDex, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package jadx.plugins.input.smali;
|
||||
|
||||
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.dex.DexInputPlugin;
|
||||
|
||||
public class SmaliInputPlugin implements JadxInputPlugin {
|
||||
|
||||
@Override
|
||||
public JadxPluginInfo getPluginInfo() {
|
||||
return new JadxPluginInfo("smali-input", "SmaliInput", "Load .smali files");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> input) {
|
||||
SmaliConvert convert = new SmaliConvert();
|
||||
if (!convert.execute(input)) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
return DexInputPlugin.loadDexFiles(convert.getDexFiles(), convert);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
jadx.plugins.input.smali.SmaliInputPlugin
|
@ -7,4 +7,5 @@ include 'jadx-samples'
|
||||
include 'jadx-plugins'
|
||||
include 'jadx-plugins:jadx-plugins-api'
|
||||
include 'jadx-plugins:jadx-dex-input'
|
||||
include 'jadx-plugins:jadx-smali-input'
|
||||
include 'jadx-plugins:jadx-java-convert'
|
||||
|
Loading…
x
Reference in New Issue
Block a user