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 49f8f8e47..12998f847 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -143,6 +143,7 @@ public class MainWindow extends JFrame { private static final ImageIcon ICON_COMMENT_SEARCH = UiUtils.openIcon("table_edit"); private static final ImageIcon ICON_BACK = UiUtils.openIcon("icon_back"); private static final ImageIcon ICON_FORWARD = UiUtils.openIcon("icon_forward"); + private static final ImageIcon ICON_QUARK = UiUtils.openIcon("icon_quark"); private static final ImageIcon ICON_PREF = UiUtils.openIcon("wrench"); private static final ImageIcon ICON_DEOBF = UiUtils.openIcon("lock_edit"); private static final ImageIcon ICON_LOG = UiUtils.openIcon("report"); @@ -662,6 +663,8 @@ public class MainWindow extends JFrame { } } else if (obj instanceof ApkSignature) { tabbedPane.showSimpleNode((JNode) obj); + } else if (obj instanceof QuarkReport) { + tabbedPane.showSimpleNode((JNode) obj); } else if (obj instanceof JNode) { tabbedPane.codeJump(new JumpPosition((JNode) obj)); } @@ -914,6 +917,14 @@ public class MainWindow extends JFrame { forwardAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("nav.forward")); forwardAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK)); + Action quarkAction = new AbstractAction("Quark Engine", ICON_QUARK) { + @Override + public void actionPerformed(ActionEvent e) { + new QuarkDialog(MainWindow.this).setVisible(true); + } + }; + quarkAction.putValue(Action.SHORT_DESCRIPTION, "Quark Engine"); + JMenu file = new JMenu(NLS.str("menu.file")); file.setMnemonic(KeyEvent.VK_F); file.add(openAction); @@ -998,6 +1009,8 @@ public class MainWindow extends JFrame { toolbar.addSeparator(); toolbar.add(prefsAction); toolbar.addSeparator(); + toolbar.add(quarkAction); + toolbar.addSeparator(); toolbar.add(Box.createHorizontalGlue()); toolbar.add(updateLink); @@ -1249,6 +1262,10 @@ public class MainWindow extends JFrame { return progressPane; } + public JRoot getTreeRoot() { + return treeRoot; + } + private class RecentProjectsMenuListener implements MenuListener { private final JMenu menu; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java new file mode 100644 index 000000000..c3e41f55f --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java @@ -0,0 +1,232 @@ +package jadx.gui.ui; + +import java.awt.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.swing.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonIOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; + +import jadx.gui.settings.JadxSettings; +import jadx.gui.treemodel.JRoot; +import jadx.gui.utils.NLS; +import jadx.gui.utils.logs.LogCollector; + +class QuarkDialog extends JDialog { + + private static final long serialVersionUID = 4855753773520368215L; + + private static final Logger LOG = LoggerFactory.getLogger(QuarkDialog.class); + + private File quarkReportFile; + + private final transient JadxSettings settings; + private final transient MainWindow mainWindow; + private JProgressBar progressBar; + private JPanel progressPane; + + private JComboBox selectFile; + + private final List files; + private ArrayList analyzeFile = new ArrayList(); + + public QuarkDialog(MainWindow mainWindow) { + + this.mainWindow = mainWindow; + this.settings = mainWindow.getSettings(); + this.files = mainWindow.getWrapper().getOpenPaths(); + + if (!prepareAnalysis()) { + // The files are unable to analysis by Quark + return; + } + initUI(); + settings.loadWindowPos(this); + } + + private boolean prepareAnalysis() { + + String[] exts = new String[] { "apk", "dex" }; + + if (this.files.size() != 1) { + for (Path filePath : this.files) { + String fileName = filePath.toString(); + int dotIndex = fileName.lastIndexOf('.'); + String extension = (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); + + if (!Arrays.stream(exts).anyMatch(extension::equals)) { + LOG.warn("Quark: Current file can't be analysis ", fileName); + continue; + } + analyzeFile.add(filePath); + } + return true; + } + String fileName = this.files.get(0).toString(); + int dotIndex = fileName.lastIndexOf('.'); + String extension = (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); + if (!Arrays.stream(exts).anyMatch(extension::equals)) { + LOG.warn("Quark: Current file can't be analysis ", fileName); + return false; + } + analyzeFile.add(this.files.get(0)); + return true; + } + + private String[] filesToStringArr() { + String[] arr = new String[files.size()]; + int index = 0; + for (Path file : analyzeFile) { + arr[index] = file.getFileName().toString(); + index++; + } + return arr; + } + + public final void initUI() { + + JLabel description = new JLabel("Analyzing apk using Quark-Engine"); + JLabel selectApkText = new JLabel("Select Apk/Dex"); + description.setAlignmentX(0.5f); + + selectFile = new JComboBox(filesToStringArr()); + + JPanel textPane = new JPanel(); + + textPane.add(description); + + JPanel selectApkPanel = new JPanel(); + selectApkPanel.add(selectApkText); + selectApkPanel.add(selectFile); + + progressPane = new JPanel(); + progressPane.setVisible(false); + progressPane.setSize(150, 10); + + progressBar = new JProgressBar(0, 100); + progressBar.setSize(150, 10); + progressBar.setIndeterminate(true); + progressBar.setStringPainted(false); + progressPane.add(progressBar); + + JPanel buttonPane = new JPanel(); + JButton start = new JButton("Start"); + JButton close = new JButton(NLS.str("tabs.close")); + close.addActionListener(event -> close()); + start.addActionListener(event -> analyzeAPK()); + buttonPane.add(start); + buttonPane.add(close); + getRootPane().setDefaultButton(close); + + JPanel centerPane = new JPanel(); + centerPane.add(selectApkPanel); + centerPane.add(progressPane); + Container contentPane = getContentPane(); + + contentPane.add(textPane, BorderLayout.PAGE_START); + contentPane.add(centerPane); + contentPane.add(buttonPane, BorderLayout.PAGE_END); + + setTitle("Quark Engine"); + pack(); + setSize(200, 125); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setModalityType(ModalityType.MODELESS); + setLocationRelativeTo(null); + } + + private void analyzeAPK() { + LoadTask task = new LoadTask(); + task.execute(); + } + + private void loadReportFile() { + try { + JsonObject quarkReport = (JsonObject) JsonParser.parseReader(new FileReader(quarkReportFile.getAbsolutePath().toString())); + + JRoot root = mainWindow.getCacheObject().getJRoot(); + + QuarkReport quarkNode = QuarkReport.analysisAPK(quarkReport); + + root.update(); + root.add(quarkNode); + + mainWindow.reloadTree(); + + } catch (JsonIOException | JsonSyntaxException | FileNotFoundException e) { + LOG.error("Quark: Load report failed: ", e); + } + + } + + private void close() { + dispose(); + } + + @Override + public void dispose() { + LogCollector.getInstance().resetListener(); + settings.saveWindowPos(this); + super.dispose(); + } + + private class LoadTask extends SwingWorker { + public LoadTask() { + progressPane.setVisible(true); + } + + @Override + public Void doInBackground() { + try { + + quarkReportFile = File.createTempFile("QuarkReport-", ".json"); + + String outputPath = quarkReportFile.getAbsolutePath().toString(); + String apkName = selectFile.getSelectedItem().toString(); + String apkPath = null; + for (Path path : files) { + if (path.getFileName().toString().equals(apkName)) { + apkPath = path.toString(); + } + } + String cmd = "quark -a " + apkPath + " -s -o " + outputPath; + Runtime run = Runtime.getRuntime(); + Process process = run.exec(cmd); + + BufferedReader buf = new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = ""; + LOG.debug("Quark analyzing..."); + while ((output = buf.readLine()) != null) { + LOG.debug(output); + } + + } catch (IOException e) { + LOG.error("Quark failed: ", e); + dispose(); + } + return null; + } + + @Override + public void done() { + loadReportFile(); + dispose(); + } + } + +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java b/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java new file mode 100644 index 000000000..23e196cd6 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java @@ -0,0 +1,105 @@ +package jadx.gui.ui; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JNode; +import jadx.gui.utils.UiUtils; + +public class QuarkReport extends JNode { + + private static final long serialVersionUID = -766800957202637021L; + + private static final Logger LOG = LoggerFactory.getLogger(QuarkReport.class); + + private static final ImageIcon REPORT_ICON = UiUtils.openIcon("report"); + + private String content; + private String apkFileName; + + private JsonObject reportData; + + public static QuarkReport analysisAPK(JsonObject data) { + return new QuarkReport(data); + } + + public QuarkReport(JsonObject data) { + this.reportData = data; + this.apkFileName = data.get("apk_filename").getAsString(); + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return REPORT_ICON; + } + + @Override + public String makeString() { + return "Quark analysis report"; + } + + @Override + public String getContent() { + if (content != null) { + return this.content; + } + try { + + JsonArray crimes = (JsonArray) this.reportData.get("crimes"); + + StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); + + builder.append("

Quark Analysis Report

"); + builder.append("

"); + builder.append("File name: "); + builder.append(apkFileName); + builder.append("

"); + builder.append(""); + builder.append(""); + builder.append(""); + builder.append(""); + + for (Object obj : crimes) { + JsonObject crime = (JsonObject) obj; + String crimeDes = crime.get("crime").getAsString(); + String confidence = crime.get("confidence").getAsString(); + + builder.append(""); + } + + builder.append("
Potential Malicious ActivitiesConfidence
"); + builder.append(crimeDes); + builder.append(""); + builder.append(confidence); + builder.append("
"); + this.content = builder.toString(); + + } catch (Exception e) { + LOG.error(e.getMessage(), e); + StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); + builder.append("

"); + builder.escape("Quark analysis failed!"); + builder.append("

");
+			builder.escape(ExceptionUtils.getStackTrace(e));
+			builder.append("
"); + return builder.toString(); + } + + return this.content; + } + +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index ae5e0452d..18104e516 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -305,6 +305,9 @@ public class TabbedPane extends JTabbedPane { if (node instanceof ApkSignature) { return new HtmlPanel(this, node); } + if (node instanceof QuarkReport) { + return new HtmlPanel(this, node); + } return new ClassCodeContentPanel(this, node); } diff --git a/jadx-gui/src/main/resources/icons-16/icon_quark.png b/jadx-gui/src/main/resources/icons-16/icon_quark.png new file mode 100644 index 000000000..1524100e2 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/icon_quark.png differ