diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index a2d8275f7ff..7829a72c492 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -15,5 +15,7 @@ dependencies { } compile 'com.google.guava:guava:27.1-jre' + compile 'ca.mcgill.sable:soot:3.3.0' + testCompile 'org.apache.commons:commons-lang3:3.8.1' } diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 6707a72ac63..e7574108ead 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -56,6 +56,8 @@ public class JadxArgs { private boolean fsCaseSensitive; + private String sootAndroidJar; + public enum RenameEnum { CASE, VALID, PRINTABLE } @@ -308,6 +310,14 @@ private void updateRenameFlag(boolean enabled, RenameEnum flag) { } } + public String getSootAndroidJar() { + return sootAndroidJar; + } + + public void setSootAndroidJar(String sootAndroidJar) { + this.sootAndroidJar = sootAndroidJar; + } + @Override public String toString() { return "JadxArgs{" + "inputFiles=" + inputFiles diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 7391e7adb18..4daca7e6303 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -1,8 +1,11 @@ package jadx.api; import java.io.File; +import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -25,6 +28,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import soot.Main; +import soot.Printer; +import soot.Scene; +import soot.SootClass; +import soot.options.Options; + import jadx.core.Jadx; import jadx.core.ProcessClass; import jadx.core.dex.attributes.AFlag; @@ -335,6 +344,39 @@ void generateSmali(ClassNode cls) { } } + public void generateJimple(ClassNode cls) { + try { + String jar = args.getSootAndroidJar(); + if (jar == null || jar.trim().length() == 0) { + throw new RuntimeException("soot Android jar not set -> see Preferences"); + } + Path jarPath = Paths.get(jar); + if (!Files.isRegularFile(jarPath)) { + throw new RuntimeException("soot Android jar not accessible: " + jarPath); + } + + Options o = Options.v(); + if (o.process_dir().size() == 0) { + Path path = cls.dex().getDexFile().getPath(); + // Soot not initialized + o.set_process_multiple_dex(true); + o.set_allow_phantom_refs(true); + o.set_process_dir(Collections.singletonList(path.toAbsolutePath().toString())); + o.set_src_prec(Options.src_prec_apk); + o.set_force_android_jar(jarPath.toAbsolutePath().toString()); + Main.v().autoSetOptions(); + Scene.v().loadNecessaryClasses(); + } + SootClass sootClass = Scene.v().getSootClass(cls.getFullName()); + StringWriter sw = new StringWriter(4096); + PrintWriter writerOut = new PrintWriter(sw); + Printer.v().printTo(sootClass, writerOut); + cls.setJimple(sw.toString()); + } catch (Exception e) { + cls.setJimple(e.toString()); + } + } + RootNode getRoot() { return root; } diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index e9387b2fd00..7443c724bf5 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -74,6 +74,16 @@ public synchronized String getSmali() { return cls.getSmali(); } + public String getJimple() { + if (decompiler == null) { + return null; + } + if (cls.getSmali() == null) { + decompiler.generateJimple(cls); + } + return cls.getJimple(); + } + public synchronized void unload() { cls.unload(); } @@ -268,4 +278,5 @@ public int hashCode() { public String toString() { return getFullName(); } + } 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 74542b1e119..037a0679ca2 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 @@ -56,6 +56,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { private CodeWriter code; // store smali private String smali; + + // store Soot Jimple code + private String jimple; + // store parent for inner classes or 'this' otherwise private ClassNode parentClass; @@ -483,6 +487,14 @@ public void setSmali(String smali) { this.smali = smali; } + public String getJimple() { + return jimple; + } + + public void setJimple(String jimple) { + this.jimple = jimple; + } + public String getSmali() { return smali; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 56284b594b3..175928a9c8e 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -60,6 +60,9 @@ public class JadxSettings extends JadxCLIArgs { private Map windowPos = new HashMap<>(); private int mainWindowExtendedState = JFrame.NORMAL; + + private String sootAndroidJar; + /** * UI setting: the width of the tree showing the classes, resources, ... */ @@ -377,6 +380,14 @@ public void setMainWindowExtendedState(int mainWindowExtendedState) { partialSync(settings -> settings.mainWindowExtendedState = mainWindowExtendedState); } + public String getSootAndroidJar() { + return sootAndroidJar; + } + + public void setSootAndroidJar(String sootAndroidJar) { + this.sootAndroidJar = sootAndroidJar; + } + private void upgradeSettings(int fromVersion) { LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION); if (fromVersion == 0) { @@ -430,6 +441,13 @@ private void upgradeSettings(int fromVersion) { sync(); } + @Override + public JadxArgs toJadxArgs() { + JadxArgs jadxArgs = super.toJadxArgs(); + jadxArgs.setSootAndroidJar(this.sootAndroidJar); + return jadxArgs; + } + @Override protected JadxCLIArgs newInstance() { return new JadxSettings(); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 140eedf28e9..f628c154f89 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -8,7 +8,10 @@ import java.util.Collection; import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -293,6 +296,27 @@ private SettingsGroup makeDecompilationGroup() { needReload(); }); + JTextField sootAndroidJar = new JTextField(); + sootAndroidJar.setText(StringUtils.defaultString(settings.getSootAndroidJar())); + sootAndroidJar.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void insertUpdate(DocumentEvent e) { + changedUpdate(e); + } + + @Override + public void removeUpdate(DocumentEvent e) { + changedUpdate(e); + } + + @Override + public void changedUpdate(DocumentEvent e) { + settings.setSootAndroidJar(sootAndroidJar.getText()); + needReload(); + } + }); + SpinnerNumberModel spinnerModel = new SpinnerNumberModel( settings.getThreadsCount(), 1, Runtime.getRuntime().availableProcessors() * 2, 1); JSpinner threadsCount = new JSpinner(spinnerModel); @@ -375,6 +399,7 @@ private SettingsGroup makeDecompilationGroup() { other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive); other.addRow(NLS.str("preferences.fallback"), fallback); other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode); + other.addRow("Soot Android JAR file: ", sootAndroidJar); return other; } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index 269f68b4b96..c369ad839f5 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -86,6 +86,11 @@ public String getSmali() { return cls.getSmali(); } + @Override + public String getJimple() { + return cls.getJimple(); + } + @Override public String getSyntaxName() { return SyntaxConstants.SYNTAX_STYLE_JAVA; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 40593c64ae0..f59cdb86950 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -32,6 +32,10 @@ public String getSmali() { return null; } + public String getJimple() { + return null; + } + public String getSyntaxName() { return SyntaxConstants.SYNTAX_STYLE_NONE; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index b3bc86f0d58..b668b79df71 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -66,6 +66,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import soot.G; + import jadx.api.JadxArgs; import jadx.api.JavaNode; import jadx.api.ResourceFile; @@ -368,6 +370,7 @@ private void update() { } protected void resetCache() { + G.reset(); cacheObject.reset(); // TODO: decompilation freezes sometime with several threads int threadsCount = settings.getThreadsCount(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java index 9e51a71b824..13f595f0530 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java @@ -21,6 +21,8 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel { private final CodePanel javaCodePanel; private final CodePanel smaliCodePanel; + private final CodePanel jimpleCodePanel; + private JTabbedPane areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM); public ClassCodeContentPanel(TabbedPane panel, JNode jnode) { @@ -28,11 +30,13 @@ public ClassCodeContentPanel(TabbedPane panel, JNode jnode) { javaCodePanel = new CodePanel(this, new CodeArea(this)); smaliCodePanel = new CodePanel(this, new SmaliArea(this)); + jimpleCodePanel = new CodePanel(this, new JimpleArea(this)); setLayout(new BorderLayout()); areaTabbedPane.add(javaCodePanel, NLS.str("tabs.code")); areaTabbedPane.add(smaliCodePanel, NLS.str("tabs.smali")); + areaTabbedPane.add(jimpleCodePanel, "Jimple"); add(areaTabbedPane); javaCodePanel.load(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/JimpleArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JimpleArea.java new file mode 100644 index 00000000000..e2f06d91447 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JimpleArea.java @@ -0,0 +1,20 @@ +package jadx.gui.ui.codearea; + +import jadx.gui.ui.ContentPanel; + +public final class JimpleArea extends AbstractCodeArea { + private static final long serialVersionUID = -747171470554800028L; + + JimpleArea(ContentPanel contentPanel) { + super(contentPanel); + setEditable(false); + } + + @Override + public void load() { + if (getText().isEmpty()) { + setText(node.getJimple()); + setCaretPosition(0); + } + } +}